Chapter 15 — Key Takeaways (Microsoft Q#)

The Q# page. §15.8's T-gate cliff is the thing to remember even if you never write Q#.

Setup

pip install qdk
from qdk import qsharp        # NOT `import qsharp` (deprecated)
qsharp.init()                 # NOT `import qdk` alone -- no eval/run/estimate there

🗝️ qsharp warns DeprecationWarning: ... Please use the 'qdk' package instead. Top-level qdk does not re-export eval/run/estimate — they live in qdk.qsharp. qdk is an umbrella also carrying qdk.qiskit, qdk.cirq, qdk.openqasm, qdk.azure. Verified qdk 1.31.0.

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;
}
  Counter({'(One, One)': 514, '(Zero, Zero)': 486})

operation may touch qubits · use allocates for a scope · let is immutable · M returns Result (Zero/One, not a bool or int) · ResetAll is required (see below).

function vs operation — the central distinction

function operation
allocate qubits NO yes
call quantum ops NO yes
deterministic yes no
  function F() : Unit { use q = Qubit(); H(q); }
  -> Qdk.Qsc.CallableLimits.QubitAlloc: functions cannot allocate qubits

The classical and quantum halves of a hybrid program are distinguishable by declaration, not by convention you hope everyone follows.

What the compiler refuses

  int + double                   Qdk.Qsc.TypeCk.TyMismatch      expected Int, found Double
  missing return                 Qdk.Qsc.TypeCk.TyMismatch      expected Unit, found Int
  quantum op in a function       Qdk.Qsc.CallableLimits.QubitAlloc
  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

★ Qubit release discipline

  use q = Qubit(); X(q);      // never reset
  -> Error: Qubit0 released while not in |0⟩ state
     Qdk.Qsc.Eval.ReleasedQubitNotZero    (with a call stack)

Qubits come from a shared pool; the next allocator is entitled to assume $|0\rangle$. A dirty qubit corrupts whoever gets it next — a bug appearing in a different part of the program from its cause. No other framework here checks this.

⚠️ The check is at SCOPE EXIT, not every reuse. Chapter 9's mid-operation reset hazard (H, M, no reset, H, M) runs cleanlyCounter({'Zero': 251, 'One': 249}) — because the qubit is reset before the block ends.

A guarantee is only as broad as its statement. (4th instance: Ch. 11 optimized-away test, Ch. 12 averaged statistic, Ch. 13 inert DD.)

★ Functors for free

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

Adjoint Rot(q);                    // generated
Controlled Rot([c], q);            // generated
  Rot then Adjoint Rot -> {'Zero': 200}   (200 of 200 — the identity)

Omitting the declaration is a compile error: Qdk.Qsc.TypeCk.MissingFunctor: expected superset of Adj, found empty set

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

is Adj is a PROOF OBLIGATION, not bookkeeping. An operation containing a measurement has no adjoint, and declaring one anyway is rejected (Qdk.Qsc.LogicSeparation.OpCallForbidden).

The compiler derives the inverse from the definition, so it cannot drift out of sync — unlike a hand-written inverse you must maintain.

★★★ Resource estimation: the T-gate cliff

   T gates   physical   algorithm   factories   % 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

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

This is Chapter 11's Gottesman–Knill theorem presenting its invoice. Clifford circuits simulate in polynomial time (§11.4) → no advantage. Here, from the hardware side: the gates that make a quantum computer worth building are exactly the gates that make it enormous.

Clifford gates apply transversally, directly on encoded logical qubits. T gates need magic state distillation — a factory consuming many noisy states to make one clean $|T\rangle$.

Saturation → space–time trade. 30 and 100 T gates both need 98,658 qubits with 15 factories, while runtime grows 111.6 → 363.6 μs. Beyond a point the estimator reuses factories instead of building more.

One Toffoli = 7 T gates. Not a primitive under error correction.

★★ The parameter that dominates

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

4,882 vs 180 — a factor of 27 — from one order of magnitude in physical error rate.

Code distance $d$ grows as the error rate approaches threshold, and a surface-code patch costs $O(d^2)$. Improving fidelity is quadratically more valuable than adding qubits.

RSA-2048, priced (CS1)

   RSA bits   logical q          T count   physical qubits      runtime
        256         772       20,401,094       2,429,840        3.4 min
       1024       3,092    1,309,965,025      11,168,726       4.2 hours
       2048       6,189   10,496,900,071      24,937,084       1.5 days

Cross-check: Gidney & Ekerå (2021) — 20M qubits, 8 hours. Same order on both axes, from an independent analysis. The agreement validates the method.

6,189 logical vs 24.9 million physical. Quoting the logical count as a hardware requirement is the most common error in public discussion of this question.

★ The fraction INVERTS with scale. T factories are 93% of a small circuit and 3% of RSA-2048 — long runtimes amortize factories, large registers dwarf them.

A measurement at one scale is evidence about that scale. (3rd instance: Ch. 10 routing overhead, Ch. 11 MPS timing.)

Gap to today: ~1,000 physical qubits now vs ~25 million needed — four to five orders of magnitude, a fidelity and manufacturing problem, not an algorithmic one.

What the compiler catches (CS2)

3 caught + 1 impossible, 8 missed, scored against this book's own documented bugs.

Caught: argument-type confusion (Ch. 6) · missing inverse (Ch. 13) · dirty qubit release. Impossible: Chapter 8's parameter-ordering bug — Q# has no positional-binding-against-sorted-names API at all (neither does Cirq).

Missed: layout trap · QASM phase loss · dead qubit · mis-targeted mitigation · inert DD · endianness · optimized-away test · mid-operation reuse.

Every missed bug is a well-typed program that does the wrong thing. Types catch errors of FORM. Nearly every bug in this book was an error of CORRESPONDENCE — between program and machine, between two conventions, between a technique and the situation.

And the caught ones are the cheap ones to catch by other means; the expensive bugs ran successfully and returned plausible numbers.

"The language does not contain the shape of the mistake" beats "the compiler catches the mistake." API design prevents more bugs than type checking.

When to reach for Q

Task Q#?
Resource estimation yes — best in class
Large, long-lived codebase yes
Many Adjoint/Controlled variants yes
IBM hardware today · device noise · VQE loops · 50-line scripts no

🔬 Unusual recommendation: learn the ESTIMATOR without adopting the language. It consumes logical counts (qubits, T count, measurements) extractable from any framework — which is exactly what this chapter's checkpoint does. Smallest user base of the five; only production-grade estimator.

Common pitfalls

  • import qsharp (deprecated) or import qdk alone (no eval/run).
  • Reading the release check as "prevents qubit-reuse bugs."
  • Forgetting is Adj + Ctl, or declaring it on an operation containing a measurement.
  • Quoting logical qubit counts as hardware requirements.
  • Extrapolating the 93%-factory figure to large algorithms.
  • Reading a T count off a circuit that has not been decomposed to Clifford+T.

Project piece added this chapter

vqelab/resources.pylogical_counts() (transpiles to Clifford+T first, so Toffolis reveal their 7 T gates), estimate_resources(), estimate_circuit(), t_count_sweep(), and a ResourceEstimate with factory_fraction. Prices any framework's circuit via qdk.estimator.LogicalCountsno Q# source required. 10 tests pass, including test_toffoli_costs_seven_t_gates, test_clifford_only_needs_no_t_factories, and test_one_t_gate_triples_the_machine.