Skip to content

Experimental noise model & simulation module (tracking) #1863

Open

Description

@bramathon

Tracking issue for the experimental noise model and simulation module, originally proposed as one large PR (#1848) and split into a stack of five reviewable PRs. This issue holds the overall design; each PR below is scoped to one piece and stacked on the previous one (review/merge top to bottom).

PRs (merge in order)

Each branch is based on the previous one; the tip of #1862 reproduces the original #1848 branch exactly (modulo the per-PR version bumps).


Description

This work adds new noise modeling and simulation utilities. It is intended to replace the existing reference simulators and the existing NoiseModel. The proposed migration path is to add these utilities as experimental private modules, while marking the existing simulation and noise utilities as deprecated. In the next major version of pyquil, the deprecated utilities will be replaced by the new utilities.

Dependencies

This adds the new dependency rigetti-quax. This package provides a wide set of utilities for representing quantum objects including states, gates, measurements and noise channels. It's based on jax, which delivers high performance but adds jax as a transitive dependency to pyquil. jax is a relatively mature package but somewhat larger than existing dependencies (around 80MB compared to scipy at 40MB), so this is worth some discussion.

Noise

First, the noise.py file is promoted to a module. This is non-breaking as the same functions continue to be exported from pyquil.noise. Inside the module are several new private files. When these are promoted to the public API, they will be exported from pyquil.noise.

New Noise Model System (pyquil/noise/_noise_model.py)

We introduce a frozen-dataclass-based NoiseModel container that collects per-instruction noise channels. The role of the noise model is to store a collection of channels, which together make up a device noise model. Channels are associated with instructions in the program and we can get the channel for a particular instruction via NoiseModel.get_channel(inst).

  • NoiseModelLike — Protocol defining the get_channel interface for custom implementations.
  • NoiseModel — The canonical use case. Accepts an Iterable of channels (list, tuple, set, generator), stores them as an immutable Tuple. Supports + for composition and get_channel(inst) for lookup.
  • DepolarizingNoiseModel — Convenience model returning a depolarizing channel for any gate.
  • CompositeNoiseModel — Chains multiple NoiseModelLike objects, returning the first non-None channel.

Noise models can also be constructed from the instruction set architecture, giving users a straightforward path to a device-realistic noise model.

Quax-Backed Noise Channels (pyquil/noise/_channels.py)

Four frozen dataclasses representing physical noise processes, each backed by a quax operator (SuperOp, KrausMap, or QuantumInstrument). The main role of the channel is to associate a superoperator with a particular instruction. For example, the instruction CZ 0 1 indicates that qubits 0 and 1 undergo the unitary CZ operation (via the quil spec); the channel associates that instruction with a superoperator — a higher-dimensional representation of the operation that includes the effects of noise.

Class Purpose
Channel Noisy gate instruction
MeasurementChannel Noisy measurement (QuantumInstrument)
ResetChannel Noisy reset operation
CycleChannel Noise for an entire instruction cycle (multi-gate group)

Simulation

A noise model is not very interesting on its own; we want a simulator which can simulate its effects on programs. The existing pyquil simulators are limited: the numpy reference simulators have poor performance, while the QVM has limited options for representing noise. Here we replace the reference numpy simulators with a jax-accelerated, flexible simulation framework.

Simulators (pyquil/simulation/_simulator.py)

A simulator object is constructed from a program. The hierarchy is based on capabilities and scale.

Simulator Use Case JAX-Compatible Scale Noise Measurements
PureStateVectorSimulator Gate-only programs (no noise/measurements/resets) jit + grad <26 qubits No No
DensityMatrixSimulator Any program, optionally with noise jit + grad <13 qubits Yes No
TrajectorySimulator Monte Carlo trajectory simulation jit (per-batch) <26 qubits Yes Yes
DynamicTrajectorySimulator Eager, dynamic per-qudit dims (leakage-aware) No (data-dependent shapes) leakage-dependent Yes Yes

Linearizer / DAG / Resolver / Compressor Pipeline (pyquil/simulation/_resolver.py)

The simulator is an object rather than a simple function because efficient simulation requires constructing several related closures — the linearizer, resolver, compressor, and calculator.

  • Linearizer — Converts MemoryMap → flat JAX parameter vector.
  • Resolver — Converts parameter vector → (operator, subsystem) pairs, consulting the noise model.
  • Compressor — Greedy edge contraction on the program DAG, merging adjacent operators up to max_subsystem_size qubits.
  • Calculator — Computes the final state from the resolved and compressed operators.

These closures are tightly coupled — each is constructed from the program structure and can be compiled with jax.jit, which is key to good performance. The simulator class combines them into a single logical construct. The architecture is documented in docs/source/simulation_architecture.rst (added in #1860).

Usage

from pyquil.gates import CNOT, H
from pyquil.quil import Program
from pyquil.simulation._simulator import PureStateVectorSimulator

program = Program(H(0), CNOT(0, 1))
sim = PureStateVectorSimulator(program)
psi = sim.compute()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions