Skip to content

Add a dispatch-based API for turning noiseless circuits into noisy circuits #729

Description

@Krastanov

This issue is part of unitaryHACK26. You have to be registered to complete this issue.
Learn more about the PR submission process here and about unitaryHACK rules here!

A note about AI Slop: while we are open to collaboration with LLMs for unitaryHACK, fully AI-generated PRs are not acceptable. It is at the maintainers' discretion whether or not LLM-generated PRs are the right fit for the issues, and those that appear fully AI-generated may be immediately rejected. Read unitaryHACK's full AI Policy here.

Note from the maintainer: If you have not contributed before to open source projects, no AI agent use is permitted for this issue.


QuantumClifford already has useful building blocks for noisy simulations:
NoiseOp, NoiseOpAll, NoisyGate, PauliNoise,
UnbiasedUncorrelatedNoise, mctrajectories, and pftrajectories. However,
users currently have to manually insert noise operations into circuit vectors or
write one-off helper functions. A small dispatch-based noisify API would make
it much easier to define a noise model once and apply it consistently to a
noiseless circuit.

Relevant links:

Desired user-facing behavior

A simple noise model should be easy to apply to a vector of operations:

using QuantumClifford

circuit = [sHadamard(1), sCNOT(1, 2), sMZ(1, 1)]
noisy = noisify(circuit, PauliNoise(1e-3, 1e-3, 1e-3))

The core behavior can start from this multiple-dispatch pattern:

noisify(circuit::AbstractVector, noise) = reduce(vcat, noisify.(circuit, (noise,)))
noisify(anything, noise) = [anything]
noisify(g::AbstractSingleQubitOperator, noise) = [NoiseOp(noise, affectedqubits(g)), g]
noisify(g::AbstractTwoQubitOperator, noise) = [NoiseOp(noise, affectedqubits(g)), g]

For practical use, there should also be a structured configuration object for
different noise locations:

noise = CircuitNoise(
    single_qubit = PauliNoise(1e-4, 1e-4, 1e-4),
    two_qubit = PauliNoise(1e-3, 1e-3, 1e-3),
    idle = PauliNoise(1e-5, 1e-5, 1e-5),
    measurement = PauliNoise(2e-3, 2e-3, 2e-3),
)

noisy = noisify(circuit, noise; nqubits=2)
frames = pftrajectories(noisy; trajectories=10_000)

The exact names can change, but the API should preserve the ability to extend
behavior with method definitions for new operation types and new noise-model
types.

Suggested implementation

Implement a small CircuitNoise or NoiseModel type with fields for common
locations:

  • single-qubit gate noise
  • two-qubit gate noise
  • idle/waiting noise
  • measurement noise
  • reset noise, if separate from measurement noise

Also consider a NoNoise sentinel for omitted locations. This avoids requiring
users to pass nothing and makes dispatch cleaner.

Useful behavior to define:

  • noisify(circuit::AbstractVector, noise_model; nqubits=nothing) returns a new
    vector and does not mutate the input.
  • Single-qubit and two-qubit Clifford gates get the configured gate noise on
    their affected qubits.
  • Measurements get the configured measurement noise. It is acceptable for the
    first version to model this as pre-measurement Pauli noise on the measured
    qubits, as long as the semantics are documented.
  • NoiseOp and NoisyGate should not accidentally get double-noisified unless
    explicitly requested.
  • Classical-only operations such as ClassicalXOR should pass through unchanged.
  • Idle noise can be added to setdiff(1:nqubits, affectedqubits(op)) for each
    operation when nqubits is provided. If idle noise is requested and the number
    of qubits cannot be inferred, throw a helpful error.

In scope

  • A documented noisify API.
  • A structured noise configuration type.
  • Dispatch methods for single-qubit gates, two-qubit gates, measurements, reset
    measurements, existing noise operations, and classical operations.
  • Tests that verify the inserted operation order and affected qubits.
  • Tests showing the resulting noisy circuit can be simulated with
    pftrajectories or mctrajectories.
  • Documentation with a minimal example and a configuration example.

Out of scope

  • Automatic circuit scheduling or momentization.
  • Importing hardware calibration files.
  • General non-Pauli quantum channels.
  • Rewriting the existing trajectory simulators.

Acceptance criteria

  • noisify([sHadamard(1), sCNOT(1, 2)], noise) returns a circuit containing
    NoiseOp entries on the expected qubits.
  • A structured model can assign different noise to one-qubit gates, two-qubit
    gates, idle qubits, and measurements.
  • The original circuit is not mutated.
  • Existing explicit NoiseOp operations are handled in a documented way.
  • Tests cover the simple dispatch example and the structured configuration case.
  • The docs explain where the noise is inserted relative to each operation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions