39 min read

> *"The difference between a phase you can see and a phase you cannot is the difference between a

Prerequisites

  • 1
  • 2

Learning Objectives

  • Build single-qubit circuits with named registers, draw them in text and matplotlib, and read a circuit diagram fluently.
  • Apply X, H, Z, S, T, and the rotation gates, and predict each one's effect on the state before running it.
  • Inspect a circuit's exact state with Statevector and read amplitudes, probabilities, and the Bloch vector from it.
  • Distinguish global phase from relative phase, explain why one is unobservable and the other is the basis of every quantum algorithm, and demonstrate the difference with a two-circuit experiment.
  • Convert between a gate and its 2x2 unitary matrix in both directions, and verify a circuit's matrix with Operator.
  • Use Rx, Ry, and Rz for continuous control, and derive the measurement probabilities of Ry(theta) analytically.

Chapter 3: Qubit Manipulation in Code

"The difference between a phase you can see and a phase you cannot is the difference between a quantum computer and an expensive random number generator."

Overview

In Chapter 2 you ran a circuit containing two gates you did not understand. This chapter makes them transparent, and adds every other single-qubit gate you will need.

The organizing idea is simple and it will carry you a long way: a single-qubit gate is a $2\times2$ matrix, a qubit state is a length-2 vector, and applying a gate is a matrix–vector multiplication. That is the whole mathematical content of single-qubit quantum computing. If you can multiply a $2\times2$ matrix by a vector, you can predict exactly what any single-qubit circuit does, and by the end of this chapter you will be doing it in your head for the common cases.

Along the way you get the single most useful debugging tool in quantum programming: Statevector, which shows you the exact quantum state at any point — amplitudes, phases, probabilities, and all. This is a simulator privilege, forbidden on hardware, and you should lean on it shamelessly. Chapter 26 is built almost entirely on it.

The chapter's centerpiece is phase. Phase is the thing that makes quantum computing more than probabilistic computing, and it is invisible in exactly the way that makes it easy to dismiss. Two states can have identical measurement probabilities and behave completely differently a moment later. Section 3.5 demonstrates that with two circuits that differ by one gate and produce opposite answers with certainty. If you take one thing from this chapter, take that.

In this chapter, you will learn to:

  • Build circuits with named registers and read a circuit diagram fluently.
  • Apply X, H, Z, S, T, and the continuous rotations $R_x$, $R_y$, $R_z$.
  • Inspect exact states with Statevector, and read amplitudes, probabilities, and Bloch vectors.
  • Distinguish global phase (unobservable, ignorable) from relative phase (the whole point).
  • Convert between a gate and its matrix in both directions, and verify with Operator.
  • Use rotation gates for continuous control, and derive $P(0) = \cos^2(\theta/2)$ for $R_y(\theta)$.

Learning Paths

How to read this chapter by track. - 🔰 Beginner — read all of it, and run every snippet. §3.5 and §3.7 are the ones that take a second pass; everything else is mechanical. - 🔬 Researcher — §3.3 (Statevector) and §3.8 (Operator) are the tools you will use to verify every construction you publish. §3.6's rotation conventions are a frequent source of sign errors across papers. - 🤖 Quantum ML — §3.6 is your chapter. Every variational model is built from parameterized rotations, and the derivative of $\cos^2(\theta/2)$ is where the parameter-shift rule comes from (Chapter 32). - 🏗️ Quantum Engineer — §3.8's ⚙️ callout on how H decomposes into rz and sx is the practical one; virtual $Z$-rotations being free is a fact you will exploit constantly. - 🔐 Security — skim; you need §3.2 and §3.5 for literacy and can move quickly.


3.1 Circuits, Registers, and Drawing

Start with the simplest possible circuit: one qubit, no gates.

from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
print(qc.draw())
q:

An empty wire. The qubit is in $|0\rangle$ — always, at construction, with no exception. There is no uninitialized quantum memory and no way to ask for a random starting state.

Add a gate:

qc = QuantumCircuit(1)
qc.x(0)
print(qc.draw())
   ┌───┐
q: ┤ X ├
   └───┘

Time runs left to right. The wire is one qubit's history.

Named registers

QuantumCircuit(2, 2) gives you anonymous registers named q and c. For anything you will return to, name them:

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

data = QuantumRegister(2, "data")
anc = QuantumRegister(1, "anc")          # an ancilla, in the sense of Chapter 19
out = ClassicalRegister(2, "out")

qc = QuantumCircuit(data, anc, out)
qc.h(data[0])
qc.cx(data[0], data[1])
qc.measure(data, out)
print(qc.draw())
        ┌───┐     ┌─┐
data_0: ┤ H ├──■──┤M├───
        └───┘┌─┴─┐└╥┘┌─┐
data_1: ─────┤ X ├─╫─┤M├
             └───┘ ║ └╥┘
  anc_0: ──────────╫──╫─
                   ║  ║
 out: 2/═══════════╩══╩═
                   0  1

Two payoffs. The diagram is readable at a glance, and — the practical one — result[0].data.out.get_counts() now says what it means. Recall the Chapter 2 pitfall: the register name is how you address results, and an anonymous c in a circuit with three registers is a guaranteed confusion.

Drawing options

qc.draw()                  # text (always works, no dependencies)
qc.draw("mpl")             # matplotlib, publication quality; needs pylatexenc
qc.draw("latex_source")    # LaTeX source
qc.draw(idle_wires=False)  # hide wires with nothing on them -- essential after transpiling
qc.draw(fold=-1)           # never wrap; useful for wide circuits

idle_wires=False becomes indispensable the first time you transpile a 2-qubit circuit onto a 127-qubit device and get 125 empty lines.

3.2 The Gates That Flip and Mix

Three gates do most of the work. Meet them through code, then through matrices.

X — the quantum NOT

from qiskit.quantum_info import Statevector

qc = QuantumCircuit(1)
print(Statevector(qc))          # before

qc.x(0)
print(Statevector(qc))          # after
Statevector([1.+0.j, 0.+0.j], dims=(2,))
Statevector([0.+0.j, 1.+0.j], dims=(2,))

The two numbers are the amplitudes of $|0\rangle$ and $|1\rangle$. X swapped them: $|0\rangle \to |1\rangle$ and $|1\rangle \to |0\rangle$. It is a bit flip, and on a classical input it is exactly NOT.

$$X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}$$

H — the gate that creates superposition

qc = QuantumCircuit(1)
qc.h(0)
sv = Statevector(qc)
print(sv)
print(sv.probabilities())
Statevector([0.70711+0.j, 0.70711+0.j], dims=(2,))
[0.5 0.5]

Equal amplitudes, so equal probabilities. This state is called $|+\rangle$.

$$H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}$$

Note the minus sign in the corner. It does nothing to $|0\rangle$ and everything to $|1\rangle$:

qc = QuantumCircuit(1)
qc.x(0)              # prepare |1>
qc.h(0)
print(Statevector(qc))
Statevector([ 0.70711+0.j, -0.70711+0.j], dims=(2,))

Same probabilities — both 0.5 — but the second amplitude is negative. This state is $|-\rangle$, and it is a genuinely different state from $|+\rangle$ despite being measurement-identical. That difference is the subject of §3.5, and it is the most important idea in the chapter.

Z — the gate that does nothing, until it does

qc = QuantumCircuit(1)
qc.z(0)
print(Statevector(qc))          # Z applied to |0>
Statevector([1.+0.j, 0.+0.j], dims=(2,))

Nothing happened. Z leaves $|0\rangle$ completely alone.

qc = QuantumCircuit(1)
qc.h(0)                          # |+>
before = Statevector(qc)
qc.z(0)
after = Statevector(qc)
print(before.data.round(4))
print(after.data.round(4))
print(before.probabilities(), after.probabilities())
[0.7071+0.j 0.7071+0.j]
[ 0.7071+0.j -0.7071+0.j]
[0.5 0.5] [0.5 0.5]

Z flipped the sign of the $|1\rangle$ amplitude, turning $|+\rangle$ into $|-\rangle$. The measurement probabilities are unchanged — identical, both 50/50 — so if you measure now, you learn nothing about whether Z was applied.

$$Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}$$

Hold that thought for four sections.

⚛️ The Physics Underneath — Amplitudes, not probabilities.

The state of a qubit is $\alpha|0\rangle + \beta|1\rangle$ with $\alpha, \beta$ complex and $|\alpha|^2 + |\beta|^2 = 1$. Measurement yields 0 with probability $|\alpha|^2$ and 1 with probability $|\beta|^2$.

The amplitudes carry more information than the probabilities, because complex numbers have a phase and probabilities do not. $\beta = +0.7071$ and $\beta = -0.7071$ give the same probability and are different states.

That surplus information is the entire computational resource. A probabilistic classical computer tracks probabilities, which are non-negative and can only add. A quantum computer tracks amplitudes, which can be negative or complex and can therefore cancel. Cancellation — interference — is how every quantum algorithm concentrates probability onto the right answer. Without it you have an expensive random number generator.

3.3 Seeing the State: Statevector

Statevector is your microscope. Learn its whole interface now; you will use it constantly.

from qiskit.quantum_info import Statevector

qc = QuantumCircuit(1)
qc.h(0)
qc.t(0)
sv = Statevector(qc)

print(sv.data)                    # raw complex amplitudes as a numpy array
print(sv.probabilities())         # |amplitude|^2, as an array
print(sv.probabilities_dict())    # the same, keyed by bitstring
print(sv.dims())                  # subsystem dimensions
[0.70710678+0.j         0.5       +0.5j]
[0.5 0.5]
{'0': 0.4999999999999999, '1': 0.4999999999999999}
(2,)

Note that Statevector(qc) simulates the circuit exactly, from the all-zeros state, with no sampling and no noise. It is not a measurement; there are no shots. It is the mathematical truth about the circuit.

Three consequences worth stating explicitly.

It is exact. No sampling error. Probabilities come out as 0.4999999999999999 rather than 0.5 only because of floating-point arithmetic.

It is impossible on hardware. No physical process reveals a quantum state. This is a simulator privilege and you must never let it into an algorithm's correctness argument — only into your debugging.

It costs $2^n$ memory. Fine at 1 qubit, fine at 20, impossible at 50. Chapter 1's exponential wall applies here directly, and it is why the debugging technique of Chapter 26 is always "shrink the instance first."

Convenience constructors

Statevector.from_label("0")        # |0>
Statevector.from_label("1")        # |1>
Statevector.from_label("+")        # |+>
Statevector.from_label("-")        # |->
Statevector.from_label("r")        # |+i>  (the "right" state)
Statevector.from_label("l")        # |-i>
Statevector.from_label("01")       # two qubits: q1=0, q0=1  <- little-endian, as always

These are ideal for tests. "Does my state-preparation subroutine produce $|-\rangle$?" is a one-liner:

from qiskit.quantum_info import Statevector

qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
assert Statevector(qc).equiv(Statevector.from_label("-"))

equiv compares up to global phase, which is almost always what you want and which §3.7 explains. Using == here is a common and confusing error.

🧱 Project Checkpointcircuits.py v0.

Add the project's first quantum module: a one-parameter ansatz and a verification helper.

```python

vqelab/circuits.py -- v0

from qiskit import QuantumCircuit from qiskit.circuit import Parameter

def single_qubit_ansatz(theta: Parameter | float | None = None) -> QuantumCircuit: """The smallest possible variational circuit: one qubit, one knob.""" theta = Parameter("theta") if theta is None else theta qc = QuantumCircuit(1, name="ansatz1") qc.ry(theta, 0) return qc ```

Why $R_y$ and not $R_x$ or $R_z$? Because $R_y$ has real amplitudes (see the matrix in §3.6), which makes the state easy to reason about and plot, and because it sweeps $P(0)$ smoothly from 1 to 0 as $\theta$ goes from 0 to $\pi$ — exactly the behavior a variational optimizer needs. That choice will still be the right one in Chapter 24 when the ansatz has twelve parameters.

The checkpoint file also adds state_of(circuit), a thin wrapper over Statevector that returns amplitudes, probabilities, and the Bloch vector together — the debugging view you will want in every chapter from here to the end.

3.4 The Bloch Sphere, in Code

A single qubit's state has four real numbers ($\alpha$ and $\beta$, each complex), minus one for normalization, minus one for the unobservable global phase — two free parameters. Two parameters means a surface, and that surface is a sphere.

                    z = |0⟩
                       │
                       │
                    ___│___
                  /    │    \
                 /     │     \
   |−⟩ ─────────┼──────┼──────┼───────── |+⟩   x
                 \     │     /
                  \____│____/
                       │
                       │
                    z = |1⟩

           y axis (into the page): |+i⟩ and |−i⟩
  • North pole $= |0\rangle$, south pole $= |1\rangle$.
  • $|+\rangle$ and $|-\rangle$ sit on the $x$ axis; $|{+}i\rangle$ and $|{-}i\rangle$ on the $y$ axis.
  • Every point on the surface is a valid state. Every gate is a rotation of the sphere.

That last sentence is the payoff: single-qubit gates are rotations, and thinking of them that way makes their composition intuitive.

Where the two parameters come from

The count in this section's opening sentence deserves to be done slowly, because it explains why the picture is a sphere and not something else, and because one of the two subtractions is the entire subject of §3.7.

Start with the raw description. A single-qubit state is $\alpha|0\rangle + \beta|1\rangle$ with $\alpha, \beta \in \mathbb{C}$. Two complex numbers is four real numbers.

Subtract one for normalization. $|\alpha|^2 + |\beta|^2 = 1$ is one real equation, and one equation removes one degree of freedom. We have gone from $\mathbb{R}^4$ to the unit sphere sitting inside it. Three parameters left.

Subtract one for global phase. $|\psi\rangle$ and $e^{i\gamma}|\psi\rangle$ are the same physical state for every $\gamma$ — §3.7 shows why — so the entire circle $\{e^{i\gamma}|\psi\rangle : \gamma \in [0, 2\pi)\}$ collapses to a single point. Two parameters left.

The important part is which subtraction is which, because they are different kinds of thing.

Normalization is a constraint. States that violate it do not exist. If you construct one, Qiskit raises, and the error is immediate and obvious.

Global phase is a redundancy. Every state is present; we simply have infinitely many labels for each one. A redundancy never raises. It produces two names for one object and lets you spend an afternoon proving they differ — which they do, as arrays, and do not, as physics. That asymmetry is why the global-phase pitfall is a recurring feature of this book (Chapter 6 §6.6, Chapter 18 §18.3, Chapter 26 §26.3) and normalization is not.

The standard coordinates make the surviving two parameters explicit:

$$|\psi\rangle = \cos\frac{\theta}{2}\,|0\rangle \;+\; e^{i\varphi}\sin\frac{\theta}{2}\,|1\rangle, \qquad \theta \in [0, \pi], \quad \varphi \in [0, 2\pi).$$

$\theta$ is the polar angle measured down from the north pole; $\varphi$ is the azimuth around the equator. Notice that the quotient has already been taken: the $|0\rangle$ amplitude has been chosen real and non-negative. That is not a derivation, it is a choice of representative — one label per state, picked by convention. Every state has exactly one such representative except at the two poles, where $\varphi$ is undefined because the other amplitude vanishes. Longitude is undefined at the Earth's poles for precisely the same reason, and it is the same kind of harmless coordinate singularity.

Converting to Cartesian coordinates gives the Bloch vector:

$$(x, y, z) = (\sin\theta\cos\varphi,\ \sin\theta\sin\varphi,\ \cos\theta).$$

$$x^2 + y^2 + z^2 = \sin^2\theta(\cos^2\varphi + \sin^2\varphi) + \cos^2\theta = \sin^2\theta + \cos^2\theta = 1.$$

The radius is 1 identically — not approximately, not for the examples we happened to pick. Every normalized single-qubit state lands exactly on the surface, which is why the numbers in the next subsection come out to 1.0000 every time without anyone arranging it.

You can generate any $(\theta, \varphi)$ with two gates, and check the formula:

import numpy as np
from qiskit.quantum_info import Statevector

for th_f, ph_f in [(0, 0), (0.5, 0), (0.5, 0.5), (0.5, 1.0),
                   (1.0, 0), (0.25, 0.5), (0.75, 1.5)]:
    th, ph = th_f * np.pi, ph_f * np.pi
    qc = QuantumCircuit(1)
    qc.ry(th, 0)                  # sets the polar angle
    qc.rz(ph, 0)                  # sets the azimuth
    a, b = Statevector(qc).data
    got = (2 * np.real(np.conj(a) * b), 2 * np.imag(np.conj(a) * b),
           abs(a) ** 2 - abs(b) ** 2)
    want = (np.sin(th) * np.cos(ph), np.sin(th) * np.sin(ph), np.cos(th))
    print(f"theta={th_f:.2f}pi phi={ph_f:.2f}pi  "
          f"measured ({got[0]:+.4f}, {got[1]:+.4f}, {got[2]:+.4f})  "
          f"predicted ({want[0]:+.4f}, {want[1]:+.4f}, {want[2]:+.4f})")
theta=0.00pi phi=0.00pi  measured (+0.0000, +0.0000, +1.0000)  predicted (+0.0000, +0.0000, +1.0000)
theta=0.50pi phi=0.00pi  measured (+1.0000, +0.0000, +0.0000)  predicted (+1.0000, +0.0000, +0.0000)
theta=0.50pi phi=0.50pi  measured (+0.0000, +1.0000, +0.0000)  predicted (+0.0000, +1.0000, +0.0000)
theta=0.50pi phi=1.00pi  measured (-1.0000, +0.0000, +0.0000)  predicted (-1.0000, +0.0000, +0.0000)
theta=1.00pi phi=0.00pi  measured (+0.0000, +0.0000, -1.0000)  predicted (+0.0000, +0.0000, -1.0000)
theta=0.25pi phi=0.50pi  measured (+0.0000, +0.7071, +0.7071)  predicted (+0.0000, +0.7071, +0.7071)
theta=0.75pi phi=1.50pi  measured (-0.0000, -0.7071, -0.7071)  predicted (-0.0000, -0.7071, -0.7071)

Exact agreement at every point, including the two poles where $\varphi$ does nothing.

Note what rz did in that loop. It set the azimuth of the Bloch vector while changing no measurement probability — $z$ is untouched, so $P(0) = (1+z)/2$ is untouched. rz is the gate that moves a state without moving any number you can measure directly. That is the whole of §3.5 restated as geometry, and it is why the $\varphi$ coordinate is exactly as hard to observe as it is important.

📐 Math Aside — Why $\theta/2$ in the state and $\theta$ on the sphere.

The half-angle that §3.6 warns about is already here, in the parameterization, before any gate has been applied. It is not a Qiskit convention; it is the map between two different spaces.

The state lives in $\mathbb{C}^2$; the Bloch vector lives in $\mathbb{R}^3$. The map between them is two-to-one: $|\psi\rangle$ and $-|\psi\rangle$ have the same Bloch vector, because the Cartesian formulas above are built from products $\bar{\alpha}\beta$ and $|\alpha|^2$, each of which is blind to an overall sign.

Run $\theta$ from $0$ to $2\pi$ in the state formula and the state traverses a full circle, ending at $-|0\rangle$. Run the same $\theta$ through $(\sin\theta\cos\varphi, \sin\theta\sin\varphi, \cos\theta)$ and the Bloch vector traverses a full circle too — but it started and ended at the north pole, having gone all the way around, while the state got only halfway home.

Two turns of the state, one turn of the sphere. The factor of two in $R_y(\theta)$'s matrix is the bookkeeping that reconciles them, which is why Case Study 1's bug is a geometric fact rather than a documentation lapse, and why it will still be there in whatever framework you switch to.

Computing the Bloch vector

import numpy as np
from qiskit.quantum_info import Statevector

def bloch_vector(sv: Statevector) -> tuple[float, float, float]:
    """Cartesian Bloch coordinates of a single-qubit state."""
    a, b = sv.data
    return (2 * np.real(np.conj(a) * b),
            2 * np.imag(np.conj(a) * b),
            abs(a) ** 2 - abs(b) ** 2)

states = {
    "|0>":      [],
    "|1>":      ["x"],
    "|+>":      ["h"],
    "|->":      ["x", "h"],
    "|+i>":     ["h", "s"],
    "T H |0>":  ["h", "t"],
}
for label, ops in states.items():
    qc = QuantumCircuit(1)
    for op in ops:
        getattr(qc, op)(0)
    x, y, z = bloch_vector(Statevector(qc))
    print(f"{label:<9} ({x:+.4f}, {y:+.4f}, {z:+.4f})")
|0>       (+0.0000, +0.0000, +1.0000)
|1>       (+0.0000, +0.0000, -1.0000)
|+>       (+1.0000, +0.0000, +0.0000)
|->       (-1.0000, +0.0000, +0.0000)
|+i>      (+0.0000, +1.0000, +0.0000)
T H |0>   (+0.7071, +0.7071, +0.0000)

Every one of these has length exactly 1 — they are all on the sphere's surface.

Now reread the gates as rotations:

Gate Rotation
X 180° about the $x$ axis (north pole ↔ south pole)
Y 180° about the $y$ axis
Z 180° about the $z$ axis ($|+\rangle \leftrightarrow |-\rangle$, and $|0\rangle$ fixed)
H 180° about the diagonal axis halfway between $x$ and $z$
S 90° about $z$ ($|+\rangle \to |{+}i\rangle$)
T 45° about $z$

Look at the table and the output together. Z is a $z$-rotation, so it cannot move $|0\rangle$ — that state is on the rotation axis. That is why §3.2's qc.z(0) on $|0\rangle$ did nothing. Not a special case; a fixed point.

And T is a 45° $z$-rotation, which is why T H |0> landed at $(0.7071, 0.7071, 0)$ — exactly halfway between the $x$ and $y$ axes on the equator.

Plotting it

from qiskit.visualization import plot_bloch_multivector
import matplotlib.pyplot as plt

qc = QuantumCircuit(1)
qc.h(0)
qc.t(0)
plot_bloch_multivector(Statevector(qc))
plt.savefig("bloch.png", dpi=150, bbox_inches="tight")

🧪 Run It — Watch the sphere move.

Build a circuit one gate at a time and plot after each:

python qc = QuantumCircuit(1) for step, gate in enumerate(["h", "t", "t", "s", "z"]): getattr(qc, gate)(0) x, y, z = bloch_vector(Statevector(qc)) print(f"after {gate.upper():<2} ({x:+.3f}, {y:+.3f}, {z:+.3f})")

Every gate in that list is a $z$-rotation except H. Predict, before running, that the point will move onto the equator and then travel around it without ever leaving. Then confirm the $z$ coordinate stays at 0 for the entire sequence.

That is what "a gate is a rotation" buys you: you predicted a five-gate circuit's behavior without multiplying a single matrix.

⚠️ Common Pitfall — The Bloch sphere is a one-qubit story.

There is no Bloch sphere for two qubits. plot_bloch_multivector will happily draw two spheres for a two-qubit state, and for a product state those two spheres tell the whole truth.

For an entangled state they do not. Draw the Bell state that way and you get two arrows of zero length pointing nowhere — because neither qubit has a state of its own. The picture is not wrong; it is telling you something true and easily misread. The information lives in the correlation, which no per-qubit picture can show.

Chapter 4 §4.4 returns to this. Do not build intuition that depends on visualizing many qubits at once; it does not generalize, and that failure of generalization is precisely why quantum computing is hard.

3.5 Phase: The Thing You Cannot See Yet

Here is the demonstration that the whole chapter is built toward.

Two circuits. They differ by a single Z gate. Both contain a state whose measurement probabilities are exactly 50/50 at the moment Z would act. Run them.

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

sim = AerSimulator()

# Circuit A: H, then H
a = QuantumCircuit(1, 1)
a.h(0)
a.h(0)
a.measure(0, 0)

# Circuit B: H, then Z, then H
b = QuantumCircuit(1, 1)
b.h(0)
b.z(0)          # <-- the only difference
b.h(0)
b.measure(0, 0)

print("H H   :", sim.run(transpile(a, sim), shots=1024, seed_simulator=1234).result().get_counts())
print("H Z H :", sim.run(transpile(b, sim), shots=1024, seed_simulator=1234).result().get_counts())
H H   : {'0': 1024}
H Z H : {'1': 1024}

Not 60/40. Not "mostly 1." Every single shot, with certainty, opposite answers.

Sit with that. Halfway through both circuits the qubit is in a state that would measure 0 half the time and 1 half the time. The Z gate changes nothing about those probabilities — we verified that in §3.2, both [0.5 0.5]. And yet the final answers are deterministic and opposite.

Here is the whole story in amplitudes:

Circuit A                              Circuit B
|0⟩                                    |0⟩
  ↓ H                                    ↓ H
(|0⟩ + |1⟩)/√2         = |+⟩           (|0⟩ + |1⟩)/√2         = |+⟩
                                         ↓ Z
                                       (|0⟩ − |1⟩)/√2         = |−⟩
  ↓ H                                    ↓ H
|0⟩                                    |1⟩
  ↓ measure                              ↓ measure
'0' with certainty                     '1' with certainty

The second H does not "undo" the first. It interferes the two branches. In circuit A the $|1\rangle$ contributions cancel and the $|0\rangle$ contributions add. In circuit B the sign flip reverses which cancels.

📐 Math Aside — The cancellation, explicitly.

$H|0\rangle = \tfrac{1}{\sqrt2}(|0\rangle + |1\rangle)$ and $H|1\rangle = \tfrac{1}{\sqrt2}(|0\rangle - |1\rangle)$.

Circuit A, applying $H$ to $|+\rangle$:

$$H\!\left[\tfrac{1}{\sqrt2}(|0\rangle + |1\rangle)\right] > = \tfrac{1}{2}\bigl[(|0\rangle + |1\rangle) + (|0\rangle - |1\rangle)\bigr] > = \tfrac{1}{2}\bigl[2|0\rangle + 0\cdot|1\rangle\bigr] = |0\rangle$$

Circuit B, applying $H$ to $|-\rangle$:

$$H\!\left[\tfrac{1}{\sqrt2}(|0\rangle - |1\rangle)\right] > = \tfrac{1}{2}\bigl[(|0\rangle + |1\rangle) - (|0\rangle - |1\rangle)\bigr] > = \tfrac{1}{2}\bigl[0\cdot|0\rangle + 2|1\rangle\bigr] = |1\rangle$$

The $|1\rangle$ terms cancel in the first and the $|0\rangle$ terms cancel in the second. That is interference, and it is the only thing a quantum computer does that a probabilistic classical computer cannot. Probabilities are non-negative and can only accumulate; amplitudes can be negative and can annihilate.

Every algorithm in Part IV is an elaborate arrangement for making the wrong answers cancel.

The pattern to recognize

H … Z … H is not an isolated trick. It is the skeleton of a large fraction of quantum computing:

  1. H on every qubit — create a superposition over all inputs.
  2. Some operation that applies phases depending on the input — an oracle (Chapter 19).
  3. H again — convert those invisible phases into visible measurement outcomes.

That is Deutsch–Jozsa. It is Bernstein–Vazirani. It is the diffusion operator in Grover's algorithm. It is, in a generalized form (the QFT replacing the final H layer), Shor's algorithm.

You have just run the smallest possible instance of it.

3.6 Rotation Gates: Continuous Control

Everything so far has been discrete: flip, or don't. Real algorithms need continuous knobs, and those are the rotation gates.

$$R_x(\theta) = \begin{pmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\[4pt] -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2}\end{pmatrix} \qquad R_y(\theta) = \begin{pmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\[4pt] \sin\frac{\theta}{2} & \cos\frac{\theta}{2}\end{pmatrix} \qquad R_z(\theta) = \begin{pmatrix} e^{-i\theta/2} & 0 \\[4pt] 0 & e^{i\theta/2}\end{pmatrix}$$

Each rotates the Bloch sphere by $\theta$ about the named axis.

Note the $\theta/2$. A $2\pi$ rotation of the state is only a $\pi$ rotation of the Bloch sphere — you have to turn a qubit through $4\pi$ to get back to exactly where you started. This is a real physical fact about spin-½ systems, and practically it is a reliable source of factor-of-two bugs. When a rotation does half or twice what you expected, check this first.

$R_y$ is the workhorse, because its matrix is entirely real, so it keeps real states real and is easy to reason about:

import numpy as np
from qiskit.quantum_info import Statevector

for frac in (0, 0.25, 0.5, 0.75, 1.0):
    qc = QuantumCircuit(1)
    qc.ry(frac * np.pi, 0)
    p = Statevector(qc).probabilities()
    print(f"Ry({frac:.2f}pi)  P(0)={p[0]:.4f}  P(1)={p[1]:.4f}")
Ry(0.00pi)  P(0)=1.0000  P(1)=0.0000
Ry(0.25pi)  P(0)=0.8536  P(1)=0.1464
Ry(0.50pi)  P(0)=0.5000  P(1)=0.5000
Ry(0.75pi)  P(0)=0.1464  P(1)=0.8536
Ry(1.00pi)  P(0)=0.0000  P(1)=1.0000

A smooth sweep from certainly-0 to certainly-1, passing through an equal superposition at $\theta = \pi/2$ — which is $|+\rangle$, the same state H produces.

The closed form falls straight out of the matrix. Applying $R_y(\theta)$ to $|0\rangle$ gives amplitudes $(\cos\frac{\theta}{2},\ \sin\frac{\theta}{2})$, so

$$P(0) = \cos^2\!\frac{\theta}{2}, \qquad P(1) = \sin^2\!\frac{\theta}{2}.$$

Check it: $\theta = \pi/4$ gives $\cos^2(\pi/8) = 0.8536$. ✓

🤖 For the Quantum ML path — this is where the parameter-shift rule comes from.

$P(0) = \cos^2(\theta/2)$ is differentiable, and its derivative is $-\tfrac12\sin\theta$. That means a measurement outcome is a smooth function of a circuit parameter, which means you can do gradient descent on quantum circuits.

You cannot get that derivative by backpropagation — there is no intermediate state to propagate through without destroying it. But there is an exact identity that recovers it from two circuit evaluations at shifted parameter values, and that identity is the parameter-shift rule. It is the foundation of every variational algorithm in this book and the whole of Part VI.

Chapter 32 §32.3 derives it. It follows from exactly the trigonometry on this page.

Composing rotations

Two rotations in a row is a rotation. Which one depends entirely on whether the axes agree.

Same axis: the angles add. This is exact, not approximate:

from qiskit.quantum_info import Operator

c1 = QuantumCircuit(1); c1.rz(0.3, 0); c1.rz(0.7, 0)
c2 = QuantumCircuit(1); c2.rz(1.0, 0)
print(np.allclose(Operator(c1).data, Operator(c2).data))
print(Operator(c1).data.round(4))
True
[[0.8776-0.4794j 0.    +0.j    ]
 [0.    +0.j     0.8776+0.4794j]]

$R_z(0.3)\,R_z(0.7) = R_z(1.0)$, on the nose — and $0.8776 = \cos(0.5)$, $0.4794 = \sin(0.5)$, which is the half angle of $1.0$ doing exactly what the matrix definition says. The proof is one line: $R_z(\theta) = \operatorname{diag}(e^{-i\theta/2}, e^{i\theta/2})$, diagonal matrices multiply entrywise, and exponents add.

This is a fact the transpiler lives on. Every adjacent pair of same-axis rotations in your circuit is one rotation, and the optimizer will merge them without being asked — that is what the Optimize1qGatesDecomposition pass does, and it runs from optimization level 1 upward. It is one of the reasons a hand-written circuit almost never survives transpilation in recognizable form (Chapter 28 §28.5).

Different axes: order matters. Rotations about different axes do not commute, and here is the smallest demonstration:

c3 = QuantumCircuit(1); c3.rx(0.3, 0); c3.rz(0.7, 0)   # Rx first
c4 = QuantumCircuit(1); c4.rz(0.7, 0); c4.rx(0.3, 0)   # Rz first

print("identical operators:", np.allclose(Operator(c3).data, Operator(c4).data))
print("equal up to global phase:", Operator(c3).equiv(Operator(c4)))
for label, c in (("Rx then Rz", c3), ("Rz then Rx", c4)):
    sv = Statevector(c)
    print(f"  {label}: {sv.data.round(4)}   P = {sv.probabilities().round(4)}")
identical operators: False
equal up to global phase: False
  Rx then Rz: [0.9288-0.339j  0.0512-0.1404j]   P = [0.9777 0.0223]
  Rz then Rx: [ 0.9288-0.339j  -0.0512-0.1404j]   P = [0.9777 0.0223]

Read those two lines carefully, because they are §3.5's lesson arriving from a completely different direction.

The two circuits produce different states. Their measurement probabilities are identical to four decimals — [0.9777 0.0223] both times. The difference is confined to the sign of the real part of the $|1\rangle$ amplitude, which is a relative phase, which the computational-basis measurement cannot see. And equiv reports False, so this is not a global phase you may ignore: it is a real, observable difference that this particular measurement is blind to.

That is the book's recurring warning — a measurement that cannot detect the thing being asked about — appearing in its smallest possible form. If you had swapped two gates by accident and tested the result by sampling in the computational basis, the test would have passed.

⚠️ Common Pitfall — Reading circuit order backwards.

qc.rx(0.3, 0) then qc.rz(0.7, 0) draws left to right as $R_x$ then $R_z$, and the corresponding matrix product is written right to left: $R_z(0.7)\,R_x(0.3)$. The gate applied first sits rightmost, because it acts on the state vector first.

Circuit diagrams read like a timeline; matrix products read like function composition. Everyone mixes these up at least once, and because non-commuting rotations still give the same probabilities in the example above, the mistake can survive a sloppy test. When comparing a hand-derived matrix against Operator(qc), if the two are transposes-of-each-other-ish or differ in an off-diagonal sign, check the order before you check the algebra.

How far does composition get you? Rotations about a single axis form a one-parameter family, so no amount of $R_z$ will ever move $|0\rangle$. Two alternating axes are enough for everything: $R_z(\phi)\,R_y(\theta)\,R_z(\lambda)$ reaches every single-qubit unitary up to global phase, which is the Euler-angle decomposition. Three parameters, because the rotation group of a sphere is three-dimensional. Case Study 2 builds that construction, verifies it on 200 random unitaries, and shows what it costs in pulses.

Phase gates

p(λ) applies a phase to the $|1\rangle$ amplitude only:

$$P(\lambda) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\lambda} \end{pmatrix}$$

The named gates are special cases: $Z = P(\pi)$, $S = P(\pi/2)$, $T = P(\pi/4)$.

from qiskit.quantum_info import Operator

qc = QuantumCircuit(1)
for _ in range(8):
    qc.t(0)
print(Operator(qc).data.round(6))
[[1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j]]

Eight T gates make the identity, because $T$ is an eighth of a full turn — hence the name "$\pi/8$ gate," which confusingly refers to $\lambda/2 = \pi/8$ rather than to $\lambda = \pi/4$. Four make $Z$. Try it.

🗝️ Version Notep versus the old u1.

Older Qiskit had u1(λ), u2(φ,λ), and u3(θ,φ,λ). These were removed. The modern equivalents: p(λ) replaces u1, and the general single-qubit gate is u(θ,φ,λ).

p and u1 differ by a global phase, which is unobservable on its own but is observable when the gate is used as a controlled operation. If you are porting old code that uses cu1, read §3.7 before assuming the substitution is free.

3.7 Global Phase Versus Relative Phase

Two kinds of phase. One matters enormously and one does not matter at all, and telling them apart prevents a specific class of wasted afternoon.

Relative phase is a phase difference between amplitudes within a state. $|+\rangle$ and $|-\rangle$ differ by a relative phase, and §3.5 showed that this difference is observable — dramatically so.

Global phase multiplies the entire state by a unit-magnitude complex number. It is unobservable, always, by any measurement whatsoever.

qc = QuantumCircuit(1)
qc.x(0)
qc.z(0)
qc.x(0)
qc.z(0)
print(Operator(qc).data.round(4))
print(Statevector(qc).data.round(4), Statevector(qc).probabilities())
[[-1.+0.j  0.+0.j]
 [ 0.+0.j -1.+0.j]]
[-1.+0.j  0.+0.j] [1. 0.]

That circuit implements $-I$: the identity times $-1$. The state is $-|0\rangle$, and the probability of measuring 0 is 1, exactly as it is for $|0\rangle$. You cannot construct any experiment that distinguishes $|\psi\rangle$ from $-|\psi\rangle$, or from $e^{i\phi}|\psi\rangle$ for any $\phi$, because probabilities depend on $|\alpha|^2$ and the phase vanishes under the modulus.

Practical consequences:

Compare with equiv, not ==.

from qiskit.quantum_info import Statevector

a = Statevector([1, 0])
b = Statevector([-1, 0])
print(a == b)        # False   -- different arrays
print(a.equiv(b))    # True    -- same physical state

Testing a state-preparation routine with == and getting a failure over a global phase is a rite of passage. equiv is what you want essentially always, and Chapter 27 builds its whole statevector-testing approach on it.

Transpilers introduce global phases freely, and track them. Look:

qc = QuantumCircuit(1)
qc.h(0)
t = transpile(qc, basis_gates=["rz", "sx", "x"], optimization_level=1)
print(t.draw())
print("global phase:", t.global_phase)
   ┌─────────┐┌────┐┌─────────┐
q: ┤ Rz(π/2) ├┤ √X ├┤ Rz(π/2) ├
   └─────────┘└────┘└─────────┘
global phase: 0.7853981633974483

Your H became $R_z(\pi/2)\cdot\sqrt{X}\cdot R_z(\pi/2)$ plus a global phase of $\pi/4$. The transpiler is not being sloppy; it records the phase because it is bookkeeping it needs if this circuit ever becomes a controlled operation.

⚠️ Common Pitfall — Global phase stops being global when you control it.

This is the exception that makes the rule dangerous.

$-I$ is unobservable. Controlled-$(-I)$ is not. If the control qubit is in superposition, the $-1$ applies only to the branch where the control is 1 — which makes it a relative phase between branches, and relative phases are observable.

This is the mechanism behind phase kickback, which is the engine of Deutsch–Jozsa, Bernstein–Vazirani, and quantum phase estimation. What looks like an ignorable bookkeeping detail is, one control qubit later, the entire algorithm.

Chapter 19 §19.3 is built on this. For now: never delete a global phase from a subcircuit you might later control.

The concrete instance: p(λ) versus rz(λ)

The pitfall above is abstract. Here is the specific pair of gates it will actually bite you with, and it is a pair you have already met — §3.6's 🗝️ Version Note promised this section.

Both gates rotate about $z$ by $\lambda$. They differ only in where they put the phase:

$$P(\lambda) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\lambda}\end{pmatrix}, \qquad R_z(\lambda) = \begin{pmatrix} e^{-i\lambda/2} & 0 \\ 0 & e^{i\lambda/2}\end{pmatrix}.$$

Factor $e^{-i\lambda/2}$ out of $R_z$:

$$R_z(\lambda) = e^{-i\lambda/2}\begin{pmatrix} 1 & 0 \\ 0 & e^{i\lambda}\end{pmatrix} = e^{-i\lambda/2}\, P(\lambda) \qquad\Longleftrightarrow\qquad P(\lambda) = e^{i\lambda/2}\, R_z(\lambda).$$

They differ by exactly the global phase $e^{i\lambda/2}$ — the half angle again. $P$ is the "asymmetric" convention that pins the $|0\rangle$ amplitude at 1; $R_z$ is the "symmetric" convention that splits the phase evenly so that the gate is a genuine rotation generated by $Z/2$.

Confirm it:

import numpy as np
from qiskit.quantum_info import Operator
from qiskit.circuit.library import PhaseGate, RZGate

lam = np.pi / 2
print(Operator(PhaseGate(lam)).data.round(4))
print(Operator(RZGate(lam)).data.round(4))
print("equiv:", Operator(PhaseGate(lam)).equiv(Operator(RZGate(lam))))
print("equal:", Operator(PhaseGate(lam)) == Operator(RZGate(lam)))
[[1.+0.j 0.+0.j]
 [0.+0.j 0.+1.j]]
[[0.7071-0.7071j 0.    +0.j    ]
 [0.    +0.j     0.7071+0.7071j]]
equiv: True
equal: False

equiv: True, equal: False — the signature of a pure global-phase difference. Now put both through the interference circuit from §3.5, the one that was so brutally sensitive to relative phase:

for gate in ("p", "rz"):
    qc = QuantumCircuit(1, 1)
    qc.h(0)
    getattr(qc, gate)(np.pi, 0)
    qc.h(0)
    qc.measure(0, 0)
    print(f"H {gate}(pi) H :",
          sim.run(transpile(qc, sim), shots=1024,
                  seed_simulator=1234).result().get_counts())
H p(pi) H : {'1': 1024}
H rz(pi) H : {'1': 1024}

Identical. Both 1,024 out of 1,024. The circuit that could tell $|+\rangle$ from $|-\rangle$ with certainty cannot tell p(π) from rz(π) at all. Substituting one for the other is genuinely free.

★ Now control them

Same two gates, one control qubit. Put the control in superposition with H, put the target in $|1\rangle$ so the phase actually has something to act on, apply the controlled gate, then H the control and measure it. This is the §3.5 interferometer with the control qubit playing the role of the measured qubit — it is the smallest possible phase-estimation circuit.

def kickback(gate: str, lam: float) -> dict:
    qc = QuantumCircuit(2, 1)
    qc.h(0)                       # control into superposition
    qc.x(1)                       # target into |1>
    getattr(qc, gate)(lam, 0, 1)  # cp or crz
    qc.h(0)
    qc.measure(0, 0)
    return sim.run(transpile(qc, sim), shots=1024,
                   seed_simulator=1234).result().get_counts()

print("cp(pi) :", kickback("cp", np.pi))
print("crz(pi):", kickback("crz", np.pi))
cp(pi) : {'1': 1024}
crz(pi): {'1': 503, '0': 521}

One is a certainty. The other is a coin flip.

Nothing about the two gates' own behaviour changed. p(π) and rz(π) are still measurement-identical in every uncontrolled circuit. But controlling them exposed the $e^{i\lambda/2}$ that separated them, and it came out as the difference between an algorithm that works and 1,024 shots of noise.

That is the pitfall above, quantified. It is also the single most useful thing in this chapter for anyone who is going to write oracles, because it is exactly the failure mode of a hand-built phase oracle.

📐 Math Aside — Where the coin flip comes from.

Write the control's state at each step. Let $\Phi$ be the phase the controlled gate applies to the branch where the control is $|1\rangle$.

$$|0\rangle \xrightarrow{\ H\ } \tfrac{1}{\sqrt2}(|0\rangle + |1\rangle) > \xrightarrow{\ \text{controlled-}U\ } \tfrac{1}{\sqrt2}(|0\rangle + e^{i\Phi}|1\rangle) > \xrightarrow{\ H\ } \tfrac{1}{2}\bigl[(1 + e^{i\Phi})|0\rangle + (1 - e^{i\Phi})|1\rangle\bigr]$$

$$P(0) = \tfrac14\bigl|1 + e^{i\Phi}\bigr|^2 = \tfrac14(2 + 2\cos\Phi) > = \frac{1 + \cos\Phi}{2} = \cos^2\frac{\Phi}{2}.$$

The whole circuit is a phase-to-probability converter, and $\cos^2(\Phi/2)$ is the same functional form as $R_y$'s $P(0)$ in §3.6 — for the same reason, since both are interference between two paths differing by a phase.

Now read off $\Phi$ for each gate, with the target in $|1\rangle$:

  • cp(λ) applies $e^{i\lambda}$ to $|1\rangle$, so $\Phi = \lambda$ and $P(0) = \cos^2(\lambda/2)$.
  • crz(λ) applies $e^{+i\lambda/2}$ to $|1\rangle$, so $\Phi = \lambda/2$ and $P(0) = \cos^2(\lambda/4)$.

At $\lambda = \pi$: $\cos^2(\pi/2) = 0$ — certainty, and it comes out '1'. $\cos^2(\pi/4) = 0.5$ — a coin flip. The measured 1,024 and 503/521 are exactly these two numbers.

The prediction is a whole curve, not one point, so test the whole curve. This is the statevector version — no shots, no sampling error — against the closed forms just derived:

def p0(gate: str, lam: float) -> float:
    qc = QuantumCircuit(2)
    qc.h(0)
    qc.x(1)
    getattr(qc, gate)(lam, 0, 1)
    qc.h(0)
    return Statevector(qc).probabilities([0])[0]        # marginal on the control

hdr = f"{'lambda':>8} | {'cp':>8} | {'cos^2(l/2)':>11} | {'crz':>8} | {'cos^2(l/4)':>11}"
print(hdr)
print("-" * len(hdr))
for frac in (0.0, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0):
    lam = frac * np.pi
    print(f"{frac:>6.2f}pi | {p0('cp', lam):>8.4f} | {np.cos(lam/2)**2:>11.4f} | "
          f"{p0('crz', lam):>8.4f} | {np.cos(lam/4)**2:>11.4f}")
  lambda |       cp |  cos^2(l/2) |      crz |  cos^2(l/4)
----------------------------------------------------------
  0.00pi |   1.0000 |      1.0000 |   1.0000 |      1.0000
  0.25pi |   0.8536 |      0.8536 |   0.9619 |      0.9619
  0.50pi |   0.5000 |      0.5000 |   0.8536 |      0.8536
  0.75pi |   0.1464 |      0.1464 |   0.6913 |      0.6913
  1.00pi |   0.0000 |      0.0000 |   0.5000 |      0.5000
  1.50pi |   0.5000 |      0.5000 |   0.1464 |      0.1464
  2.00pi |   1.0000 |      1.0000 |   0.0000 |      0.0000

Exact agreement in every cell. And now the structure is visible: crz's fringe is the same fringe, stretched by a factor of two. cp completes a full inversion by $\lambda = \pi$; crz needs $\lambda = 2\pi$. Substituting one for the other does not break your circuit randomly — it halves every phase in it.

Which means the bug is worse than a crash. A halved phase still produces smooth, monotonic, plausible output. Your Bernstein–Vazirani will return a bitstring. Your phase estimation will return a phase. Case Study 1 documented the same shape of failure for the half-angle in $R_y$, and this is that bug's twin: same factor, same invisibility, same reason.

🐛 Debug This — Phase estimation returns half the right answer.

Symptom. A hand-built controlled-phase oracle gives an estimate that is consistently 50% of the known value. Doubling the input "fixes" it. Every unit test on the uncontrolled gate passes.

Diagnosis. You wrote crz where you meant cp, or vice versa. The uncontrolled tests passed because the gates are measurement-identical uncontrolled; the controlled behaviour differs by $e^{i\lambda/2}$.

Confirm it in one line — the diagonal of the controlled operator is the whole story:

python for gate in ("cp", "crz"): qc = QuantumCircuit(2) getattr(qc, gate)(np.pi / 2, 0, 1) print(f"{gate:>4}:", np.diag(Operator(qc).data).round(4))

text cp: [1.+0.j 1.+0.j 1.+0.j 0.+1.j] crz: [1. +0.j 0.7071-0.7071j 1. +0.j 0.7071+0.7071j]

cp has three 1s and one phase. crz has two. With Qiskit's little-endian ordering and the control on qubit 0, the entries where the control is $|1\rangle$ are indices 1 and 3 — and crz phases both of them, by $e^{\mp i\lambda/2}$. cp phases only the one where control and target are $|1\rangle$.

The consequence practitioners miss: with the target in $|0\rangle$, cp does literally nothing for any $\lambda$ — measured $P(0) = 1.0000$ across the whole sweep — while crz still produces the full $\cos^2(\lambda/4)$ fringe. A test that happens to leave the target in $|0\rangle$ will report the two gates as maximally different rather than subtly so, which is a confusing place to start debugging from.

The fix, exact rather than approximate:

```python want = QuantumCircuit(2) want.cp(lam, 0, 1)

fix = QuantumCircuit(2) fix.crz(lam, 0, 1) fix.p(lam / 2, 0) # the missing phase, on the CONTROL

print(np.allclose(Operator(fix).data, Operator(want).data)) # True ```

Not equivexactly equal. Controlling the global phase $e^{i\lambda/2}$ turns it into a phase gate on the control qubit, so putting it back is a one-line p(λ/2). The algebra: controlling $P(\lambda) = e^{i\lambda/2}R_z(\lambda)$ gives $\text{ctrl-}P(\lambda) = \bigl[P(\lambda/2) \otimes I\bigr]\cdot \text{ctrl-}R_z(\lambda)$, where the $P(\lambda/2)$ acts on the control alone.

💰 Cost and Queue — Getting it right is free.

There is no cost argument for using the wrong one. Both transpile to the same two-qubit budget:

text cp(pi/2) -> {'rz': 3, 'cx': 2} global phase 0.392699 crz(pi/2) -> {'rz': 2, 'cx': 2} global phase 0.000000

Two CX gates either way. The correct gate costs exactly one additional rz — and Chapter 31 §31.1 measured rz at 0.0 ns on real hardware. The difference between the right answer and a coin flip is one free gate.

Two-qubit gates are where the error lives (Chapter 30 measured a single chip's two-qubit error ranging from 0.00750 to 0.07205 — a factor of 9.6), so an identical CX count means an identical error budget. Choose on correctness; there is nothing else to trade.

Where this shows up outside your own code. Three places, all of them measured elsewhere in this book:

  • QASM round trips. Chapter 6 §6.6 measured a global phase of $\pi/4$ going in and 0.000000 coming out, silently, while equiv reported True. Chapter 6's case study then watched that loss invert an answer — {'1': 1757} becoming {'0': 1758} — in a circuit that passed every equivalence test it was given.
  • Framework translation. Chapter 18 §18.3 measured the same loss from the other direction: global phase 1.047198 -> 0.000000 LOST. §18.4 states the rule — OpenQASM transfers the circuit, not the phase.
  • Debugging. Chapter 26 §26.3 lays out four different equality tests for circuits and shows that all four are correct, answering different questions. Which one you want depends on whether the subcircuit will ever be controlled. That is the decision this section is asking you to be able to make.

🔬 Honest Assessment — What this experiment does and does not show.

It shows that p and rz are interchangeable in isolation and are not interchangeable under control, and it shows the exact functional form of the discrepancy. Both halves are exact statevector results with no sampling involved; the shot counts merely illustrate them.

It does not show that global phase is "secretly observable." It is not. The controlled circuit is a different physical operation on a larger system, and what is observable there is a relative phase between two branches of a two-qubit state. Nothing measures the phase of an isolated qubit, here or anywhere.

The practical rule is unchanged and is narrower than "global phase matters": track the global phase of any subcircuit that could become a controlled operation, and use equiv everywhere else. Qiskit's .global_phase attribute exists precisely so you do not have to think about this — right up until you serialize the circuit, at which point Chapter 6 §6.6 applies.

3.8 Gate Equals Matrix: The Bridge, in Both Directions

The chapter's organizing claim, made concrete.

Direction 1: gate → matrix

from qiskit.quantum_info import Operator
from qiskit.circuit.library import XGate, HGate, SGate, TGate, SXGate

for name, gate in [("X", XGate()), ("H", HGate()), ("S", SGate()),
                   ("T", TGate()), ("SX", SXGate())]:
    print(f"{name}:\n{Operator(gate).data.round(4)}\n")
X:
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]

H:
[[ 0.7071+0.j  0.7071+0.j]
 [ 0.7071+0.j -0.7071+0.j]]

S:
[[1.+0.j 0.+0.j]
 [0.+0.j 0.+1.j]]

T:
[[1.    +0.j     0.    +0.j    ]
 [0.    +0.j     0.7071+0.7071j]]

SX:
[[0.5+0.5j 0.5-0.5j]
 [0.5-0.5j 0.5+0.5j]]

Operator works on whole circuits too, which makes it a verification tool:

import numpy as np

qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)

print(Operator(qc).data.round(6))
print("equals X:", Operator(qc).equiv(Operator(XGate())))
[[ 0.+0.j  1.+0.j]
 [ 1.+0.j  0.+0.j]]
equals X: True

$HZH = X$. That is §3.5's experiment restated as an algebraic identity — and it explains why the H Z H circuit produced 1 with certainty: the whole circuit was an X gate in disguise.

Being able to check an identity like that in three lines, before running anything, is a genuinely large productivity difference. Chapter 27 turns it into a test suite.

Direction 2: matrix → gate

Any $2\times2$ unitary matrix is a legal quantum gate, and Qiskit will build the circuit for you:

import numpy as np
from qiskit.quantum_info import Operator

# A 30-degree rotation about the y axis, written directly as a matrix.
theta = np.pi / 6
m = np.array([[np.cos(theta/2), -np.sin(theta/2)],
              [np.sin(theta/2),  np.cos(theta/2)]])

qc = QuantumCircuit(1)
qc.unitary(Operator(m), 0, label="my_gate")
print(Statevector(qc).probabilities().round(4))
[0.9830 0.0170]

$\cos^2(\pi/12) = 0.9830$. ✓

Unitary means $U^\dagger U = I$ — the conjugate transpose is the inverse. This is exactly the condition for the operation to preserve total probability, which is why every quantum gate is unitary and why every quantum gate is reversible. There is no quantum AND gate, because AND destroys information. Chapter 19 §19.5 is entirely about working around that.

📐 Math Aside — What "unitary" buys, in three lines.

The claim is that $U^\dagger U = I$ is probability conservation. The derivation is short enough to be worth doing, because it turns a definition you have to memorize into one you can reconstruct.

Total probability is the squared norm of the state:

$$\sum_i P(i) = \sum_i |\psi_i|^2 = \langle\psi|\psi\rangle.$$

After the gate, the state is $U|\psi\rangle$, and its squared norm is

$$\bigl(U|\psi\rangle\bigr)^\dagger\bigl(U|\psi\rangle\bigr) > = \langle\psi|U^\dagger U|\psi\rangle.$$

Substituting $U^\dagger U = I$ gives $\langle\psi|\psi\rangle$ — the norm we started with, for every state $|\psi\rangle$. And the converse holds too: if the norm is preserved for every $|\psi\rangle$, then $\langle\psi|(U^\dagger U - I)|\psi\rangle = 0$ for all $|\psi\rangle$, which forces $U^\dagger U = I$. The two conditions are the same condition.

So "unitary" is not an extra rule imposed on quantum mechanics from outside. It is the only class of linear maps under which "the probabilities sum to 1" survives, and quantum mechanics is linear for independent reasons.

Check it numerically — 1,000 random $2\times2$ unitaries applied to 1,000 random normalized states:

text worst |norm - 1| over 1000 random unitary x random state: 5.551e-16

That is floating-point noise, not physics. Now the contrast — a perfectly reasonable-looking matrix that is not unitary:

python M = np.array([[1.0, 0.0], [0.0, 0.5]]) # "damp the |1> amplitude" w = M @ np.array([1, 1]) / np.sqrt(2) # applied to |+> print(w.round(4), np.linalg.norm(w).round(6), (abs(w) ** 2).sum().round(6))

text [0.7071 0.3536] 0.790569 0.625

The probabilities now sum to 0.625. Thirty-seven percent of the outcome has gone somewhere that is not an outcome. That is not a gate; it is a description of the qubit leaking — which is a real physical process, and exactly why open-system dynamics needs the density-matrix machinery of Chapter 11 rather than a bigger unitary.

Two consequences you will use constantly. First, $U^{-1} = U^\dagger$ means the inverse of any circuit is free to construct — reverse the order, dagger each gate — which is what qc.inverse() does and what makes uncomputation possible (Chapter 19 §19.5). Second, unitaries preserve inner products, not just norms, so two states that start orthogonal stay orthogonal. No gate can ever make two distinguishable states less distinguishable. That is the constraint every quantum algorithm is designed around, and it is why the no-cloning theorem of Chapter 38 §38.2 is a theorem rather than an engineering limitation.

⚙️ Under the Transpiler — Why rz is free and sx is not.

Current IBM hardware implements the single-qubit basis $\{r_z, s_x, x\}$, and the three are not equal in cost:

  • rz(θ) is virtual. It is not a pulse at all. The control electronics implement a $Z$-rotation by shifting the phase reference of every subsequent pulse on that qubit — bookkeeping in the classical controller. Zero duration, zero error. Chapter 2's Case Study 2 measured seven rz gates in the Bell circuit contributing nothing to its error budget.
  • sx is a real pulse. Roughly 57 ns on a current device, with an error around $3\times10^{-4}$.
  • x is a real pulse, essentially two sx worth of rotation.

Now look back at the H decomposition: $R_z \cdot \sqrt{X} \cdot R_z$. Its true cost is one pulse, not three operations, because both $R_z$ gates are free.

The engineering consequence, which Chapter 28 exploits repeatedly: restructure circuits so that rotations land on the $z$ axis wherever possible. An algorithm expressed in $R_z$ and $\sqrt{X}$ can be dramatically cheaper than the same algorithm expressed in $R_x$ and $R_y$, even with an identical gate count.

The five-gate skeleton

H decomposed into three operations. That is the easy case, because H is a special gate. Here is what a generic single-qubit unitary becomes — a random one, so nothing about it is convenient:

from qiskit.quantum_info import random_unitary

qc = QuantumCircuit(1)
qc.unitary(random_unitary(2, seed=99), 0)
d = transpile(qc, basis_gates=["rz", "sx", "x"], optimization_level=3)
print(d.draw())
print(dict(d.count_ops()), "global phase:", d.global_phase)
global phase: 0.36441
   ┌───────────┐┌────┐┌─────────────┐┌────┐┌─────────────┐
q: ┤ Rz(0.572) ├┤ √X ├┤ Rz(-2.6293) ├┤ √X ├┤ Rz(-3.0786) ├
   └───────────┘└────┘└─────────────┘└────┘└─────────────┘
{'rz': 3, 'sx': 2} global phase: 0.3644078208360213

$$R_z(\lambda_3)\ \sqrt{X}\ R_z(\lambda_2)\ \sqrt{X}\ R_z(\lambda_1)$$

Five gates, and this is the general shape. Three free angles, which is the Euler-angle count from §3.6 — the rotation group of a sphere is three-dimensional, so three angles is not an upper bound that a cleverer compiler might improve. It is exactly right. The two $\sqrt{X}$ gates are fixed: they carry no parameter and are the same pulse every time. All the gate's individuality lives in the three $R_z$ angles.

That is a strange-looking way to build a rotation until you see why hardware wants it.

⚛️ The Physics Underneath — Why the angles ride on the $z$ rotations.

A superconducting qubit is driven by a microwave pulse at the qubit's frequency. The pulse's envelope sets how far the state rotates; the pulse's phase sets which axis in the $xy$ plane it rotates about. Calibrating an envelope is slow, physical, and drifts; the phase is a number in the control electronics.

So the hardware calibrates one envelope — a 90° rotation, $\sqrt{X}$ — with great care, and then reaches every other single-qubit gate by changing phases around it. An rz is not executed at all: the controller redefines what "the $x$ axis" means for every subsequent pulse on that qubit. Chapter 31 §31.1 measured the consequence directly:

text sx 56.9 ns (all qubits) x 56.9 ns (all qubits) rz 0.0 ns (all qubits)

rz is 0.0 ns because nothing happens. The gate is a bookkeeping update in the classical controller, applied for free while the next pulse is being emitted.

This is why the basis is $\{r_z, s_x, x\}$ and not, say, $\{r_x, r_y, r_z\}$: it is the set that pushes every continuous parameter onto the free operation and leaves only fixed, precisely calibrated pulses on the expensive one.

Now cost the skeleton, using Chapter 31 §31.1's measured durations:

   Rz(l3)      0.0 ns      virtual -- a phase update in the controller
   sqrt(X)    56.9 ns      real pulse
   Rz(l2)      0.0 ns
   sqrt(X)    56.9 ns      real pulse
   Rz(l1)      0.0 ns
   ---------------------
   total     113.8 ns      for ANY single-qubit unitary

Any single-qubit gate on this device costs 113.8 ns. Not "about"; the three variable angles contribute nothing at all. A gate whose matrix took you an hour to derive costs the same as an X.

Put that beside the other operations Chapter 31 measured on the same device:

   arbitrary 1q gate     113.8 ns      (derived: 2 x 56.9)
   H                      56.9 ns      (one sqrt(X); see the decomposition above)
   ecr (2-qubit)         341.3 - 881.8 ns, median 533.3
   measure             1,216.0 ns

$533.3 / 113.8 = 4.69$, and $1{,}216 / 113.8 = 10.7$. An entangling gate is nearly five arbitrary single-qubit gates, and a measurement is more than ten. That ratio is the whole justification for the advice in Case Study 2: count two-qubit depth, not gates.

📊 What the Numbers Say — "Free" means free of time, not free of thought.

rz costs 0.0 ns and has no calibrated error. It is genuinely free on the two axes people usually measure. It is not free on a third: it still has to be right.

The p-versus-rz result in §3.7 is precisely a bug that costs nothing to execute and everything to get wrong, and the ⚙️ callout's advice — restructure so rotations land on $z$ — is advice to fill your circuits with exactly the gates whose phase conventions are easiest to confuse.

Chapter 31 §31.1 also ran the counterfactual, which is the honest way to size the benefit: if rz were not virtual and cost a full 56.9 ns like sx, the 83 rz gates in Chapter 29's ansatz would add 4.72 μs of qubit-time and a 1.94% fidelity loss. That is the real magnitude of what the virtual-$Z$ trick is buying — not a rounding error, and about 3.7 times the entire measured effect of dynamical decoupling in Chapter 31 §31.4.

🔀 In Another Framework — the same gate, three different phase conventions.

Every framework has both conventions and names them differently. The pairs that differ by a global phase, in the sense of §3.7:

text Qiskit qc.p(lam, 0) qc.rz(lam, 0) Cirq cirq.Z ** (lam/pi) cirq.rz(lam) PennyLane qml.PhaseShift(lam, 0) qml.RZ(lam, 0)

Within each row, the two are measurement-identical uncontrolled and differ by $e^{i\lambda/2}$ under control — the result of §3.7, restated three times.

Cirq's Z ** t notation is the honest one here: raising a gate to a power is the phase convention, since $Z^t = \operatorname{diag}(1, e^{i\pi t})$ pins the $|0\rangle$ entry at 1. Chapters 14 and 16 cover the frameworks properly; Chapter 18 §18.3 measures what survives translation between them, and global phase is on the list of what does not.

3.9 Summary

A single-qubit state is two complex amplitudes, $\alpha|0\rangle + \beta|1\rangle$, normalized. A gate is a $2\times2$ unitary matrix. Applying a gate is a matrix–vector product. That is all of single-qubit quantum computing.

The amplitudes carry more than the probabilities, because they have phase. That surplus is the computational resource: amplitudes can be negative or complex and can therefore cancel, and cancellation — interference — is what a probabilistic classical computer cannot do.

X flips. H creates superposition, mapping $|0\rangle \to |+\rangle$ and $|1\rangle \to |-\rangle$. Z flips the sign of the $|1\rangle$ amplitude, which does nothing to $|0\rangle$ and turns $|+\rangle$ into $|-\rangle$ — with no change to any measurement probability.

Statevector(qc) gives you the exact state: amplitudes, probabilities, Bloch vector. It is exact, it is impossible on hardware, and it costs $2^n$ memory. It is the single most useful debugging tool you have, and Chapter 26 is built on it.

The Bloch sphere makes single-qubit states geometric. Every gate is a rotation; Z cannot move $|0\rangle$ because that state lies on its axis. The picture does not survive entanglement, and Chapter 4 §4.4 says why.

The demonstration to remember: H H gives 0 with certainty; H Z H gives 1 with certainty. The Z gate changes no probability at the moment it acts, and reverses the final answer completely. That is interference, and the pattern — superpose, apply phases, interfere — is the skeleton of Deutsch–Jozsa, Bernstein–Vazirani, Grover, and Shor.

Rotation gates give continuous control. $R_y(\theta)$ on $|0\rangle$ gives $P(0) = \cos^2(\theta/2)$; the half-angle is a persistent source of factor-of-two bugs. That differentiability is where the parameter-shift rule and all of variational quantum computing come from.

Global phase is unobservable — compare states with equiv, not ==except when the operation is controlled, at which point it becomes a relative phase and drives phase kickback.

The sharpest version of that, measured. p(λ) and rz(λ) differ by the global phase $e^{i\lambda/2}$. Uncontrolled they are indistinguishable: H p(π) H and H rz(π) H both give {'1': 1024}. Controlled, in a kickback interferometer with the target in $|1\rangle$, cp(π) gives {'1': 1024} — a certainty — and crz(π) gives {'1': 503, '0': 521} — a coin flip. The interferometer converts phase to probability as $P(0) = \cos^2(\Phi/2)$, and the two gates put $\Phi = \lambda$ and $\Phi = \lambda/2$ into it, so crz runs the same fringe at half the frequency. Substituting one for the other does not crash anything; it halves every phase in your algorithm and returns a plausible wrong answer. The repair is exact and free: crz(λ) plus p(λ/2) on the control equals cp(λ) on the nose, and costs one extra rz at 0.0 ns.

The five-gate skeleton $R_z\,\sqrt{X}\,R_z\,\sqrt{X}\,R_z$ is what any single-qubit unitary becomes on IBM hardware — three free angles (the Euler count), two fixed pulses. Using Chapter 31 §31.1's measured durations, that is 113.8 ns for any single-qubit gate whatsoever, against a median 533.3 ns entangling gate and a 1,216 ns measurement. Complexity is free at the single-qubit level; two-qubit depth is the number that costs.

$HZH = X$, verifiable in three lines with Operator. And on real hardware, rz is virtual: zero time, zero error. Structure your circuits to use it.


Next: Chapter 4 — two qubits, four amplitudes, and the operation that makes quantum computing more than a collection of independent coins. You will build the Bell state you ran in Chapter 2 and finally understand what it is.