# 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", [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.")
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 :

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 the`ElectronicStructureHamiltonian`

using \(h_{pq},h_{pqrs}\);define a

`MolecularHamiltonian`

using \(I_{uv},I_{uvwx}\) and extract the`ElectronicStructureHamiltonian`

using its`get_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())
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]) +
(-0.1+0j) * (XZ|[0, 1]) +
(0.175+0j) * (Z|[1]) +
(-0.175+0j) * (Z|[0]) +
(-0.175+0j) * (ZZ|[0, 1])
```

More information about the transforms is available below:

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