Creating and manipulating Hamiltonians
This page explains how to create and manipulate Hamiltonian using qat.fermion
module
Table of Contents
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)
print(f"H_elec is in {H_elec.htype.name} representation")
Traceback (most recent call last):
File "<stdin>", line 13, in <module>
AttributeError: 'ElectronicStructureHamiltonian' object has no attribute 'htype'
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 theElectronicStructureHamiltonian
using \(h_{pq},h_{pqrs}\);define a
MolecularHamiltonian
using \(I_{uv},I_{uvwx}\) and extract theElectronicStructureHamiltonian
using 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()