To construct run-and-measure-style programs, there are no changes in program construction. When using explicit MEASURE
instructions or using parametric programs for massive speed increases, please read about the new quil memory model.
Below, we build a program that constructs a Bell state.
In [1]:
from pyquil import Program
from pyquil.gates import *
program = Program(
H(0),
CNOT(0, 1),
)
print(program)
QVMConnection
In pyQuil 1.x, you would use the QVMConnection
to run quantum programs on a web-hosted quantum simulator (the "Quantum Virtual Machine"). To run on a real quantum processor (QPU), you would switch all instances of QVMConnection
to QPUConnection
. However, QVMConnection
let you do many unphysical operations that are unsupported on a real QPU. These methods are detailed below and have been deprecated in favor of a new API that clearly delineats the physical from the unphysical.
When simulating a quantum program, we can inspect its wavefunction. This is very useful! It is--however--impossible to inspect the wavefunction of a real quantum processor. This is one of the central tenets of quantum information!
Attempting to switch instances of QVMConnection
to QPUConnection
results in an error. Additionally, using QVMConnection
with a noise model will cause the wavefunction
call to behave badly.
In [2]:
from pyquil.api import QVMConnection
wfn_old = QVMConnection().wavefunction(program)
print(wfn_old)
WavefunctionSimulator
encapsulates all functionality that requires peering into a wavefunction. This also opens the door for different types of simulators other than those backed by a wavefunction. For example, you can simulate a quantum circuit with a density matrix simulation or a path integral simulation.
In [3]:
from pyquil.api import WavefunctionSimulator
wfn = WavefunctionSimulator().wavefunction(program)
print(wfn)
Many quantum algorithms involve calculating expectation values of quantum observables. We can represent quantum observables with PauliSum
s. When using a simulator, it is possible to exactly calculate expectation values (a consequence of having access to the full wavefunction) whereas when running on a QPU you must estimate expectation values by sampling from many runs of a program.
Let's use our program defined above to construct a bell state.
In [4]:
print(program)
And we wish to measure the following quantum observables $O$
Exercise for the reader: convince yourself that $(1-ZZ)/2$ is the XOR function
In [5]:
from pyquil.paulis import sZ
z0 = (1-sZ(0))*0.5
z1 = (1-sZ(1))*0.5
xor = (1-sZ(0)*sZ(1))*0.5
A Bell state is entangled, so each of the 1-qubit operators should give an expectation value of 0.5 (as each qubit taken individually has a 50% change of being measured in the 0 or 1 state) whereas the two-qubit operator should give an expectation value of 0 (as the two qubits considered together will always have even parity)
In [6]:
for observable in [z0, z1, xor]:
expectation = QVMConnection().pauli_expectation(prep_prog=program, pauli_terms=observable)
print(observable, '\t', expectation)
In [7]:
for observable in [z0, z1, xor]:
expectation = WavefunctionSimulator().expectation(prep_prog=program, pauli_terms=observable)
print(observable, '\t', expectation)
pyQuil 2 is tailored for Rigetti's Quantum Cloud Services (QCS). In prior releases, executing programs on either the QVM or QPU involved communicating with a cloud-hosted endpoint. Now, you are empowered with
Taken together, there is no longer any motivation for supporting asynchronous access to either the QVM or QPU.
When running many programs it was often possible to reduce runtime by batching jobs and exploiting the async queue. The following example does not work in pyQuil 2 but gives a sketch about how this would have worked.
import numpy as np
cxn = QVMConnection()
thetas = np.linspace(0, 2*np.pi, 20)
jobs = [Program(RY(theta, 0)) for theta in thetas]
job_ids = [cxn.run_and_measure_async(job, qubits=[0], trials=1000) for job in jobs]
bitstrings = [np.asarray(cxn.wait_for_job(job)) for job in jobs]
Since this is such an important use case, there have been many changes to support running many programs as quickly as possible. We demonstrate an equivalent, synchronous version of the example given above. To idiomatically run this set of jobs, there are additional features you should use that are not covered in this document. Please continue reading the documentation, especially the page covering parametric programs.
In [8]:
import numpy as np
cxn = QVMConnection()
thetas = np.linspace(0, np.pi, 20)
bitstrings = [np.asarray(cxn.run_and_measure(Program(RY(theta, 0)), qubits=[0], trials=1000))
for theta in thetas]
In [9]:
%matplotlib inline
from matplotlib import pyplot as plt
averages = [np.mean(bs[:,0]) for bs in bitstrings]
_ = plt.plot(thetas, averages, 'o-')