# Installation and Getting Started¶

This toolkit provides some simple libraries for writing quantum programs.

```
from pyquil.quil import Program
import pyquil.api as api
from pyquil.gates import *
qvm = api.QVMConnection()
p = Program()
p.inst(H(0), CNOT(0, 1))
<pyquil.pyquil.Program object at 0x101ebfb50>
wavefunction = qvm.wavefunction(p)
print(wavefunction)
(0.7071067812+0j)|00> + (0.7071067812+0j)|11>
```

It comes with a few parts:

**Quil**: The Quantum Instruction Language standard. Instructions written in Quil can be executed on any implementation of a quantum abstract machine, such as the quantum virtual machine (QVM), or on a real quantum processing unit (QPU). More details regarding Quil can be found in the whitepaper.**QVM**: A Quantum Virtual Machine, which is an implementation of the quantum abstract machine on classical hardware. The QVM lets you use a regular computer to simulate a small quantum computer. You can access the Rigetti QVM running in the cloud with your API key. Sign up here to get your key.**pyQuil**: A Python library to help write and run Quil code and quantum programs.**QPUConnection**: pyQuil also includes some a special connection which lets you run experiments on Rigetti’s prototype superconducting quantum processors over the cloud.

## Environment Setup¶

### Prerequisites¶

Before you can start writing quantum programs, you will need Python 2.7 (version 2.7.10 or greater) or Python 3.6 and the Python package manager pip.

Note

PyQuil works on both Python 2 and 3. However, Rigetti **strongly** recommends
using Python 3 if possible. Future feature developments in PyQuil may support
Python 3 only.

### Installation¶

You can install pyQuil directly from the Python package manager pip using:

```
pip install pyquil
```

To instead install the bleeding-edge version from source, clone the pyquil GitHub repository, navigate into its directory in a terminal, and run:

```
pip install -e .
```

On Mac/Linux, if this command does not succeed because of permissions errors, then instead run:

```
sudo pip install -e .
```

This will also install pyQuil’s dependencies (numpy, requests, etc.) if you do not already have them.

The library will now be available globally.

### Connecting to the Rigetti Forest¶

pyQuil can be used to build and manipulate Quil programs without restriction. However, to run programs (e.g., to get wavefunctions, get multishot experiment data), you will need an API key for Rigetti Forest. This will allow you to run your programs on the Rigetti QVM or QPU.

Sign up here to get a Forest API key, it’s free and only takes a few seconds.

It’s also highly recommended to join our public slack channel where you can connect with other users and Rigetti members for support.

Run the following command to automatically set up the config. This will prompt you for the required information (URL, key, and user id). It will then create a file in the proper location (the user’s root directory):

```
pyquil-config-setup
```

If the setup completed successfully then you can skip to the next section.

You can also create the configuration file manually if you’d like and place it at `~/.pyquil_config`

.
The configuration file is in INI format and should contain all the information required to connect to Forest:

```
[Rigetti Forest]
key: <Rigetti Forest API key>
user_id: <Rigetti User ID>
```

Alternatively, you can place the file at your own chosen location and then set the `PYQUIL_CONFIG`

environment
variable to the path of the file.

Note

You may specify an absolute path or use the ~ to indicate your home directory.
On Linux, this points to `/users/username`

.
On Mac, this points to `/Users/Username`

.
On Windows, this points to `C:\Users\Username`

Note

Windows users may find it easier to name the file `pyquil.ini`

and open it using notepad. Then, set the
`PYQUIL_CONFIG`

environment variable by opening up a command prompt and running:
`setenv PYQUIL_CONFIG=C:\Users\Username\pyquil.ini`

As a last resort, connection information can be provided via environment variables.

```
export QVM_API_KEY=<Rigetti Forest API key>
export QVM_USER_ID=<Rigetti User ID>
```

If you are still seeing errors or warnings then file a bug using Github Issues.

## Running your first quantum program¶

pyQuil is a Python library that helps you write programs in the Quantum Instruction Language (Quil).
It also ships with a simple script `examples/run_quil.py`

that runs Quil code directly. You can
test your connection to Forest using this script by executing the following on your command line

```
cd examples/
python run_quil.py hello_world.quil
```

You should see the following output array `[[1, 0, 0, 0, 0, 0, 0, 0]]`

. This indicates that you have
a good connection to our API.

You can continue to write more Quil code in files and run them using the `run_quil.py`

script. The
following sections describe how to use the pyQuil library directly to build quantum programs in
Python.

## Basic pyQuil Usage¶

To ensure that your installation is working correctly, try running the
following Python commands interactively. First, import the `quil`

module (which constructs quantum programs) and the `api`

module (which
allows connections to the Rigetti QVM). We will also import some basic
gates for pyQuil as well as numpy.

```
from pyquil.quil import Program
import pyquil.api as api
from pyquil.gates import *
import numpy as np
```

Next, we want to open a connection to the QVM.

```
qvm = api.QVMConnection()
```

Now we can make a program by adding some Quil instruction using the
`inst`

method on a `Program`

object.

```
p = Program()
p.inst(X(0)).measure(0, 0)
```

```
<pyquil.quil.Program at 0x101d45a90>
```

This program simply applies the \(X\)-gate to the zeroth qubit, measures that qubit, and stores the measurement result in the zeroth classical register. We can look at the Quil code that makes up this program simply by printing it.

```
print(p)
```

```
X 0
MEASURE 0 [0]
```

Most importantly, of course, we can see what happens if we run this program on the QVM:

```
classical_regs = [0] # A list of which classical registers to return the values of.
qvm.run(p, classical_regs)
```

```
[[1]]
```

We see that the result of this program is that the classical register
`[0]`

now stores the state of qubit 0, which should be
\(\left\vert 1\right\rangle\) after an \(X\)-gate. We can of
course ask for more classical registers:

```
qvm.run(p, [0, 1, 2])
```

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

The classical registers are initialized to zero, so registers `[1]`

and `[2]`

come out as zero. If we stored the measurement in a
different classical register we would obtain:

```
p = Program() # clear the old program
p.inst(X(0)).measure(0, 1)
qvm.run(p, [0, 1, 2])
```

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

We can also run programs multiple times and accumulate all the results in a single list.

```
coin_flip = Program().inst(H(0)).measure(0, 0)
num_flips = 5
qvm.run(coin_flip, [0], num_flips)
```

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

Try running the above code several times. You will see that you will, with very high probability, get different results each time.

As the QVM is a virtual machine, we can also inspect the wavefunction of a program directly, even without measurements:

```
coin_flip = Program().inst(H(0))
qvm.wavefunction(coin_flip)
```

```
<pyquil.wavefunction.Wavefunction at 0x1088a2c10>
```

The return value is a Wavefunction object that stores the amplitudes of the quantum state at the conclusion of the program. We can print this object

```
coin_flip = Program().inst(H(0))
wavefunction = qvm.wavefunction(coin_flip)
print(wavefunction)
```

```
(0.7071067812+0j)|0> + (0.7071067812+0j)|1>
```

To see the amplitudes listed as a sum of computational basis states. We can index into those amplitudes directly or look at a dictionary of associated outcome probabilities.

```
assert wavefunction[0] == 1 / np.sqrt(2)
# The amplitudes are stored as a numpy array on the Wavefunction object
print(wavefunction.amplitudes)
prob_dict = wavefunction.get_outcome_probs() # extracts the probabilities of outcomes as a dict
print(prob_dict)
prob_dict.keys() # these stores the bitstring outcomes
assert len(wavefunction) == 1 # gives the number of qubits
```

```
[ 0.70710678+0.j 0.70710678+0.j]
{'1': 0.49999999999999989, '0': 0.49999999999999989}
```

The result from a wavefunction call also contains an optional amount of classical memory to check:

```
coin_flip = Program().inst(H(0)).measure(0,0)
wavefunction = qvm.wavefunction(coin_flip, classical_addresses=range(9))
classical_mem = wavefunction.classical_memory
```

Additionally, we can pass a random seed to the Connection object. This allows us to reliably reproduce measurement results for the purpose of testing:

```
seeded_cxn = api.QVMConnection(random_seed=17)
print(seeded_cxn.run(Program(H(0)).measure(0, 0), [0], 20))
seeded_cxn = api.QVMConnection(random_seed=17)
# This will give identical output to the above
print(seeded_cxn.run(Program(H(0)).measure(0, 0), [0], 20))
```

It is important to remember that this `wavefunction`

method is just a useful debugging tool
for small quantum systems, and it cannot be feasibly obtained on a
quantum processor.

### Some Program Construction Features¶

Multiple instructions can be applied at once or chained together. The following are all valid programs:

```
print("Multiple inst arguments with final measurement:")
print(Program().inst(X(0), Y(1), Z(0)).measure(0, 1))
print("Chained inst with explicit MEASURE instruction:")
print(Program().inst(X(0)).inst(Y(1)).measure(0, 1).inst(MEASURE(1, 2)))
print("A mix of chained inst and measures:")
print(Program().inst(X(0)).measure(0, 1).inst(Y(1), X(0)).measure(0, 0))
print("A composition of two programs:")
print(Program(X(0)) + Program(Y(0)))
```

```
Multiple inst arguments with final measurement:
X 0
Y 1
Z 0
MEASURE 0 [1]
Chained inst with explicit MEASURE instruction:
X 0
Y 1
MEASURE 0 [1]
MEASURE 1 [2]
A mix of chained inst and measures:
X 0
MEASURE 0 [1]
Y 1
X 0
MEASURE 0 [0]
A composition of two programs:
X 0
Y 0
```

### Fixing a Mistaken Instruction¶

If an instruction was appended to a program incorrectly, one can pop it off.

```
p = Program().inst(X(0))
p.inst(Y(1))
print("Oops! We have added Y 1 by accident:")
print(p)
print("We can fix by popping:")
p.pop()
print(p)
print("And then add it back:")
p += Program(Y(1))
print(p)
```

```
Oops! We have added Y 1 by accident:
X 0
Y 1
We can fix by popping:
X 0
And then add it back:
X 0
Y 1
```

### The Standard Gate Set¶

The following gates methods come standard with Quil and `gates.py`

:

- Pauli gates
`I`

,`X`

,`Y`

,`Z`

- Hadamard gate:
`H`

- Phase gates:
`PHASE(`

\(\theta\)`)`

,`S`

,`T`

- Controlled phase gates:
`CZ`

,`CPHASE00(`

\(\alpha\)`)`

,`CPHASE01(`

\(\alpha\)`)`

,`CPHASE10(`

\(\alpha\)`)`

,`CPHASE(`

\(\alpha\)`)`

- Cartesian rotation gates:
`RX(`

\(\theta\)`)`

,`RY(`

\(\theta\)`)`

,`RZ(`

\(\theta\)`)`

- Controlled \(X\) gates:
`CNOT`

,`CCNOT`

- Swap gates:
`SWAP`

,`CSWAP`

,`ISWAP`

,`PSWAP(`

\(\alpha\)`)`

The parameterized gates take a real or complex floating point number as an argument.

### Defining New Gates¶

New gates can be easily added inline to Quil programs. All you need is a matrix representation of the gate. For example, below we define a \(\sqrt{X}\) gate.

```
import numpy as np
# First we define the new gate from a matrix
x_gate_matrix = np.array(([0.0, 1.0], [1.0, 0.0]))
sqrt_x = np.array([[ 0.5+0.5j, 0.5-0.5j],
[ 0.5-0.5j, 0.5+0.5j]])
p = Program().defgate("SQRT-X", sqrt_x)
# Then we can use the new gate,
p.inst(("SQRT-X", 0))
print(p)
```

```
DEFGATE SQRT-X:
0.5+0.5i, 0.5-0.5i
0.5-0.5i, 0.5+0.5i
SQRT-X 0
```

```
print(qvm.wavefunction(p))
```

```
(0.5+0.5j)|0> + (0.5-0.5j)|1>
```

Quil in general supports defining parametric gates, though right now only static gates are supported by pyQuil. Below we show how we can define \(X_0\otimes \sqrt{X_1}\) as a single gate.

```
# A multi-qubit defgate example
x_gate_matrix = np.array(([0.0, 1.0], [1.0, 0.0]))
sqrt_x = np.array([[ 0.5+0.5j, 0.5-0.5j],
[ 0.5-0.5j, 0.5+0.5j]])
x_sqrt_x = np.kron(x_gate_matrix, sqrt_x)
p = Program().defgate("X-SQRT-X", x_sqrt_x)
# Then we can use the new gate
p.inst(("X-SQRT-X", 0, 1))
wavefunction = qvm.wavefunction(p)
print(wavefunction)
```

```
(0.5+0.5j)|01> + (0.5-0.5j)|11>
```

## Advanced Usage¶

### Quantum Fourier Transform (QFT)¶

Let us do an example that includes multi-qubit parameterized gates.

Here we wish to compute the discrete Fourier transform of
`[0, 1, 0, 0, 0, 0, 0, 0]`

. We do this in three steps:

- Write a function called
`qft3`

to make a 3-qubit QFT quantum program. - Write a state preparation quantum program.
- Execute state preparation followed by the QFT on the QVM.

First we define a function to make a 3-qubit QFT quantum program. This is a mix of Hadamard and CPHASE gates, with a final bit reversal correction at the end consisting of a single SWAP gate.

```
from math import pi
def qft3(q0, q1, q2):
p = Program()
p.inst( H(q2),
CPHASE(pi/2.0, q1, q2),
H(q1),
CPHASE(pi/4.0, q0, q2),
CPHASE(pi/2.0, q0, q1),
H(q0),
SWAP(q0, q2) )
return p
```

There is a very important detail to recognize here: The function
`qft3`

doesn’t *compute* the QFT, but rather it *makes a quantum
program* to compute the QFT on qubits `q0`

, `q1`

, and `q2`

.

We can see what this program looks like in Quil notation by doing the following:

```
print(qft3(0, 1, 2))
```

```
H 2
CPHASE(1.5707963267948966) 1 2
H 1
CPHASE(0.7853981633974483) 0 2
CPHASE(1.5707963267948966) 0 1
H 0
SWAP 0 2
```

Next, we want to prepare a state that corresponds to the sequence we want to compute the discrete Fourier transform of. Fortunately, this is easy, we just apply an \(X\)-gate to the zeroth qubit.

```
state_prep = Program().inst(X(0))
```

We can verify that this works by computing its wavefunction. However, we
need to add some “dummy” qubits, because otherwise `wavefunction`

would return a two-element vector.

```
add_dummy_qubits = Program().inst(I(1), I(2))
wavefunction = qvm.wavefunction(state_prep + add_dummy_qubits)
print(wavefunction)
```

```
(1+0j)|001>
```

If we have two quantum programs `a`

and `b`

, we can concatenate them
by doing `a + b`

. Using this, all we need to do is compute the QFT
after state preparation to get our final result.

```
wavefunction = qvm.wavefunction(state_prep + qft3(0, 1, 2))
print(wavefunction.amplitudes)
```

```
array([ 3.53553391e-01+0.j , 2.50000000e-01+0.25j ,
2.16489014e-17+0.35355339j, -2.50000000e-01+0.25j ,
-3.53553391e-01+0.j , -2.50000000e-01-0.25j ,
-2.16489014e-17-0.35355339j, 2.50000000e-01-0.25j ])
```

We can verify this works by computing the (inverse) FFT from NumPy.

```
from numpy.fft import ifft
ifft([0,1,0,0,0,0,0,0], norm="ortho")
```

```
array([ 0.35355339+0.j , 0.25000000+0.25j ,
0.00000000+0.35355339j, -0.25000000+0.25j ,
-0.35355339+0.j , -0.25000000-0.25j ,
0.00000000-0.35355339j, 0.25000000-0.25j ])
```

### Classical Control Flow¶

Here are a couple quick examples that show how much richer the classical
control of a Quil program can be. In this first example, we have a
register called `classical_flag_register`

which we use for looping.
Then we construct the loop in the following steps:

- We first initialize this register to
`1`

with the`init_register`

program so our while loop will execute. This is often called the*loop preamble*or*loop initialization*. - Next, we write body of the loop in a program itself. This will be a program that computes an \(X\) followed by an \(H\) on our qubit.
- Lastly, we put it all together using the
`while_do`

method.

```
# Name our classical registers:
classical_flag_register = 2
# Write out the loop initialization and body programs:
init_register = Program(TRUE([classical_flag_register]))
loop_body = Program(X(0), H(0)).measure(0, classical_flag_register)
# Put it all together in a loop program:
loop_prog = init_register.while_do(classical_flag_register, loop_body)
print(loop_prog)
```

```
TRUE [2]
LABEL @START1
JUMP-UNLESS @END2 [2]
X 0
H 0
MEASURE 0 [2]
JUMP @START1
LABEL @END2
```

Notice that the `init_register`

program applied a Quil instruction directly to a
classical register. There are several classical commands that can be used in this fashion:

`TRUE`

which sets a single classical bit to be 1`FALSE`

which sets a single classical bit to be 0`NOT`

which flips a classical bit`AND`

which operates on two classical bits`OR`

which operates on two classical bits`MOVE`

which moves the value of a classical bit at one classical address into another`EXCHANGE`

which swaps the value of two classical bits

In this next example, we show how to do conditional branching in the
form of the traditional `if`

construct as in many programming
languages. Much like the last example, we construct programs for each
branch of the `if`

, and put it all together by using the `if_then`

method.

```
# Name our classical registers:
test_register = 1
answer_register = 0
# Construct each branch of our if-statement. We can have empty branches
# simply by having empty programs.
then_branch = Program(X(0))
else_branch = Program()
# Make a program that will put a 0 or 1 in test_register with 50% probability:
branching_prog = Program(H(1)).measure(1, test_register)
# Add the conditional branching:
branching_prog.if_then(test_register, then_branch, else_branch)
# Measure qubit 0 into our answer register:
branching_prog.measure(0, answer_register)
print(branching_prog)
```

```
H 1
MEASURE 1 [1]
JUMP-WHEN @THEN3 [1]
JUMP @END4
LABEL @THEN3
X 0
LABEL @END4
MEASURE 0 [0]
```

We can run this program a few times to see what we get in the
`answer_register`

.

```
qvm.run(branching_prog, [answer_register], 10)
```

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

### Parametric Depolarizing Noise¶

The Rigetti QVM has support for emulating certain types of noise models.
One such model is *parametric Pauli noise*, which is defined by a
set of 6 probabilities:

- The probabilities \(P_X\), \(P_Y\), and \(P_Z\) which
define respectively the probability of a Pauli \(X\), \(Y\),
or \(Z\) gate getting applied to
*each*qubit after*every*gate application. These probabilities are called the*gate noise probabilities*. - The probabilities \(P_X'\), \(P_Y'\), and \(P_Z'\) which
define respectively the probability of a Pauli \(X\), \(Y\),
or \(Z\) gate getting applied to the qubit being measured
*before*it is measured. These probabilities are called the*measurement noise probabilities*.

We can instantiate a noisy QVM by creating a new connection with these probabilities specified.

```
# 20% chance of a X gate being applied after gate applications and before measurements.
gate_noise_probs = [0.2, 0.0, 0.0]
meas_noise_probs = [0.2, 0.0, 0.0]
noisy_qvm = api.QVMConnection(gate_noise=gate_noise_probs, measurement_noise=meas_noise_probs)
```

We can test this by applying an \(X\)-gate and measuring. Nominally,
we should always measure `1`

.

```
p = Program().inst(X(0)).measure(0, 0)
print("Without Noise: {}".format(qvm.run(p, [0], 10)))
print("With Noise : {}".format(noisy_qvm.run(p, [0], 10)))
```

```
Without Noise: [[1], [1], [1], [1], [1], [1], [1], [1], [1], [1]]
With Noise : [[0], [0], [0], [0], [0], [1], [1], [1], [1], [0]]
```

### Parametric Programs¶

A big advantage of working in pyQuil is that you are able to leverage all the functionality of
Python to generate Quil programs. In quantum/classical hybrid algorithms this often leads to
situations where complex classical functions are used to generate Quil programs. pyQuil provides
a convenient construction to allow you to use Python functions to generate templates of Quil
programs, called `ParametricPrograms`

:

```
# This function returns a quantum circuit with different rotation angles on a gate on qubit 0
def rotator(angle):
return Program(RX(angle, 0))
from pyquil.parametric import ParametricProgram
par_p = ParametricProgram(rotator) # This produces a new type of parameterized program object
```

The parametric program `par_p`

now takes the same arguments as `rotator`

:

```
print(par_p(0.5))
```

```
RX(0.5) 0
```

We can think of `ParametricPrograms`

as a sort of template for Quil programs. They cache computations
that happen in Python functions so that templates in Quil can be efficiently substituted.

### Pauli Operator Algebra¶

Many algorithms require manipulating sums of Pauli combinations, such as
\(\sigma = \frac{1}{2}I - \frac{3}{4}X_0Y_1Z_3 + (5-2i)Z_1X_2,\) where
\(G_n\) indicates the gate \(G\) acting on qubit \(n\). We
can represent such sums by constructing `PauliTerm`

and `PauliSum`

.
The above sum can be constructed as follows:

```
from pyquil.paulis import ID, sX, sY, sZ
# Pauli term takes an operator "X", "Y", "Z", or "I"; a qubit to act on, and
# an optional coefficient.
a = 0.5 * ID
b = -0.75 * sX(0) * sY(1) * sZ(3)
c = (5-2j) * sZ(1) * sX(2)
# Construct a sum of Pauli terms.
sigma = a + b + c
print("sigma = {}".format(sigma))
```

```
sigma = 0.5*I + -0.75*X0*Y1*Z3 + (5-2j)*Z1*X2
```

Right now, the primary thing one can do with Pauli terms and sums is to construct the exponential of the Pauli term, i.e., \(\exp[-i\beta\sigma]\). This is accomplished by constructing a parameterized Quil program that is evaluated when passed values for the coefficients of the angle \(\beta\).

Related to exponentiating Pauli sums we provide utility functions for finding the commuting subgroups of a Pauli sum and approximating the exponential with the Suzuki-Trotter approximation through fourth order.

When arithmetic is done with Pauli sums, simplification is automatically done.

The following shows an instructive example of all three.

```
import pyquil.paulis as pl
# Simplification
sigma_cubed = sigma * sigma * sigma
print("Simplified : {}".format(sigma_cubed))
print()
#Produce Quil code to compute exp[iX]
H = -1.0 * sX(0)
print("Quil to compute exp[iX] on qubit 0:")
print(pl.exponential_map(H)(1.0))
```

```
Simplified : (32.46875-30j)*I + (-16.734375+15j)*X0*Y1*Z3 + (71.5625-144.625j)*Z1*X2
Quil to compute exp[iX] on qubit 0:
H 0
RZ(-2.0) 0
H 0
```

A more sophisticated feature of pyQuil is that it can create templates of Quil programs in
ParametricProgram objects. An example use of these templates is in exponentiating a Hamiltonian
that is parametrized by a constant. This commonly occurs in variational algorithms. The function
`exponential_map`

is used to compute exp[i * alpha * H] without explicitly filling in a value for
alpha.

```
parametric_prog = pl.exponential_map(H)
print(parametric_prog(0.0))
print(parametric_prog(1.0))
print(parametric_prog(2.0))
```

This ParametricProgram now acts as a template, caching the result of the `exponential_map`

calculation so that it can be used later with new values.

## Connections¶

Larger pyQuil programs can involve more qubits and take a longer time to run. Instead of running the
program immediately, you can insert your programs into a queue. This is done with the `use_queue`

parameter to QVMConnection. By default, this parameter is set to False which means it skips
the queue and runs it immediately. However, the QVM will reject programs that are more than
19 qubits or take longer than 10 seconds to run. Therefore, to run programs of a larger size you must
set the `use_queue`

parameter to True which has more overhead.

```
from pyquil.quil import Program
from pyquil.api import QVMConnection
qvm = QVMConnection(use_queue=True)
qvm.run(Program(X(0).measure(0, 0), [0])
```

The Forest queue also allows an asynchronous mode of interaction with methods postfixed with _async. This means that there is a seperate query to post a job and to get the result.

```
from pyquil.quil import Program
from pyquil.gates import X, H, I
from pyquil.api import QVMConnection
qvm = QVMConnection()
job_id = qvm.run_async(Program(X(0)).measure(0, 0), [0])
```

The job_id is a string that uniquely identifies the job in Forest. You can use the .get_job method on QVMConnection to get the current status.

```
job = qvm.get_job(job_id)
if not job.is_done():
time.sleep(1)
job = qvm.get_job(job_id)
print(job.result())
```

```
[[1]]
```

The wait_for_job method periodically checks for updates and prints the job’s position in the queue, similar to the above code.

```
job = qvm.wait_for_job(job_id)
print(job.result())
```

```
[[1]]
```

## Optimized Calls¶

This same pattern as above applies to the `wavefunction()`

,
`expectation()`

and `run_and_measure()`

.
These are very useful if used appropriately: They all execute a given program *once and only once*
and then either return the final wavefunction or use it to generate expectation values or a
specified number of random bitstring samples.

Warning

This behavior can have unexpected consequences if the program that prepares the final state
is non-deterministic, e.g., if it contains measurements and/or noisy gate applications.
In this case, the final state after the program execution is itself a random variable
and a single call to these functions therefore **cannot** sample the full space of outcomes.
Therefore, if the program is non-deterministic and sampling the full program output distribution
is important for the application at hand, we recommend using the basic
`run()`

API function as this re-runs the full program for every
requested trial.

## Exercises¶

### Exercise 1 - Quantum Dice¶

Write a quantum program to simulate throwing an 8-sided die. The Python function you should produce is:

```
def throw_octahedral_die():
# return the result of throwing an 8 sided die, an int between 1 and 8, by running a quantum program
```

Next, extend the program to work for any kind of fair die:

```
def throw_polyhedral_die(num_sides):
# return the result of throwing a num_sides sided die by running a quantum program
```

### Exercise 2 - Controlled Gates¶

We can use the full generality of NumPy to construct new gate matrices.

- Write a function
`controlled`

which takes a \(2\times 2\) matrix \(U\) representing a single qubit operator, and makes a \(4\times 4\) matrix which is a controlled variant of \(U\), with the first argument being the*control qubit*. - Write a Quil program to define a controlled-\(Y\) gate in this manner. Find the wavefunction when applying this gate to qubit 1 controlled by qubit 0.

### Exercise 3 - Grover’s Algorithm¶

Write a quantum program for the single-shot Grover’s algorithm. The Python function you should produce is:

```
# data is an array of 0's and 1's such that there are exactly three times as many
# 0's as 1's
def single_shot_grovers(data):
# return an index that contains the value 1
```

As an example: `single_shot_grovers([0,0,1,0])`

should return 2.

**HINT** - Remember that the Grover’s diffusion operator is: