Complete Active Space Self-Consistent Field

User guide for how to run complete active space self-consistent field (CASSCF) through DaltonProject.

Specifying CAS

To a run a CASSCF calculation through the DaltonProject, the first to do is to specify molecule and basis set:

import daltonproject as dp

molecule, basis = dp.mol_reader('pyridine.mol')

To run a CASSCF calculation, an initial guess of orbitals is needed, one of the simplest guesses is Hartree-Fock orbitals. A Hartree-Fock calculation can be run to generate these orbitals:

hf = dp.QCMethod('HF')
prop = dp.Property(energy=True)
hf_result = dp.dalton.compute(molecule, basis, hf, prop)
print('Hartree-Fock energy =', hf_result.energy)
# Hartree-Fock energy = -243.637999873274
print('Electrons =', hf_result.num_electrons)
# Electrons = 42

No special options are needed for the Hartree-Fock calculation. The simplest possible CAS would be to include the HOMO and LUMO orbitals, i.e. two active orbitals. Since pyridine has 42 electrons, pyridine will have 21 doubly occupied orbitals. If the HOMO is to be included in the active space there will then be 20 inactive orbitals left. This choice will correspond to a CAS(2,2) from canonical Hartree-Fock orbitals. The CASSCF calculation is now ready to be setup.

casscf = dp.QCMethod('CASSCF')
casscf.complete_active_space(2, 2, 20)
casscf.input_orbital_coefficients(hf_result.orbital_coefficients)
prop = dp.Property(energy=True)
casscf_result = dp.dalton.compute(molecule, basis, casscf, prop)
print('CASSCF energy =', casscf_result.energy)
# CASSCF energy = -243.642555771886

The specification complete_active_space(2, 2, 20) is of the form complete_active_space(active electrons, active orbitals, inactive orbitals). The line input_orbital_coefficients(hf_output.orbital_coefficients) specifies the initial orbitals. Here the initial orbitals are taken from the previously run Hartree-Fock calculation.

The complete Python script for this example is:

import daltonproject as dp

molecule, basis = dp.mol_reader('pyridine.mol')

hf = dp.QCMethod('HF')
prop = dp.Property(energy=True)
hf_result = dp.dalton.compute(molecule, basis, hf, prop)
print('Hartree-Fock energy =', hf_result.energy)
# Hartree-Fock energy = -243.637999873274
print('Electrons =', hf_result.num_electrons)
# Electrons = 42

casscf = dp.QCMethod('CASSCF')
casscf.complete_active_space(2, 2, 20)
casscf.input_orbital_coefficients(hf_result.orbital_coefficients)
prop = dp.Property(energy=True)
casscf_result = dp.dalton.compute(molecule, basis, casscf, prop)
print('CASSCF energy =', casscf_result.energy)
# CASSCF energy = -243.642555771886

Picking CAS based on Natural Orbital Occupations

The Dalton Project provides tools to help select a CAS based on natural orbital occupations. As an example, let us use these tools on MP2 natural orbital occupations.

import daltonproject as dp

molecule, basis = dp.mol_reader('pyridine.mol')
molecule.analyze_symmetry()

mp2 = dp.QCMethod('MP2')
prop = dp.Property(energy=True)
mp2_result = dp.dalton.compute(molecule, basis, mp2, prop)
print(mp2_result.filename)
# b2773ec9e68d368d0c4b6889a5ec6299edc94101
print('MP2 energy =', mp2_result.energy)
# MP2 energy = -243.9889166118

Note also that symmetry has been enabled. Now the natural orbital occupations can be inspected:

import daltonproject as dp

mp2_output = dp.dalton.OutputParser('b2773ec9e68d368d0c4b6889a5ec6299edc94101')
nat_occs = mp2_output.natural_occupations
print(dp.natural_occupation.scan_occupations(nat_occs))

The method scan_occupations(natural occupation numbers) will per default list the 14 most important strongly occupied natural orbitals and weakly occupied natural orbitals. For this example, the print will produce the output below

Strongly occupied natural orbitals                      Weakly occupied natural orbitals
Symmetry   Occupation   Change in occ.   Diff. to 2     Symmetry   Occupation   Change in occ.
   2          1.9345        0.0000        0.0655            2          0.0698        0.0000
   4          1.9367        0.0022        0.0633            4          0.0630        0.0068
   2          1.9674        0.0307        0.0326            2          0.0325        0.0304
   3          1.9766        0.0093        0.0234            3          0.0218        0.0107
   1          1.9782        0.0016        0.0218            1          0.0207        0.0011
   3          1.9788        0.0006        0.0212            3          0.0180        0.0027
   1          1.9832        0.0044        0.0168            1          0.0175        0.0005
   3          1.9835        0.0003        0.0165            1          0.0166        0.0009
   1          1.9862        0.0026        0.0138            3          0.0128        0.0038
   1          1.9870        0.0008        0.0130            1          0.0126        0.0002
   3          1.9886        0.0016        0.0114            1          0.0119        0.0007
   1          1.9895        0.0009        0.0105            3          0.0119        0.0000
   3          1.9908        0.0013        0.0092            3          0.0118        0.0001
   1          1.9924        0.0015        0.0076            1          0.0105        0.0012

The scan of the natural occupation numbers can aid the user in picking a suitable active space. In this example, the minimal active space has been highlighted in yellow. This active space has been picked as the minimal active space since the most significant jump in natural occupations happens to the next orbitals. The CASSCF calculation can now be setup.

import daltonproject as dp

molecule, basis = dp.mol_reader('pyridine.mol')
molecule.analyze_symmetry()
mp2_output = dp.dalton.OutputParser('b2773ec9e68d368d0c4b6889a5ec6299edc94101')

casscf = dp.QCMethod('CASSCF')
prop = dp.Property(energy=True)
nat_occs = mp2_output.natural_occupations
electrons, cas, inactive = dp.natural_occupation.pick_cas_by_thresholds(nat_occs, 1.94, 0.06)
casscf.complete_active_space(electrons, cas, inactive)
casscf.input_orbital_coefficients(mp2_output.orbital_coefficients)
casscf_result = dp.dalton.compute(molecule, basis, casscf, prop)
print('CASSCF energy =', casscf_result.energy)
# CASSCF energy = -243.699847108852

Since we determined the proper active space, based on natural orbital occupations, the number of active electrons, active orbitals, and inactive orbitals does not need to be manually specified. Dalton Project can determine these quantities based on the threshold we pick for the strongly occupied natural orbitals and weakly occupied natural orbitals. The method pick_cas_by_thresholds(nat_occs, 1.94, 0.06) is of the form pick_cas_by_thresholds(natural occupation numbers, strongly occupied threshold, weakly occupied threshold). All strongly occupied natural orbitals below the threshold will be included in the active space together with all weakly occupied natural orbitals above the threshold. For this specific example electrons=4, cas=[0, 2, 0, 2] and inactive=[11, 1, 7, 0].