Simulate Noisy Circuits

Quantum computations are susceptible to noise from the environment, which can introduce errors and alter results. pyrauli provides a comprehensive NoiseModel class to simulate the effects of various noise channels on your quantum circuits. This guide demonstrates how to apply noise models when working with Circuit objects directly, or when using pyrauli as a backend within Qiskit via PBackend and PyrauliEstimator.

Apply Noise to a pyrauli Circuit

You can introduce noise into a simulation by creating a NoiseModel and associating it with a Circuit. The noise model is applied automatically.

# 1. Create a Noise Model that adds depolarizing noise after every H gate and amplitude damping noise after each X gate
p = 0.1  # Noise strength
noise_model = NoiseModel()
noise_model.add_unital_noise_on_gate(QGate.H, UnitalNoise.Depolarizing, p)
noise_model.add_amplitude_damping_on_gate(QGate.X, p)

# 2. Create a noisy circuit using the noise model
qc_noisy = Circuit(1, noise_model=noise_model)
qc_noisy.add_operation("H", 0)

# 3. Run, everything is taken care of automatically
res_noisy = qc_noisy.run(Observable("X"))

The Importance of Truncation with Noise

Noise models, especially those that split a single Pauli term into multiple terms (like amplitude damping), can cause the number of terms in an Observable to grow rapidly. Without management, this can quickly make the simulation intractable.

To counteract this, it is crucial to use a Truncator with an appropriate SchedulingPolicy when running noisy simulations. This will prune terms that are unlikely to contribute significantly to the final expectation value, keeping the simulation feasible.

The example below shows how to configure a circuit to apply a CoefficientTruncator after every gate that splits the observable, ensuring the complexity remains under control.

qc = Circuit(
    nb_qubits=4,
    truncator=CoefficientTruncator(0.1),
    # Crucially, the policy must be set to run the truncator
    truncate_policy=AlwaysAfterSplittingPolicy()
)

Note

See Manage Simulation Complexity for more information on using Truncator.

Use Noise Models with Qiskit Backends

When using pyrauli as a backend within the Qiskit ecosystem, you can specify noise models at two levels: either when initializing the backend for a default noise setting, or on a per-run basis for specific experiments.

With PBackend

You can initialize a PBackend with a default noise model.

# Define a default noise model
p = 0.1
default_noise = NoiseModel()
default_noise.add_unital_noise_on_gate(QGate.H, UnitalNoise.Depolarizing, p)

# Initialize the backend with this model
backend = PBackend(noise_model=default_noise)

However, you can easily override this default by passing a different NoiseModel directly to the run() method. This provides the flexibility to compare different noise scenarios without creating multiple backend instances.

job = backend.run(
    [simple_pub],
    truncator=override_trunc,
    merge_policy=override_policy,
    truncate_policy=override_policy,
    noise_model=override_nm
)

With PyrauliEstimator

The same principle applies to the PyrauliEstimator. You can configure a default noise model during initialization or override it in the run() call for specific Program-Backend-Unification (PUB) executions.

This example demonstrates how to set an initial NoiseModel on the estimator and then override it in a run() call to apply a different noise channel for a specific job.

estimator = PyrauliEstimator(
    truncator=initial_trunc,
    merge_policy=initial_policy,
    truncate_policy=initial_policy,
    noise_model=initial_nm
)

# 3. Run with overriding options
job = estimator.run(
    [simple_pub],
    truncator=override_trunc,
    merge_policy=override_policy,
    truncate_policy=override_policy,
    noise_model=override_nm
)
result = job.result()