# Basic usage

We will describe here the basic usage of the 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_{1}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", ), Term(-0.4, "ZY", [0, 1])])

>>> print(f"H = {H}")
H = 0.3 * (X|) +
-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])


### 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:

$H = \sum_{pq} h_{pq}a_p^\dagger a_q + \frac{1}{2} \sum_{pqrs} h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s + r \mathbb{I}$

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.")
H_elec is in FERMION representation.


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 :

$H = \sum_{uv\sigma} I_{uv}c^{\dagger}_{u\sigma}c_{v\sigma} + \frac{1}{2}\sum_{uvwx}\sum_{\sigma \sigma'} I_{uvwx}c^{\dagger}_{u\sigma}c^{\dagger}_{v\sigma'}c_{k\sigma'}c_{l\sigma} + r\mathbb{I}$

Should you need to define an ElectronicStructureHamiltonian using the one- and two-body integrals $$I_{uv}$$ and $$I_{uvwx}$$, you have two options:

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", ), Term(0.5, "Y", )])

>>> print(H.get_matrix())
array([[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.

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.1+0j) * (XZ|[0, 1]) +
(0.175+0j) * (Z|) +
(-0.175+0j) * (Z|) +
(-0.175+0j) * (ZZ|[0, 1])


## 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.

Here is a list of the different models currently implemented: