This is a tutorial on how to implement a simple program to solve the Hartree-Fock with SCF iterations.
We will be following the algorithm described in Szabo and Ostlund, 3.4.6, p. 146 to implement the Hartree-Fock method for the hydrogen molecule. The steps are as follows:
- Obtain a guess at the density matrix.
- Calculate the exchange and coulomb matrices from the density matrix and the two-electron repulsion integrals.
- Add exchange and coulomb matrices to the core-Hamiltonian to obtain the Fock matrix.
- Diagonalize the Fock matrix.(use eigh)
- Select the occupied orbitals and calculate the new density matrix.
- Compute the energy.
- Compute the errors and check for convergence.
- If converged, return the energy.
- If not converged, return to the second step with the new density matrix.
To start, you can implement this algorithm for the hydrogen molecule by running the following code:
mol = "h2"
r = 1.0
inp = f"{mol}-{r:.4f}"
e = main(inp) # H2 molecule with bond length 1.0
then try some more complicated molecules, such as HeH+
mol = "heh+"
r = 1.0
inp = f"{mol}-{r:.4f}"
e = main(inp) # HeH+ molecule with bond length 1.0
and H2O
mol = "h2o"
r = 1.0
inp = f"{mol}-{r:.4f}"
e = main(inp) # water molecule with bond length 1.0
After implementing the self-consistent field (SCF) procedure, we can use it to compute the potential energy surface of the
for r in numpy.linspace(0.5, 3.0, 61):
inp = f"h2-{r:.4f}"
e = main(inp)
print(f"H2: r={r: 6.4f}, e={e: 12.8f}")
You can also try other molecules, such as matplotlib
package.
An example of how to plot the data can be found in the file ./data/plot.ipynb.
The unrestricted Hartree-Fock (UHF) method can be used to study molecules that have broken spin-symmetry, such as those that have a non-zero magnetic moment or unpaired electrons. In this case, the potential energy surface can be different from that obtained from the restricted Hartree-Fock method.
To implement the UHF method, we will create a new function solve_uhf
that follows the same steps as the restricted Hartree-Fock method, but with the added flexibility of allowing different spin-orbitals to have different occupation numbers.
There have something different between RHF and UHF. In UHF method, you need to solve two generalized eigenvalue problems:
And the exchange and coulomb matrices become to
$$ \begin{cases} G^{\alpha}{\mu \nu} = \sum{\lambda \sigma} \left[ P_{\lambda \sigma}^{t} (\mu \nu | \lambda \sigma) - P_{\lambda \sigma}^{\alpha} (\mu \sigma | \lambda \nu) \right] \ G^{\beta}{\mu \nu} = \sum{\lambda \sigma} \left[ P_{\lambda \sigma}^{t} (\mu \nu | \lambda \sigma) - P_{\lambda \sigma}^{\beta} (\mu \sigma | \lambda \nu) \right] \ P^{t} = P^{\alpha} + P^{\beta} \end{cases} $$
The Fock matrix building is the same as RHF, just add
If you use the one-electron matrix guess to calculate molecules that all electrons are paired, such as hydrogen, water, methane, etc. You will find that
The simplest method is to mix the HOMO and the LUMO use this formula if they are orthonormal
It's important to note that even if you have implemented the UHF method, the results may not be consistent with the reference. In order to get the correct dissociation potential energy surface, you may need to carefully break the spin symmetry.
When you first multiply two matrices, you maybe calculate it in a loop. But in numpy, you can improve it with einsum. Such as an n-col k-row matrix A times a k-col m-row matrix B got an n-col m-row matrix C can be written like
numpy.eninsum("nk, km -> nm", A, B, C)
It means
numpy
scipy
- If you wish to use
./data/gen_data.py
to generate the integrals,pyscf
is also required.
- Szabo and Ostlund, Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory, Dover Publications, New York, 1996
- Helgaker, Jørgensen, and Olsen, Molecular Electronic-Structure Theory, Wiley, New York, 2000