Skill Tracer
Skills › Scientific Agent Skills › cirq › noise.md

noise.md

234 lines · skills/cirq/references/noise.md at 5f2bc03

Open on GitHub
1# Noise modeling and mitigation
2
3Examples target Cirq 1.7.0 and small local systems. Noise assumptions are part of
4the scientific model: specify gate durations, idle evolution, readout, qubit
5order, and calibration date. A phenomenological channel is not a complete
6hardware characterization.
7

Noise modeling and mitigation

Examples target Cirq 1.7.0 and small local systems. Noise assumptions are part of the scientific model: specify gate durations, idle evolution, readout, qubit order, and calibration date. A phenomenological channel is not a complete hardware characterization.

Raw lines 1 to 7
8## Channels and their parameters
9
10```python
11import cirq
12import numpy as np
13from scipy.optimize import curve_fit
15q = cirq.LineQubit(0)
16simulator = cirq.DensityMatrixSimulator(dtype=np.complex128, seed=42)
17channels = [
18 cirq.depolarize(p=0.01),
19 cirq.amplitude_damp(gamma=0.1),
20 cirq.phase_damp(gamma=0.1),
21 cirq.bit_flip(p=0.01),
22 cirq.phase_flip(p=0.01),
23 cirq.generalized_amplitude_damp(p=0.8, gamma=0.2),
25for channel in channels:
26 rho = simulator.simulate(cirq.Circuit(cirq.H(q), channel(q))).final_density_matrix
27 assert np.isclose(np.trace(rho), 1)
28 assert np.linalg.eigvalsh(rho).min() >= -1e-10
29```
31- `depolarize(p)` applies a nonidentity Pauli with total probability p; p is not
32 directly the average gate infidelity. With `n_qubits=2` it is a joint channel,
33 different from independent one-qubit channels on the pair.
34- `amplitude_damp(gamma)` decays |1> to |0> with probability gamma.
35- `phase_damp(gamma)` multiplies off-diagonal entries by `sqrt(1-gamma)`.
36- `generalized_amplitude_damp(p, gamma)` has decay probability `p*gamma` and
37 excitation probability `(1-p)*gamma`; p weights the relaxation branch.
38- `cirq.reset(q)` resets to |0>. To prepare |1>, reset and then apply X.

Channels and their parameters

import cirq
import numpy as np
from scipy.optimize import curve_fit

q = cirq.LineQubit(0)
simulator = cirq.DensityMatrixSimulator(dtype=np.complex128, seed=42)
channels = [
    cirq.depolarize(p=0.01),
    cirq.amplitude_damp(gamma=0.1),
    cirq.phase_damp(gamma=0.1),
    cirq.bit_flip(p=0.01),
    cirq.phase_flip(p=0.01),
    cirq.generalized_amplitude_damp(p=0.8, gamma=0.2),
]
for channel in channels:
    rho = simulator.simulate(cirq.Circuit(cirq.H(q), channel(q))).final_density_matrix
    assert np.isclose(np.trace(rho), 1)
    assert np.linalg.eigvalsh(rho).min() >= -1e-10
  • depolarize(p) applies a nonidentity Pauli with total probability p; p is not directly the average gate infidelity. With n_qubits=2 it is a joint channel, different from independent one-qubit channels on the pair.
  • amplitude_damp(gamma) decays |1> to |0> with probability gamma.
  • phase_damp(gamma) multiplies off-diagonal entries by sqrt(1-gamma).
  • generalized_amplitude_damp(p, gamma) has decay probability p*gamma and excitation probability (1-p)*gamma; p weights the relaxation branch.
  • cirq.reset(q) resets to |0>. To prepare |1>, reset and then apply X.
Raw lines 8 to 39
40## Gate-specific and qubit-specific noise
42```python
43class GateNoise(cirq.NoiseModel):
44 def noisy_operation(self, op):
45 if cirq.is_measurement(op):
46 return op # model readout separately
47 n = len(op.qubits)
48 if n in (1, 2) and cirq.has_unitary(op):
49 p = 0.001 if n == 1 else 0.01
50 return [op, cirq.depolarize(p, n_qubits=n).on(*op.qubits)]
51 return op
53class QubitSpecificNoise(cirq.NoiseModel):
54 def __init__(self, qubit_noise_map):
55 self.qubit_noise_map = qubit_noise_map
57 def noisy_operation(self, op):
58 if cirq.is_measurement(op):
59 return op
60 return [op, [self.qubit_noise_map[q](q) for q in op.qubits
61 if q in self.qubit_noise_map]]
63circuit = cirq.Circuit(cirq.H(q), cirq.measure(q, key="result"))
64noisy_circuit = circuit.with_noise(GateNoise())
65result = simulator.run(noisy_circuit, repetitions=1000)
66```
68`with_noise` handles the returned operation tree and time ordering. Do not collect
69an operation and its following noise on the same qubit into one `Moment` (overlap
70is invalid). Gate-based models above omit idle-qubit noise. A constant model
71`cirq.ConstantQubitNoiseModel(cirq.depolarize(0.01))` instead adds a layer to every
72system qubit after every nonvirtual moment, including measurement moments.

Gate-specific and qubit-specific noise

class GateNoise(cirq.NoiseModel):
    def noisy_operation(self, op):
        if cirq.is_measurement(op):
            return op  # model readout separately
        n = len(op.qubits)
        if n in (1, 2) and cirq.has_unitary(op):
            p = 0.001 if n == 1 else 0.01
            return [op, cirq.depolarize(p, n_qubits=n).on(*op.qubits)]
        return op

class QubitSpecificNoise(cirq.NoiseModel):
    def __init__(self, qubit_noise_map):
        self.qubit_noise_map = qubit_noise_map

    def noisy_operation(self, op):
        if cirq.is_measurement(op):
            return op
        return [op, [self.qubit_noise_map[q](q) for q in op.qubits
                     if q in self.qubit_noise_map]]

circuit = cirq.Circuit(cirq.H(q), cirq.measure(q, key="result"))
noisy_circuit = circuit.with_noise(GateNoise())
result = simulator.run(noisy_circuit, repetitions=1000)

with_noise handles the returned operation tree and time ordering. Do not collect an operation and its following noise on the same qubit into one Moment (overlap is invalid). Gate-based models above omit idle-qubit noise. A constant model cirq.ConstantQubitNoiseModel(cirq.depolarize(0.01)) instead adds a layer to every system qubit after every nonvirtual moment, including measurement moments.

Raw lines 40 to 73
74## Relaxation with T1 and T2
76For zero-temperature Markovian relaxation, `1/Tphi = 1/T2 - 1/(2*T1)` and the
77phase-damping parameter is `1-exp(-2*t/Tphi)`. Using `1-exp(-t/T2)` as phase damping
78in addition to amplitude damping double counts relaxation and gives the wrong
79coherence decay. The following fixed-duration model is deliberately local to each
80operation; a scheduled hardware model must also account for idle intervals.
82```python
83class ThermalNoise(cirq.NoiseModel):
84 def __init__(self, T1, T2, gate_time):
85 values = np.asarray([T1, T2, gate_time], dtype=float)
86 if not np.all(np.isfinite(values)) or T1 <= 0 or T2 <= 0 or gate_time < 0:
87 raise ValueError("Use finite positive T1/T2 and nonnegative duration")
88 if T2 > 2*T1:
89 raise ValueError("This relaxation model requires T2 <= 2*T1")
90 self.gamma_amp = -np.expm1(-gate_time/T1)
91 inverse_Tphi = max(0.0, 1/T2 - 1/(2*T1))
92 self.gamma_phase = -np.expm1(-2*gate_time*inverse_Tphi)
94 def noisy_operation(self, op):
95 if cirq.is_measurement(op):
96 return op
97 return [op, [
98 [cirq.amplitude_damp(self.gamma_amp)(q), cirq.phase_damp(self.gamma_phase)(q)]
99 for q in op.qubits
100 ]]
102thermal = ThermalNoise(T1=50e-6, T2=30e-6, gate_time=25e-9)
103thermal_circuit = cirq.Circuit(cirq.I(q)).with_noise(thermal)
104plus = np.array([1, 1], dtype=complex) / np.sqrt(2)
105thermal_rho = simulator.simulate(thermal_circuit, initial_state=plus).final_density_matrix
106np.testing.assert_allclose(thermal_rho[0, 1], 0.5*np.exp(-25e-9/30e-6), atol=1e-12)
107```

Relaxation with T1 and T2

For zero-temperature Markovian relaxation, 1/Tphi = 1/T2 - 1/(2*T1) and the phase-damping parameter is 1-exp(-2*t/Tphi). Using 1-exp(-t/T2) as phase damping in addition to amplitude damping double counts relaxation and gives the wrong coherence decay. The following fixed-duration model is deliberately local to each operation; a scheduled hardware model must also account for idle intervals.

class ThermalNoise(cirq.NoiseModel):
    def __init__(self, T1, T2, gate_time):
        values = np.asarray([T1, T2, gate_time], dtype=float)
        if not np.all(np.isfinite(values)) or T1 <= 0 or T2 <= 0 or gate_time < 0:
            raise ValueError("Use finite positive T1/T2 and nonnegative duration")
        if T2 > 2*T1:
            raise ValueError("This relaxation model requires T2 <= 2*T1")
        self.gamma_amp = -np.expm1(-gate_time/T1)
        inverse_Tphi = max(0.0, 1/T2 - 1/(2*T1))
        self.gamma_phase = -np.expm1(-2*gate_time*inverse_Tphi)

    def noisy_operation(self, op):
        if cirq.is_measurement(op):
            return op
        return [op, [
            [cirq.amplitude_damp(self.gamma_amp)(q), cirq.phase_damp(self.gamma_phase)(q)]
            for q in op.qubits
        ]]

thermal = ThermalNoise(T1=50e-6, T2=30e-6, gate_time=25e-9)
thermal_circuit = cirq.Circuit(cirq.I(q)).with_noise(thermal)
plus = np.array([1, 1], dtype=complex) / np.sqrt(2)
thermal_rho = simulator.simulate(thermal_circuit, initial_state=plus).final_density_matrix
np.testing.assert_allclose(thermal_rho[0, 1], 0.5*np.exp(-25e-9/30e-6), atol=1e-12)
Raw lines 74 to 108
109## Asymmetric classical readout errors
111Cirq's confusion matrix has **rows = true state, columns = recorded state**.
112Do not average asymmetric errors into one bit-flip probability.
114```python
115p1_given_0, p0_given_1 = 0.01, 0.20
116confusion = np.array([[1-p1_given_0, p1_given_0], [p0_given_1, 1-p0_given_1]])
117measurement = cirq.measure(q, key="result", confusion_map={(0,): confusion})
118readout_result = cirq.Simulator(seed=42).run(
119 cirq.Circuit(cirq.X(q), measurement), repetitions=2000
121```
123The tuple `(0,)` selects a position within the measurement, not the numerical
124coordinate of a qubit. For column probability vectors, observed = confusion.T @
125true. Correlated readout needs a joint matrix, and full n-qubit calibration scales
126as `2**n` prepared states.
128```python
129def mitigate_readout_probabilities(measured_probs, confusion):
130 measured_probs = np.asarray(measured_probs, dtype=float)
131 confusion = np.asarray(confusion, dtype=float)
132 if confusion.shape != (len(measured_probs), len(measured_probs)):
133 raise ValueError("Confusion matrix and outcome vector dimensions differ")
134 if (not np.all(np.isfinite(confusion)) or np.any(confusion < 0)
135 or not np.allclose(confusion.sum(axis=1), 1)):
136 raise ValueError("Expected a row-stochastic confusion matrix")
137 if (not np.all(np.isfinite(measured_probs)) or np.any(measured_probs < 0)
138 or not np.isclose(measured_probs.sum(), 1)):
139 raise ValueError("Expected normalized measured probabilities")
140 if np.linalg.cond(confusion) > 1e8:
141 raise ValueError("Readout correction is ill-conditioned")
142 # May contain negative entries from shot noise; retain as quasi-probabilities.
143 return np.linalg.solve(confusion.T, measured_probs)
145true_probs = np.array([0.25, 0.75])
146corrected = mitigate_readout_probabilities(confusion.T @ true_probs, confusion)
147np.testing.assert_allclose(corrected, true_probs)
148```
150Do not silently clip negatives, round to integer counts, or discard mass. Report
151uncertainty from both experimental and calibration shots; use a constrained fit
152if physical probabilities are required, explicitly reporting its assumptions.

Asymmetric classical readout errors

Cirq's confusion matrix has rows = true state, columns = recorded state. Do not average asymmetric errors into one bit-flip probability.

p1_given_0, p0_given_1 = 0.01, 0.20
confusion = np.array([[1-p1_given_0, p1_given_0], [p0_given_1, 1-p0_given_1]])
measurement = cirq.measure(q, key="result", confusion_map={(0,): confusion})
readout_result = cirq.Simulator(seed=42).run(
    cirq.Circuit(cirq.X(q), measurement), repetitions=2000
)

The tuple (0,) selects a position within the measurement, not the numerical coordinate of a qubit. For column probability vectors, observed = confusion.T @ true. Correlated readout needs a joint matrix, and full n-qubit calibration scales as 2**n prepared states.

def mitigate_readout_probabilities(measured_probs, confusion):
    measured_probs = np.asarray(measured_probs, dtype=float)
    confusion = np.asarray(confusion, dtype=float)
    if confusion.shape != (len(measured_probs), len(measured_probs)):
        raise ValueError("Confusion matrix and outcome vector dimensions differ")
    if (not np.all(np.isfinite(confusion)) or np.any(confusion < 0)
            or not np.allclose(confusion.sum(axis=1), 1)):
        raise ValueError("Expected a row-stochastic confusion matrix")
    if (not np.all(np.isfinite(measured_probs)) or np.any(measured_probs < 0)
            or not np.isclose(measured_probs.sum(), 1)):
        raise ValueError("Expected normalized measured probabilities")
    if np.linalg.cond(confusion) > 1e8:
        raise ValueError("Readout correction is ill-conditioned")
    # May contain negative entries from shot noise; retain as quasi-probabilities.
    return np.linalg.solve(confusion.T, measured_probs)

true_probs = np.array([0.25, 0.75])
corrected = mitigate_readout_probabilities(confusion.T @ true_probs, confusion)
np.testing.assert_allclose(corrected, true_probs)

Do not silently clip negatives, round to integer counts, or discard mass. Report uncertainty from both experimental and calibration shots; use a constrained fit if physical probabilities are required, explicitly reporting its assumptions.

Raw lines 109 to 153
154## Benchmarking
156Use an actual Clifford sequence followed by its inverse for randomized
157benchmarking. Random Pauli/H/S gates without inversion are not an RB experiment.
158Cirq supplies a single-qubit RB workflow:
160```python
161rb = cirq.experiments.single_qubit_rb(
162 cirq.Simulator(seed=42), q,
163 parameters=cirq.experiments.RBParameters(
164 num_clifford_range=[2, 4, 8, 16], num_circuits=3, repetitions=100,
165 ),
166 rng_or_seed=42,
168print(rb.data)
169```
171This ideal smoke example checks survival, not a reliable hardware error estimate.
172For real RB, sample enough independent Clifford sequences at multiple lengths,
173fit `A*p**m+B`, and report uncertainty and the gate/Clifford convention. Avoid
174extracting a fit from an all-one ideal curve.
176For linear XEB, the estimator is `D * mean(p_ideal(observed_bitstrings)) - 1`.
177It is not the logarithmic cross-entropy formula. Use Cirq's implementation:
179```python
180q0, q1 = cirq.LineQubit.range(2)
181ideal = cirq.Circuit(cirq.ry(0.4)(q0), cirq.rx(0.8)(q1), cirq.CZ(q0, q1))
182samples = cirq.Simulator(seed=42).run(
183 ideal + cirq.Circuit(cirq.measure(q0, q1, key="result")), repetitions=100
185bitstrings = [cirq.big_endian_bits_to_int(row) for row in samples.measurements["result"]]
186xeb = cirq.experiments.xeb_fidelity(ideal, bitstrings, qubit_order=[q0, q1])
187```
189The tiny example is an API smoke test, not a suitable random-circuit ensemble
190for interpreting XEB as circuit fidelity. Finite-sample estimates need not stay
191in [0,1]. Preserve ideal circuit and measured qubit ordering.

Benchmarking

Use an actual Clifford sequence followed by its inverse for randomized benchmarking. Random Pauli/H/S gates without inversion are not an RB experiment. Cirq supplies a single-qubit RB workflow:

rb = cirq.experiments.single_qubit_rb(
    cirq.Simulator(seed=42), q,
    parameters=cirq.experiments.RBParameters(
        num_clifford_range=[2, 4, 8, 16], num_circuits=3, repetitions=100,
    ),
    rng_or_seed=42,
)
print(rb.data)

This ideal smoke example checks survival, not a reliable hardware error estimate. For real RB, sample enough independent Clifford sequences at multiple lengths, fit A*p**m+B, and report uncertainty and the gate/Clifford convention. Avoid extracting a fit from an all-one ideal curve.

For linear XEB, the estimator is D * mean(p_ideal(observed_bitstrings)) - 1. It is not the logarithmic cross-entropy formula. Use Cirq's implementation:

q0, q1 = cirq.LineQubit.range(2)
ideal = cirq.Circuit(cirq.ry(0.4)(q0), cirq.rx(0.8)(q1), cirq.CZ(q0, q1))
samples = cirq.Simulator(seed=42).run(
    ideal + cirq.Circuit(cirq.measure(q0, q1, key="result")), repetitions=100
)
bitstrings = [cirq.big_endian_bits_to_int(row) for row in samples.measurements["result"]]
xeb = cirq.experiments.xeb_fidelity(ideal, bitstrings, qubit_order=[q0, q1])

The tiny example is an API smoke test, not a suitable random-circuit ensemble for interpreting XEB as circuit fidelity. Finite-sample estimates need not stay in [0,1]. Preserve ideal circuit and measured qubit ordering.

Raw lines 154 to 192
193## Zero-noise extrapolation and cancellation
195For a simulation study, vary a specified noise parameter while keeping the ideal
196circuit and observable fixed. Hardware ZNE instead needs a justified way to
197scale physical noise, such as characterized gate folding. A fit can amplify
198statistical/model error and does not prove error mitigation worked.
200```python
201def extrapolate_exponential(noise_levels, expectation_values):
202 x, y = np.asarray(noise_levels, float), np.asarray(expectation_values, float)
203 if x.ndim != 1 or y.shape != x.shape or len(np.unique(x)) < 4:
204 raise ValueError("Need at least four distinct noise levels and matched values")
205 if not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)) or np.any(x < 0):
206 raise ValueError("Expected finite nonnegative levels and finite expectations")
207 def model(x, a, b, c):
208 return a*np.exp(-b*x) + c
209 fit, covariance = curve_fit(model, x, y, p0=[0.8, 1.0, 0.0], maxfev=10000)
210 return float(model(0.0, *fit)), fit, covariance
211```
213The zero-noise value is **a+c**, not the asymptote c. Compare fit families, hold
214out noise levels, and propagate measurement uncertainty. Probabilistic error
215cancellation needs a characterized implementable noisy operation basis and
216quasi-probability sampling weights; it cannot be implemented by an unspecified
217scalar inverse error rate.

Zero-noise extrapolation and cancellation

For a simulation study, vary a specified noise parameter while keeping the ideal circuit and observable fixed. Hardware ZNE instead needs a justified way to scale physical noise, such as characterized gate folding. A fit can amplify statistical/model error and does not prove error mitigation worked.

def extrapolate_exponential(noise_levels, expectation_values):
    x, y = np.asarray(noise_levels, float), np.asarray(expectation_values, float)
    if x.ndim != 1 or y.shape != x.shape or len(np.unique(x)) < 4:
        raise ValueError("Need at least four distinct noise levels and matched values")
    if not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)) or np.any(x < 0):
        raise ValueError("Expected finite nonnegative levels and finite expectations")
    def model(x, a, b, c):
        return a*np.exp(-b*x) + c
    fit, covariance = curve_fit(model, x, y, p0=[0.8, 1.0, 0.0], maxfev=10000)
    return float(model(0.0, *fit)), fit, covariance

The zero-noise value is a+c, not the asymptote c. Compare fit families, hold out noise levels, and propagate measurement uncertainty. Probabilistic error cancellation needs a characterized implementable noisy operation basis and quasi-probability sampling weights; it cannot be implemented by an unspecified scalar inverse error rate.

Raw lines 193 to 218
219## Google noise and plots
221Use `cirq_google.engine.load_device_noise_properties(processor_id)` for bundled
222median data, then `NoiseModelFromGoogleNoiseProperties`. See the fully local QVM
223in [simulation.md](simulation.md). Live `get_device_specification()` supplies
224constraints, not noise properties. Calibration maps use metric names as keys,
225then qubit tuples to lists of values; inspect metric names/units before plotting.
227For GridQubit heatmaps, missing calibration values should be NaN/masked, never
228zero error. Keep calibration date, gate definition, and units in the figure.
230Sources: [channels and measurements](https://quantumai.google/cirq/noise/representing_noise),
231[phase damping](https://quantumai.google/reference/python/cirq/PhaseDampingChannel),
232[RB](https://github.com/quantumlib/Cirq/blob/v1.7.0/cirq-core/cirq/experiments/qubit_characterizations.py),
233[XEB](https://github.com/quantumlib/Cirq/blob/v1.7.0/cirq-core/cirq/experiments/fidelity_estimation.py),
234[Google QVM](https://quantumai.google/cirq/simulate/quantum_virtual_machine).

Google noise and plots

Use cirq_google.engine.load_device_noise_properties(processor_id) for bundled median data, then NoiseModelFromGoogleNoiseProperties. See the fully local QVM in simulation.md. Live get_device_specification() supplies constraints, not noise properties. Calibration maps use metric names as keys, then qubit tuples to lists of values; inspect metric names/units before plotting.

For GridQubit heatmaps, missing calibration values should be NaN/masked, never zero error. Keep calibration date, gate definition, and units in the figure.

Sources: channels and measurements, phase damping, RB, XEB, Google QVM.

Raw lines 219 to 234