Creating and manipulating Hamiltonians
This page explains how to create and manipulate Hamiltonian using qat.fermion
module
Defining a Hamiltonian
Many problems in physics are defined by their Hamiltonians. The basic objects we will manipulate are
SpinHamiltonian and FermionHamiltonian.
These classes can be seen as an Observable constructor, of which they inherit,
while containing additional methods.
Let us see in more details how to define a SpinHamiltonian and a
FermionHamiltonian.
Spin Hamiltonians
The spin Hamiltonian acting on 2 qubits \(0\) and \(1\) defined by \(H = 0.3 X_{0} - 0.4 Z_{0}Y_{1}\) can be written:
from qat.core import Term
from qat.fermion.hamiltonians import SpinHamiltonian
# Define the number of qubits
nqbits = 2
# Define the Hamiltonian
H = SpinHamiltonian(nqbits, [Term(0.3, "X", [0]), Term(-0.4, "ZY", [0, 1])])
print(f"H = {H}")
H = 0.3 * (X|[0]) +
-0.4 * (ZY|[0, 1])
Fermionic Hamiltonians
If we consider now a fermionic Hamiltonian \(H = 0.3 (C^{\dagger}_{0}C_{1} + C^{\dagger}_{1}C_{0}) + 1.4 C^{\dagger}_{0}C_{1}C^{\dagger}_{1}C_{0}\), we should write:
from qat.core import Term
from qat.fermion.hamiltonians import FermionHamiltonian
# Define the number of qubits
nqbits = 2
# Define the Hamiltonian
H = FermionHamiltonian(nqbits, [Term(0.3, "Cc", [0, 1]), Term(0.3, "Cc", [1, 0]), Term(1.4, "CcCc", [0, 1, 1, 0])])
print(f"H = {H}")
H = 0.3 * (Cc|[0, 1]) +
0.3 * (Cc|[1, 0]) +
1.4 * (Cc|[0, 0]) +
1.4 * (CCcc|[0, 1, 0, 1])
Note
When compatible, FermionHamiltonian can be transformed to
ElectronicStructureHamiltonian via the method
to_electronic().
Fermionic Hamiltonian using one and two-electrons integrals
In chemistry, problems are often more easily described using interaction terms instead of fermionic operators.
The electronic-structure Hamiltonian is defined by:
The definition of this Hamiltonian is done via the ElectronicStructureHamiltonian class, which
accepts the one and two-body terms \(h_{pq}\) and \(h_{pqrs}\) as inputs.
import numpy as np
from qat.fermion import ElectronicStructureHamiltonian
# Define the interaction integrals
h_pq = 0.2 * np.array([[0, 1], [1, 0]])
h_pqrs = np.zeros((2, 2, 2, 2))
h_pqrs[0, 1, 1, 0] = 0.7
h_pqrs[1, 0, 0, 1] = 0.7
# Define the ElectronicStructureHamiltonian
H_elec = ElectronicStructureHamiltonian(h_pq, h_pqrs, -6)
ElectronicStructureHamiltonian can be transformed to
FermionHamiltonian via the method
to_fermion().
The ElectronicStructureHamiltonian inherits from the
FermionHamiltonian class, and thus contains every method implemented in
FermionHamiltonian.
Note
An alternative definition for the electronic-structure Hamiltonian is :
Should you need to define an ElectronicStructureHamiltonian using the one- and two-body integrals \(I_{uv}\) and \(I_{uvwx}\), you have two options:
convert the \(I_{uv},I_{uvwx}\) to \(h_{pq},h_{pqrs}\) using the
qat.fermion.chemistry.ucc.convert_to_h_integrals()function and define theElectronicStructureHamiltonianusing \(h_{pq},h_{pqrs}\);define a
MolecularHamiltonianusing \(I_{uv},I_{uvwx}\) and extract theElectronicStructureHamiltonianusing itsget_electronic_hamiltonian()method.
For more information on this type of body integrals, see the class MolecularHamiltonian documentation.
You can also consult the Jupyter notebook on spin-fermion transforms.
The get_matrix() method
The objects SpinHamiltonian, FermionHamiltonian and
ElectronicStructureHamiltonian allow for the direct usage of the underlying Hamiltonian matrix.
Bare in mind that this method should not be used for big Hamiltonians, as the memory cost might be too much to handle on your machine.
You can access the Hamiltonian matrix by using the get_matrix() method of the
Hamiltonian classes.
from qat.core import Term
from qat.fermion import SpinHamiltonian
H = SpinHamiltonian(2, [Term(0.5, "Y", [0]), Term(0.5, "Y", [1])])
print(H.get_matrix())
[[0.+0.j 0.-0.5j 0.-0.5j 0.+0.j ]
[0.+0.5j 0.+0.j 0.+0.j 0.-0.5j]
[0.+0.5j 0.+0.j 0.+0.j 0.-0.5j]
[0.+0.j 0.+0.5j 0.+0.5j 0.+0.j ]]
Fermionic to spin representation
A problem formulated in fermionic representation often needs to be converted to a spin representation, so that it can be handled by
a quantum computer. To do so, one can use the to_spin() method.
Three transforms are available:
the Jordan-Wigner transform (default),
the Bravyi-Kitaev transform,
the parity method.
Example
Let us transform the previous ElectronicStructureHamiltonian to a spin Hamiltonian:
import numpy as np
from qat.fermion import ElectronicStructureHamiltonian
# Define the fermionic Hamiltonian
h_pq = 0.2 * np.array([[0, 1], [1, 0]])
h_pqrs = np.zeros((2, 2, 2, 2))
h_pqrs[0, 1, 1, 0] = 0.7
h_pqrs[1, 0, 0, 1] = 0.7
# Define the Hamiltonian
H_fermion = ElectronicStructureHamiltonian(h_pq, h_pqrs, -6)
# Transform it to a spin Hamiltonian using Bravyi-Kitaev transform
H_spin = H_fermion.to_spin(method="bravyi-kitaev")
# Similarly, we could have used "jordan-wigner" or "parity"
print(f"H = {H_spin}")
H = (-5.825+0j) * I^2 +
(0.1+0j) * (X|[0]) +
(-0.1+0j) * (XZ|[0, 1]) +
(0.175+0j) * (Z|[1]) +
(-0.175+0j) * (Z|[0]) +
(-0.175+0j) * (ZZ|[0, 1])
Transforming a ElectronicStructureHamiltonian into a spin Hamiltonian
relies on functions located in the qat.fermion.transforms module:
Jordan-Wigner transform uses
transform_to_jw_basis()Bravyi-Kitaev transform uses
transform_to_bk_basis()Parity basis transform uses
transform_to_parity_basis()
Spin and fermionic Hamiltonian operations
Spin and fermionic Hamiltonian handle basic algebraic operations. This allows for the computation of commutators in both spin and fermionic representations:
import numpy as np
from qat.core import Term
from qat.fermion.hamiltonians import FermionHamiltonian
H_fermion1 = FermionHamiltonian(2, [Term(1.0, "Cc", [0, 1]), Term(0.5, "CCcc", [0, 1, 0, 1])])
H_fermion2 = FermionHamiltonian(2, [Term(1.0, "Cc", [1, 0]), Term(0.5, "CCcc", [1, 0, 1, 0])])
H_spin1 = H_fermion1.to_spin()
H_spin2 = H_fermion2.to_spin()
fermion_comutator_matrix = (H_fermion1 | H_fermion2).get_matrix()
spin_comutator_matrix = (H_spin1 | H_spin2).get_matrix()
is_equal_sign = "=" if np.all(np.equal(fermion_comutator_matrix, spin_comutator_matrix)) else "!="
print(f"Fermionic commutator {is_equal_sign} spin commutator matrix")
Fermionic commutator = spin commutator matrix
Note
In some cases, it is preferable to compute commutators in fermionic representation rather than in spin representation, as the built-in Wick ordering might simplify many fermionic terms, which may speed up the commutator computation as well as subsequent computations
Some Hamiltonian constructors
The Hubbard model or the Anderson model are very widely used. For that reason, we included several Hamiltonian constructors to help you define the system you are interested in more easily.
Module qat.fermion.hamiltonians provide tools to create Hamiltonian based on these models:
Hubbard Hamiltonian: the toy-model of strong correlations - this Hamiltonian can be created using
make_hubbard_model()Single-impurity Anderson Model: describes one correlated site embedded in a fermionic bath - this Hamiltonian can be created using
make_anderson_model()Embedded Hamiltonian: describes a cell of N correlated sites embedded in a N-level fermionic bath - this Hamiltonian can be created using
make_embedded_model()