Part III. Circuit Model · Chapter 19
Multi-Qubit Circuits
Two qubits share one four-dimensional state space, and every gate in the circuit acts on a specific piece of it. This chapter drills the discipline that keeps multi-qubit work honest: fix a basis order, expand the tensor products, and check entanglement by factoring, not by counting wires.
In this chapter 8 sections
Reader question. How can a reader apply gates to selected wires while preserving the declared joint-basis ordering?
Lift a local gate into the full state space with tensor products and permutations, apply multi-qubit gates to the correct indices, and track the complete joint vector; wire diagrams are syntax, while basis order defines the actual coordinate operation.
- This chapter does not optimize arbitrary tensor contractions or introduce tensor-network simulation.
- It does not assume every SDK uses the same endianness or wire-index convention.
Gate scope becomes a full-space operator
Construct I⊗H and H⊗I explicitly.
A two-qubit circuit is not two one-qubit circuits sharing a page. The joint state lives in a four-dimensional space with one amplitude per computational-basis string:
Gates then have scope. A one-qubit gate drawn on one wire acts as that gate on one factor and as the identity on the other; a two-qubit gate mixes the branches according to its own rule. Entanglement appears when the result can no longer be written as one state per wire.
The working discipline is boring and absolute: choose a basis order once, write it at the top of the page, and never let it drift.
Evidence boundary. Local operations on composite systems are represented by tensoring with identity operators. [John Watrous] [Michael A. Nielsen]
Nonadjacent wires require an index map
Show how a three-qubit target is located.
A product of two one-qubit states expands by distribution:
Four amplitudes, but built from only four numbers with heavy structure; that is what separable means. A general two-qubit state has four free amplitudes and no such factorization. Wire count tells you the dimension of the space; by itself it says nothing about entanglement.
Scope is the other half of the discipline. H on the first qubit is the matrix ; on the second it is . CNOT with the first qubit controlling is a different permutation from CNOT with the second controlling. Same gate symbols, different operators.
Evidence boundary. Multi-qubit gate application depends on wire indices and a declared basis ordering. [Michael A. Nielsen] [openqasm3]
A two-gate circuit by two methods
Compare full matrices with index-wise amplitude updates.
The second is quieter and more expensive: basis-order drift. Write your amplitudes in the order , then silently switch to halfway through, and every subsequent trace can look plausible while being wrong. This is the multi-qubit version of a sign error, and code review rarely catches it because each line is consistent with some convention.
Evidence boundary. Equivalent abstract circuits may serialize statevectors differently across conventions. [openqasm3] [John Preskill]
Notation contract: Three-qubit basis order and endianness declared; G_on_q uses explicit tensor/permutation construction; wire labels remain stable.
Factor after every stage when possible
Use separability as a debugging aid.
Tensor products are why exact simulation is exponential. A statevector simulator stores complex amplitudes for qubits; a one-qubit gate becomes a strided pass that updates pairs of amplitudes; a controlled gate permutes or mixes the branches whose control bits are set. Thirty qubits is a billion amplitudes; the math from this chapter, running out of RAM.
Depth and width are software concerns too. Two circuits with the same unitary can schedule completely differently after decomposition, so treat the abstract circuit as the semantic object and the compiled circuit as the execution object. Chapter 22 prices the difference.
Endianness bugs that still normalize
Demonstrate a wrong but plausible result.
When you review a simulator, SDK, or compiler pass, look for the conventions page: qubit ordering, endianness of basis strings, gate scope, and measurement bit order. Most integration bugs in this space are convention mismatches between two components that are each internally correct.
A serious platform states these conventions in its API documentation and tests them with small hand-checkable traces; exactly the traces you just did. If the conventions are undocumented, expect to discover them during a debugging session instead.
Wire-scope conformance suite
| Field | Reader-visible record |
|---|---|
| Format | Reference simulator fixtures for three basis-order conventions |
| Verification | Tests local gates on every wire, nonadjacent CNOT, norm preservation, and detection of swapped |01>/|10> coordinates. |
| Availability | Published companion and deterministic command |
The state-vector laboratory checks this chapter's two-qubit ordering and joint-gate trace.
cd labs && python -m unittest tests.test_simulator -vExecutable reference fixture
Run with Python 3.11 or later. The final assertion is the chapter-level pass condition for this small instance.
import math
def validate_layout(qubits, layout):
if tuple(sorted(layout)) != tuple(range(qubits)):
raise ValueError("layout must map each logical wire exactly once")
def wire_mask(wire, qubits, layout):
validate_layout(qubits, layout)
if not 0 <= wire < qubits:
raise ValueError("wire out of range")
return 1 << (qubits - 1 - layout[wire])
def encode_bits(bits, layout):
qubits = len(bits)
index = 0
for wire, bit in enumerate(bits):
if bit:
index |= wire_mask(wire, qubits, layout)
return index
def decode_bits(index, qubits, layout):
return tuple(1 if index & wire_mask(wire, qubits, layout) else 0 for wire in range(qubits))
def apply_x(state, wire, qubits, layout):
mask = wire_mask(wire, qubits, layout)
output = [0j] * len(state)
for basis, amplitude in enumerate(state):
output[basis ^ mask] += amplitude
return output
def apply_h(state, wire, qubits, layout):
mask = wire_mask(wire, qubits, layout)
output = state[:]
scale = math.sqrt(0.5)
for basis in range(len(state)):
if basis & mask == 0:
paired = basis | mask
output[basis] = (state[basis] + state[paired]) * scale
output[paired] = (state[basis] - state[paired]) * scale
return output
def apply_cnot(state, control, target, qubits, layout):
if control == target:
raise ValueError("control and target must differ")
control_mask = wire_mask(control, qubits, layout)
target_mask = wire_mask(target, qubits, layout)
output = [0j] * len(state)
for basis, amplitude in enumerate(state):
output[basis ^ target_mask if basis & control_mask else basis] += amplitude
return output
layouts = ((0, 1, 2), (2, 1, 0), (1, 2, 0))
local_gate_cases = 0
for layout in layouts:
for wire in range(3):
state = [0j] * 8
state[encode_bits((0, 0, 0), layout)] = 1.0
output = apply_x(state, wire, 3, layout)
observed_index = next(index for index, amplitude in enumerate(output) if abs(amplitude) > 0.9)
expected = tuple(1 if index == wire else 0 for index in range(3))
assert decode_bits(observed_index, 3, layout) == expected
local_gate_cases += 1
bell_supports = []
for layout in layouts:
state = [0j] * 8
state[encode_bits((0, 0, 0), layout)] = 1.0
state = apply_cnot(apply_h(state, 0, 3, layout), 0, 2, 3, layout)
support = {decode_bits(index, 3, layout): round(abs(amplitude) ** 2, 12)
for index, amplitude in enumerate(state) if abs(amplitude) > 1e-12}
assert support == {(0, 0, 0): 0.5, (1, 0, 1): 0.5}
bell_supports.append(support)
for layout in ((0, 1, 2, 3), (3, 2, 1, 0)):
state = [0j] * 16
state[encode_bits((1, 0, 0, 0), layout)] = 1.0
output = apply_cnot(state, 0, 3, 4, layout)
observed = next(index for index, amplitude in enumerate(output) if abs(amplitude) > 0.9)
assert decode_bits(observed, 4, layout) == (1, 0, 0, 1)
ket_01_msb = encode_bits((0, 1), (0, 1))
ket_01_lsb = encode_bits((0, 1), (1, 0))
assert (ket_01_msb, ket_01_lsb) == (1, 2)
bad_layout_rejected = False
try:
encode_bits((0, 1, 0), (0, 0, 2))
except ValueError:
bad_layout_rejected = True
assert bad_layout_rejected
print(f"PASS: 19 wire-scope suite checks {local_gate_cases} local gates, {len(bell_supports)} layout-stable Bell traces, and 2 nonadjacent CNOTs; |01> indices={ket_01_msb}/{ket_01_lsb}")
Scope boundary
- This chapter does not optimize arbitrary tensor contractions or introduce tensor-network simulation.
- It does not assume every SDK uses the same endianness or wire-index convention.
Depth commitment. Two lifted operators, one nonadjacent gate, one convention bug, and conformance fixtures.
Practice problem
Apply H to q2 and CNOT(q2,q0) on |001> under a stated big-endian convention, then repeat under little-endian serialization.
- Deliverable
- Two coordinate traces and one convention-conversion map.
- Pass condition
- Reference fixtures verify both traces represent the same abstract labeled-qubit evolution.
Verification record
Expected solution form. Side-by-side index trace with permutation-matrix verification.
Model answer. Under q0-q1-q2 big-endian labels, H on q2 maps |001> to (|000>-|001>)/sqrt(2), and CNOT(q2,q0) gives (|000>-|101>)/sqrt(2). A little-endian serializer changes array indices, not labeled kets; conversion back to canonical labels must yield the same state.
Model result and check. CI converts both outputs to a canonical labeled basis and asserts equality.
Acceptance test. Reference fixtures verify both traces represent the same abstract labeled-qubit evolution.
Companion work
Artifacts for this chapter
These entries resolve to checked-in local source. Commands are reproduced exactly from the chapter manifest, and source-embedded fixtures are exported as direct downloads.
Reproduce or test
cd labs && python -m unittest tests.test_simulator -v
Provenance
Sources and review
- John Watrous. The Theory of Quantum Information. Cambridge University Press / University of Waterloo. 2018textbook
- Michael A. Nielsen and Isaac L. Chuang. Quantum Computation and Quantum Information. Cambridge University Press. 2010textbook
- OpenQASM Technical Steering Committee. OpenQASM 3 specification. Linux Foundation Joint Development Foundation. 2026official technical specification
- John Preskill. Lecture Notes for Physics 219: Quantum Computation. California Institute of Technology. 2018graduate lecture notes
The load-bearing claims in the chapter are mapped inline to this registered source set. A citation supports only the bounded claim beside it.