Getting Started with PyQuil (Forest SDK)
Learn Rigetti's PyQuil framework, write quantum programs in the QUIL instruction language and run them on Rigetti's superconducting quantum processors via Quantum Cloud Services.
Circuit diagrams
What is PyQuil?
PyQuil is the Python SDK built by Rigetti Computing for programming quantum computers. While frameworks like Qiskit and Cirq give you a high-level circuit-building API, PyQuil takes a different approach: it wraps Quil (Quantum Instruction Language), a low-level quantum assembly language designed to map directly onto the native instruction set of Rigetti’s superconducting processors.
What makes this interesting is that Quil treats classical control flow and quantum operations as first-class citizens in the same instruction stream. You can declare classical memory, perform conditional logic, and issue quantum gates all within one program. This tight coupling between classical and quantum code is closer to what real quantum processors actually execute, and it gives you fine-grained control over what happens on the hardware.
PyQuil targets Rigetti’s Quantum Cloud Services (QCS) platform, where programs run on Rigetti’s superconducting qubit processors (the Ankaa family as of 2025). But you do not need hardware access to start learning. PyQuil ships with a local simulator and compiler that let you develop and test programs entirely on your own machine.
Installation
Install PyQuil from PyPI:
pip install pyquil
Setting Up the QVM and Compiler
Before you can run any PyQuil program locally, you need two services running in the background:
- QVM (Quantum Virtual Machine): a classical simulator that executes Quil programs and returns measurement results, faithfully modeling the probabilistic behavior of a real quantum processor.
- quilc: the Quil compiler, which takes your high-level gate instructions (like
HandCNOT) and compiles them down to the native gate set of the target processor.
The easiest way to run both is through Docker:
# Pull the images
docker pull rigetti/qvm
docker pull rigetti/quilc
# Start the QVM on port 5000 and the compiler on port 5555
docker run -d -p 5000:5000 rigetti/qvm -S
docker run -d -p 5555:5555 rigetti/quilc -S
The -d flag runs each container in the background, and -S starts each in server mode so PyQuil can communicate with them over HTTP. You can verify they are running with docker ps. If you prefer not to use Docker, Rigetti also provides standalone binary downloads for Linux and macOS in the Forest SDK.
Your First Program: Building a Bell State
In PyQuil, you construct quantum programs by creating a Program object and appending gate instructions to it. Let’s build a Bell state, the simplest example of quantum entanglement.
The following code creates a two-qubit circuit that puts qubit 0 into superposition, then entangles it with qubit 1. We also declare a classical memory register called ro (short for “readout”) to store our measurement results:
from pyquil import Program, get_qc
from pyquil.gates import H, CNOT, MEASURE
# Create an empty quantum program
p = Program()
# Declare 2 classical bits to store measurement outcomes
ro = p.declare('ro', 'BIT', 2)
# Apply a Hadamard gate to qubit 0, creating an equal superposition of |0> and |1>
p += H(0)
# Apply a CNOT gate with qubit 0 as control and qubit 1 as target
# This entangles the two qubits
p += CNOT(0, 1)
# Measure both qubits and store results in classical memory
p += MEASURE(0, ro[0])
p += MEASURE(1, ro[1])
print(p)
Printing the program shows you the raw Quil instructions that PyQuil generates:
DECLARE ro BIT[2]
H 0
CNOT 0 1
MEASURE 0 ro[0]
MEASURE 1 ro[1]
Notice the nearly 1:1 mapping between your Python code and the Quil output. This transparency is one of PyQuil’s biggest strengths: you always know exactly what instructions will be sent to the processor.
Running on the QVM
Now let’s execute this program on the local QVM simulator. The get_qc function creates a “quantum computer” object; passing '2q-qvm' gives us a simulated 2-qubit machine. We wrap the program in a loop to collect 1000 measurement samples (called “shots”):
from pyquil import Program, get_qc
from pyquil.gates import H, CNOT, MEASURE
from collections import Counter
p = Program()
ro = p.declare('ro', 'BIT', 2)
p += H(0)
p += CNOT(0, 1)
p += MEASURE(0, ro[0])
p += MEASURE(1, ro[1])
# Run the circuit 1000 times
p.wrap_in_numshots_loop(1000)
# Connect to the 2-qubit QVM simulator
qc = get_qc('2q-qvm')
results = qc.run(p).readout_data.get('ro')
# Tally the measurement outcomes
counts = Counter(map(tuple, results))
print(counts)
# Expected: Counter({(0, 0): ~500, (1, 1): ~500})
Understanding the result: You should see roughly 500 counts of (0, 0) and 500 counts of (1, 1), with small statistical fluctuations. This is the hallmark of a Bell state: the two qubits are perfectly correlated, so they always give the same outcome, but which outcome you get on any single shot is completely random. You will never see (0, 1) or (1, 0).
Rigetti’s Native Gate Set
When quilc compiles your program for real Rigetti hardware, it decomposes all gates into the processor’s native gate set. For Rigetti’s superconducting processors, the native gates are:
- RZ(theta): rotation around the Z-axis (implemented virtually, with zero error)
- RX(theta): rotation around the X-axis (typically only pi/2 or pi rotations)
- CZ: controlled-Z, the native two-qubit entangling gate
This differs significantly from IBM’s native gate set, which centers on SX (sqrt-X), RZ, and CX (CNOT). The key distinction is the two-qubit gate: Rigetti uses CZ while IBM uses CX. Both are universal for quantum computation, but the choice affects how algorithms compile and how errors propagate.
Your Hadamard gate H(0), for example, is not native to Rigetti hardware. The compiler will decompose it into a sequence of RZ and RX rotations.
The Quil Instruction Set
After quilc compiles your Bell state program for a Rigetti processor, the Quil output looks something like this (the exact angles depend on the target device calibration):
DECLARE ro BIT[2]
PRAGMA INITIAL_REWIRING "PARTIAL"
RZ(pi/2) 0
RX(pi/2) 0
RZ(-pi/2) 0
CZ 0 1
RX(-pi/2) 1
RZ(pi/2) 1
MEASURE 0 ro[0]
MEASURE 1 ro[1]
The original two-gate circuit (H then CNOT) has been expanded into a sequence of native RZ, RX, and CZ instructions. The PRAGMA line tells the compiler how to map logical qubits onto physical qubits on the chip. This is the actual instruction stream that would execute on the processor.
Gate Set Reference
PyQuil provides the full standard gate library alongside Rigetti’s native gates:
from pyquil.gates import (
H, X, Y, Z, # Single-qubit Paulis + Hadamard
CNOT, CZ, CCNOT, # Two and three-qubit controlled gates
RX, RY, RZ, # Parametric rotation gates
PHASE, # Phase shift gate
SWAP, ISWAP, # Swap family gates
)
# Example: rotate qubit 0 by pi/4 around the Z-axis
p += RZ(3.14159 / 4, 0)
You can use any of these in your programs. The quilc compiler handles decomposition into native gates automatically when you target real hardware or a realistic QVM.
Noise and Realistic Simulation
For more realistic testing, PyQuil lets you add decoherence noise to your simulations. This models the T1 (energy relaxation) and T2 (dephasing) timescales that limit real superconducting qubits:
from pyquil.noise import add_decoherence_noise
noisy_program = add_decoherence_noise(p, T1=30e-6, T2=20e-6)
noisy_qc = get_qc('2q-qvm')
results = noisy_qc.run(noisy_program).readout_data.get('ro')
With noise enabled, you will start seeing small numbers of (0, 1) and (1, 0) outcomes in your Bell state experiment, reflecting the imperfections of real hardware.
PyQuil vs Qiskit vs Cirq
| Feature | PyQuil | Qiskit | Cirq |
|---|---|---|---|
| Developer | Rigetti | IBM | |
| Abstraction level | Low (Quil assembly) | High (Python circuit API) | Medium (moment-based) |
| Target hardware | Rigetti Ankaa series | IBM Eagle/Heron | Google Sycamore / Willow |
| Native 2Q gate | CZ | CX (CNOT) | sqrt-iSWAP / CZ |
| Local simulator | QVM (requires Docker or binary) | Aer (pip install) | Built-in (pip install) |
| Free hardware access | QCS (research programs) | IBM Quantum (free tier) | Limited availability |
| Best for | Hardware-close programming, Quil development | Education, broad ecosystem, IBM hardware | NISQ research, custom noise models |
If you want the most transparent view of what your quantum program actually does at the instruction level, PyQuil is an excellent choice. If you want the largest community and easiest onboarding, Qiskit is hard to beat. Cirq sits in between, offering a clean API with strong support for custom device topologies.
Common Mistakes
QVM or quilc not running. This is the most frequent issue for newcomers. If you see a ConnectionError or timeout when calling qc.run(), it almost certainly means the QVM or quilc Docker container is not running. Check with docker ps and restart the containers if needed.
Wrong endpoint configuration. PyQuil expects the QVM on http://localhost:5000 and quilc on tcp://localhost:5555 by default. If you changed the ports in your docker run command, you need to set the QCS_SETTINGS_APPLICATIONS_PYQUIL_QVM_URL and QCS_SETTINGS_APPLICATIONS_PYQUIL_QUILC_URL environment variables to match.
Confusing standard gates with native gates. Writing a circuit with H and CNOT works perfectly in simulation, but when you compile for real hardware, quilc decomposes these into RZ, RX, and CZ sequences. If you are benchmarking circuit depth or analyzing error budgets, always inspect the compiled Quil (using qc.compile(p)) to see the true gate count.
Forgetting to declare classical memory. Unlike Qiskit, where measurement results are automatically routed to classical registers, PyQuil requires you to explicitly declare classical memory with p.declare('ro', 'BIT', n) and pass it to each MEASURE instruction. Omitting this will raise an error.
Next Steps
- Work through the official PyQuil documentation and its tutorial notebooks
- Experiment with quilc compilation: use
qc.compile(p)to see how your circuits decompose into native gates for different processor topologies - Explore parametric compilation for variational algorithms like VQE, where gate angles change between runs
- Try Rigetti’s noise model tools to benchmark how your circuits perform under realistic decoherence
- If you have access to QCS, run your Bell state on real hardware and compare the noisy results to your QVM simulations
Was this tutorial helpful?