7
$\begingroup$

Background

My colleague and I have built an interface between PySCF and MRCC which seems to work all the time for RHF references, but only sometimes for UHF references. We use PySCF to calculate the integrals and print them in FCIDUMP format, then we convert the FCIDUMP file into MRCC's fort.55 format.

The format of the FCIDUMP follows the UHF standard designed by Knowles and Handy, in which after the header part, separate 4-index and 2-index integrals are written for alpha-alpha, beta-beta, and alph-beta orbitals, with a line of zeroes separating them. Unfortunately, despite this format being used in MOLPRO, and being compatible with the fort.55 file in some major quantum chemistry codes like MRCC or CFOUR, it is not natively supported in PySCF.

The Problem

We considered two open-shell systems, Li and B++. Both systems have 3 electrons occupying s-type orbitals. For B++, the coupled cluster energies obtained from our PySCF-MRCC interface are identical to the ones obtained using MRCC-MRCC (which is how we will label "standalone MRCC" in which the integrals, SCF and AO->MO transformation are calculated using MRCC, and the post-SCF calculations are also done by MRCC).

B++ (6-31G) (symmetry is on)

mrcc-mrcc
CCSD    -23.372809509970
CCSD(T) -23.372810050030
CCSDT   -23.372810049178

pyscf-mrcc
CCSD    -23.372809509970
CCSD(T) -23.372810050030
CCSDT   -23.372810049178

But for Li, this is what we get:

Li (6-31G) (symmetry is on)

mrcc-mrcc
UHF     -7.431235814771
CCSD    -7.431553874113
CCSD(T) -7.431554248616
CCSDT   -7.431554228106

pysc-mrcc
UHF     -7.431235814771
CCSD    -7.431274408997
CCSD(T) -7.431274447587
CCSDT   -7.431274443531

Upon some further debugging, we are finding that the generated fort.55 in the case B++ is similar to the one generated in MRCC in terms of having the same ORBSYM and the same indices for the integrals. However, for the case of Li, the generated fort.55 is having similar ORBSYM with that generated with MRCC but the indices and the number of integrals are not the same. Specifically, the irreducible representationsin of the orbitals:

Li (6-31G) mrcc-mrcc:   
'Ag' 'Ag' 'B1u' 'B2u' 'B3u' 'Ag' 'B1u' 'B2u' 'B3u'

Li (6-31G) pyscf-mrcc:
'Ag' 'Ag' 'B1u' 'B2u' 'B3u' 'Ag' 'B1u' 'B2u' 'B3u'

About the integrals, there are 1163 integrals generated from mrcc-mrcc while 1166 integrals from pyscf-mrcc in the case of Li, thus I won't be able to show all of them here but rather the trend of differences. Interested readers are encouraged to refer to the fort.55 generated with pyscf-mrcc and the other fort.55 generated in mrcc-mrcc. By comparing those two files, you will find that the first section of integrals which corresponds to the alpha-alpha 2e integrals have similar indices, for instance:

Li fort.55 mrcc-mrcc
 3.314991659E-01  2  2  1  1
 2.189901842E-01  3  3  1  1

Li fort.55 pyscf-mrcc
 3.314991659E-01  2  2  1  1
 2.189901842E-01  3  3  1  1

B++ fort.55 mrcc-mrcc
 7.534175484E-01  2  2  1  1
 7.056882647E-01  3  3  1  1

B++ fort.55 pyscf-mrcc
 7.534175484E-01  2  2  1  1
 7.056882647E-01  3  3  1  1

However, in the beta-beta 2e integrals, we start to see differences in the indices and the corresponding coefficients, for instance:

Li fort.55 mrcc-mrcc  
 1.945413288E-01  5  5  1  1
 1.120655588E-01  6  1  1  1
 
Li fort.55 pyscf-mrcc
 1.945413288E-01  5  5  1  1
 7.570801776E-02  6  3  1  1

B++ fort.55 mrcc-mrcc
 6.73635998E-01   5  5  1  1
-2.84983078E-01   6  3  1  1

B++ fort.55 pyscf-mrcc
 6.73635998E-01   5  5  1  1
-2.84983078E-01   6  3  1  1

Similar thing could be observed for alpha-beta integrals as well. In the case of Li, the indices or coefficients are sometimes different between mrcc-mrcc and pyscf-mrcc.

I will point out that the energies actually do match if we turn off symmetry, but then the RAM and CPU time are significantly greater, which means that we won't be able to do very-high-order coupled cluster calculations with big basis sets.

Why is the code described below working for B++ but not for Li?


The code

Suppose that we have defined a Mol object for an open-shell system, followed by running a UHF calculation. In the next step, we convert the 2e integrals using 4-fold permutation symmetry for alpha-alpha, beta-beta, and alpha-beta orbitals which is saved in corresponding eri_aaaa, eri_bbbb, and eri_aabb, respectively. Moreover, the transformed 1e core Hamiltonian for alpha and beta are also calculated and stored in h_aa and h_bb, respectively.

orbs = mf.mo_coeff
nmo = orbs[0].shape[0]

eri_aaaa = pyscf.ao2mo.restore(4,pyscf.ao2mo.incore.general(mf._eri, (orbs[0],orbs[0],orbs[0],orbs[0]), compact=False),nmo)
eri_bbbb = pyscf.ao2mo.restore(4,pyscf.ao2mo.incore.general(mf._eri, (orbs[1],orbs[1],orbs[1],orbs[1]), compact=False),nmo)
eri_aabb = pyscf.ao2mo.restore(4,pyscf.ao2mo.incore.general(mf._eri, (orbs[0],orbs[0],orbs[1],orbs[1]), compact=False),nmo)

h_core = mf.get_hcore(mol)
h_aa = reduce(numpy.dot, (orbs[0].T, h_core, orbs[0]))
h_bb = reduce(numpy.dot, (orbs[1].T, h_core, orbs[1]))
nuc = mol.energy_nuc()

In the next section of the code, we try to handle the symmetry of the orbitals in a way that will match the format of fort.55. We also define the range of values for integral indices:

if mol.symmetry:
        groupname = mol.groupname
        if groupname in ('SO3', 'Dooh'):
            logger.info(mol, 'Lower symmetry from %s to D2h', groupname)
            raise RuntimeError('Lower symmetry from %s to D2h' % groupname)
        elif groupname == 'Coov':
            logger.info(mol, 'Lower symmetry from Coov to C2v')
            raise RuntimeError('''Lower symmetry from Coov to C2v''')
orbsym = pyscf.symm.label_orb_symm(mol,mol.irrep_name,mol.symm_orb,orbs[0])
orbsym = numpy.array(orbsym)
orbsym = [param.IRREP_ID_TABLE[groupname][i]+1 for i in orbsym]
a_inds = [i+1 for i in range(orbs[0].shape[0])]
b_inds = [i+1 for i in range(orbs[1].shape[1])]
nelec = mol.nelec
tol=1e-18

The last part is considered the main part of the script, which is supposed to handle the header of the generated file in a way that will match the fort.55 format. It is supposed to deal with the 4-index, and 2-index integrals in which we are assuming that we are using a 4-fold permutation symmetry. The sections after '4-fold symmetry' are in the sequence of alpha-alpha, beta-beta, alpha-beta 2e integrals, followed by alpha, beta 1e integrals, and finally the nuclear repulsion energy.

with open('fort.55', 'w') as fout:
        if not isinstance(nelec, (int, numpy.number)):
            ms    = abs(nelec[0] - nelec[1])
            nelec =     nelec[0]  + nelec[1]
        else: ms=0
        fout.write(f"{nmo:1d} {nelec:1d}\n")
        if orbsym is not None and len(orbsym) > 0:
            fout.write(f"{' '.join([str(x) for x in orbsym])}\n")
        else:
            fout.write(f"{' 1' * nmo}\n")
        fout.write(' 150000\n')
        output_format = float_format + ' %5d %5d %5d %5d\n'
        #4-fold symmetry
        kl = 0
        for l in range(nmo):
            for k in range(0, l+1):
                ij = 0
                for i in range(0, nmo):
                    for j in range(0, i+1):
                        if i >= k:
                            if abs(eri_aaaa[ij,kl]) > tol:
                                fout.write(output_format % (eri_aaaa[ij,kl], a_inds[i], a_inds[j], a_inds[k], a_inds[l]))
                        ij += 1
                kl += 1
        fout.write(' 0.00000000000000000000E+00' + '     0     0     0     0\n')
        kl = 0
        for l in range(nmo):
            for k in range(0, l+1):
                ij = 0
                for i in range(0, nmo):
                    for j in range(0, i+1):
                        if i >= k:
                            if abs(eri_bbbb[ij,kl]) > tol:
                                fout.write(output_format % (eri_bbbb[ij,kl], b_inds[i], b_inds[j], b_inds[k], b_inds[l]))
                        ij += 1
                kl += 1
        fout.write(' 0.00000000000000000000E+00' + '     0     0     0     0\n')
        ij = 0
        for j in range(nmo):
            for i in range(0, j+1):
                kl = 0
                for k in range(nmo):
                    for l in range(0, k+1):
                        if abs(eri_aabb[ij,kl]) > tol:
                            fout.write(output_format % (eri_aabb[ij,kl], a_inds[i], a_inds[j], b_inds[k], b_inds[l]))
                        kl += 1
                ij +=1

        fout.write(' 0.00000000000000000000E+00' + '     0     0     0     0\n')
        h_aa = h_aa.reshape(nmo,nmo)
        h_bb = h_bb.reshape(nmo,nmo)
        output_format = float_format + ' %5d %5d     0     0\n'
        for i in range(nmo):
            for j in range(nmo):
                fout.write(output_format % (h_aa[i,j], a_inds[i], a_inds[j]))
        fout.write(' 0.00000000000000000000E+00' + '     0     0     0     0\n')
        for i in range(nmo):
            for j in range(nmo):
                fout.write(output_format % (h_bb[i,j], b_inds[i], b_inds[j]))
        fout.write(' 0.00000000000000000000E+00' + '     0     0     0     0\n')
        output_format = float_format + '     0     0     0     0\n'
        fout.write(output_format % nuc)
$\endgroup$
1

1 Answer 1

3
$\begingroup$

The answer to this question brings me back to a question I have asked before on how to handle the orbital symmetries in PySCF from UHF calculation. The following is an answer to both this and the previously asked question.

Taking Li and B++ as a study case was very helpful in finding out the source of the problem. It started from debugging the indices of the integrals printed out in the output fort.55, we just know that the indices in Li fort.55 are being different than those in B++ fort.55 despite that both systems have 3 electrons and very close basis sets (6-31G).

The first observation about the difference in indices was that one of the spin channels is giving correct indices while the other is not. To follow the source of the problem, I compared the 2D matrices of the molecular coefficients which is orbs = mf.mo_coeff in our code. orbs is a tuple made up of two 2D matrices orbs[0] and orbs[1] with shape (n_ao x n_mo) that stands for alpha and beta spin channels, respectively.

Let's first take the Li in (6-31G) basis set and see what those matrices look like along with orbital irrep symmetries in both alpha and beta spins:

Li(alpha)
----------
[[ 0.99916986 -0.18405498   0.           0.           0.          0.08160798    0.          0.          0.        ]
 [ 0.01452023  0.39182563   0.           0.           0.          2.32752816    0.          0.          0.        ]
 [-0.00708556  0.64703095   0.           0.           0.         -2.28686432    0.          0.          0.        ]
 [ 0.          0.           0.           0.           0.15992471  0.            0.          0.          1.66671702]
 [ 0.          0.           0.           0.15992471   0.          0.            0.          1.66671702  0.        ]
 [ 0.          0.           0.15992471   0.           0.          0.            1.66671702  0.          0.        ]
 [ 0.          0.           0.           0.           0.86715839  0.            0.          0.         -1.43232604]
 [ 0.          0.           0.           0.86715839   0.          0.            0.         -1.43232604  0.        ]
 [ 0.          0.           0.86715839   0.           0.          0.           -1.43232604  0.          0.        ]]
  'Ag'        'Ag'         'B1u'        'B2u'        'B3u'       'Ag'         'B1u'        'B2u'        'B3u'

Li(beta)
------
[[ 0.99895047 -0.19961828   0.           0.           0.          0.            0.          0.          0.03357083]
 [ 0.01704519 -0.1818869    0.           0.           0.          0.            0.          0.          2.35324289]
 [-0.00796785  1.18016357   0.           0.           0.          0.            0.          0.         -2.06291012]
 [ 0.          0.           0.           0.          -0.07524436  0.            0.          1.67268044  0.        ]
 [ 0.          0.           0.          -0.07524436   0.          0.            1.67268044  0.          0.        ]
 [ 0.          0.          -0.07524436   0.           0.          1.67268044    0.          0.          0.        ]
 [ 0.          0.           0.           0.           1.05934049  0.            0.         -1.29665696  0.        ]
 [ 0.          0.           0.           1.05934049   0.          0.           -1.29665696  0.          0.        ]
 [ 0.          0.           1.05934049   0.           0.         -1.29665696    0.          0.          0.        ]]
  'Ag'        'Ag'         'B1u'        'B2u'        'B3u'       'B1u'         'B2u'       'B3u'       'Ag'

Next we look at the matrices from B++ in (6-31G) basis set:

B++(alpha)
---------
[[ 0.99777896 -0.2378433   0.          0.          0.          0.              0.          0.          0.02207402]
 [ 0.01311857  0.74857217  0.          0.          0.          0.              0.          0.         -1.74187139]
 [-0.00377825  0.31119524  0.          0.          0.          0.              0.          0.          1.86004522]
 [ 0.          0.          0.          0.          0.76962899  0.              0.         -1.02182872  0.        ]
 [ 0.          0.          0.          0.76962899  0.          0.             -1.02182872  0.          0.        ]
 [ 0.          0.          0.76962899  0.          0.         -1.02182872      0.          0.          0.        ]
 [ 0.          0.          0.          0.          0.31880524  0.              0.          1.23888092  0.        ]
 [ 0.          0.          0.          0.31880524  0.          0.              1.23888092  0.          0.        ]
 [ 0.          0.          0.31880524  0.          0.          1.23888092      0.          0.          0.        ]]
  'Ag'        'Ag'         'B1u'      'B2u'       'B3u'        'B1u'           'B2u'       'B3u'       'Ag'

B++(beta)
--------
[[ 0.9972463  -0.23531008  0.          0.          0.          0.             0.            0.         -0.05242638]
 [ 0.01672256  0.52970147  0.          0.          0.          0.             0.            0.          1.82038047]
 [-0.00527263  0.53654142  0.          0.          0.          0.             0.            0.         -1.80796031]
 [ 0.          0.          0.          0.          0.71032284  0.             0.           -1.06390986  0.        ]
 [ 0.          0.          0.          0.71032284  0.          0.            -1.06390986    0.          0.        ]
 [ 0.          0.          0.71032284  0.          0.         -1.06390986     0.            0.          0.        ]
 [ 0.          0.          0.          0.          0.38868623  0.             0.            1.21876402  0.        ]
 [ 0.          0.          0.          0.38868623  0.          0.             1.21876402    0.          0.        ]
 [ 0.          0.          0.38868623  0.          0.          1.21876402     0.            0.          0.        ]] 
  'Ag'        'Ag'         'B1u'      'B2u'       'B3u'        'B1u'           'B2u'       'B3u'       'Ag'

You can see that the difference between those two study cases is in the symmetries of the orbitals. B++ and for some reason had the same orbital symmetries in alpha and beta spins, while for Li, it was not.

The answer to the question 'Why does our PySCF-MRCC interface work for B++ but not for Li?'

The difference in the orbital symmetries of the above two matrices orbs[0] and orbs[1] will further affect the eri_aaaa, eri_bbbb, and eri_aabb matrices leading to fort.55 with unexpected indices that are different than what we get in the case of B++ or from MRCC-MRCC.

The solution to this problem would be by arranging the columns in the above matrices to always guarantee that orbitals have the same irrep symmetries berfore building any of the eri matrices.

The following two functions are added to the code, the first one for extracting the orbitals symmetries and the second for re-arranging the columns of beta matrices or orbs[1] to always have the same irrep as the alpha or orbs[0] matrices:

def compute_mo_irreps(mol, mo_coeff):    
    symm_orbs = mol.symm_orb 
    irrep_labels = mol.irrep_name  
    mo_irreps = []
    for mo in mo_coeff.T: 
        projections = [numpy.linalg.norm(symm_orbs[i].T @ mo) for i in range(len(symm_orbs))]
        irrep_idx = numpy.argmax(projections)  
        mo_irreps.append(irrep_labels[irrep_idx])
    return mo_irreps

def align_beta_orbitals_to_alpha(mol, orbs):
    alpha_orbs, beta_orbs = orbs
    alpha_irreps = compute_mo_irreps(mol, mf.mo_coeff[0])
    beta_irreps = compute_mo_irreps(mol, mf.mo_coeff[1])
    beta_orbs_sorted = []
    used_indices = set()
    for target_irrep in alpha_irreps:
        for idx, beta_irrep in enumerate(beta_irreps):
            if beta_irrep == target_irrep and idx not in used_indices:
                beta_orbs_sorted.append(beta_orbs[:, idx])
                used_indices.add(idx)
                break
        else:
            raise ValueError(f"No matching beta orbital found for alpha irrep: {target_irrep}")
    beta_orbs_sorted = numpy.column_stack(beta_orbs_sorted)
    return alpha_orbs, beta_orbs_sorted

Validation:

In order to validate the above suggested solution, we re-examine Li (6-31G) with the modified code, generate the fort.55 and use it with mrcc, the energies came out matching between mrcc-mrcc and pyscf-mrcc:

mrcc-mrcc:
CCSD    -7.431553874113
CCSD[T] -7.431554248391
CCSD(T) -7.431554248616
CCSDT   -7.431554228106
-----------------------
pyscf-mrcc(before)
CCSD    -7.431274408997
CCSD[T] -7.431274447668
CCSD(T) -7.431274447587
CCSDT   -7.431274443534
----------------------
pyscf-mrcc(after)
CCSD    -7.431553874113
CCSD[T] -7.431554248391
CCSD(T) -7.431554248616
CCSDT   -7.431554228106

To further validate the solution, we have examined a bigger system, Boron (5 electrons) in aVDZ, you can also see that the energies are matching after using the modified code:

mrcc-mrcc
CCSD    -24.591070884367
CCSD[T] -24.592032345321
CCSD(T) -24.592013924427
CCSDT   -24.592415126975
----------------------
pyscf-mrcc(before):
CCSD    -24.540498169179
CCSD[T] -24.540534632400
CCSD(T) -24.540533400675
CCSDT   -24.540549543244
------------------------
pyscf-mrcc(after)
CCSD    -24.591070884367
CCSD[T] -24.592032345321
CCSD(T) -24.592013924427
CCSDT   -24.592415126975
  • Acknowledgment: I would like to thank Nike Dattani for the discussion we had on this question. It was quite helpful in finding out the source of the problem.
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .