This page shows how to use Qiskit Aer primitives to efficiently simulate stabilizer circuits, including those subject to Pauli noise.
Stabilizer circuits, also known as Clifford circuits, are an important restricted class of quantum circuits that can be efficiently simulated classically. There are several equivalent ways to define stabilizer circuits. One definition is that a stabilizer circuit is a quantum circuit that consists solely of the following gates:
Note that using Hadamard and S, we can construct any Pauli rotation gate (, , and ) that has an angle contained in the set (up to global phase), so we can include these gates in the definition as well.
Stabilizer circuits are important to the study of quantum error correction. Their classical simulability also makes them useful for verifying the output of quantum computers. For example, suppose you want to execute a quantum circuit that uses 100 qubits on a quantum computer. How do you know that the quantum computer is behaving correctly? A quantum circuit on 100 qubits is beyond the reach of brute-force classical simulation. By modifying your circuit so that it becomes a stabilizer circuit, you can run circuits on the quantum computer that have a similar structure to your desired circuit, but which you can simulate on a classical computer. By checking the output of the quantum computer on the stabilizer circuits, you can gain confidence that it is behaving correctly on the non-stabilizer circuits as well. See Evidence for the utility of quantum computing before fault tolerance (opens in a new tab) for an example of this idea in practice.
Exact and noisy simulation with Qiskit Aer primitives shows how to use Qiskit Aer (opens in a new tab) to perform exact and noisy simulations of generic quantum circuits. Consider the example circuit used in that article, an 8-qubit circuit built using EfficientSU2: :
from qiskit.circuit.library import EfficientSU2 n_qubits = 8 circuit = EfficientSU2(n_qubits) circuit.decompose().draw("mpl")
Using Qiskit Aer, we were able to simulate this circuit easily. However, suppose we set the number of qubits to 500: :
n_qubits = 500 circuit = EfficientSU2(n_qubits) # don't try to draw the circuit because it's too large
Because the cost of simulating quantum circuits scales exponentially with the number of qubits, such a large circuit would generally exceed the capabilities of even a high-performance simulator like Qiskit Aer. Classical simulation of generic quantum circuits becomes infeasible when the number of qubits exceeds roughly 50 to 100 qubits. However, note that the EfficientSU2 circuit is parameterized by angles on and gates. If all of these angles are contained in the set , then the circuit is a stabilizer circuit, and it can be efficiently simulated!
In the following cell, we run the circuit with the Sampler primitive backed by the stabilizer circuit simulator, using parameters chosen randomly such that the circuit is guaranteed to be a stabilizer circuit. :
import numpy as np from qiskit_aer.primitives import Sampler measured_circuit = circuit.copy() measured_circuit.measure_all() rng = np.random.default_rng(1234) params = rng.choice( [0, np.pi / 2, np.pi, 3 * np.pi / 2], size=circuit.num_parameters, ) # Initialize a Sampler backed by the stabilizer circuit simulator exact_sampler = Sampler(backend_options=dict(method="stabilizer")) job = exact_sampler.run(measured_circuit, params) exact_quasis = job.result().quasi_dists
The stabilizer circuit simulator also supports noisy simulation, but only for a restricted class of noise models. Specifically, any quantum noise must be characterized by a Pauli error (opens in a new tab) channel. Depolarizing error (opens in a new tab) falls into this category, so it can be simulated too. Classical noise channels like readout error (opens in a new tab) can also be simulated.
The following code cell runs the same simulation as before, but this time specifying a noise model that adds depolarizing error of 2% to each CX gate, as well as readout error that flips each measured bit with 5% probability. :
from qiskit_aer.noise import NoiseModel, depolarizing_error, ReadoutError noise_model = NoiseModel() cx_depolarizing_prob = 0.02 bit_flip_prob = 0.05 noise_model.add_all_qubit_quantum_error( depolarizing_error(cx_depolarizing_prob, 2), ["cx"] ) noise_model.add_all_qubit_readout_error( ReadoutError( [ [1 - bit_flip_prob, bit_flip_prob], [bit_flip_prob, 1 - bit_flip_prob], ] ) ) noisy_sampler = Sampler( backend_options=dict(method="stabilizer", noise_model=noise_model) ) job = noisy_sampler.run(measured_circuit, params) noisy_quasis = job.result().quasi_dists
Now, let's use the Estimator primitive backed by the stabilizer simulator to compute the expectation value of the observable . Due to the special structure of stabilizer circuits, the result is very likely to be 0. :
from qiskit.quantum_info import SparsePauliOp from qiskit_aer.primitives import Estimator observable = SparsePauliOp("Z" * n_qubits) exact_estimator = Estimator( backend_options=dict(method="stabilizer"), approximation=True, ) job = exact_estimator.run(circuit, observable, params) exact_value = job.result().values exact_value
/tmp/ipykernel_21071/287309019.py:6: DeprecationWarning: ``qiskit_aer.primitives.estimator.Estimator.__init__()``'s argument ``approximation`` is deprecated as of qiskit-aer 0.13. It will be removed no earlier than 3 months after the release date. approximation=True will be default in the future. exact_estimator = Estimator(