Repository: pyscf/pyscf — pull request #2062 (python)
Title: Check cylindrical symmetry of the orbitals for FCI solver (issue #2022)


Resolved issues:
title: CASSCF with symmetry for B-H throws an error
body: Hi,

Calculating the CAS(2,2) energy of B-H with `symmetry=True` throws an error. For Li-H or for `symmetry=False` or for CAS(3,2) there is no error. Issue #1548 seemed to be related. Also, I am running PySCF 2.4.0.

Could someone help figure this out? Thanks!

```
from pyscf import gto, scf, mcscf

### CAS(3,2) runs fine
mol1 = gto.M(atom="B 0 0 0; H 1.5 0 0", basis="sto3g", symmetry=True)
mc1 = mcscf.CASSCF(scf.RHF(mol1).run(), 3, 2)
mc1.kernel()

### Li-H runs fine
mol2 = gto.M(atom="Li 0 0 0; H 1.5 0 0", basis="sto3g", symmetry=True)
mc2 = mcscf.CASSCF(scf.RHF(mol2).run(), 2, 2)
mc2.kernel()

### symmetry=False runs fine
mol3 = gto.M(atom="B 0 0 0; H 1.5 0 0", basis="sto3g", symmetry=False)
mc3 = mcscf.CASSCF(scf.RHF(mol3).run(), 2, 2)
mc3.kernel()

### throws error
mol4 = gto.M(atom="B 0 0 0; H 1.5 0 0", basis="sto3g", symmetry=True)
mc4 = mcscf.CASSCF(scf.RHF(mol4).run(), 2, 2)
mc4.kernel()
```

the output is

```
converged SCF energy = -24.7216853309577
/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det
  r = _umath_linalg.det(a, signature=signature)
/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det
  r = _umath_linalg.det(a, signature=signature)
CASSCF energy = -24.7615485480989
CASCI E = -24.7615485480989  E(CI) = -1.32807642898465  S^2 = 0.0000000
converged SCF energy = -7.86335762153512
CASSCF energy = -7.88099257585260
CASCI E = -7.88099257585260  E(CI) = -1.09902483008292  S^2 = 0.0000000
converged SCF energy = -24.7216853309577
CASSCF energy = -24.7435717749394
CASCI E = -24.7435717749394  E(CI) = -1.29911474076125  S^2 = 0.0000000
converged SCF energy = -24.7216853309577
Traceback (most recent call last):
  File "/Users/vrn738/PycharmProjects/mrpt2/molecules/BH/casscf_symmetry_issue.py", line 20, in <module>
    mc4.kernel()
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/mcscf/mc1step_symm.py", line 73, in kernel
    _kern(self, mo_coeff,
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/mcscf/mc1step.py", line 351, in kernel
    e_tot, e_cas, fcivec = casscf.casci(mo, ci0, eris, log, locals())
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/mcscf/mc1step.py", line 878, in casci
    e_tot, e_cas, fcivec = casci.kernel(fcasci, mo_coeff, ci0, log,
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/mcscf/casci.py", line 608, in kernel
    e_tot, fcivec = casci.fcisolver.kernel(h1eff, eri_cas, ncas, nelecas,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/fci/direct_spin1_symm.py", line 781, in kernel
    ci0 = self.get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym_ir)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/fci/direct_spin1_symm.py", line 724, in get_init_guess
    ci0 = get_init_guess_cyl_sym(
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/fci/direct_spin1_symm.py", line 291, in get_init_guess_cyl_sym
    ca = _cyl_sym_csf2civec(strsa, addra, orbsym, degen_mapping)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/fci/direct_spin1_symm.py", line 348, in _cyl_sym_csf2civec
    u = _cyl_sym_orbital_rotation(orbsym, degen_mapping)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/vrn738/PycharmProjects/mrpt2/venv/lib/python3.11/site-packages/pyscf/fci/direct_spin1_symm.py", line 369, in _cyl_sym_orbital_rotation
    u[j,j] = sqrthi
    ~^^^^^
IndexError: index 2 is out of bounds for axis 0 with size 2
```

Hints:

Fix patch:
diff --git a/pyscf/fci/direct_spin1_cyl_sym.py b/pyscf/fci/direct_spin1_cyl_sym.py
index 0056bbd992..80aa27192f 100644
--- a/pyscf/fci/direct_spin1_cyl_sym.py
+++ b/pyscf/fci/direct_spin1_cyl_sym.py
@@ -42,9 +42,9 @@
 from pyscf.fci import cistring
 from pyscf.fci import direct_spin1
 from pyscf.fci import direct_spin1_symm
-from pyscf.fci.direct_spin1_symm import (_sv_associated_det,
-                                         _strs_angular_momentum,
-                                         _cyl_sym_orbital_rotation)
+from pyscf.fci.direct_spin1_symm import (
+    _sv_associated_det, _strs_angular_momentum, _cyl_sym_orbital_rotation,
+    _validate_degen_mapping)
 from pyscf.fci import direct_nosym
 from pyscf.fci import addons
 from pyscf import __config__
@@ -558,6 +558,10 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         if not hasattr(orbsym, 'degen_mapping'):
             degen_mapping = map_degeneracy(h1e.diagonal(), orbsym)
             orbsym = lib.tag_array(orbsym, degen_mapping=degen_mapping)
+        if not _validate_degen_mapping(orbsym.degen_mapping, norb):
+            raise lib.exceptions.PointGroupSymmetryError(
+                'Incomplete 2D-irrep orbitals for cylindrical symmetry.\n'
+                f'orbsym = {orbsym}.')
 
         u = _cyl_sym_orbital_rotation(orbsym, orbsym.degen_mapping)
         h1e = u.dot(h1e).dot(u.conj().T)
diff --git a/pyscf/fci/direct_spin1_symm.py b/pyscf/fci/direct_spin1_symm.py
index 272a432ec6..017505836a 100644
--- a/pyscf/fci/direct_spin1_symm.py
+++ b/pyscf/fci/direct_spin1_symm.py
@@ -257,6 +257,13 @@ def get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
         raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
     return ci0
 
+def _validate_degen_mapping(mapping, norb):
+    '''Check if 2D irreps are properly paired'''
+    mapping = np.asarray(mapping)
+    return (mapping.max() < norb and
+            # Must be self-conjugated
+            numpy.array_equal(mapping[mapping], numpy.arange(norb)))
+
 def get_init_guess_cyl_sym(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
     neleca, nelecb = _unpack_nelec(nelec)
     strsa = strsb = cistring.gen_strings4orblist(range(norb), neleca)
@@ -751,6 +758,11 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
                 orbsym = lib.tag_array(orbsym, degen_mapping=degen_mapping)
             if davidson_only is None:
                 davidson_only = True
+            if not _validate_degen_mapping(orbsym.degen_mapping, norb):
+                raise lib.exceptions.PointGroupSymmetryError(
+                    'Incomplete 2D-irrep orbitals for cylindrical symmetry.\n'
+                    f'orbsym = {orbsym}. '
+                    f'Retry {self.__class__} with D2h subgroup symmetry.')
 
         wfnsym_ir = self.guess_wfnsym(norb, nelec, ci0, orbsym, wfnsym, **kwargs)
         self.sym_allowed_idx = sym_allowed_indices(nelec, orbsym, wfnsym_ir)


Test patch:
diff --git a/pyscf/fci/test/test_spin1_cyl_sym.py b/pyscf/fci/test/test_spin1_cyl_sym.py
index 84e014d690..0648528e31 100644
--- a/pyscf/fci/test/test_spin1_cyl_sym.py
+++ b/pyscf/fci/test/test_spin1_cyl_sym.py
@@ -212,6 +212,15 @@ def test_linearmole_a2(self):
         mc.run()
         self.assertAlmostEqual(mc.e_tot, 2.8999951068356475, 8)
 
+    def test_incomplete_orbsym(self):
+        sol = direct_spin1_cyl_sym.FCI(gto.Mole())
+        no, ne = 2, 2
+        h1 = np.ones((no,no))
+        h2 = np.ones((no,no,no,no))
+        orbsym = lib.tag_array(np.array([0,3]), degen_mapping=[0,2])
+        with self.assertRaises(lib.exceptions.PointGroupSymmetryError):
+            sol.kernel(h1, h2, no, ne, orbsym=orbsym)
+
 if __name__ == "__main__":
     print("Full Tests for spin1-symm")
     unittest.main()
diff --git a/pyscf/fci/test/test_spin1_symm.py b/pyscf/fci/test/test_spin1_symm.py
index 462009e3dd..4b63d48d27 100644
--- a/pyscf/fci/test/test_spin1_symm.py
+++ b/pyscf/fci/test/test_spin1_symm.py
@@ -15,7 +15,7 @@
 
 import unittest
 import numpy
-from pyscf import gto
+from pyscf import gto, lib
 from pyscf import scf
 from pyscf import ao2mo
 from pyscf import fci
@@ -197,6 +197,17 @@ def test_linearmole(self):
         ci1 = fci.addons.transform_ci(ci_y, (3,3), u.T)
         self.assertAlmostEqual(abs(ci1.ravel().dot(ci_y.ravel())), 1, 9)
 
+    def test_incomplete_orbsym(self):
+        mol = gto.Mole()
+        mol.groupname = 'Dooh'
+        sol = direct_spin1_symm.FCI(mol)
+        no, ne = 2, 2
+        h1 = numpy.ones((no,no))
+        h2 = numpy.ones((no,no,no,no))
+        orbsym = lib.tag_array(numpy.array([0,3]), degen_mapping=[0,2])
+        with self.assertRaises(lib.exceptions.PointGroupSymmetryError):
+            sol.kernel(h1, h2, no, ne, orbsym=orbsym)
+
 if __name__ == "__main__":
     print("Full Tests for spin1-symm")
     unittest.main()