Hands-On Introduction to DiVincenzo Criteria with Qiskit 2
Introduction
Physicist David DiVincenzo outlined five key requirements for any physical implementation of a quantum computer, plus two additional criteria for quantum communication. In this notebook, we will experience each DiVincenzo criterion through practical Qiskit demonstrations. Rather than going deep into theory, each section briefly explains one criterion and then provides code exercises using Qiskit 2. You will get to run circuits on simulators and real IBM Quantum devices to explore each principle hands-on.
DiVincenzo’s Five Criteria for Quantum Computation:
- A scalable physical system with well-characterized qubits.
- Ability to initialize qubits to a simple fiducial state (e.g. |00…0〉).
- Long decoherence times (qubit coherence much longer than gate operation time).
- A universal set of quantum gates (able to perform arbitrary unitary operations).
- Qubit-specific measurement capability (read out the state of each qubit).
(DiVincenzo also described two criteria for quantum communication: the ability to interconvert stationary and “flying” qubits, and to faithfully transmit flying qubits between locations. We include these in a recommended activity at the end of this notebook.)
Each of the following sections corresponds to one criterion. We'll use Qiskit to illustrate the concept with code and interactive experiments you can try. For example, we will see how scaling up the number of qubits and circuit depth affects outcomes (Criterion 1), how to reset and prepare qubit states (Criterion 2), how to measure qubits on simulators vs real devices (Criterion 4), how Qiskit composes universal gates (Criterion 3), and how finite coherence (T₁, T₂) impacts computations (Criterion 5). By the end, you'll have a deeper intuition for what each DiVincenzo criterion means in practice and how Qiskit enables experimenting with them.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy
# Install necessary packages
!pip install qiskit[visualization] qiskit-ibm-runtime qiskit-aer qiskit_ibm_runtime
1. Criterion 1 – Scalable, Well-Characterized Qubits
Criterion 1: “A scalable physical system with well characterized qubits.” This means we need a quantum hardware platform where we can increase the number of qubits and still control them reliably. Each qubit’s properties (energy levels, error rates, connectivity, etc.) should be well understood. Essentially, we want to build bigger circuits without the system breaking down. In practice, as we scale up qubit count or circuit depth, errors and decoherence accumulate, so demonstrating scalability also means understanding how increasing size affects performance.
Goal of Demo: Use Qiskit to show the effect of scaling up a circuit (in qubit count or gate depth) on the output fidelity. We’ll simulate an ideal vs noisy scenario to see how a larger system or deeper circuit succumbs to decoherence and errors.
First, let's construct a small entangled state (GHZ state) on 3 qubits, then a larger one on 5 qubits, as a simple scaling test. A GHZ state of n qubits is . In an ideal simulation, measuring an n-qubit GHZ yields only two outcomes (all 0s or all 1s) with equal probability. We will compare the ideal output to a noisy output as we increase n or circuit depth.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import SamplerV2 as Sampler
# 3-qubit GHZ circuit
qc3 = QuantumCircuit(3, 3)
qc3.h(0)
qc3.cx(0, 1)
qc3.cx(1, 2)
qc3.measure([0, 1, 2], [0, 1, 2])
# 5-qubit GHZ circuit (scaling up the number of qubits)
qc5 = QuantumCircuit(5, 5)
qc5.h(0)
qc5.cx(0, range(1, 5)) # entangle qubit 0 with all others
qc5.measure(range(5), range(5))
# Transpile for a simulator backend
sim_backend = AerSimulator()
pm = generate_preset_pass_manager(backend=sim_backend, optimization_level=1)
isa_qc3 = pm.run(qc3)
isa_qc5 = pm.run(qc5)
# Run ideal simulations (no noise)
sampler = Sampler(mode=sim_backend)
job3 = sampler.run([isa_qc3], shots=1024)
result3 = job3.result()
counts3 = result3[0].data.c.get_counts()
job5 = sampler.run([isa_qc5], shots=1024)
result5 = job5.result()
counts5 = result5[0].data.c.get_counts()
print("3-qubit GHZ counts (ideal):", counts3)
plot_histogram(counts3, legend=['3-qubit ideal'], figsize=(6,4))
print("5-qubit GHZ counts (ideal):", counts5)
plot_histogram(counts5, legend=['5-qubit ideal'], figsize=(6,4))
Expected outcome (ideal case): The 3-qubit GHZ ideally yields roughly 50% 000 and 50% 111 in the counts. The 5-qubit GHZ yields ~50% 00000 and 50% 11111. No other bit-strings appear because the state is ideally fully coherent and entangled. You should see two tall bars on the histogram for each circuit corresponding to all-zeros and all-ones outcomes.
Next, let's see what happens in a noisy environment. We will use Qiskit Aer’s noise model capabilities to mimic a real device’s errors. For example, we can take an IBM backend’s properties to create a noise model that includes gate errors, finite gate times, qubit relaxation (T₁), dephasing (T₂), and readout errors. Here, we’ll use a fake backend that represents the IBM Quantum Brisbane device to generate a noise model, and re-run the GHZ circuits through that.
Exercise 1a: Simulate with Noise
Complete the code below to simulate the GHZ circuits on a noisy simulator based on the FakeBrisbane backend. This will show you how performance degrades as the system scales in a realistic noise environment.
from qiskit_ibm_runtime.fake_provider import FakeBrisbane
# We will reuse the ideal circuits qc3 and qc5 and their results from the previous cell.
# 1. Create a fake backend for IBM Quantum Brisbane
brisbane_backend = FakeBrisbane()
# 2. Create a noisy AerSimulator from the fake backend's properties
noisy_sim = AerSimulator.from_backend(brisbane_backend)
# 3. Transpile the circuits for the noisy simulator
pm = generate_preset_pass_manager(backend=noisy_sim, optimization_level=1)
isa_qc3_noisy = pm.run(qc3)
isa_qc5_noisy = pm.run(qc5)
# 4. Run the noisy simulations using the Sampler and get the counts
sampler = Sampler(mode=noisy_sim)
job3 = sampler.run([isa_qc3_noisy], shots=1024)
result3_noisy = job3.result()
counts3_noisy = result3_noisy[0].data.meas.get_counts()
job5 = sampler.run([isa_qc5_noisy], shots=1024)
result5_noisy = job5.result()
counts5_noisy = result5_noisy[0].data.meas.get_counts()
# --- END YOUR CODE ---
# This part is done for you to visualize the results.
print("3-qubit GHZ counts (noisy):", counts3_noisy)
plot_histogram(counts3_noisy, legend=['3-qubit noisy'], figsize=(6,4))
print("5-qubit GHZ counts (noisy):", counts5_noisy)
plot_histogram(counts5_noisy, legend=['5-qubit noisy'], figsize=(6,4))
Exercise 1b: Run on real IBM Quantum computer
The code below runs the GHZ circuits on a real IBM Quantum computer. This will show you how performance degrades on a real device.
# your_api_key = "deleteThisAndPasteYourAPIKeyHere"
# your_crn = "deleteThisAndPasteYourCRNHere"
# QiskitRuntimeService.save_account(
# channel="ibm_quantum_platform",
# token=your_api_key,
# instance=your_crn,
# name="fallfest-2025",
# )
# Check that the account has been saved properly
# service = QiskitRuntimeService(name="fallfest-2025")
# print(service.saved_accounts())
# We will reuse the ideal circuits qc3 and qc5 and their results from the previous cell.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService(name="fallfest-2025")
real_backend = service.least_busy(operational=True, simulator=False)
print("Running on " + real_backend.name)
pm = generate_preset_pass_manager(backend=real_backend, optimization_level=1)
isa_qc3r = pm.run(qc3)
isa_qc5r = pm.run(qc5)
sampler = Sampler(mode=real_backend)
job3r = sampler.run([isa_qc3r], shots=1024)
result3r = job3r.result()
counts3r = result3r[0].data.c.get_counts()
job5r = sampler.run([isa_qc5r], shots=1024)
result5r = job5r.result()
counts5r = result5r[0].data.c.get_counts()
print("3-qubit GHZ counts (real):", counts3r)
plot_histogram(counts3r, legend=['3-qubit real'], figsize=(6,4))
print("5-qubit GHZ counts (real):", counts5r)
plot_histogram(counts5r, legend=['5-qubit real'], figsize=(6,4))
Expected outcome (noisy vs ideal): With noise, whether simulated or on a real device, the GHZ state is less perfect. You will see additional outcomes beyond all-0s and all-1s. For 3 qubits, instead of 100% in 000/111, some probability leaks into other bitstrings (e.g. 001, 010, etc.) due to gate errors or decoherence flipping some qubits. For 5 qubits, the effect is even more pronounced; the larger circuit (more qubits and CNOT gates) accumulates more error, so the all-0 and all-1 peaks are lower, and many other outcomes appear. This trend illustrates the challenge of scalability: as we scale up, maintaining high fidelity gets harder without error correction.
Insight: A scalable quantum computer needs to preserve quantum correlations as the system grows. Our examples show how increasing qubit count/gate depth causes outcome fidelity to drop when noise is present. The remaining criteria will deal with keeping those qubits well-behaved (low error, initializable, etc.) as we scale.
2. Criterion 2 – Qubit Initialization
Criterion 2: “The ability to initialize the state of the qubits to a simple fiducial state, such as |000…〉.” All qubits should reliably start in a known reference state (typically the ground state |0〉 for each qubit). Initialization is essential so that algorithms begin on a clean slate. In practice, on IBM quantum devices each qubit is automatically reset to |0〉 at the start of each circuit execution. Qiskit also provides instructions to reset qubits or prepare custom states during a computation.
Goal of Demo: Show how to initialize qubits in Qiskit, both at the start and mid-circuit. We will demonstrate using the reset instruction and state preparation methods.
Exercise 2: Prepare a Specific State
In the code block below, complete the QuantumCircuit to prepare the state . This means qubit 0 should be in state and qubit 1 should be in state . Use the appropriate gate and instruction to achieve this.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
# Create a circuit to initialize qubits to |10> and verify by measurement
qc_init = QuantumCircuit(2, 2)
# --- YOUR CODE HERE ---
# 1. Set qubit 1 to the |1> state
qc_init.x(1)
# 2. Explicitly reset qubit 0 to the |0> state
qc_init.reset(0)
# --- END YOUR CODE ---
qc_init.measure([0, 1], [0, 1])
qc_init.draw('mpl')
# Run the circuit and check the outcome
sim_backend = AerSimulator()
pm = generate_preset_pass_manager(backend=sim_backend, optimization_level=1)
isa_qc_init = pm.run(qc_init)
sampler = Sampler(mode=sim_backend)
job = sampler.run([isa_qc_init], shots=1024)
result = job.result()
counts = result[0].data.c.get_counts()
print("Outcome of |10> state measured in Z-basis:", counts)
plot_histogram(counts)
You should see 10 (binary for qubit1=1, qubit0=0) with 100% probability from the simulation, meaning qubit 1 was successfully prepared in |1〉 and qubit 0 in |0〉.
Now, for a more general state preparation, Qiskit allows initialization to arbitrary states using the initialize method. For example, let's prepare a qubit in the state