QPUs vs. QVMs

The process of developing quantum software often begins with testing against a QVM. Eventually, you'll come to a point where you're ready to run your programs against a real QPU.

This guide will focus specifically on the differences between QPUs and QVMs when targeting them via the Forest SDK.

Changing Targets in pyQuil

Your pyQuil code will more than likely include a call to get_qc(). For instance, if you're targeting a 2-qubit QVM, the call might look something like:

qc = get_qc("2q-qvm")

From a pyQuil perspective, changing from a QVM to a QPU (and vice versa) is usually as simple as changing the string within the get_qc() function call. For example, to target the Aspen-9 QPU, you can change the line above to:

qc = get_qc("Aspen-9")

Implementation Differences

In both cases above, the qc object will be an instance of a pyQuil QuantumComputer. A QuantumComputer is comprised of:

  • A quantum abstract machine, accessed via qc.qam (class QAM)

  • An abstract compiler, accessed via qc.compiler (class AbstractCompiler)

If your code accesses either qc.qam or qc.compiler, it's important to understand that the QAM and AbstractCompiler implementations differ between QPU and QVM use cases.

For instance, if your code targets a QVM, qc.qam will be a QVM instance, and qc.compiler will be a QVMCompiler instance. However, if your code targets a QPU, qc.qam will be a QPU instance, and qc.compiler will be a QPUCompiler instance.

While these subcomponents follow common interfaces, namely QAM and AbstractCompiler, there may be some methods or properties that are accessible on the QPU-based instances but not on the QVM-based instances, and vice versa.

You can make your code robust by performing type checks on qc.qam and/or qc.compiler. For example:

pyQuil v3
pyQuil v3
from pyquil import get_qc
from pyquil.api import QPUCompiler
qc = get_qc("Aspen-9") # or "2q-qvm"
if isinstance(qc.compiler, QPUCompiler):
# Working with a QPU - refresh calibrations

Without the type check on line 6, your code might run well when targeting a QPU but raise an error when changing to a QVM (because get_calibration_program() is not available on QVMCompiler). With the type check in place, your code will run seamlessly between both cases.

For full details on the pyQuil classes mentioned in this section, see the pyQuil reference.

Practical Considerations

QPU Access

While QVMs typically run in your current environment and therefore don't have special access requirements, QPUs are remote resources hosted by QCS. As such, a QPU will only be accessible to you during a reservation window. You must also have network access to the QPU, which is most easily obtained by using your provisioned JupyterLab IDE.

QPU Topology & Fidelities

When moving from a QVM to a QPU, it's important to consider qubit topology. Qubit topology describes which qubits can be used together in quantum operations. For a QVM, this is a fully-connected graph — meaning any qubit can be used with any other qubit. This is not necessarily the case for a QPU, in which only certain qubits can be used together.

You can inspect a QPU's topology when making a QPU reservation on the QCS website, as shown below.

On a QPU, qubits and qubit pairs also have differing fidelities for certain operations. You can view this information by clicking Device Calibration under a QPU when making a reservation, as shown above.


Assume your quantum program includes an operation on qubits 0 and 1. On a QVM, your program may run well, but what about on a particular QPU? What if these qubits are not connected in the desired QPU's topology? Or perhaps they are connected but have poor fidelity for the operation in question. To run your program on the desired QPU, you can take advantage of compiler rewiring.

Rewiring is a step that the Quil compiler will perform to select optimal qubits for you. The qubits selected will be based on a QPU's topology and calibration information. Rewiring will happen by default, if needed, but rewiring behavior can also be explicitly overridden using a compiler directive.

To learn more about rewiring, including how to override default behavior, see the pyQuil documentation on rewiring.

If you'd like to simulate the topology and fidelities of a real QPU, you can create a QVM based on a QPU using the as_qvm parameter of get_qc() :

qc = get_qc("Aspen-9", as_qvm=True)

Note: This does not simulate the noise model of the QPU — only the qubit topology and operator fidelities.

Active Reset

Active reset — enacted by using the Quil RESET instruction at the beginning of a program — forces all qubits to their ground states. This is useful when running sequential programs against a QPU, as it reduces the time needed for qubits to return to their ground states between programs.

Active reset has no effect when running programs against a QVM, as a QVM will start with its qubits in their ground states regardless of active reset.

It may seem theoretically trivial to use active reset when targeting a QPU. However, its use can have side effects in practice, as active reset has its own associated fidelity (as seen in the figure under QPU Topology & Fidelities) — it's important to be aware of this when moving from a QVM to a QPU.