# Executing quantum circuits¶

The workflow of a quantum computer, henceforth called a **“Quantum Processing Unit” (QPU)**, is the following:
its state (register of qubits) is initialized,
a series of quantum gates (a quantum circuit \(\mathcal{C}\)) is operated on the register,
and a measurement is made on the final state \(|\psi\rangle\) of the QPU.

QLM provides classical emulators of QPUs that allow to simulate the execution of this workflow.

In this page, you will learn how to use the various objects required to describe this workflow and to run a QLM QPU.

## Describing a quantum job¶

The computational jobs that are fed to a QPU primarily consist of two main components:

the

**quantum circuit**to be executed on the qubit register. You have learned how to create such a circuit here.the

**final measurement**to be carried out on the final state of the register, i.e on the wavefunction \(|\psi\rangle\) that has been prepared by the quantum circuit.

**Two types** of final measurements are commonly used in quantum algorithms:

“

**Sample**”: Measuring the projection of the wavefunction on the computational basis (formally speaking, this means measuring qubits “along the Z axis”). For instance, if the final state is \(|\psi\rangle=(|00\rangle+|11\rangle)/\sqrt{2}\), measuring “in the computational basis” will yield the bitstring “00” in 50% (\(1/\sqrt{2}^2\)) of cases, and “11” the rest of the time. Because of the statistical nature of the measurement, many repeated measurements on the computational basis, yielding many different bitstrings or “**samples**”, are necessary to get an accurate estimate of the frequencies of each computational basis state (“00”: 50%, “01”: 0%, “10”: 0%, “11”: 50% in our case). We will call “**nbshots**” the number of such repetitions (with a classical emulator, one can in general emulate an infinite number of shots since one has access to the exact frequencies). Of course, one may decide to measure only a subset of qubits.“

**Observable**”: Measuring the value of a quantum mechanical**observable**\(O\) in the final state. On average, such a value is given by the formula \(\langle O \rangle = \langle \psi | O |\psi \rangle\). While this average value can directly be computed using a classical computer, on an actual QPU, only a limited set of observables can be measured. In practice, most QPUs provide only “Z-axis” measurements, i.e, as described above, the possibility to measure the projection of the wavefunction on computational basis states. In order to perform the estimation of the average value of an observable, one thus needs to convert the computation of \(\langle O \rangle\) into a series of Z axis measurements (with possible modifications of the circuit), estimate bitstrings frequencies as described above (with a given number of shots or repetitions), and combine those subresults back into an estimate of \(\langle O \rangle\).

Let us emphasize the fact that while the first final processing type is native to most actual QPUs, the same does not necessarily holds for the “OBSERVABLE” processing type. In the absence of a native support, the ObservableSplitter plugin of the QLM can enhance the QPU with an observable-processing capacity.

In QLM, quantum jobs describing these tasks are implemented by a `qat.core.Job`

object, that contains

the circuit \(\mathcal{C}\) to be executed

the type of the final processing (

`SAMPLE`

or`OBS`

), with the necessary parameters (which qubits to be measured or which observable to evaluate, respectively)the number of allowed repetitions (shots)

Note that by default, the processing type is `SAMPLE`

, with all qubits being measured, and an infinite number of shots (i.e no statistical uncertainty).

## A full example on the Bell state circuit¶

Here, we exemplify the execution of a simple Bell circuit with the different modes described above.

### SAMPLE mode¶

#### Infinite number of shots, all qubits¶

We take the same example as the one we examined in the Getting Started page for an illustration of this case:

```
from qat.lang.AQASM import Program, H, CNOT
from qat.qpus import get_default_qpu
# Create a circuit
qprog = Program()
qbits = qprog.qalloc(2)
H(qbits[0])
CNOT(qbits[0], qbits[1])
circuit = qprog.to_circ()
# Create a job
job = circuit.to_job() # no parameters, equivalent to "nbshots=0"
# Instantiate a QPU (simulator)
qpu = get_default_qpu()
# Execute
result = qpu.submit(job)
for sample in result:
print("State %s: probability %s, amplitude %s" % (sample.state, sample.probability, sample.amplitude))
```

```
State |00>: probability 0.4999999999999999, amplitude (0.7071067811865475+0j)
State |11>: probability 0.4999999999999999, amplitude (0.7071067811865475+0j)
```

You can notice that a `Job`

is created from a circuit using the circuit’s `to_job()`

method.
This job is then fed to the QPU’s `submit`

method, which returns a `result`

object.
In SAMPLE mode, this result can be iterated on, yielding the various “samples”, with each sample
corresponding to a given bitstring (`state`

) and its frequency of appearance (`probability`

) upon
conducting a Z measurement over the final state \(|\psi\rangle\). States with zero probability
are not listed (one can set a threshold `amp_threshold`

to filter out states below a certain probability amplitude).
Here, we also print the probability *amplitude* (`amplitude`

) corresponding to each computational basis state.
Let us stress that this piece of information is in general not available from an actual QPU, but merely
from some classical simulators.

#### Finite number of shots, all qubits¶

Let us switch to a finite `nbshots`

:

```
from qat.lang.AQASM import Program, H, CNOT
from qat.qpus import get_default_qpu
# Create a circuit
qprog = Program()
qbits = qprog.qalloc(2)
H(qbits[0])
CNOT(qbits[0], qbits[1])
circuit = qprog.to_circ()
# Create a job
job = circuit.to_job(nbshots=100)
# Execute
result = get_default_qpu().submit(job)
for sample in result:
print("State %s: probability %s +/- %s" % (sample.state, sample.probability, sample.err))
```

```
State |11>: probability 0.59 +/- 0.04943110704237104
State |00>: probability 0.41 +/- 0.04943110704237104
```

Notice how the estimated probability of the states differs from the ideal one due to “shot noise”. The `err`

field of the `sample`

object contains the standard error of the mean on the frequency of appearance.
It decreases as \(1/\sqrt{n_\mathrm{shots}}\).

#### Infinite number of shots, only one qubit¶

Here, we decide to measure only the second qubit:

```
from qat.lang.AQASM import Program, H, CNOT
from qat.qpus import get_default_qpu
# Create a circuit
qprog = Program()
qbits = qprog.qalloc(2)
H(qbits[0])
CNOT(qbits[0], qbits[1])
circuit = qprog.to_circ()
# Create a job
job = circuit.to_job(nbshots=0, qubits=[1])
# Execute
result = get_default_qpu().submit(job)
for sample in result:
print("State %s: probability %s +/- %s" % (sample.state, sample.probability, sample.err))
```

```
State |0>: probability 0.4999999999999999 +/- None
State |1>: probability 0.4999999999999999 +/- None
```

As expected, the probability of measuring “0” on the second qubit is the same as the
probability of measuring “1”. Here, `err`

is `None`

because we took an infinite number
of shots.

### OBSERVABLE mode¶

Let us now turn to the second type of final processing, namely the “OBSERVABLE” mode. Our goal is to compute the average value of the following observable:

for the Bell state \(|\psi\rangle=(|00\rangle+|11\rangle)/\sqrt{2}\). A straightforward computation yields \(\langle O \rangle = 0\). Let us now perform this computation with QLM.

#### Infinite number of shots¶

Let us start with a computation devoid of any shot noise. It directly yields the expectation value \(\langle O \rangle\):

```
from qat.lang.AQASM import Program, H, CNOT
from qat.qpus import get_default_qpu
# Create a circuit
qprog = Program()
qbits = qprog.qalloc(2)
H(qbits[0])
CNOT(qbits[0], qbits[1])
circuit = qprog.to_circ()
# Create an observable
from qat.core import Observable, Term
obs = Observable(2, pauli_terms=[Term(1, "XZ", [0, 1])])
# Create a job
job = circuit.to_job(observable=obs)
# Execute
result = get_default_qpu().submit(job)
print("<O> = ", result.value)
```

```
<O> = 0.0
```

#### Finite number of shots¶

We now look at the effect of shot noise, i.e the fact that in practice one can only repeat the measurement a finite number of times:

```
from qat.lang.AQASM import Program, H, CNOT
from qat.qpus import get_default_qpu
# Create a circuit
qprog = Program()
qbits = qprog.qalloc(2)
H(qbits[0])
CNOT(qbits[0], qbits[1])
circuit = qprog.to_circ()
# Create an observable
from qat.core import Observable, Term
obs = Observable(2, pauli_terms=[Term(1, "XZ", [0, 1])])
# Create a job
for nbshots in [100, 1000, 10000]:
job = circuit.to_job(observable=obs, nbshots=nbshots)
result = get_default_qpu().submit(job)
print(f"<O> ({nbshots} shots) = {result.value} +/- {result.error}")
```

```
<O> (100 shots) = 0.0 +/- None
<O> (1000 shots) = 0.0 +/- None
<O> (10000 shots) = 0.0 +/- None
```

## Further information¶

You will find a complete documentation of the objects mentioned above in the source code documentation:

job:

`qat.core.Job`

. See also:`qat.core.Circuit.to_job()`

.observable:

`qat.core.Observable`

and`qat.core.Term`

.results and samples:

`qat.core.Result`

and`qat.core.wrappers.result.Sample`

.