from pecos import sim, Qasm, GeneralNoiseModelBuilder
PECOS provides builder classes for constructing quantum noise models with a fluent, method-chaining API. The GeneralNoiseModelBuilder is the most comprehensive builder, offering fine-grained control over various noise parameters.
The simplest way to add noise to your QASM simulations is using the GeneralNoiseModelBuilder:
from pecos import sim, Qasm, GeneralNoiseModelBuilder
# Define a circuit
qasm = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0], q[1];
measure q -> c;
"""
# Create noise model with builder
noise = (
GeneralNoiseModelBuilder()
.with_seed(42) # Reproducible randomness
.with_p1_probability(0.001) # Single-qubit gate error
.with_p2_probability(0.01)
) # Two-qubit gate error
# Use with sim()
results = sim(Qasm(qasm)).noise(noise).run(1000)The GeneralNoiseModelBuilder provides methods to configure all aspects of quantum noise:
noise = (
GeneralNoiseModelBuilder()
# Gate errors
.with_p1_probability(0.001) # Single-qubit gate error
.with_p2_probability(0.01) # Two-qubit gate error
# State preparation and measurement
.with_prep_probability(0.0005) # State preparation error
.with_meas_0_probability(0.002) # Measurement 0→1 flip
.with_meas_1_probability(0.003)
) # Measurement 1→0 flipThe builder supports both "total" and "average" error probabilities:
# Average probability (recommended for physical intuition)
noise = (
GeneralNoiseModelBuilder()
.with_average_p1_probability(0.001) # Converted to total internally
.with_average_p2_probability(0.01)
)
# Total probability (used internally by the engine)
noise = (
GeneralNoiseModelBuilder().with_p1_probability(0.00133).with_p2_probability(0.0133) # Total for single-qubit
) # Total for two-qubitNote: Average probabilities are more intuitive as they represent the actual error rate per gate. Total probabilities include a conversion factor based on the number of Pauli operators.
Specify custom Pauli error distributions instead of uniform depolarizing noise:
noise = (
GeneralNoiseModelBuilder()
# Single-qubit Pauli errors
.with_p1_pauli_model(
{
"X": 0.5, # 50% X errors
"Y": 0.3, # 30% Y errors
"Z": 0.2, # 20% Z errors
}
)
# Two-qubit Pauli errors
.with_p2_pauli_model(
{
"IX": 0.25, # 25% error on second qubit only
"XI": 0.25, # 25% error on first qubit only
"XX": 0.5, # 50% correlated X errors
}
)
)noise = (
GeneralNoiseModelBuilder()
.with_seed(42) # Random seed for reproducibility
.with_scale(1.5) # Scale all error rates by 1.5x
.with_leakage_scale(0.1) # 10% of errors cause leakage
.with_emission_scale(0.05)
) # 5% spontaneous emissionMake specific gates ideal (no noise):
noise = (
GeneralNoiseModelBuilder()
.with_p1_probability(0.001)
.with_p2_probability(0.01)
# Single gate
.with_noiseless_gate("H")
# Multiple gates
.with_noiseless_gate("S")
.with_noiseless_gate("T")
.with_noiseless_gate("MEASURE")
)Idle gates are timing markers by default. They do not silently inherit
single-qubit gate noise from p1 or with_p1_probability(...).
This is intentional: adding an idle location changes circuit timing, while adding idle noise changes the physical noise model. To model idle decoherence, use an API that explicitly attaches idle noise or an explicit channel to idle locations. This keeps scheduling changes from accidentally changing the noise model.
Simple uniform noise on all operations:
# Uniform depolarizing noise
noise = (
GeneralNoiseModelBuilder()
.with_p1_probability(0.001)
.with_p2_probability(0.01)
.with_prep_probability(0.001)
.with_meas_0_probability(0.001)
.with_meas_1_probability(0.001)
)Model based on typical superconducting qubit parameters:
noise = (
GeneralNoiseModelBuilder()
.with_seed(42)
# Gate errors (two-qubit gates are typically 10x worse)
.with_average_p1_probability(0.0001) # 0.01% single-qubit error
.with_average_p2_probability(0.001) # 0.1% two-qubit error
# State prep and measurement (often dominant errors)
.with_prep_probability(0.001) # 0.1% prep error
.with_meas_0_probability(0.01) # 1% false positive
.with_meas_1_probability(0.005)
) # 0.5% false negativeIon traps have different characteristics than superconducting qubits:
noise = (
GeneralNoiseModelBuilder()
.with_seed(42)
# Excellent single-qubit gates
.with_average_p1_probability(0.00001) # 0.001% error
# Two-qubit gates are the limiting factor
.with_average_p2_probability(0.003) # 0.3% error
# State preparation
.with_prep_probability(0.001) # 0.1% error
# Asymmetric measurement (bright/dark state detection)
.with_meas_0_probability(0.001) # Dark state error
.with_meas_1_probability(0.005)
) # Bright state error (higher)Model with biased errors (e.g., more phase errors than bit flips):
noise = (
GeneralNoiseModelBuilder()
# Biased single-qubit errors
.with_average_p1_probability(0.001)
.with_p1_pauli_model(
{
"X": 0.1, # 10% bit flips
"Y": 0.1, # 10% Y errors
"Z": 0.8, # 80% phase errors (dominant)
}
)
# Biased two-qubit errors
.with_average_p2_probability(0.01)
.with_p2_pauli_model(
{
"IZ": 0.3, # 30% phase on second qubit
"ZI": 0.3, # 30% phase on first qubit
"ZZ": 0.2, # 20% correlated phase
"XX": 0.2, # 20% other errors
}
)
)Here's a comprehensive example showing various builder features:
from pecos import sim, Qasm
from pecos_rslib import GeneralNoiseModelBuilder
from collections import Counter
# QASM circuit: 3-qubit GHZ state
qasm = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[3];
creg c[3];
h q[0];
cx q[0], q[1];
cx q[1], q[2];
measure q -> c;
"""
# Build comprehensive noise model
noise = (
GeneralNoiseModelBuilder()
# Reproducibility
.with_seed(42)
# Global scaling
.with_scale(1.2) # 20% higher error rates
# Make Hadamard gates perfect
.with_noiseless_gate("H")
# State preparation
.with_prep_probability(0.001)
# Single-qubit gates with biased errors
.with_average_p1_probability(0.0001)
.with_p1_pauli_model(
{
"X": 0.2,
"Y": 0.2,
"Z": 0.6, # More dephasing
}
)
# Two-qubit gates
.with_average_p2_probability(0.001)
# Asymmetric measurement
.with_meas_0_probability(0.002)
.with_meas_1_probability(0.005)
)
# Run simulation
results = sim(Qasm(qasm)).noise(noise).run(1000)
# Analyze results
counts = Counter(results.to_dict()["c"])
print("GHZ state measurement results:")
for state, count in counts.most_common(5):
binary = format(state, "03b")
print(f"|{binary}>: {count}")-
Use Average Probabilities: They're more intuitive and match experimental error rates.
-
Set Seeds for Reproducibility: Always use
.with_seed()for reproducible results in research. -
Start Simple: Begin with uniform probabilities, then add complexity as needed.
-
Match Hardware Specs: Use error rates from device calibration data when available.
-
Consider Error Hierarchies: Typically: measurement > two-qubit > state prep > single-qubit.
-
Use Noiseless Gates Sparingly: Only for gates that are effectively perfect (e.g., virtual Z rotations).
While builders offer maximum flexibility, PECOS also provides simpler convenience functions:
from pecos import depolarizing_noise, GeneralNoiseModelBuilder
# Simple depolarizing (uniform probability)
simple = depolarizing_noise().with_uniform_probability(0.001)
# Equivalent with GeneralNoiseModelBuilder
builder = (
GeneralNoiseModelBuilder()
.with_p1_probability(0.001)
.with_p2_probability(0.001)
.with_prep_probability(0.001)
.with_meas_0_probability(0.001)
.with_meas_1_probability(0.001)
)
# Builder advantages:
# - Fine-grained control
# - Pauli error models
# - Scaling factors
# - Noiseless gates
# - Crosstalk modeling- For performance optimization, see QASM Simulation Guide
- For the complete API reference, see the API Documentation