Exercises: Setting Up
This is a hands-on chapter, so most of these want you at a keyboard. Predict the output before you run each one. The prediction is the exercise; the run only tells you whether you were right. Build that habit now, on circuits small enough to trace completely by hand — it is the habit that makes Chapter 26 possible.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper.
Solutions: worked answers to the daggered (†) and odd-numbered problems are in
appendices/answers-to-selected.md; runnable code is in
code/exercise-solutions.py. Exercises marked 🌐 submit jobs to real hardware and will consume
queue time — batch them into one session.
Part A — Warm-ups ⭐
2.1 † Run the version-check script from §2.1 and record all five lines. Where should you keep this record, and why does this book insist on it?
2.2 Name the three Qiskit packages you installed and state, in one sentence each, what each one is responsible for. Which one knows nothing about hardware?
2.3 † Why does this book insist on python -m pip install rather than plain pip install? What
specific error does the habit prevent, and why is that error confusing when it appears?
2.4 In QuantumCircuit(2, 2), what does each 2 mean? What state do the qubits start in, and
how do you know?
2.5 † Give three rules for handling an IBM Quantum API token, and state what you would do first if you discovered you had committed one to a public repository.
2.6 In the counts dictionary {'01': 58}, which qubit measured 1? State the convention by name.
Part B — Simulator ⭐⭐
Predict first, then run. All of these are free and instant.
2.7 † Run the Bell circuit on AerSimulator with shots equal to 10, 100, 1,000, 10,000, and
100,000 — without a seed, five times each. For each shot count, record the fraction of 00
results across the five runs and compute the spread (max minus min).
a. What happens to the spread as shots increase?
b. Roughly what power of shots does the spread scale with? Compare your answer to
$1/\sqrt{N}$.
c. At which shot count does the spread first drop below 1 percentage point?
2.8 Predict the output, then run:
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.h(1)
qc.measure([0, 1], [0, 1])
How does this differ from the Bell circuit, both in the counts you get and in what the two qubits are doing? (You will be able to answer the second part properly after Chapter 4; answer it now anyway and compare later.)
2.9 † Predict, then run:
qc = QuantumCircuit(2, 2)
qc.x(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
Explain the result in terms of what CNOT does. Is there any superposition in this circuit?
2.10 Take the Bell circuit and add a second CNOT immediately after the first, with the same control and target. Predict the counts, then run. Explain the result. What general property of CNOT does this demonstrate?
2.11 † Run the Bell circuit with seed_simulator=1234, three times. Then with
seed_simulator=99, three times. Then with no seed, three times. Describe exactly what changes and
what does not, and state the rule about when a quoted simulator result must carry a seed.
2.12 Write a function bell_counts(shots, seed=None) that builds the Bell circuit, runs it, and
returns the counts dictionary. Use it to produce a table of shots versus the fraction of results
that were 00, for shot counts from 10 to 100,000 in powers of ten.
2.13 † Build the Bell circuit, then remove the measurement instructions and run it. What error do you get? Explain, in one sentence, what the circuit did and why there is nothing to report.
Part C — Hardware 🌐 ⭐⭐
These consume queue time. Write them all first, then run them in one sitting.
2.14 † 🌐 Run the Bell circuit on real hardware. Record: the backend name, the number of qubits,
the pre- and post-transpilation depth, the job ID, and the full counts dictionary. Compute the
fraction of shots that landed in 01 or 10 combined — call this the error fraction.
2.15 🌐 Run the same circuit on the same backend three more times, at least an hour apart. Compute the error fraction each time. How much does it vary? What does that variation tell you about treating a single hardware run as a measurement?
2.16 † 🌐 Run the Bell circuit at 100 shots and at 4,000 shots on the same backend. Compare the error fractions.
a. Did the error fraction shrink with more shots? b. Did the precision of your estimate of the error fraction improve? c. Explain the difference between (a) and (b) in your own words. This is the single most important distinction in the chapter.
2.17 🌐 Submit a job, immediately record the job ID, and close your Python session entirely.
Start a new session and retrieve the result using service.job(job_id). Why is this workflow the
right default rather than a trick?
2.18 † Print the transpiled circuit for your backend with isa_circuit.draw(idle_wires=False)
and list the gate counts with isa_circuit.count_ops(). Which of your original gates survive
verbatim? Which physical qubits did the transpiler choose, and how can you tell?
Part D — Deeper ⭐⭐⭐
2.19 † Using backend.target, write a script that reports, for your chosen backend: the native
basis gates, the number of qubits, and — for one specific pair of connected qubits — the two-qubit
gate error rate and the readout error of each qubit. Then estimate, from those numbers alone, what
error fraction you would expect for the Bell circuit. Compare to what you measured in 2.14. Are you
within a factor of two?
2.20 Transpile the Bell circuit for the same backend at optimization levels 0, 1, 2, and 3. Tabulate depth and gate counts for each. Does level 3 always produce the shallowest circuit? Time each transpilation. Under what circumstances would you choose level 0 deliberately?
2.21 † The chapter claims sampling noise shrinks with shots and device noise does not. Design an experiment that would distinguish the two using only simulator runs and one hardware run, and say what result would falsify the claim. (Hint: you know the ideal distribution exactly.)
2.22 Take the Bell circuit and deliberately entangle qubits that are far apart on the device —
for example, physical qubits 0 and 50 — by passing initial_layout=[0, 50] to the pass manager.
Compare the transpiled depth and the resulting error fraction to the default layout. Explain the
difference. (This previews
Chapter 29,
and it is the most striking single demonstration in this chapter's exercises.)
2.23 † Six error messages are catalogued in §2.8. Deliberately cause four of them, record the exact message, and write a one-line diagnosis for each. Keep the file; it becomes a personal troubleshooting reference and it is far more memorable than reading the list.
Part E — Project ⭐⭐
2.24 † Implement the Chapter 2 🧱 Project Checkpoint: create vqelab/backends.py with a
get_backend(kind) function that returns an AerSimulator for "sim" and the least busy real
device for "hardware", and raises a clear error for anything else.
2.25 Add a check_credentials() function to backends.py that verifies a saved account exists
and returns a clear, actionable message if not — rather than letting an authentication failure
surface as a stack trace at submission time. Test it by temporarily renaming your credentials file.
2.26 ⭐⭐⭐ Add a describe(backend) function that prints a one-line summary of any backend:
name, qubit count, whether it is a simulator, and (for real devices) the basis gates. Make it work
for both AerSimulator and a real backend without special-casing at the call site. This is a small
exercise in the interface discipline that the whole project depends on, and it is harder than it
looks — the two object types do not share an API.