Use Qiskit Aer to run the same circuit three ways: ideally, with a controlled noise model, and with a three-qubit repetition code that detects and corrects one bit flip. The useful workflow is to establish an unencoded baseline, encode a logical bit across three data qubits, measure two stabilizers with ancillas, apply a syndrome-controlled correction, and calculate the logical-error probability from a fixed number of shots.
This tutorial uses the current package combination documented by IBM Quantum: qiskit[all]~=2.5.2, qiskit-aer~=0.17.0, and, when runtime access is needed, qiskit-ibm-runtime~=0.47.0. Pin and record the versions installed in your own environment because circuit-control APIs and result formatting can change.
As an Amazon Associate I earn from qualifying purchases.
What this simulation demonstrates
A three-qubit repetition code stores a classical logical value as |000> or |111>. A single Pauli-X error on one data qubit changes one of those states, but the two parity checks reveal which qubit disagrees with the other two. A conditional X gate then restores the encoded value.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →The code is deliberately narrow. It corrects bit-flip errors only; it does not correct phase flips and is not a complete quantum-error-correction code. The memory experiment below protects classical information represented by the encoded state, not an arbitrary unknown qubit. Treat every measured improvement as conditional on the selected noise channel, gate placement, calibration snapshot, transpilation, decoder, and shot count.
#1 Best Overall
Install Qiskit and Aer
Create an isolated Python environment, then install a pinned Qiskit distribution and Aer simulator:
python -m venv qec-qiskit
source qec-qiskit/bin/activate
python -m pip install --upgrade pip
python -m pip install "qiskit[all]~=2.5.2" "qiskit-aer~=0.17.0"
On Windows PowerShell, activate the environment with qec-qiskitScriptsActivate.ps1. If you need IBM Quantum Runtime in the same environment, install qiskit-ibm-runtime~=0.47.0 as documented for the current guide. Verify the actual versions rather than assuming that the requested constraints were resolved:
python -c "import qiskit, qiskit_aer; print(qiskit.__version__); print(qiskit_aer.__version__)"
Choose an Aer simulation method
AerSimulator supports several methods. Start with automatic; it chooses a representation based on the circuit and noise model. Select a method explicitly when you know what the experiment requires.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minute| Method | Use it when | Important trade-off |
|---|---|---|
automatic |
You want Aer to select a suitable representation. | Convenient, but the chosen method can change as the circuit or noise model changes. |
stabilizer |
The circuit and errors are supported Clifford operations, including the repetition-code example. | Usually scales much better for large Clifford circuits, but cannot represent arbitrary non-Clifford operations or unsupported channels. |
statevector |
You need a pure-state representation or operations outside the stabilizer family. | Memory grows exponentially with qubit count. |
density_matrix |
Mixed states and general noise must be represented directly. | More memory-intensive than a statevector and quickly expensive as qubits increase. |
matrix_product_state |
The circuit has entanglement structure that an MPS representation can exploit. | Performance depends strongly on entanglement and gate pattern. |
For the code below, begin with AerSimulator(). To request a method explicitly, use AerSimulator(method="stabilizer"), then confirm that every operation and error in your circuit is supported.
Establish an ideal, unencoded baseline
First measure a single qubit prepared in |1>. With no noise, every one of 4,000 shots should be classified as a one, apart from a software or circuit-construction mistake.
Rank #2
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
shots = 4000
baseline = QuantumCircuit(1, 1)
baseline.x(0)
baseline.measure(0, 0)
ideal_sim = AerSimulator()
ideal_result = ideal_sim.run(baseline, shots=shots).result()
ideal_counts = ideal_result.get_counts()
print(ideal_counts)
The expected ideal output is a counts dictionary containing {'1': 4000}. Keep this shot count, preparation, measurement, and simulator seed policy unchanged when you compare other circuits.
Build the three-qubit repetition circuit
Logical encoding
Use data qubits data[0] through data[2]. Prepare data[0] in the chosen classical state and copy it with two CNOT gates:
qc.x(data[0]) # prepare the logical value 1
qc.cx(data[0], data[1])
qc.cx(data[0], data[2])
After encoding, the logical one is represented as |111>. For a logical zero, omit the first X and the encoded state is |000>.
Measure the two stabilizers
Two ancillas measure the parities Z0 Z1 and Z1 Z2. Prepare each ancilla in |+> with H, apply CNOTs from the two data qubits into that ancilla, apply H again, and measure the ancilla. The resulting syndrome identifies a single bit flip:
| Physical error | Z0 Z1 result |
Z1 Z2 result |
Syndrome register value | Correction |
|---|---|---|---|---|
| None | 0 | 0 | 0 | None |
| X on data[0] | 1 | 0 | 1 | X on data[0] |
| X on data[1] | 1 | 1 | 3 | X on data[1] |
| X on data[2] | 0 | 1 | 2 | X on data[2] |
In this layout, syndrome bit zero stores the first parity and syndrome bit one stores the second. Qiskit treats the first bit in a classical register as the least-significant bit when a whole register is used as a condition, which produces the integer values in the table.
Complete example with a deterministic error
The following circuit inserts an X on the middle data qubit so that the syndrome and correction can be checked without relying on random noise. It uses separate syndrome and readout registers, allowing the conditional operation to test the two-bit syndrome register.
Recommended Free Tools
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
# Three data qubits, two syndrome ancillas, two syndrome bits, one logical readout bit.
data = QuantumRegister(3, "data")
anc = QuantumRegister(2, "anc")
syndrome = ClassicalRegister(2, "syndrome")
readout = ClassicalRegister(1, "readout")
qc = QuantumCircuit(data, anc, syndrome, readout)
# Prepare and encode logical |1> as |111>.
qc.x(data[0])
qc.cx(data[0], data[1])
qc.cx(data[0], data[2])
# Deterministic test error: flip the middle data qubit.
qc.x(data[1])
# Measure Z0*Z1 into syndrome[0].
qc.h(anc[0])
qc.cx(data[0], anc[0])
qc.cx(data[1], anc[0])
qc.h(anc[0])
qc.measure(anc[0], syndrome[0])
# Measure Z1*Z2 into syndrome[1].
qc.h(anc[1])
qc.cx(data[1], anc[1])
qc.cx(data[2], anc[1])
qc.h(anc[1])
qc.measure(anc[1], syndrome[1])
# Correct the qubit identified by the syndrome.
with qc.if_test((syndrome, 1)):
qc.x(data[0])
with qc.if_test((syndrome, 3)):
qc.x(data[1])
with qc.if_test((syndrome, 2)):
qc.x(data[2])
# All three data qubits now agree; measure one as the decoded logical value.
qc.measure(data[0], readout[0])
sim = AerSimulator()
result = sim.run(qc, shots=4000).result()
print(result.get_counts())
The exact conditional syntax and the way multiple classical registers are printed in result keys depend on the installed Qiskit version. If your version rejects if_test, consult that version’s circuit-control API and keep the syndrome mapping unchanged. With the deterministic middle-qubit X and an ideal simulator, the syndrome should be 11, the correction should run on data[1], and the logical readout should be one in every shot.
Add a controlled noise model with Aer
Aer noise is explicit: define a channel, attach it to the operations it represents, and record those choices with your result. The example below applies a one-qubit bit-flip channel to X gates and a two-qubit depolarizing channel to CNOT gates.
from qiskit_aer.noise import NoiseModel, pauli_error, depolarizing_error
noise = NoiseModel()
bit_flip = pauli_error([("X", 0.01), ("I", 0.99)])
noise.add_all_qubit_quantum_error(bit_flip, ["x"])
# A two-qubit channel is required for a two-qubit gate.
cx_error = depolarizing_error(0.01, 2)
noise.add_all_qubit_quantum_error(cx_error, ["cx"])
noisy_sim = AerSimulator(noise_model=noise, seed_simulator=1234)
noisy_result = noisy_sim.run(qc, shots=4000).result()
print(noisy_result.get_counts())
The probabilities above are illustrative settings for a controlled experiment, not a hardware benchmark. You can also model measurement errors with Aer’s ReadoutError, and use helper channels for Pauli, depolarizing, bit-flip, and thermal-relaxation-style behavior. Attach each error to the gate or measurement that it is intended to model; adding an error to every operation without a physical rationale makes the result difficult to interpret.
Optional readout error
If the experiment is intended to include classical measurement mistakes, define a readout confusion matrix and attach it to the measured qubits. Keep readout noise separate from gate noise in your notes so you can identify which mechanism changed the logical-error rate.
Rank #4
Run comparable ideal and noisy experiments
Use at least three comparison arms:
- Unencoded: prepare and measure one physical qubit.
- Encoded with end correction: run the three-qubit circuit, measure the syndrome once, correct once, and then decode.
- Repeated correction cycles: insert idle or gate cycles, measure the syndrome repeatedly, and correct after each cycle or defer corrections according to the experiment being tested.
Use the same shot count, logical-state preparation, final measurement convention, random-seed policy, and decoder rule wherever a comparison is intended. A minimal driver for the first two arms looks like this:
shots = 4000
seed = 1234
ideal_sim = AerSimulator(seed_simulator=seed)
noisy_sim = AerSimulator(noise_model=noise, seed_simulator=seed)
# baseline is the one-qubit circuit created earlier
ideal_baseline = ideal_sim.run(baseline, shots=shots).result()
noisy_baseline = noisy_sim.run(baseline, shots=shots).result()
ideal_encoded = ideal_sim.run(qc, shots=shots).result()
noisy_encoded = noisy_sim.run(qc, shots=shots).result()
print("ideal baseline", ideal_baseline.get_counts())
print("noisy baseline", noisy_baseline.get_counts())
print("ideal encoded", ideal_encoded.get_counts())
print("noisy encoded", noisy_encoded.get_counts())
For a fair test of stochastic noise, remove the deterministic qc.x(data[1]) test error or run separate experiments. The inserted X is useful for proving that the decoder works, but leaving it in changes the experiment from a random-noise comparison to a noise-plus-known-error comparison.
Calculate logical-error probability
For an encoded logical one, a shot is a logical failure when the decoded readout is zero. For a logical zero, reverse the classification. If the readout bit is classical bit index 2 in the register construction above, Qiskit’s marginal-count helper can isolate it:
from qiskit.result import marginal_counts
def logical_error_probability(result, shots, expected_logical_bit="1"):
marginal = marginal_counts(result, indices=[2])
wrong = marginal.get("0" if expected_logical_bit == "1" else "1", 0)
return wrong / shots
p_ideal = logical_error_probability(ideal_encoded, shots)
p_noisy = logical_error_probability(noisy_encoded, shots)
print("ideal logical error", p_ideal)
print("noisy logical error", p_noisy)
Check the index against your circuit and Qiskit version by inspecting result.get_counts(); multiple classical registers can be displayed with spaces and in register order that is not obvious from a printed key. If the readout bit is not index 2 in your construction, pass its actual classical-bit index.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Report shot uncertainty
A measured error rate is an estimate, not an exact probability. If k logical failures occur in N shots, the observed rate is k/N. For a rough standard error when the rate is not near zero or one, use sqrt(p(1-p)/N). For small counts, especially zero failures, report the shot count and use a binomial confidence interval rather than claiming that the true error probability is zero.
Best Value
Model repeated syndrome-and-correction cycles
A single correction at the end answers whether one encoded preparation survives the selected circuit. A memory-style experiment inserts a defined number of idle or gate cycles and measures the syndrome after each cycle. For every cycle:
- Apply the chosen idle, delay, or gate sequence to the data qubits.
- Measure
Z0 Z1andZ1 Z2with fresh or reset ancillas. - Decode the syndrome using the same mapping shown earlier.
- Apply the corresponding conditional X, or record the Pauli frame and defer the physical correction.
- Repeat for the requested cycle count.
- Measure one data qubit and classify the final logical value.
When comparing dynamic correction with correction deferred until final measurement, keep the preparation, measurement, number of cycles, shot count, and decoder identical. Plot logical-error probability against physical error probability or idle-cycle count. The resulting curve describes the selected simulation model; it is not a universal threshold for quantum error correction.
Use a device-informed noise model carefully
Aer can construct an approximate noise model from reported backend calibration properties, including gate-error and relaxation parameters. This is useful for emulating selected characteristics of a noisy processor, but it is not a complete replica of hardware behavior. Calibration data change over time, and the generated model does not automatically capture every correlated, crosstalk, scheduling, control, leakage, or compilation effect.
Record the backend and calibration snapshot, the gates included, the transpilation settings, the simulator method, and the decoder whenever you publish a result. A result from a device-derived model should be labeled as a simulation based on reported calibration data, not as a measurement performed on that device.
Common failures and fixes
The conditional gate is rejected
- Confirm the installed Qiskit version and use its supported dynamic-circuit syntax. Current versions use the
if_testcontext shown above. - Condition on the two-bit syndrome register, not on a register that also contains the final logical readout.
- Make sure the syndrome measurements occur before the conditional block.
The syndrome appears reversed
- Check which parity was measured into each classical bit.
- Remember that a whole classical register is interpreted with its first bit as the least-significant bit.
- Inspect a deterministic test: inject X on data[0], data[1], and data[2] separately and verify the values 1, 3, and 2.
The noise model has no visible effect
- Confirm that the error is attached to an operation actually present in the circuit after transpilation.
- Use enough shots to resolve the expected rate and print the noisy counts.
- Do not assume that adding a one-qubit error to a two-qubit gate is valid; use a two-qubit channel such as
depolarizing_error(probability, 2). - If you expect measurement failures, add a readout channel explicitly; gate noise alone does not create readout errors.
The logical result is worse after correction
- Include the extra ancilla gates, measurements, and conditional operations in the noise model. Syndrome extraction can introduce more opportunities for failure.
- Separate the deterministic-error validation run from the stochastic-noise run.
- Compare against an unencoded circuit with equivalent preparation and final measurement, and report all circuit and noise-model choices.
Simulation runs out of memory
- Try
method="stabilizer"for supported Clifford circuits. - Use
statevectorordensity_matrixonly when the circuit or channel requires it. - Reduce qubit count, shots, or cycle count while debugging, then restore the planned experiment.
What a defensible result includes
- Installed Qiskit and Aer versions.
- Simulator method and whether it was selected automatically.
- Logical-state preparation and the exact encoding and decoder.
- Noise channels, probabilities, and the gates or measurements to which they were attached.
- Whether a deterministic error was inserted.
- Shot count, random-seed policy, and logical-error definition.
- For device-informed models, the backend calibration snapshot and transpilation choices.
- Uncertainty information or, at minimum, the number of failures and total shots.
With those details, Aer provides a reproducible way to test how a repetition-code decoder behaves under a specified model. Without them, an apparent improvement can reflect a changed circuit, measurement convention, or noise placement rather than error correction itself.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

