48 min read

> *"Every other framework in this book will let you write a program that is wrong. Q# argues with you

Prerequisites

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 9
  • 11
  • 13

Learning Objectives

  • Write, compile, and run Q# operations from Python.
  • Distinguish `function` from `operation` and explain why the compiler enforces it.
  • Use `use` blocks and satisfy the qubit release discipline.
  • Declare `is Adj + Ctl` and get inverse and controlled variants for free.
  • Run the resource estimator and read its output.
  • Explain why T gates dominate the cost of fault-tolerant computation.

Chapter 15: Microsoft Q

"Every other framework in this book will let you write a program that is wrong. Q# argues with you first, and then tells you it would need twelve thousand qubits."

Overview

Qiskit and Cirq are Python libraries. Q# is a language — with a type system, a compiler, and opinions. That difference produces two things nothing else in this book has.

The compiler rejects programs that are wrong. Not wrong at runtime; wrong at compile time. A classical function that tries to allocate a qubit, an operation used in reverse without declaring it reversible, a value assigned to an immutable binding — all refused before anything runs. Several of the bugs this book has spent chapters diagnosing are, in Q#, syntax errors.

And the resource estimator tells you what a circuit would actually cost. Not on today's hardware — on a fault-tolerant machine, in physical qubits and wall-clock time, with error correction included.

That second capability produces the chapter's headline. Take a two-qubit circuit — H, CNOT, and a handful of T gates — and ask what it costs fault-tolerantly:

   T gates   physical qubits   for the algorithm   for T factories    % T factories
         0               450                 450                 0             0.0%
         1             2,882                 882             2,000            69.4%
         3            12,642                 882            11,760            93.0%
        10            61,642                 882            60,760            98.6%

Zero T gates: 450 physical qubits. One T gate: 2,882. A single gate, and the machine gets 6.4 times bigger.

That cliff is Chapter 11's Gottesman–Knill theorem presenting its invoice. §11.4 established that Clifford circuits are classically simulable in polynomial time — that T gates are what make a quantum computer worth building. Here the same boundary appears as a hardware requisition, and by ten T gates 98.6% of the machine is not running your algorithm at all. It is manufacturing the magic states that make T gates possible.

In this chapter, you will learn to:

  • Run Q# from Python and read compiler errors that are worth reading.
  • Use function vs operation, and see the compiler enforce the distinction.
  • Satisfy the qubit release discipline.
  • Get Adjoint and Controlled for free.
  • Run the resource estimator and interpret its breakdown.
  • Explain why T gates dominate fault-tolerant cost.

Learning Paths

How to read this chapter by track. - 🔰 Beginner — §15.2, §15.4, §15.5. The compiler errors teach more than the syntax. - 🔬 Researcher — §15.7 and §15.8. Resource estimation is how claims about quantum advantage get checked. - 🤖 Quantum ML — skim to §15.7; Q# is not built for your loop, but the T-count argument applies to your circuits too. - 🏗️ Quantum Engineer — all of it. §15.3 and §15.6 are the language design worth stealing. - 🔐 Security — §15.8 directly; it is the honest answer to "when can quantum computers break RSA," and Chapter 23 builds on it.


15.1 Setup

Q# runs from Python through the QDK package.

from qdk import qsharp

qsharp.init()

🗝️ Version Note — the package moved.

The old package was qsharp, installed with pip install qsharp. Importing it now warns:

text DeprecationWarning: The 'qsharp' package is deprecated and will be removed in a future release. Please use the 'qdk' package instead.

The replacement is pip install qdk, and the migration is:

python import qsharp # old from qdk import qsharp # current

Note that import qdk alone is not enough — the top-level qdk module does not re-export eval, run, or estimate. Those live in the qdk.qsharp submodule, which is why the import above takes the form it does. qdk is an umbrella package that also carries qdk.qiskit, qdk.cirq, qdk.openqasm, and qdk.azure.

Verified with qdk 1.31.0. Both packages report the same version number, which is a strong hint that qsharp is now a thin alias.

15.2 The First Program

operation BellPair() : (Result, Result) {
    use (a, b) = (Qubit(), Qubit());
    H(a);
    CNOT(a, b);
    let r = (M(a), M(b));
    ResetAll([a, b]);
    return r;
}
qsharp.eval(bell_source)
results = qsharp.run("BellPair()", shots=1000)
  Counter({'(One, One)': 514, '(Zero, Zero)': 486})

Six things in nine lines, and all of them are language design.

operation declares something that may touch qubits. Its return type (Result, Result) is declared, not inferred.

use (a, b) = (Qubit(), Qubit()) allocates qubits for the enclosing scope. There is no global register and no qubit indices — qubits are values with lifetimes, borrowed from a pool and returned when the block ends.

H(a) and CNOT(a, b) look like every other framework, which is the point: the physics does not change.

let binds an immutable value. Rebinding it is a compile error (§15.4). Mutable bindings require mutable and set, and the distinction is enforced.

M(a) returns a Result — a distinct type with values Zero and One. Not a boolean, not an integer. You cannot accidentally do arithmetic on a measurement outcome.

ResetAll returns the qubits to $|0\rangle$. This is not politeness; §15.5 shows what happens without it.

Qubits as values with lifetimes

The line worth staring at is use (a, b) = (Qubit(), Qubit()), because it is the one design decision from which most of the rest of Q# follows.

Every other framework in this book models a quantum program as a circuit over a fixed register. You build a QuantumCircuit(5), and qubit 3 means "index 3 of that register" for the whole life of the object. Indices are integers, integers are interchangeable, and nothing stops you from writing qc.h(3) in a helper that had no business touching qubit 3.

Q# models a quantum program as a scope in which qubits exist. Qubit() is not an index; it is an opaque value with no observable identity, obtained from an allocator and returned at the closing brace. There is no qubits[3], because there is no array to index into unless you make one.

Three consequences fall straight out of that:

Aliasing is impossible by construction. Two use blocks cannot name the same qubit, because neither one names a qubit at all — each holds a value the runtime handed it. Chapter 8's habit of passing qubit indices between functions, and Chapter 12's dead-qubit selection, are both problems about which index refers to what. In Q# the question does not arise at the source level.

The allocator can move things. Because your program never observes a qubit's identity, the runtime is free to map the same Qubit() value onto different physical qubits on different runs, or to reuse one after its scope closes. That is exactly the freedom Chapter 10's transpiler has to fight for in Qiskit, where the layout has to be reconciled against a circuit that already committed to indices.

And a qubit's state at scope exit becomes checkable, because there is a well-defined moment at which the value stops being yours. §15.5 is that check, and it exists only because use is a block.

🔀 In Another Framework — the same nine lines, four ways.

The Bell pair above is nine lines in every framework in this book. What differs is entirely in how the qubits come into existence.

text Qiskit QuantumCircuit(2) qubits are INDICES into a fixed register Cirq cirq.LineQubit.range(2) qubits are VALUES, but global and reusable PennyLane qml.device(wires=2) qubits are WIRE LABELS on a device Q# use (a, b) = (Qubit(), Qubit()) qubits are SCOPED, allocated values

Cirq is the interesting middle case. Chapter 14 showed that cirq.LineQubit(0) is a value rather than an index, which already removes a class of confusion — two references to LineQubit(0) are the same qubit because they are the same object, not because they happen to hold the same integer. But a LineQubit has no lifetime. You can construct one anywhere, use it in two unrelated circuits, and nothing objects.

Q# is Cirq's value semantics plus a lifetime. That single addition is what makes the release discipline expressible, and it is the reason §15.5 has no analogue in the other four frameworks: they have nowhere to put the check.

None of this is a correctness claim about the physics. All four produce the same Bell pair. It is a claim about which mistakes each one lets you write down.

15.3 function versus operation

Q#'s central type distinction, and the one worth stealing.

function operation
May allocate qubits no yes
May call quantum operations no yes
Deterministic yes no
Purpose classical computation quantum computation

A function is pure classical code. It cannot touch a qubit, and the compiler enforces it:

function BadFunction() : Unit {
    use q = Qubit();      // <- rejected
    H(q);
}
  Qdk.Qsc.CallableLimits.QubitAlloc
    x functions cannot allocate qubits

This is a genuinely useful guarantee. A hybrid quantum program is mostly classical code, and being able to see at a glance which parts can possibly have quantum side effects makes the program far easier to reason about. Chapter 24's variational loop is a function computing a cost from measurement results, calling an operation that produces them — and in Q# that structure is visible in the declarations rather than a convention you hope everyone follows.

Why this particular guarantee is cheap to check

function versus operation is an effect system, and it is the smallest useful one: a single bit of effect, "may touch a qubit," propagated up the call graph.

The checking rule is one line. A function may call only functions. Since use and every quantum primitive live in operation, the property is closed under calls: if the body of a function contains no direct qubit allocation and every callee is also a function, then no execution of it can allocate a qubit. The compiler does not need to reason about what your code means, only about which names it mentions.

That is why the error is Qdk.Qsc.CallableLimits.QubitAlloc and not something about types — it comes from a reachability check on the call graph, and it is decidable in one pass.

The dividend is not the error message; it is the reading. Chapter 26's whole debugging strategy rests on being able to say "this part is classical, so I can test it classically." In Python that sentence is a claim about a codebase you have to verify by inspection every time it changes. In Q# it is a declaration the build enforces, and the enforcement survives every refactor by someone who has never read Chapter 26.

The limit is equally sharp, and worth stating alongside it. The effect system tracks whether quantum operations happen, not whether the right ones happen. A function that computes the wrong cost from correct measurement results is well-typed, well-effected, and wrong — which is Case Study 2's whole finding in miniature.

⚙️ Under the Transpiler — what a Q# program actually becomes.

The chapter's own release error, in §15.5, leaks the answer:

text at QIR.Runtime.__quantum__rt__qubit_release

Q# lowers to QIR — Quantum Intermediate Representation, an LLVM-based IR with a small runtime library of __quantum__rt__ and __quantum__qis__ functions. use is not a syntactic construct that vanishes at compile time; it becomes a call to __quantum__rt__qubit_allocate, matched by a call to __quantum__rt__qubit_release at the closing brace.

Three things follow from that, and they explain several of this chapter's observations:

The release check is a runtime check, not a type check. It is a branch inside __quantum__rt__qubit_release, which is why §15.5's failure arrives with a call stack rather than a compile error, and why the message names a runtime symbol rather than a source construct.

function/operation does not survive to QIR. It is enforced entirely in the front end; the IR has no notion of purity. The guarantee is a property of the Q# compiler, not of the artefact it produces — so a QIR module assembled from somewhere else carries none of it.

And the compile target is framework-neutral by design. A QIR module is a program representation, not a Q# program, which is consistent with qdk shipping qdk.qiskit, qdk.cirq, and qdk.openqasm alongside qdk.qsharp (§15.1). It is also what makes §15.9's recommendation — take the estimator without the language — more than a slogan: the estimator's input is a set of logical counts, and Q# is only one of the things that can produce them.

15.4 What the Compiler Refuses

Six programs, each of which a Python framework would accept or fail on only at runtime:

  int + double                   Qdk.Qsc.TypeCk.TyMismatch
                                   expected Int, found Double
  missing return                 Qdk.Qsc.TypeCk.TyMismatch
                                   expected Unit, found Int
  quantum op inside a function   Qdk.Qsc.CallableLimits.QubitAlloc
                                   functions cannot allocate qubits
  swapped argument types         Qdk.Qsc.TypeCk.TyMismatch
                                   expected Double, found Qubit
  undefined variable             Qdk.Qsc.Resolve.NotFound
                                   `y` not found
  reassign an immutable `let`    Qdk.Qsc.BorrowCk.Mutability
                                   cannot update immutable variable

No implicit numeric conversion. 1 + 2.0 is a type error. Python computes 3.0 and moves on; in numerical quantum code, silent int-to-float promotion is a genuine source of wrong angles.

Argument order is checked. Rx(q, 1.0) — qubit first, angle second — is rejected because Rx takes (Double, Qubit). In Python this is a runtime TypeError at best and a confusing failure deep in a call stack at worst.

🔬 Honest Assessment — how much does static typing actually buy here?

Not everything. The type system cannot know your algorithm is wrong, cannot check that your oracle marks the right state, and cannot catch Chapter 14's endianness bug — Q# has its own convention and a mistranslation into it is perfectly well-typed.

What it does catch is a specific and unglamorous class: argument order, numeric type confusion, undeclared reversibility, and mutation of things you said were constant. In Python those become runtime errors, which is fine when the code path runs in a test and expensive when it runs after forty minutes in a queue.

The honest summary: Q# eliminates a category of bug rather than a category of confusion. That is worth real money on large codebases and worth much less on a fifty-line script. Judge it the way you would judge static typing anywhere else, because it is the same trade.

15.5 The Qubit Release Discipline

The feature nothing else in this book has.

operation LeakQubit() : Unit {
    use q = Qubit();
    X(q);                 // left in |1>, never reset
}
  Error: Qubit0 released while not in |0⟩ state
  Qdk.Qsc.Eval.ReleasedQubitNotZero
    at QIR.Runtime.__quantum__rt__qubit_release
    at LeakQubit in line_0:3:9

A qubit that leaves its use block in any state other than $|0\rangle$ is an error. With a call stack.

The reason is resource management. Qubits are borrowed from a shared pool; the next operation that allocates one is entitled to assume it starts in $|0\rangle$. A qubit returned dirty silently corrupts whoever gets it next — and that failure appears in a different part of the program from the bug, which is the worst possible property for a bug to have.

Every other framework in this book handles this by convention. Q# checks it.

⚠️ Common Pitfall — the check is at scope exit, not at every reuse.

It is tempting to read this as "Q# prevents qubit-reuse bugs." It does not. Measured:

qsharp operation ReuseWithoutReset() : Result { use q = Qubit(); H(q); let first = M(q); // no Reset here -- q is in |first>, not |0> H(q); let second = M(q); Reset(q); return second; }

text Counter({'Zero': 251, 'One': 249})

This runs cleanly. The mid-operation reuse without a reset — exactly Chapter 9 §9.5's reset-and-reuse hazard — is not caught, because the qubit is reset before the scope ends. The discipline is about what you hand back to the pool, not about whether your algorithm is right.

A guarantee is only as broad as its statement. Read what a checker actually promises.

15.6 Adjoint and Controlled: Functors for Free

Declare an operation's capabilities and the compiler generates the variants:

operation Rot(q : Qubit) : Unit is Adj + Ctl {
    H(q);
    T(q);
}

is Adj + Ctl means "this operation has an adjoint (inverse) and a controlled version, and you may generate them." You then write:

Rot(q);                      // U
Adjoint Rot(q);              // U†     -- generated, not hand-written
Controlled Rot([c], q);      // C-U    -- generated
Adjoint Controlled Rot([c], q);

Verified — Rot followed by Adjoint Rot should be the identity:

  Counter({'Zero': 200})        200 of 200 shots

This is a substantial win. Chapter 13's gate folding needed $U^\dagger$; Chapter 19's oracles need controlled versions of arbitrary subroutines; Chapter 21's Grover diffuser is built from a controlled reflection. In Python you write the inverse by hand and keep it in sync, or call .inverse() and hope the framework handles your custom gate. In Q# the compiler derives it from the definition, so it cannot drift out of sync.

And omitting the declaration is a compile error, not a runtime surprise:

  Qdk.Qsc.TypeCk.MissingFunctor
    x type error
    `-> expected superset of Adj, found empty set

📐 Math Aside — why the compiler can do this at all.

For a sequence of unitaries, the adjoint is the reversed sequence of adjoints:

$$(U_1 U_2 \cdots U_n)^\dagger = U_n^\dagger \cdots U_2^\dagger U_1^\dagger$$

So if every primitive knows its own inverse — and $H^\dagger = H$, $T^\dagger = T^{-1}$, and so on are all known — the compiler can mechanically reverse the body and adjoint each element.

The catch is that this only works for straight-line unitary code. An operation containing a measurement has no adjoint (measurement is irreversible), and one containing classical control flow that depends on a measurement cannot be reversed either. That is precisely what is Adj is: a promise that the body is reversible, which the compiler then verifies. The declaration is not bookkeeping; it is a proof obligation.

Why Q# can do this and the Python frameworks cannot

It is tempting to file this under "Q# has a nicer API," which misses the point. Qiskit has .inverse() and .control(), and they work. The difference is what they apply to.

In Qiskit, a subroutine is a Python function that builds data.

def rot(qc, q):          # not a gate; a builder
    qc.h(q)
    qc.t(q)

There is nothing here to invert. rot is opaque: it can branch on a NumPy value, read a calibration file, emit different gates on different calls, or append a measurement halfway through. The only way to obtain its inverse is to run it, capture the resulting QuantumCircuit, and invert that. Which means the inverse exists for a particular invocation, not for the definition.

Q# inverts the definition, because a Q# operation is a declaration with a signature the compiler can inspect, not a function that emits one. Three things follow, and each of them is a property Qiskit structurally cannot have:

The obligation propagates. An operation may be declared is Adj only if every operation it calls is also is Adj. That is the same call-graph closure argument as §15.3's effect system, applied to reversibility instead of purity, and it is why declaring is Adj on a body containing M(q) is rejected outright rather than failing later.

The check happens once, at the definition. Qiskit's .inverse() is checked at the moment you call it, on the circuit you happen to have. Case Study 2's bug 12 is exactly that failure mode — Chapter 13's gate folding called .inverse() on an ISA circuit and got IBMInputValueError: The instruction sxdg ... is not supported, discovered by the primitive rejecting the job after it had been submitted.

And the inverse cannot drift. Edit the body of Rot and its adjoint changes with it, because the adjoint was never written down. A hand-maintained inverse in Python is a second definition of the same thing, and two definitions of the same thing is a maintenance hazard with a known failure mode.

🔬 Honest Assessment — free is not the same as good.

The compiler-generated Controlled Rot is correct by construction. Nothing here says it is efficient. Mechanically controlling every element of a body is a legitimate construction and often a wasteful one; a hand-written controlled version can exploit structure the compiler cannot see — that two rotations commute, that a controlled-controlled-Z is cheaper than controlling each factor, that a phase can be pushed onto the control.

This matters more here than anywhere else in the chapter, because §15.8 is about to establish that the T count is what you pay for, and a mechanically generated controlled operation can carry a T count several times larger than a hand-written one. Exercise 15.9 asks you to measure the difference on your own operation rather than take either side on faith.

The right summary is that Q# removes a correctness burden, not a cost burden. You never write a wrong inverse again. You may still write an expensive one.

15.7 Resource Estimation

The capability that makes Q# worth knowing even if you never ship Q# code.

Every result in this book so far has been about today's hardware — noisy, ~100 qubits, no error correction. The resource estimator answers a different question: what would this cost on a machine that actually works?

qsharp.estimate("Circuit()")

For a two-qubit circuit with H, CNOT, and three T gates:

  physical qubits:  12,642
  runtime:          36,400 ns

  algorithmicLogicalQubits:        9
  algorithmicLogicalDepth:         4
  numTstates:                      3
  numTfactories:                   3
  physicalQubitsForAlgorithm:    882
  physicalQubitsForTfactories: 11,760

Read the last two lines. Of 12,642 physical qubits, 882 run the algorithm and 11,760 — 93% — manufacture magic states.

The logical counts, which the estimator derives from the program:

  {'numQubits': 2, 'tCount': 3, 'rotationCount': 0, 'cczCount': 0, 'measurementCount': 1}

Two logical qubits become nine after error correction; nine logical qubits become 882 physical ones; and three T gates require 11,760 more.

What the estimator is a model of

A number with no model behind it is an opinion with a decimal point. The estimator's saving grace is that it will tell you its entire model if you ask — every assumption is in the jobParams field of the result, and there are fewer of them than you would guess:

  qecScheme
    name                          surface_code
    errorCorrectionThreshold      0.01
    crossingPrefactor             0.03
    physicalQubitsPerLogicalQubit 2 * codeDistance * codeDistance
    logicalCycleTime              (4*twoQubitGateTime + 2*oneQubitMeasurementTime) * codeDistance
    maxCodeDistance               50

  qubitParams (qubit_gate_ns_e3)
    instructionSet                GateBased
    oneQubitGateTime               50 ns      oneQubitGateErrorRate          0.001
    twoQubitGateTime               50 ns      twoQubitGateErrorRate          0.001
    tGateTime                      50 ns      tGateErrorRate                 0.001
    oneQubitMeasurementTime       100 ns      oneQubitMeasurementErrorRate   0.001
                                              idleErrorRate                  0.001

  errorBudget                     0.001    ->  logical 0.0005 | tstates 0.0005 | rotations 0.0
  constraints                     maxDistillationRounds 3

That is the whole thing. Six timing-and-error numbers describing a qubit, five describing a code, one describing how much failure you will tolerate. Everything in §15.8's tables is a consequence.

Four of those inputs are worth understanding before you quote anything the estimator produces, because each encodes an assumption that could be wrong in a way the output will not advertise.

errorCorrectionThreshold: 0.01 is the assumed surface-code threshold. Chapter 25 §25.8 measured a threshold directly for the repetition code and established the concept: below it, distance buys you exponential error suppression; above it, distance makes things worse. The estimator hard-codes 1% for the surface code, which is in the range published for circuit-level depolarizing noise — and is optimistic relative to noise with correlated or leakage components, which Chapter 30 §30.5 is entirely about randomized benchmarking failing to see.

errorBudget: 0.001 says you will accept a one-in-a-thousand chance that the whole computation returns garbage. It is a choice, and it is the input people forget is a choice. Everything scales against it.

physicalQubitsPerLogicalQubit: 2d² is the surface-code patch cost. Chapter 25's code comparison table gives the same $[[\approx 2d^2, 1, d]]$ scaling, from a completely different direction — which is a genuine cross-check, since Chapter 25 derived it from stabilizer counting and the estimator uses it as a modelling assumption.

maxDistillationRounds: 3 caps how many nested distillation stages a T factory may use. It is the reason §15.8's numbers have a ceiling at all, and the reason a bad enough qubit produces an error rather than an enormous estimate.

The single most important thing to understand about this list is what is not in it. No connectivity graph. No calibration data. No individual qubit quality, no dead links, no drift. The estimator models a machine that is uniform — every qubit is the median qubit, forever. Chapter 30 §30.3 measured the two-qubit error rate on one real chip supporting a quoted value anywhere from 0.00750 to 0.07205, a factor of 9.6, and Chapter 39 measured cz errors from 1.79e-03 all the way to 1.00 on dead links. A uniform-qubit model of that chip does not describe it.

That is not a criticism so much as a statement of scope. The estimator is answering a question about a machine nobody has built, and assuming uniformity is the only defensible thing to do about a device whose defect distribution does not yet exist. But it means the estimate is a floor expressed in a currency — physical qubits — that a real fabrication yield will inflate.

📊 What the Numbers Say — four outputs, four different questions.

The estimator returns a lot of fields. Four of them answer questions people actually ask, and confusing them is the most common way to misuse this tool:

text algorithmicLogicalQubits 9 "how big is my algorithm?" physicalQubitsForAlgorithm 882 "how much machine does the algorithm occupy?" physicalQubitsForTfactories 11,760 "how much machine does the SUPPORT occupy?" physicalQubits 12,642 "how big must the machine be?"

Only the last one is a hardware requirement. Case Study 1 shows what happens when the first is quoted as though it were the last: RSA-2048 needs 6,189 logical qubits and 24,937,084 physical ones, and the entire public conversation about "4,000 qubits to break RSA" is people quoting the small number and being heard as quoting the large one.

The middle two are the interesting ones, and no other tool in this book reports anything like them. Their ratio — not their sum — is what §15.8 and Case Study 1 disagree about, and working out why they disagree is the most useful hour in this chapter.

The number that is easy to get is not the number that answers the question. physicalQubits is the easy number, and it is the one that hides the split.

Reading 12,642 as arithmetic

Every digit of that estimate is reconstructible from the model above. Doing it once converts the estimator from an oracle into a calculator, which is the difference between quoting a number and understanding it.

Step 1 — lay out the logical qubits. Nearest-neighbour constraints mean you cannot just use two patches; you need routing space between them. The estimator's own report gives the formula:

$$Q_{\text{logical}} = 2Q_{\text{alg}} + \lceil\sqrt{8Q_{\text{alg}}}\,\rceil + 1$$

For $Q_{\text{alg}} = 2$: $4 + \lceil\sqrt{16}\rceil + 1 = 4 + 4 + 1 = \mathbf{9}$. Measured across nine different algorithm sizes from 2 to 6,189 logical qubits, this formula reproduced the estimator's layout exactly every time — including RSA-2048's 6,189 → 12,602.

Step 2 — find the algorithmic depth. Under the estimator's scheduling model, each T gate, each arbitrary rotation, and each measurement layer costs one logical cycle:

$$D_{\text{alg}} = t_{\text{count}} + r_{\text{count}} + m_{\text{count}} = 3 + 0 + 1 = \mathbf{4}$$

T gates are serialized. That single fact is why runtime grows linearly in T count all the way to RSA-2048's ten billion, and it is worth carrying into §15.8.

Step 3 — divide the error budget. Half the budget protects the logical qubits, spread over every qubit-cycle of the computation; half protects the T states, spread over every T state:

$$\varepsilon_{\text{qubit}} = \frac{0.0005}{Q_{\text{logical}} \times D_{\text{alg}}} = \frac{0.0005}{9 \times 4} = 1.389\times10^{-5}, \qquad \varepsilon_{T} = \frac{0.0005}{3} = 1.667\times10^{-4}$$

Both reproduce the estimator's reported requiredLogicalQubitErrorRate and requiredLogicalTstateErrorRate to the digit. It is a union bound, and like every union bound it is conservative — which is the honest direction for a cost estimate to err in.

Step 4 — pick the code distance. The surface code's logical error rate follows

$$P_L(d) = a\left(\frac{p}{p_{\text{th}}}\right)^{\lfloor (d+1)/2 \rfloor} \qquad a = 0.03,\quad p_{\text{th}} = 0.01$$

At $p = 10^{-3}$ this is $0.03 \times 10^{-\lfloor (d+1)/2 \rfloor}$. Distance 5 gives $3\times10^{-5}$, which fails the $1.389\times10^{-5}$ requirement. Distance 7 gives $3\times10^{-6}$, which passes. The estimator reports $d = 7$ and a logical error rate of 3.00e-6 — the formula does not approximate its answer, it is its answer.

Step 5 — multiply out.

  physical qubits per logical patch     2 d^2 = 2 x 49  =     98
  algorithm                             9 x 98          =    882
  T factory (measured)                                  =  3,920 each
  factories needed                                      =      3
  factories                             3 x 3,920       = 11,760
                                                          ------
  total                                 882 + 11,760    = 12,642

Nothing was measured that could not have been calculated. Which is the point: the estimator is a bookkeeper for a model you can hold in your head, not a black box, and that is exactly what makes it worth trusting in the one regime — fault tolerance — where you cannot check its answer against hardware.

📐 Math Aside — solving for $d$, and why fidelity beats qubit count.

Invert Step 4. To reach a required logical error rate $\varepsilon$:

$$a\left(\frac{p}{p_{\text{th}}}\right)^{(d+1)/2} \le \varepsilon > \qquad\Longrightarrow\qquad > d \;\ge\; 2\,\frac{\ln(a/\varepsilon)}{\ln(p_{\text{th}}/p)} - 1$$

Read the denominator. The distance you need is inversely proportional to $\ln(p_{\text{th}}/p)$ — how many factors below threshold you are. And the qubit cost is $2d^2$, so:

$$\text{qubits per logical qubit} \;\propto\; \left(\frac{1}{\ln(p_{\text{th}}/p)}\right)^{2}$$

Improving $p$ by a factor of ten adds one to $\ln(p_{\text{th}}/p)$ measured in decades, and the saving is squared. That is the entire mechanism behind §15.8's factor of 27, and it is why the gain is so lumpy: at $p = 10^{-3}$ you are one decade below threshold; at $10^{-4}$, two. Going from one decade to two halves the required distance and therefore quarters the patch.

There is a second reading of the same formula, and it is the one hardware roadmaps ignore. The logical cycle time is $(4t_{2q} + 2t_{\text{meas}}) \times d$ — linear in $d$. So a smaller distance does not merely shrink the machine, it speeds it up: at e3, $d = 7$ gives a 2,800 ns logical cycle; at e4, $d = 3$ gives 1,200 ns. §15.8's comparison shows the compounded effect — 28.0 μs to 3.6 μs, a 7.8× speedup obtained without touching the algorithm, of which 2.3× is the cycle time and the rest is the shorter schedule a smaller machine permits.

Every remedy is denominated in the currency of the disease — but this one is denominated in both currencies at once, which is rare enough to be worth planning around.

15.8 The T-Gate Cliff

Scale the T count and the structure becomes unmistakable:

   T gates   physical qubits   algorithm   T factories   % T fact.   runtime (μs)   # factories
         0               450         450             0       0.0%            2.0             0
         1             2,882         882         2,000      69.4%           28.0             1
         3            12,642         882        11,760      93.0%           36.4             3
        10            61,642         882        60,760      98.6%           30.8            10
        30            98,658       1,458        97,200      98.5%          111.6            15
       100            98,658       1,458        97,200      98.5%          363.6            15

Three separate lessons live in that table.

The cliff at one

Zero T gates: 450 qubits. One T gate: 2,882. A 6.4× jump for a single gate.

Confirmed against a Clifford-only circuit:

  Clifford only (H, CNOT, S)   tCount=0   physical qubits    450   T factories 0
  one T gate added             tCount=1   physical qubits  2,882   T factories 1

This is the Gottesman–Knill theorem, priced. Chapter 11 §11.4 measured that Clifford circuits simulate in polynomial time at any size — a thousand-qubit Clifford circuit runs on a laptop. The consequence drawn there was that Clifford circuits give no quantum advantage. Here is the same boundary from the hardware side: the gates that make a quantum computer worth building are exactly the gates that make it enormous.

Clifford gates are cheap under error correction because they can be applied transversally — directly on the encoded logical qubits. T gates cannot. They require magic state distillation: a separate factory that consumes many noisy states to produce one clean $|T\rangle$ state, which is then consumed to apply a single gate.

Why Clifford is free and T is not — the same fact, twice

Chapter 11 §11.4 and this table are not two facts that happen to line up. They are one algebraic property, observed from opposite ends.

The Clifford group is defined as the set of unitaries that map Pauli operators to Pauli operators under conjugation — the normalizer of the Pauli group:

$$C \in \mathcal{C} \iff C P C^\dagger \in \mathcal{P} \ \text{ for every } P \in \mathcal{P}$$

Two enormous consequences follow from that one line, and they are the two halves of this chapter.

Consequence one: Clifford circuits are classically simulable. You do not have to track $2^n$ amplitudes; you track the $n$ stabilizer generators, each a Pauli string. A Clifford gate updates each generator to another Pauli string, so the representation stays the same size. That is Gottesman–Knill, and it is why Chapter 11 measured a thousand-qubit Clifford circuit running on a laptop.

Consequence two: Clifford gates are almost free under error correction. Error correction works by tracking which Pauli errors have occurred — the Pauli frame. Push that frame through a Clifford gate and it comes out the other side as a different Pauli frame, which you record and carry on. No new machinery, no state consumed. In the surface code the logical $S$, $H$, and $CNOT$ are reachable by transversal operations and lattice surgery, and errors introduced this way stay on the physical qubit they started on rather than spreading through the block.

Now do the same calculation for T:

$$T X T^\dagger = \frac{1}{\sqrt{2}}\left(X + Y\right)$$

Not a Pauli. A superposition of two of them. Both consequences fail simultaneously: the stabilizer tableau cannot represent the result, so classical simulation blows up; and the Pauli frame cannot be pushed through, so error correction has nothing to track.

The gate that is hard to simulate and the gate that is expensive to protect are the same gate, for the same reason. That is the sentence this chapter's headline table is a price list for. There is no framework, no compiler, and no clever encoding in which you get the first without paying for the second, because they are not two properties.

⚛️ The Physics Underneath — Eastin–Knill, and what a factory is actually doing.

The obvious question is whether some other code makes T transversal too. The answer is a theorem, and it is a hard no.

The Eastin–Knill theorem (2009): for any quantum error-detecting code, the group of transversal logical gates is finite — and a finite group cannot be universal, because universality requires a dense subgroup of $SU(2^k)$. Every code you can build has some set of gates that are free and some that are not, and the second set is never empty.

This is a conservation law, not an engineering gap. No amount of code design removes the cost; it only moves it around. The surface code's choice is to make the entire Clifford group cheap and put the whole bill on T, which is the reason the T count is the currency of fault tolerance rather than, say, the CNOT count.

What the factory does about it is buy the missing gate as a state. The identity is that a T gate can be applied by consuming one copy of $|T\rangle = \frac{1}{\sqrt2}(|0\rangle + e^{i\pi/4}|1\rangle)$ using only Clifford operations, measurement, and a classically conditioned correction. Gate teleportation converts a hard gate into an easy circuit plus a hard resource — and resources, unlike gates, can be manufactured off-line, in parallel, and in advance.

The manufacturing protocol is distillation. The classic 15-to-1 round encodes fifteen noisy $|T\rangle$ states in a $[[15,1,3]]$ Reed–Muller code, measures stabilizers, and post-selects on the clean syndrome. The output error rate is cubic in the input:

$$p_{\text{out}} \approx 35\,p_{\text{in}}^{\,3}$$

text p_in = 1e-2 -> 3.5e-05 286x suppression p_in = 1e-3 -> 3.5e-08 28,571x p_in = 1e-4 -> 3.5e-11 two rounds from 1e-3: 1.5e-21

A cubic map has a fixed point, at $35p^2 = 1$, or $p \approx 0.169$. Distillation has its own threshold — and at 17% it is far more forgiving than the code's 1%. Which is the whole reason the scheme works: you may distil states that are far too noisy to compute with.

The cliff, decomposed

450 → 2,882 looks like one effect. It is two, and separating them changes what you do about it.

Part of the cliff is the factory. Part of it is the code distance, and that part would be there even if magic states were free. Measured:

                        T=0      T=1     what changed
  required qubit error rate
                    1.11e-04  2.78e-05   budget now shared with T states; depth 1 -> 2
  code distance          5        7      3e-5 fails at d=5; 3e-6 passes at d=7
  physical per logical  50       98      2d^2: 2x25 -> 2x49
  algorithm qubits     450      882      9 logical x (50 -> 98)
  T factories            0        1      2,000 physical qubits
                       ---     -----
  total                450    2,882

The first three rows have nothing to do with magic states. Adding one T gate makes the computation one logical cycle longer and forces the error budget to be split two ways, which tightens the per-qubit-cycle requirement by a factor of four, which forces the code distance up from 5 to 7, which costs $(7/5)^2 = 1.96\times$ on every logical qubit in the machine — including the ones that never touch a T gate.

Then the factory arrives on top of that, at 2,000 qubits, which is another 3.27×.

$$1.96 \times 3.27 = 6.4$$

Both factors are consequences of non-Cliffordness, but only one of them is "distillation is expensive." The other is a budget effect that would still be there if magic states were free — and it is the one that generalizes, because it applies to any operation that lengthens the computation.

The saturation, and the space–time trade

At 30 and 100 T gates the physical qubit count is identical — 98,658, with 15 factories — while runtime grows from 111.6 μs to 363.6 μs.

The estimator stopped buying factories and started reusing them. Beyond a point you trade time for space, running the same distillation hardware repeatedly instead of building more. Which of the two you want depends on whether you are constrained by qubit count or by coherence — and the estimator will optimize either way if you ask it.

Amortization, and why the factory fraction inverts

The saturation above is not a quirk of the numbers 30 and 100. It is a law, and once you have it the chapter's most confusing result — that T factories are 93% of a small circuit and 3% of RSA-2048 — stops being a paradox.

Start with how many factories the estimator buys. Each factory produces one $|T\rangle$ state per run and takes $T_{\text{fact}}$ nanoseconds to do it. Over a computation lasting $T_{\text{run}}$ nanoseconds, one factory can complete $\lfloor T_{\text{run}}/T_{\text{fact}}\rfloor$ runs, so:

$$N_{\text{fact}} = \left\lceil \frac{t_{\text{count}}} {\lfloor T_{\text{run}} / T_{\text{fact}} \rfloor} \right\rceil$$

Measured against every row of §15.8's table:

   T    runtime ns   factory ns   runs each   ceil(T/runs)   estimator
   1        28,000       26,000           1              1           1
   3        36,400       36,400           1              3           3
  10        30,800       30,800           1             10          10
  30       111,600       46,800           2             15          15
 100       363,600       46,800           7             15          15

Exact on all five. Now push it. §15.7's Step 2 established that T gates are serialized — the logical depth is essentially the T count. So $T_{\text{run}} \approx t_{\text{count}} \times t_{\text{cycle}}$, and substituting:

$$N_{\text{fact}} \;\approx\; \frac{t_{\text{count}}} {\;t_{\text{count}} \times t_{\text{cycle}} / T_{\text{fact}}\;} \;=\; \frac{T_{\text{fact}}}{t_{\text{cycle}}}$$

★★ The T count cancels. For any algorithm long enough that the factory duration is small compared with the runtime, the number of factories you need is not a function of how many T gates you have — it is the number of logical cycles a single factory run occupies, and nothing else.

That is a strong prediction, so it is worth testing on programs rather than on toys:

                              T count   pred.   estimator   algorithm q   factory q    % fact
  Grover n=8                    1,128      11          11         8,450     106,480     92.6%
  Grover n=12                   7,900      12          12        15,750     162,240     91.2%
  Grover n=16                  44,622      14          14        26,010     252,000     90.6%
  Grover n=20                 229,944      15          15        31,212     270,000     89.6%
  Ch.19 oracle, no ancillas    26,978      16          16        12,600     288,000     95.8%
  Ch.19 oracle, v-chain            55      11          11        10,164      71,280     87.5%
  RSA-2048             10,496,900,071      13          13    24,221,044     716,040      2.9%

Seven for seven, across eight orders of magnitude in T count. (It misses on the two-qubit toy circuits, where the algorithm is shorter than a single factory run and the asymptotic argument does not apply — which is exactly where you should expect an asymptotic argument to miss.)

Now read the last two columns. From Grover n=8 to RSA-2048 the T count rises by a factor of nine million and the factory footprint stays between 71,280 and 716,040 physical qubits — while the algorithm footprint climbs from 8,450 to 24,221,044.

$$\text{factory fraction} = \frac{N_{\text{fact}} \times S_{\text{fact}}} {N_{\text{fact}} \times S_{\text{fact}} + Q_{\text{logical}} \times 2d^2}$$

The numerator is a constant. The denominator grows with the register. That is the entire inversion, and it is a much sharper statement than "long runtimes amortize factories" — because the amortization is already complete by a thousand T gates, and after that it does nothing at all.

Which raises the question the two measurements are really about: how big does the register have to be? Holding the T count fixed at Grover-20's 229,944 and growing the algorithm:

   algorithm qubits   logical after layout    d   algorithm q   factory q   % factories
                 20                     54   17        31,212     270,000        89.6%
                 50                    121   19        87,362     252,000        74.3%
                100                    230   19       166,060     252,000        60.3%
                150                    336   19       242,592     252,000        51.0%
                200                    441   19       318,402     252,000        44.2%
                400                    858   21       756,756     216,000        22.2%
              2,000                  4,128   21     3,640,896     216,000         5.6%
              6,189                 12,602   23    13,332,916     198,000         1.5%

★ The crossover is at roughly 160 algorithm qubits, and it has nothing to do with the T count — which was held fixed down the entire table. Below a couple of hundred logical qubits you are buying a distillation plant with a computer attached; above it, a computer with a distillation plant attached.

So §15.8's 93% and Case Study 1's 3% are both correct, and the variable that separates them is neither the T count nor the runtime. It is register width. Small-circuit measurements of the factory fraction do not extrapolate, and nothing in the small-circuit data announces which side of 160 qubits you are on.

A measurement taken at one scale is evidence about that scale. Chapter 10's routing overhead and Chapter 11's MPS timing made the same point about different quantities; this is the third instance, and the only one where the number moves by a factor of thirty.

💰 Cost and Queue — the currency this chapter is denominated in.

Every other cost discussion in this book is in shots, queue minutes, and dollars. Here it is in physical qubits, and the two are not convertible — which is itself the finding.

Try the conversion anyway, because the failure is instructive. Appendix G's rate card gives IBM pay-as-you-go at ~$96 per minute of QPU time. RSA-2048's estimated 1.5-day run is 2,172 minutes of device time:

text 2,172 min x $96/min = $208,512 to factor RSA-2048

Now set that beside Chapter 39's measurement of one 31.2-second VQE run, priced three ways on today's platforms: $50 per-minute, $7,432 per-shot, $185,542 on trapped ions.

Breaking RSA-2048 and running a 31-second VQE come out within 13% of each other. The arithmetic is right and the conclusion is absurd, which tells you the model is wrong rather than the numbers.

What is wrong is that per-minute pricing prices access, not computation. Chapter 39's own summary — you are not paying for device time, you are paying for access — is the reason. On a fault-tolerant machine the cost is not the 1.5 days. It is the twenty-five million qubits, which nobody rents by the minute because nobody has one.

The honest unit for this chapter is physical qubits, and it is honest precisely because it does not convert. A quantum advantage claim priced in dollars per shot has silently assumed the machine already exists.

The parameter that dominates everything

Same circuit, four assumed qubit technologies:

  qubit_gate_ns_e3      physical qubits   4,882     runtime    28.0 μs
  qubit_gate_ns_e4      physical qubits     180     runtime     3.6 μs
  qubit_maj_ns_e4       physical qubits   7,818     runtime   140.0 μs
  qubit_maj_ns_e6       physical qubits     198     runtime    18.0 μs

4,882 versus 180 physical qubits — a factor of 27 — from the assumed physical error rate alone. e3 means a $10^{-3}$ error rate and e4 means $10^{-4}$: one order of magnitude better hardware, 27× fewer qubits.

The mechanism is code distance. To reach a target logical error rate you need a distance $d$ that grows as the physical error rate approaches the threshold, and the qubit cost of a surface-code patch scales as $d^2$. Improving physical error rates is quadratically more valuable than adding qubits — which is why hardware roadmaps talk about fidelity at least as much as qubit count, and why Chapter 1 §1.5's warning against ranking devices by qubit count has a fault-tolerant analogue.

📉 Noise Report — walking the physical error rate up to the threshold.

The four named technologies sample the space very coarsely. Sweeping the physical error rate continuously on the same 2-qubit, 3-T circuit shows what the shape actually is:

text p code distance physical qubits runtime 0.001 7 12,642 36,400 ns 0.003 15 49,062 54,000 ns 0.005 25 117,090 110,000 ns 0.007 49 981,216 196,000 ns 0.008 REJECTED "computed code distance 69 is too high; maximum allowed is 50" 0.009 REJECTED "computed code distance 145 is too high" 0.010 REJECTED "expected value between 0 and 0.01"

A factor of seven in the physical error rate costs a factor of 78 in machine size, and then the tool stops answering.

That is not a bug. It is Chapter 25 §25.8's threshold theorem showing up as an error message. At $p = 0.007$ you are barely below the assumed threshold of 0.01, and the required distance has already run away — 49, against 7 at $p = 0.001$. At 0.009 it wants distance 145. At 0.01 the estimator refuses the input outright, because there is no distance that works: you are on the wrong side of a phase transition and the answer is not "expensive," it is "impossible."

Chapter 25's warning is the operative one: being barely below threshold is nearly as bad as being above it. These numbers are what that sentence costs. And note where today's hardware sits — Chapter 30 §30.3 measured two-qubit errors on a real chip from 0.00750 to 0.07205, which straddles this entire table and runs past its right-hand edge.

★★ The cliff is a property of the error rate, not of the T gate

If T gates are intrinsically expensive, the cliff should appear wherever you look. Run the identical sweep at $10^{-4}$ instead of $10^{-3}$:

   T gates |  e3 physical   d   factory type          |  e4 physical   d   factory type
         0 |          450   5   none                  |          162   3   none
         1 |        2,882   7   15-to-1 space eff.    |          180   3   trivial 1-to-1
         3 |       12,642   7   15-to-1 space eff.    |          180   3   trivial 1-to-1
         4 |            -   -   -                     |          180   3   trivial 1-to-1
         5 |            -   -   -                     |        1,962   3   15-to-1 space eff.
        10 |       61,642   7   15-to-1 RM prep       |        5,742   3   15-to-1 RM prep
        30 |       98,658   9   15-to-1 space eff.    |       15,450   5   15-to-1 space eff.
       100 |       98,658   9   15-to-1 space eff.    |       15,450   5   15-to-1 space eff.

At $10^{-4}$ the first T gate costs 18 physical qubits — 162 to 180, a 1.11× jump instead of 6.4×. The chapter's headline effect is simply not there.

Then it reappears, between four T gates and five: 180 → 1,962, a 10.9× jump. The cliff did not vanish. It moved.

And its new location is predictable to the gate. A T factory named trivial 1-to-1 performs no distillation at all — it hands the raw physical state through. The estimator picks it whenever the raw tGateErrorRate already meets the required T-state error rate, which §15.7's Step 3 gives as $\varepsilon_{\text{tstates}} / t_{\text{count}}$. Distillation therefore begins when

$$\frac{\varepsilon_{\text{tstates}}}{t_{\text{count}}} \le p_T \qquad\Longrightarrow\qquad t_{\text{count}} \;\ge\; \frac{0.0005}{10^{-4}} = 5$$

Predicted at five. Measured at five — T=4 is trivial 1-to-1 at 180 qubits, T=5 is 15-to-1 space efficient at 1,962. The same calculation at $10^{-3}$ gives $0.0005/10^{-3} = 0.5$, so distillation is required from the first T gate, which is precisely the cliff §15.8 opened with.

★★ So the headline is not "T gates cost 6.4×." It is: a T gate costs 6.4× when your physical error rate is worse than your per-T-state error budget, and 1.11× when it is not. The cliff is a statement about the gap between hardware quality and algorithmic demand, and the gap closes from both directions — better qubits, or fewer T gates.

Which reframes the chapter's other conclusion. §15.8 has been reading the error rate as a multiplier on machine size. It is better read as a threshold on whether distillation is needed at all, and the 27× is what crossing that threshold is worth.

A full estimate: Grover on twenty bits

Everything so far has been priced on toy circuits. Here is the procedure end-to-end on a real algorithm from Part IV, using counts this book measured rather than assumed.

Step 1 — get the logical counts. Chapter 21 §21.6 measured a 20-bit Grover search at 229,944 T gates across 804 iterations, each iteration containing an oracle (143 T) and a diffuser (143 T), using the favourable ancilla-assisted accounting. Twenty search qubits, twenty measurements.

Step 2 — price them.

  Grover, n = 20  (N = 1,048,576)          229,944 T gates

  logical qubits after layout                        54
  code distance                                      17
  physical qubits per logical qubit                 578
  algorithm                                      31,212
  T factories               15 x 18,000  =      270,000    (89.6%)
                                              ---------
  total physical qubits                         301,212
  runtime                                          1.56 s
  factory runs                                   15,330

Chapter 21 ran this estimate for $n = 8$, 12, and 16 — 114,930, 177,990, and 278,010 physical qubits — and stopped short of $n = 20$. This is the missing row, and it is the one the chapter's argument actually needs.

Step 3 — compare to what. Chapter 21 §21.6's classical baseline for the same problem: 1,048,576 evaluations of a cheap predicate, well under a second on one core.

  quantum    301,212 physical qubits, fault-tolerant, 1.56 s
  classical  1 core, < 1 s

Grover loses on both axes at once, which is worth stating plainly because the quadratic speedup is real and the loss is not a contradiction of it. Chapter 21 §21.6 derived the crossover as $\sqrt N > c_q/c_c$ and put $c_q/c_c$ at $10^9$–$10^{12}$. This estimate is where that ratio comes from: 1.56 seconds on a machine roughly 300× larger than the largest device that exists — Case Study 1 puts today's ceiling at about 1,000 physical qubits with no error correction at scale — against a fraction of a second on one core.

"Compared to what?" applies to fault-tolerant estimates too, and it is easier to forget here than anywhere else in the book, because there is no baseline in the estimator's output. It reports what your circuit costs. It has no opinion on whether you should have run it.

🐛 Debug This — the T count was zero and the machine was still enormous.

logical_counts transpiles into a Clifford+T basis before counting, precisely so that a Toffoli reveals its true cost. Watch it work, and then watch it not work:

```text Toffoli, as written {'ccx': 1} t = 0 Toffoli, decomposed {'cx': 6, 't': 4, 'tdg': 3, 'h': 2} t + tdg = 7 correct

QFT-8, as written {'qft': 1} QFT-8, decomposed t + tdg = 0, rz = 84, h = 8, cx = 68 ```

A QFT on eight qubits decomposes to zero T gates. Its entire non-Clifford cost is 84 arbitrary rz rotations, and rz is in the Clifford+T basis list, so it survives decomposition untouched.

Price it both ways:

text tCount=0, rotationCount=0 (reading the T count only) 2,450 physical qubits, 0 factories tCount=0, rotationCount=84 (reporting the rotations) 114,930 physical qubits, 11 factories

A 47× understatement, with a T count of zero on both sides. The estimator synthesizes each arbitrary rotation into T gates itself and reports numTsPerRotation: 15 — 84 rotations become 1,260 T states, which is why eleven factories appear out of a circuit with no T in it.

The fix is to report rotationCount, not just tCount — which vqelab.resources.logical_counts does. The failure mode is reading tCount off its output and stopping there. Chapter 22 §22.6's figure of 7,926 T gates for an 8-qubit QFT is the same cost measured through a different synthesis path at a different precision; the disagreement between 1,260 and 7,926 is a synthesis-precision assumption, not an error in either.

A second instance of the same trap, in the opposite direction: the estimator accepts cczCount natively and charges four T states for a CCZ, against seven if you hand it the Clifford+T decomposition. Measured on three qubits: cczCount=1 → 16,856 physical qubits; tCount=7 → 28,616. Declaring a Toffoli as a Toffoli saves 41% of the machine. It also explains an apparent inconsistency inside this chapter: the project checkpoint asserts a Toffoli is 7 T gates (Qiskit's decomposition), while Case Study 1 models Shor at 4 T gates per Toffoli (the estimator's native cost). Both are right in their own frame, and logical_counts currently hardcodes cczCount=0, so it always pays the seven.

What would have to change

The chapter's conclusion — twenty-five million qubits for RSA-2048 — invites the question of what would move it. Sweeping the physical error rate on Case Study 1's counts:

   physical error rate   code distance   q per logical   physical qubits   runtime
                  1e-3            31           1,922          24,937,084     36.2 h
                  3e-4            21             882          11,329,980     24.5 h
                  1e-4            15             450           5,894,900     17.5 h
                  3e-5            13             338           4,340,116     15.2 h
                  1e-5            11             242           3,085,324     12.8 h
                  1e-6             7              98           1,260,476      8.2 h

Three orders of magnitude in physical error rate buys 19.8× in qubits and 4.4× in time. Which is a great deal, and also far less than three orders of magnitude — because §15.7's Math Aside showed that $d$ scales as $1/\ln(p_{\text{th}}/p)$, and a logarithm is a stubborn thing to fight.

A thousandfold better hardware than anything that exists still leaves you needing 1.26 million physical qubits. The qubit count does not go to zero; it goes to a large number more slowly.

So the leverage has to come from somewhere else, and there are three candidates:

Better constructions. Case Study 1 notes that Gidney and Ekerå's own RSA figure fell by more than an order of magnitude through better circuits alone, at fixed hardware. Chapter 19 §19.6 is the same effect in miniature — 26,978 T gates down to 55 by spending six ancillas. Algorithmic improvement has historically outrun hardware improvement on this problem, and it is the only one of the three that costs nothing to attempt.

A cheaper non-Clifford gate. Eastin–Knill guarantees some gate is expensive, not that it is T. Codes with cheaper magic states, or with a different transversal set, move the bill rather than removing it — but moving a bill by a factor of ten is worth having.

A larger error budget. The 0.001 in jobParams is a choice. Measured on the 3-T circuit, relaxing it from $10^{-3}$ to $10^{-2}$ takes the machine from 12,642 physical qubits to 500 — because $d$ drops from 7 to 5 and, at that requirement, no distillation is needed at all. A one-in-a-hundred failure rate is perfectly acceptable for an algorithm you can verify classically, and factoring is exactly such an algorithm: multiply the factors back together. That is not a rounding error in the estimate; on this circuit it is a 25× discount, and it is the input people are least likely to question.

🧪 Run It — the whole chapter in about a minute.

Everything in §15.7 and §15.8 runs locally with no Azure account. Three experiments, in increasing order of what they will teach you:

1. Reproduce the cliff. code/example-05-resource-estimation.py sweeps 0, 1, 3, 10, 30, and 100 T gates and prints §15.8's table. Confirm 450 and 2,882 before trusting anything else here.

2. Print the model, not the answer. The estimate result carries a jobParams field:

python r = qsharp.estimate("Circ()") print(r["jobParams"]["qecScheme"]) # threshold, prefactor, 2d^2, cycle time print(r["jobParams"]["qubitParams"]) # six numbers describing a qubit print(r["logicalQubit"]) # the code distance it chose, and why

Read logicalQubit["codeDistance"] on every estimate you run. It is the single number that explains the machine size, and it is the one the summary output does not show you.

3. Break it on purpose. Feed a custom qubitParams with an error rate of 0.008 and read the refusal. Then 0.01. A tool that says "no" at the threshold is telling you something a tool that extrapolates would hide, and it is the most useful failure mode in this chapter.

🧱 Project Checkpointvqelab/resources.py: what would this cost for real?

vqelab can choose good qubits (Ch. 12) and mitigate what is left (Ch. 13). This adds the question those chapters cannot answer: is this circuit ever going to work?

logical_counts(circuit) extracts the numbers the estimator actually consumes — qubit count, T count, rotation count, CCZ count, measurement count — from a Qiskit circuit, so you can get a T-count for work you did in Part II.

estimate_resources(counts, error_rate, budget) calls the Q# estimator through qsharp.estimate and returns physical qubits, runtime, and the algorithm/factory split, because the split is the interesting part and the total hides it.

t_count_sweep(circuit) reproduces §15.8's table for your circuit, so the cliff is something you measure rather than take on faith.

Its tests assert the two structural facts: a Clifford-only circuit requires zero T factories, and adding a single T gate increases the physical qubit requirement by more than 3×. Both are properties of fault tolerance, not of this implementation, so they are safe to assert.

15.9 When to Reach for Q

Task Q#?
Resource estimation for a fault-tolerant algorithm yes — best in class
Large, long-lived quantum codebase yes — the type system pays off
Algorithms needing many Adjoint/Controlled variants yes — free and always correct
Teaching qubit lifetime and reversibility yes — the compiler does the teaching
Running on IBM hardware today no — use Qiskit
Device-accurate noise simulation no — use Qiskit
Variational / ML loops no — use PennyLane (Ch. 16)
A fifty-line experiment no — the ceremony is not worth it

🔬 Honest Assessment — Q#'s position in the ecosystem.

Q# has by far the smallest user base of the frameworks in this book. Fewer tutorials, fewer Stack Overflow answers, fewer third-party libraries, and a much smaller pool of people to hire.

It also has the only production-grade resource estimator, and it is the only one of the five that treats quantum programming as a software engineering problem rather than a scripting problem.

The practical recommendation is unusual: learn Q#'s resource estimator without necessarily adopting Q#. The estimator takes logical counts — qubit count, T count, measurement count — which you can extract from a circuit written in any framework. §15.8's project checkpoint does exactly this. You get the most valuable thing Q# offers without rewriting your codebase in it.

If you are starting a large quantum codebase from scratch and expect it to live for years, the type system argument is real and worth taking seriously.

15.10 Summary

Q# is a language, not a library. pip install qdk, then from qdk import qsharp — the old qsharp package is deprecated, and import qdk alone does not expose eval/run/estimate.

The compiler refuses programs that are wrong: no implicit Int/Double conversion, argument order checked, undefined names caught, immutable let bindings enforced, and functions cannot allocate qubits or call quantum operations — so the classical and quantum parts of a hybrid program are distinguishable by declaration rather than by convention.

Qubits have lifetimes. use allocates for a scope, and a qubit released in any state other than $|0\rangle$ is a runtime error with a call stackQubit0 released while not in |0⟩ state. No other framework here checks this. But the check is at scope exit, not at every reuse: Chapter 9's mid-operation reset hazard runs cleanly if you reset before the block ends. A guarantee is only as broad as its statement.

is Adj + Ctl gets you inverse and controlled variants for free, generated by the compiler and therefore unable to drift out of sync with the definition. Verified: Rot then Adjoint Rot gives Zero on 200 of 200 shots. Omitting the declaration is a compile error (MissingFunctor: expected superset of Adj, found empty set) — and the declaration is a proof obligation, since operations containing measurement or measurement-dependent control flow have no adjoint.

★ The resource estimator prices fault tolerance. A two-qubit circuit with three T gates: 12,642 physical qubits, of which 882 run the algorithm and 11,760 (93%) are T factories.

★★ The T-gate cliff. Zero T gates: 450 physical qubits. One T gate: 2,882 — a 6.4× jump for a single gate. By ten T gates, 98.6% of the machine is manufacturing magic states rather than computing. This is Chapter 11's Gottesman–Knill theorem presenting its invoice: Clifford gates are transversal and cheap; T gates need magic state distillation and are not.

Beyond ~15 factories the estimator trades space for time — 30 and 100 T gates both need 98,658 qubits, while runtime grows 111.6 → 363.6 μs.

And the physical error rate dominates everything else. The same circuit needs 4,882 qubits at $10^{-3}$ and 180 at $10^{-4}$ — a factor of 27 from one order of magnitude in hardware quality, because code distance enters the qubit count quadratically. Improving fidelity beats adding qubits.

The estimator is a calculator, not an oracle. Its entire model is in jobParams: a surface code with threshold 0.01 and crossing prefactor 0.03, $2d^2$ qubits per patch, a cycle time linear in $d$, and a 0.001 error budget split evenly between logical qubits and T states. Every digit of 12,642 is reconstructible from it — layout $2Q + \lceil\sqrt{8Q}\rceil + 1 = 9$, depth $t + r + m = 4$, required rate $0.0005/(9{\times}4)$, then the smallest odd $d$ with $0.03(p/p_{\text{th}})^{\lfloor(d+1)/2\rfloor}$ below it, giving $d = 7$, $2d^2 = 98$, and $9 \times 98 = 882$. What is not in the model is connectivity, calibration, or qubit-to-qubit variation — it prices a uniform machine, and Chapter 30 §30.3 measured a real chip's two-qubit error spanning a factor of 9.6.

★★ The cliff is a property of the error rate, not of the T gate. Run the identical sweep at $10^{-4}$ and the first T gate costs 162 → 180 physical qubits, a 1.11× jump instead of 6.4× — the estimator selects a trivial 1-to-1 factory that performs no distillation at all. The cliff does not vanish; it moves, reappearing between four and five T gates (180 → 1,962, 10.9×) at exactly the point the model predicts: distillation begins when $\varepsilon_{\text{tstates}}/t_{\text{count}} \le p_T$, or $t \ge 0.0005/10^{-4} = 5$. The 6.4× headline is a statement about the gap between hardware quality and algorithmic demand, not about T gates as such.

★★ And the 93%-versus-3% factory fraction inverts on register width, not on T count. The number of factories converges to $\lceil T_{\text{fact}}/t_{\text{cycle}}\rceil$ — the T count cancels — which held exactly for all seven realistic algorithms tested, from Chapter 19's 55-T oracle to RSA-2048's ten billion. The factory footprint is therefore roughly constant (71,280 to 716,040 physical qubits across that entire range) while the algorithm's grows with the register. Holding the T count fixed at Grover-20's 229,944 and growing only the register, the fraction crosses 50% at about 160 algorithm qubits. Amortization is complete by a thousand T gates and does nothing after that; what inverts the fraction is width.

Priced end to end, a 20-bit Grover search needs 301,212 physical qubits and 1.56 s — against a classical baseline of one core and under a second (Chapter 21 §21.6). "Compared to what?" applies to fault-tolerant estimates too, and it is easiest to forget here, because the estimator reports what your circuit costs and has no opinion on whether you should have run it.

Recommendation: learn the estimator without necessarily adopting the language. It consumes logical counts you can extract from any framework, which is the most valuable thing Q# offers and the cheapest to obtain.


Next: Chapter 16 — PennyLane, which takes the opposite position from Q# on almost everything. Where Q# adds ceremony to catch errors, PennyLane removes it to make circuits differentiable, treating a quantum circuit as just another layer in a machine-learning model — and making the gradient, not the measurement, the primary output.