Steven GellerQuantum Computing, End to End

Book contents

Current section

Part III. Circuit Model

  1. Circuit Notation as a Programming Language
  2. One-Qubit Gates and the Bloch Sphere
  3. Controlled Gates and Reversible Classical Computation
  4. Multi-Qubit Circuits
  5. Bell States and GHZ States
  6. No-Cloning and Why Copying Breaks Quantum Logic
  7. Circuit Cost: Depth, Width, Gate Set, Connectivity

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.

Lab
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.

Scope and non-goals.
  • 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 and basis order in a two-qubit circuit Every gate has a scope; identity fills the rest |q0> |q1> H X H on q0 means H ⊗ I X on q1 means I ⊗ X fix the order: |00>, |01>, |10>, |11> 4 amplitudes, one declared order; drift corrupts every gate trace after it
Figure 19.1. Notice that the drawing underspecifies the operator: the same H symbol means H ⊗ I here and I ⊗ H one wire down. The declared basis order on the right is what makes any of it checkable.

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:

00,01,10,11\lvert00\rangle,\lvert01\rangle,\lvert10\rangle,\lvert11\rangle(19.1)

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:

(a0+b1)(c0+d1)=ac00+ad01+bc10+bd11(a\lvert0\rangle+b\lvert1\rangle)\otimes(c\lvert0\rangle+d\lvert1\rangle)=ac\lvert00\rangle+ad\lvert01\rangle+bc\lvert10\rangle+bd\lvert11\rangle(19.2)

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 HIH\otimes I; on the second it is IHI\otimes H. 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 00,01,10,11\lvert00\rangle,\lvert01\rangle,\lvert10\rangle,\lvert11\rangle, then silently switch to 00,10,01,11\lvert00\rangle,\lvert10\rangle,\lvert01\rangle,\lvert11\rangle 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]

Threequbitbasisorderandendiannessdeclared;Gonqusesexplicittensor/permutationconstruction;wirelabelsremainstable.Three-qubit basis order and endianness declared; G_on_q uses explicit tensor/permutation construction; wire labels remain stable.(19.3)

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 2n2^n complex amplitudes for nn 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

Acceptance contract for Wire-scope conformance suite
FieldReader-visible record
FormatReference simulator fixtures for three basis-order conventions
VerificationTests local gates on every wire, nonadjacent CNOT, norm preservation, and detection of swapped |01>/|10> coordinates.
AvailabilityPublished 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 -v

Executable 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.

  1. Reproduce or test

    cd labs && python -m unittest tests.test_simulator -v

Provenance

Sources and review

  1. John Watrous. The Theory of Quantum Information. Cambridge University Press / University of Waterloo. 2018textbook
  2. Michael A. Nielsen and Isaac L. Chuang. Quantum Computation and Quantum Information. Cambridge University Press. 2010textbook
  3. OpenQASM Technical Steering Committee. OpenQASM 3 specification. Linux Foundation Joint Development Foundation. 2026official technical specification
  4. 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.

Cite this chapter