qcs 0.26.0-rc.0

High level interface for running Quil on a QPU
Documentation

qcs-sdk-rust

The qcs crate is a high-level interface to Rigetti's Quantum Cloud Services, allowing Rust developers to run Quil programs on Rigetti's QPUs. This crate is a Rust port of PyQuil, though it currently has a much smaller feature set.

For the C-bindings to this library, check out qcs-sdk-c

Documentation

This crate is documented primarily via rustdoc comments and examples, which are available on docs.rs.

Features

The crate supports several features, and some of them imply others, forming a DAG:

Feature Description Implied Features
manual-tests Used to run tests that require some manual setup.
experimental Enables experimental features specific to Rigetti QPUs.
tracing Enables the tracing features in qcs-api-client-* crates.
tracing-config Enables the tracing-config features in qcs-api-client-* crates. tracing
tracing-opentelemetry Enables tracing with OpenTelemetry. tracing-config
python Exposes Python bindings via PyO3. experimental, tracing-opentelemtry
stubs Enables Python typing for stub generation. python

In addition, there currently exists the feature _insecure-issuer-validation which enables a feature of the same name in the qcs-api-client-common crate. The purpose of the feature there, as here, is to relax token issuer validation during testing. It is not intended to be used in production, and not considered part of the public API.

Changes Post Crate Merge

Prior to v0.21 (for Python users) and v0.25 (for Rust users), this repository maintained separate a crate to expose Python bindings to this library. Starting at the above versions, we've merged those crate together, supported by the python feature flag. With this change, we've united the version numbers for packages.

The new version comes with breaking changes for Python users, which are summarized below and detailed in the CHANGELOG. For the trouble, we now support the latest versions of PyO3 and Python. This comes with improvements to security, stability, and performance and makes our codebase much easier to support moving forward.

Rust Breaking Changes

For Rust consumers, the previously Unit-variants of the qvm::api::AddressRequest enum are now empty tuple variants to make them compatible with PyO3's "Complex enums". That means the following variants must have () appended to their usages:

// Previously you could use this:
match address_request {
    qvm::api::AddressRequest::IncludeAll => ...,
    qvm::api::AddressRequest::ExcludeAll => ...,
    qvm::api::AddressRequest::Indices(indices) => ...,
}

// Now you must use this:
match address_request {
    qvm::api::AddressRequest::IncludeAll() => ...,
    qvm::api::AddressRequest::ExcludeAll() => ...,
    qvm::api::AddressRequest::Indices(indices) => ...,
}

Python Breaking Changes

For Python consumers, please be aware of the following changes:

  • We are dropping support for Python 3.9 and adding support through Python 3.13.
  • We no longer wrap Rust types with an additional layer for Python interop, but instead directly expose them as #[pyclass]es; in particular, the from_*, as_*, to_*, is_*, and inner methods have been removed, You should replace their usage with more typical Python operations (see below for examples).
  • Some Python classes that conceptually represented a collection of possible types now operate as if they were simply a type alias of those possibilities:
    • The Register class is now simply list[int] | list[complex].
    • The ISA Family enumeration is now a simple enum of known values; unknown values are represented simply as their str name.
    • These classes remain "Complex Enums" with different class variants, but in many cases, functions and constructors have been updated to accept any of the aliased varaints, and the inner method returns the same:
      • RegisterData: list[list[int]] | list[list[float]] | list[list[complex]]
      • ReadoutValues: list[int] | list[float] | list[complex]
      • MemoryValues: list[int] | list[float]
  • compiler.quilc.CompilationResult.program is now a quil.Program instead of a str. To obtain the str representation, simply call to_quil() on the program.
  • Service.Quilc has been renamed Service.QUILC to match other Python enumeration variants.
  • ExecutionData now requires result_data, whereas it had been optional for pickle support. Constructing it without explicit result_data is almost certainly a bug, so now that it implements __getnewargs__, the result_data parameter is required.
  • Most synchronous wrappers around _async methods and functions now raise an error if used within an async context. You should use await and the _async method instead. Although some synchronous wrappers may happen to work within an async context incidentally, it is not officially supported behavior, and you should use their async counterparts.

Specific Python Examples

Instead of using from_*, just use the target class's constructor directly. For example:

- readout_value = ReadoutValues.from_integer([0, 1])
+ readout_value = ReadoutValues.Integer([0, 1])

Replace is_*, to_*, as_*, and inner with match. Here's an example of extracting inner elements:

match readout_value:
    case (ReadoutValues.Integer(matrix) | ReadoutValues.Real(matrix) | ReadoutValues.Complex(matrix)):
        return matrix[0]
    case _:
        raise ValueError(f"Unsupported readout type: {type(ro)}")

If needed, you can replace inner with _0, usually paired with an isinstance check. Keep in mind that enumerated subclasses are often named after the class they take as a parameter. The following asserts are all valid:

readout_value = ReadoutValues.Integer([0, 1])
assert isinstance(readout_value, ReadoutValues)
assert isinstance(readout_value, ReadoutValues.Integer)
assert readout_value._0 == np.array([0, 1], dtype=np.int32)

Development

Most development tasks are automated with cargo-make (like make, but you can have dependencies on other Rust tools and a ton of useful tasks are built in). Install cargo-make by doing cargo install cargo-make. Then you can invoke it with either cargo make <task> or makers <task>. Tasks are defined in files called Makefile.toml.

In order to run all checks exactly the same way that CI does, use makers ci-flow from the project root (workspace).

Dependencies

Because this library relies on [ØMQ], [cmake] is required:

  • macOS [Homebrew] : brew install cmake
  • Windows [Chocolatey]: choco install cmake
  • Debian: apt install cmake

Running Tests

The best way to go about this is via makers or cargo make with no task. This will default to dev-test-flow which formats all code, builds, and tests everything.

Any tests which cannot be run in CI should be run with makers manual. These tests require configured QCS credentials with access to internal functions, as well as a connection to the Rigetti VPN.

Linting

makers lint will lint run all static checks.

Documentation

To build the docs.rs-style docs, run makers docs. You can also do makers serve-docs to launch a local webserver for viewing immediately.

Release

To release this crate, manually run the release workflow in GitHub Actions.