Introducing pyQuil v4

The 4.0 major release of pyQuil moves the foundation of program parsing, manipulation, compilation, and execution into Rigetti’s latest generation of SDKs written in Rust. This comes with improved performance, stronger type safety, better error messages, and access to exciting new features.

As a first step, read through the Changelog to get an overview of what’s new. Pay special attention to the breaking changes you may need to accommodate. In the rest of this introduction, we’ll expand on some of the key changes and new features.

Parameters & memory

In order to provide more flexibility when executing parameterized Programs, the execution methods on QAM, QVM, QPU and the like now accept an optional memory_map keyword parameter. This parameter is defined as a mapping of a memory region’s name to a sequence of values that will be used to initialize that memory region before executing the program. This replaces the ability to use the write_memory method on a Program. Here is an example of how you might use a memory map in practice:

from pyquil.api import get_qc
from pyquil.gates import RZ
from pyquil.quil import Program

qc = get_qc("Ankaa-9Q-1")
program = Program()
theta = program.declare("theta", "REAL")
program += RZ(theta, 0)
exe = qc.compile(program)

# Previously, we would've used program.write_memory(region_name="theta", value=0.0)
memory_map = {"theta": [0.0]}

result = qc.run(exe, memory_map=memory_map)

The MemoryMap type is defined as Mapping[str, Union[Sequence[int], Sequence[float]]. Note that the values mapped to a memory region must always be a sequence. This is different from write_memory which would allow writing an atomic value to a region of length 1.

QCS Gateway and execution options

The QCS Gateway is a new service that provides on-demand access to a QPU. See the Gateway documentation for more information on what it is and why you might find it useful.

In pyQuil v4, Gateway is enabled by default and it is generally recommended to keep it on. However, if you have a use case for sending your job to the QPU directly, you can use the new ExecutionOptions and ConnectionStrategy classes to configure your request:

from pyquil.api import get_qc, ExecutionOptionsBuilder, ConnectionStrategy
from pyquil.quil import Program

qc = get_qc("Ankaa-9Q-1")
program = Program()
exe = qc.compile(program)

# Use an ``ExecutionOptionsBuilder`` to build a custom ``ExecutionOptions``
execution_options_builder = ExecutionOptionsBuilder()
execution_options_builder.connection_strategy = ConnectionStrategy.direct_access()
execution_options = execution_options_builder.build()

# Option 1: Override execution options on a per-request basis.
result = qc.run(exe, execution_options=execution_options)

# Option 2: Sets the default options for all execution requests where no execution_options parameter is provided.
result = qc.qam.execution_options = execution_options

Accessing raw execution data

In previous versions of pyQuil, readout data was always returned as a mapping of memory regions to rectangular matrices that contained one value per memory reference, per shot. However, it shouldn’t be assumed that readout data will always fit this shape. For example, programs that reuse qubits or use dynamic control flow can emit a different amount of values per shot, breaking the assumption that readout data will contain one value for each memory reference per shot. In these cases, it’s better to rely on the author of the program to wrangle the data into the shape they expect, so we’ve made it possible to access raw readout data.

Note

You can learn more about how QPU readout data works in the QCS documentation

In v4, readout data continues to be accessible in the same way as before, but if the readout data generated by your program doesn’t fit a rectangular matrix, a RegisterMatrixConversionError will be raised. In this case, you should use the get_raw_readout_data method to access the raw data and build the data structure you need.

Warning

It’s possible to have a program that results in a rectangular matrix of readout data, but have more than one value per memory reference per shot due to qubit reuse. In these cases, a RegisterMatrixConversionError will _not_ be raised, since the resulting matrix would be valid for some number of shots. It’s important to be aware of this possibility, and to still use get_raw_readout_data if that possibility is a concern.

import numpy as np
from pyquil.api import RegisterMatrixConversionError

def process_raw_data(raw_data) -> np.ndarray:
     # Process the data into a matrix that makes sense for your
     # program
     ...

result = qc.run(exe)

try:
     matrix = result.get_register_map()
 except RegisterMatrixConversionError:
     matrix = process_raw_data(result.get_raw_readout_data())

Using the new QPU Compiler Backend

Rigetti’s next-generation QPU compiler is accessible through pyQuil v4. This backend is required for Ankaa-family QPUs and can be configured with the new QPUCompilerAPIOptions class. This class is a type alias of qcs-sdk-python’s TranslationOptions. See its API documentation for more information on all the available parameters.

from pyquil.api import get_qc, QPUCompilerAPIOptions
from pyquil.quil import Program

program = Program()
qc = get_qc("Ankaa-9Q-1")

api_options = QPUCompilerAPIOptions()
api_options.v2(
   passive_reset_delay_seconds=0.0002
)

# Option 1: Apply to all compiled programs
qc.compiler.api_options = api_options

# Option 2: Apply to one specific compilation
qc.compiler.native_quil_to_executable(program, api_options=api_options)