An example with a fermionic Hamiltonian¶
QutipQPU can support schedules and observables written in a second-quantized language (not necessarily only spins as is customary). Here, we show a small example on how to evolve a fermionic Hamiltonian.
Let us consider the case of two spinless fermions interacting via an interaction term with strength $U$ and a hopping (kinetic) term $J(t)$:
$$\large H(t) = J(t) \left( c_0^\dagger c_1 + c_1^\dagger c_0 \right ) + U n_0 n_1 $$
On the QLM we use the letter C for $c^\dagger$ and c for $c$ to encode the fermionic creation and annihilation operators, respectively. These come together to define the number operator $n_i = c^\dagger_i c_i$ in the above example. With this in mind, let's construct the Schedule encapsulating $H(t)$ and its evolution time:
import numpy as np
from qat.core import Observable, Term, Schedule
from qat.core.variables import Variable, sin
# Define a time Variable
t = Variable("t", float)
# Create and display the Schedule
J = sin(t)
U = 1.0
evolution_duration = np.pi
drive = [(J, Observable(2, pauli_terms=[Term(1, "Cc", [0, 1]), Term(1, "Cc", [1, 0])])),
(U, Observable(2, pauli_terms=[Term(1, "CcCc", [0, 0, 1, 1])])) ]
schedule = Schedule(drive=drive,
tmax=evolution_duration)
schedule.display()
Define an observable¶
We are interested in the expectation value of the following observable:
$ \hat O = n_0 $ i.e the occupation of site $0$.
In the cell below, we define such an observable:
obs = Observable(2, pauli_terms=[Term(1, 'Cc', [0, 0])])
Simulate in a noisy and noiseless setting¶
We then do a simulation by constructing a Job and submitting it to QutipQPU.
In this job, we also specify the initial state, here $\left|01\right>$, i.e. orbital $0$ is empty while orbital $1$ is occupied.
We simulate with two processors:
- an ideal processor (with no noise)
- a processor with dephasing noise (via Lindblad operators $n_0$ and $n_1$)
# Construct the Job
job = schedule.to_job(job_type="OBS", observable=obs, psi_0="01")
from qat.qpus import QutipQPU
# Create a noiseless QPU and submit the job
qpu = QutipQPU(sim_method="deterministic",
nsteps=50) # number of time steps for integration
res = qpu.submit(job)
print("noiseless <n_0 (t_max)> =", res.value)
[1/1] Cythonizing qtcoeff_f230db93af3a399e4166e330cf4663.pyx
running build_ext
building 'qtcoeff_f230db93af3a399e4166e330cf4663' extension
g++ -fno-strict-overflow -Wsign-compare -DNDEBUG -g -O3 -Wall -I/usr/local/lib64/openmpi/../../include/openmpi-x86_64 -fPIC -I/usr/local/qaptiva/python_references/112/rhel9/ref312/lib/python3.12/site-packages/qutip/core/data -I/usr/local/qaptiva/python_references/112/rhel9/ref312/lib/python3.12/site-packages/numpy/_core/include -I/usr/local/qaptiva/python_references/112/rhel9/ref312/include -I/usr/local/include/python3.12 -c qtcoeff_f230db93af3a399e4166e330cf4663.cpp -o build/temp.linux-x86_64-cpython-312/qtcoeff_f230db93af3a399e4166e330cf4663.o -w -O3 -funroll-loops
g++ -fno-strict-overflow -Wsign-compare -DNDEBUG -g -O3 -Wall -I/usr/local/lib64/openmpi/../../include/openmpi-x86_64 -shared -L/usr/local/lib64/openmpi/lib -I/usr/local/lib64/openmpi/../../include/openmpi-x86_64 build/temp.linux-x86_64-cpython-312/qtcoeff_f230db93af3a399e4166e330cf4663.o -L/usr/local/lib -o build/lib.linux-x86_64-cpython-312/qtcoeff_f230db93af3a399e4166e330cf4663.cpython-312-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-cpython-312/qtcoeff_f230db93af3a399e4166e330cf4663.cpython-312-x86_64-linux-gnu.so ->
Done
Did not find any quantum register info: wrapping results with a dummy BITSTRING register of length 0
Post processing a list of results of length 1
noiseless <n_0 (t_max)> = 0.8268208165630487
from qat.hardware import HardwareModel
# Describe the noisy environment with jump operators
jump_ops=[Observable(2, pauli_terms=[(Term(0.5, 'Cc', [0, 0]))]),
Observable(2, pauli_terms=[(Term(0.5, 'Cc', [1, 1]))])]
hardware_model = HardwareModel(jump_operators=jump_ops)
# Create a 'noisy' QPU
qpu_noisy = QutipQPU(hardware_model=hardware_model,
sim_method="deterministic",
nsteps=50)
# Submit the job
res_noisy = qpu_noisy.submit(job)
print("noisy <n_0 (t_max)> =", res_noisy.value)
New batch of length 1 submitted
Compiling a batch of length 1
Starting compilation...
Returning compiled batch.
Resource management is not available, passing through.
Running jobs...
Done
Did not find any quantum register info: wrapping results with a dummy BITSTRING register of length 0
Post processing a list of results of length 1
noisy <n_0 (t_max)> = 0.7805360058248992
Observable's value over time¶
We can plot how the value of the observable changes over time - these values are stored in the value_data field of the Result object:
import matplotlib.pyplot as plt
# Plotting
time_points = [float(i) for i in res.value_data.keys()]
plt.plot(time_points, [res.value_data[k].re for k in res.value_data.keys()],
label="ideal processor")
plt.plot(time_points, [res_noisy.value_data[k].re for k in res_noisy.value_data.keys()],
label="noisy processor")
# Labelling
plt.legend(loc="best")
plt.xlabel(r"time $t$")
plt.ylabel(r"$\langle n_0 \rangle$")
plt.grid()
We see that as expected, at time $t=0$, the occupation of orbital $0$ is $0$, and that this occupation increases over time - less in the noisy setting.
For general fermionic computations, one can look at the dedicated library qat-fermion.