Chapter 3: Shor's Algorithm

Factoring as Period Finding, Two Integers Factored End to End, and What the Same Circuit Costs at Cryptographic Size

📖 Reading Time: 45-50 minutes 📊 Difficulty: Intermediate 💻 Code Examples: 7 📝 Exercises: 5

🌐 EN | 🇯🇵 JP | Last sync: 2026-08-13

Fundamental Mathematics Dojo > Intermediate Quantum Algorithms > Chapter 3

This is the algorithm that made the field famous, and it is the one place in this course where the speedup is superpolynomial and nobody disputes it. Factoring an $n$-bit integer takes $\exp\left(O(n^{1/3}(\log n)^{2/3})\right)$ operations with the best known classical method and $O(n^3)$ gates with Shor's, and that gap is not a constant factor, not a conditional advantage, and not contingent on a data-loading assumption. Chapter 1's quadratic speedup came with a page of qualifications; this one does not.

What it does come with is a size. The circuit that factors 15 in this chapter uses thirteen qubits; the circuit that would factor a 2048-bit RSA modulus needs of order $10^7$ physical qubits and $10^9$ Toffoli gates under standard error-correction assumptions, and Section 3.4 works that estimate through from stated premises. The honest position sits between two familiar errors. One is to say that quantum computers break RSA — they do not, because none of them can run this circuit at that size, and the distance is measured in orders of magnitude rather than in engineering refinements. The other is to conclude from that distance that the algorithm does not matter, which is equally wrong: the mathematics is settled, the migration to lattice-based cryptography is already specified and underway for exactly this reason, and a factoring circuit is the cleanest existence proof that quantum computation is not merely a different way of writing the same complexity classes.

The technical content is short, because Chapter 2 did most of the work. Factoring reduces to finding the multiplicative order of a random integer modulo $N$; order finding is phase estimation applied to a particular unitary; and phase estimation is the inverse QFT applied after controlled powers. Everything new here is the number theory on either side of the quantum circuit — the reduction going in, and continued fractions coming out — plus the observation that the expensive part of the circuit is not the Fourier transform at all but the modular arithmetic.

Learning Objectives

After completing this chapter, you will be able to:


3.1 From Factoring to Order Finding

The reduction

Let $N$ be an odd composite that is not a prime power, and pick an integer $a$ with $1 < a < N$ and $\gcd(a, N) = 1$. The multiplicative order of $a$ modulo $N$ is the least $r > 0$ with

$$ a^r \equiv 1 \pmod N $$

Suppose $r$ is known and even. Then $x = a^{r/2}$ satisfies $x^2 \equiv 1 \pmod N$, so

$$ (x-1)(x+1) \equiv 0 \pmod N $$

which says $N$ divides the product. If neither factor is divisible by $N$ — that is, if $x \not\equiv \pm 1 \pmod N$ — then $N$ must share a nontrivial factor with each, and

$$ \gcd\left(a^{r/2} - 1,\, N\right), \qquad \gcd\left(a^{r/2} + 1,\, N\right) $$

are proper divisors of $N$, computable in microseconds by Euclid's algorithm. That is the entire reduction. Factoring is not attacked directly at any point; what the quantum computer supplies is one integer, $r$.

The construction fails in exactly two ways, and both are visible in the derivation. If $r$ is odd, $a^{r/2}$ is not an integer and there is nothing to compute. If $x \equiv -1 \pmod N$, the second gcd is $N$ and the first is 1, so both divisors are trivial. Note that $x \equiv +1$ cannot happen, because it would make $r/2$ an order smaller than $r$.

Why a random base usually works

The standard theorem: if $N$ has $k \ge 2$ distinct odd prime factors and $a$ is drawn uniformly from the integers coprime to $N$, then

$$ \Pr\left[r \text{ even and } a^{r/2} \not\equiv -1 \pmod N\right] \; \ge \; 1 - \frac{1}{2^{k-1}} \; \ge \; \frac{1}{2} $$

The proof is Chinese-remainder bookkeeping on the 2-adic valuations of the orders modulo each prime power, and it is in every textbook; what matters here is the shape of the conclusion. A failure is not a wrong answer but a detected failure — $\gcd$ returns 1 or $N$, which is checked classically and immediately — so the algorithm simply draws a new base. Repetition converts a per-base probability of one half into failure probability $2^{-m}$ after $m$ bases, and $m = 30$ already makes it negligible.

Two classes of $N$ never reach the quantum circuit. Even $N$ is factored by inspection. Perfect powers $N = c^b$ are found by testing the $\lfloor \log_2 N \rfloor$ integer roots, which is why the theorem's hypothesis of two distinct prime factors is not a restriction: the excluded cases are the easy ones. There is also a free lunch on the way in: if the randomly drawn $a$ happens to satisfy $\gcd(a, N) > 1$, that gcd is already a factor and no circuit runs at all. For the tiny $N$ of this chapter that happens often enough to matter, which Example 6 quantifies and which is a caution about reading small demonstrations.

Continued fractions

The quantum subroutine will not return $r$. It returns an integer $k$ from a $t$-bit register, and with probability at least $4/\pi^2 \approx 0.405$ that $k$ is the grid point nearest to $s/r$, in which case

$$ \left\lvert \frac{k}{2^t} - \frac{s}{r} \right\rvert \le \frac{1}{2^{t+1}} $$

for some unknown $s \in \lbrace 0, 1, \ldots, r-1 \rbrace$. The bound describes that best outcome and not every measurement: a $k$ further away is perfectly possible, the continued-fraction step then returns a denominator that fails the classical check $a^{q} \equiv 1$, and the circuit is simply re-run. Exercise 2 states the bound in that conditional form. Recovering $s/r$ in lowest terms from a real number known to that accuracy is a classical problem with a classical answer. If $2^t \ge N^2$ then $1/2^{t+1} < 1/(2r^2)$ for every $r < N$, and a rational with denominator below $N$ that is that close to $k/2^t$ is unique and appears among the continued-fraction convergents of $k/2^t$. The convergents are generated by the recurrence

$$ \frac{h_i}{q_i}, \qquad h_i = c_i h_{i-1} + h_{i-2}, \qquad q_i = c_i q_{i-1} + q_{i-2} $$

where $c_i$ are the partial quotients of the Euclidean algorithm on $k$ and $2^t$; the whole computation is integer arithmetic and costs $O(n^3)$ bit operations. This is why the counting register is $t = 2n + 1$ qubits and not $n$: the extra factor of two is what makes the rational reconstruction unique.

One subtlety survives, and it is the reason the postprocessing in the code below tries more than one candidate. The convergent returns $s/r$ in lowest terms, so what comes out is $r/\gcd(s,r)$. When $\gcd(s,r) > 1$ the denominator is a proper divisor of $r$, and the classical check $a^{q} \equiv 1 \pmod N$ detects this immediately. Multiplying the candidate by 2 or 3 repairs the common cases at the cost of a few modular exponentiations; running the circuit twice and taking the least common multiple of the two denominators is the other standard remedy.

Code Example 1: The Toolbox, Re-listed

This chapter needs the state-vector simulator of Introduction to Quantum Computing, Chapter 2 and the Fourier machinery of Chapter 2 of this course. Both are reproduced here — the functions this chapter needs, verbatim; only the module docstring is adapted, because two sources are being merged into one file — so that nothing below depends on a file you have to reconstruct. Save it as shorlib.py. The convention is the one inherited from the introductory course and never changed: big-endian, qubit 0 leftmost in the ket and most significant in the amplitude index, so the integer read out of a $t$-qubit counting register is $k = \sum_j q_j 2^{t-1-j}$.

"""Chapter 3 toolbox. Save as shorlib.py and start every example with
`from shorlib import *`.

Part 1 is the state-vector simulator of Introduction to Quantum Computing,
Chapter 2 -- the functions this chapter needs, verbatim. Part 2 is the Fourier
machinery of Chapter 2 of this course, also verbatim.
"""
import numpy as np

# ---- part 1: the mini-simulator, verbatim ------------------------------------
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)


def ket(bits: str) -> np.ndarray:
    """'01' -> the 4-dimensional basis state |01> (big-endian)."""
    n = len(bits)
    psi = np.zeros(2 ** n, dtype=complex)
    psi[int(bits, 2)] = 1.0
    return psi


def apply_gate(state, U, targets, n):
    """Apply the 2^k x 2^k unitary U to the listed target qubits of an n-qubit state."""
    k = len(targets)
    psi = state.reshape([2] * n)          # 1. view as an n-index tensor
    psi = np.moveaxis(psi, targets, range(k))   # 2. bring targets to the front
    rest = psi.shape[k:]
    psi = psi.reshape(2 ** k, -1)         # 3. flatten and multiply
    psi = U @ psi
    psi = psi.reshape(list((2,) * k) + list(rest))
    psi = np.moveaxis(psi, range(k), targets)   # 4. put the axes back
    return psi.reshape(-1)


def probs(state):
    """Born-rule probabilities of all 2^n outcomes."""
    return np.abs(state) ** 2


# ---- part 2: the Fourier machinery of Chapter 2, verbatim --------------------
SWAP4 = np.array([[1, 0, 0, 0],
                  [0, 0, 1, 0],
                  [0, 1, 0, 0],
                  [0, 0, 0, 1]], dtype=complex)


def cphase(theta):
    """Controlled phase gate diag(1, 1, 1, exp(i theta)) on a qubit pair."""
    return np.diag([1.0, 1.0, 1.0, np.exp(1j * theta)]).astype(complex)


def iqft(state, qubits, n):
    """Inverse QFT: the same gates in reverse order with conjugated phases."""
    m = len(qubits)
    for j in range(m // 2):
        state = apply_gate(state, SWAP4, [qubits[j], qubits[m - 1 - j]], n)
    for j in reversed(range(m)):
        for k in reversed(range(j + 1, m)):
            state = apply_gate(state, cphase(-np.pi / 2 ** (k - j)),
                               [qubits[k], qubits[j]], n)
        state = apply_gate(state, H, [qubits[j]], n)
    return state


def controlled(U):
    """Block-diagonal controlled version of U; the control is the first qubit."""
    d = U.shape[0]
    C = np.eye(2 * d, dtype=complex)
    C[d:, d:] = U
    return C

Only four of the simulator's nine functions are needed here, and only the inverse transform of Chapter 2's pair, because order finding never applies a forward QFT. The controlled helper of Chapter 2, Example 4 does the rest of the work: it turns any $2^k \times 2^k$ unitary into a $2^{k+1} \times 2^{k+1}$ one, which apply_gate then applies to a control qubit plus $k$ targets anywhere in the register.

Code Example 2: The Classical Half

Everything in Section 3.1 above, computed. The point of tabulating every coprime base rather than sampling a few is that the failure modes then become countable, and the theorem's $1/2$ bound becomes a number you can check rather than a claim you accept.

"""Chapter 3, Example 2: the classical half of Shor's algorithm.
Continues from Example 1 (same session)."""

import numpy as np
from math import gcd


def order(a, N):
    """Smallest r > 0 with a^r = 1 (mod N), by brute force. Classical, O(N)."""
    x, r = a % N, 1
    while x != 1:
        x = (x * a) % N
        r += 1
    return r


def factors_from_order(a, r, N):
    """The last step of the reduction: gcd(a^(r/2) -+ 1, N)."""
    if r % 2 != 0:
        return None, "r is odd"
    x = pow(a, r // 2, N)
    if x == N - 1:
        return None, "a^(r/2) = -1 mod N"
    f1, f2 = gcd(x - 1, N), gcd(x + 1, N)
    for f in (f1, f2):
        if 1 < f < N:
            return (f, N // f), "ok"
    return None, "only trivial gcds"


for N in (15, 21):
    coprime = [a for a in range(2, N) if gcd(a, N) == 1]
    print(f"N = {N}: every base coprime to N, and what it yields")
    print("-" * 74)
    print(f"  {'a':>4}{'r = ord_N(a)':>14}{'r even':>8}"
          f"{'a^(r/2) mod N':>15}{'factors':>12}{'verdict':>20}")
    good = 0
    for a in coprime:
        r = order(a, N)
        fac, why = factors_from_order(a, r, N)
        if fac:
            good += 1
        half = pow(a, r // 2, N) if r % 2 == 0 else None
        print(f"  {a:>4}{r:>14}{'yes' if r % 2 == 0 else 'no':>8}"
              f"{('-' if half is None else str(half)):>15}"
              f"{('-' if fac is None else f'{fac[0]}x{fac[1]}'):>12}"
              f"{why:>20}")
    print(f"\n  {good} of {len(coprime)} bases succeed "
          f"({good/len(coprime):.3f}); the theorem guarantees at least 1/2 "
          f"for\n  an N with two distinct odd prime factors.\n")

print("The cases the quantum part never sees")
print("-" * 74)


def classical_shortcuts(N):
    """Everything Shor's algorithm dispatches classically before measuring."""
    if N % 2 == 0:
        return f"even: 2 x {N//2}"
    for b in range(2, int(np.log2(N)) + 1):
        root = round(N ** (1.0 / b))
        for cand in (root - 1, root, root + 1):
            if cand > 1 and cand ** b == N:
                return f"perfect power: {cand}^{b}"
    return None


for N in (15, 21, 16, 27, 35, 2 ** 10, 91):
    sc = classical_shortcuts(N)
    print(f"  N = {N:>5}: " + (sc if sc else "needs order finding"))
print("  A random base can also be lucky: if gcd(a, N) > 1 the factor is free.")
print(f"  For N = 15 that happens for a in "
      f"{[a for a in range(2, 15) if 1 < gcd(a, 15) < 15]}, "
      f"{len([a for a in range(2,15) if 1 < gcd(a,15) < 15])}/13 of the bases.")

print("\nContinued fractions: recovering r from a noisy s/r")
print("-" * 74)


def convergents(num, den, max_den):
    """Continued-fraction convergents of num/den with denominator <= max_den."""
    out = []
    h2, h1, k2, k1 = 0, 1, 1, 0
    n, d = num, den
    while d:
        q = n // d
        h, k = q * h1 + h2, q * k1 + k2
        if k > max_den:
            break
        out.append((h, k))
        h2, h1, k2, k1 = h1, h, k1, k
        n, d = d, n - q * d
    return out


for num, den, max_den, label in [(1365, 4096, 21, "k = 1365, t = 12, N = 21"),
                                 (683, 2048, 21, "k = 683, t = 11, N = 21"),
                                 (192, 256, 15, "k = 192, t = 8, N = 15"),
                                 (3, 8, 15, "k = 3, t = 3, N = 15")]:
    cs = convergents(num, den, max_den)
    print(f"  {label:<26} {num}/{den} = {num/den:.6f}")
    print("     convergents: " + "  ".join(f"{h}/{k}" for h, k in cs))
print("  The last convergent with denominator <= N is the candidate for r; "
      "the check\n  a^r = 1 mod N is classical and cheap, so a wrong guess "
      "costs nothing.")
N = 15: every base coprime to N, and what it yields
--------------------------------------------------------------------------
     a  r = ord_N(a)  r even  a^(r/2) mod N     factors             verdict
     2             4     yes              4         3x5                  ok
     4             2     yes              4         3x5                  ok
     7             4     yes              4         3x5                  ok
     8             4     yes              4         3x5                  ok
    11             2     yes             11         5x3                  ok
    13             4     yes              4         3x5                  ok
    14             2     yes             14           -  a^(r/2) = -1 mod N

  6 of 7 bases succeed (0.857); the theorem guarantees at least 1/2 for
  an N with two distinct odd prime factors.

N = 21: every base coprime to N, and what it yields
--------------------------------------------------------------------------
     a  r = ord_N(a)  r even  a^(r/2) mod N     factors             verdict
     2             6     yes              8         7x3                  ok
     4             3      no              -           -            r is odd
     5             6     yes             20           -  a^(r/2) = -1 mod N
     8             2     yes              8         7x3                  ok
    10             6     yes             13         3x7                  ok
    11             6     yes              8         7x3                  ok
    13             2     yes             13         3x7                  ok
    16             3      no              -           -            r is odd
    17             6     yes             20           -  a^(r/2) = -1 mod N
    19             6     yes             13         3x7                  ok
    20             2     yes             20           -  a^(r/2) = -1 mod N

  6 of 11 bases succeed (0.545); the theorem guarantees at least 1/2 for
  an N with two distinct odd prime factors.

The cases the quantum part never sees
--------------------------------------------------------------------------
  N =    15: needs order finding
  N =    21: needs order finding
  N =    16: even: 2 x 8
  N =    27: perfect power: 3^3
  N =    35: needs order finding
  N =  1024: even: 2 x 512
  N =    91: needs order finding
  A random base can also be lucky: if gcd(a, N) > 1 the factor is free.
  For N = 15 that happens for a in [3, 5, 6, 9, 10, 12], 6/13 of the bases.

Continued fractions: recovering r from a noisy s/r
--------------------------------------------------------------------------
  k = 1365, t = 12, N = 21   1365/4096 = 0.333252
     convergents: 0/1  1/3
  k = 683, t = 11, N = 21    683/2048 = 0.333496
     convergents: 0/1  1/2  1/3
  k = 192, t = 8, N = 15     192/256 = 0.750000
     convergents: 0/1  1/1  3/4
  k = 3, t = 3, N = 15       3/8 = 0.375000
     convergents: 0/1  1/2  1/3  3/8
  The last convergent with denominator <= N is the candidate for r; the check
  a^r = 1 mod N is classical and cheap, so a wrong guess costs nothing.

What to look for. For $N = 15$ six of the seven coprime bases succeed and for $N = 21$ six of eleven, both above the guaranteed $1/2$ — though both counts run over $1 < a < N$ and so exclude $a = 1$, which the theorem's sample space includes and which always fails; over $\mathbb{Z}_N^\ast$ the $N = 21$ rate is exactly $6/12$, so the bound is attained rather than beaten, as Exercise 1 works out for $N = 33$. And the single failure mode for $N = 15$ is $a = 14 \equiv -1$, whose order is 2 and whose $a^{r/2}$ is $-1$ by construction. For $N = 21$ both failure modes appear: $a = 4$ and $a = 16$ have order 3, and $a = 5, 17, 20$ hit $a^{r/2} \equiv -1$. Reading the table as a whole, every failure is a property of the base, detected by two lines of classical arithmetic, and none of them is a property of the algorithm.

The continued-fraction block previews the postprocessing. The row $k = 1365$, $t = 12$ is the one to study: $1365/4096 = 0.333252$, whose convergents stop at $1/3$, and $3$ is not the order of 2 modulo 21 — the order is 6. The true $s/r$ was $2/6$, which reduces to $1/3$, and $\gcd(s,r) = 2$ has been lost. The remedy is the one described above, and the row $k = 683$ shows the alternative path: there the convergents pass through $1/2$ and reach $1/3$, so the same doubling recovers 6.


3.2 The Order-Finding Circuit

The unitary and its eigenvalues

Define, for $\gcd(a, N) = 1$, the operator

$$ U_a \lvert y \rangle = \lvert ay \bmod N \rangle $$

on an $n$-qubit register with $n = \lceil \log_2 N \rceil$, extended by the identity on the $2^n - N$ basis states with $y \ge N$. It is unitary because multiplication by $a$ permutes the residues coprime to $N$ — indeed $U_a$ is nothing but a permutation matrix, and $U_a^r = I$.

Its eigenvectors are Fourier modes over the orbit of 1. For $s \in \lbrace 0, \ldots, r-1 \rbrace$,

$$ \lvert u_s \rangle = \frac{1}{\sqrt{r}}\sum_{j=0}^{r-1} e^{-2\pi i sj/r}\, \lvert a^j \bmod N \rangle, \qquad U_a \lvert u_s \rangle = e^{2\pi i s/r}\, \lvert u_s \rangle $$

so every eigenphase of $U_a$ is a multiple of $1/r$. Phase estimation on $U_a$ therefore returns an estimate of $s/r$, and that is the whole idea. The remaining problem is that preparing $\lvert u_s \rangle$ requires knowing $r$. The resolution is the reason the algorithm is elegant: the computational basis state $\lvert 1 \rangle$ is the uniform superposition of all of them,

$$ \lvert 1 \rangle = \frac{1}{\sqrt{r}}\sum_{s=0}^{r-1} \lvert u_s \rangle $$

so feeding $\lvert 1 \rangle$ into phase estimation returns a uniformly random $s \in \lbrace 0, \ldots, r-1 \rbrace$ — as Chapter 2's Example 4 showed, an input that is a superposition of eigenvectors returns each eigenphase with its own weight. The outcome $s = 0$ is useless and occurs with probability $1/r$; every other outcome carries information about $r$.

Where the cost is

The circuit needs controlled $U_a^{2^j}$ for $j = 0, \ldots, t-1$, and the naive reading — $2^t - 1$ applications of $U_a$ — is not how it is done. Because $a^{2^j} \bmod N$ is a classical computation, each controlled power is a single controlled modular multiplication by a precomputed constant. There are therefore $t = 2n+1$ of them, each a reversible modular multiplier costing $O(n^2)$ elementary gates with schoolbook arithmetic, for $O(n^3)$ in total. Against that, the inverse QFT on $t$ qubits costs $t(t+1)/2 = O(n^2)$.

The ratio is the point, and it is worth stating as a slogan: Shor's algorithm is a modular-exponentiation circuit with a Fourier transform stapled to the end. The Fourier transform is the part that gets the name and the part that is asymptotically free. Every serious resource estimate for factoring is an estimate of the arithmetic — which adder, which multiplier, how many ancillas, how much of it can be windowed or precomputed — and the QFT does not appear in the leading term. This is also why the approximate QFT is universally used: its error is negligible next to everything else. One implementation note that Chapter 2 has already paid for: nothing here requires the $t$-qubit counting register to be held coherently, because the iterative single-ancilla phase estimation of Section 2.3 applies to $U_a$ unchanged, replacing $t = 2n+1$ counting qubits by one ancilla and $t$ rounds of measurement and feedback. That is the standard route to the compact factoring circuits in the literature, which quote of order $2n$ qubits rather than $3n$.

Modular exponentiation Inverse QFT
What it does writes $a^k \bmod N$ into the work register reads the period out of the counting register
Count $2n+1$ controlled modular multiplications $t(t+1)/2$ rotations, $\lfloor t/2 \rfloor$ swaps
Gates $O(n^3)$ $O(n^2)$
Non-Clifford content Toffolis in the adders — the dominant cost small-angle rotations, mostly droppable
Ancillas $O(n)$ working registers none

Code Example 3: The Order-Finding Circuit

"""Chapter 3, Example 3: the order-finding circuit, and where its cost is.
Continues from Example 2 (same session)."""

import numpy as np


def modmul_unitary(a, N, n_work):
    """Permutation matrix for |y> -> |a y mod N>, the identity on y >= N.

    A permutation because multiplication by a is a bijection of Z_N when
    gcd(a, N) = 1. On real hardware this is a reversible modular multiplier
    built from adders, not a matrix.
    """
    d = 2 ** n_work
    U = np.zeros((d, d), dtype=complex)
    for y in range(d):
        U[(a * y) % N if y < N else y, y] = 1.0
    return U


def order_finding_state(a, N, t, n_work):
    """Full state after the controlled modular exponentiation and inverse QFT.

    Counting qubits 0..t-1 (qubit 0 = most significant), work register
    t..t+n_work-1 initialized to |1>. Only t controlled modular multiplications
    are needed, because a^(2^j) mod N is computed classically beforehand.
    """
    n = t + n_work
    state = np.kron(ket('0' * t), ket(format(1, f'0{n_work}b')))
    for j in range(t):
        state = apply_gate(state, H, [j], n)
    for j in range(t):
        a_pow = pow(a, 2 ** (t - 1 - j), N)
        state = apply_gate(state, controlled(modmul_unitary(a_pow, N, n_work)),
                           [j] + list(range(t, n)), n)
    return iqft(state, list(range(t)), n)


print("The modular multiplication operator is a permutation")
print("-" * 74)
for a, N, n_work in [(7, 15, 4), (2, 21, 5)]:
    U = modmul_unitary(a, N, n_work)
    r = order(a, N)
    Ur = np.linalg.matrix_power(U, r)
    print(f"  a = {a}, N = {N}, {n_work} work qubits, dimension {2**n_work}")
    print(f"    unitary: max |U^dag U - I| = "
          f"{np.max(np.abs(U.conj().T @ U - np.eye(2**n_work))):.1e}")
    print(f"    order r = {r}:  max |U^r - I| = "
          f"{np.max(np.abs(Ur - np.eye(2**n_work))):.1e}")
    orbit = [1]
    while True:
        nxt = (orbit[-1] * a) % N
        if nxt == 1:
            break
        orbit.append(nxt)
    print(f"    orbit of |1>: " + " -> ".join(str(y) for y in orbit)
          + " -> 1")
    print(f"    basis states with y >= N: {2**n_work - N} left untouched, "
          f"which is what keeps U unitary")

print("\nWhere the gates actually go")
print("-" * 74)
print("  Counting register t = 2n+1 bits, work register n = ceil(log2 N) bits.")
print(f"  {'N':>10}{'n':>6}{'t':>7}{'qubits':>9}{'ctrl mod-mults':>16}"
      f"{'~gates in them':>17}{'~QFT gates':>12}")
for N in [15, 21, 2 ** 16 + 1, 2 ** 64 + 1, 2 ** 1024 + 1, 2 ** 2048 + 1]:
    n_bits = N.bit_length()
    t = 2 * n_bits + 1
    label = f"{N}" if N < 1000 else f"~2^{n_bits-1}"
    print(f"  {label:>10}{n_bits:>6}{t:>7}{t + n_bits:>9}{t:>16}"
          f"{t * n_bits ** 2:>17d}{t * (t + 1) // 2:>12d}")
print("  The modular arithmetic outweighs the QFT by a factor ~n at every "
      "size:\n  Shor's algorithm is a modular-exponentiation circuit with a "
      "Fourier transform\n  stapled to the end, not the other way round.")

print("\nThe state just before the inverse QFT, N = 15, a = 7, t = 4")
print("-" * 74)
a, N, n_work, t = 7, 15, 4, 4
n = t + n_work
st = np.kron(ket('0' * t), ket(format(1, f'0{n_work}b')))
for j in range(t):
    st = apply_gate(st, H, [j], n)
for j in range(t):
    st = apply_gate(st, controlled(modmul_unitary(pow(a, 2 ** (t-1-j), N),
                                                 N, n_work)),
                    [j] + list(range(t, n)), n)
work_probs = probs(st).reshape(2 ** t, -1).sum(axis=0)
print("  work-register marginal (only the orbit of 1 is populated):")
print("   " + "  ".join(f"|{y}>: {work_probs[y]:.4f}"
                        for y in np.flatnonzero(work_probs > 1e-9)))
print(f"  each of the {int(round(1/work_probs.max()))} orbit states carries "
      f"probability 1/r, and the counting register\n  conditioned on any one "
      "of them is periodic with period r -- which is what the\n  inverse QFT "
      "then reads.")
The modular multiplication operator is a permutation
--------------------------------------------------------------------------
  a = 7, N = 15, 4 work qubits, dimension 16
    unitary: max |U^dag U - I| = 0.0e+00
    order r = 4:  max |U^r - I| = 0.0e+00
    orbit of |1>: 1 -> 7 -> 4 -> 13 -> 1
    basis states with y >= N: 1 left untouched, which is what keeps U unitary
  a = 2, N = 21, 5 work qubits, dimension 32
    unitary: max |U^dag U - I| = 0.0e+00
    order r = 6:  max |U^r - I| = 0.0e+00
    orbit of |1>: 1 -> 2 -> 4 -> 8 -> 16 -> 11 -> 1
    basis states with y >= N: 11 left untouched, which is what keeps U unitary

Where the gates actually go
--------------------------------------------------------------------------
  Counting register t = 2n+1 bits, work register n = ceil(log2 N) bits.
           N     n      t   qubits  ctrl mod-mults   ~gates in them  ~QFT gates
          15     4      9       13               9              144          45
          21     5     11       16              11              275          66
       ~2^16    17     35       52              35            10115         630
       ~2^64    65    131      196             131           553475        8646
     ~2^1024  1025   2051     3076            2051       2154831875     2104326
     ~2^2048  2049   4099     6148            4099      17209245699     8402950
  The modular arithmetic outweighs the QFT by a factor ~n at every size:
  Shor's algorithm is a modular-exponentiation circuit with a Fourier transform
  stapled to the end, not the other way round.

The state just before the inverse QFT, N = 15, a = 7, t = 4
--------------------------------------------------------------------------
  work-register marginal (only the orbit of 1 is populated):
   |1>: 0.2500  |4>: 0.2500  |7>: 0.2500  |13>: 0.2500
  each of the 4 orbit states carries probability 1/r, and the counting register
  conditioned on any one of them is periodic with period r -- which is what the
  inverse QFT then reads.

What to look for. $U_a$ is a permutation matrix to exact zero error, and $U_a^r = I$ exactly — an integer identity computed in floating point with no residue, because permutation matrices multiply without rounding. The orbits printed are the sequences $1, a, a^2, \ldots$ modulo $N$, of length exactly $r$, and the states outside $\lbrace 0, \ldots, N-1 \rbrace$ are fixed points, which is how the operator stays unitary on a register whose dimension is not $N$.

The accounting table is Section 3.2's claim in numbers. At $n = 2049$ bits the modular arithmetic contributes $1.7\times10^{10}$ gates and the QFT $8.4\times10^6$: a ratio of two thousand. Every discussion of "the quantum Fourier transform breaking RSA" has the emphasis in the wrong place.

The last block shows the state that phase estimation actually acts on. After the controlled modular exponentiation, the work register is supported on exactly the $r$ elements of the orbit, each with probability $1/r$, and conditioned on any one of them the counting register holds a superposition periodic with period $r$ — precisely the state of Chapter 2's Example 3. The inverse QFT then reads the period, and the offset, which is which element of the orbit was measured, is discarded exactly as Example 3 predicted.


3.3 Factoring 15 and 21, End to End

Code Example 4: $N = 15$, Where the Period Divides the Register

$N = 15$ is the traditional demonstration, and it is unrepresentatively kind: every order is 2 or 4, both divide $2^t$, and the output distribution is therefore exactly peaked with no leakage at all. That makes it the right place to check the postprocessing against a case where the answer is visible by eye.

"""Chapter 3, Example 4: factoring N = 15 end to end.
Continues from Example 3 (same session)."""

import numpy as np
import matplotlib.pyplot as plt
from math import gcd


def postprocess(k, t, a, N, max_multiple=3):
    """Turn a measured counting-register value k into (r, factors) or None.

    Continued fractions give candidate denominators for k/2^t ~ s/r. A
    convergent returns r/gcd(s, r) rather than r, so a few small multiples of
    each candidate are tried as well; that repairs the common gcd = 2 or 3 and
    is a fixed number of classical modular exponentiations, not a search. The
    convergent 0/1 is skipped because s = 0 carries no information about r.
    """
    for _, r_cand in convergents(k, 2 ** t, N):
        if r_cand < 2:
            continue
        for m in range(1, max_multiple + 1):
            r = m * r_cand
            if r <= N and pow(a, r, N) == 1:
                return r, factors_from_order(a, r, N)[0]
    return None, None


a, N, n_work, t = 7, 15, 4, 8
print(f"Order finding for a = {a}, N = {N}: t = {t} counting qubits, "
      f"{t + n_work} qubits total")
print("-" * 76)
state = order_finding_state(a, N, t, n_work)
p = probs(state).reshape(2 ** t, -1).sum(axis=1)
r_true = order(a, N)
print(f"  true order r = {r_true}, so the ideal peaks sit at "
      f"k = j * 2^t/r = j * {2**t // r_true}")
print(f"  {'k':>6}{'p(k)':>10}{'k/2^t':>10}{'CF convergents':>26}"
      f"{'r found':>9}{'factors':>10}")
for k in np.flatnonzero(p > 1e-9):
    r, fac = postprocess(int(k), t, a, N)
    cs = "  ".join(f"{h}/{q}" for h, q in convergents(int(k), 2 ** t, N))
    print(f"  {k:>6}{p[k]:>10.4f}{k/2**t:>10.4f}{cs:>26}"
          f"{('-' if r is None else r):>9}"
          f"{('-' if fac is None else f'{fac[0]}x{fac[1]}'):>10}")
print(f"  total probability on those {int((p > 1e-9).sum())} outcomes: "
      f"{p[p > 1e-9].sum():.6f}")

print("\nExact success probability, summed over the whole distribution")
print("-" * 76)
succ = sum(p[k] for k in range(2 ** t)
           if postprocess(k, t, a, N)[1] is not None)
print(f"  P(a nontrivial factor of {N} on one run with a = {a}) = {succ:.6f}")
print(f"  P(failure)  = {1 - succ:.6f}, and p(k = 0) alone is {p[0]:.6f}")

print("\nSampling the circuit, 2000 shots")
print("-" * 76)
rng = np.random.default_rng(20260813)
counts = rng.multinomial(2000, p)
found = {}
for k in np.flatnonzero(counts):
    r, fac = postprocess(int(k), t, a, N)
    key = "no factor" if fac is None else f"{fac[0]} x {fac[1]}"
    found[key] = found.get(key, 0) + int(counts[k])
    print(f"  k = {k:>3} seen {counts[k]:>5} times  ->  "
          f"r = {r if r else '-':>2}, {key}")
print("  tally: " + ",  ".join(f"{k}: {v}" for k, v in sorted(found.items())))

print("\nEvery usable base for N = 15, at t = 8")
print("-" * 76)
print(f"  {'a':>4}{'r':>4}{'peaks':>8}{'P(success)':>13}{'factors found':>16}")
for a_ in [a_ for a_ in range(2, N) if gcd(a_, N) == 1]:
    st = order_finding_state(a_, N, t, n_work)
    pp = probs(st).reshape(2 ** t, -1).sum(axis=1)
    s = 0.0
    facs = set()
    for k in range(2 ** t):
        r, fac = postprocess(k, t, a_, N)
        if fac:
            s += pp[k]
            facs.add(tuple(sorted(fac)))
    print(f"  {a_:>4}{order(a_, N):>4}{int((pp > 1e-9).sum()):>8}{s:>13.6f}"
          f"{(', '.join(f'{f[0]}x{f[1]}' for f in facs) or '-'):>16}")

fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].bar(np.arange(2 ** t), p, width=1.0, color="tab:blue")
for j in range(r_true):
    ax[0].axvline(j * 2 ** t / r_true, color="k", ls=":", lw=0.8)
ax[0].set_xlabel("measured k"); ax[0].set_ylabel("probability")
ax[0].set_title(f"N = 15, a = 7, t = 8: r = {r_true} divides $2^t$")
p14 = probs(order_finding_state(14, N, t, n_work)).reshape(2 ** t, -1).sum(axis=1)
ax[1].bar(np.arange(2 ** t), p14, width=1.0, color="tab:red")
ax[1].set_xlabel("measured k"); ax[1].set_ylabel("probability")
ax[1].set_title("a = 14: r = 2, clean peaks, no factor")
plt.tight_layout()
plt.show()
Order finding for a = 7, N = 15: t = 8 counting qubits, 12 qubits total
----------------------------------------------------------------------------
  true order r = 4, so the ideal peaks sit at k = j * 2^t/r = j * 64
       k      p(k)     k/2^t            CF convergents  r found   factors
       0    0.2500    0.0000                       0/1        -         -
      64    0.2500    0.2500                  0/1  1/4        4       3x5
     128    0.2500    0.5000                  0/1  1/2        4       3x5
     192    0.2500    0.7500             0/1  1/1  3/4        4       3x5
  total probability on those 4 outcomes: 1.000000

Exact success probability, summed over the whole distribution
----------------------------------------------------------------------------
  P(a nontrivial factor of 15 on one run with a = 7) = 0.750000
  P(failure)  = 0.250000, and p(k = 0) alone is 0.250000

Sampling the circuit, 2000 shots
----------------------------------------------------------------------------
  k =   0 seen   483 times  ->  r =  -, no factor
  k =  64 seen   530 times  ->  r =  4, 3 x 5
  k = 128 seen   505 times  ->  r =  4, 3 x 5
  k = 192 seen   482 times  ->  r =  4, 3 x 5
  tally: 3 x 5: 1517,  no factor: 483

Every usable base for N = 15, at t = 8
----------------------------------------------------------------------------
     a   r   peaks   P(success)   factors found
     2   4       4     0.750000             3x5
     4   2       2     0.500000             3x5
     7   4       4     0.750000             3x5
     8   4       4     0.750000             3x5
    11   2       2     0.500000             3x5
    13   4       4     0.750000             3x5
    14   2       2     0.000000               -

What to look for. Four outcomes, each with probability exactly $1/4$, at $k = 0, 64, 128, 192$ — the multiples of $2^t/r = 64$. Three of them yield the factorization and one, $k = 0$, does not: the useless $s = 0$ outcome, which by the argument in Section 3.2 has probability $1/r$. The exact per-run success probability is therefore $3/4$, and the 2000-shot sample returns $1517/2000 = 0.759$.

The middle rows show the continued fractions doing real work. At $k = 128$ the convergent is $1/2$, not $1/4$: the true $s/r$ was $2/4$ and the fraction reduced. The candidate 2 fails the check $7^2 \equiv 4 \pmod{15}$, the doubled candidate 4 passes, and the factors follow. That is the $\gcd(s,r) > 1$ case from Section 3.1, occurring in a quarter of all runs for this base.

The base table closes the loop with Example 2. Every base with $r = 4$ gives success probability $3/4$; every base with $r = 2$ gives $1/2$, because there are only two peaks and one of them is $k = 0$; and $a = 14$ gives exactly zero, because its order is even and its $a^{r/2}$ is $-1$, so no measured $k$ can help. The circuit is not failing there — it is returning $r = 2$ perfectly, and the reduction has no use for it.

Code Example 5: $N = 21$, Where It Does Not

Twenty-one is the smallest instance that shows the general behaviour. The order of 2 modulo 21 is 6, six does not divide any power of two, and the output distribution leaks.

"""Chapter 3, Example 5: factoring N = 21, where r does not divide 2^t.
Continues from Example 4 (same session)."""

import numpy as np
import matplotlib.pyplot as plt
from math import gcd

a, N, n_work = 2, 21, 5
r_true = order(a, N)
print(f"N = {N}, a = {a}: true order r = {r_true}, and 2^t/r is never an "
      f"integer")
print("-" * 78)
print(f"  {'t':>4}{'qubits':>8}{'peak k':>9}{'k/2^t':>10}{'s/r ideal':>11}"
      f"{'p(peak)':>10}{'P(success)':>12}{'P(k=0)':>9}")
dists = {}
for t in range(6, 14):
    st = order_finding_state(a, N, t, n_work)
    p = probs(st).reshape(2 ** t, -1).sum(axis=1)
    dists[t] = p
    k = int(np.argmax(p[1:]) + 1)
    s_over_r = round(k / 2 ** t * r_true) / r_true
    succ = sum(p[j] for j in range(2 ** t)
               if postprocess(j, t, a, N)[1] is not None)
    print(f"  {t:>4}{t + n_work:>8}{k:>9}{k/2**t:>10.6f}{s_over_r:>11.6f}"
          f"{p[k]:>10.4f}{succ:>12.6f}{p[0]:>9.4f}")

t = 11
p = dists[t]
print(f"\nThe six peaks at t = {t}, and what each one postprocesses to")
print("-" * 78)
top = np.sort(np.argsort(p)[::-1][:6])
print(f"  {'k':>6}{'p(k)':>9}{'k/2^t':>10}{'nearest s/r':>13}"
      f"{'CF convergents':>26}{'r':>4}{'factors':>10}")
for k in top:
    r, fac = postprocess(int(k), t, a, N)
    cs = "  ".join(f"{h}/{q}" for h, q in convergents(int(k), 2 ** t, N))
    s = round(k / 2 ** t * r_true)
    print(f"  {k:>6}{p[k]:>9.4f}{k/2**t:>10.6f}{f'{s}/{r_true}':>13}"
          f"{cs:>26}{('-' if r is None else r):>4}"
          f"{('-' if fac is None else f'{fac[0]}x{fac[1]}'):>10}")
print(f"  the six peaks hold {p[top].sum():.4f} of the probability; the rest "
      f"leaks into\n  the {2**t - 6} remaining bins because r = {r_true} does "
      f"not divide 2^{t} = {2**t}")

print(f"\nSampling the t = {t} circuit, 2000 shots")
print("-" * 78)
rng = np.random.default_rng(20260813)
counts = rng.multinomial(2000, p)
tally = {}
for k in np.flatnonzero(counts):
    _, fac = postprocess(int(k), t, a, N)
    key = "no factor" if fac is None else f"{fac[0]} x {fac[1]}"
    tally[key] = tally.get(key, 0) + int(counts[k])
print(f"  distinct k values observed: {int((counts > 0).sum())}")
print("  " + ",  ".join(f"{k}: {v}" for k, v in sorted(tally.items())))
print(f"  measured success rate {tally.get('7 x 3', 0)/2000:.4f} against the "
      f"exact "
      f"{sum(p[j] for j in range(2**t) if postprocess(j, t, a, N)[1]):.4f}")

print("\nAll usable bases for N = 21 at t = 11")
print("-" * 78)
print(f"  {'a':>4}{'r':>4}{'P(success)':>13}{'factors found':>16}"
      f"{'why it can fail':>26}")
for a_ in [x for x in range(2, N) if gcd(x, N) == 1]:
    st = order_finding_state(a_, N, t, n_work)
    pp = probs(st).reshape(2 ** t, -1).sum(axis=1)
    s = 0.0
    facs = set()
    for k in range(2 ** t):
        rr, fac = postprocess(k, t, a_, N)
        if fac:
            s += pp[k]
            facs.add(tuple(sorted(fac)))
    r_ = order(a_, N)
    why = ("r odd" if r_ % 2 else
           ("a^(r/2) = -1" if pow(a_, r_ // 2, N) == N - 1 else
            "only k = 0 and CF misses"))
    print(f"  {a_:>4}{r_:>4}{s:>13.6f}"
          f"{(', '.join(f'{f[0]}x{f[1]}' for f in facs) or '-'):>16}{why:>26}")

fig, ax = plt.subplots(1, 2, figsize=(11, 4))
for t_, style in [(8, "-"), (11, "-")]:
    ax[0].plot(np.arange(2 ** t_) / 2 ** t_, dists[t_], style, lw=1,
               label=f"t = {t_}")
for s in range(r_true):
    ax[0].axvline(s / r_true, color="k", ls=":", lw=0.8)
ax[0].set_xlabel("$k/2^t$"); ax[0].set_ylabel("probability")
ax[0].set_title("N = 21, a = 2: peaks near $s/6$")
ax[0].legend(fontsize=8)
ax[1].plot(list(dists), [sum(dists[t_][j] for j in range(2 ** t_)
                             if postprocess(j, t_, a, N)[1] is not None)
                         for t_ in dists], "o-", color="tab:green")
ax[1].set_xlabel("counting qubits $t$"); ax[1].set_ylabel("P(success)")
ax[1].set_ylim(0, 1)
ax[1].set_title("Success probability of one run")
plt.tight_layout()
plt.show()
N = 21, a = 2: true order r = 6, and 2^t/r is never an integer
------------------------------------------------------------------------------
     t  qubits   peak k     k/2^t  s/r ideal   p(peak)  P(success)   P(k=0)
     6      11       32  0.500000   0.500000    0.1670    0.787013   0.1670
     7      12       64  0.500000   0.500000    0.1667    0.815174   0.1667
     8      13      128  0.500000   0.500000    0.1667    0.823615   0.1667
     9      14      256  0.500000   0.500000    0.1667    0.828564   0.1667
    10      15      512  0.500000   0.500000    0.1667    0.830918   0.1667
    11      16     1024  0.500000   0.500000    0.1667    0.832118   0.1667
    12      17     2048  0.500000   0.500000    0.1667    0.832721   0.1667
    13      18     4096  0.500000   0.500000    0.1667    0.833029   0.1667

The six peaks at t = 11, and what each one postprocesses to
------------------------------------------------------------------------------
       k     p(k)     k/2^t  nearest s/r            CF convergents   r   factors
       0   0.1667  0.000000          0/6                       0/1   -         -
     341   0.1140  0.166504          1/6                  0/1  1/6   6       7x3
     683   0.1140  0.333496          2/6             0/1  1/2  1/3   6       7x3
    1024   0.1667  0.500000          3/6                  0/1  1/2   6       7x3
    1365   0.1140  0.666504          4/6        0/1  1/1  1/2  2/3   6       7x3
    1707   0.1140  0.833496          5/6             0/1  1/1  5/6   6       7x3
  the six peaks hold 0.7893 of the probability; the rest leaks into
  the 2042 remaining bins because r = 6 does not divide 2^11 = 2048

Sampling the t = 11 circuit, 2000 shots
------------------------------------------------------------------------------
  distinct k values observed: 78
  7 x 3: 1677,  no factor: 323
  measured success rate 0.8385 against the exact 0.8321

All usable bases for N = 21 at t = 11
------------------------------------------------------------------------------
     a   r   P(success)   factors found           why it can fail
     2   6     0.832118             3x7  only k = 0 and CF misses
     4   3     0.000000               -                     r odd
     5   6     0.000000               -              a^(r/2) = -1
     8   2     0.500000             3x7  only k = 0 and CF misses
    10   6     0.832118             3x7  only k = 0 and CF misses
    11   6     0.832118             3x7  only k = 0 and CF misses
    13   2     0.500000             3x7  only k = 0 and CF misses
    16   3     0.000000               -                     r odd
    17   6     0.000000               -              a^(r/2) = -1
    19   6     0.832118             3x7  only k = 0 and CF misses
    20   2     0.000000               -              a^(r/2) = -1

What to look for. The peaks sit at $k/2^t = 0.166504$ and so on: near $s/6$ but never on it, exactly as Chapter 2's Example 3 predicted for a period that does not divide the register size. Only $78.9\%$ of the probability is on the six best bins at $t = 11$; the sampled run saw 78 distinct values of $k$ rather than 6.

And yet the success probability is $0.8321$, higher than the fraction of probability sitting on the peaks. That is not a contradiction and it is the most instructive number in the chapter: outcomes in the shoulders of a peak still round to the same convergent, so continued fractions recover $r$ from them too. The postprocessing is not merely tidying up — it is what converts a broad distribution into a sharp answer. The $t$-scan shows the convergence: $0.787$ at $t = 6$ rising to $0.833$ at $t = 13$, approaching the ceiling $1 - 1/r = 5/6 = 0.8333$ set by the useless $s = 0$ outcome.

The per-base table repeats Example 2's classification, now with the circuit in the loop. The three bases with $a^{r/2} \equiv -1$ and the two with odd order give exactly zero, deterministically; the six usable bases give $0.5$ or $0.83$ depending on whether $r$ is 2 or 6. Nothing is probabilistic about which bases work — only about whether a given run of a working base lands on a useful $k$.

Code Example 6: The Complete Algorithm, and How Often It Works

Both halves together, with the classical shortcuts, the random base, the retry loop, and statistics over 600 complete runs.

"""Chapter 3, Example 6: the whole algorithm, and how often it works.
Continues from Example 5 (same session)."""

import numpy as np
from math import gcd

_CACHE = {}


def measure_k(a, N, t, n_work, rng):
    """One shot of the quantum subroutine, from the cached exact distribution."""
    key = (a, N, t, n_work)
    if key not in _CACHE:
        st = order_finding_state(a, N, t, n_work)
        _CACHE[key] = probs(st).reshape(2 ** t, -1).sum(axis=1)
    p = _CACHE[key]
    return int(rng.choice(p.size, p=p / p.sum()))


def shor(N, rng, max_rounds=10):
    """Shor's algorithm as it is actually specified, with all classical steps.

    Returns (factors, n_quantum_calls, route). A round is one random base plus
    one call to the order-finding circuit.
    """
    if N % 2 == 0:
        return tuple(sorted((2, N // 2))), 0, "even"
    for b in range(2, N.bit_length() + 1):
        root = round(N ** (1.0 / b))
        for cand in (root - 1, root, root + 1):
            if cand > 1 and cand ** b == N:
                return tuple(sorted((cand, N // cand))), 0, "perfect power"
    n_work = N.bit_length()
    t = 2 * n_work + 1
    calls = 0
    for _ in range(max_rounds):
        a = int(rng.integers(2, N))
        g = gcd(a, N)
        if g > 1:
            return tuple(sorted((g, N // g))), calls, "lucky gcd"
        calls += 1
        k = measure_k(a, N, t, n_work, rng)
        _, fac = postprocess(k, t, a, N)
        if fac:
            return tuple(sorted(fac)), calls, "order finding"
    return None, calls, "gave up"


for N in (15, 21):
    rng = np.random.default_rng(1234)
    n_runs = 600
    routes, calls_hist, results = {}, [], {}
    for _ in range(n_runs):
        fac, calls, route = shor(N, rng)
        routes[route] = routes.get(route, 0) + 1
        calls_hist.append(calls)
        key = "failed" if fac is None else f"{fac[0]} x {fac[1]}"
        results[key] = results.get(key, 0) + 1
    calls_hist = np.array(calls_hist)
    print(f"N = {N}: {n_runs} complete runs "
          f"(t = {2*N.bit_length()+1} counting qubits)")
    print("-" * 78)
    for key in sorted(results):
        print(f"  result {key:<12} {results[key]:>5} runs "
              f"({results[key]/n_runs:.4f})")
    for key in sorted(routes):
        print(f"  route  {key:<12} {routes[key]:>5} runs "
              f"({routes[key]/n_runs:.4f})")
    print(f"  quantum calls per run: mean {calls_hist.mean():.3f}, "
          f"median {int(np.median(calls_hist))}, max {calls_hist.max()}")
    hist = np.bincount(calls_hist)
    print("  calls histogram: " + "  ".join(
        f"{i}: {c}" for i, c in enumerate(hist) if c))
    print()

print("Where the failures come from, counted exactly rather than sampled")
print("-" * 78)
print(f"  {'N':>4}{'bases a':>9}{'lucky gcd':>11}{'r odd':>8}"
      f"{'a^(r/2)=-1':>12}{'usable':>8}{'mean P(succ | usable)':>23}")
for N in (15, 21):
    n_work = N.bit_length()
    t = 2 * n_work + 1
    lucky = [a for a in range(2, N) if gcd(a, N) > 1]
    cop = [a for a in range(2, N) if gcd(a, N) == 1]
    odd_r = [a for a in cop if order(a, N) % 2]
    minus1 = [a for a in cop if order(a, N) % 2 == 0
              and pow(a, order(a, N) // 2, N) == N - 1]
    usable = [a for a in cop if a not in odd_r and a not in minus1]
    ps = []
    for a in usable:
        st = order_finding_state(a, N, t, n_work)
        p = probs(st).reshape(2 ** t, -1).sum(axis=1)
        ps.append(sum(p[k] for k in range(2 ** t)
                      if postprocess(k, t, a, N)[1] is not None))
    print(f"  {N:>4}{len(cop) + len(lucky):>9}{len(lucky):>11}{len(odd_r):>8}"
          f"{len(minus1):>12}{len(usable):>8}{np.mean(ps):>23.6f}")
print("  Overall probability of success per round = "
      "P(lucky gcd) + P(usable base) * P(circuit succeeds).")
print("  Every failure is detected classically in microseconds, so the "
      "algorithm simply\n  draws a new base. Repetition turns a per-round "
      "probability of about one half\n  into a failure probability of 2^-m "
      "after m rounds, at negligible cost.")
N = 15: 600 complete runs (t = 9 counting qubits)
------------------------------------------------------------------------------
  result 3 x 5          600 runs (1.0000)
  route  lucky gcd      352 runs (0.5867)
  route  order finding   248 runs (0.4133)
  quantum calls per run: mean 0.747, median 1, max 5
  calls histogram: 0: 262  1: 254  2: 63  3: 17  4: 3  5: 1

N = 21: 600 complete runs (t = 11 counting qubits)
------------------------------------------------------------------------------
  result 3 x 7          600 runs (1.0000)
  route  lucky gcd      404 runs (0.6733)
  route  order finding   196 runs (0.3267)
  quantum calls per run: mean 0.873, median 1, max 7
  calls histogram: 0: 262  1: 215  2: 85  3: 22  4: 10  5: 4  6: 1  7: 1

Where the failures come from, counted exactly rather than sampled
------------------------------------------------------------------------------
     N  bases a  lucky gcd   r odd  a^(r/2)=-1  usable  mean P(succ | usable)
    15       13          6       0           1       6               0.666667
    21       19          8       2           3       6               0.721412
  Overall probability of success per round = P(lucky gcd) + P(usable base) * P(circuit succeeds).
  Every failure is detected classically in microseconds, so the algorithm simply
  draws a new base. Repetition turns a per-round probability of about one half
  into a failure probability of 2^-m after m rounds, at negligible cost.

What to look for. Every one of the 600 runs succeeds for both $N$, with a mean of $0.75$ and $0.87$ calls to the quantum circuit respectively and a worst case of 5 and 7. That is the retry loop working exactly as the theorem says it should.

Now the caution, and it is the reason this example exists. For $N = 15$, $59\%$ of the runs never touched the quantum circuit at all, and for $N = 21$ it is $67\%$: a randomly drawn $a$ had $\gcd(a, N) > 1$ and the factor came out of the gcd. Those two figures are route fractions over completed runs, and they are larger than the per-draw probability of a lucky gcd, which is $1 - (\phi(N)-1)/(N-2) = 6/13 = 0.4615$ for $N = 15$ and $8/19 = 0.4211$ for $N = 21$ — the base is drawn uniformly from $2 \le a < N$, so $a = 1$ is excluded from both counts. The route fractions are the larger numbers because a lucky draw always ends the run immediately, whereas a coprime draw may fail its two classical tests and force another draw. Either way the fraction goes to zero as $N$ grows, but for $N$ this small the classical shortcut is doing most of the work. Any demonstration of Shor's algorithm on a two-digit number is therefore a demonstration of the postprocessing and the circuit mechanics, and not evidence about factoring. The exact-count table at the end separates the contributions cleanly, so nothing has to be taken on trust.


3.4 What This Means, and What It Does Not

The speedup is real, and it is not exponential

The best known classical factoring algorithm, the general number field sieve, runs in

$$ \exp\left(\left(\tfrac{64}{9}\right)^{1/3} (\ln N)^{1/3} (\ln \ln N)^{2/3}\right) $$

operations — subexponential in $\ln N$, superpolynomial. Shor's is $O(n^3)$ with $n = \log_2 N$, i.e. polynomial. The gap is therefore superpolynomial rather than exponential, and the distinction is not pedantry: it means the crossover point is much larger than a naive $2^n$-versus-$n^3$ comparison suggests, and it means that no classical algorithm has to be exponentially bad for the quantum one to win.

It also means there is no proof that factoring is classically hard. Shor's algorithm shows factoring is in the quantum polynomial class; nobody has shown it is outside the classical one. A classical polynomial factoring algorithm would be a shock, but it would not contradict any theorem. The correct statement of what Shor proved is about the quantum side only.

The size, from stated assumptions

Turning $n^3$ into a machine requires three inputs, and quoting a resource estimate without them is meaningless.

Example 7 carries three explicit choices through — $p = 10^{-3}$, threshold $10^{-2}$, cycle time $1\ \mu$s — and reaches $10^7$ physical qubits and hours of runtime for $n = 2048$. Those figures are in the same range as the published estimates, which is reassuring about the arithmetic and not about the premises: every one of the three inputs is an assumption, and the published numbers have moved by orders of magnitude as the assumptions improved. Read the exponents, not the mantissas.

The two errors, side by side

Overstatement Understatement
"Quantum computers break RSA" "Shor's algorithm is a curiosity"
No device can run this circuit; the gap is $10^6$ or more in qubit count The mathematics is settled and the exponent is polynomial
Treats a 2-digit demonstration as evidence about 617-digit moduli Ignores that stored ciphertext can be decrypted later
Ignores that most of a small demonstration is classical arithmetic Ignores that the standards migration is a direct consequence

The right response to both is the same: state the circuit size, state the assumptions, and separate what is proved from what is engineered.

What survives, and why

Shor's algorithm is not a general-purpose attack. It solves the hidden subgroup problem in a finite abelian group, and factoring, discrete logarithms modulo a prime, and elliptic-curve discrete logarithms are all instances. What all three have in common is a group structure with a period to find; the QFT is the character transform of that group, and it is what makes the period measurable.

Where the hardness of a cryptosystem does not come from an abelian group, Shor has nothing to say. Symmetric ciphers and hash functions rest on unstructured search, where only Grover applies: a quadratic speedup, answered by doubling the key or digest length, which costs a few per cent of performance and no redesign. Lattice problems — the shortest and closest vector problems underlying Learning With Errors — are in neither box. There is no abelian hidden subgroup to exploit, and the best known quantum attacks improve only the constant in the exponent of the best classical ones: heuristic lattice sieving runs in about $2^{0.292n}$ classically and about $2^{0.265n}$ quantumly, an improvement a modest increase in dimension absorbs. "No known speedup" is the wrong way to say it; "no known speedup that changes the shape of the cost" is right. That, and not any timeline, is why lattice-based key exchange and signatures are the standard post-quantum replacements, and why the migration is being carried out as ordinary standards engineering rather than as a response to a demonstration.

One asymmetry makes the migration urgent independently of when a machine appears: encrypted traffic can be recorded now and decrypted later, so the confidentiality of anything with a long secrecy requirement depends on the algorithm in use today, not on the algorithm in use when a quantum computer exists. Signatures do not have this problem, since a signature only has to resist attack while it is being trusted. That distinction, rather than any prediction, is what sets the priority order of a real migration plan.

Code Example 7: The Distance to Cryptographic Size

"""Chapter 3, Example 7: the distance from N = 21 to a cryptographic modulus.
Continues from Example 6 (same session)."""

import numpy as np

# Every constant below is a stated modelling assumption, not a measurement.
P_PHYS = 1e-3          # physical two-qubit error rate assumed
P_THRESH = 1e-2        # surface-code threshold assumed
CYCLE = 1e-6           # surface-code measurement cycle assumed, seconds
TOFFOLI_COEF = 0.3     # Toffolis in modular exponentiation ~ 0.3 n^3


def gnfs_ops(n_bits):
    """Heuristic asymptotic cost of the general number field sieve."""
    lnN = n_bits * np.log(2.0)
    return np.exp((64.0 / 9.0) ** (1 / 3) * lnN ** (1 / 3)
                  * np.log(lnN) ** (2 / 3))


def surface_code_distance(n_logical_ops):
    """Smallest odd d with 0.1 (p/p_th)^((d+1)/2) < 1/(10 n_logical_ops)."""
    for d in range(3, 60, 2):
        if 0.1 * (P_PHYS / P_THRESH) ** ((d + 1) / 2) < 0.1 / n_logical_ops:
            return d
    return None


print("Circuit size against modulus size (this chapter's own accounting)")
print("-" * 78)
print(f"  {'n bits':>8}{'logical qubits':>16}{'Toffolis ~0.3n^3':>19}"
      f"{'GNFS ops':>13}{'quantum/GNFS':>15}")
for n_bits in [5, 32, 256, 1024, 2048, 4096]:
    logical = 3 * n_bits
    toff = TOFFOLI_COEF * n_bits ** 3
    g = gnfs_ops(n_bits)
    print(f"  {n_bits:>8}{logical:>16d}{toff:>19.2e}{g:>13.2e}"
          f"{toff/g:>15.2e}")
print("  The crossover is not in doubt -- polynomial beats subexponential -- "
      "but it\n  arrives at a circuit size, not at a date.")

print("\nWhat error correction does to those numbers")
print("-" * 78)
print(f"  assumptions: physical error rate {P_PHYS:.0e}, threshold "
      f"{P_THRESH:.0e}, cycle time {CYCLE:.0e} s")
print(f"  {'n bits':>8}{'Toffolis':>12}{'code distance':>15}"
      f"{'physical qubits':>18}   wall clock")
for n_bits in [(21).bit_length(), 256, 1024, 2048]:
    logical = 3 * n_bits
    toff = TOFFOLI_COEF * n_bits ** 3
    d = surface_code_distance(max(toff, 10))
    phys = 2 * logical * d ** 2
    phys_total = 2.5 * phys          # +150% for magic-state factories
    seconds = toff * d * CYCLE       # one Toffoli ~ d cycles, serialized
    unit = ("s" if seconds < 3600 else
            "hours" if seconds < 3 * 86400 else "days")
    val = (seconds if unit == "s" else
           seconds / 3600 if unit == "hours" else seconds / 86400)
    print(f"  {n_bits:>8}{toff:>12.2e}{d:>15}{phys_total:>18.2e}"
          f"{val:>10.1f} {unit}")
print("  Read only the exponents. The number of physical qubits is millions "
      "and the\n  runtime is hours to days -- both several orders of magnitude "
      "beyond anything\n  that has been built, and both sensitive to every "
      "assumption listed above.")

print("\nThe gap, stated as a ratio")
print("-" * 78)
n_demo, n_rsa = 5, 2048
print(f"  N = 21 needed {2*n_demo+1 + n_demo} simulated qubits and "
      f"{2*n_demo+1} controlled modular multiplications.")
print(f"  ratio of Toffoli counts, {n_rsa} bits to {n_demo} bits: "
      f"{(n_rsa/n_demo)**3:.2e}")
print(f"  ratio of logical qubit counts: {n_rsa/n_demo:.0f}")
print(f"  ratio of state-vector memory if you tried to simulate it: "
      f"2^{3*n_rsa} amplitudes")
print("  A demonstration on 21 is a demonstration of the postprocessing, not "
      "of the\n  cryptanalysis. Nothing about it is evidence that the "
      "cryptanalysis is near --\n  and nothing about it is evidence that the "
      "algorithm is wrong, either.")

print("\nWhat a quantum computer does and does not break")
print("-" * 78)
rows = [
    ("RSA", "factoring", "Shor, polynomial", "broken in principle"),
    ("Diffie-Hellman, DSA", "discrete log mod p", "Shor, polynomial",
     "broken in principle"),
    ("Elliptic-curve DH/DSA", "discrete log on a curve", "Shor, polynomial",
     "broken in principle; smaller circuits than RSA"),
    ("AES-128 / AES-256", "unstructured key search", "Grover, quadratic",
     "key length doubles, then safe"),
    ("SHA-2 / SHA-3 preimage", "unstructured search", "Grover, quadratic",
     "output length doubles, then safe"),
    ("Lattice problems (LWE)", "shortest/closest vector", "sieving: 2^0.265n",
     "the basis of the standard replacements"),
    ("Hash-based signatures", "preimage resistance", "Grover, quadratic",
     "parameter bump, then safe"),
]
print(f"  {'primitive':<24}{'hard problem':<26}{'quantum attack':<22}"
      f"status")
for a, b, c, d_ in rows:
    print(f"  {a:<24}{b:<26}{c:<22}{d_}")
print("\n  Two rows need their fine print. Collision resistance is NOT "
      "quadratically\n  sped up: the classical birthday attack already costs "
      "2^(n/2), and the best\n  known quantum collision finder (BHT) reaches "
      "2^(n/3) while needing 2^(n/3)\n  quantum memory, so hash-based "
      "signature parameters are set by preimage and\n  second-preimage "
      "resistance, where Grover does apply. And lattices are not "
      "'no\n  known speedup': heuristic sieving improves from 2^(0.292n) "
      "classically to about\n  2^(0.265n) quantumly. That is a change in the "
      "exponent's constant, absorbed by a\n  modest increase in dimension, "
      "not the polynomial-versus-exponential change Shor\n  gives for "
      "factoring.")
print("\n  The pattern: Shor needs an abelian group with a hidden period. "
      "Where the\n  hardness is unstructured search instead, only Grover "
      "applies, and a quadratic\n  speedup is answered by doubling a key "
      "length. Lattice problems fall in neither\n  box, which is why "
      "lattice-based key exchange and signatures are the standard\n  "
      "post-quantum replacements -- a migration that is engineering, "
      "already specified,\n  and independent of when or whether a "
      "cryptographically useful quantum computer\n  is built.")
Circuit size against modulus size (this chapter's own accounting)
------------------------------------------------------------------------------
    n bits  logical qubits   Toffolis ~0.3n^3     GNFS ops   quantum/GNFS
         5              15           3.75e+01     2.89e+01       1.30e+00
        32              96           9.83e+03     9.73e+04       1.01e-01
       256             768           5.03e+06     1.12e+14       4.51e-08
      1024            3072           3.22e+08     1.32e+26       2.45e-18
      2048            6144           2.58e+09     1.53e+35       1.68e-26
      4096           12288           2.06e+10     1.29e+47       1.60e-37
  The crossover is not in doubt -- polynomial beats subexponential -- but it
  arrives at a circuit size, not at a date.

What error correction does to those numbers
------------------------------------------------------------------------------
  assumptions: physical error rate 1e-03, threshold 1e-02, cycle time 1e-06 s
    n bits    Toffolis  code distance   physical qubits   wall clock
         5    3.75e+01              3          6.75e+02       0.0 s
       256    5.03e+06             13          6.49e+05      65.4 s
      1024    3.22e+08             17          4.44e+06       1.5 hours
      2048    2.58e+09             19          1.11e+07      13.6 hours
  Read only the exponents. The number of physical qubits is millions and the
  runtime is hours to days -- both several orders of magnitude beyond anything
  that has been built, and both sensitive to every assumption listed above.

The gap, stated as a ratio
------------------------------------------------------------------------------
  N = 21 needed 16 simulated qubits and 11 controlled modular multiplications.
  ratio of Toffoli counts, 2048 bits to 5 bits: 6.87e+07
  ratio of logical qubit counts: 410
  ratio of state-vector memory if you tried to simulate it: 2^6144 amplitudes
  A demonstration on 21 is a demonstration of the postprocessing, not of the
  cryptanalysis. Nothing about it is evidence that the cryptanalysis is near --
  and nothing about it is evidence that the algorithm is wrong, either.

What a quantum computer does and does not break
------------------------------------------------------------------------------
  primitive               hard problem              quantum attack        status
  RSA                     factoring                 Shor, polynomial      broken in principle
  Diffie-Hellman, DSA     discrete log mod p        Shor, polynomial      broken in principle
  Elliptic-curve DH/DSA   discrete log on a curve   Shor, polynomial      broken in principle; smaller circuits than RSA
  AES-128 / AES-256       unstructured key search   Grover, quadratic     key length doubles, then safe
  SHA-2 / SHA-3 preimage  unstructured search       Grover, quadratic     output length doubles, then safe
  Lattice problems (LWE)  shortest/closest vector   sieving: 2^0.265n     the basis of the standard replacements
  Hash-based signatures   preimage resistance       Grover, quadratic     parameter bump, then safe

  Two rows need their fine print. Collision resistance is NOT quadratically
  sped up: the classical birthday attack already costs 2^(n/2), and the best
  known quantum collision finder (BHT) reaches 2^(n/3) while needing 2^(n/3)
  quantum memory, so hash-based signature parameters are set by preimage and
  second-preimage resistance, where Grover does apply. And lattices are not 'no
  known speedup': heuristic sieving improves from 2^(0.292n) classically to about
  2^(0.265n) quantumly. That is a change in the exponent's constant, absorbed by a
  modest increase in dimension, not the polynomial-versus-exponential change Shor
  gives for factoring.

  The pattern: Shor needs an abelian group with a hidden period. Where the
  hardness is unstructured search instead, only Grover applies, and a quadratic
  speedup is answered by doubling a key length. Lattice problems fall in neither
  box, which is why lattice-based key exchange and signatures are the standard
  post-quantum replacements -- a migration that is engineering, already specified,
  and independent of when or whether a cryptographically useful quantum computer
  is built.

What to look for. The first table shows the shape of the two costs, and its last column should not be read as locating a crossover. The quantum side carries an optimised constant — $0.3n^3$ Toffolis, the product of a decade of arithmetic-circuit work — while the classical side is the bare asymptotic form of the sieve with its constant set to 1, and the sieve's true constant is not 1. What the table does establish is that the ratio falls by twenty-five orders of magnitude between 32 and 2048 bits: one function is polynomial and the other is subexponential, and no choice of constants changes that. Where the two curves actually cross is a question about constants that neither column pins down, and it says nothing about when either side can be run.

The second table is what error correction does. Reaching $2.6\times10^9$ Toffolis at a physical error rate of $10^{-3}$ needs code distance 19, hence $10^7$ physical qubits and about half a day of runtime on the stated assumptions. Every number in that row is a consequence of the four constants declared at the top of the file, and changing the physical error rate by one decade moves $d$ by roughly a factor of two — 19 at $p = 10^{-3}$, 9 at $p = 10^{-4}$ — and therefore the physical qubit count, which goes as $d^2$, by roughly a factor of 4.5. This is the sense in which "how far away is it" is a materials and engineering question rather than an algorithms question: the algorithm has been finished for decades.

The third block states the gap as ratios, and the fourth is the summary a reader should leave with. Shor breaks the three primitives whose hardness is an abelian hidden subgroup problem. Grover halves the effective key length of everything whose hardness is unstructured search, which is repaired by doubling a parameter. Lattice problems are attacked by neither mechanism at more than an exponent-shaving level — quantum sieving $2^{0.265n}$ against classical $2^{0.292n}$ — which is why they are the replacement. None of those three statements depends on a date.


Exercises

Exercise 1: The Reduction by Hand

Take $N = 33$ and $a = 5$.

  1. Compute the order $r$ of 5 modulo 33 by hand or by repeated multiplication.
  2. Apply the reduction: is $r$ even, what is $a^{r/2} \bmod N$, and what do the two gcds give?
  3. Repeat for $a = 10$ and for $a = 32$. Classify each outcome by the two failure modes of Section 3.1.
  4. $33 = 3 \times 11$ has $k = 2$ distinct odd prime factors, so the theorem promises a success probability of at least $1 - 2^{-1} = 1/2$. Count the coprime bases and the successful ones, and compare.
Solution

1. \(5^1 = 5\), \(5^2 = 25\), \(5^3 = 125 = 26\), \(5^4 = 130 = 31\), \(5^5 = 155 = 23\), \(5^6 = 115 = 16\), \(5^7 = 80 = 14\), \(5^8 = 70 = 4\), \(5^9 = 20\), \(5^{10} = 100 = 1\). So \(r = 10\).

2. \(r\) is even; \(5^5 = 23 \bmod 33\), which is neither 1 nor 32. \(\gcd(22, 33) = 11\) and \(\gcd(24, 33) = 3\): both nontrivial, and \(33 = 3 \times 11\).

3. \(a = 10\): \(10^2 = 100 = 1\), so \(r = 2\) and \(10^1 = 10\); \(\gcd(9,33) = 3\), \(\gcd(11,33) = 11\) — success. \(a = 32 \equiv -1\): \(r = 2\) and \(a^{r/2} = 32 \equiv -1\), the second failure mode, both gcds trivial. Neither of these two exhibits the first failure mode, odd \(r\); for \(N = 33\) that mode first appears at \(a = 4\), whose order is 5, and part 4 counts every instance of both.

4. There are \(\phi(33) = 20\) coprime residues, of which 19 lie in \(1 < a < 33\). Enumerating them, ten succeed and nine fail, and both failure modes are represented. The ten successes are \(a = 5, 7, 10, 13, 14, 19, 20, 23, 26, 28\), of order 10 except for \(a = 10\) and \(a = 23\), which have order 2. Five fail with \(a^{r/2} \equiv -1\): \(a = 2, 8, 17, 29\) at order 10 and \(a = 32\) at order 2. Four fail with odd order: \(a = 4, 16, 25, 31\), all of order \(r = 5\).

So the measured rate over \(1 < a < 33\) is \(10/19 = 0.526\). The theorem's own sample space is \(a\) drawn uniformly from the integers coprime to \(N\), which includes \(a = 1\); \(a = 1\) has order 1, which is odd, so it fails, and the rate over \(\mathbb{Z}_{33}^{\ast}\) is exactly \(10/20 = 0.5000\). The bound is tight here, not conservative — and the same is true of \(N = 21\), where the count is \(6/12 = 0.5000\). For \(k = 2\) the theorem promises \(\ge 1 - 2^{-1}\) and these two moduli attain it exactly; the slack people expect from "at least a half" is a property of moduli with more prime factors, not of this bound. The systematic way to check is to reuse Code Example 2 with \(N = 33\), which requires no changes to the code.

Exercise 2: Why $t = 2n + 1$

The counting register is twice as long as the work register plus one bit.

  1. Show that if $2^t \ge N^2$ then $1/2^{t+1} < 1/(2r^2)$ for every $r < N$.
  2. The continued-fraction uniqueness theorem says that if $\lvert x - s/r \rvert < 1/(2r^2)$ with $\gcd(s,r) = 1$ then $s/r$ is a convergent of $x$. Combine this with part 1 to justify $t = 2n+1$.
  3. What goes wrong if $t = n$? Estimate the probability that two different fractions with denominators below $N$ lie within $2^{-n-1}$ of each other.
  4. Code Example 5 succeeds at $t = 6$ for $N = 21$, where the theorem asks for $t = 11$. Why is that not a contradiction?
Solution

1. \(2^t \ge N^2 > r^2\) for \(r < N\), so \(1/2^{t+1} \le 1/(2N^2) < 1/(2r^2)\).

2. Phase estimation guarantees \(\lvert k/2^t - s/r \rvert \le 2^{-(t+1)}\) for the best outcome, which by part 1 is below \(1/(2r^2)\); the theorem then puts \(s/r\) among the convergents of \(k/2^t\), which the Euclidean algorithm enumerates in \(O(n)\) steps. \(t = 2n+1\) is the smallest register that guarantees \(2^t \ge N^2\) for all \(n\)-bit \(N\).

3. With \(t = n\) the guarantee is only \(\lvert k/2^n - s/r\rvert \le 2^{-(n+1)} \approx 1/(2N)\), and there are \(\Theta(N^2)\) fractions with denominator below \(N\) in \([0,1)\), so the typical spacing is \(\Theta(1/N^2)\) — far smaller than the error bar. Many candidate fractions fit, and the reconstruction is not unique. The algorithm still works sometimes, which is exactly part 4.

4. The theorem is a worst-case guarantee over all \(r < N\). For a specific small \(r\) — here \(r = 6\) — the fractions \(s/6\) are widely separated, so a much coarser register resolves them. \(t = 2n+1\) is what you must choose when \(r\) is unknown and could be as large as \(N\); it is not what a particular instance needs.

Exercise 3: Reading the $N = 21$ Distribution

Use the $t = 11$ output of Code Example 5.

  1. The six peaks hold $0.7893$ of the probability but the success probability is $0.8321$. Where does the extra $0.043$ come from?
  2. The ceiling as $t \to \infty$ is $5/6$. Derive it.
  3. For $a = 8$ (order 2) the success probability is exactly $0.5$ at every $t$. Explain why it is exactly one half and not approximately.
  4. Suppose you may run the circuit twice and combine the results. Describe a postprocessing rule that raises the per-experiment success probability above $5/6$, and estimate the new value for $a = 2$, $N = 21$.
Solution

1. From the shoulders. An outcome \(k\) one or two bins away from a peak still has \(k/2^t\) closer to the same \(s/r\) than to any other fraction with denominator below 21, so its continued-fraction expansion returns the same convergent. The postprocessing has a capture radius wider than one bin, and that radius is what the extra probability lives in.

2. The input \(\lvert 1 \rangle\) is the uniform superposition of the \(r\) eigenvectors \(\lvert u_s \rangle\), so \(s\) is uniform on \(\lbrace 0,\ldots,r-1\rbrace\). The outcome \(s = 0\) puts the peak at \(k = 0\) and yields no information about \(r\), so the ceiling is \(1 - 1/r = 5/6\) for \(r = 6\). Nothing about \(t\) changes this.

3. With \(r = 2\) the eigenphases are \(0\) and \(1/2\), both exactly representable in any \(t \ge 1\) bits, so the distribution is exactly two delta peaks at \(k = 0\) and \(k = 2^{t-1}\), each with probability exactly \(1/2\). There is no leakage and therefore no \(t\) dependence — and the ceiling \(1 - 1/r\) is attained.

4. Take the two measured denominators \(q_1, q_2\) and test \(\mathrm{lcm}(q_1, q_2)\) as well as each separately. Failure now requires both runs to be uninformative, i.e. both to give \(s = 0\), or the lcm to still miss \(r\). With \(P(s = 0) = 1/6\) per run the probability of both failing that way is \(1/36\), giving a success probability of about \(0.97\) for two runs — at twice the circuit cost. This is the standard remedy and it is why "the algorithm succeeds with probability at least a half" is not a practical limitation.

Exercise 4: The Cost Is the Arithmetic

Use the scaling model of Code Example 3.

  1. For $n = 1025$ — the $\sim 2^{1024}$ row of Code Example 3, where $t = 2n+1 = 2051$ — compute the ratio of modular-exponentiation gates to inverse-QFT gates under the $n^3$ versus $t^2/2$ model, and state how it scales with $n$.
  2. Suppose someone improves the QFT by a factor of 100. By how much does the total circuit shrink at $n = 1024$?
  3. Suppose instead someone improves the modular multiplier from $n^2$ to $n^{1.6}$ gates (Karatsuba-style). By how much does the total shrink?
  4. What does this imply about which part of a factoring circuit deserves optimization effort, and about how to read a paper title containing "efficient QFT"?
Solution

1. Arithmetic \(\approx t n^2 = 2051 \times 1025^2 = 2.15\times10^9\); QFT \(\approx t(t+1)/2 = 2.1\times10^6\). Ratio \(\approx 1020\), and it grows as \(n^3/n^2 = n\), so the disparity widens with size.

2. The QFT falls from \(2.1\times10^6\) to \(2.1\times10^4\), changing the total from \(2.152\times10^9\) to \(2.150\times10^9\) — a saving of one part in a thousand.

3. \(t n^{1.6} = 2051 \times 1025^{1.6} = 2051 \times 6.56\times10^4 = 1.35\times10^8\), a factor of 16 off the total. Two orders of magnitude of QFT improvement is worth less than a modest improvement in the multiplier.

4. All the effort belongs in the arithmetic — which is where it has in fact gone, through windowed arithmetic, better adders and Toffoli-count reduction. A paper about a more efficient QFT is a paper about the negligible term of a factoring circuit; it may matter for other algorithms, where the QFT is not accompanied by \(n^3\) of arithmetic, but not for this one.

Exercise 5: Auditing a Claim

A press release states: "Our processor has factored a 48-bit number using Shor's algorithm, a record. Extrapolating, RSA-2048 is within reach."

  1. Using Code Example 3's model, what circuit size does a genuine 48-bit factorization require, in qubits and in modular multiplications?
  2. Name three ways a demonstration can produce the right factors without running that circuit, and say what published detail would let you rule each one out.
  3. Take the $n^3$ scaling at face value: what is the ratio of circuit sizes between 48 bits and 2048 bits, and what does "within reach" have to mean for the extrapolation to hold?
  4. Write the two sentences you would want the release to contain instead.
Solution

1. \(n = 48\), \(t = 97\), so \(t + n = 145\) logical qubits, 97 controlled modular multiplications, and \(\sim t n^2 = 2.2\times10^5\) gates in the arithmetic. With error correction at the assumptions of Example 7 that is a few thousand physical qubits at minimum, and every gate must succeed coherently.

2. (i) The base \(a\) was chosen so that \(\gcd(a,N) > 1\) or so that \(r\) is tiny and known, which collapses the circuit — ruled out by publishing the base, the order, and the full measured distribution. (ii) The modular exponentiation was compiled using the known factorization, which is the standard criticism of small Shor demonstrations — ruled out by publishing a circuit that depends only on \(N\) and \(a\). (iii) The postprocessing did the work: with \(N\) small, testing a few candidate \(r\) classically finds the factors without any quantum information — ruled out by reporting the per-run success probability against the theoretical value, as Code Examples 4 and 5 do.

3. \((2048/48)^3 = 7.8\times10^4\) in gate count and \(2048/48 = 43\) in qubit count, before error correction; after it, the physical-qubit ratio is larger still because the code distance grows with the circuit size. "Within reach" would have to mean that five orders of magnitude in coherent gate count and four in physical qubits are incremental, which no reading of the hardware supports.

4. Something like: "We factored a 48-bit integer with a circuit that uses only \(N\) and the randomly chosen base as input, and the measured per-run success probability agrees with the theoretical \(1 - 1/r\) to within sampling error." And: "Scaling the same circuit to 2048 bits requires \(\sim 10^9\) Toffoli gates and, under standard surface-code assumptions, \(\sim 10^7\) physical qubits; this demonstration is not evidence about that regime." The first sentence is what makes the result checkable; the second is what makes it honest.


Summary

Key Takeaways

1. Factoring reduces to order finding, classically

2. Order finding is phase estimation on a permutation

3. The Fourier transform is the cheap part

4. Both integers factor, and the statistics match theory

5. The cryptographic conclusion, in both directions

Practical implications

Where This Leads

Chapter 3 is the high-water mark of provable quantum advantage in this course, and it is worth noticing what made it possible: a problem with a hidden group structure, an exact periodicity, and a classical postprocessing step that turns one measurement into an answer. Chapter 4 turns to the application with the strongest physical motivation and no such structure — simulating a Hamiltonian — where the object of study is $e^{-iHt}$ itself. The techniques there (block encoding, qubitization, randomized compilation) are the modern replacements for the Trotter decomposition of the introductory course, and they are also what supplies the controlled $e^{-iH\tau}$ that Chapter 2's phase estimation assumed for free.

← Chapter 2: QFT and Phase Estimation Chapter 4: Modern Hamiltonian Simulation →

Disclaimer