# New in Forest 2 - QuantumComputer¶

PyQuil is for constructing and running quantum programs on real quantum
computers. With the release of pyQuil 2, we have changed parts of the
API to better reflect that focus. Instead of swapping between a
`QVMConnection`

and a `QPUConnection`

, you will primarily deal with
a `QuantumComputer`

with consistent API and behavior regardless of

- QVM / QPU
- Presence of noise model
- Device topology

## Running a program¶

Let’s show how you can run a simple program on a `QuantumComputer`

first we start with the relevant imports.

```
In [1]:
```

```
from pyquil import Program
from pyquil.gates import *
```

We’ll write a function that takes a list of qubits and returns a pyQuil
`Program`

that constructs an entangled “GHZ” state. This is a
generalization of the two-qubit Bell state.

```
In [2]:
```

```
def ghz_state(qubits):
"""Create a GHZ state on the given list of qubits by applying
a Hadamard gate to the first qubit followed by a chain of CNOTs
"""
program = Program()
program += H(qubits[0])
for q1, q2 in zip(qubits, qubits[1:]):
program += CNOT(q1, q2)
return program
```

For example, creating a GHZ state on qubits 1, 2, and 3 would look like:

```
In [3]:
```

```
program = ghz_state(qubits=[0, 1, 2])
print(program)
```

```
H 0
CNOT 0 1
CNOT 1 2
```

## Debugging with `WavefunctionSimulator`

¶

We can check that this program gives us the desired wavefunction by using WavefunctionSimulator.wavefunction()

```
In [4]:
```

```
from pyquil.api import WavefunctionSimulator
wfn = WavefunctionSimulator().wavefunction(program)
print(wfn)
```

```
(0.7071067812+0j)|000> + (0.7071067812+0j)|111>
```

We can’t get the wavefunction from a real quantum computer though, so instead we’ll sample bitstrings. We expect to always measure the bitstring 000 or the bitstring 111 based on the definition of a GHZ state and confirmed by our wavefunction simulation.

`get_qc`

¶

We’ll construct a `QuantumComputer`

via the helper method
get_qc. You may be
tempted to use the `QuantumComputer`

constructor directly. Please
refer to the advanced
documentation
to see how to do that. Our program uses 3 qubits, so we’ll ask for a
3-qubit QVM.

```
In [5]:
```

```
from pyquil import get_qc
qc = get_qc('3q-qvm')
qc
```

```
Out[5]:
```

```
QuantumComputer[name="3q-qvm"]
```

We can do a quick check to make sure it has 3 qubits

```
In [6]:
```

```
qc.qubits()
```

```
Out[6]:
```

```
[0, 1, 2]
```

## Sampling with `run_and_measure`

¶

`QuantumComputer.run_and_measure`

will run a given program (that does
not have explicit `MEASURE`

instructions) and then measure *all qubits
present in the quantum computer*.

```
In [7]:
```

```
bitstrings = qc.run_and_measure(program, trials=10)
bitstrings
```

```
Out[7]:
```

```
{0: array([1, 0, 0, 1, 1, 1, 1, 0, 1, 0]),
1: array([1, 0, 0, 1, 1, 1, 1, 0, 1, 0]),
2: array([1, 0, 0, 1, 1, 1, 1, 0, 1, 0])}
```

Let’s programatically verify that we always measure 000 or 111 by “summing” each bitstring and checking if it’s eather 0 (for 000) or 3 (for 111)

```
In [8]:
```

```
import numpy as np
bitstring_array = np.vstack(bitstrings[q] for q in qc.qubits()).T
sums = np.sum(bitstring_array, axis=1)
sums
```

```
Out[8]:
```

```
array([3, 0, 0, 3, 3, 3, 3, 0, 3, 0])
```

```
In [9]:
```

```
sample_is_ghz = np.logical_or(sums == 0, sums == 3)
sample_is_ghz
```

```
Out[9]:
```

```
array([ True, True, True, True, True, True, True, True, True,
True])
```

```
In [10]:
```

```
np.all(sample_is_ghz)
```

```
Out[10]:
```

```
True
```

## Change alert: `run_and_measure`

will return a dictionary of 1d bitstrings.¶

*Not* a 2d array. To demonstrate why, consider a lattice whose qubits
are not contiguously indexed from 0.

```
In [11]:
```

```
# TODO: we need a lattice that is not zero-indexed
# qc = get_qc('Aspen-0-3Q-B')
# qc.run_and_measure(ghz_state(qubits=[1,2,3]))
```

## Change alert: All qubits are measured¶

PyQuil 1.x’s `run_and_measure`

would only measure qubits used in the
given program. Now all qubits (per `qc.qubits()`

) are measured. This
is easier to reason about and reflects the reality of running on a QPU.
When accounting for noise or when running QCVV tasks, you may be
interested in the measurement results of qubits that weren’t even used
in your program!

```
In [12]:
```

```
qc = get_qc('4q-qvm')
bitstrings = qc.run_and_measure(Program(X(0), X(1), X(2)), trials=10)
bitstrings
```

```
Out[12]:
```

```
{0: array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
1: array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
2: array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
3: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])}
```

You can drop qubits you’re not interested in by indexing into the returned dictionary

```
In [13]:
```

```
# Stacking everything
np.vstack(bitstrings[q] for q in qc.qubits()).T
```

```
Out[13]:
```

```
array([[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0]])
```

```
In [14]:
```

```
# Stacking what you want (contrast with above)
qubits = [0, 1, 2]
np.vstack(bitstrings[q] for q in qubits).T
```

```
Out[14]:
```

```
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]])
```

## Change alert: `run_and_measure`

works with noise models now.¶

In pyQuil 1.x, `run_and_measure`

would not work with noise models. Now
noise models are supported. Pre-configured noise models can be used via
`get_qc('xxxx-noisy-qvm')`

.

As a consequence, `run_and_measure`

for large numbers of trials will
be slower in Pyquil 2.

```
In [15]:
```

```
qc = get_qc('3q-noisy-qvm')
bitstrings = qc.run_and_measure(program, trials=10)
bitstrings
```

```
Out[15]:
```

```
{0: array([0, 1, 1, 0, 0, 1, 1, 0, 1, 1]),
1: array([0, 1, 1, 0, 0, 1, 1, 0, 1, 1]),
2: array([0, 1, 1, 0, 0, 1, 1, 0, 1, 1])}
```

```
In [16]:
```

```
bitstring_array = np.vstack(bitstrings[q] for q in qc.qubits()).T
sums = np.sum(bitstring_array, axis=1)
sums
```

```
Out[16]:
```

```
array([0, 3, 3, 0, 0, 3, 3, 0, 3, 3])
```

```
In [17]:
```

```
# Noise means now we measure things other than 000 or 111
np.all(np.logical_or(sums == 0, sums == 3))
```

```
Out[17]:
```

```
True
```

`list_quantum_computers`

¶

You can find all possible arguments to `get_qc`

with
`list_quantum_computers`

```
In [18]:
```

```
from pyquil import list_quantum_computers
# TODO: unauthenticated endpoint
# list_quantum_computers()
```

`QuantumComputer`

s have a topology¶

An important restriction when running on a real quantum computer is the
mapping of qubits to the supported two-qubit gates. The QVM is designed
to provide increasing levels of “realism” to guarantee that if your
program executes successfully on `get_qc("Aspen-xxx-noisy-qvm")`

then
it will execute successfully on `get_qc("Aspen-xxx")`

*

* guarantee not currently guaranteed. This is a work in progress.

## Inspecting the topology¶

You can access a topology by `qc.qubit_topology()`

, which will return
a NetworkX representation of qubit connectivity. You can access the full
set of supported instructions by `qc.get_isa()`

. For example, we
include a generic QVM named `"9q-square-qvm"`

that has a square
topology.

```
In [19]:
```

```
qc = get_qc('9q-square-qvm')
%matplotlib inline
import networkx as nx
nx.draw(qc.qubit_topology())
from matplotlib import pyplot as plt
_ = plt.title('9q-square-qvm', fontsize=18)
```

## What If I don’t want a topology?¶

`WavefunctionSimulator`

still has no notion of qubit connectivity, so
feel free to use that for simulating quantum algorithms that you aren’t
concerned about running on an actual QPU.

Above we used `get_qc("3q-qvm")`

, `"4q-qvm"`

, and indeed you can do
any `"{n}q-qvm"`

(subject to computational resource constraints).
These QVM’s are constructed with a topology! It just happens to be fully
connected

```
In [20]:
```

```
nx.draw(get_qc('5q-qvm').qubit_topology())
_ = plt.title('5q-qvm is fully connected', fontsize=16)
```

## Heirarchy of realism¶

`WavefunctionSimulator`

to debug algorithm`get_qc("5q-qvm")`

to debug sampling`get_qc("9q-square-qvm")`

to debug mapping to a lattice`get_qc("9q-square-noisy-qvm"`

) to debug generic noise characteristics`get_qc("Aspen-0-16Q-A-qvm")`

to debug mapping to a real lattice`get_qc("Aspen-0-16Q-A-noisy-qvm")`

to debug noise characteristics of a real device`get_qc("Aspen-0-16Q-A")`

to run on a real device

## “What is a `QuantumComputer`

?” Advanced Edition¶

A `QuantumComputer`

is a wrapper around three constituent parts, each
of which has a programatic interface that must be respected by all
classes that implement the interface. By having clear interfaces we can
write backend-agnostic methods on `QuantumComputer`

and mix-and-match
backing objects.

The following diagram shows the three objects that must be provided when
constructing a `QuantumComputer`

“by hand”. The abstract classes are
backed in grey with example implementing classes listed below. Please
consult the api
reference
for details on each interface.

As an example, let’s construct a 5-qubit QVM with one central node and only even numbered qubits.

```
In [21]:
```

```
topology = nx.from_edgelist([
(10, 2),
(10, 4),
(10, 6),
(10, 8),
])
from pyquil.device import NxDevice
device = NxDevice(topology)
from pyquil.api._qac import AbstractCompiler
class MyLazyCompiler(AbstractCompiler):
def quil_to_native_quil(self, program):
return program
def native_quil_to_executable(self, nq_program):
return nq_program
from pyquil.api import QuantumComputer, QVM, ForestConnection
my_qc = QuantumComputer(
name='my-qvm',
qam=QVM(connection=ForestConnection()),
device=device,
compiler=MyLazyCompiler(),
)
nx.draw(my_qc.qubit_topology())
```

```
In [22]:
```

```
my_qc.run_and_measure(Program(X(10)), trials=5)
```

```
Out[22]:
```

```
{2: array([0, 0, 0, 0, 0]),
4: array([0, 0, 0, 0, 0]),
6: array([0, 0, 0, 0, 0]),
8: array([0, 0, 0, 0, 0]),
10: array([1, 1, 1, 1, 1])}
```