Loading [MathJax]/jax/output/CommonHTML/jax.js
/ Demos / Quantum Chemistry / Fermionic operators

Fermionic operators

Published: June 27, 2023. Last updated: December 13, 2024.

Fermionic creation and annihilation operators are commonly used to construct Hamiltonians and other observables of molecules and spin systems 1. In this demo, you will learn how to use PennyLane to create fermionic operators and map them to a qubit representation for use in quantum algorithms.

Constructing fermionic operators

The fermionic creation and annihilation operators can be constructed in PennyLane similarly to Pauli operators by using FermiC and FermiA for creation and annihilation operators, respectively.

from pennylane import FermiC, FermiA

a0_dag = FermiC(0) a1 = FermiA(1)

We used the compact notations a0_dag to denote a creation operator applied to the 0th orbital and a1 to denote an annihilation operator applied to the 1st orbital. Once created, these operators can be multiplied or added to each other to create new operators. A product of fermionic operators will be called a Fermi word and a linear combination of Fermi words will be called a Fermi sentence.

fermi_word = a0_dag * a1
fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1
print(fermi_sentence)
1.3 * a⁺(0) a(1)
+ 2.4 * a(1)

In this simple example, we first created the operator a0a1 and then created the linear combination 1.3a0a1+2.4a1. We can also perform arithmetic operations between Fermi words and Fermi sentences.

fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word
print(fermi_sentence)
1.3 * a⁺(0) a(1) a⁺(0) a(1)
+ 2.4 * a(1) a⁺(0) a(1)
+ 2.3 * a⁺(0) a(1)

Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in PennyLane to an integer power. For instance, we can create a more complicated operator

1.2×a0+0.5×a12.3×(a0a1)2,

in the same way that you would write down the operator on a piece of paper:

fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2
print(fermi_sentence)
1.2 * a⁺(0)
+ 0.5 * a(1)
+ -2.3 * a⁺(0) a(1) a⁺(0) a(1)

This Fermi sentence can be mapped to the qubit basis using the Jordan-Wigner transformation to get a linear combination of Pauli operators.

from pennylane import jordan_wigner

pauli_sentence = jordan_wigner(fermi_sentence) pauli_sentence
(
    0.6 * X(0)
  + -0.6j * Y(0)
  + 0.25 * (Z(0) @ X(1))
  + 0.25j * (Z(0) @ Y(1))
)

Fermionic Hamiltonians

Now that we have nice tools to create and manipulate fermionic operators, we can build some interesting fermionic Hamiltonians.

A toy model

Our first example is a toy Hamiltonian inspired by the Hückel method, which is a method for describing molecules with alternating single and double bonds. Our toy model is a simplified version of the Hückel Hamiltonian and assumes only two orbitals and a single electron.

H=α(a0a0+a1a1)+β(a0a1+a1a0).

This Hamiltonian can be constructed with pre-defined values α=0.01 and β=0.02.

h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1))
h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0))
h = h1 + h2
print(h)
0.01 * a⁺(0) a(0)
+ 0.01 * a⁺(1) a(1)
+ -0.02 * a⁺(0) a(1)
+ -0.02 * a⁺(1) a(0)

The fermionic Hamiltonian can be converted to the qubit Hamiltonian with:

h = jordan_wigner(h)

The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized to get its eigenpairs.

import numpy as np

val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) print(f"eigenvalues:\n{val}") print() print(f"eigenvectors:\n{np.real(vec.T)}")
eigenvalues:
[-0.01  0.    0.02  0.03]

eigenvectors: [[-0. -0.70710678 -0.70710678 -0. ] [ 1. 0. 0. 0. ] [ 0. 0. 0. 1. ] [ 0. -0.70710678 0.70710678 0. ]]

Hydrogen molecule

The second quantized molecular electronic Hamiltonian is usually constructed as

H=α{,}pqcpqap,αaq,α+12α,β{,}pqrscpqrsap,αaq,βar,βas,α,

where α and β denote the electron spin and p,q,r,s are the orbital indices. The coefficients c are integrals over molecular orbitals that are obtained from Hartree-Fock calculations. These integrals can be computed with PennyLane using the electron_integrals() function. We can build the molecular Hamiltonian for the hydrogen molecule as an example. We first define the atom types and the atomic coordinates.

import pennylane as qml
from jax import numpy as jnp

symbols = ["H", "H"] geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]])

Then we compute the one- and two-electron integrals, which are the coefficients c in the second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is later used to calculate the contribution of the nuclear energy to the Hamiltonian.

mol = qml.qchem.Molecule(symbols, geometry)
core, one, two = qml.qchem.electron_integrals(mol)()

These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of electrons with different spins. We have assumed that the spatial distribution of these electron pairs is the same to simplify the calculation of the integrals. However, to properly account for all electrons, we need to duplicate the integrals for electrons with the same spin. For example, the pq integral, which is the integral over the orbital p and the orbital q, can be used for both spin-up and spin-down electrons. Then, if we have a 2×2 matrix of such integrals, it will become a 4×4 matrix. The code block below simply extends the integrals by duplicating terms to account for both spin-up and spin-down electrons.

for i in range(4):
    if i < 2:
        one = one.repeat(2, axis=i)
    two = two.repeat(2, axis=i)

We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, which are the first part in the Hamiltonian above, can be added first. We will use itertools to efficiently create all the combinations we need. Some of these combinations are not allowed because of spin restrictions and we need to exclude them. You can find more details about constructing a molecular Hamiltonian in reference 1.

import itertools

n = one.shape[0]

h = 0.0

for p, q in itertools.product(range(n), repeat=2): if p % 2 == q % 2: # to account for spin-forbidden terms h += one[p, q] * FermiC(p) * FermiA(q)

The two-body terms can be added with:

for p, q, r, s in itertools.product(range(n), repeat=4):
    if p % 2 == s % 2 and q % 2 == r % 2:  # to account for spin-forbidden terms
        h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s)

We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to the qubit basis.

h.simplify()
h = jordan_wigner(h)

We also need to include the contribution of the nuclear energy.

h += np.sum(core * qml.Identity(0))

This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can also compute the ground-state energy by diagonalizing the matrix representation of the Hamiltonian in the computational basis.

np.linalg.eigh(h.sparse_matrix().toarray())[0].min()
-1.1368472302251442

Summary

This demo explains how to create and manipulate fermionic operators in PennyLane, which is as easy as writing the operators on paper. PennyLane supports several arithmetic operations between fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and intuitive to construct complicated fermionic Hamiltonians such as molecular Hamiltonians.

References

1(1,2)

Peter R. Surjan, “Second Quantized Approach to Quantum Chemistry”. Springer-Verlag, 1989.

About the author

Total running time of the script: (0 minutes 3.164 seconds)

Soran Jahangiri

Soran Jahangiri

I am a quantum scientist and software developer working at Xanadu. My work is focused on developing and implementing quantum algorithms in PennyLane.

Total running time of the script: (0 minutes 3.164 seconds)