Task: FCI wavefunction symmetry (pyscf/pyscf#1529)

org: pyscf
repo: pyscf
pr_number: 1529
issue_title: FCI wavefunction symmetry
issue_body: * Support Dinfh and Cinv symmetry in FCI solver
* Fix issue #1517
* Adjust FCI initial guess
resolved_issues: number: 1517
title: Problems with FCI in Dooh symmetry
body: I am trying to run some FCI calculations in Dooh symmetry. The following input
```
import numpy
from pyscf import gto, scf, fci

mol = gto.Mole()
# Singlet bond length 2.913
mol.build(
    atom = 'Li 0 0 0; Li 0 0 2.913',
    unit = 'angstrom',
    basis = 'cc-pVDZ',
    spin = 0,
    verbose = 4,
    symmetry = True,
    symmetry_subgroup = 'Dooh'
)
mf = scf.RHF(mol)
mf.kernel()

ci_energies = {}
for sym in ['A1g', 'A1u', 'E1ux', 'E1uy', 'E1gx', 'E1gy', 'E2gx', 'E2gy', 'E2ux', 'E2uy', 'E3ux', 'E3uy', 'E3gx', 'E3gy']:
    mci = fci.FCI(mol, mf.mo_coeff, singlet=False)
    mci.wfnsym = sym
    e, civec = mci.kernel(nroots=3)
    ci_energies[sym] = e
    print('{}: {}'.format(sym,e))
```
gives the following energies
```
A1g: [-14.90048097 -14.82656573 -14.80619407]
A1u: [-14.86012998 -14.83774646 -14.75794947]
E1ux: [-14.84879138 -14.80549729 -14.74183726]
E1uy: [-14.84879138 -14.80549729 -14.74183726]
E1gx: [-14.79618456 -14.78343722 -14.75798428]
E1gy: [-14.79618456 -14.78343722 -14.75798428]
E2gx: [-14.90048097 -14.82656573 -14.80619407]
E2gy: [-14.7461423  -14.74326066 -14.64015681]
E2ux: [-14.86012998 -14.83774646 -14.75794947]
E2uy: [-14.705979   -14.7043607  -14.66552235]
E3ux: [-14.84879138 -14.80549729 -14.74183726]
E3uy: [-14.84879138 -14.80549729 -14.74183726]
E3gx: [-14.79618456 -14.78343722 -14.75798428]
E3gy: [-14.79618456 -14.78343722 -14.75798428]
```
As you can see, there is a big problem in the E2 energies: 

- the x and y data are different
- `E2gx` results in the same energies as `A1g`
- `E2ux` results in the same energies as `A1u`
- the lowest Phi state `E3ux` is lower in energy than the Delta state `E2gy`

Running with `verbose=10` shows `Guessing CI wfn symmetry = 0` for the `E2gx` calculation (same as for the `A1g` calculation), while `E2ux` and `A1u` both show `Guessing CI wfn symmetry = 5`, so this could be just a problem with a wrong initial guess

### Minimal API referenced by tests (typed signatures)
_Added only when tests call brand‑new symbols introduced by the fix; types reflect annotations present in the repo or `Unknown` if not annotated._

- Function in `pyscf/fci/direct_spin1_cyl_sym.py`: `def guess_wfnsym(solver, norb, nelec, fcivec, orbsym, wfnsym, kwargs) -> int: ...`
  **Inputs:** solver: fci.direct_spin1.FCISolver, norb: int, nelec: Union[int, tuple], fcivec: numpy.ndarray, orbsym: Unknown, wfnsym: Unknown, kwargs: dict
  **Output:** int

- Function in `pyscf/fci/direct_spin1_symm.py`: `def _cyl_sym_csf2civec(strs, addr, orbsym, degen_mapping) -> numpy.ndarray: ...`
  **Inputs:** strs: numpy.ndarray, addr: numpy.ndarray, orbsym: Unknown, degen_mapping: numpy.ndarray
  **Output:** numpy.ndarray

- Function in `pyscf/fci/direct_spin1_symm.py`: `def _cyl_sym_orbital_rotation(orbsym, degen_mapping) -> numpy.ndarray: ...`
  **Inputs:** orbsym: Unknown, degen_mapping: numpy.ndarray
  **Output:** numpy.ndarray

- Function in `pyscf/fci/direct_spin1_symm.py`: `def guess_wfnsym(solver, norb, nelec, fcivec, orbsym, wfnsym, kwargs) -> int: ...`
  **Inputs:** solver: fci.direct_spin1.FCISolver, norb: int, nelec: Union[int, tuple], fcivec: numpy.ndarray, orbsym: Unknown, wfnsym: Unknown, kwargs: dict
  **Output:** int

- Function in `pyscf/gto/mole.py`: `def ao_rotation_matrix(mol, orientation) -> numpy.ndarray: ...`
  **Inputs:** mol: gto.Mole, orientation: numpy.ndarray
  **Output:** numpy.ndarray

```python
def guess_wfnsym(solver, norb, nelec, fcivec, orbsym, wfnsym, kwargs) -> int: ...
def _cyl_sym_csf2civec(strs, addr, orbsym, degen_mapping) -> numpy.ndarray: ...
def _cyl_sym_orbital_rotation(orbsym, degen_mapping) -> numpy.ndarray: ...
def guess_wfnsym(solver, norb, nelec, fcivec, orbsym, wfnsym, kwargs) -> int: ...
def ao_rotation_matrix(mol, orientation) -> numpy.ndarray: ...
```

Reference solution:
fix_patch: diff --git a/pyscf/fci/addons.py b/pyscf/fci/addons.py
index d9b470b4c8..acd4fbe7c7 100644
--- a/pyscf/fci/addons.py
+++ b/pyscf/fci/addons.py
@@ -417,8 +417,9 @@ def guess_wfnsym(ci, norb, nelec, orbsym):
         Irrep ID
     '''
     neleca, nelecb = _unpack_nelec(nelec)
-    strsa = numpy.asarray(cistring.make_strings(range(norb), neleca))
-    strsb = numpy.asarray(cistring.make_strings(range(norb), nelecb))
+    strsa = strsb = numpy.asarray(cistring.make_strings(range(norb), neleca))
+    if neleca != nelecb:
+        strsb = numpy.asarray(cistring.make_strings(range(norb), nelecb))
     if isinstance(ci, numpy.ndarray) and ci.ndim <= 2:
         wfnsym = _guess_wfnsym(ci, strsa, strsb, orbsym)
     else:
@@ -781,7 +782,7 @@ def transform_ci(ci, nelec, u):
     if neleca == 0:
         trans_ci_a = numpy.ones((1, 1))
     else:
-        trans_ci_a = numpy.zeros((na_old, na_new))
+        trans_ci_a = numpy.zeros((na_old, na_new), dtype=ua.dtype)
         strs_old = numpy.asarray(cistring.make_strings(range(norb_old), neleca))
 
         # Unitary transformation array trans_ci is the overlap between two sets of CI basis.
@@ -808,7 +809,7 @@ def transform_ci(ci, nelec, u):
     elif nelecb == 0:
         trans_ci_b = numpy.ones((1, 1))
     else:
-        trans_ci_b = numpy.zeros((nb_old, nb_new))
+        trans_ci_b = numpy.zeros((nb_old, nb_new), dtype=ub.dtype)
         strs_old = numpy.asarray(cistring.make_strings(range(norb_old), nelecb))
 
         occ_masks_old = (strs_old[:,None] & one_particle_strs_old) != 0
diff --git a/pyscf/fci/cistring.py b/pyscf/fci/cistring.py
index 0a8b5d1120..62fc928e93 100644
--- a/pyscf/fci/cistring.py
+++ b/pyscf/fci/cistring.py
@@ -98,6 +98,7 @@ def _strs2occslst(strs, norb):
     occslst = numpy.where(occ_masks)[1].reshape(na,-1)
     return numpy.asarray(occslst, dtype=numpy.int32).view(OIndexList)
 def _occslst2strs(occslst):
+    occslst = numpy.asarray(occslst)
     na, nelec = occslst.shape
     strs = numpy.zeros(na, dtype=numpy.int64)
     for i in range(nelec):
diff --git a/pyscf/fci/direct_nosym.py b/pyscf/fci/direct_nosym.py
index 428b366e6e..b9c597ae7b 100644
--- a/pyscf/fci/direct_nosym.py
+++ b/pyscf/fci/direct_nosym.py
@@ -133,6 +133,8 @@ def energy(h1e, eri, fcivec, norb, nelec, link_index=None):
     ci1 = contract_2e(h2e, fcivec, norb, nelec, link_index)
     return numpy.dot(fcivec.reshape(-1), ci1.reshape(-1))
 
+make_hdiag = direct_spin1.make_hdiag
+
 
 class FCISolver(direct_spin1.FCISolver):
     def __init__(self, *args, **kwargs):
diff --git a/pyscf/fci/direct_spin0_symm.py b/pyscf/fci/direct_spin0_symm.py
index af670570ca..fc6be225f4 100644
--- a/pyscf/fci/direct_spin0_symm.py
+++ b/pyscf/fci/direct_spin0_symm.py
@@ -38,6 +38,7 @@
 from pyscf import ao2mo
 from pyscf.lib import logger
 from pyscf import symm
+from pyscf.scf.hf_symm import map_degeneracy
 from pyscf.fci import cistring
 from pyscf.fci import direct_spin0
 from pyscf.fci import direct_spin1
@@ -88,22 +89,23 @@ def contract_2e(eri, fcivec, norb, nelec, link_index=None, orbsym=None, wfnsym=0
 
     ci0 = []
     ci1 = []
+    wfnsym_in_d2h = wfnsym % 10
     for ir in range(TOTIRREPS):
-        ma, mb = aidx[ir].size, aidx[wfnsym ^ ir].size
+        ma, mb = aidx[ir].size, aidx[wfnsym_in_d2h ^ ir].size
         ci0.append(numpy.zeros((ma,mb)))
         ci1.append(numpy.zeros((ma,mb)))
         if ma > 0 and mb > 0:
-            lib.take_2d(fcivec, aidx[ir], aidx[wfnsym ^ ir], out=ci0[ir])
+            lib.take_2d(fcivec, aidx[ir], aidx[wfnsym_in_d2h ^ ir], out=ci0[ir])
     ci0_ptrs = Tirrep(*[x.ctypes.data_as(ctypes.c_void_p) for x in ci0])
     ci1_ptrs = Tirrep(*[x.ctypes.data_as(ctypes.c_void_p) for x in ci1])
     libfci.FCIcontract_2e_symm1(eri_ptrs, ci0_ptrs, ci1_ptrs,
                                 ctypes.c_int(norb), nas, nas,
                                 ctypes.c_int(nlinka), ctypes.c_int(nlinka),
                                 linka_ptr, linka_ptr, dimirrep,
-                                ctypes.c_int(wfnsym))
+                                ctypes.c_int(wfnsym_in_d2h))
     for ir in range(TOTIRREPS):
         if ci0[ir].size > 0:
-            lib.takebak_2d(ci1new, ci1[ir], aidx[ir], aidx[wfnsym ^ ir])
+            lib.takebak_2d(ci1new, ci1[ir], aidx[ir], aidx[wfnsym_in_d2h ^ ir])
     ci1 = lib.transpose_sum(ci1new, inplace=True).reshape(fcivec_shape)
     return ci1.view(direct_spin1.FCIvector)
 
@@ -161,33 +163,77 @@ def get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
     strsa = cistring.gen_strings4orblist(range(norb), neleca)
     airreps = direct_spin1_symm._gen_strs_irrep(strsa, orbsym)
     na = nb = len(airreps)
+    hdiag = hdiag.reshape(na,nb)
 
-    init_strs = []
-    iroot = 0
-    for addr in numpy.argsort(hdiag):
-        addra = addr // nb
-        addrb = addr % nb
-        if airreps[addra] ^ airreps[addrb] == wfnsym:
-            if (addrb,addra) not in init_strs:
-                init_strs.append((addra,addrb))
-                iroot += 1
-                if iroot >= nroots:
-                    break
     ci0 = []
-    for addra,addrb in init_strs:
-        x = numpy.zeros((na,nb))
-        if addra == addrb == 0:
+    iroot = 0
+    sym_allowed = (airreps[:,None] ^ airreps) == wfnsym
+    idx = numpy.arange(na)
+    sym_allowed[idx[:,None] < idx] = False
+    idx_a, idx_b = numpy.where(sym_allowed)
+    for k in hdiag[idx_a,idx_b].argsort():
+        addra, addrb = idx_a[k], idx_b[k]
+        x = numpy.zeros((na, nb))
+        if addra == addrb:
             x[addra,addrb] = 1
         else:
             x[addra,addrb] = x[addrb,addra] = numpy.sqrt(.5)
         ci0.append(x.ravel().view(direct_spin1.FCIvector))
+        iroot += 1
+        if iroot >= nroots:
+            break
 
-    # Add noise
-    #ci0[0][0 ] += 1e-5
-    #ci0[0][-1] -= 1e-5
     if len(ci0) == 0:
-        raise RuntimeError('No determinant matches the target symmetry %s' %
-                           wfnsym)
+        raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
+    return ci0
+
+def get_init_guess_cyl_sym(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
+    neleca, nelecb = direct_spin1._unpack_nelec(nelec)
+    strsa = cistring.gen_strings4orblist(range(norb), neleca)
+    airreps_d2h = direct_spin1_symm._gen_strs_irrep(strsa, orbsym)
+    a_ls = direct_spin1_symm._strs_angular_momentum(strsa, orbsym)
+
+    wfnsym_in_d2h = wfnsym % 10
+    wfn_momentum = symm.basis.linearmole_irrep2momentum(wfnsym)
+    na = nb = len(strsa)
+    hdiag = hdiag.reshape(na,nb)
+    degen = orbsym.degen_mapping
+    ci0 = []
+    iroot = 0
+    wfn_ungerade = wfnsym_in_d2h >= 4
+    a_ungerade = airreps_d2h >= 4
+    sym_allowed = a_ungerade[:,None] == a_ungerade ^ wfn_ungerade
+    # total angular momentum == wfn_momentum
+    sym_allowed &= a_ls[:,None] == wfn_momentum - a_ls
+    idx = numpy.arange(na)
+    sym_allowed[idx[:,None] < idx] = False
+
+    idx_a, idx_b = numpy.where(sym_allowed)
+    for k in hdiag[idx_a,idx_b].argsort():
+        addra, addrb = idx_a[k], idx_b[k]
+        ca = direct_spin1_symm._cyl_sym_csf2civec(strsa, addra, orbsym, degen)
+        cb = direct_spin1_symm._cyl_sym_csf2civec(strsa, addrb, orbsym, degen)
+        if wfn_momentum > 0 or wfnsym in (0, 5):
+            x = ca.real[:,None] * cb.real
+            x-= ca.imag[:,None] * cb.imag
+        else:
+            x = ca.imag[:,None] * cb.real
+            x+= ca.real[:,None] * cb.imag
+        if addra == addrb:
+            norm = numpy.linalg.norm(x)
+        else:
+            x = x + x.T
+            norm = numpy.linalg.norm(x)
+            if norm < 1e-7:
+                continue
+        x *= 1./norm
+        ci0.append(x.ravel().view(direct_spin1.FCIvector))
+        iroot += 1
+        if iroot >= nroots:
+            break
+
+    if len(ci0) == 0:
+        raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
     return ci0
 
 
@@ -209,8 +255,9 @@ def dump_flags(self, verbose=None):
         if isinstance(self.wfnsym, str):
             log.info('specified CI wfn symmetry = %s', self.wfnsym)
         elif isinstance(self.wfnsym, (int, numpy.number)):
+            groupname = getattr(self.mol, 'groupname', None)
             log.info('specified CI wfn symmetry = %s',
-                     symm.irrep_id2name(self.mol.groupname, self.wfnsym))
+                     symm.irrep_id2name(groupname, self.wfnsym))
 
     def absorb_h1e(self, h1e, eri, norb, nelec, fac=1):
         return direct_spin1.absorb_h1e(h1e, eri, norb, nelec, fac)
@@ -235,22 +282,13 @@ def contract_2e(self, eri, fcivec, norb, nelec, link_index=None,
     def get_init_guess(self, norb, nelec, nroots, hdiag):
         wfnsym = direct_spin1_symm._id_wfnsym(self, norb, nelec, self.orbsym,
                                               self.wfnsym)
-        return get_init_guess(norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
-
-    def guess_wfnsym(self, norb, nelec, fcivec=None, orbsym=None, wfnsym=None,
-                     **kwargs):
-        if orbsym is None:
-            orbsym = self.orbsym
-        if fcivec is None:
-            wfnsym = direct_spin1_symm._id_wfnsym(self, norb, nelec, orbsym,
-                                                  wfnsym)
+        if getattr(self.mol, 'groupname', None) in ('Dooh', 'Coov'):
+            return get_init_guess_cyl_sym(
+                norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
         else:
-            wfnsym = addons.guess_wfnsym(fcivec, norb, nelec, orbsym)
+            return get_init_guess(norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
 
-        verbose = kwargs.get('verbose', None)
-        log = logger.new_logger(self, verbose)
-        log.debug('Guessing CI wfn symmetry = %s', wfnsym)
-        return wfnsym
+    guess_wfnsym = direct_spin1_symm.guess_wfnsym
 
     def kernel(self, h1e, eri, norb, nelec, ci0=None,
                tol=None, lindep=None, max_cycle=None, max_space=None,
@@ -264,7 +302,22 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         self.norb = norb
         self.nelec = nelec
 
+        if (not hasattr(orbsym, 'degen_mapping') and
+            getattr(self.mol, 'groupname', None) in ('Dooh', 'Coov')):
+            degen_mapping = map_degeneracy(h1e.diagonal(), orbsym)
+            orbsym = lib.tag_array(orbsym, degen_mapping=degen_mapping)
+
         wfnsym = self.guess_wfnsym(norb, nelec, ci0, orbsym, wfnsym, **kwargs)
+
+        if wfnsym > 7:
+            # Symmetry broken for Dooh and Coov groups is often observed.
+            # A larger max_space is helpful to reduce the error. Also it is
+            # hard to converge to high precision.
+            if max_space is None and self.max_space == FCISolver.max_space:
+                max_space = 20 + 7 * nroots
+            if tol is None and self.conv_tol == FCISolver.conv_tol:
+                tol = 1e-7
+
         with lib.temporary_env(self, orbsym=orbsym, wfnsym=wfnsym):
             e, c = direct_spin0.kernel_ms0(self, h1e, eri, norb, nelec, ci0, None,
                                            tol, lindep, max_cycle, max_space,
@@ -274,64 +327,3 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         return e, c
 
 FCI = FCISolver
-
-
-if __name__ == '__main__':
-    from functools import reduce
-    from pyscf import gto
-    from pyscf import scf
-
-    mol = gto.Mole()
-    mol.verbose = 0
-    mol.output = None
-    mol.atom = [
-        ['O', ( 0., 0.    , 0.   )],
-        ['H', ( 0., -0.757, 0.587)],
-        ['H', ( 0., 0.757 , 0.587)],]
-    mol.basis = {'H': 'sto-3g',
-                 'O': 'sto-3g',}
-    mol.symmetry = 1
-    mol.build()
-    m = scf.RHF(mol)
-    ehf = m.scf()
-
-    norb = m.mo_coeff.shape[1]
-    nelec = mol.nelectron
-    h1e = reduce(numpy.dot, (m.mo_coeff.T, scf.hf.get_hcore(mol), m.mo_coeff))
-    eri = ao2mo.incore.full(m._eri, m.mo_coeff)
-    numpy.random.seed(1)
-    na = cistring.num_strings(norb, nelec//2)
-    fcivec = numpy.random.random((na,na))
-    fcivec = fcivec + fcivec.T
-
-    orbsym = symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb, m.mo_coeff)
-    print(numpy.allclose(orbsym, [0, 0, 2, 0, 3, 0, 2]))
-    cis = FCISolver(mol)
-    cis.orbsym = orbsym
-    fcivec = addons.symmetrize_wfn(fcivec, norb, nelec, cis.orbsym, wfnsym=0)
-    ci1 = cis.contract_2e(eri, fcivec, norb, nelec)
-    ci1ref = direct_spin0.contract_2e(eri, fcivec, norb, nelec)
-    print(numpy.allclose(ci1ref, ci1))
-    e = cis.kernel(h1e, eri, norb, nelec, ecore=m.energy_nuc(), davidson_only=True)[0]
-    print(e, e - -75.012647118991595)
-
-    mol.atom = [['H', (0, 0, i)] for i in range(8)]
-    mol.basis = {'H': 'sto-3g'}
-    mol.symmetry = True
-    mol.build()
-    m = scf.RHF(mol)
-    ehf = m.scf()
-
-    norb = m.mo_coeff.shape[1]
-    nelec = mol.nelectron
-    eri = ao2mo.incore.full(m._eri, m.mo_coeff)
-    na = cistring.num_strings(norb, nelec//2)
-    fcivec = numpy.random.random((na,na))
-    fcivec = fcivec + fcivec.T
-    orbsym = symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb, m.mo_coeff)
-    cis = FCISolver(mol)
-    cis.orbsym = orbsym
-    fcivec = addons.symmetrize_wfn(fcivec, norb, nelec, cis.orbsym, wfnsym=0)
-    ci1 = cis.contract_2e(eri, fcivec, norb, nelec)
-    ci1ref = direct_spin0.contract_2e(eri, fcivec, norb, nelec)
-    print(numpy.allclose(ci1ref, ci1))
diff --git a/pyscf/fci/direct_spin1.py b/pyscf/fci/direct_spin1.py
index acb7075e6c..5b1bcfb96b 100644
--- a/pyscf/fci/direct_spin1.py
+++ b/pyscf/fci/direct_spin1.py
@@ -428,19 +428,26 @@ def trans_rdm12(cibra, ciket, norb, nelec, link_index=None, reorder=True):
         dm1, dm2 = rdm.reorder_rdm(dm1, dm2, inplace=True)
     return dm1, dm2
 
-def _get_init_guess(na, nb, nroots, hdiag):
-    '''Initial guess is the single Slater determinant
-    '''
+def _get_init_guess(na, nb, nroots, hdiag, nelec):
     # The "nroots" lowest determinats based on energy expectation value.
     ci0 = []
-    try:
+    neleca, nelecb = _unpack_nelec(nelec)
+    if neleca == nelecb and na == nb:
+        hdiag = hdiag.reshape(na, na)
+        idx = numpy.arange(na)
+        addrs = numpy.argpartition(hdiag[idx[:,None]>=idx], nroots-1)[:nroots]
+        for addr in addrs:
+            addra = (int)((2*addr+.25)**.5 - .5 + 1e-7)
+            addrb = addr - addra*(addra+1)//2
+            x = numpy.zeros((na, na))
+            x[addra,addrb] = 1
+            ci0.append(x.ravel().view(FCIvector))
+    else:
         addrs = numpy.argpartition(hdiag, nroots-1)[:nroots]
-    except AttributeError:
-        addrs = numpy.argsort(hdiag)[:nroots]
-    for addr in addrs:
-        x = numpy.zeros((na*nb))
-        x[addr] = 1
-        ci0.append(x.ravel().view(FCIvector))
+        for addr in addrs:
+            x = numpy.zeros((na*nb))
+            x[addr] = 1
+            ci0.append(x.view(FCIvector))
 
     # Add noise
     ci0[0][0 ] += 1e-5
@@ -453,7 +460,7 @@ def get_init_guess(norb, nelec, nroots, hdiag):
     neleca, nelecb = _unpack_nelec(nelec)
     na = cistring.num_strings(norb, neleca)
     nb = cistring.num_strings(norb, nelecb)
-    return _get_init_guess(na, nb, nroots, hdiag)
+    return _get_init_guess(na, nb, nroots, hdiag, nelec)
 
 
 ###############################################################
diff --git a/pyscf/fci/direct_spin1_cyl_sym.py b/pyscf/fci/direct_spin1_cyl_sym.py
new file mode 100644
index 0000000000..ad0cf5c55f
--- /dev/null
+++ b/pyscf/fci/direct_spin1_cyl_sym.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+# Copyright 2014-2021 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.
+#
+# Author: Qiming Sun <osirpt.sun@gmail.com>
+#
+
+'''
+Cylindrical symmetry
+
+This module is much slower than direct_spin1_symm.
+
+In this implementation, complex orbitals is used to construct the Hamiltonian.
+FCI wavefunction (called complex wavefunction here) is solved using the complex
+Hamiltonian. For 2D irreps, the real part and the imaginary part of the complex
+FCI wavefunction are identical to the Ex and Ey wavefunction obtained from
+direct_spin1_symm module. However, any observables from the complex FCI
+wavefunction should have an indentical one from either Ex or Ey wavefunction
+of direct_spin1_symm.
+'''
+
+import numpy
+from pyscf import lib
+from pyscf import ao2mo
+from pyscf.lib import logger
+from pyscf import symm
+from pyscf.scf.hf_symm import map_degeneracy
+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 import direct_nosym
+from pyscf.fci import addons
+from pyscf import __config__
+
+def get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
+    neleca, nelecb = direct_spin1._unpack_nelec(nelec)
+    strsa = strsb = cistring.gen_strings4orblist(range(norb), neleca)
+    airreps_d2h = birreps_d2h = direct_spin1_symm._gen_strs_irrep(strsa, orbsym)
+    a_ls = b_ls = _strs_angular_momentum(strsa, orbsym)
+    if neleca != nelecb:
+        strsb = cistring.gen_strings4orblist(range(norb), nelecb)
+        birreps_d2h = direct_spin1_symm._gen_strs_irrep(strsb, orbsym)
+        b_ls = _strs_angular_momentum(strsb, orbsym)
+
+    wfnsym_in_d2h = wfnsym % 10
+    wfn_momentum = symm.basis.linearmole_irrep2momentum(wfnsym)
+    na = len(strsa)
+    nb = len(strsb)
+    hdiag = hdiag.reshape(na,nb)
+    degen = orbsym.degen_mapping
+    ci0 = []
+    iroot = 0
+    wfn_ungerade = wfnsym_in_d2h >= 4
+    a_ungerade = airreps_d2h >= 4
+    b_ungerade = birreps_d2h >= 4
+    sym_allowed = a_ungerade[:,None] == b_ungerade ^ wfn_ungerade
+    # total angular momentum == wfn_momentum
+    sym_allowed &= a_ls[:,None] == wfn_momentum - b_ls
+    if neleca == nelecb and na == nb:
+        idx = numpy.arange(na)
+        sym_allowed[idx[:,None] < idx] = False
+    idx_a, idx_b = numpy.where(sym_allowed)
+
+    for k in hdiag[idx_a,idx_b].argsort():
+        addra, addrb = idx_a[k], idx_b[k]
+        x = numpy.zeros((na, nb))
+        x[addra, addrb] = 1.
+        if wfnsym in (0, 1, 4, 5):
+            addra1, sign_a = _sv_associated_det(strsa[addra], degen)
+            addrb1, sign_b = _sv_associated_det(strsb[addrb], degen)
+            # If (E+) and (E-) are associated determinants
+            # (E+)(E-') + (E-)(E+') => A1
+            # (E+)(E-') - (E-)(E+') => A2
+            if wfnsym in (0, 5):  # A1g, A1u
+                # ensure 
 = 1
+                x[addra1,addrb1] += sign_a * sign_b
+            elif wfnsym in (1, 4):  # A2g, A2u
+                # ensure 
 = -1
+                x[addra1,addrb1] -= sign_a * sign_b
+
+        norm = numpy.linalg.norm(x)
+        if norm < 1e-3:
+            continue
+        x *= 1./norm
+        ci0.append(x.ravel().view(direct_spin1.FCIvector))
+        iroot += 1
+        if iroot >= nroots:
+            break
+
+    if len(ci0) == 0:
+        raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
+    return ci0
+
+
+def _guess_wfnsym(civec, strsa, strsb, orbsym):
+    degen_mapping = orbsym.degen_mapping
+    idx = abs(civec).argmax()
+    na = strsa.size
+    nb = strsb.size
+    civec = civec.reshape(na,nb)
+    addra = idx // nb
+    addrb = idx % nb
+    addra1, sign_a = _sv_associated_det(strsa[addra], degen_mapping)
+    addrb1, sign_b = _sv_associated_det(strsb[addrb], degen_mapping)
+
+    airreps_d2h = direct_spin1_symm._gen_strs_irrep(strsa[[addra]], orbsym)
+    birreps_d2h = direct_spin1_symm._gen_strs_irrep(strsb[[addrb]], orbsym)
+    a_ls = _strs_angular_momentum(strsa[[addra]], orbsym)
+    b_ls = _strs_angular_momentum(strsb[[addrb]], orbsym)
+    a_ungerade = airreps_d2h >= 4
+    b_ungerade = birreps_d2h >= 4
+    wfn_ungerade = a_ungerade[0] ^ b_ungerade[0]
+    wfn_momentum = a_ls[0] + b_ls[0]
+
+    if wfn_momentum == 0:
+        # For A1g and A1u, CI coefficient and its sigma_v associated one have
+        # the same sign
+        if sign_a*sign_b * civec[addra,addrb] * civec[addra1,addrb1] > 1e-6: # A1
+            if wfn_ungerade:
+                wfnsym = 5
+            else:
+                wfnsym = 0
+        else:
+            # For A2g and A2u, CI coefficient and its sigma_v associated one
+            # have opposite signs
+            if wfn_ungerade:
+                wfnsym = 4
+            else:
+                wfnsym = 1
+
+    elif wfn_momentum % 2 == 1:
+        if wfn_momentum > 0:  # Ex
+            if wfn_ungerade:
+                wfnsym = 7
+            else:
+                wfnsym = 2
+        else:  # Ey
+            if wfn_ungerade:
+                wfnsym = 6
+            else:
+                wfnsym = 3
+    else:
+        if wfn_momentum > 0:  # Ex
+            if wfn_ungerade:
+                wfnsym = 5
+            else:
+                wfnsym = 0
+        else:  # Ey
+            if wfn_ungerade:
+                wfnsym = 4
+            else:
+                wfnsym = 1
+
+    wfnsym += (abs(wfn_momentum) // 2) * 10
+    return wfnsym
+
+def guess_wfnsym(solver, norb, nelec, fcivec=None, orbsym=None, wfnsym=None, **kwargs):
+    '''
+    Guess point group symmetry of the FCI wavefunction.  If fcivec is
+    given, the symmetry of fcivec is used.  Otherwise the symmetry is
+    same to the HF determinant.
+    '''
+    if orbsym is None:
+        orbsym = solver.orbsym
+
+    verbose = kwargs.get('verbose', None)
+    log = logger.new_logger(solver, verbose)
+
+    neleca, nelecb = nelec = direct_spin1._unpack_nelec(nelec, solver.spin)
+    if fcivec is None or not hasattr(orbsym, 'degen_mapping'):
+        # guess wfnsym if initial guess is not given
+        wfnsym = direct_spin1_symm._id_wfnsym(solver, norb, nelec, orbsym, wfnsym)
+        log.debug('Guessing CI wfn symmetry = %s', wfnsym)
+
+    else:
+        strsa = strsb = cistring.gen_strings4orblist(range(norb), neleca)
+        if neleca != nelecb:
+            strsb = cistring.gen_strings4orblist(range(norb), nelecb)
+
+        if not isinstance(fcivec, numpy.ndarray) or fcivec.ndim > 2:
+            fcivec = fcivec[0]
+        wfnsym1 = _guess_wfnsym(fcivec, strsa, strsb, orbsym)
+
+        if (wfnsym is not None and
+            wfnsym1 != direct_spin1_symm._id_wfnsym(solver, norb, nelec, orbsym, wfnsym)):
+            raise RuntimeError(f'Input wfnsym {wfnsym} is not consistent with '
+                               f'fcivec symmetry {wfnsym1}')
+        wfnsym = wfnsym1
+    return wfnsym
+
+
+class FCISolver(direct_spin1_symm.FCISolver):
+
+    def contract_1e(self, f1e, fcivec, norb, nelec, link_index=None, **kwargs):
+        return direct_nosym.contract_1e(f1e, fcivec, norb, nelec, link_index)
+
+    def contract_2e(self, eri, fcivec, norb, nelec, link_index=None,
+                    orbsym=None, wfnsym=None, **kwargs):
+        return direct_nosym.contract_2e(eri, fcivec, norb, nelec, link_index)
+
+    def get_init_guess(self, norb, nelec, nroots, hdiag):
+        wfnsym = direct_spin1_symm._id_wfnsym(self, norb, nelec, self.orbsym, self.wfnsym)
+        return get_init_guess(norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
+
+    absorb_h1e = direct_nosym.FCISolver.absorb_h1e
+    make_hdiag = direct_nosym.FCISolver.make_hdiag
+    pspace = direct_spin1.FCISolver.pspace
+    guess_wfnsym = guess_wfnsym
+
+    def make_rdm12(self, fcivec, norb, nelec, link_index=None, reorder=True):
+        nelec = direct_spin1._unpack_nelec(nelec, self.spin)
+        dm1, dm2 = direct_spin1.make_rdm12(fcivec, norb, nelec, link_index, reorder)
+        orbsym = self.orbsym
+        degen_mapping = self.orbsym.degen_mapping
+        u = _cyl_sym_orbital_rotation(orbsym, degen_mapping)
+        dm1 = u.conj().T.dot(dm1).dot(u)
+        dm2 = lib.einsum('pqrs,pi,qj,rk,sl->ijkl', dm2, u.conj(), u, u.conj(), u)
+        return dm1.real.copy(), dm2.real.copy()
+
+    def kernel(self, h1e, eri, norb, nelec, ci0=None,
+               tol=None, lindep=None, max_cycle=None, max_space=None,
+               nroots=None, davidson_only=None, pspace_size=None,
+               orbsym=None, wfnsym=None, ecore=0, **kwargs):
+        if nroots is None: nroots = self.nroots
+        if orbsym is None: orbsym = self.orbsym
+        if wfnsym is None: wfnsym = self.wfnsym
+        if self.verbose >= logger.WARN:
+            self.check_sanity()
+        self.norb = norb
+        self.nelec = nelec
+
+        if not hasattr(orbsym, 'degen_mapping'):
+            degen_mapping = map_degeneracy(h1e.diagonal(), orbsym)
+            orbsym = lib.tag_array(orbsym, degen_mapping=degen_mapping)
+        self.orbsym = orbsym
+        u = _cyl_sym_orbital_rotation(orbsym, orbsym.degen_mapping)
+        wfnsym = self.guess_wfnsym(norb, nelec, ci0, orbsym, wfnsym, **kwargs)
+
+        h1e = u.dot(h1e).dot(u.conj().T)
+        eri = ao2mo.restore(1, eri, norb)
+        eri = lib.einsum('pqrs,ip,jq,kr,ls->ijkl', eri, u, u.conj(), u, u.conj())
+        assert abs(h1e.imag).max() < 1e-12, 'Cylindrical symmetry broken'
+        assert abs(eri.imag).max() < 1e-12, 'Cylindrical symmetry broken'
+        h1e = h1e.real.copy()
+        # Note: although eri is real, it does not have the permutation relation
+        # (ij|kl) = (ji|kl) = (ij|lk) = (ji|lk)
+        # The nosym version fci contraction is required
+        eri = eri.real.copy()
+
+        neleca, nelecb = direct_spin1._unpack_nelec(nelec)
+        link_indexa = cistring.gen_linkstr_index(range(norb), neleca)
+        link_indexb = cistring.gen_linkstr_index(range(norb), nelecb)
+        with lib.temporary_env(self, orbsym=orbsym, wfnsym=wfnsym):
+            e, c = direct_spin1.kernel_ms1(self, h1e, eri, norb, nelec, ci0,
+                                           (link_indexa,link_indexb),
+                                           tol, lindep, max_cycle, max_space,
+                                           nroots, davidson_only, pspace_size,
+                                           ecore=ecore, **kwargs)
+
+        self.eci, self.ci = e, c
+        return e, c
+
+FCI = FCISolver
diff --git a/pyscf/fci/direct_spin1_symm.py b/pyscf/fci/direct_spin1_symm.py
index ff482260c0..cf728a61e7 100644
--- a/pyscf/fci/direct_spin1_symm.py
+++ b/pyscf/fci/direct_spin1_symm.py
@@ -38,6 +38,7 @@
 from pyscf import lib
 from pyscf.lib import logger
 from pyscf import symm
+from pyscf.scf.hf_symm import map_degeneracy
 from pyscf.fci import cistring
 from pyscf.fci import direct_spin1
 from pyscf.fci import addons
@@ -93,30 +94,31 @@ def contract_2e(eri, fcivec, norb, nelec, link_index=None, orbsym=None, wfnsym=0
     # aa, ab
     ci0 = []
     ci1 = []
+    wfnsym_in_d2h = wfnsym % 10
     for ir in range(TOTIRREPS):
-        ma, mb = aidx[ir].size, bidx[wfnsym ^ ir].size
+        ma, mb = aidx[ir].size, bidx[wfnsym_in_d2h ^ ir].size
         ci0.append(numpy.zeros((ma,mb)))
         ci1.append(numpy.zeros((ma,mb)))
         if ma > 0 and mb > 0:
-            lib.take_2d(fcivec, aidx[ir], bidx[wfnsym ^ ir], out=ci0[ir])
+            lib.take_2d(fcivec, aidx[ir], bidx[wfnsym_in_d2h ^ ir], out=ci0[ir])
     ci0_ptrs = Tirrep(*[x.ctypes.data_as(ctypes.c_void_p) for x in ci0])
     ci1_ptrs = Tirrep(*[x.ctypes.data_as(ctypes.c_void_p) for x in ci1])
     libfci.FCIcontract_2e_symm1(eri_ptrs, ci0_ptrs, ci1_ptrs,
                                 ctypes.c_int(norb), nas, nbs,
                                 ctypes.c_int(nlinka), ctypes.c_int(nlinkb),
                                 linka_ptr, linkb_ptr, dimirrep,
-                                ctypes.c_int(wfnsym))
+                                ctypes.c_int(wfnsym_in_d2h))
     for ir in range(TOTIRREPS):
         if ci0[ir].size > 0:
-            lib.takebak_2d(ci1new, ci1[ir], aidx[ir], bidx[wfnsym ^ ir])
+            lib.takebak_2d(ci1new, ci1[ir], aidx[ir], bidx[wfnsym_in_d2h ^ ir])
 
     # bb, ba
     ci0T = []
     for ir in range(TOTIRREPS):
-        mb, ma = bidx[ir].size, aidx[wfnsym ^ ir].size
+        mb, ma = bidx[ir].size, aidx[wfnsym_in_d2h ^ ir].size
         ci0T.append(numpy.zeros((mb,ma)))
         if ma > 0 and mb > 0:
-            lib.transpose(ci0[wfnsym ^ ir], out=ci0T[ir])
+            lib.transpose(ci0[wfnsym_in_d2h ^ ir], out=ci0T[ir])
     ci0, ci0T = ci0T, None
     ci1 = [numpy.zeros_like(x) for x in ci0]
     ci0_ptrs = Tirrep(*[x.ctypes.data_as(ctypes.c_void_p) for x in ci0])
@@ -125,10 +127,11 @@ def contract_2e(eri, fcivec, norb, nelec, link_index=None, orbsym=None, wfnsym=0
                                 ctypes.c_int(norb), nbs, nas,
                                 ctypes.c_int(nlinkb), ctypes.c_int(nlinka),
                                 linkb_ptr, linka_ptr, dimirrep,
-                                ctypes.c_int(wfnsym))
+                                ctypes.c_int(wfnsym_in_d2h))
     for ir in range(TOTIRREPS):
         if ci0[ir].size > 0:
-            lib.takebak_2d(ci1new, lib.transpose(ci1[ir]), aidx[wfnsym ^ ir], bidx[ir])
+            lib.takebak_2d(ci1new, lib.transpose(ci1[ir]),
+                           aidx[wfnsym_in_d2h ^ ir], bidx[ir])
     return ci1new.reshape(fcivec_shape).view(direct_spin1.FCIvector)
 
 
@@ -176,15 +179,20 @@ def energy(h1e, eri, fcivec, norb, nelec, link_index=None, orbsym=None, wfnsym=0
 
 def _id_wfnsym(cisolver, norb, nelec, orbsym, wfnsym):
     '''Guess wfnsym or convert wfnsym to symmetry ID if it's a symmetry label'''
+    gpname = getattr(cisolver.mol, 'groupname', None)
     if wfnsym is None:
         neleca, nelecb = _unpack_nelec(nelec)
         wfnsym = 0  # Ag, A1 or A
         for i in orbsym[nelecb:neleca]:
             wfnsym ^= i % 10
+        if gpname in ('Dooh', 'Coov'):
+            l = 0
+            for i in orbsym[nelecb:neleca]:
+                l += symm.basis.linearmole_irrep2momentum(i)
+            wfnsym += (l//2) * 10
     elif isinstance(wfnsym, str):
-        wfnsym = symm.irrep_name2id(cisolver.mol.groupname, wfnsym)
-    # % 10 to convert irrep_ids to irrep of D2h
-    return wfnsym % 10
+        wfnsym = symm.irrep_name2id(gpname, wfnsym)
+    return wfnsym
 
 def _gen_strs_irrep(strs, orbsym):
     # % 10 to convert irrep_ids to irrep of D2h
@@ -199,28 +207,31 @@ def _gen_strs_irrep(strs, orbsym):
             irreps[numpy.bitwise_and(strs, 1 << i) > 0] ^= ir
     return irreps
 
-def _get_init_guess(airreps, birreps, nroots, hdiag, orbsym, wfnsym=0):
+def _get_init_guess(airreps, birreps, nroots, hdiag, nelec, orbsym, wfnsym=0):
+    neleca, nelecb = _unpack_nelec(nelec)
     na = len(airreps)
     nb = len(birreps)
+    hdiag = hdiag.reshape(na,nb)
     ci0 = []
     iroot = 0
-    for addr in numpy.argsort(hdiag):
-        x = numpy.zeros((na*nb))
-        addra = addr // nb
-        addrb = addr % nb
-        if airreps[addra] ^ birreps[addrb] == wfnsym:
-            x[addr] = 1
-            ci0.append(x.view(direct_spin1.FCIvector))
-            iroot += 1
-            if iroot >= nroots:
-                break
-    try:
-        # Add noise
-        ci0[0][0 ] += 1e-5
-        ci0[0][-1] -= 1e-5
-    except IndexError:
-        raise IndexError('Configuration of required symmetry (wfnsym=%d) not found' % wfnsym)
+    sym_allowed = airreps[:,None] == wfnsym ^ birreps
+    if neleca == nelecb and na == nb:
+        idx = numpy.arange(na)
+        sym_allowed[idx[:,None] < idx] = False
+    idx_a, idx_b = numpy.where(sym_allowed)
+    for k in hdiag[idx_a,idx_b].argsort():
+        addra, addrb = idx_a[k], idx_b[k]
+        x = numpy.zeros((na, nb))
+        x[addra,addrb] = 1
+        ci0.append(x.ravel().view(direct_spin1.FCIvector))
+        iroot += 1
+        if iroot >= nroots:
+            break
+
+    if len(ci0) == 0:
+        raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
     return ci0
+
 def get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
     neleca, nelecb = _unpack_nelec(nelec)
     strsa = cistring.gen_strings4orblist(range(norb), neleca)
@@ -228,8 +239,155 @@ def get_init_guess(norb, nelec, nroots, hdiag, orbsym, wfnsym=0):
     if neleca != nelecb:
         strsb = cistring.gen_strings4orblist(range(norb), nelecb)
         birreps = _gen_strs_irrep(strsb, orbsym)
-    return _get_init_guess(airreps, birreps, nroots, hdiag, orbsym, wfnsym)
+    return _get_init_guess(airreps, birreps, nroots, hdiag, nelec, orbsym, wfnsym)
+
+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)
+    airreps_d2h = birreps_d2h = _gen_strs_irrep(strsa, orbsym)
+    a_ls = b_ls = _strs_angular_momentum(strsa, orbsym)
+    if neleca != nelecb:
+        strsb = cistring.gen_strings4orblist(range(norb), nelecb)
+        birreps_d2h = _gen_strs_irrep(strsb, orbsym)
+        b_ls = _strs_angular_momentum(strsb, orbsym)
+
+    wfnsym_in_d2h = wfnsym % 10
+    wfn_momentum = symm.basis.linearmole_irrep2momentum(wfnsym)
+    na = len(strsa)
+    nb = len(strsb)
+    hdiag = hdiag.reshape(na,nb)
+    degen_mapping = orbsym.degen_mapping
+    ci0 = []
+    iroot = 0
+    wfn_ungerade = wfnsym_in_d2h >= 4
+    a_ungerade = airreps_d2h >= 4
+    b_ungerade = birreps_d2h >= 4
+    sym_allowed = a_ungerade[:,None] == b_ungerade ^ wfn_ungerade
+    # total angular momentum == wfn_momentum
+    sym_allowed &= a_ls[:,None] == wfn_momentum - b_ls
+    if neleca == nelecb and na == nb:
+        idx = numpy.arange(na)
+        sym_allowed[idx[:,None] < idx] = False
+    idx_a, idx_b = numpy.where(sym_allowed)
+
+    for k in hdiag[idx_a,idx_b].argsort():
+        addra, addrb = idx_a[k], idx_b[k]
+        ca = _cyl_sym_csf2civec(strsa, addra, orbsym, degen_mapping)
+        cb = _cyl_sym_csf2civec(strsb, addrb, orbsym, degen_mapping)
+        if wfnsym in (0, 1, 4, 5):
+            addra1, sign_a = _sv_associated_det(strsa[addra], degen_mapping)
+            addrb1, sign_b = _sv_associated_det(strsb[addrb], degen_mapping)
+            if wfnsym in (1, 4) and addra == addra1 and addrb == addrb1:
+                continue
+            x = ca[:,None] * cb
+
+            # If (E+) and (E-) are associated determinants
+            # (E+)(E-') + (E-)(E+') => A1
+            # (E+)(E-') - (E-)(E+') => A2
+            if addra != addra1:
+                ca = _cyl_sym_csf2civec(strsa, addra1, orbsym, degen_mapping)
+            if addrb != addrb1:
+                cb = _cyl_sym_csf2civec(strsb, addrb1, orbsym, degen_mapping)
+            if wfnsym in (0, 5):  # A1g, A1u
+                x += sign_a * sign_b * ca[:,None] * cb
+                #assert (sign_a*sign_b==1 and x.imag==0) or (sign_a*sign_b==-1 and x.real==0)
+            elif wfnsym in (1, 4):  # A2g, A2u
+                x -= sign_a * sign_b * ca[:,None] * cb
+                #assert (sign_a*sign_b==1 and x.real==0) or (sign_a*sign_b==-1 and x.imag==0)
+            if numpy.linalg.norm(x.real) > 1e-6:
+                x = x.real.copy()
+            else:
+                x = x.imag.copy()
+
+        elif wfn_momentum > 0:
+            x = ca.real[:,None] * cb.real
+            x-= ca.imag[:,None] * cb.imag
+        else:
+            x = ca.imag[:,None] * cb.real
+            x+= ca.real[:,None] * cb.imag
+
+        norm = numpy.linalg.norm(x)
+        if norm < 1e-3:
+            continue
+        x *= 1./norm
+        ci0.append(x.ravel().view(direct_spin1.FCIvector))
+        iroot += 1
+        if iroot >= nroots:
+            break
+
+    if len(ci0) == 0:
+        raise RuntimeError(f'Initial guess for symmetry {wfnsym} not found')
+    return ci0
 
+def _cyl_sym_csf2civec(strs, addr, orbsym, degen_mapping):
+    '''For orbital basis rotation from E(+/-) basis to Ex/Ey basis, mimic the CI
+    transformation  addons.transform_ci(civec, (0, nelec), u)
+    '''
+    norb = orbsym.size
+    one_particle_strs = numpy.asarray([1 << i for i in range(norb)])
+    occ_masks = (strs[:,None] & one_particle_strs) != 0
+    na = strs.size
+    occ_idx_all_strs = numpy.where(occ_masks)[1].reshape(na,-1)
+
+    u = _cyl_sym_orbital_rotation(orbsym, degen_mapping)
+    ui = u[occ_masks[addr]].T.copy()
+    minors = ui[occ_idx_all_strs]
+    civec = numpy.linalg.det(minors)
+    return civec
+
+def _cyl_sym_orbital_rotation(orbsym, degen_mapping):
+    '''Rotation to transform (E+)/(E-) basis to Ex/Ey basis
+    |Ex/Ey> = |E(+/-)> * u
+    '''
+    norb = orbsym.size
+    u = numpy.zeros((norb, norb), dtype=numpy.complex128)
+    sqrth = .5**.5
+    sqrthi = sqrth * 1j
+    for i, j in enumerate(degen_mapping):
+        if i == j:  # 1d irrep
+            if orbsym[i] in (1, 4):  # A2g, A2u
+                u[i,i] = 1j
+            else:
+                u[i,i] = 1
+        elif orbsym[i] % 10 in (0, 2, 5, 7):  # Ex, E(+)
+            u[j,j] = sqrthi
+            u[i,j] = sqrthi
+            u[j,i] = sqrth
+            u[i,i] = -sqrth
+    return u
+
+def _sv_associated_det(ci_str, degen_mapping):
+    '''Associated determinant for the sigma_v operation'''
+    ci_str1 = 0
+    nelec = 0
+    sign = 1
+    for i, j in enumerate(degen_mapping):
+        if ci_str & (1 << i) > 0:
+            if i > j and ci_str & (1 << j) > 0:
+                # Ex, Ey orbitals swapped
+                sign = -sign
+            ci_str1 |= 1 << j
+            nelec += 1
+    return cistring.str2addr(degen_mapping.size, nelec, ci_str1), sign
+
+def _strs_angular_momentum(strs, orbsym):
+    # angular momentum for each orbitals
+    orb_l = (orbsym // 10) * 2
+    e1_mask = numpy.isin(orbsym % 10, (2, 3, 6, 7))
+    orb_l[e1_mask] += 1
+    ey_mask = numpy.isin(orbsym % 10, (1, 3, 4, 6))
+    orb_l[ey_mask] *= -1
+
+    # total angular for each determinant (CSF)
+    ls = numpy.zeros(len(strs), dtype=int)
+    if isinstance(strs, cistring.OIndexList):
+        nocc = strs.shape[1]
+        for i in range(nocc):
+            ls += orb_l[strs[:,i]]
+    else:
+        for i, l in enumerate(orb_l):
+            ls[numpy.bitwise_and(strs, 1 << i) > 0] += l
+    return ls
 
 def reorder_eri(eri, norb, orbsym):
     if orbsym is None:
@@ -254,7 +412,10 @@ def reorder_eri(eri, norb, orbsym):
     for ir, nnorb in enumerate(dimirrep):
         idx = numpy.asarray(numpy.where(trilirrep == ir)[0], dtype=numpy.int32)
         rank_in_irrep[idx] = numpy.arange(nnorb, dtype=numpy.int32)
-        eri_irs[ir] = lib.take_2d(eri, idx, idx)
+        eri_ir = lib.take_2d(eri, idx, idx)
+        # Drop small integrals which may break symmetry?
+        #eri_ir[abs(eri_ir) < 1e-13] = 0
+        eri_irs[ir] = eri_ir
         p0 += nnorb
     return eri_irs, rank_in_irrep, old_eri_irrep
 
@@ -275,6 +436,149 @@ def gen_str_irrep(strs, orbsym, link_index, rank_eri, irrep_eri):
     link_index = [link_index.take(aidx[ir], axis=0) for ir in range(TOTIRREPS)]
     return aidx, link_index
 
+def _guess_wfnsym_cyl_sym(civec, strsa, strsb, orbsym):
+    degen_mapping = orbsym.degen_mapping
+    idx = abs(civec).argmax()
+    na = strsa.size
+    nb = strsb.size
+    addra = idx // nb
+    addrb = idx % nb
+    addra1, sign_a = _sv_associated_det(strsa[addra], degen_mapping)
+    addrb1, sign_b = _sv_associated_det(strsb[addrb], degen_mapping)
+    addra, addra1 = min(addra,addra1), max(addra,addra1)
+    addrb, addrb1 = min(addrb,addrb1), max(addrb,addrb1)
+    ca = ca1 = _cyl_sym_csf2civec(strsa, addra, orbsym, degen_mapping)
+    cb = cb1 = _cyl_sym_csf2civec(strsb, addrb, orbsym, degen_mapping)
+    if addra != addra1:
+        ca1 = _cyl_sym_csf2civec(strsa, addra1, orbsym, degen_mapping)
+    if addrb != addrb1:
+        cb1 = _cyl_sym_csf2civec(strsb, addrb1, orbsym, degen_mapping)
+    ua = numpy.stack([ca, ca1])
+    ub = numpy.stack([cb, cb1])
+    # civec is in the Ex/Ey basis. Transform the largest coefficient to
+    # (E+)/(E-) basis.
+    c_max = ua.conj().dot(civec.reshape(na,nb)).dot(ub.conj().T)
+
+    airreps_d2h = _gen_strs_irrep(strsa[[addra,addra1]], orbsym)
+    birreps_d2h = _gen_strs_irrep(strsb[[addrb,addrb1]], orbsym)
+    a_ls = _strs_angular_momentum(strsa[[addra,addra1]], orbsym)
+    b_ls = _strs_angular_momentum(strsb[[addrb,addrb1]], orbsym)
+    a_ungerade = airreps_d2h >= 4
+    b_ungerade = birreps_d2h >= 4
+    idx = abs(c_max).argmax()
+    idx_a, idx_b = idx // 2, idx % 2
+    wfn_ungerade = a_ungerade[idx_a] ^ b_ungerade[idx_b]
+    wfn_momentum = a_ls[idx_a] + b_ls[idx_b]
+
+    if wfn_momentum == 0:
+        # For A1g and A1u, CI coefficient and its sigma_v associated one have
+        # the same sign
+        if (sign_a*sign_b * c_max[0,0].real * c_max[1,1].real > 1e-6 or
+            sign_a*sign_b * c_max[0,0].imag * c_max[1,1].imag > 1e-6):  # A1
+            if wfn_ungerade:
+                wfnsym = 5
+            else:
+                wfnsym = 0
+        else:
+            # For A2g and A2u, CI coefficient and its sigma_v associated one
+            # have opposite signs
+            if wfn_ungerade:
+                wfnsym = 4
+            else:
+                wfnsym = 1
+
+    elif wfn_momentum % 2 == 1:
+        if abs(c_max[idx_a,idx_b].real) > 1e-6:  # Ex
+            if wfn_ungerade:
+                wfnsym = 7
+            else:
+                wfnsym = 2
+        else:  # Ey
+            if wfn_ungerade:
+                wfnsym = 6
+            else:
+                wfnsym = 3
+    else:
+        if abs(c_max[idx_a,idx_b].real) > 1e-6:  # Ex
+            if wfn_ungerade:
+                wfnsym = 5
+            else:
+                wfnsym = 0
+        else:  # Ey
+            if wfn_ungerade:
+                wfnsym = 4
+            else:
+                wfnsym = 1
+
+    wfnsym += (abs(wfn_momentum) // 2) * 10
+    return wfnsym
+
+def guess_wfnsym(solver, norb, nelec, fcivec=None, orbsym=None, wfnsym=None, **kwargs):
+    '''
+    Guess point group symmetry of the FCI wavefunction.  If fcivec is
+    given, the symmetry of fcivec is used.  Otherwise the symmetry is
+    same to the HF determinant.
+    '''
+    if orbsym is None:
+        orbsym = solver.orbsym
+
+    verbose = kwargs.get('verbose', None)
+    log = logger.new_logger(solver, verbose)
+    neleca, nelecb = nelec = _unpack_nelec(nelec, solver.spin)
+
+    groupname = getattr(solver.mol, 'groupname', None)
+    if fcivec is None:
+        # guess wfnsym if initial guess is not given
+        wfnsym = _id_wfnsym(solver, norb, nelec, orbsym, wfnsym)
+        log.debug('Guessing CI wfn symmetry = %s', wfnsym)
+
+    elif wfnsym is None:
+        if groupname in ('Dooh', 'Coov'):
+            strsa = strsb = cistring.gen_strings4orblist(range(norb), neleca)
+            if neleca != nelecb:
+                strsb = cistring.gen_strings4orblist(range(norb), nelecb)
+            if not isinstance(fcivec, numpy.ndarray) or fcivec.ndim > 2:
+                fcivec = fcivec[0]
+            wfnsym = _guess_wfnsym_cyl_sym(fcivec, strsa, strsb, orbsym)
+        else:
+            wfnsym = addons.guess_wfnsym(fcivec, norb, nelec, orbsym)
+        log.debug('Guessing CI wfn symmetry = %s', wfnsym)
+
+    else:
+        # verify if the input wfnsym is consistent with the symmetry of fcivec
+        strsa = strsb = cistring.gen_strings4orblist(range(norb), neleca)
+        if neleca != nelecb:
+            strsb = cistring.gen_strings4orblist(range(norb), nelecb)
+
+        if groupname in ('Dooh', 'Coov'):
+            if not isinstance(fcivec, numpy.ndarray) or fcivec.ndim > 2:
+                fcivec = fcivec[0]
+            wfnsym1 = _guess_wfnsym_cyl_sym(fcivec, strsa, strsb, orbsym)
+            if wfnsym1 != _id_wfnsym(solver, norb, nelec, orbsym, wfnsym):
+                raise RuntimeError(f'Input wfnsym {wfnsym} is not consistent with '
+                                   f'fcivec symmetry {wfnsym1}')
+            wfnsym = wfnsym1
+        else:
+            na, nb = strsa.size, strsb.size
+            orbsym_in_d2h = numpy.asarray(orbsym) % 10
+            airreps = numpy.zeros(na, dtype=numpy.int32)
+            birreps = numpy.zeros(nb, dtype=numpy.int32)
+            for i, ir in enumerate(orbsym_in_d2h):
+                airreps[numpy.bitwise_and(strsa, 1 << i) > 0] ^= ir
+                birreps[numpy.bitwise_and(strsb, 1 << i) > 0] ^= ir
+
+            wfnsym = _id_wfnsym(solver, norb, nelec, orbsym, wfnsym)
+            groupname = getattr(solver.mol, 'groupname', None)
+            mask = airreps[:,None] == (wfnsym % 10) ^ birreps
+
+            if isinstance(fcivec, numpy.ndarray) and fcivec.ndim <= 2:
+                fcivec = [fcivec]
+            if all(abs(c.reshape(na, nb)[mask]).max() < 1e-5 for c in fcivec):
+                raise RuntimeError('Input wfnsym {wfnsym} is not consistent with '
+                                   'fcivec coefficients')
+
+    return wfnsym
+
 
 class FCISolver(direct_spin1.FCISolver):
 
@@ -293,14 +597,16 @@ def dump_flags(self, verbose=None):
         if isinstance(self.wfnsym, str):
             log.info('Input CI wfn symmetry = %s', self.wfnsym)
         elif isinstance(self.wfnsym, (int, numpy.number)):
-            try:
-                log.info('Input CI wfn symmetry = %s',
-                         symm.irrep_id2name(self.mol.groupname, self.wfnsym))
-            except KeyError:
-                raise RuntimeError('FCISolver cannot find mwfnsym Id %s in group %s. '
-                                   'This might be caused by the projection from '
-                                   'high-symmetry group to D2h symmetry.' %
-                                   (self.wfnsym, self.mol.groupname))
+            groupname = getattr(self.mol, 'groupname', None)
+            if groupname is not None:
+                try:
+                    log.info('Input CI wfn symmetry = %s',
+                             symm.irrep_id2name(groupname, self.wfnsym))
+                except KeyError:
+                    raise RuntimeError('FCISolver cannot find mwfnsym Id %s in group %s. '
+                                       'This might be caused by the projection from '
+                                       'high-symmetry group to D2h symmetry.' %
+                                       (self.wfnsym, groupname))
         else:
             log.info('CI wfn symmetry = %s', self.wfnsym)
         return self
@@ -331,55 +637,13 @@ def contract_2e(self, eri, fcivec, norb, nelec, link_index=None,
 
     def get_init_guess(self, norb, nelec, nroots, hdiag):
         wfnsym = _id_wfnsym(self, norb, nelec, self.orbsym, self.wfnsym)
-        nelec = _unpack_nelec(nelec, self.spin)
-        return get_init_guess(norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
-
-    def guess_wfnsym(self, norb, nelec, fcivec=None, orbsym=None, wfnsym=None,
-                     **kwargs):
-        '''
-        Guess point group symmetry of the FCI wavefunction.  If fcivec is
-        given, the symmetry of fcivec is used.  Otherwise the symmetry is
-        based on the HF determinant.
-        '''
-        if orbsym is None:
-            orbsym = self.orbsym
-
-        verbose = kwargs.get('verbose', None)
-        log = logger.new_logger(self, verbose)
-
-        nelec = _unpack_nelec(nelec, self.spin)
-        if fcivec is None:
-            # guess wfnsym if initial guess is not given
-            wfnsym = _id_wfnsym(self, norb, nelec, orbsym, wfnsym)
-            log.debug('Guessing CI wfn symmetry = %s', wfnsym)
-
-        elif wfnsym is None:
-            wfnsym = addons.guess_wfnsym(fcivec, norb, nelec, orbsym)
-            log.debug('Guessing CI wfn symmetry = %s', wfnsym)
-
+        if getattr(self.mol, 'groupname', None) in ('Dooh', 'Coov'):
+            return get_init_guess_cyl_sym(
+                norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
         else:
-            # verify if the input wfnsym is consistent with the symmetry of fcivec
-            neleca, nelecb = nelec
-            strsa = numpy.asarray(cistring.make_strings(range(norb), neleca))
-            strsb = numpy.asarray(cistring.make_strings(range(norb), nelecb))
-            na, nb = strsa.size, strsb.size
+            return get_init_guess(norb, nelec, nroots, hdiag, self.orbsym, wfnsym)
 
-            orbsym_in_d2h = numpy.asarray(orbsym) % 10
-            airreps = numpy.zeros(na, dtype=numpy.int32)
-            birreps = numpy.zeros(nb, dtype=numpy.int32)
-            for i, ir in enumerate(orbsym_in_d2h):
-                airreps[numpy.bitwise_and(strsa, 1 << i) > 0] ^= ir
-                birreps[numpy.bitwise_and(strsb, 1 << i) > 0] ^= ir
-
-            wfnsym = _id_wfnsym(self, norb, nelec, orbsym, wfnsym)
-            mask = (airreps.reshape(-1,1) ^ birreps) == wfnsym
-
-            if isinstance(fcivec, numpy.ndarray) and fcivec.ndim <= 2:
-                fcivec = [fcivec]
-            if all(abs(c.reshape(na, nb)[mask]).max() < 1e-5 for c in fcivec):
-                raise RuntimeError('Input wfnsym is not consistent with fcivec coefficients')
-
-        return wfnsym
+    guess_wfnsym = guess_wfnsym
 
     def kernel(self, h1e, eri, norb, nelec, ci0=None,
                tol=None, lindep=None, max_cycle=None, max_space=None,
@@ -393,8 +657,22 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         self.norb = norb
         self.nelec = nelec
 
+        if (not hasattr(orbsym, 'degen_mapping') and
+            getattr(self.mol, 'groupname', None) in ('Dooh', 'Coov')):
+            degen_mapping = map_degeneracy(h1e.diagonal(), orbsym)
+            orbsym = lib.tag_array(orbsym, degen_mapping=degen_mapping)
+
         wfnsym = self.guess_wfnsym(norb, nelec, ci0, orbsym, wfnsym, **kwargs)
 
+        if wfnsym > 7:
+            # Symmetry broken for Dooh and Coov groups is often observed.
+            # A larger max_space is helpful to reduce the error. Also it is
+            # hard to converge to high precision.
+            if max_space is None and self.max_space == FCISolver.max_space:
+                max_space = 20 + 7 * nroots
+            if tol is None and self.conv_tol == FCISolver.conv_tol:
+                tol = 1e-7
+
         with lib.temporary_env(self, orbsym=orbsym, wfnsym=wfnsym):
             e, c = direct_spin1.kernel_ms1(self, h1e, eri, norb, nelec, ci0, None,
                                            tol, lindep, max_cycle, max_space,
@@ -404,71 +682,3 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         return e, c
 
 FCI = FCISolver
-
-
-if __name__ == '__main__':
-    from functools import reduce
-    from pyscf import gto
-    from pyscf import scf
-
-    mol = gto.Mole()
-    mol.verbose = 0
-    mol.output = None
-    mol.atom = [
-        ['O', ( 0., 0.    , 0.   )],
-        ['H', ( 0., -0.757, 0.587)],
-        ['H', ( 0., 0.757 , 0.587)],]
-    mol.basis = {'H': 'sto-3g',
-                 'O': 'sto-3g',}
-    mol.symmetry = 1
-    mol.build()
-    m = scf.RHF(mol)
-    ehf = m.scf()
-
-    norb = m.mo_coeff.shape[1]
-    nelec = mol.nelectron-1
-    h1e = reduce(numpy.dot, (m.mo_coeff.T, scf.hf.get_hcore(mol), m.mo_coeff))
-    eri = ao2mo.incore.full(m._eri, m.mo_coeff)
-    numpy.random.seed(1)
-    na = cistring.num_strings(norb, nelec//2+1)
-    nb = cistring.num_strings(norb, nelec//2)
-    fcivec = numpy.random.random((na,nb))
-
-    orbsym = symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb, m.mo_coeff)
-    cis = FCISolver(mol)
-    cis.orbsym = orbsym
-    fcivec = addons.symmetrize_wfn(fcivec, norb, nelec, cis.orbsym, wfnsym=0)
-
-    ci1 = cis.contract_2e(eri, fcivec, norb, nelec, orbsym=cis.orbsym, wfnsym=0)
-    ci1ref = direct_spin1.contract_2e(eri, fcivec, norb, nelec)
-    print(numpy.allclose(ci1ref, ci1))
-
-    ci1 = contract_2e(eri, fcivec, norb, nelec, orbsym=orbsym)
-    ci1ref = direct_spin1.contract_2e(eri, fcivec, norb, nelec)
-    print(numpy.allclose(ci1ref, ci1))
-    cis.wfnsym = 3
-    e = cis.kernel(h1e, eri, norb, nelec, ecore=m.energy_nuc(), davidson_only=True)[0]
-    print(e, e - -74.695029029452357)
-
-    mol.atom = [['H', (0, 0, i)] for i in range(8)]
-    mol.basis = {'H': 'sto-3g'}
-    mol.symmetry = True
-    mol.build()
-    m = scf.RHF(mol)
-    ehf = m.scf()
-
-    norb = m.mo_coeff.shape[1]
-    nelec = mol.nelectron + 1
-    eri = ao2mo.incore.full(m._eri, m.mo_coeff)
-    na = cistring.num_strings(norb, nelec//2+1)
-    nb = cistring.num_strings(norb, nelec//2)
-    fcivec = numpy.random.random((na,nb))
-    orbsym = symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb, m.mo_coeff)
-    orbsym = orbsym % 10
-    fcivec = addons.symmetrize_wfn(fcivec, norb, nelec, orbsym, wfnsym=5)
-    cis = FCISolver(mol)
-    cis.orbsym = orbsym
-    cis.wfnsym = 5
-    ci1 = cis.contract_2e(eri, fcivec, norb, nelec)
-    ci1ref = direct_spin1.contract_2e(eri, fcivec, norb, nelec)
-    print(numpy.allclose(ci1ref, ci1))
diff --git a/pyscf/fci/selected_ci.py b/pyscf/fci/selected_ci.py
index 360fbd78ab..c31d3e46cd 100644
--- a/pyscf/fci/selected_ci.py
+++ b/pyscf/fci/selected_ci.py
@@ -762,7 +762,7 @@ def get_init_guess(self, ci_strs, norb, nelec, nroots, hdiag):
         '''
         na = len(ci_strs[0])
         nb = len(ci_strs[1])
-        ci0 = direct_spin1._get_init_guess(na, nb, nroots, hdiag)
+        ci0 = direct_spin1._get_init_guess(na, nb, nroots, hdiag, nelec)
         return [_as_SCIvector(x, ci_strs) for x in ci0]
 
     def make_hdiag(self, h1e, eri, ci_strs, norb, nelec):
diff --git a/pyscf/fci/selected_ci_symm.py b/pyscf/fci/selected_ci_symm.py
index 8dc23f16d9..0ee292b39a 100644
--- a/pyscf/fci/selected_ci_symm.py
+++ b/pyscf/fci/selected_ci_symm.py
@@ -159,8 +159,8 @@ def get_init_guess(self, ci_strs, norb, nelec, nroots, hdiag):
                                               self.wfnsym)
         airreps = direct_spin1_symm._gen_strs_irrep(ci_strs[0], self.orbsym)
         birreps = direct_spin1_symm._gen_strs_irrep(ci_strs[1], self.orbsym)
-        ci0 = direct_spin1_symm._get_init_guess(airreps, birreps,
-                                                nroots, hdiag, self.orbsym, wfnsym)
+        ci0 = direct_spin1_symm._get_init_guess(
+            airreps, birreps, nroots, hdiag, nelec, self.orbsym, wfnsym)
         return [selected_ci._as_SCIvector(x, ci_strs) for x in ci0]
 
     def guess_wfnsym(self, norb, nelec, fcivec=None, orbsym=None, wfnsym=None,
@@ -215,6 +215,9 @@ def kernel(self, h1e, eri, norb, nelec, ci0=None,
         if self.verbose >= logger.WARN:
             self.check_sanity()
 
+        if getattr(self.mol, 'groupname', None) in ('Dooh', 'Coov'):
+            raise NotImplementedError
+
         with lib.temporary_env(self, orbsym=orbsym, wfnsym=wfnsym):
             e, c = selected_ci.kernel_float_space(self, h1e, eri, norb, nelec, ci0,
                                                   tol, lindep, max_cycle, max_space,
diff --git a/pyscf/gto/mole.py b/pyscf/gto/mole.py
index a65c0aca77..b2d38ad1df 100644
--- a/pyscf/gto/mole.py
+++ b/pyscf/gto/mole.py
@@ -259,6 +259,22 @@ def sph2spinor_l(l):
     '''Real spherical to spinor transformation matrix for angular moment l'''
     return sph2spinor_kappa(0, l)
 
+def ao_rotation_matrix(mol, orientation):
+    '''Matrix u to rotate AO basis to a new orientation.
+
+    atom_new_coords = mol.atom_coords().dot(orientation.T)
+    new_AO = u * mol.AO
+    new_orbitals_coef = u.dot(orbitals_coef)
+    '''
+    from pyscf.symm.basis import _momentum_rotation_matrices
+    Ds = _momentum_rotation_matrices(mol, orientation)
+    u = []
+    for i in range(mol.nbas):
+        l = mol.bas_angular(i)
+        nc = mol.bas_nctr(i)
+        u.extend([Ds[l]] * nc)
+    return scipy.linalg.block_diag(*u)
+
 def atom_types(atoms, basis=None, magmom=None):
     '''symmetry inequivalent atoms'''
     atmgroup = {}
@@ -3580,6 +3596,8 @@ def search_shell_id(self, atm_id, l):
 
     __add__ = conc_mol
 
+    ao_rotation_matrix = ao_rotation_matrix
+
     def cart2sph_coeff(self, normalized='sp'):
         '''Transformation matrix that transforms Cartesian GTOs to spherical
         GTOs for all basis functions
diff --git a/pyscf/lib/linalg_helper.py b/pyscf/lib/linalg_helper.py
index ceb9d1b4eb..8b4c1bacc6 100644
--- a/pyscf/lib/linalg_helper.py
+++ b/pyscf/lib/linalg_helper.py
@@ -425,10 +425,13 @@ def davidson1(aop, x0, precond, tol=1e-12, max_cycle=50, max_space=12,
                     log.warn('Check to see if `pick` function %s is providing '
                              'linear dependent vectors', pick.__name__)
                 if len(xt) == 0:
-                    msg = ('No more linearly independent basis were found. '
-                           'Unless loosen the lindep tolerance (current value '
-                           f'{lindep}), the diagonalization solver is not able '
-                           'to find eigenvectors.')
+                    if icyc == 0:
+                        msg = 'Initial guess is empty or zero'
+                    else:
+                        msg = ('No more linearly independent basis were found. '
+                               'Unless loosen the lindep tolerance (current value '
+                               f'{lindep}), the diagonalization solver is not able '
+                               'to find eigenvectors.')
                     raise LinearDependenceError(msg)
             x0 = None
             max_dx_last = 1e9
diff --git a/pyscf/mcscf/addons.py b/pyscf/mcscf/addons.py
index c524aa58aa..d696ad4686 100644
--- a/pyscf/mcscf/addons.py
+++ b/pyscf/mcscf/addons.py
@@ -1321,10 +1321,10 @@ def approx_kernel(self, h1, h2, norb, nelec, ci0=None, **kwargs):
             cs = []
             for ix, (solver, my_args, my_kwargs) in enumerate (self._loop_solver(_state_args (ci0))):
                 c0 = my_args[0]
-                try:
+                if hasattr(solver, 'approx_kernel'):
                     e, c = solver.approx_kernel(h1, h2, norb, self._get_nelec(solver, nelec), ci0=c0,
                                                 orbsym=self.orbsym, **kwargs)
-                except AttributeError:
+                else:
                     e, c = solver.kernel(h1, h2, norb, self._get_nelec(solver, nelec), ci0=c0,
                                          orbsym=self.orbsym, **kwargs)
                 if solver.nroots == 1:
diff --git a/pyscf/mcscf/casci_symm.py b/pyscf/mcscf/casci_symm.py
index a6fc4d6d76..e1d61e0b79 100644
--- a/pyscf/mcscf/casci_symm.py
+++ b/pyscf/mcscf/casci_symm.py
@@ -107,39 +107,41 @@ def label_symmetry_(mc, mo_coeff, ci0=None):
 
     active_orbsym = getattr(mc.fcisolver, 'orbsym', [])
     if (not getattr(active_orbsym, '__len__', None)) or len(active_orbsym) == 0:
-        mc.fcisolver.orbsym = orbsym[ncore:nocc]
+        if getattr(mo_coeff, 'degen_mapping', None) is not None:
+            degen_mapping = mo_coeff.degen_mapping[ncore:nocc] - ncore
+            # Attach to degen_mapping to orbsym, this is a temporary solution
+            # for fci.direct_spin1_symm
+            mc.fcisolver.orbsym = lib.tag_array(
+                orbsym[ncore:nocc], degen_mapping=degen_mapping)
+        else:
+            mc.fcisolver.orbsym = orbsym[ncore:nocc]
     log.info('Symmetries of active orbitals: %s',
-             ' '.join([symm.irrep_id2name(mc.mol.groupname, irrep) for irrep in mc.fcisolver.orbsym]))
+             ' '.join([symm.irrep_id2name(mc.mol.groupname, irrep)
+                       for irrep in mc.fcisolver.orbsym]))
 
-    wfnsym = 0
-    if getattr(mc.fcisolver, 'wfnsym', None) is not None:
-        wfnsym = mc.fcisolver.wfnsym
+    wfnsym = getattr(mc.fcisolver, 'wfnsym', None)
+    if wfnsym is not None:
         log.debug('Use fcisolver.wfnsym %s', wfnsym)
 
     elif (ci0 is None and
-          # mc._scf may not be initialized
-          mc._scf.mo_coeff is not None):
+        # mc._scf may not be initialized
+        mc._scf.mo_coeff is not None):
         # Guess wfnsym based on HF determinant.  mo_coeff may not be HF
         # canonical orbitals.  Some checks are needed to ensure that mo_coeff
         # are derived from the symmetry adapted SCF calculations.
         if mo_coeff is mc._scf.mo_coeff:
-            wfnsym = 0
-            orbsym_in_d2h = numpy.asarray(orbsym) % 10  # convert to D2h irreps
-            for ir in orbsym_in_d2h[mc._scf.mo_occ == 1]:
-                wfnsym ^= ir
-            mc.fcisolver.wfnsym = wfnsym
+            mc.fcisolver.wfnsym = wfnsym = mc._scf.get_wfnsym()
             log.debug('Set CASCI wfnsym %s based on HF determinant', wfnsym)
 
-        elif getattr(mo_coeff, 'orbsym', None) is not None:  # It may be reordered SCF orbitals
+        else:
             cas_orb = mo_coeff[:,ncore:nocc]
-            s = reduce(numpy.dot, (cas_orb.conj().T, mc._scf.get_ovlp(), mc._scf.mo_coeff))
-            if numpy.all(numpy.max(s, axis=1) > 1-1e-9):
-                idx = numpy.argmax(s, axis=1)
-                cas_orbsym_in_d2h = numpy.asarray(orbsym[ncore:nocc]) % 10
+            # check if cas_orb are SCF orbitals reordered
+            s1 = reduce(numpy.dot, (cas_orb.conj().T, s, mc._scf.mo_coeff))
+            if numpy.all(numpy.max(s1, axis=1) > 1-1e-9):
+                idx = numpy.argmax(s1, axis=1)
+                cas_orbsym = orbsym[ncore:nocc]
                 cas_occ = mc._scf.mo_occ[idx]
-                wfnsym = 0
-                for ir in cas_orbsym_in_d2h[cas_occ == 1]:
-                    wfnsym ^= ir
+                wfnsym = mc._scf.get_wfnsym(mo_occ=cas_occ, orbsym=cas_orbsym)
                 mc.fcisolver.wfnsym = wfnsym
                 log.debug('Active space are constructed from canonical SCF '
                           'orbitals %s', idx)
@@ -156,8 +158,8 @@ def label_symmetry_(mc, mo_coeff, ci0=None):
             log.warn('mwfnsym Id %s not found in group %s. This might be caused by '
                      'the projection from high-symmetry group to D2h symmetry.',
                      wfnsym, mc.mol.groupname)
-
-    log.info('Active space CI wfn symmetry = %s', wfnsym)
+    if wfnsym is not None:
+        log.info('Active space CI wfn symmetry = %s', wfnsym)
 
     return mo_coeff_with_orbsym
 
diff --git a/pyscf/mcscf/mc1step.py b/pyscf/mcscf/mc1step.py
index 77932305eb..561754386a 100644
--- a/pyscf/mcscf/mc1step.py
+++ b/pyscf/mcscf/mc1step.py
@@ -1100,20 +1100,8 @@ def solve_approx_ci(self, h1, h2, ci0, ecore, e_cas, envs):
 
         h2eff = self.fcisolver.absorb_h1e(h1, h2, ncas, nelecas, .5)
 
-        # Be careful with the symmetry adapted contract_2e function. When the
-        # symmetry adapted FCI solver is used, the symmetry of ci0 may be
-        # different to fcisolver.wfnsym. This function may output 0.
-        if getattr(self.fcisolver, 'guess_wfnsym', None):
-            wfnsym = self.fcisolver.guess_wfnsym(self.ncas, self.nelecas, ci0)
-        else:
-            wfnsym = None
-
         def contract_2e(c):
-            if wfnsym is None:
-                hc = self.fcisolver.contract_2e(h2eff, c, ncas, nelecas)
-            else:
-                with lib.temporary_env(self.fcisolver, wfnsym=wfnsym):
-                    hc = self.fcisolver.contract_2e(h2eff, c, ncas, nelecas)
+            hc = self.fcisolver.contract_2e(h2eff, c, ncas, nelecas)
             return hc.ravel()
 
         hc = contract_2e(ci0)
diff --git a/pyscf/scf/hf_symm.py b/pyscf/scf/hf_symm.py
index 6b272c32ea..9c72b00239 100644
--- a/pyscf/scf/hf_symm.py
+++ b/pyscf/scf/hf_symm.py
@@ -36,6 +36,7 @@
 from pyscf.scf import uhf
 from pyscf.scf import rohf
 from pyscf.scf import chkfile
+from pyscf.lib.exceptions import PointGroupSymmetryError
 from pyscf import __config__
 
 WITH_META_LOWDIN = getattr(__config__, 'scf_analyze_with_meta_lowdin', True)
@@ -311,11 +312,30 @@ def eig(mf, h, s):
     cs = []
     es = []
     orbsym = []
-    for ir in range(nirrep):
-        e, c = mf._eigh(h[ir], s[ir])
-        cs.append(c)
-        es.append(e)
-        orbsym.append([mol.irrep_id[ir]] * e.size)
+    if mol.groupname in ('Dooh', 'Coov'):
+        for ir in range(nirrep):
+            irrep_id = mol.irrep_id[ir]
+            irrep_1d = irrep_id in (0, 1, 4, 5)
+            irrep_2dx = irrep_id % 2 == 0
+            if irrep_1d or irrep_2dx:
+                e, c = mf._eigh(h[ir], s[ir])
+                cs.append(c)
+                es.append(e)
+                orbsym.append([mol.irrep_id[ir]] * e.size)
+
+            if not irrep_1d and irrep_2dx:
+                # force 2D irreps using the same coefficients
+                irrep_conj = irrep_id ^ 1
+                assert mol.irrep_id[ir+1] == irrep_conj
+                cs.append(c)
+                es.append(e)
+                orbsym.append([irrep_conj] * e.size)
+    else:
+        for ir in range(nirrep):
+            e, c = mf._eigh(h[ir], s[ir])
+            cs.append(c)
+            es.append(e)
+            orbsym.append([mol.irrep_id[ir]] * e.size)
     e = numpy.hstack(es)
     c = so2ao_mo_coeff(mol.symm_orb, cs)
     c = lib.tag_array(c, orbsym=numpy.hstack(orbsym))
@@ -330,22 +350,63 @@ def get_orbsym(mol, mo_coeff, s=None, check=False):
     else:
         orbsym = symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb,
                                      mo_coeff, s, check)
-    return numpy.asarray(orbsym)
+    return orbsym
+
+def map_degeneracy(mo_energy, orbsym):
+    '''Find degeneracy correspondence for cylindrical symmetry'''
+    norb = orbsym.size
+    ex_mask = numpy.isin(orbsym % 10, (0, 2, 5, 7))
+
+    degen_mapping = numpy.arange(norb)
+    for ir_ex in set(orbsym[ex_mask]):
+        if ir_ex in (0, 1, 5, 4):
+            continue
 
-def get_wfnsym(mf, mo_coeff=None, mo_occ=None):
-    orbsym = mf.get_orbsym(mo_coeff)
-    if mf.mol.groupname in ('SO3', 'Dooh', 'Coov'):
+        if ir_ex % 2 == 0:
+            ir_ey = ir_ex + 1
+        else:
+            ir_ey = ir_ex - 1
+        ex_idx = numpy.where(orbsym == ir_ex)[0]
+        ey_idx = numpy.where(orbsym == ir_ey)[0]
+        if ex_idx.size != ey_idx.size:
+            raise PointGroupSymmetryError('Degenerated orbitals required')
+        mo_ex = mo_energy[ex_idx].round(7)
+        mo_ey = mo_energy[ey_idx].round(7)
+        mapping = numpy.where(abs(mo_ex[:,None] - mo_ey) < 1e-6)[1]
+        if mapping.size != ex_idx.size:
+            raise PointGroupSymmetryError('Degenerated orbitals required')
+        degen_mapping[ex_idx] = ey_idx[mapping]
+        degen_mapping[ey_idx] = ex_idx[mapping.argsort()]
+
+    return degen_mapping
+
+def get_wfnsym(mf, mo_coeff=None, mo_occ=None, orbsym=None):
+    if mo_occ is None:
+        mo_occ = mf.mo_occ
+    if orbsym is None:
+        orbsym = mf.get_orbsym(mo_coeff)
+
+    if mf.mol.groupname == 'SO3':
         if numpy.any(orbsym > 7):
             logger.warn(mf, 'Wave-function symmetry for %s not supported. '
                         'Wfn symmetry is mapped to D2h/C2v group.',
                         mf.mol.groupname)
-            orbsym = orbsym % 10
 
-    if mo_occ is None:
-        mo_occ = mf.mo_occ
     wfnsym = 0
-    for ir in orbsym[mo_occ == 1]:
+    for ir in orbsym[mo_occ == 1] % 10:
         wfnsym ^= ir
+
+    if mf.mol.groupname in ('Dooh', 'Coov'):
+        orb_l = (orbsym // 10) * 2
+        e1_mask = numpy.isin(orbsym % 10, (2, 3, 6, 7))
+        orb_l[e1_mask] += 1
+        ey_mask = numpy.isin(orbsym % 10, (1, 3, 4, 6))
+        orb_l[ey_mask] *= -1
+        a_l = orb_l[mo_occ != 0]
+        b_l = orb_l[mo_occ == 2]
+        wfn_momentum = a_l.sum() + b_l.sum()
+        wfnsym += (abs(wfn_momentum) // 2) * 10
+
     return wfnsym
 
 
@@ -461,9 +522,20 @@ def _finalize(self):
         idx = numpy.hstack((idx[self.mo_occ> 0][o_sort],
                             idx[self.mo_occ==0][v_sort]))
         self.mo_energy = self.mo_energy[idx]
-        orbsym = self.get_orbsym(self.mo_coeff, self.get_ovlp())
-        self.mo_coeff = lib.tag_array(self.mo_coeff[:,idx], orbsym=orbsym[idx])
         self.mo_occ = self.mo_occ[idx]
+        orbsym = self.get_orbsym(self.mo_coeff, self.get_ovlp())
+        orbsym = orbsym[idx]
+        degen_mapping = None
+        if self.mol.groupname in ('Dooh', 'Coov'):
+            try:
+                degen_mapping = map_degeneracy(self.mo_energy, orbsym)
+            except PointGroupSymmetryError:
+                logger.warn(self, 'Orbital degeneracy broken')
+        if degen_mapping is None:
+            self.mo_coeff = lib.tag_array(self.mo_coeff[:,idx], orbsym=orbsym)
+        else:
+            self.mo_coeff = lib.tag_array(
+                self.mo_coeff[:,idx], orbsym=orbsym, degen_mapping=degen_mapping)
         if self.chkfile:
             chkfile.dump_scf(self.mol, self.chkfile, self.e_tot, self.mo_energy,
                              self.mo_coeff, self.mo_occ, overwrite_mol=False)
@@ -583,7 +655,7 @@ def get_grad(self, mo_coeff, mo_occ, fock=None):
     def get_occ(self, mo_energy=None, mo_coeff=None):
         if mo_energy is None: mo_energy = self.mo_energy
         mol = self.mol
-        if not self.mol.symmetry:
+        if not mol.symmetry:
             return rohf.ROHF.get_occ(self, mo_energy, mo_coeff)
 
         if getattr(mo_energy, 'mo_ea', None) is not None:
@@ -712,9 +784,20 @@ def _finalize(self):
                                            mo_ea=mo_ea, mo_eb=mo_eb)
         else:
             self.mo_energy = self.mo_energy[idx]
-        orbsym = self.get_orbsym(self.mo_coeff, self.get_ovlp())
-        self.mo_coeff = lib.tag_array(self.mo_coeff[:,idx], orbsym=orbsym[idx])
         self.mo_occ = self.mo_occ[idx]
+        orbsym = self.get_orbsym(self.mo_coeff, self.get_ovlp())
+        orbsym = orbsym[idx]
+        degen_mapping = None
+        if self.mol.groupname in ('Dooh', 'Coov'):
+            try:
+                degen_mapping = map_degeneracy(self.mo_energy, orbsym)
+            except PointGroupSymmetryError:
+                logger.warn(self, 'Orbital degeneracy broken')
+        if degen_mapping is None:
+            self.mo_coeff = lib.tag_array(self.mo_coeff[:,idx], orbsym=orbsym)
+        else:
+            self.mo_coeff = lib.tag_array(
+                self.mo_coeff[:,idx], orbsym=orbsym, degen_mapping=degen_mapping)
         if self.chkfile:
             chkfile.dump_scf(self.mol, self.chkfile, self.e_tot, self.mo_energy,
                              self.mo_coeff, self.mo_occ, overwrite_mol=False)
@@ -883,23 +966,3 @@ class HF1e(ROHF):
 
 
 del (WITH_META_LOWDIN)
-
-
-if __name__ == '__main__':
-    from pyscf import gto
-    mol = gto.Mole()
-    mol.build(
-        verbose = 1,
-        output = None,
-        atom = [['H', (0.,0.,0.)],
-                ['H', (0.,0.,1.)], ],
-        basis = {'H': 'ccpvdz'},
-        symmetry = True
-    )
-
-    method = RHF(mol)
-    method.verbose = 5
-    method.irrep_nelec['A1u'] = 2
-    energy = method.kernel()
-    print(energy)
-    method.analyze()
diff --git a/pyscf/scf/uhf_symm.py b/pyscf/scf/uhf_symm.py
index 8a22c9bd9d..447457d371 100644
--- a/pyscf/scf/uhf_symm.py
+++ b/pyscf/scf/uhf_symm.py
@@ -29,6 +29,7 @@
 from pyscf.scf import hf_symm
 from pyscf.scf import uhf
 from pyscf.scf import chkfile
+from pyscf.lib.exceptions import PointGroupSymmetryError
 from pyscf import __config__
 
 WITH_META_LOWDIN = getattr(__config__, 'scf_analyze_with_meta_lowdin', True)
@@ -256,23 +257,35 @@ def get_orbsym(mol, mo_coeff, s=None, check=False):
                   hf_symm.get_orbsym(mol, mo_coeff[1], s, check))
     return orbsym
 
-def get_wfnsym(mf, mo_coeff=None, mo_occ=None):
-    orbsyma, orbsymb = mf.get_orbsym(mo_coeff)
-    if mf.mol.groupname in ('SO3', 'Dooh', 'Coov'):
+def get_wfnsym(mf, mo_coeff=None, mo_occ=None, orbsym=None):
+    if mo_occ is None:
+        mo_occ = mf.mo_occ
+    if orbsym is None:
+        orbsym = mf.get_orbsym(mo_coeff)
+    orbsyma, orbsymb = orbsym
+
+    if mf.mol.groupname == 'SO3':
         if numpy.any(orbsyma > 7):
             logger.warn(mf, 'Wave-function symmetry for %s not supported. '
                         'Wfn symmetry is mapped to D2h/C2v group.',
                         mf.mol.groupname)
-            orbsyma = orbsyma % 10
-            orbsymb = orbsymb % 10
 
-    if mo_occ is None:
-        mo_occ = mf.mo_occ
     wfnsym = 0
-    for ir in orbsyma[mo_occ[0] == 1]:
+    for ir in orbsyma[mo_occ[0] == 1] % 10:
         wfnsym ^= ir
-    for ir in orbsymb[mo_occ[1] == 1]:
+    for ir in orbsymb[mo_occ[1] == 1] % 10:
         wfnsym ^= ir
+
+    if mf.mol.groupname in ('Dooh', 'Coov'):
+        a_l = (orbsyma // 10) * 2
+        a_l[numpy.isin(orbsyma % 10, (2, 3, 6, 7))] += 1
+        a_l[numpy.isin(orbsyma % 10, (1, 3, 4, 6))] *= -1
+        b_l = (orbsymb // 10) * 2
+        b_l[numpy.isin(orbsymb % 10, (2, 3, 6, 7))] += 1
+        b_l[numpy.isin(orbsymb % 10, (1, 3, 4, 6))] *= -1
+        wfn_momentum = a_l[mo_occ[0]==1].sum() + b_l[mo_occ[1]==1].sum()
+        wfnsym += (abs(wfn_momentum) // 2) * 10
+
     return wfnsym
 
 
@@ -325,30 +338,55 @@ def eig(self, h, s):
         nirrep = mol.symm_orb.__len__()
         s = symm.symmetrize_matrix(s, mol.symm_orb)
         ha = symm.symmetrize_matrix(h[0], mol.symm_orb)
-        cs = []
-        es = []
-        orbsym = []
-        for ir in range(nirrep):
-            e, c = self._eigh(ha[ir], s[ir])
-            cs.append(c)
-            es.append(e)
-            orbsym.append([mol.irrep_id[ir]] * e.size)
-        ea = numpy.hstack(es)
-        ca = hf_symm.so2ao_mo_coeff(mol.symm_orb, cs)
-        ca = lib.tag_array(ca, orbsym=numpy.hstack(orbsym))
-
         hb = symm.symmetrize_matrix(h[1], mol.symm_orb)
-        cs = []
-        es = []
-        orbsym = []
-        for ir in range(nirrep):
-            e, c = self._eigh(hb[ir], s[ir])
-            cs.append(c)
-            es.append(e)
-            orbsym.append([mol.irrep_id[ir]] * e.size)
-        eb = numpy.hstack(es)
-        cb = hf_symm.so2ao_mo_coeff(mol.symm_orb, cs)
-        cb = lib.tag_array(cb, orbsym=numpy.hstack(orbsym))
+        cs_b = []
+        cs_a = []
+        es_b = []
+        es_a = []
+        orbsym_a = []
+        orbsym_b = []
+        if mol.groupname in ('Dooh', 'Coov'):
+            for ir in range(nirrep):
+                irrep_id = mol.irrep_id[ir]
+                irrep_1d = irrep_id in (0, 1, 4, 5)
+                irrep_2dx = irrep_id % 2 == 0
+                if irrep_1d or irrep_2dx:
+                    ea, ca = self._eigh(ha[ir], s[ir])
+                    eb, cb = self._eigh(hb[ir], s[ir])
+                    cs_a.append(ca)
+                    cs_b.append(cb)
+                    es_a.append(ea)
+                    es_b.append(eb)
+                    orbsym_a.append([mol.irrep_id[ir]] * ea.size)
+                    orbsym_b.append([mol.irrep_id[ir]] * eb.size)
+
+                if not irrep_1d and irrep_2dx:
+                    # force 2D irreps using the same coefficients
+                    irrep_conj = irrep_id ^ 1
+                    assert mol.irrep_id[ir+1] == irrep_conj
+                    cs_a.append(ca)
+                    cs_b.append(cb)
+                    es_a.append(ea)
+                    es_b.append(eb)
+                    orbsym_a.append([irrep_conj] * ea.size)
+                    orbsym_b.append([irrep_conj] * eb.size)
+        else:
+            for ir in range(nirrep):
+                ea, ca = self._eigh(ha[ir], s[ir])
+                eb, cb = self._eigh(hb[ir], s[ir])
+                cs_a.append(ca)
+                cs_b.append(cb)
+                es_a.append(ea)
+                es_b.append(eb)
+                orbsym_a.append([mol.irrep_id[ir]] * ea.size)
+                orbsym_b.append([mol.irrep_id[ir]] * eb.size)
+
+        ea = numpy.hstack(es_a)
+        eb = numpy.hstack(es_b)
+        ca = hf_symm.so2ao_mo_coeff(mol.symm_orb, cs_a)
+        ca = lib.tag_array(ca, orbsym=numpy.hstack(orbsym_a))
+        cb = hf_symm.so2ao_mo_coeff(mol.symm_orb, cs_b)
+        cb = lib.tag_array(cb, orbsym=numpy.hstack(orbsym_b))
         return numpy.asarray((ea,eb)), (ca,cb)
 
     def get_grad(self, mo_coeff, mo_occ, fock=None):
@@ -479,8 +517,24 @@ def _finalize(self):
                              idxb[self.mo_occ[1]==0][vb_sort]))
         self.mo_energy = (ea[idxa], eb[idxb])
         orbsyma, orbsymb = self.get_orbsym(self.mo_coeff, self.get_ovlp())
-        self.mo_coeff = (lib.tag_array(self.mo_coeff[0][:,idxa], orbsym=orbsyma[idxa]),
-                         lib.tag_array(self.mo_coeff[1][:,idxb], orbsym=orbsymb[idxb]))
+        orbsyma = orbsyma[idxa]
+        orbsymb = orbsymb[idxb]
+        degen_a = degen_b = None
+        if self.mol.groupname in ('Dooh', 'Coov'):
+            try:
+                degen_a = hf_symm.map_degeneracy(self.mo_energy[0], orbsyma)
+                degen_b = hf_symm.map_degeneracy(self.mo_energy[1], orbsymb)
+            except PointGroupSymmetryError:
+                logger.warn(self, 'Orbital degeneracy broken')
+        if degen_a is None or degen_b is None:
+            mo_a = lib.tag_array(self.mo_coeff[0][:,idxa], orbsym=orbsyma)
+            mo_b = lib.tag_array(self.mo_coeff[1][:,idxb], orbsym=orbsymb)
+        else:
+            mo_a = lib.tag_array(self.mo_coeff[0][:,idxa], orbsym=orbsyma,
+                                 degen_mapping=degen_a)
+            mo_b = lib.tag_array(self.mo_coeff[1][:,idxb], orbsym=orbsymb,
+                                 degen_mapping=degen_b)
+        self.mo_coeff = (mo_a, mo_b)
         self.mo_occ = (self.mo_occ[0][idxa], self.mo_occ[1][idxb])
         if self.chkfile:
             chkfile.dump_scf(self.mol, self.chkfile, self.e_tot, self.mo_energy,
@@ -522,25 +576,3 @@ class HF1e(UHF):
 
 
 del (WITH_META_LOWDIN)
-
-
-if __name__ == '__main__':
-    from pyscf import gto
-    mol = gto.Mole()
-    mol.build(
-        verbose = 1,
-        output = None,
-        atom = [['H', (0.,0.,0.)],
-                ['H', (0.,0.,1.)], ],
-        basis = {'H': 'ccpvdz'},
-        symmetry = True,
-        charge = -1,
-        spin = 1
-    )
-
-    method = UHF(mol)
-    method.verbose = 5
-    method.irrep_nelec['A1u'] = (1,0)
-    energy = method.kernel()
-    print(energy)
-    method.analyze()
diff --git a/pyscf/symm/basis.py b/pyscf/symm/basis.py
index c6a8188ab3..374d8ec956 100644
--- a/pyscf/symm/basis.py
+++ b/pyscf/symm/basis.py
@@ -24,9 +24,11 @@
 import numpy
 from pyscf.data.elements import _symbol, _rm_digit
 from pyscf import gto
+from pyscf import lib
 from pyscf.lib.exceptions import PointGroupSymmetryError
 from pyscf.symm import geom
 from pyscf.symm import param
+from pyscf.symm.Dmatrix import Dmatrix, get_euler_angles
 
 __all__ = ['tot_parity_odd',
            'symm_adapted_basis',
@@ -37,7 +39,7 @@
            'linearmole_irrep_id2symb',
            'linearmole_symm_adapted_basis',
            'so3_irrep_symb2id',
-           'so3_irrep_id2symb',]
+           'so3_irrep_id2symb']
 
 OP_PARITY_ODD = {
     'E'  : (0, 0, 0),
@@ -132,7 +134,7 @@ def symm_adapted_basis(mol, gpname, orig=0, coordinates=None):
         if idx:
             l_idx[l] = numpy.hstack(idx)
 
-    Ds = _ao_rotation_matrices(mol, coordinates)
+    Ds = _momentum_rotation_matrices(mol, coordinates)
     so = []
     irrep_ids = []
     for ir, c in enumerate(sodic):
@@ -148,10 +150,8 @@ def symm_adapted_basis(mol, gpname, orig=0, coordinates=None):
 
     return so, irrep_ids
 
-def _ao_rotation_matrices(mol, axes):
-    '''Cache the rotation matrices'''
-    from pyscf import lib
-    from pyscf.symm.Dmatrix import Dmatrix, get_euler_angles
+def _momentum_rotation_matrices(mol, axes):
+    '''Cache the rotation matrices for each angular momentum'''
     alpha, beta, gamma = get_euler_angles(numpy.eye(3), axes)
     ANG_OF = 1
     l_max = mol._bas[:,ANG_OF].max()
@@ -464,31 +464,32 @@ def linearmole_irrep_id2symb(gpname, irrep_id):
         if irrep_id < 10:
             return DOOH_IRREP_SYMBS[irrep_id]
         else:
+            l = abs(linearmole_irrep2momentum(irrep_id))
             n = irrep_id % 10
-            m = irrep_id // 10
-            if n in (0, 1, 5, 4):
-                rn = m*2
-            else:
-                rn = m*2+1
-            return 'E%d%s' % (rn, DOOH_IRREP_SYMBS_EXT[n])
+            return 'E%d%s' % (l, DOOH_IRREP_SYMBS_EXT[n])
     elif gpname == 'Coov':
         if irrep_id < 10:
             return COOV_IRREP_SYMBS[irrep_id]
         else:
+            l = abs(linearmole_irrep2momentum(irrep_id))
             n = irrep_id % 10
-            m = irrep_id // 10
-            if n < 2:
-                rn = m*2
-            else:
-                rn = m*2+1
             if n % 2:
                 xy = 'y'
             else:
                 xy = 'x'
-            return 'E%d%s' % (rn, xy)
+            return 'E%d%s' % (l, xy)
     else:
         raise PointGroupSymmetryError('%s is not proper for linear molecule.' % gpname)
 
+def linearmole_irrep2momentum(irrep_id):
+    if irrep_id % 10 in (0, 1, 5, 4):
+        l = irrep_id // 10 * 2
+    else:
+        l = irrep_id // 10 * 2 + 1
+    if irrep_id % 10 in (1, 3, 4, 6):  # Ey
+        l *= -1
+    return l
+
 def linearmole_symm_adapted_basis(mol, gpname, orig=0, coordinates=None):
     assert (gpname in ('Dooh', 'Coov'))
     assert (not mol.cart)
@@ -668,7 +669,7 @@ def add_so(irrep_name, c):
         if idx:
             l_idx[l] = numpy.hstack(idx)
 
-    Ds = _ao_rotation_matrices(mol, coordinates)
+    Ds = _momentum_rotation_matrices(mol, coordinates)
     so = []
     for i in irrep_idx:
         c_ir = numpy.vstack(sodic[irrep_names[i]]).T