Edit

Build noise models for multi-qubit gates

The Microsoft Quantum Development Kit (QDK) Python package supports flexible noise models for multi-qubit gates in quantum program simulations. You can model correlated errors and other noise effects across multiple qubits to more accurately represent the behavior of quantum hardware.

For more information on noise models in the QDK, see How to build noise models for quantum simulations in the QDK.

Prerequisites

To build noise models in the QDK, install these tools.

  • Visual Studio Code (VS Code) with QDK extension and Jupyter extension installed.
  • The latest version of the qdk Python package with the jupyter extra.
pip install --upgrade "qdk[jupyter]"

Correlated noise

Multi-qubit gates can produce correlated noise, where the same noise pattern applies to all qubits that the gate operates on. To set correlated noise on multi-qubit gates, specify a noise parameter for each qubit.

For example, this Python code sets correlated bit flips on $CX$ gates with a 2% probability. When noise occurs on a $CX$ gate, an $X$ gate applies to both the control qubit and the target qubit. The noise is correlated because the noise always applies to both qubits.

from qdk.simulation import NoiseConfig

noise = NoiseConfig()

noise.cx.xx = 0.02

To make the noise uncorrelated, set multiple noise types with the identity parameter.

noise.cx.xi = 0.02 # Bit flip on control qubit, do nothing to target qubit
noise.cx.ix = 0.02 # Do nothing to control qubit, bit flip on target qubit

In this model, each noise entry has a 2% probability. Because the simulator samples at most one configured noise entry for each gate operation, this model can't apply $X$ noise to both qubits during the same $CX$ operation.

To model the possibility of noise on both qubits, configure another noise setting that applies noise to both qubits.

noise.cx.xi = 0.02 # Bit flip on control qubit, do nothing to target qubit
noise.cx.ix = 0.02 # Do nothing to control qubit, bit flip on target qubit
noise.cx.xx = 0.02 # Bit flip on both qubits

Noise on non-Clifford multi-qubit rotations

The stabilizer simulator supports arbitrary-angle $R_{XX}$, $R_{YY}$, and $R_{ZZ}$ rotations through stabilizer branching. Noise configured on rxx, ryy, or rzz is applied after the ideal rotation. This behavior doesn't change if the rotation creates additional stabilizer branches.

Each non-Clifford rotation can increase the number of branches, so memory usage and shot time can grow exponentially. Use the stabilizer simulator for circuits with a limited number of non-Clifford rotations, and use the CPU or GPU full-state simulator when non-Clifford operations make up a substantial part of the circuit.

Qubit loss policies

Qubit loss is a type of noise where qubits are lost from the device. Loss policies define the behavior of a multi-qubit gate when one or more of the qubits is missing when the operation begins. The QDK supports these loss policies.

Loss policy Gates the policy can apply to Effect on remaining qubits
SKIP All gates The gate has no effect on the remaining qubits
PROPAGATE All gates The other qubits are also lost
DEGRADE rxx, ryy, and rzz Apply as single-qubit gate to remaining qubit
RESIDUAL_S_DAGGER All gates Apply s_adj gate to remaining qubits
APPLY_ANYWAY swap Remaining qubit still swaps states with lost qubit

For loss policies to affect your simulation, include qubit loss in your noise model. The simulation tracks lost qubits. Loss policies apply when a gate operates on at least one lost qubit.

To include loss policies in your noise model, use LossPolicy from the qdk.simulation module. This code shows examples to set each policy.

from qdk.simulation import NoiseConfig, LossPolicy

noise = NoiseConfig()

noise.x.l = 0.01 # 1% chance of qubit loss after X gates

noise.cz.on_loss   = LossPolicy.SKIP               # Don't apply CZ to remaining qubit
noise.cx.on_loss   = LossPolicy.PROPAGATE          # Both qubits are lost from CX gate
noise.rxx.on_loss  = LossPolicy.DEGRADE            # Apply Rx gate to remaining qubit
noise.ryy.on_loss  = LossPolicy.RESIDUAL_S_DAGGER  # Apply S-adjoint to remaining qubit
noise.swap.on_loss = LossPolicy.APPLY_ANYWAY       # Perform swap anyway

For all loss policies, configured noise can still affect the remaining qubits. For example, this code can still apply bit flip noise on CZ gates even when one of the qubits in a CZ gate is missing.

noise.z.l = 0.01 # Introduce qubit loss

noise.cz.xx = 0.02 # Set correlated bit flip noise on CZ gates
noise.cz.on_loss = LossPolicy.SKIP

Custom noise intrinsics

To build more complex noise models, the QDK has custom noise intrinsics for Q# and OpenQASM programs. Noise intrinsics behave like custom gates that you insert into your program to model correlated noise. You can use custom intrinsics to model noise on the gates that NoiseConfig supports, or on custom gates.

These examples show how to build a custom noise intrinsic that models crosstalk between three qubits. The noise intrinsic applies correlated bit flips to two of the qubits after a $CNOT$ gate is applied.

Add noise intrinsics to a Q# program

In Q# programs, use @NoiseIntrinsic() to declare a noise intrinsic. Then, use the intrinsic method from NoiseConfig to configure the noise intrinsic.

To configure and use the example noise intrinsic, follow these steps in a Jupyter notebook.

  1. Import the required objects and set the QIR target profile.

    from qdk import init, TargetProfile
    from qdk import qsharp
    from qdk.simulation import run_qir, NoiseConfig
    from qdk.widgets import Histogram
    
    init(target_profile=TargetProfile.Adaptive_RIF)
    
  2. Write a Q# program called GHZ that calls a noise intrinsic called Crosstalk3Q after each $CNOT$ gate.

    %%qsharp
    
    // A noise intrinsic that represents crosstalk on 3 qubits.
    // In the ideal circuit this is a no-op; the simulator injects
    // Pauli errors according to the NoiseConfig.
    @NoiseIntrinsic()
    operation Crosstalk3Q(q0: Qubit, q1: Qubit, q2: Qubit) : Unit {
        body intrinsic;
    }
    
    // Prepare a GHZ state on 3 qubits, with crosstalk after each CNOT.
    operation GHZ() : Result[] {
        use qs = Qubit[3];
        H(qs[0]);
        CNOT(qs[0], qs[1]);
        Crosstalk3Q(qs[0], qs[1], qs[2]);  // crosstalk hits all 3 qubits
        CNOT(qs[1], qs[2]);
        Crosstalk3Q(qs[0], qs[1], qs[2]);  // crosstalk again
        MResetEachZ(qs)
    }
    
  3. Configure the noise table for the intrinsic. Set the number of qubits, the types of noise, and the probability for each noise type.

    noise = NoiseConfig()
    table = noise.intrinsic("Crosstalk3Q", num_qubits=3)
    table.ixx = 0.10  # 10% XX on qubits 1-2
    table.xxi = 0.05  #  5% XX on qubits 0-1
    
  4. Compile the program to QIR.

    qir = qsharp.compile("GHZ()")
    
  5. Run the simulation on the stabilizer simulator and plot a histogram of the results.

    result = run_qir(qir, shots=1000, noise=noise, type="stabilizer")
    Histogram(result)
    
  6. To compare the result to a simulation without noise, run the simulation again with no noise model.

    result = run_qir(qir, shots=1000, type="stabilizer")
    Histogram(result)
    

Add noise intrinsics to an OpenQASM program

In OpenQASM programs, use @qdk.qir.noise_intrinsic to create a noise intrinsic as a custom gate definition. Then, use the intrinsic method from NoiseConfig to configure the noise intrinsic.

To write an OpenQASM program with a noise intrinsic called crosstalk_3q and compile the program into QIR, run this code in a Jupyter notebook.

from qdk.openqasm import compile, OutputSemantics
from qdk import TargetProfile
from qdk.simulation import run_qir, NoiseConfig
from qdk.widgets import Histogram

qasm_source = """
OPENQASM 3.0;
include "stdgates.inc";

// A noise intrinsic that represents crosstalk on 3 qubits.
// In the ideal circuit this is a no-op; the simulator injects
// Pauli errors according to the NoiseConfig.
@qdk.qir.noise_intrinsic
gate crosstalk_3q q0, q1, q2 {}

qubit[3] qs;

// Prepare a GHZ state on 3 qubits, with crosstalk after each CNOT.
h qs[0];
cx qs[0], qs[1];
crosstalk_3q qs[0], qs[1], qs[2];  // crosstalk hits all 3 qubits
cx qs[1], qs[2];
crosstalk_3q qs[0], qs[1], qs[2];  // crosstalk again

bit[3] res = measure qs;
"""

qir_qasm = compile(
    qasm_source,
    output_semantics=OutputSemantics.OpenQasm,
    target_profile=TargetProfile.Base,
)

To configure the noise intrinsic and run the simulation, run this code.

noise = NoiseConfig()
table = noise.intrinsic("crosstalk_3q", num_qubits=3)
table.ixx = 0.10  # 10% XX on qubits 1-2
table.xxi = 0.05  #  5% XX on qubits 0-1

result = run_qir(
    qir_qasm,
    shots=1000,
    noise=noise,
    type="stabilizer",
)
Histogram(result)