diff --git a/.github/workflows/wai-quantum-estimate.yml b/.github/workflows/wai-quantum-estimate.yml index ef1f8364b..fbb7b6a46 100644 --- a/.github/workflows/wai-quantum-estimate.yml +++ b/.github/workflows/wai-quantum-estimate.yml @@ -8,6 +8,13 @@ name: wai-quantum resource estimation # frontier, the seals, and the planar model and its receipts # (quantum_resource, quantum_resource_receipt) beside them. # +# The published-architecture check (src/published_architectures.rs) feeds the +# stated assumptions of published resource estimates into the estimators +# (quantum_arch, quantum_bicycle and the estimate job), holds every row to its +# published figure (tests/data/published_figures.tsv: the digits it is printed +# to, or a guard on ours) and the page docs/published-architectures.md to what +# they give. Its search over 4¹¹ Paulis runs in a release build only. +# # In a release build the calibration test measures every point of the # decoders' table again (the rotated surface-code memory experiment, decoded # by exact matching and by union-find on the same shots), so the code model @@ -87,6 +94,24 @@ jobs: quantum_resource_receipt::tests::a_counted_program_binds_its_digest_and_its_epsilon; do grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/estimate.log" || { echo "::error::$t did not run and pass"; exit 1; } done + - name: Published architectures, re-estimated, and the page that prints them + shell: bash + working-directory: wai/wai-quantum + run: | + set -euo pipefail + cargo test --locked --release --features full --lib -- quantum_arch:: quantum_bicycle:: published_architectures:: 2>&1 | tee "$RUNNER_TEMP/published.log" + grep -qE '^test result: ok\. [1-9][0-9]+ passed; 0 failed; 0 ignored' "$RUNNER_TEMP/published.log" || { echo "::error::the published-architecture tests did not all run and pass"; exit 1; } + for t in published_architectures::tests::the_page_is_what_the_estimators_give \ + published_architectures::tests::every_row_holds_to_its_published_figure \ + published_architectures::tests::the_ours_column_holds_only_ours_and_the_text_says_what_the_sources_say \ + quantum_bicycle::tests::the_cost_tables_are_what_the_search_finds \ + quantum_bicycle::tests::the_source_s_mean_costs_come_from_taking_z_by_the_inverse \ + quantum_bicycle::tests::the_fits_give_the_printed_rates \ + quantum_arch::tests::the_2021_rsa_table_is_reproduced_to_its_printed_digits \ + quantum_arch::tests::the_chemistry_estimate_is_priced_by_the_same_clock; do + grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/published.log" || { echo "::error::$t did not run and pass"; exit 1; } + done + cargo run --locked --release --example published_architectures --features full - name: The engine without receipts still builds the estimate shell: bash working-directory: wai/wai-quantum diff --git a/wai/changes/2026-11-06-published-architectures.md b/wai/changes/2026-11-06-published-architectures.md new file mode 100644 index 000000000..fcbb9bbe8 --- /dev/null +++ b/wai/changes/2026-11-06-published-architectures.md @@ -0,0 +1,24 @@ +### wai-quantum: published architectures, re-estimated + +- `published_architectures` feeds the stated assumptions of published resource estimates into + the crate's estimators and prints, for each quantity, the published figure, ours, the delta + and why they differ: a different assumption, the source's model, the source's own figures, + a limit of ours, or open. The targets are the bicycle architecture of arXiv:2506.03094; + RSA-2048 on the surface code (arXiv:1905.09749); FeMoCo (arXiv:2011.03494); RSA-2048 on + yoked surface codes (arXiv:2505.15917); and the planar model's own source + (arXiv:2211.07629). The published figures are kept in `tests/data/published_figures.tsv`, + each with where it is printed and the rule a row is held to: the digits the source prints, + its rounding, the range its fits' printed parameters allow, or, for a row that does not + agree, a guard on ours. `docs/published-architectures.md` holds the rows, and tests hold + the rows to their figures and the page to the code; `--example published_architectures` + prints them. +- `quantum_bicycle` prices a machine of bivariate-bicycle code modules: the gross and two-gross + codes from their printed polynomials and basis, the native measurements and a search for the + in-module cost of every Pauli, the instruction rates from the source's failure-spectrum + fits, and the capability of a machine of a given size. +- `quantum_arch` prices the 2021 surface-code machine: a Toffoli whose fixups are teleported + away runs at the reaction-limited tick, not the lattice-surgery beat + (`Timing::tick_us`, `Timing::teleported_add_us`); lookups through two access hallways + (`Timing::double_access_lookup_us`); the two-level 15-to-1 and 8T-to-CCZ factory + (`DistilledCczFactory`); and the windowed factoring cost (`windowed_factoring_estimate`). +- The resource-estimation workflow runs these in a release build and requires each by name. diff --git a/wai/wai-quantum/Cargo.toml b/wai/wai-quantum/Cargo.toml index 1ac4f6381..2b57a2678 100644 --- a/wai/wai-quantum/Cargo.toml +++ b/wai/wai-quantum/Cargo.toml @@ -409,6 +409,10 @@ required-features = ["quantum_ldpc"] name = "arch_factoring" required-features = ["quantum_arch"] +[[example]] +name = "published_architectures" +required-features = ["quantum_arch", "quantum_engine"] + [[example]] name = "estimate_calibration" required-features = ["quantum_engine", "quantum_uf"] diff --git a/wai/wai-quantum/README.md b/wai/wai-quantum/README.md index 542c44253..ccf9ad346 100644 --- a/wai/wai-quantum/README.md +++ b/wai/wai-quantum/README.md @@ -86,7 +86,7 @@ never creates information. | `quantum_nqs` | **neural quantum states**: spin models on any graph (Heisenberg, frustrated J₁–J₂, transverse-field Ising), a restricted Boltzmann machine with real parameters (and Marshall's sign on bipartite lattices) or complex ones that learn a sign structure nobody supplied, projected onto a momentum and spin-flip sector, variational Monte Carlo by Metropolis chains or exact enumeration, and stochastic reconfiguration solved in whichever of parameter or sample space is smaller. Held to exact diagonalisation and to DMRG on the same operator sum | | `quantum_noisy` | **noisy circuits**: quantum trajectories on the state vector (depolarizing, dephasing, amplitude damping, readout), an exact density-matrix referee up to 12 qubits that reuses the same kernels, linear cross-entropy fidelity (sampled, exact and the circuit's own), and the digital error model. Measures where that model is off: below the true fidelity by a margin that grows with the error rate, not with depth | | `quantum_ldpc` | **qLDPC memory at circuit level**: bivariate-bicycle codes from their two polynomials (`k` by rank, logical bases), the depth-8 syndrome cycle with circuit-level noise, its detector error model, and BP-OSD decoding (min-sum with adaptive scaling, ordered statistics with a combination sweep). The error model has the same columns as the source's own decoding matrices, and on identical syndromes the decoder makes the source's decisions shot for shot | -| `quantum_arch` | **architecture-level estimation**: yoked surface-code storage (row and grid parity checks over patches, hot and cold), magic-state cultivation read off the published simulation curves, CCZ factories, and a clock set by whichever is slowest of lattice surgery, the magic-state supply and the control loop. Assembles whole-machine estimates; its storage search matches the source's footprint tool layout for layout | +| `quantum_arch` | **architecture-level estimation**: yoked surface-code storage (row and grid parity checks over patches, hot and cold), magic-state cultivation read off the published simulation curves, CCZ factories, and a clock set by whichever is slowest of lattice surgery, the magic-state supply and the control loop. Assembles whole-machine estimates; its storage search matches the source's footprint tool layout for layout. Also the reaction-limited clock and two-level factory of the 2021 surface-code machine, and (`quantum_bicycle`) machines of bivariate-bicycle code modules | | `quantum_receipt` | signed, energy-accounted receipt over a reconstruction | | `quantum_ops` | the operations/attestation layer | | `quantum_cal`, `quantum_control`, `quantum_noise` | calibration, filter-function robust control, DD noise spectroscopy, Cycle-Benchmarking noise learning | @@ -451,6 +451,14 @@ under a million noisy qubits (arXiv:2505.15917). printed runtime is 14% above its own tallies. With every correction applied, the machine needs 958,480 qubits and 4.3 days. The conclusion holds. +It prices two more machines. The 2021 surface-code machine of +arXiv:1905.09749 teleports each Toffoli's fixups away and runs at the reaction +time, with a two-level factory; every row of that paper's RSA table comes +back. `quantum_bicycle` prices a line of bivariate-bicycle code modules, the +architecture of arXiv:2506.03094, from its printed codes, instructions and +rates. [Published architectures, re-estimated](docs/published-architectures.md) +puts these papers' own figures through the estimators, with each delta and why. + ## Error correction at circuit level An error-correction experiment is a noisy Clifford circuit, its detectors and diff --git a/wai/wai-quantum/docs/README.md b/wai/wai-quantum/docs/README.md index 2b8cf7482..a4b993a99 100644 --- a/wai/wai-quantum/docs/README.md +++ b/wai/wai-quantum/docs/README.md @@ -30,6 +30,8 @@ run one says why. - [The compiler](compiler.md): the passes, the ε budget and the certificate. - [Resource estimates](estimate.md): every number of an estimate and what it assumes. +- [Published architectures, re-estimated](published-architectures.md): published + resource estimates through these estimators, each delta and why. - [The reader's diagnostics](qasm-diagnostics.md) and [what it does with each construct](qasm-feature-matrix.md). - [The job layer's refusal codes](job-refusals.md). diff --git a/wai/wai-quantum/docs/README.zh-CN.md b/wai/wai-quantum/docs/README.zh-CN.md index ac1cb3dd2..6c8469420 100644 --- a/wai/wai-quantum/docs/README.zh-CN.md +++ b/wai/wai-quantum/docs/README.zh-CN.md @@ -19,6 +19,7 @@ - [判定](verdicts.md):引擎对一个答案检查到了什么程度,意图,以及修复。 - [编译器](compiler.md):各遍处理、ε 预算和证书。 - [资源估算](estimate.md):估算中的每个数字及其假设。 +- [已发表架构的重新估算](published-architectures.md):用这些估算器重算已发表的资源估算,给出每处差异及其原因。 - [读取器的诊断](qasm-diagnostics.md),以及[它如何处理每种构造](qasm-feature-matrix.md)。 - [其他格式](formats.md):按配置文件读写的 QIR 和 jeff,每次转换都经过检查。 - [作业层的拒绝代码](job-refusals.md)。 diff --git a/wai/wai-quantum/docs/published-architectures.md b/wai/wai-quantum/docs/published-architectures.md new file mode 100644 index 000000000..83bb3577b --- /dev/null +++ b/wai/wai-quantum/docs/published-architectures.md @@ -0,0 +1,369 @@ +# Published architectures, re-estimated + +This page puts published fault-tolerant resource estimates through this crate's +estimators. For each quantity it gives the published figure, ours, the delta +between them, and why they differ. Each estimator gets the source's stated +assumptions (its code, distances, error rates, cycle and reaction times, +factories and logical counts). Nothing is fitted to the figures it is compared +with, with one exception its row names: which end of the line the Ising +program's T factory sits at, which the source does not print. + +Every row has one of six classes: + +- **agrees**: equal to the digits the source prints; +- **assumption**: we were given, or chose, a different assumption, and the row + states it; +- **their model**: the source models it differently, and the row says how; +- **their numbers**: the source's own figures disagree with each other or with + the method it states, and the row says where; +- **our limit**: our estimator does not model this, and the row says what it + leaves out; +- **open**: not resolved, and the row says what is known. + +The published figures are kept apart from the code that computes ours, in +`tests/data/published_figures.tsv`: each with where it is printed, as printed, +its value and its rule. A row that agrees agrees to the digits the source +prints: exactly, to the nearest printed unit, rounded up or down where the +source rounds so, or, for a figure ours takes from the source's fits, within +the range the fits' printed parameters allow. Every other row carries a guard, +our figure to four digits. A test holds every row to its rule, and another holds +the rows below to the example's output, so a change to an estimator that moves +a figure fails until its guard, its reason and this page are updated together. +The resource-estimation workflow runs them in a release build. + +```text +cargo run --release --example published_architectures --features full # columns +cargo run --release --example published_architectures --features full -- --markdown # this page's rows +cargo run --release --example published_architectures --features full -- --guards # ours, for the guards +``` + +## The sources + +A row's source names the section, table or figure where its published figure +is printed. `source files` means the figure is quoted from the paper's arXiv +source (its TeX tables and plot data), which keeps more digits than its PDF +prints; every other figure is quoted with the digits the PDF prints. + +- **The bicycle architecture**, arXiv:2506.03094 (2025): the gross `[[144,12,12]]` + and two-gross `[[288,12,18]]` codes, its Tables 1–3, 5, 6, 9 and 10, §§1.2, + 3.2 and 4, Fig. 9, Fig. 10, Fig. 17 and Appendices A.1, A.2, A.7 and A.10. + Our model of it is `quantum_bicycle`. Beside it, the same codes' memory + figures in arXiv:2308.07915, Table 1. Its authors' compiler is released as + `bicycle_compiler`, `bicycle_cliffords` and `bicycle_common` on crates.io + (0.1.0 of 2025-07-31); one row below cites its source. +- **RSA-2048 on the surface code**, arXiv:1905.09749 (Quantum 5, 433, 2021): its + Tables 1 and 3, §§2.10–2.14, and the cost script in its ancillary files. Our + model of it is `quantum_arch::windowed_factoring_estimate`, and its counts go + through the estimate job as well. +- **FeMoCo on the surface code**, arXiv:2011.03494 (PRX Quantum 2, 030305, + 2021): its Table III, §IV.C and Fig. 10, on the same factory and clock. +- **RSA-2048 on yoked surface codes**, arXiv:2505.15917 (2025): §3.2, through + `quantum_arch::factoring_estimate`. +- **The planar model's own source**, arXiv:2211.07629 (2022): Appendix F, + through `quantum_resource`. + +## What it finds + +**The bicycle architecture.** Our model reproduces the paper's physical- and +logical-qubit counts exactly, the 24 fitted rates of its Table 10 (each +within the range its fits' printed parameters allow), every failure probability +of its Table 10 from its instruction counts, and its error-rate bounds for the +Ising program. Its other differences are explained in their rows: + +- Its four mean compile costs (Fig. 9) come out to every printed digit when a + shift's action is taken as A on X̄ and A⁻¹ on Z̄, on both halves of a module. + That is what its authors' released compiler did in `bicycle_cliffords` 0.1.0 + (`src/measurement.rs`). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is + how the operators move, and ours follows the operators; its version 0.2.1 + takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. (On the printed basis a shift + acts as A on the first half and Aᵀ on the second, where the paper says A on + both; taken as the paper says, with Z̄ by A⁻ᵀ, every cost comes out as ours.) +- Its idle and shift rates are the simulated circuit's failure, undivided, + where its text says they are divided by the circuit's C cycles. Ours divides, + as the text says, and that choice is about a third of a point of the gross + capability at 10⁻³ (its own row). +- Its Ising program takes 15,542,400 T gates in its tables and 19,036,800 in + its text and Fig. 1. pygridsynth 1.0.1, the version it cites, gives the + 15,542,400 exactly. +- Its gross Ising machine of 4,817 qubits takes the adapter of the 10⁻³ + factory beside the 10⁻⁴ factory it names (4,801). +- The Ising program's inter-module measurements match its 946,800 with the + factory after the last module; before the first, 781,200. The paper prints + neither: this is the one choice here made to fit a published figure. + +Two differences are open. Its gross capabilities at 10⁻⁴ need a failure per +rotation of 8.3·10⁻⁷ that does not depend on the machine's size, where its +factory's printed error is 4·10⁻⁸; its released compiler's capability estimate +at those machines would settle it, and this check does not run third-party +code. And the earlier paper's memory rate for the same codes, arXiv:2308.07915, +is about 1,250 times the later's fit at the same p: their decoders, circuits and +fitting differ, and which accounts for it is not found here. + +**RSA-2048 (2021).** The re-run of its cost script reproduces every row of its +Table 3 to the digits it prints, which it takes up to two, and its Table 1's +Toffoli count. Its title's 8 hours is the re-run's 7.31 hours expected across +retries, taken up to one digit. Its hand-worked §2.14 (23 million qubits) +rounds as it goes and says so. Its §2.12 reads a 6.4% distillation error from +an earlier paper's spreadsheet where its own script's formula gives 0.6%. Our +estimate job, given the same counts, finds the same code distance but takes 12 +times as long: it prices the planar architecture of arXiv:2211.07629, three +logical steps of `d` cycles a Toffoli, and has no reaction-limited clock. That +clock is in `quantum_arch` (**our limit** in the job). + +**FeMoCo.** Its floorplan, factory, Toffoli rate and 3 days come back on the +same clock; its §IV.C.5 says 3.5 days where §IV.C.4 says 3. The estimate job +again runs at the beat. + +**RSA-2048 (2025).** As `quantum_arch` documents: its stated durations give +10.6 hours a shot where it prints 12.07, its hot-storage counts disagree with +its own formula, and so on. Its Toffoli count comes back. + +**The planar model's source** comes back whole. + +## What this does not do + +- **The estimate job has no reaction-limited clock.** It prices the planar + architecture, and a laid-out machine that teleports its fixups takes about + a tenth of the time the job gives. `quantum_arch` has that clock; the job + does not offer it. +- **The bicycle machine is not scheduled.** A rotation's time is the mean of + its parts, a few percent under the paper's compiled critical path, and + repeated in-module measurements are not removed. +- **The bicycle rates are the paper's.** No decoder here has run its + instructions; its fits extrapolate far below what any simulation reaches, + and its two-gross measurement rates are assumed without data. +- **No third-party code runs here.** The paper's released compiler would + settle the open gross rows. + +## The rows + + + +### The bicycle machine: its parts + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| gross: code block `c`, by `2n` | arXiv:2506.03094 Table 1 | 288 | 288 | 0 | agrees | | +| gross: processing unit `u`, as tallied | arXiv:2506.03094 Table 1 | 90 | 90 | 0 | agrees | | +| gross: adapter `a`, by `2(d−1)` | arXiv:2506.03094 Table 1 | 22 | 22 | 0 | agrees | | +| two-gross: code block `c`, by `2n` | arXiv:2506.03094 Table 1 | 576 | 576 | 0 | agrees | | +| two-gross: processing unit `u`, as tallied | arXiv:2506.03094 Table 1 | 158 | 158 | 0 | agrees | | +| two-gross: adapter `a`, by `2(d−1)` | arXiv:2506.03094 Table 1 | 34 | 34 | 0 | agrees | | +| 10⁻³ gross factory: adapter `a′`, by `2d−1` | arXiv:2506.03094 Table 3 | 29 | 29 | 0 | agrees | | +| 10⁻³ two-gross factory: adapter `a′`, by `2d−1` | arXiv:2506.03094 Table 3 | 29 | 29 | 0 | agrees | | +| 10⁻⁴ gross factory: adapter `a′`, by `2d−1` | arXiv:2506.03094 Table 3 | 13 | 13 | 0 | agrees | | +| 10⁻⁴ two-gross factory: adapter `a′`, by `2d−1` | arXiv:2506.03094 Table 3 | 49 | 49 | 0 | agrees | | +| logical qubits in 5,000 physical qubits, gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a) | 121 | 121 | 0 | agrees | | +| logical qubits in 50,000 physical qubits, two-gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a) | 704 | 704 | 0 | agrees | | +| logical qubits in 5,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a) | 110 | 110 | 0 | agrees | | +| logical qubits in 50,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a) | 1,342 | 1,342 | 0 | agrees | | +| logical qubits in 50,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a) | 440 | 440 | 0 | agrees | | +| logical qubits in 500,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a) | 6,886 | 6,886 | 0 | agrees | | + +### The bicycle machine: logical error rates + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| gross `P_I` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 1.6·10⁻⁹ | 1.61e-9 | +0.36% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_U` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 4.0·10⁻⁷ | 4.01e-7 | +0.22% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_M` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 1.1·10⁻⁵ | 1.11e-5 | +0.72% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_C` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 2.0·10⁻³ | 2.01e-3 | +0.66% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_I` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 1.4·10⁻¹⁵ | 1.44e-15 | +2.93% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_U` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 6.1·10⁻¹³ | 6.07e-13 | -0.45% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_M` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 1.0·10⁻⁹ | 1.01e-9 | +1.43% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_C` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 4.8·10⁻⁸ | 4.81e-8 | +0.24% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_I` at p = 5·10⁻⁵ | arXiv:2506.03094 Table 10 | 4.5·10⁻¹⁷ | 4.46e-17 | -0.99% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_U` at p = 5·10⁻⁵ | arXiv:2506.03094 Table 10 | 2.2·10⁻¹⁴ | 2.16e-14 | -1.63% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_M` at p = 5·10⁻⁵ | arXiv:2506.03094 Table 10 | 5.7·10⁻¹¹ | 5.74e-11 | +0.70% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_C` at p = 5·10⁻⁵ | arXiv:2506.03094 Table 10 | 2.8·10⁻⁹ | 2.79e-9 | -0.41% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_I` at p = 3·10⁻⁵ | arXiv:2506.03094 Table 10 | 3.5·10⁻¹⁸ | 3.54e-18 | +1.02% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_U` at p = 3·10⁻⁵ | arXiv:2506.03094 Table 10 | 1.9·10⁻¹⁵ | 1.92e-15 | +0.92% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_M` at p = 3·10⁻⁵ | arXiv:2506.03094 Table 10 | 5.9·10⁻¹² | 5.90e-12 | +0.07% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_C` at p = 3·10⁻⁵ | arXiv:2506.03094 Table 10 | 3.2·10⁻¹⁰ | 3.18e-10 | -0.73% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_I` at p = 10⁻⁵ | arXiv:2506.03094 Table 10 | 1.5·10⁻²⁰ | 1.51e-20 | +0.62% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_U` at p = 10⁻⁵ | arXiv:2506.03094 Table 10 | 9.5·10⁻¹⁸ | 9.53e-18 | +0.29% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_M` at p = 10⁻⁵ | arXiv:2506.03094 Table 10 | 3.3·10⁻¹⁴ | 3.29e-14 | -0.28% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross `P_C` at p = 10⁻⁵ | arXiv:2506.03094 Table 10 | 2.1·10⁻¹² | 2.10e-12 | +0.03% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| two-gross `P_I` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 8.2·10⁻²¹ | 8.13e-21 | -0.84% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| two-gross `P_U` at p = 10⁻³ | arXiv:2506.03094 Table 10 | 3.3·10⁻¹⁵ | 3.24e-15 | -1.71% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| two-gross `P_I` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 5.4·10⁻³⁹ | 5.39e-39 | -0.23% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| two-gross `P_U` at p = 10⁻⁴ | arXiv:2506.03094 Table 10 | 1.3·10⁻³⁷ | 1.34e-37 | +3.07% | agrees | Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit. | +| gross idle, one syndrome cycle, p = 10⁻³ | arXiv:2506.03094 §A.7 against Table 2 | 10^−8.8 | 1.61e-10 | ×0.101 | their numbers | §A.7 says an idle's or a shift's rate is the simulated circuit's failure divided by its C cycles. Table 2 and Table 10 print the circuit's failure undivided: Table 6's fits give them so. Ours divides, as the text says, and is lower by C (10 for gross, 18 for two-gross). | +| two-gross idle, one syndrome cycle, p = 10⁻³ | arXiv:2506.03094 §A.7 against Table 2 | 10^−20.1 | 4.52e-22 | ×0.057 | their numbers | §A.7 says an idle's or a shift's rate is the simulated circuit's failure divided by its C cycles. Table 2 and Table 10 print the circuit's failure undivided: Table 6's fits give them so. Ours divides, as the text says, and is lower by C (10 for gross, 18 for two-gross). | +| gross automorphism, p = 10⁻³ | arXiv:2506.03094 §A.7 against Table 2 | 10^−6.4 | 4.01e-8 | ×0.101 | their numbers | §A.7 says an idle's or a shift's rate is the simulated circuit's failure divided by its C cycles. Table 2 and Table 10 print the circuit's failure undivided: Table 6's fits give them so. Ours divides, as the text says, and is lower by C (10 for gross, 18 for two-gross). | +| gross memory per cycle, p = 10⁻³ | arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 | 2·10⁻⁷ | 1.61e-10 | ×8.0e-4 | open | Two papers on the same code at the same p. The earlier (decoded by BP-OSD) measures 2·10⁻⁷ a syndrome cycle for the gross code at 10⁻³; the later's fits (Relay-BP) give a circuit of C idle cycles, divided by C as its text says, 1.61e-10: a factor of about 1,246 (about 125 undivided). Their decoders, circuits and fitting differ, and which accounts for the factor is not found here. Ours takes the later's fits. | +| gross memory per cycle, p = 10⁻⁴ | arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 | 8·10⁻¹³ | 1.44e-16 | ×1.8e-4 | open | Two papers on the same code at the same p. The earlier (decoded by BP-OSD) measures 2·10⁻⁷ a syndrome cycle for the gross code at 10⁻³; the later's fits (Relay-BP) give a circuit of C idle cycles, divided by C as its text says, 1.61e-10: a factor of about 1,246 (about 125 undivided). Their decoders, circuits and fitting differ, and which accounts for the factor is not found here. Ours takes the later's fits. | +| two-gross memory per cycle, p = 10⁻³ | arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 | 2·10⁻¹² | 4.52e-22 | ×2.3e-10 | open | Two papers on the same code at the same p. The earlier (decoded by BP-OSD) measures 2·10⁻⁷ a syndrome cycle for the gross code at 10⁻³; the later's fits (Relay-BP) give a circuit of C idle cycles, divided by C as its text says, 1.61e-10: a factor of about 1,246 (about 125 undivided). Their decoders, circuits and fitting differ, and which accounts for the factor is not found here. Ours takes the later's fits. | +| two-gross memory per cycle, p = 10⁻⁴ | arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 | 1·10⁻²² | 2.99e-40 | ×3.0e-18 | open | Two papers on the same code at the same p. The earlier (decoded by BP-OSD) measures 2·10⁻⁷ a syndrome cycle for the gross code at 10⁻³; the later's fits (Relay-BP) give a circuit of C idle cycles, divided by C as its text says, 1.61e-10: a factor of about 1,246 (about 125 undivided). Their decoders, circuits and fitting differ, and which accounts for the factor is not found here. Ours takes the later's fits. | + +### The bicycle machine: the compile + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| native measurements | arXiv:2506.03094 §3.2 | 540 | 540 | 0 | agrees | | +| any shift, in generators, at most | arXiv:2506.03094 §1.2 | 2 | 2 | 0 | agrees | | +| native rotations | arXiv:2506.03094 §3.2 | 510 | 245 | ×0.480 | their numbers | §3.2 defines a native rotation by a native measurement Q ⊗ R with Q one of X, Y, Z on the pivot: there are 245 such R. Over all the native measurements there are 510 distinct R ≠ I: 284 occur with Q = I, 19 of them also with Q ≠ I. That its 510 counts those is our inference from the matching count. Fig. 9's first bin holds 245. | +| mean in-module measurements per Pauli, pivot basis chosen, gross | arXiv:2506.03094 Fig. 9 caption, right | 18.48 | 18.526 | +0.25% | their numbers | Our search runs from the printed polynomials, basis and native set, over the 4¹¹ − 1 Paulis (4¹² − 1 with the pivot fixed). The authors' released compiler, bicycle_cliffords 0.1.0 (crates.io, 2025-07-31), takes a shift's action in src/measurement.rs as A on X̄ and A⁻¹ on Z̄, on both halves of a module; taken so, our search gives the source's four means to every printed digit (a test in quantum_bicycle holds this). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is how the operators move and what ours follows; its version 0.2.1 takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. The difference moves a capability here by under 0.6%. | +| the same, two-gross | arXiv:2506.03094 Fig. 9 caption, right | 18.58 | 18.482 | -0.53% | their numbers | Our search runs from the printed polynomials, basis and native set, over the 4¹¹ − 1 Paulis (4¹² − 1 with the pivot fixed). The authors' released compiler, bicycle_cliffords 0.1.0 (crates.io, 2025-07-31), takes a shift's action in src/measurement.rs as A on X̄ and A⁻¹ on Z̄, on both halves of a module; taken so, our search gives the source's four means to every printed digit (a test in quantum_bicycle holds this). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is how the operators move and what ours follows; its version 0.2.1 takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. The difference moves a capability here by under 0.6%. | +| mean in-module measurements per Pauli, pivot basis fixed, gross | arXiv:2506.03094 Fig. 9 caption, left | 18.55 | 18.593 | +0.23% | their numbers | Our search runs from the printed polynomials, basis and native set, over the 4¹¹ − 1 Paulis (4¹² − 1 with the pivot fixed). The authors' released compiler, bicycle_cliffords 0.1.0 (crates.io, 2025-07-31), takes a shift's action in src/measurement.rs as A on X̄ and A⁻¹ on Z̄, on both halves of a module; taken so, our search gives the source's four means to every printed digit (a test in quantum_bicycle holds this). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is how the operators move and what ours follows; its version 0.2.1 takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. The difference moves a capability here by under 0.6%. | +| the same, two-gross | arXiv:2506.03094 Fig. 9 caption, left | 18.67 | 18.561 | -0.58% | their numbers | Our search runs from the printed polynomials, basis and native set, over the 4¹¹ − 1 Paulis (4¹² − 1 with the pivot fixed). The authors' released compiler, bicycle_cliffords 0.1.0 (crates.io, 2025-07-31), takes a shift's action in src/measurement.rs as A on X̄ and A⁻¹ on Z̄, on both halves of a module; taken so, our search gives the source's four means to every printed digit (a test in quantum_bicycle holds this). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is how the operators move and what ours follows; its version 0.2.1 takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. The difference moves a capability here by under 0.6%. | + +### The bicycle machine: what a machine of a given size can run + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| T count at failure ⅓, 5,000 physical qubits, gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 13.5958 | 1.365e1 | +0.42% | assumption | Ours divides P_I and P_U by C, as §A.7's text says (the rates section); with them undivided, as Table 10 prints them, ours is 1.361e1 (next row). That choice is +0.30% of the difference, and the sampling below +0.12%. | +| the same, P_I and P_U undivided, as Table 10 prints them, 5,000 physical qubits, gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 13.5958 | 1.361e1 | +0.12% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. | +| timesteps per T, 5,000 physical qubits, gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 3367.15 | 3190.77 | -5.24% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| T count at failure ⅓, 50,000 physical qubits, two-gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 3.4712·10⁶ | 3.478e6 | +0.21% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. Our division of P_I and P_U by C, as §A.7's text says, is +0.00% of it (next row: undivided). | +| the same, P_I and P_U undivided, as Table 10 prints them, 50,000 physical qubits, two-gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 3.4712·10⁶ | 3.478e6 | +0.20% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. | +| timesteps per T, 50,000 physical qubits, two-gross, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), source files | 7057.34 | 7060.97 | +0.05% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| T count at failure ⅓, 5,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 2.2209·10⁵ | 4.701e5 | ×2.117 | open | Its two gross rows at this p need a failure per rotation of 6.70·10⁻⁸ per module (ours: 6.69·10⁻⁸) plus 8.3·10⁻⁷ whatever the machine's size. Ours has 4·10⁻⁸ there: Table 3's factory, an injection's inter-module measurement and one link fewer. Nothing the paper prints accounts for the other 7.9·10⁻⁷. Its authors' compiler is released (bicycle_compiler 0.1.0 on crates.io): its capability estimate at these two machines would settle it; this check does not run it. | +| the same, P_I and P_U undivided, as Table 10 prints them, 5,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 2.2209·10⁵ | 4.700e5 | ×2.116 | open | Its two gross rows at this p need a failure per rotation of 6.70·10⁻⁸ per module (ours: 6.69·10⁻⁸) plus 8.3·10⁻⁷ whatever the machine's size. Ours has 4·10⁻⁸ there: Table 3's factory, an injection's inter-module measurement and one link fewer. Nothing the paper prints accounts for the other 7.9·10⁻⁷. Its authors' compiler is released (bicycle_compiler 0.1.0 on crates.io): its capability estimate at these two machines would settle it; this check does not run it. | +| timesteps per T, 5,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 3090.55 | 2912.77 | -5.75% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| T count at failure ⅓, 50,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 3.7033·10⁴ | 4.064e4 | +9.73% | open | Its two gross rows at this p need a failure per rotation of 6.70·10⁻⁸ per module (ours: 6.69·10⁻⁸) plus 8.3·10⁻⁷ whatever the machine's size. Ours has 4·10⁻⁸ there: Table 3's factory, an injection's inter-module measurement and one link fewer. Nothing the paper prints accounts for the other 7.9·10⁻⁷. Its authors' compiler is released (bicycle_compiler 0.1.0 on crates.io): its capability estimate at these two machines would settle it; this check does not run it. | +| the same, P_I and P_U undivided, as Table 10 prints them, 50,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 3.7033·10⁴ | 4.063e4 | +9.71% | open | Its two gross rows at this p need a failure per rotation of 6.70·10⁻⁸ per module (ours: 6.69·10⁻⁸) plus 8.3·10⁻⁷ whatever the machine's size. Ours has 4·10⁻⁸ there: Table 3's factory, an injection's inter-module measurement and one link fewer. Nothing the paper prints accounts for the other 7.9·10⁻⁷. Its authors' compiler is released (bicycle_compiler 0.1.0 on crates.io): its capability estimate at these two machines would settle it; this check does not run it. | +| timesteps per T, 50,000 physical qubits, gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 3135.16 | 2912.77 | -7.09% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| T count at failure ⅓, 50,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 7.0154·10¹⁵ | 7.033e15 | +0.26% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. Our division of P_I and P_U by C, as §A.7's text says, is +0.00% of it (next row: undivided). | +| the same, P_I and P_U undivided, as Table 10 prints them, 50,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 7.0154·10¹⁵ | 7.033e15 | +0.26% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. | +| timesteps per T, 50,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 5435.87 | 5300.97 | -2.48% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| T count at failure ⅓, 500,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 4.4830·10¹⁴ | 4.494e14 | +0.25% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. Our division of P_I and P_U by C, as §A.7's text says, is +0.00% of it (next row: undivided). | +| the same, P_I and P_U undivided, as Table 10 prints them, 500,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 4.4830·10¹⁴ | 4.494e14 | +0.25% | their model | Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them. | +| timesteps per T, 500,000 physical qubits, two-gross, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), source files | 5436.91 | 5300.97 | -2.50% | our limit | Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest. | +| surface code: logical qubits, 5,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 45 | 45 | 0 | agrees | | +| surface code: T count, its formula, 5,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 7 | 7 | 0 | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 5,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 7 | 1.23e2 | ×17.637 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 5,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a) | 381 | 381 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | +| surface code: logical qubits, 50,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 42 | 42 | 0 | agrees | | +| surface code: T count, its formula, 50,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 2.5·10⁶ | 2.46e6 | -1.58% | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 50,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a), §A.10 | 2.5·10⁶ | 1.32e8 | ×52.910 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 50,000 qubits, p = 10⁻³ | arXiv:2506.03094 Fig. 10(a) | 2,269 | 2,269 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | +| surface code: logical qubits, 5,000 qubits, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), §A.10 | 21 | 21 | 0 | agrees | | +| surface code: T count, its formula, 5,000 qubits, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), §A.10 | 377,928 | 377,928 | 0 | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 5,000 qubits, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a), §A.10 | 377,928 | 2.65e7 | ×70.000 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 5,000 qubits, p = 10⁻⁴ | arXiv:2506.03094 Fig. 10(a) | 115 | 115 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | +| surface code: logical qubits, 50,000 qubits, p = 10⁻⁴, d = 7 | arXiv:2506.03094 Fig. 10(a), §A.10 | 250 | 250 | 0 | agrees | | +| surface code: T count, its formula, 50,000 qubits, p = 10⁻⁴, d = 7 | arXiv:2506.03094 Fig. 10(a), §A.10 | 31,746 | 31,746 | 0 | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 50,000 qubits, p = 10⁻⁴, d = 7 | arXiv:2506.03094 Fig. 10(a), §A.10 | 31,746 | 2.22e6 | ×70.000 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 50,000 qubits, p = 10⁻⁴, d = 7 | arXiv:2506.03094 Fig. 10(a) | 115 | 115 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | +| surface code: logical qubits, 50,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 27 | 27 | 0 | agrees | | +| surface code: T count, its formula, 50,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 1.2·10¹⁵ | 1.21e15 | +0.86% | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 50,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 1.2·10¹⁵ | 2.06e17 | ×1.7e2 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 50,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a) | 509 | 509 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | +| surface code: logical qubits, 500,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 416 | 416 | 0 | agrees | | +| surface code: T count, its formula, 500,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 7.9·10¹³ | 7.86e13 | -0.56% | agrees | §A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count. | +| surface code: T count, our code model, 500,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a), §A.10 | 7.9·10¹³ | 1.34e16 | ×1.7e2 | their model | Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`. | +| surface code: timesteps per T, 500,000 qubits, p = 10⁻⁴, d = 17 | arXiv:2506.03094 Fig. 10(a) | 509 | 509 | 0 | agrees | §A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d. | + +### The bicycle machine: the 10 × 10 Ising model + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| physical qubits, two-gross | arXiv:2506.03094 §4 | 8,138 | 8,138 | 0 | agrees | | +| physical qubits, gross | arXiv:2506.03094 §4 | 4,817 | 4,801 | -0.33% | their numbers | §4 names the 10⁻⁴ gross factory, whose adapter is 13 qubits (Table 3). 4,817 takes the 29 of the 10⁻³ factory. | +| rotations | arXiv:2506.03094 §A.10 | 184,000 | 184,000 | 0 | agrees | | +| inter-module measurements `N_C`, gross | arXiv:2506.03094 Table 9 | 9.4680·10⁵ | 946,800 | 0 | assumption | Sites in order and the factory after the last module give the paper's count; before the first module, 781,200. The paper prints neither, and this is the end chosen to match it: the one choice in this check made to fit a published figure. | +| the same on two-gross | arXiv:2506.03094 Table 9 | 9.4680·10⁵ | 946,800 | 0 | assumption | Sites in order and the factory after the last module give the paper's count; before the first module, 781,200. The paper prints neither, and this is the end chosen to match it: the one choice in this check made to fit a published figure. | +| in-module measurements `N_M`, gross | arXiv:2506.03094 Table 9, source files | 4,215,240 | 4,283,200 | +1.61% | our limit | Ours removes no repeated in-module measurement, which theirs does (§4), and takes sites in order, which it does not print. Its counts also carry the compile section's action of a shift on Z̄, which puts its gross below its two-gross; ours has them the other way round. | +| in-module measurements `N_M`, two-gross | arXiv:2506.03094 Table 9, source files | 4,238,828 | 4,148,800 | -2.12% | our limit | Ours removes no repeated in-module measurement, which theirs does (§4), and takes sites in order, which it does not print. Its counts also carry the compile section's action of a shift on Z̄, which puts its gross below its two-gross; ours has them the other way round. | +| automorphisms `N_U`, gross | arXiv:2506.03094 Table 9, source files | 2,270,400 | 2,728,800 | +20.19% | our limit | Ours takes the fewest shifts on a path of fewest measurements, and merges none; theirs is a compiler's. | +| automorphisms `N_U`, two-gross | arXiv:2506.03094 Table 9, source files | 2,294,400 | 2,368,000 | +3.21% | our limit | Ours takes the fewest shifts on a path of fewest measurements, and merges none; theirs is a compiler's. | +| T injections `N_T` | arXiv:2506.03094 Table 9, source files | 15,542,400 | 14,665,600 | -5.64% | their model | pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives 79, 80, 81 and 84, and also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows. | +| T injections as §4 and Fig. 1(b) state | arXiv:2506.03094 §4; Fig. 1(b), source files | 1.9·10⁷ (Fig. 1(b): 19,036,800) | 14,665,600 | -22.96% | their numbers | §4 says N_T = 1.9·10⁷ and Fig. 1(b) plots 19,036,800. Table 9 and every failure probability of Table 10 take 15,542,400, which the row above compares ours with. | +| idles `N_I`, two-gross, from its `N_T` | arXiv:2506.03094 Table 9, source files | 41,636,714,298 | 42,100,476,000 | +1.11% | our limit | Ours: every module idles while the factory works, and otherwise never. Theirs counts the idles of its schedule. | +| failure probability, gross, p = 10⁻³, its counts | arXiv:2506.03094 Table 10 | 3.33·10⁴ | 3.331e4 | +0.03% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, gross, p = 10⁻⁴, its counts | arXiv:2506.03094 Table 10 | 7.97·10⁻¹ | 7.977e-1 | +0.09% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, gross, p = 5·10⁻⁵, its counts | arXiv:2506.03094 Table 10 | 4.62·10⁻² | 4.622e-2 | +0.05% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, gross, p = 3·10⁻⁵, its counts | arXiv:2506.03094 Table 10 | 5.27·10⁻³ | 5.263e-3 | -0.14% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, gross, p = 10⁻⁵, its counts | arXiv:2506.03094 Table 10 | 3.48·10⁻⁵ | 3.478e-5 | -0.07% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, two-gross, p = 10⁻³, its counts | arXiv:2506.03094 Table 10 | 1.65·10⁻² | 1.653e-2 | +0.19% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, two-gross, p = 10⁻⁴, its counts | arXiv:2506.03094 Table 10 | 1.65·10⁻¹¹ | 1.653e-11 | +0.19% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. | +| failure probability, two-gross, p = 10⁻³, a T injection at its factory | arXiv:2506.03094 Table 10 | 1.65·10⁻² | 3.274e-1 | ×19.841 | their model | Table 10 sets `P_T = P_C` ("the T factory can be chosen to distill states of sufficient fidelity") and §4 neglects the factory. At the factory it allocates, Table 3's 2·10⁻⁸, the T injections alone fail with 0.31. | +| physical error rate for failure 10⁻³, two-gross | arXiv:2506.03094 §A.10 | 7.32·10⁻⁴ | 7.322e-4 | +0.03% | agrees | Its power law through two of its points of Table 10, solved for a failure of 10⁻³; the band moves the fits' parameters by their last printed digit. | +| physical error rate for failure 10⁻³, gross | arXiv:2506.03094 §A.10 | 2.09·10⁻⁵ | 2.086e-5 | -0.20% | agrees | Its power law through two of its points of Table 10, solved for a failure of 10⁻³; the band moves the fits' parameters by their last printed digit. | +| surface code, d = 9: physical error rate, Eq. (96) | arXiv:2506.03094 §A.10, Eq. (96) | 5.48·10⁻⁵ | 5.476e-5 | -0.07% | agrees | With §4's N_T of 19,036,800. §4 prints it truncated to two digits. | +| surface code, d = 9: physical error rate, our code model | arXiv:2506.03094 §A.10, Eq. (96) | 5.48·10⁻⁵ | 1.430e-4 | ×2.610 | their model | As in the capability section: a patch's error per `d` cycles, not a cycle's times `d`, and `(d+1)/2`. | +| surface code, d = 9: physical qubits | arXiv:2506.03094 §4, Fig. 10(b) | 17,010 | 33,210 | +95.24% | their numbers | §4 and Fig. 10(b) count `2d²` a logical qubit. Eq. (96) puts `2n` patches in each rotation, and §A.10's capability comparison reserves half the patches for routing: `4d²`. Ours is `4d²`. | +| surface code, d = 17: physical error rate, Eq. (96) | arXiv:2506.03094 §A.10, Eq. (96) | 5.89·10⁻⁴ | 5.892e-4 | +0.03% | agrees | With §4's N_T of 19,036,800. §4 prints it truncated to two digits. | +| surface code, d = 17: physical error rate, our code model | arXiv:2506.03094 §A.10, Eq. (96) | 5.89·10⁻⁴ | 9.446e-4 | +60.38% | their model | As in the capability section: a patch's error per `d` cycles, not a cycle's times `d`, and `(d+1)/2`. | +| surface code, d = 17: physical qubits | arXiv:2506.03094 §4, Fig. 10(b) | 58,263 | 116,063 | +99.21% | their numbers | §4 and Fig. 10(b) count `2d²` a logical qubit. Eq. (96) puts `2n` patches in each rotation, and §A.10's capability comparison reserves half the patches for routing: `4d²`. Ours is `4d²`. | +| surface code, d = 19: physical error rate, Eq. (96) | arXiv:2506.03094 §A.10, Eq. (96) | 7.85·10⁻⁴ | 7.845e-4 | -0.06% | agrees | With §4's N_T of 19,036,800. §4 prints it truncated to two digits. | +| surface code, d = 19: physical error rate, our code model | arXiv:2506.03094 §A.10, Eq. (96) | 7.85·10⁻⁴ | 1.196e-3 | +52.36% | their model | As in the capability section: a patch's error per `d` cycles, not a cycle's times `d`, and `(d+1)/2`. | +| surface code, d = 19: physical qubits | arXiv:2506.03094 §4, Fig. 10(b) | 72,663 | 144,863 | +99.36% | their numbers | §4 and Fig. 10(b) count `2d²` a logical qubit. Eq. (96) puts `2n` patches in each rotation, and §A.10's capability comparison reserves half the patches for routing: `4d²`. Ours is `4d²`. | + +### RSA-2048 on the surface code (arXiv:1905.09749, Quantum 5, 433, 2021) + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| physical qubits, re-run on its layout | arXiv:1905.09749 Table 3 | 20 (megaqubits) | 19,248,768 | -3.76% | agrees | Its script takes each figure up to two digits. | +| hours per run, re-run | arXiv:1905.09749 Table 3 | 5.1 | 5.05 | -1.06% | agrees | Its script takes each figure up to two digits. | +| retry risk, re-run | arXiv:1905.09749 Table 3 | 31% | 0.3098 | -0.08% | agrees | Its script takes it up to a whole percent. | +| megaqubit-days per run, re-run | arXiv:1905.09749 Table 3 | 4.1 | 4.05 | -1.29% | agrees | Its script takes each figure up to two digits. | +| Toffolis, re-run | arXiv:1905.09749 Table 1 | 2.7 (billions) | 2.698e9 | -0.06% | agrees | | +| factories in each piece, re-run | arXiv:1905.09749 §2.10 | 14 | 14 | 0 | agrees | | +| hours, expected across retries | arXiv:1905.09749 title | 8 hours | 7.31 | -8.62% | agrees | The re-run's 5.05 hours a run at its 30.98% retry risk is 7.31 hours expected; the title takes that up to one digit. | +| physical qubits, its worked case, level-1 distance 17 | arXiv:1905.09749 §2.14 | "approximately 23 million" | 21,616,448 | -6.02% | their numbers | That section rounds as it goes, and says so: 226 × 63 patches, where the script's layout at these distances has 226 × 61. | +| distillation error of 3·10⁹ CCZ states, distances 17 and 27 | arXiv:1905.09749 §2.12 | 6.4% | 0.0060 | ×0.094 | their numbers | That section reads a spreadsheet of an earlier paper. Its own script's formula, which produced its tables, gives 0.6%. | +| code distance, job, assumed code model | arXiv:1905.09749 §2.13 (d = 27); its counts in the estimate job | 27 | 27 | 0 | agrees | | +| code distance, job, code model fitted to our decoders | arXiv:1905.09749 §2.13 (d = 27); its counts in the estimate job | 27 | 27 | 0 | agrees | Our fit is `a = 0.0233, p* = 0.01004`, extrapolated from p = 0.002–0.005. | +| physical qubits, job, assumed code model | arXiv:1905.09749 Table 3; its counts in the estimate job | 20 (megaqubits) | 18,906,836 | -5.47% | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| physical qubits, job, fitted code model | arXiv:1905.09749 Table 3; its counts in the estimate job | 20 (megaqubits) | 18,906,836 | -5.47% | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| physical qubits, the planar model on the source's patch and error rule | arXiv:1905.09749 Table 3; its counts in the estimate job | 20 (megaqubits) | 27,571,456 | +37.86% | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| hours per run, job, assumed code model | arXiv:1905.09749 Table 3; its counts in the estimate job | 5.1 | 60.71 | ×11.904 | our limit | The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does. | +| hours per run, the planar model on the source's patch and error rule | arXiv:1905.09749 Table 3; its counts in the estimate job | 5.1 | 69.71 | ×13.668 | our limit | The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does. | +| factories, job | arXiv:1905.09749 §2.10; its counts in the estimate job | 14 CCZ per piece, 28 in all | 16 T factories | — | our limit | A CCZ state costs four T states in the planar model. | +| 1024 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 9.7 | 9.62 | -0.78% | agrees | Its script takes each figure up to two digits. | +| 1024 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 1.3 | 1.26 | -2.79% | agrees | Its script takes each figure up to two digits. | +| 3072 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 38 | 37.90 | -0.27% | agrees | Its script takes each figure up to two digits. | +| 3072 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 12 | 11.61 | -3.28% | agrees | Its script takes each figure up to two digits. | +| 4096 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 55 | 54.62 | -0.70% | agrees | Its script takes each figure up to two digits. | +| 4096 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 22 | 21.08 | -4.20% | agrees | Its script takes each figure up to two digits. | +| 8192 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 140 | 133.32 | -4.77% | agrees | Its script takes each figure up to two digits. | +| 8192 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 86 | 85.54 | -0.53% | agrees | Its script takes each figure up to two digits. | +| 12288 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 200 | 199.98 | -0.01% | agrees | Its script takes each figure up to two digits. | +| 12288 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 200 | 192.47 | -3.76% | agrees | Its script takes each figure up to two digits. | +| 16384 bits: megaqubits, re-run | arXiv:1905.09749 Table 3 | 270 | 266.64 | -1.25% | agrees | Its script takes each figure up to two digits. | +| 16384 bits: hours per run, re-run | arXiv:1905.09749 Table 3 | 350 | 342.54 | -2.13% | agrees | Its script takes each figure up to two digits. | + +### FeMoCo on the surface code (arXiv:2011.03494, PRX Quantum 2, 030305, 2021) + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| physical qubits, p = 10⁻³, its floorplan | arXiv:2011.03494 Fig. 10 | ≈ 4·10⁶ | 3,907,584 | -2.31% | agrees | 53 × 36 = 1,908 patches of 2(d+1)², as its caption works out. | +| days, p = 10⁻³, re-run on our clock | arXiv:2011.03494 §IV.C.4 | 3 | 3.01 | +0.22% | agrees | | +| what sets the pace, p = 10⁻³ | arXiv:2011.03494 §IV.C.4 | the factories (a "tick" limit) | MagicStates | — | agrees | | +| Toffoli rate, kHz | arXiv:2011.03494 §IV.C.4 | 25 | 25.81 | +3.23% | agrees | Four factories each finish a CCZ state every 5·31 cycles: 25.8 kHz, printed as 25. | +| distillation failure over 10¹⁰ Toffolis | arXiv:2011.03494 §IV.C.4 | "roughly 0.1%" | 0.00103 | +2.85% | agrees | | +| physical qubits, p = 10⁻⁴ | arXiv:2011.03494 §IV.C.5 | "one million" | 976,896 | -2.31% | agrees | | +| days, p = 10⁻⁴, re-run on our clock | arXiv:2011.03494 §IV.C.5 | 1.5 | 1.45 | -3.01% | agrees | | +| days at p = 10⁻³, as §IV.C.5 states | arXiv:2011.03494 §IV.C.5 | 3.5 | 3.01 | -14.09% | their numbers | §IV.C.4 says 3 days; §IV.C.5 scales "3.5 days" by 15/31 and gets 1.5, which is 3 days scaled. Its abstract says under four. | +| Toffolis, Table III against its layout | arXiv:2011.03494 Table III against §IV.C.4 | 5.3·10⁹ (Table III); 6.7·10⁹ (§IV.C.4) | — | — | their numbers | The paper's own two figures: Table III costs the algorithm with its own QROM choices; the layout takes an output expansion factor of 1 for the select reads (§IV.C.2), which holds fewer qubits and takes more Toffolis. Ours takes each in turn below. | +| logical qubits, Table III against its layout's data | arXiv:2011.03494 Table III against §IV.C.4 | 2,142 (Table III); under 700 data qubits (§IV.C.4) | — | — | their numbers | The paper's own two figures: Table III costs the algorithm with its own QROM choices; the layout takes an output expansion factor of 1 for the select reads (§IV.C.2), which holds fewer qubits and takes more Toffolis. Ours takes each in turn below. | +| code distance, its layout's counts | arXiv:2011.03494 §IV.C.4; its counts in the estimate job | 31 | 29 | -6.45% | assumption | Its rule is `0.1·(100p)^((d+1)/2)` a cycle (§IV.C.5: "Λ of 10"); the planar model's `0.03·(p/0.01)^((d+1)/2)` a step of `d` cycles, lower by `d/0.3`, one step of distance at this size. | +| physical qubits, job, Table III's counts | arXiv:2011.03494 §IV.C.4; its counts in the estimate job | ≈ 4·10⁶ | 8,419,152 | ×2.105 | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| physical qubits, job, its layout's counts | arXiv:2011.03494 §IV.C.4; its counts in the estimate job | ≈ 4·10⁶ | 3,529,152 | -11.77% | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| days, job, Table III's counts | arXiv:2011.03494 §IV.C.4; its counts in the estimate job | 3 | 5.34 | +77.89% | our limit | The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does. | +| days, job, its layout's counts | arXiv:2011.03494 §IV.C.4; its counts in the estimate job | 3 | 6.75 | ×2.250 | our limit | The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does. | +| physical qubits, job, p = 10⁻⁴ | arXiv:2011.03494 §IV.C.5; its counts in the estimate job | "one million" | 968,200 | -3.18% | our limit | The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand. | +| days, job, p = 10⁻⁴ | arXiv:2011.03494 §IV.C.5; its counts in the estimate job | 1.5 | 3.49 | ×2.328 | our limit | The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does. | + +### RSA-2048 on yoked surface codes (arXiv:2505.15917, 2025) + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| physical qubits | arXiv:2505.15917 §3.2 | 897,864 | 958,480 | +6.75% | their numbers | Ours keeps 152 logical qubits hot by its peak formula and buys whole grid blocks; its count keeps 131 and a fractional block. At its 131 and 430 qubits a logical qubit, our arithmetic gives its 897,864. | +| hours per shot | arXiv:2505.15917 §3.2 | 12.07 | 10.58 | -12.36% | their numbers | Its tallies at its stated durations give 10.62 hours, and its clock at whole milliseconds 10.58. | +| days per factoring | arXiv:2505.15917 §3.2 | 4.96 | 4.31 | -13.20% | their numbers | From the hours per shot above; it also multiplies by 9.1 shots where its table gives 9.2. It rounds 4.96 up to "less than a week". | +| logical qubits | arXiv:2505.15917 §3.2 | 1,409 | 1,432 | +1.63% | their numbers | §3.2 prints 1,409 for its peak formula `m + 3f + 2ℓ + len m`, which evaluates to 1,432; its Table 4 gives 1,399. | +| Toffolis per factoring, by its counting rule | arXiv:2505.15917 §3.2 | "6.5 billion" | 6.50e9 | -0.04% | agrees | | + +### The planar model's own source (arXiv:2211.07629, 2022) + +| Quantity | Source | Published | Ours | Delta | Class | Why | +|---|---|---:|---:|---:|---|---| +| code distance, RSA-2048, (ns, 10⁻⁴) | arXiv:2211.07629 Appendix F | 13 | 13 | 0 | agrees | | +| factories | arXiv:2211.07629 Appendix F | 18 | 18 | 0 | agrees | | +| physical qubits | arXiv:2211.07629 Appendix F | 8.72M | 8,716,258 | -0.04% | agrees | | +| run time, hours | arXiv:2211.07629 Appendix F | 17 hours 43 mins | 17.723 | +0.04% | agrees | Table IV prints 18 hours. | + + diff --git a/wai/wai-quantum/examples/published_architectures.rs b/wai/wai-quantum/examples/published_architectures.rs new file mode 100644 index 000000000..bdc85961c --- /dev/null +++ b/wai/wai-quantum/examples/published_architectures.rs @@ -0,0 +1,28 @@ +//! Our estimators against published architectures: for each quantity the +//! published figure, ours, the delta and why they differ. +//! +//! ```text +//! cargo run --release --example published_architectures --features full # columns +//! cargo run --release --example published_architectures --features full -- --markdown # the page's rows +//! cargo run --release --example published_architectures --features full -- --guards # ours, for the guards +//! ``` +//! +//! The page is `docs/published-architectures.md`, and a test in the crate holds +//! its rows to this output. The published figures are in +//! `tests/data/published_figures.tsv`; `--guards` prints ours, to four +//! digits, for each row that does not agree, which is its line's guard there. +use wai_quantum::published_architectures::{guards, markdown, report, tally, text}; + +fn main() -> Result<(), String> { + let sections = report()?; + if std::env::args().any(|a| a == "--markdown") { + print!("{}", markdown(§ions)); + } else if std::env::args().any(|a| a == "--guards") { + print!("{}", guards(§ions)); + } else { + print!("{}", text(§ions)); + let counts: Vec = tally(§ions).iter().map(|(c, n)| format!("{} {n}", c.name())).collect(); + println!("rows by class: {}", counts.join(", ")); + } + Ok(()) +} diff --git a/wai/wai-quantum/src/lib.rs b/wai/wai-quantum/src/lib.rs index 007851d9e..2b3be6f3a 100644 --- a/wai/wai-quantum/src/lib.rs +++ b/wai/wai-quantum/src/lib.rs @@ -187,6 +187,13 @@ pub mod quantum_resource_receipt; #[cfg(feature = "quantum_arch")] pub mod quantum_arch; +#[cfg(feature = "quantum_arch")] +pub mod quantum_bicycle; + +// Our estimators beside the published figures. +#[cfg(all(feature = "quantum_arch", feature = "quantum_engine"))] +pub mod published_architectures; + #[cfg(feature = "quantum_frame")] pub mod quantum_frame; diff --git a/wai/wai-quantum/src/published_architectures.rs b/wai/wai-quantum/src/published_architectures.rs new file mode 100644 index 000000000..f8e650ee0 --- /dev/null +++ b/wai/wai-quantum/src/published_architectures.rs @@ -0,0 +1,925 @@ +//! Our estimators against published architectures: the figure, ours and the +//! delta, for each quantity, with why they differ. +//! +//! [`report`] feeds each source's stated assumptions into this crate's +//! estimators. Nothing is fitted to the figures it is compared with, with one +//! exception its row names: which end of the line the Ising program's T +//! factory sits at, which the source does not print. +//! +//! The published figures are kept apart from this code, in +//! `tests/data/published_figures.tsv`: each with where it is printed (a +//! section, table or figure; `source files` when quoted from a paper's arXiv +//! source with more digits than its PDF prints), as it is printed, its value, +//! and its rule. The report reads them from there, so regenerating the page +//! cannot move them, and a test holds every row to its rule: +//! +//! - a row that agrees, to the digits the source prints: `exact`; `nearest u` +//! (within half its last printed unit `u`); `up u` or `down u` (where the +//! source states or applies that rounding); or `band u`, where ours comes +//! from a fit whose printed parameters are themselves rounded, and the range +//! those parameters' last digit allows must meet the printed figure's; +//! - every other numeric row, a `guard`: our figure to four digits, so a +//! change to an estimator that moves it fails until the guard, the reason +//! and the page are updated together. +//! +//! Each row has a class ([`Class`]): the two agree; we were given or chose a +//! different assumption; the source models it differently; the source's own +//! figures disagree with each other or with its stated method; our estimator +//! has a limit there; or the difference is open, with what is known about it. +//! +//! ```text +//! cargo run --release --example published_architectures --features full +//! ``` + +use crate::engine::estimate; +use crate::engine::synth::rz::rz; +use crate::quantum_arch::{factoring_estimate, patch_qubits, windowed_factoring_estimate, Bound, DistilledCczFactory, FactoringParams, Machine as ArchMachine, PhaseupCount, Timing, WindowedFactoringParams}; +use crate::quantum_bicycle::{ising_layer, Code, Instruction, InstructionCounts, Machine, PauliCosts, PauliRotation, SpectrumFit, TFactory}; +use crate::quantum_resource::{estimate as planar, EstimateConfig, InstructionSet, LogicalCounts, PhysicalQubit, QecModel}; +use crate::repro::{exp, ln}; +use serde_json::{json, Value}; +use std::collections::BTreeMap; +use std::sync::OnceLock; + +/// Why a row's two numbers differ. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Class { + /// They agree to the digits the source prints. + Agrees, + /// We were given, or chose, a different assumption, stated in the row. + Assumption, + /// The source's model does this differently from ours, and the row says how. + TheirModel, + /// The source's own figures disagree with each other or with the method it + /// states, and the row says where. + TheirNumbers, + /// Our estimator does not model this, and the row says what it leaves out. + OurLimit, + /// Not resolved: the row says what is known. + Open, +} + +impl Class { + pub fn name(self) -> &'static str { + match self { + Class::Agrees => "agrees", + Class::Assumption => "assumption", + Class::TheirModel => "their model", + Class::TheirNumbers => "their numbers", + Class::OurLimit => "our limit", + Class::Open => "open", + } + } +} + +/// How a row's figure is held. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Rule { + Exact, + /// Within half the last printed unit. + Nearest(f64), + /// Printed rounded up to the unit: `value − unit < ours ≤ value`. + Up(f64), + /// Printed rounded down to the unit: `value ≤ ours < value + unit`. + Down(f64), + /// Ours from a fit with rounded parameters: the band they allow meets + /// the printed figure's half-unit interval. + Band(f64), + /// Not agreeing: ours, to four significant digits. + Guard(f64), + /// No number on the published side. + Text, +} + +/// A published figure: where it is printed, as printed, its value and rule. +#[derive(Clone, Debug, PartialEq)] +pub struct Pin { + pub source: String, + pub printed: String, + pub value: Option, + pub rule: Rule, +} + +/// The published figures, from `tests/data/published_figures.tsv`. +pub fn pins() -> &'static BTreeMap { + static PINS: OnceLock> = OnceLock::new(); + PINS.get_or_init(|| parse_pins(include_str!("../tests/data/published_figures.tsv")).expect("the published figures read")) +} + +fn parse_pins(text: &str) -> Result, String> { + let mut out = BTreeMap::new(); + for (n, line) in text.lines().enumerate().skip(1) { + if line.trim().is_empty() || line.starts_with('#') { + continue; + } + let f: Vec<&str> = line.split('\t').collect(); + let [id, source, printed, value, rule] = f.as_slice() else { return Err(format!("line {}: five tab-separated fields", n + 1)) }; + let num = |s: &str| s.parse::().map_err(|_| format!("line {}: {s} is not a number", n + 1)); + let value = if value.is_empty() { None } else { Some(num(value)?) }; + let mut words = rule.split_whitespace(); + let rule = match (words.next(), words.next()) { + (Some("exact"), None) => Rule::Exact, + (Some("text"), None) => Rule::Text, + (Some("nearest"), Some(u)) => Rule::Nearest(num(u)?), + (Some("up"), Some(u)) => Rule::Up(num(u)?), + (Some("down"), Some(u)) => Rule::Down(num(u)?), + (Some("band"), Some(u)) => Rule::Band(num(u)?), + (Some("guard"), Some(g)) => Rule::Guard(num(g)?), + _ => return Err(format!("line {}: no rule {rule}", n + 1)), + }; + if out.insert(id.to_string(), Pin { source: source.to_string(), printed: printed.to_string(), value, rule }).is_some() { + return Err(format!("line {}: {id} twice", n + 1)); + } + } + Ok(out) +} + +/// Whether `ours` (and its band, for a fitted figure) holds to `rule` at +/// `value`. +pub fn holds(rule: Rule, value: f64, ours: f64, band: Option<(f64, f64)>) -> bool { + let slack = 1e-12 * value.abs().max(ours.abs()); + match rule { + Rule::Exact => ours == value, + Rule::Nearest(u) => (ours - value).abs() <= u / 2.0 + slack, + Rule::Up(u) => value - u < ours && ours <= value + slack, + Rule::Down(u) => value - slack <= ours && ours < value + u, + Rule::Band(u) => band.is_some_and(|(lo, hi)| lo <= value + u / 2.0 + slack && hi >= value - u / 2.0 - slack), + Rule::Guard(g) => { + // Half a unit in the guard's fourth significant digit. + let unit = exp(ln(10.0) * (g.abs().log10().floor() - 3.0)); + (ours - g).abs() <= unit / 2.0 + slack + } + Rule::Text => false, + } +} + +/// One published quantity beside ours. +#[derive(Clone, Debug, PartialEq)] +pub struct Row { + /// Its line in the published figures. + pub id: String, + pub quantity: String, + pub source: String, + pub published: String, + pub ours: String, + pub delta: String, + pub class: Class, + pub why: String, + /// The published value and ours, when both are numbers. + pub values: Option<(f64, f64)>, + /// For a fitted figure, ours with the fit's parameters at the ends of + /// their last printed digit. + pub band: Option<(f64, f64)>, + pub rule: Rule, +} + +/// A target: the rows of one published estimate. +#[derive(Clone, Debug, PartialEq)] +pub struct Section { + pub title: String, + pub rows: Vec, +} + +/// How a number is printed. +#[derive(Clone, Copy)] +enum F { + /// Whole, with thousands separated. + I, + /// `d` digits after the point, scientific. + E(usize), + /// `d` digits after the point. + D(usize), +} + +fn print(x: f64, f: F) -> String { + match f { + F::I => { + let s = format!("{:.0}", x.abs()); + let mut out = String::new(); + for (i, c) in s.chars().enumerate() { + if i > 0 && (s.len() - i).is_multiple_of(3) { + out.push(','); + } + out.push(c); + } + if x < 0.0 { + out.insert(0, '-'); + } + out + } + F::E(d) => format!("{x:.d$e}"), + F::D(d) => format!("{x:.d$}"), + } +} + +fn delta(published: f64, ours: f64) -> String { + if published == ours { + return "0".into(); + } + let ratio = ours / published; + if !(0.5..=2.0).contains(&ratio) { + return if ratio >= 100.0 || ratio < 0.01 { format!("×{ratio:.1e}") } else { format!("×{ratio:.3}") }; + } + format!("{:+.2}%", 100.0 * (ratio - 1.0)) +} + +#[derive(Default)] +struct Rows { + rows: Vec, +} + +impl Rows { + fn pin(id: &str) -> Pin { + pins().get(id).cloned().unwrap_or_else(|| panic!("no published figure {id} in tests/data/published_figures.tsv")) + } + + fn push(&mut self, id: &str, quantity: String, ours: String, values: Option<(f64, f64)>, band: Option<(f64, f64)>, class: Class, why: String) { + let p = Rows::pin(id); + let delta = values.map_or("—".to_string(), |(v, o)| delta(v, o)); + self.rows.push(Row { id: id.into(), quantity, source: p.source, published: p.printed, ours, delta, class, why, values, band, rule: p.rule }); + } + + /// A number beside its published figure. + fn num(&mut self, id: &str, quantity: impl Into, ours: f64, f: F, class: Class, why: impl Into) { + let value = Rows::pin(id).value.unwrap_or_else(|| panic!("{id} has no value")); + self.push(id, quantity.into(), print(ours, f), Some((value, ours)), None, class, why.into()); + } + + /// A number from a fit, with the band its parameters' last digit allows. + #[allow(clippy::too_many_arguments)] + fn band(&mut self, id: &str, quantity: impl Into, ours: f64, band: (f64, f64), f: F, class: Class, why: impl Into) { + let value = Rows::pin(id).value.unwrap_or_else(|| panic!("{id} has no value")); + self.push(id, quantity.into(), print(ours, f), Some((value, ours)), Some(band), class, why.into()); + } + + /// A row with no number to compare on one side. + fn text(&mut self, id: &str, quantity: impl Into, ours: impl Into, class: Class, why: impl Into) { + self.push(id, quantity.into(), ours.into(), None, None, class, why.into()); + } +} + +fn section(title: &str, rows: Rows) -> Section { + Section { title: title.into(), rows: rows.rows } +} + +const MACHINES: [(&str, u64, &str, Code, fn() -> TFactory, f64); 6] = [ + ("gross_e3_5k", 5_000, "gross, p = 10⁻³", Code::Gross, TFactory::gross_e3, 1e-3), + ("two_e3_50k", 50_000, "two-gross, p = 10⁻³", Code::TwoGross, TFactory::two_gross_e3, 1e-3), + ("gross_e4_5k", 5_000, "gross, p = 10⁻⁴", Code::Gross, TFactory::gross_e4, 1e-4), + ("gross_e4_50k", 50_000, "gross, p = 10⁻⁴", Code::Gross, TFactory::gross_e4, 1e-4), + ("two_e4_50k", 50_000, "two-gross, p = 10⁻⁴", Code::TwoGross, TFactory::two_gross_e4, 1e-4), + ("two_e4_500k", 500_000, "two-gross, p = 10⁻⁴", Code::TwoGross, TFactory::two_gross_e4, 1e-4), +]; + +fn machines() -> Section { + let mut r = Rows::default(); + for (tag, name, code) in [("gross", "gross", Code::Gross), ("two", "two-gross", Code::TwoGross)] { + r.num(&format!("table1.{tag}.c"), format!("{name}: code block `c`, by `2n`"), code.code_qubits() as f64, F::I, Class::Agrees, ""); + r.num(&format!("table1.{tag}.u"), format!("{name}: processing unit `u`, as tallied"), code.lpu_qubits() as f64, F::I, Class::Agrees, ""); + r.num(&format!("table1.{tag}.a"), format!("{name}: adapter `a`, by `2(d−1)`"), code.adapter_qubits() as f64, F::I, Class::Agrees, ""); + } + for (tag, name, factory) in [("gross_e3", "10⁻³ gross factory", TFactory::gross_e3()), ("two_e3", "10⁻³ two-gross factory", TFactory::two_gross_e3()), ("gross_e4", "10⁻⁴ gross factory", TFactory::gross_e4()), ("two_e4", "10⁻⁴ two-gross factory", TFactory::two_gross_e4())] { + r.num(&format!("table3.{tag}.adapter"), format!("{name}: adapter `a′`, by `2d−1`"), factory.adapter_qubits() as f64, F::I, Class::Agrees, ""); + } + for (tag, q, name, code, factory, _) in MACHINES { + let m = Machine::largest(code, factory(), q).expect("a module fits"); + r.num(&format!("fig10a.{tag}.n"), format!("logical qubits in {} physical qubits, {name}", print(q as f64, F::I)), m.logical_qubits() as f64, F::I, Class::Agrees, ""); + } + section("The bicycle machine: its parts", r) +} + +/// The last printed digit of the failure-spectrum fits' parameters (Table 6 +/// prints `ln f₀` and `γ` to two decimals): half of it. +const FIT_DIGIT: f64 = 0.005; + +/// A rate as the source's tables print it, from a fit whose `ln f₀` and `γ` +/// are moved by `d`. The rate grows with both, as every failing weight is at +/// least `w₀`. +fn fitted(code: Code, i: Instruction, p: f64, d: f64) -> f64 { + match code.fit(i) { + Some(f) => SpectrumFit { ln_f0: f.ln_f0 + d, gamma: f.gamma + d, ..f }.circuit_failure(p), + None => code.tabulated_rate(i, p), + } +} + +fn fitted_band(code: Code, i: Instruction, p: f64) -> (f64, f64) { + (fitted(code, i, p, -FIT_DIGIT), fitted(code, i, p, FIT_DIGIT)) +} + +/// Eq. (26) at the rates as the tables print them, a T injection at an +/// inter-module measurement's rate, the fits moved by `d`. +fn tabulated_sum(n: &InstructionCounts, code: Code, p: f64, d: f64) -> f64 { + let r = |i| fitted(code, i, p, d); + n.idle * r(Instruction::Idle) + n.automorphism * r(Instruction::Automorphism) + n.in_module * r(Instruction::InModule) + (n.inter_module + n.t_injection) * r(Instruction::InterModule) +} + +fn rates() -> Section { + let mut r = Rows::default(); + let names = [(Instruction::Idle, "I", "P_I"), (Instruction::Automorphism, "U", "P_U"), (Instruction::InModule, "M", "P_M"), (Instruction::InterModule, "C", "P_C")]; + let fmt = F::E(2); + let why = "Ours evaluates Table 6's fits by Eq. (83); the band is ours with their parameters at the ends of their last printed digit."; + for (p, tag, label) in [(1e-3, "1e-3", "10⁻³"), (1e-4, "1e-4", "10⁻⁴"), (5e-5, "5e-5", "5·10⁻⁵"), (3e-5, "3e-5", "3·10⁻⁵"), (1e-5, "1e-5", "10⁻⁵")] { + for (i, short, name) in names { + r.band(&format!("table10.gross.{short}.{tag}"), format!("gross `{name}` at p = {label}"), Code::Gross.tabulated_rate(i, p), fitted_band(Code::Gross, i, p), fmt, Class::Agrees, why); + } + } + for (p, tag, label) in [(1e-3, "1e-3", "10⁻³"), (1e-4, "1e-4", "10⁻⁴")] { + for (i, short, name) in &names[..2] { + r.band(&format!("table10.two.{short}.{tag}"), format!("two-gross `{name}` at p = {label}"), Code::TwoGross.tabulated_rate(*i, p), fitted_band(Code::TwoGross, *i, p), fmt, Class::Agrees, why); + } + } + let per = "§A.7 says an idle's or a shift's rate is the simulated circuit's failure divided by its C cycles. Table 2 and Table 10 print the circuit's failure undivided: Table 6's fits give them so. Ours divides, as the text says, and is lower by C (10 for gross, 18 for two-gross)."; + r.num("a7.gross.idle", "gross idle, one syndrome cycle, p = 10⁻³", Code::Gross.rate(Instruction::Idle, 1e-3).value, fmt, Class::TheirNumbers, per); + r.num("a7.two.idle", "two-gross idle, one syndrome cycle, p = 10⁻³", Code::TwoGross.rate(Instruction::Idle, 1e-3).value, fmt, Class::TheirNumbers, per); + r.num("a7.gross.shift", "gross automorphism, p = 10⁻³", Code::Gross.rate(Instruction::Automorphism, 1e-3).value, fmt, Class::TheirNumbers, per); + let gross_gap = 2e-7 / Code::Gross.rate(Instruction::Idle, 1e-3).value; + let two = format!( + "Two papers on the same code at the same p. The earlier (decoded by BP-OSD) measures 2·10⁻⁷ a syndrome cycle for the gross code at 10⁻³; the later's fits (Relay-BP) give a circuit of C idle cycles, divided by C as its text says, {}: a factor of about {} (about {} undivided). Their decoders, circuits and fitting differ, and which accounts for the factor is not found here. Ours takes the later's fits.", + print(Code::Gross.rate(Instruction::Idle, 1e-3).value, F::E(2)), + print(gross_gap, F::I), + print(gross_gap / 10.0, F::I) + ); + for (code, tag, name, p, label) in [(Code::Gross, "gross.1e-3", "gross", 1e-3, "10⁻³"), (Code::Gross, "gross.1e-4", "gross", 1e-4, "10⁻⁴"), (Code::TwoGross, "two.1e-3", "two-gross", 1e-3, "10⁻³"), (Code::TwoGross, "two.1e-4", "two-gross", 1e-4, "10⁻⁴")] { + r.num(&format!("2308.table1.{tag}"), format!("{name} memory per cycle, p = {label}"), code.rate(Instruction::Idle, p).value, fmt, Class::Open, two.as_str()); + } + section("The bicycle machine: logical error rates", r) +} + +/// The compile section needs the search: pass its results in. +fn compile(gross: &PauliCosts, two: &PauliCosts) -> Section { + use crate::quantum_bicycle::{fixed_pivot_histogram, histogram_mean, native_measurements, native_rotation_census, native_rotations, shift_word_lengths, ShiftAction}; + let mut r = Rows::default(); + r.num("s3.2.natives", "native measurements", native_measurements(Code::Gross).len() as f64, F::I, Class::Agrees, ""); + r.num("s3.2.shifts", "any shift, in generators, at most", f64::from(*shift_word_lengths().iter().flatten().max().unwrap_or(&0)), F::I, Class::Agrees, ""); + let (with, without, both) = native_rotation_census(Code::Gross); + r.num( + "s3.2.rotations", + "native rotations", + native_rotations(Code::Gross).len() as f64, + F::I, + Class::TheirNumbers, + format!( + "§3.2 defines a native rotation by a native measurement Q ⊗ R with Q one of X, Y, Z on the pivot: there are {with} such R. Over all the native measurements there are {} distinct R ≠ I: {without} occur with Q = I, {both} of them also with Q ≠ I. That its 510 counts those is our inference from the matching count. Fig. 9's first bin holds {with}.", + with + without - both + ), + ); + let why = "Our search runs from the printed polynomials, basis and native set, over the 4¹¹ − 1 Paulis (4¹² − 1 with the pivot fixed). The authors' released compiler, bicycle_cliffords 0.1.0 (crates.io, 2025-07-31), takes a shift's action in src/measurement.rs as A on X̄ and A⁻¹ on Z̄, on both halves of a module; taken so, our search gives the source's four means to every printed digit (a test in quantum_bicycle holds this). A CNOT circuit |x⟩ → |Ax⟩ takes Z̄ by A⁻ᵀ, which is how the operators move and what ours follows; its version 0.2.1 takes X̄ by A⁻¹ and Z̄ by Aᵀ, a consistent pair. The difference moves a capability here by under 0.6%."; + let (gross_fixed, two_fixed) = (histogram_mean(&fixed_pivot_histogram(Code::Gross, ShiftAction::Operators)), histogram_mean(&fixed_pivot_histogram(Code::TwoGross, ShiftAction::Operators))); + r.num("fig9.chosen.gross", "mean in-module measurements per Pauli, pivot basis chosen, gross", gross.mean_measurements(), F::D(3), Class::TheirNumbers, why); + r.num("fig9.chosen.two", "the same, two-gross", two.mean_measurements(), F::D(3), Class::TheirNumbers, why); + r.num("fig9.fixed.gross", "mean in-module measurements per Pauli, pivot basis fixed, gross", gross_fixed, F::D(3), Class::TheirNumbers, why); + r.num("fig9.fixed.two", "the same, two-gross", two_fixed, F::D(3), Class::TheirNumbers, why); + section("The bicycle machine: the compile", r) +} + +/// The source's surface-code row, as its §A.10 states it: `n = ⌊(q − f)/4d²⌋`, +/// `P₁ = 0.03·(p/0.01)^(d/2)` per cycle, and the largest whole `N_T` with +/// `2·N_T·n·P₁·d ≤ ⅓`. With our code model's `0.03·(p/0.01)^((d+1)/2)` per `d` +/// cycles in its place, the same budget. +fn surface_source_row(q: u64, f: u64, d: u32, p: f64) -> (u64, f64, f64) { + let dd = u64::from(d) * u64::from(d); + let n = (q - f) / (4 * dd); + let p1 = 0.03 * exp(0.5 * f64::from(d) * ln(p / 0.01)); + let budget = 1.0 / 3.0; + (n, (budget / (2.0 * n as f64 * p1 * f64::from(d))).floor(), budget / (2.0 * n as f64 * QecModel::surface_gate().logical_error(p, d))) +} + +fn capability() -> Section { + let mut r = Rows::default(); + let budget = 1.0 / 3.0; + let sampled = "Theirs samples eight random circuits of up to 10⁵ rotations and extends a straight-line fit (Fig. 17); ours is the expected count, at the rates as the tables print them."; + let e4 = "Its two gross rows at this p need a failure per rotation of 6.70·10⁻⁸ per module (ours: 6.69·10⁻⁸) plus 8.3·10⁻⁷ whatever the machine's size. Ours has 4·10⁻⁸ there: Table 3's factory, an injection's inter-module measurement and one link fewer. Nothing the paper prints accounts for the other 7.9·10⁻⁷. Its authors' compiler is released (bicycle_compiler 0.1.0 on crates.io): its capability estimate at these two machines would settle it; this check does not run it."; + let schedule = "Theirs is the critical path of a compiled schedule; ours adds the mean time of a rotation's parts, with no scheduling. Ours runs a few percent low, more where a compiler takes more shifts than the fewest."; + for (tag, q, name, code, factory, p) in MACHINES { + let m = Machine::largest(code, factory(), q).expect("a module fits"); + let label = format!("{} physical qubits, {name}", print(q as f64, F::I)); + let (ours, undivided) = (m.capability(p, budget), m.capability_undivided(p, budget)); + let published = Rows::pin(&format!("fig10a.{tag}.nt")).value.expect("a figure"); + let open = code == Code::Gross && p == 1e-4; + let ours_share = ours / undivided - 1.0; + let theirs_share = undivided / published - 1.0; + let (class, why) = if open { + (Class::Open, e4.to_string()) + } else if ours_share.abs() >= theirs_share.abs() { + (Class::Assumption, format!("Ours divides P_I and P_U by C, as §A.7's text says (the rates section); with them undivided, as Table 10 prints them, ours is {} (next row). That choice is {:+.2}% of the difference, and the sampling below {:+.2}%.", print(undivided, F::E(3)), 100.0 * ours_share, 100.0 * theirs_share)) + } else { + (Class::TheirModel, format!("{sampled} Our division of P_I and P_U by C, as §A.7's text says, is {:+.2}% of it (next row: undivided).", 100.0 * ours_share)) + }; + r.num(&format!("fig10a.{tag}.nt"), format!("T count at failure ⅓, {label}"), ours, F::E(3), class, why); + r.num(&format!("fig10a.{tag}.nt_undivided"), format!("the same, P_I and P_U undivided, as Table 10 prints them, {label}"), undivided, F::E(3), if open { Class::Open } else { Class::TheirModel }, if open { e4 } else { sampled }); + r.num(&format!("fig10a.{tag}.steps"), format!("timesteps per T, {label}"), m.rotation_timesteps(), F::D(2), Class::OurLimit, schedule); + } + let as_printed = "§A.10's formula for the surface-code row, evaluated as it states it: the largest whole T count."; + let ours = "Our planar model takes `0.03·(p/p*)^((d+1)/2)` per patch per `d` cycles, as arXiv:2211.07629 does. §A.10 takes `0.03·(p/p*)^(d/2)` per cycle, times `d` cycles: higher by `d·√(p*/p)`."; + for (tag, q, f, d, p, label, steps_factory) in [ + ("gross_e3_5k", 5_000, 454, 5, 1e-3, "5,000 qubits, p = 10⁻³", 351.0), + ("two_e3_50k", 50_000, 463, 17, 1e-3, "50,000 qubits, p = 10⁻³", 2167.0), + ("gross_e4_5k", 5_000, 810, 7, 1e-4, "5,000 qubits, p = 10⁻⁴", 73.0), + ("gross_e4_50k", 50_000, 810, 7, 1e-4, "50,000 qubits, p = 10⁻⁴, d = 7", 73.0), + ("two_e4_50k", 50_000, 18_600, 17, 1e-4, "50,000 qubits, p = 10⁻⁴, d = 17", 407.0), + ("two_e4_500k", 500_000, 18_600, 17, 1e-4, "500,000 qubits, p = 10⁻⁴, d = 17", 407.0), + ] { + let (have_n, source_nt, our_nt) = surface_source_row(q, f, d, p); + r.num(&format!("fig10a.surface.{tag}.n"), format!("surface code: logical qubits, {label}"), have_n as f64, F::I, Class::Agrees, ""); + let digits = if source_nt < 1e6 { F::I } else { F::E(2) }; + r.num(&format!("fig10a.surface.{tag}.nt"), format!("surface code: T count, its formula, {label}"), source_nt, digits, Class::Agrees, as_printed); + r.num(&format!("fig10a.surface.{tag}.nt_ours"), format!("surface code: T count, our code model, {label}"), our_nt, F::E(2), Class::TheirModel, ours); + r.num( + &format!("fig10a.surface.{tag}.steps"), + format!("surface code: timesteps per T, {label}"), + steps_factory + 6.0 * f64::from(d), + F::I, + Class::Agrees, + "§A.10: the factory's timesteps (Table 3) and the lattice surgery's 6d.", + ); + } + section("The bicycle machine: what a machine of a given size can run", r) +} + +/// The T gates of each of the Ising program's four rotation angles at +/// `epsilon`, in this crate's synthesiser: `(ZZ, X)` in the four outer +/// second-order formulas of a step, then in the middle one. +pub fn ising_t_counts(epsilon: f64) -> Result<[u64; 4], String> { + let gamma = 1.0 / (4.0 - exp(ln(4.0) / 3.0)); + let delta = 0.25; + let t = |angle: f64| rz(angle, epsilon).map(|w| w.t_count as u64); + let mid = (1.0 - 4.0 * gamma) * delta; + Ok([t(gamma * delta)?, t(2.0 * gamma * delta)?, t(mid)?, t(2.0 * mid)?]) +} + +/// The program's T gates: 80 steps of 5 second-order formulas, 360 `ZZ` and +/// 100 `X` rotations each, the middle formula at its own angles. +pub fn ising_t_count(epsilon: f64) -> Result { + let [zz, x, zz_mid, x_mid] = ising_t_counts(epsilon)?; + Ok(80 * (4 * (360 * zz + 100 * x) + 360 * zz_mid + 100 * x_mid)) +} + +/// The physical error rate at which `a·pᵇ` through two points reaches `target`. +fn power_law_bound(p1: f64, f1: f64, p2: f64, f2: f64, target: f64) -> f64 { + let slope = ln(f1 / f2) / ln(p1 / p2); + p2 * exp(ln(target / f2) / slope) +} + +/// The rotations of one second-order formula with the sites numbered from the +/// other end of the line: the factory before the first module, not after the +/// last. +fn mirrored(layer: &[PauliRotation], slots: u64) -> Vec { + let flip = |s: &u64| slots - 1 - s; + layer.iter().map(|r| PauliRotation { xs: r.xs.iter().map(flip).collect(), zs: r.zs.iter().map(flip).collect() }).collect() +} + +fn ising(gross: &PauliCosts, two: &PauliCosts) -> Result { + let mut r = Rows::default(); + let two_machine = Machine::for_logical(Code::TwoGross, TFactory::two_gross_e3(), 100); + let gross_machine = Machine::for_logical(Code::Gross, TFactory::gross_e4(), 100); + r.num("s4.ising.two.q", "physical qubits, two-gross", two_machine.physical_qubits() as f64, F::I, Class::Agrees, ""); + r.num("s4.ising.gross.q", "physical qubits, gross", gross_machine.physical_qubits() as f64, F::I, Class::TheirNumbers, "§4 names the 10⁻⁴ gross factory, whose adapter is 13 qubits (Table 3). 4,817 takes the 29 of the 10⁻³ factory."); + let layer = ising_layer(10); + r.num("a10.rotations", "rotations", (400 * layer.len()) as f64, F::I, Class::Agrees, ""); + let tally = |m: &Machine, c: &PauliCosts| { + let mut t = InstructionCounts::default(); + for rot in &layer { + let e = m.compile_rotation(c, rot); + t.inter_module += 400.0 * e.inter_module; + t.in_module += 400.0 * e.in_module; + t.automorphism += 400.0 * e.automorphism; + } + t + }; + let (g, t) = (tally(&gross_machine, gross), tally(&two_machine, two)); + let near: u64 = 400 * mirrored(&layer, gross_machine.logical_qubits()).iter().map(|x| gross_machine.links(x, true)).sum::(); + let placement = format!("Sites in order and the factory after the last module give the paper's count; before the first module, {}. The paper prints neither, and this is the end chosen to match it: the one choice in this check made to fit a published figure.", print(near as f64, F::I)); + r.num("table9.gross.nc", "inter-module measurements `N_C`, gross", g.inter_module, F::I, Class::Assumption, placement.as_str()); + r.num("table9.two.nc", "the same on two-gross", t.inter_module, F::I, Class::Assumption, placement.as_str()); + let peep = "Ours removes no repeated in-module measurement, which theirs does (§4), and takes sites in order, which it does not print. Its counts also carry the compile section's action of a shift on Z̄, which puts its gross below its two-gross; ours has them the other way round."; + r.num("table9.gross.nm", "in-module measurements `N_M`, gross", g.in_module, F::I, Class::OurLimit, peep); + r.num("table9.two.nm", "in-module measurements `N_M`, two-gross", t.in_module, F::I, Class::OurLimit, peep); + let autos = "Ours takes the fewest shifts on a path of fewest measurements, and merges none; theirs is a compiler's."; + r.num("table9.gross.nu", "automorphisms `N_U`, gross", g.automorphism, F::I, Class::OurLimit, autos); + r.num("table9.two.nu", "automorphisms `N_U`, two-gross", t.automorphism, F::I, Class::OurLimit, autos); + let counts = ising_t_counts(5e-9)?; + let synthesised = ising_t_count(5e-9)? as f64; + r.num( + "table9.nt", + "T injections `N_T`", + synthesised, + F::I, + Class::TheirModel, + format!( + "pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives {}, {}, {} and {}, and also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows.", + counts[0], counts[1], counts[2], counts[3] + ), + ); + r.num("s4.nt", "T injections as §4 and Fig. 1(b) state", synthesised, F::I, Class::TheirNumbers, "§4 says N_T = 1.9·10⁷ and Fig. 1(b) plots 19,036,800. Table 9 and every failure probability of Table 10 take 15,542,400, which the row above compares ours with."); + let idle = |nt: f64, m: &Machine| nt * m.modules as f64 * m.factory.timesteps / 8.0; + r.num("table9.two.ni", "idles `N_I`, two-gross, from its `N_T`", idle(15_542_400.0, &two_machine), F::I, Class::OurLimit, "Ours: every module idles while the factory works, and otherwise never. Theirs counts the idles of its schedule."); + + // Table 9's counts, as the source files hold them: Table 10's inputs. + let gross_counts = InstructionCounts { idle: 41_634_378_765.0, automorphism: 2_270_400.0, in_module: 4_215_240.0, inter_module: 946_800.0, t_injection: 15_542_400.0 }; + let two_counts = InstructionCounts { idle: 41_636_714_298.0, automorphism: 2_294_400.0, in_module: 4_238_828.0, inter_module: 946_800.0, t_injection: 15_542_400.0 }; + let fmt = F::E(3); + let banded = "Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit."; + for (code, counts, tag, name, ps) in [(Code::Gross, &gross_counts, "gross", "gross", &[(1e-3, "1e-3", "10⁻³"), (1e-4, "1e-4", "10⁻⁴"), (5e-5, "5e-5", "5·10⁻⁵"), (3e-5, "3e-5", "3·10⁻⁵"), (1e-5, "1e-5", "10⁻⁵")][..]), (Code::TwoGross, &two_counts, "two", "two-gross", &[(1e-3, "1e-3", "10⁻³"), (1e-4, "1e-4", "10⁻⁴")][..])] { + for (p, ptag, label) in ps { + let band = (tabulated_sum(counts, code, *p, -FIT_DIGIT), tabulated_sum(counts, code, *p, FIT_DIGIT)); + r.band(&format!("table10.fail.{tag}.{ptag}"), format!("failure probability, {name}, p = {label}, its counts"), counts.failure_as_tabulated(code, *p), band, fmt, Class::Agrees, banded); + } + } + r.num( + "table10.fail.two.factory", + "failure probability, two-gross, p = 10⁻³, a T injection at its factory", + two_counts.failure(Code::TwoGross, TFactory::two_gross_e3().error, 1e-3), + fmt, + Class::TheirModel, + "Table 10 sets `P_T = P_C` (\"the T factory can be chosen to distill states of sufficient fidelity\") and §4 neglects the factory. At the factory it allocates, Table 3's 2·10⁻⁸, the T injections alone fail with 0.31.", + ); + let bound = |code: Code, n: &InstructionCounts, (p1, p2): (f64, f64), d: f64| power_law_bound(p1, tabulated_sum(n, code, p1, d), p2, tabulated_sum(n, code, p2, d), 1e-3); + let bounded = "Its power law through two of its points of Table 10, solved for a failure of 10⁻³; the band moves the fits' parameters by their last printed digit."; + for (code, counts, tag, name, ps) in [(Code::TwoGross, &two_counts, "two", "two-gross", (1e-4, 1e-3)), (Code::Gross, &gross_counts, "gross", "gross", (1e-5, 3e-5))] { + let (a, b) = (bound(code, counts, ps, -FIT_DIGIT), bound(code, counts, ps, FIT_DIGIT)); + r.band(&format!("a10.bound.{tag}"), format!("physical error rate for failure 10⁻³, {name}"), bound(code, counts, ps, 0.0), (a.min(b), a.max(b)), fmt, Class::Agrees, bounded); + } + + // Eq. (96) with §4's N_T, which reproduces §A.10's figures. + let volume = 2.0 * 184_000.0 * 100.0 + 19_036_800.0 - 184_000.0; + let their_surface = |d: f64| 0.01 * exp(ln(1e-3 / (0.03 * d * volume)) * 2.0 / d); + let our_surface = |d: f64| 0.01 * exp(ln(1e-3 / (0.03 * volume)) * 2.0 / (d + 1.0)); + for (d, f) in [(9.0, 810.0), (17.0, 463.0), (19.0, 463.0)] { + let tag = format!("d{d}"); + r.num(&format!("a10.surface.{tag}.p"), format!("surface code, d = {d}: physical error rate, Eq. (96)"), their_surface(d), fmt, Class::Agrees, "With §4's N_T of 19,036,800. §4 prints it truncated to two digits."); + r.num(&format!("a10.surface.{tag}.p_ours"), format!("surface code, d = {d}: physical error rate, our code model"), our_surface(d), fmt, Class::TheirModel, "As in the capability section: a patch's error per `d` cycles, not a cycle's times `d`, and `(d+1)/2`."); + r.num(&format!("s4.surface.{tag}.qubits"), format!("surface code, d = {d}: physical qubits"), 4.0 * 100.0 * d * d + f, F::I, Class::TheirNumbers, "§4 and Fig. 10(b) count `2d²` a logical qubit. Eq. (96) puts `2n` patches in each rotation, and §A.10's capability comparison reserves half the patches for routing: `4d²`. Ours is `4d²`."); + } + Ok(section("The bicycle machine: the 10 × 10 Ising model", r)) +} + +/// The planar job, through the job's own door. +struct Planar { + distance: u64, + physical: f64, + factories: u64, + runtime_h: f64, +} + +fn job(counts: Value, budget: f64, p: f64, calibration: &str) -> Result { + let request = json!({ + "counts": counts, + "budget": budget, + "qubit": { "name": "1 µs cycle", "instruction_set": "gate", "gate_ns": 1000.0 / 6.0, "meas_ns": 1000.0 / 6.0, "clifford_error": p, "t_error": p }, + "calibration": calibration, + "max_slowdown": 1, + }); + let answer = estimate::call(&request)?; + let e = &answer["estimate"]; + Ok(Planar { + distance: e["distance"].as_u64().unwrap_or(0), + physical: e["physical_qubits"].as_f64().unwrap_or(0.0), + factories: e["factories"].as_u64().unwrap_or(0), + runtime_h: e["runtime_s"].as_f64().unwrap_or(0.0) / 3600.0, + }) +} + +/// The planar model on the source's own assumptions: `2(d+1)²` qubits a patch, +/// and `0.1·(100p)^((d+1)/2)` per cycle, at the distance the source uses. +fn their_noise(at_distance: f64) -> QecModel { + QecModel { name: "surface code, as the source prices it".into(), instruction_set: InstructionSet::GateBased, prefactor: 0.1 * at_distance, threshold: 0.01, qubits: (2, 4, 2), cycle: (4.0, 2.0) } +} + +fn one_us(p: f64) -> PhysicalQubit { + PhysicalQubit { name: "1 µs cycle".into(), instruction_set: InstructionSet::GateBased, gate_ns: 1000.0 / 6.0, meas_ns: 1000.0 / 6.0, clifford_error: p, t_error: p } +} + +/// What the estimate job leaves out against a laid-out surface-code machine. +const JOB_TIME: &str = "The estimate job prices the planar architecture of arXiv:2211.07629: three logical steps of `d` cycles for each Toffoli. The source teleports a Toffoli's fixups away and runs an addition at one 10 µs tick a Toffoli. The job has no such clock; quantum_arch's (the re-run rows) does."; +const JOB_SPACE: &str = "The job lays out `2N + √(8N) + 1` tiles of `2d²` qubits and T factories from 15-to-1; the source a board of `2(d+1)²`-qubit patches and CCZ factories, placed by hand."; + +fn rsa_2021() -> Result { + let params = WindowedFactoringParams::published(2048).expect("a published row"); + let e = windowed_factoring_estimate(¶ms, 1e-3, 1.0, 10.0); + let toffolis = e.toffolis.round() as u64; + let counts = json!({ "numQubits": 6189, "cczCount": toffolis }); + let assumed = job(counts.clone(), 0.31, 1e-3, "assumed")?; + let fitted = job(counts, 0.31, 1e-3, "exact_matching")?; + let source_noise = planar(&LogicalCounts { qubits: 6189, ccz: toffolis, ..Default::default() }, &one_us(1e-3), &their_noise(27.0), &EstimateConfig::new(0.31)).map_err(|x| format!("{x:?}"))?; + + let mut r = Rows::default(); + let fmt = F::E(3); + let up = "Its script takes each figure up to two digits."; + r.num("r21.table3.qubits", "physical qubits, re-run on its layout", e.physical_qubits as f64, F::I, Class::Agrees, up); + r.num("r21.table3.hours", "hours per run, re-run", e.hours, F::D(2), Class::Agrees, up); + r.num("r21.table3.risk", "retry risk, re-run", e.retry_risk, F::D(4), Class::Agrees, "Its script takes it up to a whole percent."); + r.num("r21.table3.mqd", "megaqubit-days per run, re-run", e.megaqubit_days(), F::D(2), Class::Agrees, up); + r.num("r21.table1.toffolis", "Toffolis, re-run", e.toffolis, fmt, Class::Agrees, ""); + r.num("r21.s2.10.factories", "factories in each piece, re-run", (e.factories / e.pieces) as f64, F::I, Class::Agrees, ""); + r.num( + "r21.title.hours", + "hours, expected across retries", + e.expected_hours(), + F::D(2), + Class::Agrees, + format!("The re-run's {} hours a run at its {}% retry risk is {} hours expected; the title takes that up to one digit.", print(e.hours, F::D(2)), print(100.0 * e.retry_risk, F::D(2)), print(e.expected_hours(), F::D(2))), + ); + let hand = windowed_factoring_estimate(&WindowedFactoringParams { factory: DistilledCczFactory { l1_distance: 17, l2_distance: 27 }, ..params }, 1e-3, 1.0, 10.0); + r.num("r21.s2.14.qubits", "physical qubits, its worked case, level-1 distance 17", hand.physical_qubits as f64, F::I, Class::TheirNumbers, "That section rounds as it goes, and says so: 226 × 63 patches, where the script's layout at these distances has 226 × 61."); + r.num("r21.s2.12.distillation", "distillation error of 3·10⁹ CCZ states, distances 17 and 27", 3e9 * DistilledCczFactory { l1_distance: 17, l2_distance: 27 }.error(1e-3), F::D(4), Class::TheirNumbers, "That section reads a spreadsheet of an earlier paper. Its own script's formula, which produced its tables, gives 0.6%."); + + r.num("r21.job.distance.assumed", "code distance, job, assumed code model", assumed.distance as f64, F::I, Class::Agrees, ""); + r.num("r21.job.distance.fitted", "code distance, job, code model fitted to our decoders", fitted.distance as f64, F::I, Class::Agrees, "Our fit is `a = 0.0233, p* = 0.01004`, extrapolated from p = 0.002–0.005."); + r.num("r21.job.qubits.assumed", "physical qubits, job, assumed code model", assumed.physical, F::I, Class::OurLimit, JOB_SPACE); + r.num("r21.job.qubits.fitted", "physical qubits, job, fitted code model", fitted.physical, F::I, Class::OurLimit, JOB_SPACE); + r.num("r21.job.qubits.source_rule", "physical qubits, the planar model on the source's patch and error rule", source_noise.physical_qubits as f64, F::I, Class::OurLimit, JOB_SPACE); + r.num("r21.job.hours.assumed", "hours per run, job, assumed code model", assumed.runtime_h, F::D(2), Class::OurLimit, JOB_TIME); + r.num("r21.job.hours.source_rule", "hours per run, the planar model on the source's patch and error rule", source_noise.runtime_ns / 3.6e12, F::D(2), Class::OurLimit, JOB_TIME); + r.text("r21.job.factories", "factories, job", format!("{} T factories", assumed.factories), Class::OurLimit, "A CCZ state costs four T states in the planar model."); + + for n in [1024, 3072, 4096, 8192, 12288, 16384] { + let row = windowed_factoring_estimate(&WindowedFactoringParams::published(n).expect("a published row"), 1e-3, 1.0, 10.0); + r.num(&format!("r21.table3.{n}.megaqubits"), format!("{n} bits: megaqubits, re-run"), row.physical_qubits as f64 / 1e6, F::D(2), Class::Agrees, up); + r.num(&format!("r21.table3.{n}.hours"), format!("{n} bits: hours per run, re-run"), row.hours, F::D(2), Class::Agrees, up); + } + Ok(section("RSA-2048 on the surface code (arXiv:1905.09749, Quantum 5, 433, 2021)", r)) +} + +fn femoco() -> Result { + let toffolis = 483_000.0 * 13_880.0; + let factory = DistilledCczFactory { l1_distance: 19, l2_distance: 31 }; + let clock = Timing { cycle_us: 1.0, reaction_us: 10.0, distance: 31, ccz_rounds: factory.rounds_per_ccz(), factories: 4 }; + let cheap = DistilledCczFactory { l1_distance: 9, l2_distance: 15 }; + let cheap_clock = Timing { distance: 15, ccz_rounds: cheap.rounds_per_ccz(), ..clock }; + let days = |t: &Timing| toffolis * t.tick_us() / 86.4e9; + let table = json!({ "numQubits": 2142, "cczCount": 5_300_000_000u64 }); + let layout = json!({ "numQubits": 700, "cczCount": toffolis as u64 }); + let (a, b) = (job(table, 0.01, 1e-3, "assumed")?, job(layout.clone(), 0.01, 1e-3, "assumed")?); + let low = job(layout, 0.01, 1e-4, "assumed")?; + + let mut r = Rows::default(); + r.num("femoco.fig10.qubits", "physical qubits, p = 10⁻³, its floorplan", (53 * 36 * patch_qubits(31)) as f64, F::I, Class::Agrees, "53 × 36 = 1,908 patches of 2(d+1)², as its caption works out."); + r.num("femoco.s4c4.days", "days, p = 10⁻³, re-run on our clock", days(&clock), F::D(2), Class::Agrees, ""); + r.text("femoco.s4c4.pace", "what sets the pace, p = 10⁻³", format!("{:?}", clock.bound()), if clock.bound() == Bound::MagicStates { Class::Agrees } else { Class::Open }, ""); + r.num("femoco.s4c4.rate", "Toffoli rate, kHz", 1e3 / clock.tick_us(), F::D(2), Class::Agrees, "Four factories each finish a CCZ state every 5·31 cycles: 25.8 kHz, printed as 25."); + r.num("femoco.s4c4.distillation", "distillation failure over 10¹⁰ Toffolis", 1e10 * factory.error(1e-3), F::D(5), Class::Agrees, ""); + r.num("femoco.s4c5.qubits", "physical qubits, p = 10⁻⁴", (53 * 36 * patch_qubits(15)) as f64, F::I, Class::Agrees, ""); + r.num("femoco.s4c5.days", "days, p = 10⁻⁴, re-run on our clock", days(&cheap_clock), F::D(2), Class::Agrees, ""); + r.num("femoco.s4c5.days3", "days at p = 10⁻³, as §IV.C.5 states", days(&clock), F::D(2), Class::TheirNumbers, "§IV.C.4 says 3 days; §IV.C.5 scales \"3.5 days\" by 15/31 and gets 1.5, which is 3 days scaled. Its abstract says under four."); + let qrom = "The paper's own two figures: Table III costs the algorithm with its own QROM choices; the layout takes an output expansion factor of 1 for the select reads (§IV.C.2), which holds fewer qubits and takes more Toffolis. Ours takes each in turn below."; + r.text("femoco.table3.toffolis", "Toffolis, Table III against its layout", "—", Class::TheirNumbers, qrom); + r.text("femoco.table3.qubits", "logical qubits, Table III against its layout's data", "—", Class::TheirNumbers, qrom); + r.num("femoco.job.distance", "code distance, its layout's counts", b.distance as f64, F::I, Class::Assumption, "Its rule is `0.1·(100p)^((d+1)/2)` a cycle (§IV.C.5: \"Λ of 10\"); the planar model's `0.03·(p/0.01)^((d+1)/2)` a step of `d` cycles, lower by `d/0.3`, one step of distance at this size."); + r.num("femoco.job.qubits.table", "physical qubits, job, Table III's counts", a.physical, F::I, Class::OurLimit, JOB_SPACE); + r.num("femoco.job.qubits.layout", "physical qubits, job, its layout's counts", b.physical, F::I, Class::OurLimit, JOB_SPACE); + r.num("femoco.job.days.table", "days, job, Table III's counts", a.runtime_h / 24.0, F::D(2), Class::OurLimit, JOB_TIME); + r.num("femoco.job.days.layout", "days, job, its layout's counts", b.runtime_h / 24.0, F::D(2), Class::OurLimit, JOB_TIME); + r.num("femoco.job.qubits.e4", "physical qubits, job, p = 10⁻⁴", low.physical, F::I, Class::OurLimit, JOB_SPACE); + r.num("femoco.job.days.e4", "days, job, p = 10⁻⁴", low.runtime_h / 24.0, F::D(2), Class::OurLimit, JOB_TIME); + Ok(section("FeMoCo on the surface code (arXiv:2011.03494, PRX Quantum 2, 030305, 2021)", r)) +} + +fn rsa_2025() -> Result { + let p = FactoringParams::published(2048).expect("a published row"); + let e = factoring_estimate(&p, &ArchMachine::published()).map_err(|x| x.to_string())?; + let mut r = Rows::default(); + r.num("r25.qubits", "physical qubits", e.physical_qubits() as f64, F::I, Class::TheirNumbers, "Ours keeps 152 logical qubits hot by its peak formula and buys whole grid blocks; its count keeps 131 and a fractional block. At its 131 and 430 qubits a logical qubit, our arithmetic gives its 897,864."); + r.num("r25.hours", "hours per shot", e.hours_per_shot_rounded, F::D(2), Class::TheirNumbers, "Its tallies at its stated durations give 10.62 hours, and its clock at whole milliseconds 10.58."); + r.num("r25.days", "days per factoring", e.days, F::D(2), Class::TheirNumbers, "From the hours per shot above; it also multiplies by 9.1 shots where its table gives 9.2. It rounds 4.96 up to \"less than a week\"."); + r.num("r25.logical", "logical qubits", p.peak_logical_qubits() as f64, F::I, Class::TheirNumbers, "§3.2 prints 1,409 for its peak formula `m + 3f + 2ℓ + len m`, which evaluates to 1,432; its Table 4 gives 1,399."); + let published_toffolis = p.toffolis_per_shot_source_rule(PhaseupCount::AsCoded) * f64::from(p.s + 1) / (1.0 - p.deviant); + r.num("r25.toffolis", "Toffolis per factoring, by its counting rule", published_toffolis, F::E(2), Class::Agrees, ""); + Ok(section("RSA-2048 on yoked surface codes (arXiv:2505.15917, 2025)", r)) +} + +fn planar_reference() -> Result { + let counts = LogicalCounts { qubits: 12_581, measurements: 1_080_000_000, rotations: 12, rotation_depth: 12, t_gates: 12, ccz: 3_730_000_000 }; + let e = planar(&counts, &PhysicalQubit::ns_e4(), &QecModel::surface_gate(), &EstimateConfig::new(1.0 / 3.0)).map_err(|x| format!("{x:?}"))?; + let mut r = Rows::default(); + r.num("planar.distance", "code distance, RSA-2048, (ns, 10⁻⁴)", f64::from(e.distance), F::I, Class::Agrees, ""); + r.num("planar.factories", "factories", e.factories as f64, F::I, Class::Agrees, ""); + r.num("planar.qubits", "physical qubits", e.physical_qubits as f64, F::I, Class::Agrees, ""); + r.num("planar.hours", "run time, hours", e.runtime_ns / 3.6e12, F::D(3), Class::Agrees, "Table IV prints 18 hours."); + Ok(section("The planar model's own source (arXiv:2211.07629, 2022)", r)) +} + +/// Every section, with the search run once per code. +pub fn report() -> Result, String> { + let gross = PauliCosts::search(Code::Gross); + let two = PauliCosts::search(Code::TwoGross); + Ok(vec![machines(), rates(), compile(&gross, &two), capability(), ising(&gross, &two)?, rsa_2021()?, femoco()?, rsa_2025()?, planar_reference()?]) +} + +/// The rows as Markdown tables, one per section. +pub fn markdown(sections: &[Section]) -> String { + let mut out = String::new(); + for s in sections { + out.push_str(&format!("### {}\n\n", s.title)); + out.push_str("| Quantity | Source | Published | Ours | Delta | Class | Why |\n|---|---|---:|---:|---:|---|---|\n"); + for r in &s.rows { + out.push_str(&format!("| {} | {} | {} | {} | {} | {} | {} |\n", r.quantity, r.source, r.published, r.ours, r.delta, r.class.name(), r.why)); + } + out.push('\n'); + } + out +} + +/// The rows as plain columns. +pub fn text(sections: &[Section]) -> String { + let mut out = String::new(); + for s in sections { + out.push_str(&format!("== {}\n", s.title)); + for r in &s.rows { + out.push_str(&format!("{:<76} published {:>24} ours {:>18} delta {:>9} {}\n", r.quantity, r.published, r.ours, r.delta, r.class.name())); + } + out.push('\n'); + } + out +} + +/// For each numeric row that does not agree: its id and ours to four +/// significant digits, the guard its line in the published figures takes. +pub fn guards(sections: &[Section]) -> String { + let mut out = String::new(); + for r in sections.iter().flat_map(|s| &s.rows) { + if let (Some((_, ours)), false) = (r.values, r.class == Class::Agrees) { + out.push_str(&format!("{}\t{:.3e}\n", r.id, ours)); + } + } + out +} + +/// Whether a row holds to its published figure's rule, and why not. +pub fn check(r: &Row) -> Result<(), String> { + let pin = pins().get(&r.id).ok_or(format!("{}: no published figure", r.id))?; + match (r.values, r.class, pin.rule) { + (None, _, Rule::Text) => Ok(()), + (None, _, rule) => Err(format!("{}: a row with no number, its figure's rule {rule:?}", r.id)), + (Some(_), _, Rule::Text) => Err(format!("{}: a number against a text figure", r.id)), + (Some((value, ours)), Class::Agrees, rule) => { + if matches!(rule, Rule::Guard(_)) { + return Err(format!("{}: an agreeing row needs the rule its figure is printed to, not a guard", r.id)); + } + if holds(rule, value, ours, r.band) { Ok(()) } else { Err(format!("{}: {ours} against {value} by {rule:?} (band {:?})", r.id, r.band)) } + } + (Some((_, ours)), _, Rule::Guard(g)) => { + if holds(Rule::Guard(g), 0.0, ours, None) { Ok(()) } else { Err(format!("{}: ours is {ours:.3e}, its guard {g:e}: update the guard, the reason and the page together", r.id)) } + } + (Some(_), class, rule) => Err(format!("{}: a {} row needs a guard, not {rule:?}", r.id, class.name())), + } +} + +/// How many rows of each class, in [`Class`] order. +pub fn tally(sections: &[Section]) -> [(Class, usize); 6] { + let classes = [Class::Agrees, Class::Assumption, Class::TheirModel, Class::TheirNumbers, Class::OurLimit, Class::Open]; + classes.map(|c| (c, sections.iter().flat_map(|s| &s.rows).filter(|r| r.class == c).count())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_deltas_are_what_they_say() { + assert_eq!(delta(10.0, 10.0), "0"); + assert_eq!(delta(10.0, 11.0), "+10.00%"); + assert_eq!(delta(10.0, 9.0), "-10.00%"); + assert_eq!(delta(10.0, 30.0), "×3.000"); + assert_eq!(delta(1.0, 300.0), "×3.0e2"); + assert_eq!(print(1_234_567.0, F::I), "1,234,567"); + assert_eq!(print(999.0, F::I), "999"); + } + + #[test] + fn a_rule_holds_to_the_printed_digits_and_no_further() { + // 8.72M printed to its last unit: 8,716,258 holds, 9.00M would not. + assert!(holds(Rule::Nearest(0.01e6), 8.72e6, 8_716_258.0, None)); + assert!(!holds(Rule::Nearest(0.01e6), 9.00e6, 8_716_258.0, None)); + // Six percent was the old tolerance; the printed digit is tighter. + assert!(!holds(Rule::Nearest(0.01e6), 8.72e6, 8.72e6 * 1.05, None)); + // Rounded up: 19.25 M is "20 M", 20.3 M is not, nor is 18.9 M. + assert!(holds(Rule::Up(1e6), 20e6, 19.25e6, None)); + assert!(!holds(Rule::Up(1e6), 20e6, 20.3e6, None)); + assert!(!holds(Rule::Up(1e6), 20e6, 18.9e6, None)); + assert!(holds(Rule::Down(1.0), 25.0, 25.81, None)); + assert!(!holds(Rule::Down(1.0), 25.0, 26.1, None)); + // A band meets the printed figure's interval, or does not. + assert!(holds(Rule::Band(0.1e-21), 8.2e-21, 8.13e-21, Some((8.06e-21, 8.24e-21)))); + assert!(!holds(Rule::Band(0.1e-21), 8.2e-21, 8.13e-21, Some((8.06e-21, 8.14e-21)))); + assert!(holds(Rule::Guard(1.467e7), 0.0, 14_665_600.0, None)); + assert!(!holds(Rule::Guard(1.467e7), 0.0, 14_592_000.0, None)); + assert!(holds(Rule::Exact, 381.0, 381.0, None) && !holds(Rule::Exact, 381.0, 380.0, None)); + } + + #[test] + fn the_published_figures_read_and_each_says_where_it_is_printed() { + let p = pins(); + assert!(p.len() > 150, "{}", p.len()); + for (id, pin) in p { + assert!(pin.source.starts_with("arXiv:"), "{id}: {}", pin.source); + assert!(!pin.printed.is_empty(), "{id}"); + assert_eq!(pin.value.is_none(), pin.rule == Rule::Text, "{id}"); + } + // Fig. 10(a)'s surface-code timesteps are its printed figures, not a + // formula compared with itself. + let steps: Vec = ["gross_e3_5k", "two_e3_50k", "gross_e4_5k", "gross_e4_50k", "two_e4_50k", "two_e4_500k"].iter().map(|t| p[&format!("fig10a.surface.{t}.steps")].value.unwrap()).collect(); + assert_eq!(steps, [381.0, 2269.0, 115.0, 115.0, 509.0, 509.0]); + // As the PDF prints them, or from the source files and said so. + assert_eq!(p["a10.bound.two"].printed, "7.32·10⁻⁴"); + assert_eq!(p["a10.bound.gross"].printed, "2.09·10⁻⁵"); + assert_eq!(p["table10.two.I.1e-3"].printed, "8.2·10⁻²¹"); + for (id, pin) in p.iter().filter(|(id, _)| id.starts_with("fig10a.") && id.ends_with(".nt")) { + assert!(pin.source.ends_with("source files") || id.contains("surface"), "{id}"); + } + } + + #[test] + #[cfg_attr(debug_assertions, ignore = "the search over 4¹¹ Paulis takes minutes in a debug build")] + fn the_page_is_what_the_estimators_give() { + let page = include_str!("../docs/published-architectures.md"); + let (begin, end) = ("\n\n", ""); + let start = page.find(begin).expect("the page's first marker") + begin.len(); + let stop = page.find(end).expect("the page's second marker"); + let want = markdown(&report().unwrap()); + assert!(page[start..stop] == want, "the page differs from the estimators: run the example and read the difference"); + } + + #[test] + #[cfg_attr(debug_assertions, ignore = "the search over 4¹¹ Paulis takes minutes in a debug build")] + fn every_row_holds_to_its_published_figure() { + let sections = report().unwrap(); + let mut used = std::collections::BTreeSet::new(); + let mut bad = Vec::new(); + for r in sections.iter().flat_map(|s| &s.rows) { + if r.class != Class::Agrees { + assert!(!r.why.is_empty(), "{}: a {} row with no reason", r.id, r.class.name()); + } + assert!(used.insert(r.id.clone()), "{}: two rows", r.id); + if let Err(e) = check(r) { + bad.push(e); + } + } + assert!(bad.is_empty(), "{bad:#?}"); + let unused: Vec<_> = pins().keys().filter(|k| !used.contains(*k)).collect(); + assert!(unused.is_empty(), "published figures no row takes: {unused:?}"); + } + + #[test] + #[cfg_attr(debug_assertions, ignore = "the search over 4¹¹ Paulis takes minutes in a debug build")] + fn the_ours_column_holds_only_ours_and_the_text_says_what_the_sources_say() { + let sections = report().unwrap(); + let rows: BTreeMap<&str, &Row> = sections.iter().flat_map(|s| &s.rows).map(|r| (r.id.as_str(), r)).collect(); + let synthesised = ising_t_count(5e-9).unwrap() as f64; + // The Ising program's T gates as the text states them, beside ours. + assert_eq!(rows["s4.nt"].values.unwrap().1, synthesised); + // The FeMoCo paper's two figures are both the paper's: no "ours". + assert_eq!(rows["femoco.table3.toffolis"].ours, "—"); + assert_eq!(rows["femoco.table3.qubits"].ours, "—"); + // The released compiler is cited, and nothing says it is not released. + assert!(rows["fig9.chosen.gross"].why.contains("bicycle_cliffords 0.1.0")); + assert!(rows["fig10a.gross_e4_5k.nt"].why.contains("bicycle_compiler 0.1.0")); + let all: String = sections.iter().flat_map(|s| &s.rows).map(|r| format!("{} {} {}\n", r.quantity, r.source, r.why)).collect(); + for gone in ["not released", "Fig. 16", "shortfall", "press release", "announced", "every R ≠ I"] { + assert!(!all.contains(gone), "{gone}"); + } + assert!(rows["fig10a.gross_e3_5k.nt"].why.contains("Fig. 17") || rows["fig10a.gross_e3_5k.nt_undivided"].why.contains("Fig. 17")); + // The arXiv:2308.07915 rows are open, the gap unexplained. + for t in ["gross.1e-3", "gross.1e-4", "two.1e-3", "two.1e-4"] { + assert_eq!(rows[format!("2308.table1.{t}").as_str()].class, Class::Open); + } + // The placement of the Ising factory is a stated choice, not a match. + assert_eq!(rows["table9.gross.nc"].class, Class::Assumption); + assert!(rows["table9.gross.nc"].why.contains("781,200")); + // One computation for the expected hours, in the row and its text. + let e = windowed_factoring_estimate(&WindowedFactoringParams::published(2048).unwrap(), 1e-3, 1.0, 10.0); + assert_eq!(rows["r21.title.hours"].values.unwrap().1, e.expected_hours()); + assert!(rows["r21.title.hours"].why.contains(&print(e.expected_hours(), F::D(2)))); + // The division by C is ours, and the capability row says how much of + // the gap it is. + assert_eq!(rows["fig10a.gross_e3_5k.nt"].class, Class::Assumption); + } + + #[test] + fn the_division_by_c_is_a_third_of_a_point_of_the_gross_capability() { + let m = Machine::largest(Code::Gross, TFactory::gross_e3(), 5_000).unwrap(); + let (ours, undivided) = (m.capability(1e-3, 1.0 / 3.0), m.capability_undivided(1e-3, 1.0 / 3.0)); + let share = ours / undivided - 1.0; + assert!((0.002..0.004).contains(&share), "{share}"); + // Undivided, ours is within 0.15% of the source's 13.5958. + assert!((undivided / 13.595_802_668_072_07 - 1.0).abs() < 0.0015, "{undivided}"); + } + + /// The Ising row's text names these. + #[test] + fn the_ising_angles_take_these_t_counts() { + assert_eq!(ising_t_counts(5e-9).unwrap(), [79, 80, 81, 84]); + assert_eq!(ising_t_count(5e-9).unwrap(), 14_665_600); + // pygridsynth 1.0.1's counts on the same angles give the source's total. + assert_eq!(80 * (4 * (360 * 86 + 100 * 80) + 360 * 84 + 100 * 82), 15_542_400); + } + + #[test] + fn the_surface_rows_take_the_largest_whole_t_count() { + // §A.10: "We find the maximum k ∈ N". Its formula gives 7.8 at 5,000 + // qubits and p = 10⁻³; the table prints 7. + let (n, nt, _) = surface_source_row(5_000, 454, 5, 1e-3); + assert_eq!((n, nt), (45, 7.0)); + assert_eq!(surface_source_row(5_000, 810, 7, 1e-4).1, 377_928.0); + assert_eq!(surface_source_row(50_000, 810, 7, 1e-4).1, 31_746.0); + } +} diff --git a/wai/wai-quantum/src/quantum_arch.rs b/wai/wai-quantum/src/quantum_arch.rs index bd9009557..55d75e9e1 100644 --- a/wai/wai-quantum/src/quantum_arch.rs +++ b/wai/wai-quantum/src/quantum_arch.rs @@ -144,6 +144,42 @@ //! takes 10.58 h. A factoring takes 4.3 days, shots lost to logical errors //! included. //! +//! # The 2021 factoring estimate, re-run +//! +//! arXiv:1905.09749 (Quantum 5, 433) factors 2048 bits on 20 million qubits in +//! hours, under the same physical assumptions as above, with a different +//! machine: +//! - plain patches with no yoked storage; +//! - a two-level factory, 15-to-1 at a smaller distance feeding 8T-to-CCZ +//! ([`DistilledCczFactory`]); +//! - Toffolis whose Clifford fixups are teleported away from the data, so an +//! addition runs at one *tick* per Toffoli, the slower of the reaction time +//! and the CCZ supply, and does not wait a lattice-surgery *beat* of `d` +//! cycles ([`Timing::tick_us`], [`Timing::teleported_add_us`]); +//! - lookups through access hallways on both sides of each row, at half a beat +//! per entry ([`Timing::double_access_lookup_us`]). +//! +//! [`WindowedFactoringParams`] carries its parameters and +//! [`windowed_factoring_estimate`] re-runs its cost script. Every row of its +//! RSA table is reproduced to the printed precision, by the script's own +//! rounding, which takes each figure up to two significant digits. So the +//! "20 million" is 19.25 million and the 5.1 hours per run is 5.05. +//! +//! Its title's 8 hours is the expected time across retries +//! ([`WindowedEstimate::expected_hours`]): the re-run's 5.05 hours a run at its +//! 30.98% retry risk is 7.31, taken up to one digit. +//! +//! **Where the source disagrees with itself.** +//! - Its layout section works one case by hand at level-1 distance 17, a +//! 226 × 63 board and 23 million qubits, and says so. The title and the table +//! take level-1 distance 15, a 198 × 62 board and 19.25 million. +//! - That section reads a distillation error of 6.4% off a spreadsheet. Its cost +//! script's formula gives 0.6% at the same distances. +//! +//! The same factory and clock price the chemistry estimate of +//! arXiv:2011.03494 (PRX Quantum 2, 030305): four factories at distances 19 and +//! 31 tick at 38.75 µs, and 6.7·10⁹ Toffolis take 3 days. +//! //! # Honest boundaries //! //! - **Fits are fits.** The storage formulas are path-counting extrapolations @@ -757,6 +793,75 @@ impl Timing { pub fn phaseup_us(&self, w: u32) -> f64 { self.lookup_us(w) / 2.0 } + /// One tick: the slower of the CCZ supply and the reaction time. A Toffoli + /// whose Clifford fixups are teleported away from the data takes one tick + /// and no lattice-surgery beat, unlike a [`Timing::step_us`]. + pub fn tick_us(&self) -> f64 { + self.ccz_period_us().max(self.reaction_us) + } + /// A ripple-carry addition over `n` qubits, its Toffolis at one tick each: + /// `2n − 1` ticks, a sweep and its return. + pub fn teleported_add_us(&self, n: u32) -> f64 { + f64::from((2 * n).saturating_sub(1)) * self.tick_us() + } + /// A table lookup over `w` address bits through access hallways on both + /// sides of each row: `2ʷ − 1` entries at half a beat each. + pub fn double_access_lookup_us(&self, w: u32) -> f64 { + ((1u64 << w) - 1) as f64 * self.surgery_us() / 2.0 + } + /// The measurement-based uncomputation of that lookup: `2·√(2ʷ − 1)` + /// entries at half a beat each. + pub fn double_access_unlookup_us(&self, w: u32) -> f64 { + 2.0 * (((1u64 << w) - 1) as f64).sqrt() * self.surgery_us() / 2.0 + } +} + +/// Logical error of one plain patch per surface-code cycle: +/// `0.1·(100·p)^((d+1)/2)`, so at `p = 10⁻³` each two of distance give a factor +/// of ten. The rule of arXiv:1905.09749, its "Topological error" section and +/// its cost script. +pub fn patch_cycle_error(d: u32, p: f64) -> f64 { + 0.1 * exp(0.5 * f64::from(d + 1) * ln(100.0 * p)) +} + +/// A two-level magic-state factory: 15-to-1 at distance `l1_distance` feeding +/// 8T-to-CCZ at `l2_distance` (arXiv:1812.01238, with the footprint rule of the +/// cost script of arXiv:1905.09749). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DistilledCczFactory { + pub l1_distance: u32, + pub l2_distance: u32, +} + +impl DistilledCczFactory { + /// `(width, height, depth)`, in patches of the level-2 distance and beats: + /// the six level-1 factories that feed one CCZ state beside the 3 × 6 CCZ + /// block. `(15, 8, 5)` at distances 19 and 31, `(14, 8, 5)` at 9 and 15. + pub fn footprint(&self) -> (u32, u32, f64) { + let scale = f64::from(self.l1_distance) / f64::from(self.l2_distance); + let (t1_height, t1_width, t1_depth) = (4.0 * scale, 8.0 * scale, 5.75 * scale); + let (ccz_depth, ccz_height, ccz_width) = (5.0_f64, 6.0_f64, 3.0); + let storage_width = 2.0 * scale; + let t1_factories = ((1.0 / ccz_depth * 8.0) / (1.0 / t1_depth)).ceil(); + let column = t1_height * (t1_factories / 2.0).ceil(); + let width = (t1_width * 2.0 + ccz_width + storage_width).ceil(); + let height = ccz_height.max(column).ceil(); + (width as u32, height as u32, ccz_depth.max(t1_depth)) + } + + /// Cycles per CCZ state: the depth in beats, times the level-2 distance. + pub fn rounds_per_ccz(&self) -> f64 { + self.footprint().2 * f64::from(self.l2_distance) + } + + /// Error of one CCZ state at physical error `p`. Physical T states at level + /// 0 err by `p` plus 100 cells at half the level-1 distance; level 1 gives + /// `35·p₀³` plus 1100 cells; level 2 gives `28·p₁²` plus 1000 cells. + pub fn error(&self, p: f64) -> f64 { + let l0 = p + 100.0 * patch_cycle_error(self.l1_distance / 2, p); + let l1 = 35.0 * l0 * l0 * l0 + 1100.0 * patch_cycle_error(self.l1_distance, p); + 28.0 * l1 * l1 + 1000.0 * patch_cycle_error(self.l2_distance, p) + } } /// The parameters of the approximate-residue factoring algorithm of @@ -1146,6 +1251,159 @@ pub fn factoring_estimate(params: &FactoringParams, machine: &Machine) -> Result }) } +/// The parameters of the windowed factoring construction of arXiv:1905.09749. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WindowedFactoringParams { + /// Bits in the modulus. + pub n: u32, + /// Exponent qubits, `3(n/2 − 1) − 40` for an RSA modulus in one run. + pub exponent_qubits: u32, + pub exp_window: u32, + pub mul_window: u32, + /// Bits between the oblivious carry runways. + pub runway_sep: u32, + /// Runway and coset padding beyond `⌈log₂(n²·nₑ)⌉`. + pub padding_offset: u32, + pub factory: DistilledCczFactory, +} + +impl WindowedFactoringParams { + /// A row of the source's RSA table (its table of implications for RSA), for + /// `n` ∈ {1024, 2048, 3072, 4096, 8192, 12288, 16384}. + pub fn published(n: u32) -> Option { + let (l1_distance, l2_distance, padding_offset, mul_window) = match n { + 1024 => (15, 27, 5, 5), + 2048 => (15, 27, 4, 5), + 3072 => (17, 29, 6, 4), + 4096 => (17, 31, 9, 4), + 8192 => (19, 33, 4, 4), + 12288 => (19, 33, 3, 4), + 16384 => (19, 33, 4, 4), + _ => return None, + }; + Some(WindowedFactoringParams { + n, + exponent_qubits: 3 * (n / 2 - 1) - 40, + exp_window: 5, + mul_window, + runway_sep: 1024, + padding_offset, + factory: DistilledCczFactory { l1_distance, l2_distance }, + }) + } + + /// The carry runway and coset padding: `⌈log₂(n²·nₑ)⌉` plus the offset, + /// as the source's cost script takes it (38 at `n = 2048`). Its text + /// approximates it as `3·lg n + 10`. + pub fn padding(&self) -> u64 { + let n = u64::from(self.n); + let square = n * n * u64::from(self.exponent_qubits); + u64::from(64 - (square - 1).leading_zeros()) + u64::from(self.padding_offset) + } + + /// Qubits in one runway-separated piece of the register: the separation + /// and the padding. + pub fn piece_qubits(&self) -> u64 { + u64::from(self.runway_sep) + self.padding() + } +} + +/// What the windowed construction costs, per run. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct WindowedEstimate { + /// Runway-separated pieces of the register, and each piece's board. + pub pieces: u64, + pub piece_width: u64, + pub piece_height: u64, + /// CCZ factories over all pieces. + pub factories: u64, + /// Patches on the board, and the physical qubits they cover. + pub logical_qubits: u64, + pub physical_qubits: u64, + pub toffolis: f64, + pub timing: Timing, + /// One lookup addition, µs. + pub inner_loop_us: f64, + pub hours: f64, + pub distillation_error: f64, + pub topological_error: f64, + pub deviation_error: f64, + /// The chance a run must be retried: the union of the three above and a + /// 1% post-processing allowance. + pub retry_risk: f64, +} + +impl WindowedEstimate { + /// Physical qubits × hours, in megaqubit-days. + /// Hours expected across retries: a run's hours over the chance it needs + /// no retry. + pub fn expected_hours(&self) -> f64 { + self.hours / (1.0 - self.retry_risk) + } + + pub fn megaqubit_days(&self) -> f64 { + self.physical_qubits as f64 / 1e6 * self.hours / 24.0 + } +} + +/// Re-run the cost script of arXiv:1905.09749 at physical error `p`, cycle time +/// and reaction time in µs. Factory pairs are added until the CCZ supply keeps +/// up with the reaction time, and each runway-separated piece gets its own. +pub fn windowed_factoring_estimate(params: &WindowedFactoringParams, p: f64, cycle_us: f64, reaction_us: f64) -> WindowedEstimate { + let n = u64::from(params.n); + let n_e = u64::from(params.exponent_qubits); + let sep = u64::from(params.runway_sep); + let pieces = n.div_ceil(sep); + let padding = params.padding(); + let piece_len = params.piece_qubits(); + let mul_in_bits = n + padding + 2; + let inner_loops = (n_e * 2 * mul_in_bits).div_ceil(u64::from(params.exp_window * params.mul_window)); + let classical = inner_loops as f64 * pieces as f64 / pow2(padding as i32); + let deviation_error = 4.0 * classical.sqrt(); + + let w = params.exp_window + params.mul_window; + let d = params.factory.l2_distance; + let (fw, fh, depth) = params.factory.footprint(); + let (fw, fh) = (u64::from(fw), u64::from(fh)); + let ccz_rounds = depth * f64::from(d); + let pairs = (ccz_rounds * cycle_us / reaction_us / 2.0).ceil() as u64; + let width = (fw + 1) * pairs + 1; + let register_rows = piece_len.div_ceil(width - 2); + let height = fh * 2 + 3 * 2 + 3 + 6 + register_rows * 3; + let logical_qubits = width * height * pieces; + let distillation_area = fh * fw * pairs * 2 * pieces; + + let timing = Timing { cycle_us, reaction_us, distance: d, ccz_rounds, factories: (2 * pairs) as u32 }; + let adder_depth = (piece_len * 2 - 1) as f64; + let lookup_depth = ((1u64 << w) - 1) as f64; + let unlookup_depth = 2.0 * lookup_depth.sqrt(); + let toffolis = (adder_depth * pieces as f64 + lookup_depth + unlookup_depth) * inner_loops as f64; + let inner_loop_us = timing.teleported_add_us(piece_len as u32) + timing.double_access_lookup_us(w) + timing.double_access_unlookup_us(w); + let micros = inner_loop_us * inner_loops as f64; + + let cells = (logical_qubits - distillation_area) as f64 * (micros / cycle_us); + let topological_error = cells * patch_cycle_error(d, p); + let distillation_error = toffolis * params.factory.error(p); + let survive = [topological_error, distillation_error, deviation_error, 1e-2].iter().fold(1.0, |t, e: &f64| if *e >= 1.0 { 0.0 } else { t * (1.0 - e) }); + + WindowedEstimate { + pieces, + piece_width: width, + piece_height: height, + factories: 2 * pairs * pieces, + logical_qubits, + physical_qubits: logical_qubits * patch_qubits(d), + toffolis, + timing, + inner_loop_us, + hours: micros / 3.6e9, + distillation_error, + topological_error, + deviation_error, + retry_risk: 1.0 - survive, + } +} + #[cfg(test)] mod tests { use super::*; @@ -1354,4 +1612,124 @@ mod tests { let m = StorageModel { flat: f, ..StorageModel::uniform_refit() }; assert_eq!(flat_distance(&m, 1e-15), Some(26)); } + + /// The source's rounding: up, to two significant digits. + fn printed(q: f64) -> f64 { + let n = q.log10().floor(); + let r = (q / 10f64.powf(n - 1.0)).ceil() * 10f64.powf(n - 1.0); + if r < 0.1 { + (r * 100.0).round() / 100.0 + } else if r < 10.0 { + (r * 10.0).round() / 10.0 + } else { + r.round() + } + } + + #[test] + fn the_title_s_hours_are_the_expected_hours_the_module_states() { + // The module's text: "5.05 hours a run at its 30.98% retry risk is + // 7.31", which the title's 8 hours takes up to one digit. + let e = windowed_factoring_estimate(&WindowedFactoringParams::published(2048).unwrap(), 1e-3, 1.0, 10.0); + assert_eq!(e.expected_hours(), e.hours / (1.0 - e.retry_risk)); + assert_eq!(format!("{:.2} {:.2} {:.2}", e.hours, 100.0 * e.retry_risk, e.expected_hours()), "5.05 30.98 7.31"); + assert!(e.expected_hours().ceil() == 8.0); + } + + #[test] + fn a_teleported_addition_runs_at_the_tick_and_not_the_beat() { + // arXiv:1905.09749, its Runtime section (§2.11) at n = 2048: 14 + // factories at distance 27 keep up with a 10 µs reaction time. An + // addition over a piece of c_sep + c_pad qubits takes "≈ 22 + // milliseconds", a lookup over 10 address bits "≈ 14 milliseconds". + // Its cost script's padding is 38 (its text's 3·lg n + 10 is 43): a + // piece of 1,062 qubits. + let piece = WindowedFactoringParams::published(2048).unwrap().piece_qubits(); + assert_eq!(piece, 1024 + 38); + let t = Timing { cycle_us: 1.0, reaction_us: 10.0, distance: 27, ccz_rounds: 135.0, factories: 14 }; + assert_eq!(t.tick_us(), 10.0); + assert!((t.teleported_add_us(piece as u32) / 1000.0 - 21.23).abs() < 1e-9); + // At its text's padding, 2n − 1 ticks are 21.33 ms; its text counts + // 2(c_sep + c_pad) ticks, 21.34 ms, and prints "≈ 22". + assert!((t.teleported_add_us(1024 + 43) / 1000.0 - 21.33).abs() < 1e-9); + assert!((t.double_access_lookup_us(10) / 1000.0 - 13.81).abs() < 0.01); + // The step clock waits a beat at every Toffoli: 57.3 ms. + assert!((t.add_us(piece as u32) / 1000.0 - 57.294).abs() < 1e-9); + assert!(t.add_us(piece as u32) > 2.5 * t.teleported_add_us(piece as u32)); + // Too few factories: the supply sets the tick. + let starved = Timing { factories: 4, ..t }; + assert_eq!(starved.tick_us(), 33.75); + } + + #[test] + fn the_two_level_factory_has_the_published_footprints_and_errors() { + // arXiv:1905.09749, the layout section: 15 × 8 at distances 17 and 27. + assert_eq!(DistilledCczFactory { l1_distance: 17, l2_distance: 27 }.footprint().0, 15); + assert_eq!(DistilledCczFactory { l1_distance: 17, l2_distance: 27 }.footprint().1, 8); + // arXiv:2011.03494: "15d × 8d and a depth of 5d" at 19 and 31. + let f = DistilledCczFactory { l1_distance: 19, l2_distance: 31 }; + assert_eq!(f.footprint(), (15, 8, 5.0)); + // ... and "14d × 8d" at 9 and 15. + assert_eq!(DistilledCczFactory { l1_distance: 9, l2_distance: 15 }.footprint().0, 14); + // "a roughly 0.1% chance of any distillation failure occurring when + // executing ten billion Toffolis". + let chance = 1e10 * f.error(1e-3); + assert!((0.0009..0.0011).contains(&chance), "{chance}"); + // Its text reads "a total distillation error of 6.4%" for 3 billion at 17 + // and 27 off another paper's spreadsheet. Its own cost script's formula, + // which produces its tables, gives 0.6%. + let chance = 3e9 * DistilledCczFactory { l1_distance: 17, l2_distance: 27 }.error(1e-3); + assert!((0.0055..0.0065).contains(&chance), "{chance}"); + } + + #[test] + fn the_2021_rsa_table_is_reproduced_to_its_printed_digits() { + // (n, retry risk %, volume per run, volume expected, megaqubits, hours) + for (n, risk, volume, expected, mq, hours) in [ + (1024, 6.0, 0.5, 0.5, 9.7, 1.3), + (2048, 31.0, 4.1, 5.9, 20.0, 5.1), + (3072, 9.0, 19.0, 21.0, 38.0, 12.0), + (4096, 5.0, 48.0, 51.0, 55.0, 22.0), + (8192, 5.0, 480.0, 510.0, 140.0, 86.0), + (12288, 12.0, 1700.0, 1900.0, 200.0, 200.0), + (16384, 24.0, 3900.0, 5100.0, 270.0, 350.0), + ] { + let e = windowed_factoring_estimate(&WindowedFactoringParams::published(n).unwrap(), 1e-3, 1.0, 10.0); + assert_eq!((100.0 * e.retry_risk).ceil(), risk, "n={n}: {}", e.retry_risk); + let at_published_risk = 1.0 - (100.0 * e.retry_risk).ceil() / 100.0; + assert_eq!(printed(e.megaqubit_days()), volume, "n={n}"); + assert_eq!(printed(e.megaqubit_days() / at_published_risk), expected, "n={n}"); + assert_eq!(printed(e.physical_qubits as f64 / 1e6), mq, "n={n}"); + assert_eq!(printed(e.hours), hours, "n={n}"); + } + let e = windowed_factoring_estimate(&WindowedFactoringParams::published(2048).unwrap(), 1e-3, 1.0, 10.0); + assert_eq!((e.piece_width * e.pieces, e.piece_height), (198, 62)); + assert_eq!(e.physical_qubits, 198 * 62 * 1568); + // Its layout section: 14 factories in each of the two pieces. + assert_eq!(e.factories, 28); + assert!((e.toffolis / 2.698e9 - 1.0).abs() < 1e-3, "{}", e.toffolis); + } + + #[test] + fn the_chemistry_estimate_is_priced_by_the_same_clock() { + // arXiv:2011.03494, its surface-code section. Four factories at + // distances 19 and 31, "a Toffoli production rate of 25 kHz"; 483,000 + // inner loops of 13,880 Toffolis, "3 days"; 53 × 36 patches, "about four + // million" qubits. + let f = DistilledCczFactory { l1_distance: 19, l2_distance: 31 }; + let t = Timing { cycle_us: 1.0, reaction_us: 10.0, distance: 31, ccz_rounds: f.rounds_per_ccz(), factories: 4 }; + assert_eq!(t.tick_us(), 38.75); + assert_eq!(t.bound(), Bound::MagicStates); + let toffolis = 483_000.0 * 13_880.0; + let days = toffolis * t.tick_us() / 86.4e9; + assert!((3.0..3.01).contains(&days), "{days}"); + assert_eq!(53 * 36 * patch_qubits(31), 3_907_584); + // At p = 10⁻⁴, distances 9 and 15 and the same floorplan: "about one + // million" and "1.5 days". + assert_eq!(53 * 36 * patch_qubits(15), 976_896); + let g = DistilledCczFactory { l1_distance: 9, l2_distance: 15 }; + let t = Timing { distance: 15, ccz_rounds: g.rounds_per_ccz(), ..t }; + let days = toffolis * t.tick_us() / 86.4e9; + assert!((1.45..1.46).contains(&days), "{days}"); + } } diff --git a/wai/wai-quantum/src/quantum_bicycle.rs b/wai/wai-quantum/src/quantum_bicycle.rs new file mode 100644 index 000000000..7d7db95e7 --- /dev/null +++ b/wai/wai-quantum/src/quantum_bicycle.rs @@ -0,0 +1,1491 @@ +//! The BICYCLE ARCHITECTURE: block codes in a whole-machine estimate, as part +//! of `wai.quantum.arch`. +//! +//! The planar estimator (`quantum_resource`) prices one logical qubit per tile +//! and the routing beside it. A high-rate block code does not fit that shape: +//! twelve logical qubits share one code block, and they are reached by a small +//! instruction set and not by lattice surgery on a tile. This module prices +//! that machine, as arXiv:2506.03094 specifies it, from the parts it publishes: +//! +//! - **Modules.** Two bivariate-bicycle codes, the gross `[[144,12,12]]` and +//! the two-gross `[[288,12,18]]` ([`Code`]). Each module holds the code, a +//! logical processing unit and an adapter to its neighbour. Modules sit in a +//! line, with one T factory at the end ([`Machine`]): +//! +//! ```text +//! q = M·(c + u + a) − a + a′ + f n = 11·M +//! ``` +//! +//! - **Instructions.** Idle, shift automorphism, in-module measurement, +//! inter-module measurement, T injection ([`Instruction`], [`TFactory`]). Each +//! has a duration in timesteps, one physical gate each, and a logical error +//! rate. +//! - **Logical error rates.** The source's failure-spectrum fits ([`SpectrumFit`]), +//! evaluated at any `p` by its own sum. Where the source has no data, its +//! assumed values, marked as assumed ([`RateSource`]). +//! - **The compile.** A Pauli rotation over `n` qubits becomes, per module, one +//! in-module measurement of a Pauli on the 11 data qubits, synthesised from +//! the native measurements by conjugation. This module finds the cost of every +//! one of the `4¹¹ − 1` Paulis by breadth-first search ([`PauliCosts`]), from +//! the code's polynomials ([`native_measurements`]). +//! - **The failure budget.** To leading order `P = Σⱼ Nⱼ·Pⱼ` +//! ([`InstructionCounts::failure`]). +//! +//! # Checked against the source +//! +//! Each of these is a test in this module, and the page +//! `docs/published-architectures.md` prints the published figure beside ours. +//! +//! - **The logical basis.** From the printed polynomials: the Gram matrix is +//! the identity, every operator commutes with every check, the check matrices +//! leave `k = 12`, and a shift acts on the first block as the printed matrices +//! `A_x`, `A_y`, and on the second as their transposes. +//! - **The native sets.** 540 native measurements. Any shift is at most two +//! generators. +//! - **The rates.** All 28 printed rates (`P_I, P_U, P_M, P_C` over `p`) come +//! back from the fits to the printed three digits. +//! - **The machine.** Every physical-qubit count and every logical-qubit count +//! of the source's capability table, by the equation above. One count differs +//! (below). +//! - **The Ising program's inter-module measurements**, 946,800, exactly, with +//! sites in order and the factory after the last module. The source prints +//! neither; with the factory before the first module the count is 781,200. +//! This module takes the end that gives the source's count, everywhere: the +//! one choice here made to match a published figure. +//! +//! # Where the source disagrees with itself +//! +//! Re-running it surfaced these. None of them moves its order-of-magnitude +//! conclusion. +//! +//! - **Idle and automorphism rates.** The text says the circuit's failure is +//! divided by `C` cycles. The fits give the printed rates undivided, so the +//! printed `P_I`, `P_U` are per `C` cycles: too high by 10 for the gross code +//! and 18 for the two-gross code. +//! - **The T factory.** The cultivation table gives `P_e2e = 2·10⁻⁹` at `d₁ = 5` +//! and says the factory reads it off. The factory table and the instruction +//! table print `2·10⁻⁸`. Its capability row is computed with `2·10⁻⁸`. +//! - **T count of the Ising program.** Text and figure say 19,036,800. The +//! instruction-count table, and every printed failure probability, take +//! 15,542,400. +//! - **Qubits of the Ising program, gross.** The text says 4817, which needs +//! the `p = 10⁻³` adapter of 29 qubits beside the `p = 10⁻⁴` factory. The +//! stated factory's adapter gives 4801. +//! - **Surface-code comparison, qubits.** The capability comparison reserves +//! half the patches for routing (`4d²` per logical qubit). The Ising figure +//! counts `2d²` per logical qubit. +//! - **How a shift acts.** The source says a shift acts as the same matrix `A` +//! on both halves of a module. On its printed basis it acts as `A` on the +//! first half and `Aᵀ` on the second. Taken as it states it +//! ([`ShiftAction::Stated`]), the native measurements differ but every cost +//! is the same. +//! - **Mean compile overhead.** The source prints 18.48 and 18.58 in-module +//! measurements per Pauli for gross and two-gross with the pivot's basis +//! chosen, and 18.55 and 18.67 with it fixed. From the operators the search +//! here gives 18.53, 18.48, 18.59 and 18.56. Its authors' released compiler, +//! `bicycle_cliffords` 0.1.0 (crates.io, 2025-07-31), `src/measurement.rs`, +//! takes a shift's action as `A` on `X̄` and `A⁻¹` on `Z̄`, on both halves. +//! Taken so ([`ShiftAction::SourceTables`]), the search here gives the +//! source's four figures, every printed digit. A CNOT circuit `|x⟩ → |Ax⟩` +//! takes `Z̄` by `A⁻ᵀ`, which is how the operators move; its version 0.2.1 +//! takes `X̄` by `A⁻¹` and `Z̄` by `Aᵀ`, a consistent pair. The difference +//! moves the capabilities below by under 0.6%. +//! - **Native rotations.** The source defines a native rotation by a native +//! measurement `Q ⊗ R` with `Q` one of `X, Y, Z` on the pivot, and counts 510. +//! There are 245 such `R`. Over all 540 native measurements there are 510 +//! distinct `R ≠ I`: 284 occur with `Q = I`, 19 of them also with `Q ≠ I`, +//! so 265 occur only with `Q = I`. That the source counted those 510 is our +//! inference from the matching count. Its histogram's first bin holds 245. +//! +//! One difference is open: +//! +//! - **`p = 10⁻⁴` gross capability.** Its failure per rotation is linear in the +//! modules, with the slope ours predicts, and a constant term of `8.3·10⁻⁷`, +//! twenty times the printed factory error of `4·10⁻⁸`. Nothing the source +//! prints accounts for it. Its authors' compiler is released +//! (`bicycle_compiler` 0.1.0 on crates.io); its capability estimate at these +//! machines would settle it. This module does not run it. +//! +//! # Honest boundaries +//! +//! - **The rates are the source's.** No decoder here has run these instructions. +//! `quantum_ldpc` measures the memory of the same codes, at higher `p`. The +//! fits extrapolate far below what any simulation reaches, and the source warns +//! against over-reading the two-gross idle rate. +//! - **Two-gross measurements are assumed.** `10⁻¹¹`, `10⁻⁹` at `p = 10⁻³` and +//! `10⁻²⁰`, `10⁻¹⁸` at `10⁻⁴`, with no data behind them. Between the two a +//! power law is drawn through them. +//! - **The schedule is a mean.** The source compiles random rotations and reads +//! the critical path. Here a rotation's time is the mean of its parts, so +//! timesteps per rotation are an estimate, a few percent low. +//! - **Peephole removal is not done.** The source removes repeated in-module +//! measurements. This module counts them. +//! - **A timestep has no length.** Neither the source nor this module says how +//! long a physical gate takes. Multiply by what the hardware does. + +use crate::repro::{exp, ln}; + +/// A bivariate-bicycle code module. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Code { + /// `[[144,12,12]]`: `ℓ = 12`, `m = 6`. + Gross, + /// `[[288,12,18]]`: `ℓ = 12`, `m = 12`. + TwoGross, +} + +/// An instruction of the architecture other than T injection. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Instruction { + /// One syndrome cycle on an idling module. + Idle, + /// A shift automorphism: two rounds of swaps and a syndrome cycle. + Automorphism, + /// A measurement inside a module through its logical processing unit. + InModule, + /// A joint measurement of two neighbouring modules. + InterModule, +} + +/// Where a logical error rate comes from. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RateSource { + /// A failure-spectrum fit to the source's circuit simulations. + Fit, + /// Assumed by the source, with no simulation behind it. + Assumed, +} + +/// A logical error rate per instruction, and where it comes from. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Rate { + pub value: f64, + pub source: RateSource, +} + +/// The source's failure-spectrum ansatz. Among weight-`w` fault configurations +/// the fraction that fails is `f(w) = a·(1 − exp(−(f₀/a)·(w/w₀)^γ))` for +/// `w ≥ w₀`, and 0 below, with `a = 1 − 2^−K`. Over `N` independent faults of +/// probability `q = p/15` the circuit fails with +/// `Σ_w f(w)·C(N,w)·q^w·(1−q)^(N−w)`. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SpectrumFit { + /// `ln f₀`. + pub ln_f0: f64, + pub gamma: f64, + pub w0: u32, + /// Logical operators the circuit's action is checked on. + pub k: u32, + /// Faults `N` in the simulated circuit. + pub faults: u64, + /// Instructions the simulated circuit holds, `C`. + pub instructions: u32, +} + +impl SpectrumFit { + /// The simulated circuit's probability of failure at physical error `p`. + /// The binomial weights grow outward from the mode in a fixed order, so the + /// answer is the same bits everywhere. + pub fn circuit_failure(&self, p: f64) -> f64 { + let q = p / 15.0; + let n = self.faults; + let a = 1.0 - pow2(-(self.k as i32)); + let ln_scale = self.ln_f0 - ln(a); + let w0 = f64::from(self.w0); + let fail = |w: u64| -> f64 { + if w < u64::from(self.w0) { + return 0.0; + } + let x = exp(ln_scale + self.gamma * (ln(w as f64) - ln(w0))); + a * if x < 1e-4 { x * (1.0 - x * (0.5 - x / 6.0)) } else { 1.0 - exp(-x) } + }; + let ratio = q / (1.0 - q); + let mode = (((n + 1) as f64) * q) as u64; + let (mut total, mut sum) = (1.0, fail(mode)); + let mut weight = 1.0; + let mut w = mode; + while w > 0 { + weight *= w as f64 / ((n - w + 1) as f64) / ratio; + w -= 1; + total += weight; + sum += weight * fail(w); + if weight < 1e-40 * total { + break; + } + } + let mut weight = 1.0; + let mut w = mode; + while w < n { + weight *= ((n - w) as f64) / ((w + 1) as f64) * ratio; + w += 1; + total += weight; + let term = weight * fail(w); + sum += term; + if w > u64::from(self.w0) && weight * a < 1e-22 * sum { + break; + } + } + sum / total + } + + /// Per instruction: the circuit's failure divided by the instructions it + /// holds, as the source's text says. + pub fn per_instruction(&self, p: f64) -> f64 { + self.circuit_failure(p) / f64::from(self.instructions) + } +} + +const fn spectrum(ln_f0: f64, gamma: f64, w0: u32, k: u32, faults: u64, instructions: u32) -> SpectrumFit { + SpectrumFit { ln_f0, gamma, w0, k, faults, instructions } +} + +/// A power law through two points: `v₁` at `p = 10⁻³`, `v₂` at `p = 10⁻⁴`. +fn assumed(v1: f64, v2: f64, p: f64) -> f64 { + let slope = (ln(v1) - ln(v2)) / core::f64::consts::LN_10; + exp(ln(v1) + slope * (ln(p) - ln(1e-3))) +} + +impl Code { + /// `(n, k, d)`. + pub fn parameters(self) -> (u32, u32, u32) { + match self { + Code::Gross => (144, 12, 12), + Code::TwoGross => (288, 12, 18), + } + } + + /// Physical qubits of the code block: `n` data qubits and one check qubit + /// per check, `c = 2n`. + pub fn code_qubits(self) -> u64 { + 2 * u64::from(self.parameters().0) + } + + /// Physical qubits of the logical processing unit, `u`, as the source + /// tallies them. The unit's construction is not rebuilt here. + pub fn lpu_qubits(self) -> u64 { + match self { + Code::Gross => 90, + Code::TwoGross => 158, + } + } + + /// Physical qubits of the adapter between two modules: `a = 2(d − 1)`. + pub fn adapter_qubits(self) -> u64 { + 2 * (u64::from(self.parameters().2) - 1) + } + + /// Duration in timesteps. A measurement takes `C` syndrome cycles in the + /// deformed code and one in the code. + pub fn timesteps(self, i: Instruction) -> u32 { + match (i, self) { + (Instruction::Idle, _) => 8, + (Instruction::Automorphism, _) => 14, + (Instruction::InModule | Instruction::InterModule, Code::Gross) => 120, + (Instruction::InModule | Instruction::InterModule, Code::TwoGross) => 216, + } + } + + /// The source's fit for an instruction, where it has one. An in-module + /// measurement is priced at its worst representative, `Ȳ₁`. + pub fn fit(self, i: Instruction) -> Option { + Some(match (self, i) { + (Code::Gross, Instruction::Idle) => spectrum(-31.02, 8.66, 5, 24, 210_960, 10), + (Code::Gross, Instruction::Automorphism) => spectrum(-28.63, 7.13, 5, 24, 483_840, 10), + (Code::Gross, Instruction::InModule) => spectrum(-19.49, 4.65, 5, 23, 400_117, 1), + (Code::Gross, Instruction::InterModule) => spectrum(-18.32, 5.19, 5, 23, 743_456, 1), + (Code::TwoGross, Instruction::Idle) => spectrum(-95.83, 25.78, 9, 24, 762_912, 18), + (Code::TwoGross, Instruction::Automorphism) => spectrum(-108.53, 28.22, 9, 24, 1_748_736, 18), + (Code::TwoGross, Instruction::InModule | Instruction::InterModule) => return None, + }) + } + + /// Logical error per instruction at physical error `p`. + pub fn rate(self, i: Instruction, p: f64) -> Rate { + match (self.fit(i), i) { + (Some(f), _) => Rate { value: f.per_instruction(p), source: RateSource::Fit }, + (None, Instruction::InterModule) => Rate { value: assumed(1e-9, 1e-18, p), source: RateSource::Assumed }, + (None, _) => Rate { value: assumed(1e-11, 1e-20, p), source: RateSource::Assumed }, + } + } + + /// The rate as the source's table prints it: for a fitted instruction, the + /// simulated circuit's failure, not divided by the instructions it holds. + pub fn tabulated_rate(self, i: Instruction, p: f64) -> f64 { + match self.fit(i) { + Some(f) => f.circuit_failure(p), + None => self.rate(i, p).value, + } + } + + /// Mean in-module measurements per Pauli on the 11 data qubits, from the + /// search ([`PauliCosts::search`]), which a release test repeats. + pub fn mean_measurements(self) -> f64 { + histogram_mean(&self.cost_table().0) + } + + /// Mean shift automorphisms per Pauli on the 11 data qubits, the fewest + /// along a path of fewest measurements ([`PauliCosts::search`]). + pub fn mean_automorphisms(self) -> f64 { + let (h, autos) = self.cost_table(); + autos as f64 / h.iter().sum::() as f64 + } + + /// The search's results: Paulis by native rotations `k` (`6k + 1` in-module + /// measurements), and the automorphisms over all of them. + fn cost_table(self) -> ([u64; 5], u64) { + match self { + Code::Gross => ([245, 12_579, 490_770, 3_505_249, 185_460], GROSS_AUTOMORPHISMS), + Code::TwoGross => ([245, 12_953, 506_147, 3_504_477, 170_481], TWO_GROSS_AUTOMORPHISMS), + } + } + + fn layout(self) -> &'static Layout { + match self { + Code::Gross => &GROSS, + Code::TwoGross => &TWO_GROSS, + } + } +} + +const GROSS_AUTOMORPHISMS: u64 = 76_882_096; +const TWO_GROSS_AUTOMORPHISMS: u64 = 76_073_272; + +fn pow2(e: i32) -> f64 { + let mut x = 1.0; + for _ in 0..e.unsigned_abs() { + x = if e < 0 { x / 2.0 } else { x * 2.0 }; + } + x +} + +/// How a T factory makes its states. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FactoryKind { + Cultivation, + Distillation, +} + +/// A T factory module. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct TFactory { + pub kind: FactoryKind, + /// The surface-code distance its state arrives in. + pub distance: u32, + /// Physical qubits, `f`. + pub qubits: u64, + /// Logical error of a state, `P_factory`. + pub error: f64, + /// Mean timesteps per state. + pub timesteps: f64, +} + +impl TFactory { + /// Cultivation at `d₁ = 3`, for the gross code at `p = 10⁻³`. + pub fn gross_e3() -> TFactory { + TFactory { kind: FactoryKind::Cultivation, distance: 15, qubits: 454, error: 3e-6, timesteps: 351.0 } + } + /// Cultivation at `d₁ = 5`, for the two-gross code at `p = 10⁻³`, at the + /// `2·10⁻⁸` its factory table prints. Its cultivation table gives `2·10⁻⁹`. + pub fn two_gross_e3() -> TFactory { + TFactory { kind: FactoryKind::Cultivation, distance: 15, qubits: 463, error: 2e-8, timesteps: 2167.0 } + } + /// 15-to-1 at `(7, 3, 3)`, for the gross code at `p = 10⁻⁴`. + pub fn gross_e4() -> TFactory { + TFactory { kind: FactoryKind::Distillation, distance: 7, qubits: 810, error: 4e-8, timesteps: 73.0 } + } + /// Two rounds of 15-to-1, for the two-gross code at `p = 10⁻⁴`. + pub fn two_gross_e4() -> TFactory { + TFactory { kind: FactoryKind::Distillation, distance: 25, qubits: 18_600, error: 6e-25, timesteps: 407.0 } + } + /// This factory with another error for its states. + pub fn with_error(self, error: f64) -> TFactory { + TFactory { error, ..self } + } + /// Physical qubits of the adapter to a code module: `a′ = 2d − 1`. + pub fn adapter_qubits(&self) -> u64 { + 2 * u64::from(self.distance) - 1 + } +} + +/// Mean timesteps of an in-place cultivation: a state is discarded at two +/// points, after cultivation (with probability `discard_cult`) and after +/// escape, for `discard_e2e` in all: +/// `t_cult/(1 − discard_cult) + t_escape/(1 − discard_escape)`. The source +/// rounds `discard_escape` to two digits before this division, which gives its +/// 351 and 2167 where the exact division gives 352.5 and 2243. +pub fn cultivation_timesteps(t_cult: f64, t_escape: f64, discard_cult: f64, discard_e2e: f64) -> f64 { + let discard_escape = (discard_e2e - discard_cult) / (1.0 - discard_cult); + t_cult / (1.0 - discard_cult) + t_escape / (1.0 - discard_escape) +} + +/// Data qubits per module: one of the twelve is the pivot. +pub const DATA_QUBITS: u64 = 11; + +/// A line of code modules with one T factory at its end. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Machine { + pub code: Code, + pub factory: TFactory, + pub modules: u64, +} + +/// How many of each instruction a program takes. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct InstructionCounts { + pub idle: f64, + pub automorphism: f64, + pub in_module: f64, + pub inter_module: f64, + pub t_injection: f64, +} + +impl InstructionCounts { + /// To leading order `P = Σⱼ Nⱼ·Pⱼ`, each instruction at `code`'s rate at + /// `p`, and a T injection at the factory's error plus an inter-module + /// measurement. + pub fn failure(&self, code: Code, factory_error: f64, p: f64) -> f64 { + let r = |i| code.rate(i, p).value; + self.idle * r(Instruction::Idle) + + self.automorphism * r(Instruction::Automorphism) + + self.in_module * r(Instruction::InModule) + + self.inter_module * r(Instruction::InterModule) + + self.t_injection * (factory_error + r(Instruction::InterModule)) + } + + /// [`InstructionCounts::failure`] with the idle and automorphism rates + /// undivided, as the source's tables print them. + pub fn failure_undivided(&self, code: Code, factory_error: f64, p: f64) -> f64 { + let r = |i| code.rate(i, p).value; + self.idle * code.tabulated_rate(Instruction::Idle, p) + + self.automorphism * code.tabulated_rate(Instruction::Automorphism, p) + + self.in_module * r(Instruction::InModule) + + self.inter_module * r(Instruction::InterModule) + + self.t_injection * (factory_error + r(Instruction::InterModule)) + } + + /// The same sum at the rates as the source's table prints them, with a T + /// injection at an inter-module measurement's rate. + pub fn failure_as_tabulated(&self, code: Code, p: f64) -> f64 { + let r = |i| code.tabulated_rate(i, p); + self.idle * r(Instruction::Idle) + + self.automorphism * r(Instruction::Automorphism) + + self.in_module * r(Instruction::InModule) + + self.inter_module * r(Instruction::InterModule) + + self.t_injection * r(Instruction::InterModule) + } +} + +impl Machine { + /// The fewest modules that hold `n` logical qubits. + pub fn for_logical(code: Code, factory: TFactory, n: u64) -> Machine { + Machine { code, factory, modules: n.div_ceil(DATA_QUBITS) } + } + + /// The most modules that fit in `q` physical qubits, or `None` if not even + /// one does. + pub fn largest(code: Code, factory: TFactory, q: u64) -> Option { + let per = code.code_qubits() + code.lpu_qubits() + code.adapter_qubits(); + let fixed = factory.adapter_qubits() + factory.qubits; + let m = (q + code.adapter_qubits()).checked_sub(fixed)? / per; + (m > 0).then_some(Machine { code, factory, modules: m }) + } + + /// `q = M·(c + u + a) − a + a′ + f`. + pub fn physical_qubits(&self) -> u64 { + let c = self.code; + self.modules * (c.code_qubits() + c.lpu_qubits() + c.adapter_qubits()) - c.adapter_qubits() + self.factory.adapter_qubits() + self.factory.qubits + } + + /// `n = 11·M`. + pub fn logical_qubits(&self) -> u64 { + DATA_QUBITS * self.modules + } + + /// One `T` rotation of a Pauli with support on every module: a GHZ state + /// over the modules, one in-module measurement per module, one T injection, + /// and the modules idle while the factory works. + pub fn rotation(&self) -> InstructionCounts { + let m = self.modules as f64; + InstructionCounts { + idle: m * self.factory.timesteps / f64::from(self.code.timesteps(Instruction::Idle)), + automorphism: m * self.code.mean_automorphisms(), + in_module: m * self.code.mean_measurements(), + inter_module: m - 1.0, + t_injection: 1.0, + } + } + + /// Timesteps of that rotation: the factory, two rounds of inter-module + /// measurements for the GHZ state, the injection and one module's + /// in-module sequence. A mean, not a critical path. + pub fn rotation_timesteps(&self) -> f64 { + let c = self.code; + let tc = f64::from(c.timesteps(Instruction::InterModule)); + self.factory.timesteps + tc + 2.0 * tc + c.mean_measurements() * f64::from(c.timesteps(Instruction::InModule)) + c.mean_automorphisms() * f64::from(c.timesteps(Instruction::Automorphism)) + } + + /// The most rotations that keep the program's failure at `budget`, at + /// physical error `p`. + pub fn capability(&self, p: f64, budget: f64) -> f64 { + budget / self.rotation().failure(self.code, self.factory.error, p) + } + + /// [`Machine::capability`] with the idle and automorphism rates undivided, + /// as the source's tables print them. + pub fn capability_undivided(&self, p: f64, budget: f64) -> f64 { + budget / self.rotation().failure_undivided(self.code, self.factory.error, p) + } + + /// The largest physical error at which `rotations` rotations keep the + /// failure at `budget`, by bisection on `ln p` between `10⁻⁷` and + /// `5·10⁻³`. `None` if even `10⁻⁷` does not. + pub fn required_error(&self, rotations: f64, budget: f64) -> Option { + let fails = |p: f64| rotations * self.rotation().failure(self.code, self.factory.error, p); + let (mut lo, mut hi) = (ln(1e-7), ln(5e-3)); + if fails(1e-7) > budget { + return None; + } + if fails(5e-3) <= budget { + return Some(5e-3); + } + for _ in 0..60 { + let mid = 0.5 * (lo + hi); + if fails(exp(mid)) <= budget { + lo = mid; + } else { + hi = mid; + } + } + Some(exp(lo)) + } +} + +// --------------------------------------------------------------------------- +// The codes, their logical bases and the native sets. +// --------------------------------------------------------------------------- + +/// A code on the `ℓ × m` torus, with the source's logical basis: `X̄₁ = X(p,q)`, +/// `X̄₇ = X(r,s)`, `Z̄₁ = Z(νsᵀ,νrᵀ)`, `Z̄₇ = Z(μqᵀ,μpᵀ)`, and the others their +/// shifts by `αᵢ` and `βᵢ`. +struct Layout { + l: i32, + m: i32, + p: &'static [(i32, i32)], + q: &'static [(i32, i32)], + r: &'static [(i32, i32)], + s: &'static [(i32, i32)], + mu: (i32, i32), + nu: (i32, i32), + alpha: [(i32, i32); 6], + beta: [(i32, i32); 6], +} + +static GROSS: Layout = Layout { + l: 12, + m: 6, + p: &[(4, 0), (5, 0), (6, 1), (4, 2), (5, 4), (6, 5)], + q: &[(3, 0), (4, 0), (3, 1), (3, 2), (4, 2), (3, 5)], + r: &[(0, 0), (8, 0), (1, 1), (9, 1), (3, 4), (11, 4)], + s: &[(1, 0), (9, 0), (4, 4), (8, 4), (0, 5), (8, 5)], + mu: (1, 1), + nu: (1, 1), + alpha: [(0, 0), (3, 5), (11, 5), (10, 1), (5, 4), (4, 2)], + beta: [(0, 0), (2, 4), (1, 2), (2, 5), (1, 1), (3, 1)], +}; + +static TWO_GROSS: Layout = Layout { + l: 12, + m: 12, + p: &[(2, 0), (2, 2), (8, 2), (9, 2), (3, 3), (4, 3), (7, 4), (8, 6), (6, 7), (7, 11)], + q: &[(3, 2), (5, 3), (7, 3), (11, 3), (8, 4), (8, 5), (6, 7), (4, 8), (1, 9), (1, 10)], + r: &[(4, 2), (11, 2), (0, 5), (1, 5), (5, 5), (6, 5), (1, 8), (8, 8), (2, 11), (10, 11)], + s: &[(2, 6), (2, 9), (2, 10), (11, 6), (11, 9), (11, 10), (8, 7), (11, 7), (5, 8), (11, 8)], + mu: (1, 1), + nu: (1, 1), + alpha: [(0, 0), (0, 3), (3, 7), (11, 11), (2, 9), (7, 4)], + beta: [(0, 0), (1, 1), (4, 0), (5, 4), (4, 3), (3, 5)], +}; + +/// The polynomials of the check operators, as the source writes them: +/// `A = 1 + y + x³y⁻¹`, `B = 1 + x + x⁻¹y⁻³`. +const POLY_A: [(i32, i32); 3] = [(0, 0), (0, 1), (3, -1)]; +const POLY_B: [(i32, i32); 3] = [(0, 0), (1, 0), (-1, -3)]; + +/// An operator on the `L` and `R` qubits, as two membership vectors. +type Operator = (Vec, Vec); + +impl Layout { + fn cell(&self, i: i32, j: i32) -> usize { + (i.rem_euclid(self.l) * self.m + j.rem_euclid(self.m)) as usize + } + + /// `d·terms`, or `d·termsᵀ` when `transpose`. + fn poly(&self, terms: &[(i32, i32)], d: (i32, i32), transpose: bool) -> Vec { + let mut out = vec![false; (self.l * self.m) as usize]; + let sign = if transpose { -1 } else { 1 }; + for &(a, b) in terms { + let k = self.cell(d.0 + sign * a, d.1 + sign * b); + out[k] = !out[k]; + } + out + } + + /// `X̄₁…X̄₁₂` and `Z̄₁…Z̄₁₂`. + fn basis(&self) -> ([Operator; 12], [Operator; 12]) { + let neg = |t: (i32, i32)| (-t.0, -t.1); + let add = |a: (i32, i32), b: (i32, i32)| (a.0 + b.0, a.1 + b.1); + let x = core::array::from_fn(|i| { + if i < 6 { + (self.poly(self.p, self.alpha[i], false), self.poly(self.q, self.alpha[i], false)) + } else { + let b = neg(self.beta[i - 6]); + (self.poly(self.r, b, false), self.poly(self.s, b, false)) + } + }); + let z = core::array::from_fn(|i| { + if i < 6 { + let d = add(self.beta[i], self.nu); + (self.poly(self.s, d, true), self.poly(self.r, d, true)) + } else { + let d = add(neg(self.alpha[i - 6]), self.mu); + (self.poly(self.q, d, true), self.poly(self.p, d, true)) + } + }); + (x, z) + } + + fn shifted(&self, op: &Operator, d: (i32, i32)) -> Operator { + let mv = |v: &Vec| { + let mut out = vec![false; v.len()]; + for i in 0..self.l { + for j in 0..self.m { + if v[(i * self.m + j) as usize] { + out[self.cell(i + d.0, j + d.1)] = true; + } + } + } + out + }; + (mv(&op.0), mv(&op.1)) + } +} + +/// Whether an `X` operator and a `Z` operator anticommute: the parity of their +/// overlap. +fn anticommute(x: &Operator, z: &Operator) -> bool { + let overlap = |a: &Vec, b: &Vec| a.iter().zip(b).filter(|(s, t)| **s && **t).count(); + (overlap(&x.0, &z.0) + overlap(&x.1, &z.1)) % 2 == 1 +} + +/// A logical Pauli on a module's twelve qubits, up to phase: `(x, z)` masks, +/// bit `i` for qubit `i + 1`. Qubit 1 (bit 0) is the pivot. +pub type ModulePauli = (u16, u16); + +/// The 12 shift generators' word lengths over the 36 logical shifts: +/// `A_iA_jᵀ` and `B_iB_jᵀ`. A shift by `x⁶` or `y⁶` acts as the logical +/// identity, so the group is `Z₆ × Z₆`. +pub fn shift_word_lengths() -> [[u8; 6]; 6] { + let mut generators = Vec::new(); + for t in [POLY_A, POLY_B] { + for i in 0..3 { + for j in 0..3 { + if i != j { + generators.push(((t[i].0 - t[j].0).rem_euclid(6) as usize, (t[i].1 - t[j].1).rem_euclid(6) as usize)); + } + } + } + } + let mut lengths = [[u8::MAX; 6]; 6]; + lengths[0][0] = 0; + for level in 0..6u8 { + for x in 0..6 { + for y in 0..6 { + if lengths[x][y] == level { + for g in &generators { + let (a, b) = ((x + g.0) % 6, (y + g.1) % 6); + if lengths[a][b] == u8::MAX { + lengths[a][b] = level + 1; + } + } + } + } + } + } + lengths +} + +/// The logical action of the shift `xᵃyᵇ` on a Pauli. +fn shift_pauli(lay: &Layout, x: &[Operator; 12], z: &[Operator; 12], d: (i32, i32), p: ModulePauli) -> ModulePauli { + let mut out = (0u16, 0u16); + for i in 0..12 { + if p.0 >> i & 1 == 1 { + let s = lay.shifted(&x[i], d); + for (j, zj) in z.iter().enumerate() { + if anticommute(&s, zj) { + out.0 ^= 1 << j; + } + } + } + if p.1 >> i & 1 == 1 { + let s = lay.shifted(&z[i], d); + for (j, xj) in x.iter().enumerate() { + if anticommute(xj, &s) { + out.1 ^= 1 << j; + } + } + } + } + out +} + +/// A 6 × 6 matrix over GF(2), row by row. +pub type Matrix6 = [[u8; 6]; 6]; + +/// The source's printed `A_x` and `A_y` for a code: the action of the shifts +/// by `x` and `y` on `X̄₁…X̄₆`, `|v⟩ → |Av⟩`. +pub fn printed_shift_matrices(code: Code) -> (Matrix6, Matrix6) { + match code { + Code::Gross => ( + [[0, 1, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1], [0, 0, 1, 1, 0, 0], [1, 1, 0, 1, 1, 0], [0, 1, 0, 0, 1, 0], [1, 1, 1, 1, 0, 1]], + [[1, 0, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1], [0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 0], [0, 1, 1, 0, 0, 1], [0, 0, 1, 1, 0, 1]], + ), + Code::TwoGross => ( + [[0, 1, 1, 1, 0, 1], [1, 0, 1, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 0]], + [[1, 1, 1, 1, 1, 0], [1, 1, 0, 1, 1, 1], [0, 1, 1, 0, 0, 0], [1, 0, 0, 0, 1, 0], [1, 0, 0, 1, 1, 1], [1, 0, 0, 0, 0, 1]], + ), + } +} + +fn matrix_mul(a: &Matrix6, b: &Matrix6) -> Matrix6 { + let mut c = [[0u8; 6]; 6]; + for (i, row) in c.iter_mut().enumerate() { + for (j, cell) in row.iter_mut().enumerate() { + *cell = (0..6).fold(0, |s, k| s ^ (a[i][k] & b[k][j])); + } + } + c +} + +fn matrix_pow(a: &Matrix6, e: usize) -> Matrix6 { + let mut r = [[0u8; 6]; 6]; + for (i, row) in r.iter_mut().enumerate() { + row[i] = 1; + } + for _ in 0..e { + r = matrix_mul(&r, a); + } + r +} + +fn matrix_transpose(a: &Matrix6) -> Matrix6 { + core::array::from_fn(|i| core::array::from_fn(|j| a[j][i])) +} + +/// `A·v` on six bits. +fn matrix_apply(a: &Matrix6, v: u16) -> u16 { + (0..6).fold(0, |out, j| out | (u16::from((0..6).fold(0, |s, i| s ^ (a[j][i] & (v >> i & 1) as u8))) << j)) +} + +/// How a shift is taken to act on a module's logical operators. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ShiftAction { + /// As the operators themselves move, computed from the code's basis. + Operators, + /// As the source states it (its §A.2): the printed `A` on both halves for + /// `X̄`, and so `A⁻ᵀ` for `Z̄`. On its printed basis the second half moves by + /// `Aᵀ`, so this reaches other measurements, but every cost is the same. + Stated, + /// As arXiv:2506.03094's authors' released compiler took it in + /// `bicycle_cliffords` 0.1.0 (`src/measurement.rs`): the printed `A` on both + /// halves for `X̄`, and `A⁻¹` for `Z̄`. A CNOT circuit `|x⟩ → |Ax⟩` takes + /// `Z̄` by `A⁻ᵀ`. This reproduces the source's four printed mean costs; it + /// is kept to show that, and nothing else uses it. + SourceTables, +} + +/// The native measurements, each with the fewest shift generators that reach +/// it: every non-trivial product of `X̄₁, Z̄₁, X̄₇, Z̄₇` under every shift. +pub fn native_measurements(code: Code) -> Vec<(ModulePauli, u8)> { + native_measurements_by(code, ShiftAction::Operators) +} + +/// [`native_measurements`], with shifts taken to act as `action` says. +pub fn native_measurements_by(code: Code, action: ShiftAction) -> Vec<(ModulePauli, u8)> { + let lay = code.layout(); + let (x, z) = lay.basis(); + let (ax, ay) = printed_shift_matrices(code); + let lengths = shift_word_lengths(); + let gens: [ModulePauli; 4] = [(1, 0), (0, 1), (1 << 6, 0), (0, 1 << 6)]; + let mut out: Vec<(ModulePauli, u8)> = Vec::new(); + for dx in 0..6usize { + for dy in 0..6usize { + let images = match action { + ShiftAction::Operators => gens.map(|g| shift_pauli(lay, &x, &z, (dx as i32, dy as i32), g)), + ShiftAction::Stated | ShiftAction::SourceTables => { + let a = matrix_mul(&matrix_pow(&ax, dx), &matrix_pow(&ay, dy)); + // Every shift's matrix has order dividing 6: A⁻¹ = A⁵. + let inverse = matrix_pow(&a, 5); + let on_z = if action == ShiftAction::Stated { matrix_transpose(&inverse) } else { inverse }; + gens.map(|g| { + let xs = matrix_apply(&a, g.0 & 0x3f) | matrix_apply(&a, g.0 >> 6) << 6; + let zs = matrix_apply(&on_z, g.1 & 0x3f) | matrix_apply(&on_z, g.1 >> 6) << 6; + (xs, zs) + }) + } + }; + for bits in 1u32..16 { + let mut p = (0u16, 0u16); + for (g, image) in images.iter().enumerate() { + if bits >> g & 1 == 1 { + p.0 ^= image.0; + p.1 ^= image.1; + } + } + out.push((p, lengths[dx][dy])); + } + } + } + out.sort_unstable(); + out.dedup_by_key(|e| e.0); + out +} + +/// A Pauli on the 11 data qubits: `x` bits above `z` bits. The pivot is dropped. +fn data_index(p: ModulePauli) -> u32 { + (u32::from(p.0 >> 1) << 11) | u32::from(p.1 >> 1) +} + +/// The native rotations: each Pauli `R` on the 11 data qubits that some native +/// measurement `Q ⊗ R` holds with `Q` on the pivot not the identity, because the +/// rotation's circuit needs that `Q`. With the fewest shift generators. +pub fn native_rotations(code: Code) -> Vec<(u32, u8)> { + native_rotations_by(code, ShiftAction::Operators) +} + +/// [`native_rotations`], with shifts taken to act as `action` says. +pub fn native_rotations_by(code: Code, action: ShiftAction) -> Vec<(u32, u8)> { + let mut out: Vec<(u32, u8)> = native_measurements_by(code, action) + .into_iter() + .filter(|(p, _)| (p.0 & 1, p.1 & 1) != (0, 0) && data_index(*p) != 0) + .map(|(p, l)| (data_index(p), l)) + .collect(); + out.sort_unstable(); + out.dedup_by_key(|e| e.0); + out +} + +/// The distinct Paulis `R ≠ I` on the data qubits that the native +/// measurements `Q ⊗ R` hold: with `Q ≠ I` on the pivot, with `Q = I`, and +/// with both. +pub fn native_rotation_census(code: Code) -> (u64, u64, u64) { + let natives = native_measurements(code); + let set = |pivot: bool| -> std::collections::BTreeSet { + natives.iter().filter(|(p, _)| ((p.0 & 1, p.1 & 1) != (0, 0)) == pivot).map(|(p, _)| data_index(*p)).filter(|r| *r != 0).collect() + }; + let (with, without) = (set(true), set(false)); + (with.len() as u64, without.len() as u64, with.intersection(&without).count() as u64) +} + +/// In-module cost of every Pauli on the 11 data qubits. +pub struct PauliCosts { + /// Native rotations `k` before the central measurement, by index: `x` bits + /// above `z` bits. `u8::MAX` for the identity. + level: Vec, + /// The fewest shift automorphisms along a path of fewest measurements. + automorphisms: Vec, + /// How many Paulis at each `k`. + pub histogram: Vec, +} + +impl PauliCosts { + /// Breadth-first from the native rotations, conjugating by them. Measuring + /// `Q ⊗ P` for `P` at level `k` takes `6k + 1` in-module measurements: a + /// native rotation is three measurements (the pivot in, the native one, the + /// pivot out), each rotation appears before and after, and one central + /// measurement. A native measurement with `ℓ` shift generators takes `2ℓ` + /// automorphisms, there and back. + pub fn search(code: Code) -> PauliCosts { + PauliCosts::search_by(code, ShiftAction::Operators) + } + + /// [`PauliCosts::search`], with shifts taken to act as `action` says. + pub fn search_by(code: Code, action: ShiftAction) -> PauliCosts { + let rotations = native_rotations_by(code, action); + let table: Vec<(u32, u32, u32)> = rotations.iter().map(|&(r, l)| (r, (r >> 11) | ((r & 0x7ff) << 11), 4 * u32::from(l))).collect(); + let mut level = vec![u8::MAX; 1 << 22]; + let mut automorphisms = vec![u32::MAX; 1 << 22]; + let mut frontier: Vec = Vec::with_capacity(rotations.len()); + for &(r, l) in &rotations { + level[r as usize] = 0; + automorphisms[r as usize] = 2 * u32::from(l); + frontier.push(r); + } + let mut histogram = vec![frontier.len() as u64]; + let mut k = 0u8; + loop { + k += 1; + let mut next = Vec::new(); + for &p in &frontier { + for &(r, swapped, _) in &table { + if (p & swapped).count_ones() & 1 == 1 { + let q = (p ^ r) as usize; + if level[q] == u8::MAX { + level[q] = k; + next.push(q as u32); + } + } + } + } + if next.is_empty() { + break; + } + for &p in &next { + let mut best = u32::MAX; + for &(r, swapped, cost) in &table { + if (p & swapped).count_ones() & 1 == 1 { + let q = (p ^ r) as usize; + if level[q] == k - 1 { + best = best.min(automorphisms[q].saturating_add(cost)); + } + } + } + automorphisms[p as usize] = best; + } + histogram.push(next.len() as u64); + frontier = next; + } + PauliCosts { level, automorphisms, histogram } + } + + /// In-module measurements for `Q ⊗ P`, `Q` chosen as cheapest. `None` for + /// the identity, which takes none. + pub fn measurements(&self, x: u16, z: u16) -> Option { + let k = self.level[((u32::from(x) << 11) | u32::from(z)) as usize]; + (k != u8::MAX).then(|| 1 + 6 * u32::from(k)) + } + + /// Shift automorphisms for that measurement. `None` for the identity. + pub fn automorphisms(&self, x: u16, z: u16) -> Option { + let i = ((u32::from(x) << 11) | u32::from(z)) as usize; + (self.level[i] != u8::MAX).then(|| self.automorphisms[i]) + } + + /// Mean in-module measurements over the Paulis, the identity aside. + pub fn mean_measurements(&self) -> f64 { + histogram_mean(&self.histogram) + } + + /// Automorphisms over all the Paulis, the identity aside. + pub fn total_automorphisms(&self) -> u64 { + self.level.iter().zip(&self.automorphisms).filter(|(l, _)| **l != u8::MAX).map(|(_, a)| u64::from(*a)).sum() + } +} + +/// Mean in-module measurements of a histogram by native rotations `k`. +pub fn histogram_mean(histogram: &[u64]) -> f64 { + let (mut total, mut count) = (0.0, 0.0); + for (k, c) in histogram.iter().enumerate() { + total += (1 + 6 * k) as f64 * *c as f64; + count += *c as f64; + } + total / count +} + +/// The 12-qubit Paulis `Q ⊗ P` by native rotations `k`, the pivot's `Q` fixed +/// and not chosen: for each `Q` of `I, X, Z, Y`, breadth-first from the `R` +/// with `Q ⊗ R` native. The source's other histogram (Fig. 9, left). +pub fn fixed_pivot_histogram(code: Code, action: ShiftAction) -> Vec { + let natives = native_measurements_by(code, action); + let rotations: Vec<(u32, u32)> = native_rotations_by(code, action).iter().map(|&(r, _)| (r, (r >> 11) | ((r & 0x7ff) << 11))).collect(); + let mut histogram: Vec = Vec::new(); + let mut level = vec![u8::MAX; 1 << 22]; + for pivot in [(0u16, 0u16), (1, 0), (0, 1), (1, 1)] { + level.iter_mut().for_each(|l| *l = u8::MAX); + let mut frontier: Vec = natives.iter().filter(|(p, _)| (p.0 & 1, p.1 & 1) == pivot).map(|(p, _)| data_index(*p)).filter(|r| pivot != (0, 0) || *r != 0).collect(); + frontier.sort_unstable(); + frontier.dedup(); + for &r in &frontier { + level[r as usize] = 0; + } + let mut k = 0usize; + while !frontier.is_empty() { + if histogram.len() <= k { + histogram.push(0); + } + histogram[k] += frontier.len() as u64; + k += 1; + let mut next = Vec::new(); + for &p in &frontier { + for &(r, swapped) in &rotations { + if (p & swapped).count_ones() & 1 == 1 { + let q = (p ^ r) as usize; + if level[q] == u8::MAX { + level[q] = k as u8; + next.push(q as u32); + } + } + } + } + frontier = next; + } + } + histogram +} + +// --------------------------------------------------------------------------- +// One program: the 10 × 10 transverse-field Ising model. +// --------------------------------------------------------------------------- + +/// A Pauli-generated rotation on a line of modules: `X` on each `xs`, `Z` on +/// each `zs`, sites numbered through the data qubits in order. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PauliRotation { + pub xs: Vec, + pub zs: Vec, +} + +/// The source's Ising program on a `side × side` grid: `steps` steps of a +/// fourth-order product formula, five second-order formulas per step, each with +/// the two layers of `ZZ` rotations and the layer of `X` rotations. Sites are +/// numbered row by row. These are the rotations of one second-order formula, +/// `(ZZ, ZZ, X)` in the order they are counted. +pub fn ising_layer(side: u64) -> Vec { + let mut out = Vec::new(); + let mut zz = Vec::new(); + for i in 0..side { + for j in 0..side { + let s = side * i + j; + if j + 1 < side { + zz.push(PauliRotation { xs: Vec::new(), zs: vec![s, s + 1] }); + } + if i + 1 < side { + zz.push(PauliRotation { xs: Vec::new(), zs: vec![s, s + side] }); + } + } + } + out.extend(zz.iter().cloned()); + out.extend((0..side * side).map(|s| PauliRotation { xs: vec![s], zs: Vec::new() })); + out.extend(zz); + out +} + +impl Machine { + /// The inter-module measurements of one rotation's GHZ state: from the + /// factory's module to the farthest module with support, one per link. + /// With `factory_last` the factory sits after the last module, as + /// [`Machine::compile_rotation`] takes it; otherwise before the first. + pub fn links(&self, rotation: &PauliRotation, factory_last: bool) -> u64 { + let module = |s: &u64| s / DATA_QUBITS; + let touched = rotation.xs.iter().chain(&rotation.zs).map(module); + if factory_last { + touched.min().map_or(0, |first| self.modules - 1 - first) + } else { + touched.max().unwrap_or(0) + } + } + + /// Inter-module and in-module instructions for one rotation, the factory on + /// the last module, on a machine with [`DATA_QUBITS`] sites to a module. + /// + /// - The GHZ state reaches from the factory's module to the farthest module + /// with support, one inter-module measurement per link. + /// - A module with support takes its in-module measurements. + /// - A module in that range with none takes two pivot measurements. + /// + /// Injections are counted apart: they follow from the rotation's angle. + pub fn compile_rotation(&self, costs: &PauliCosts, rotation: &PauliRotation) -> InstructionCounts { + let mut x = vec![0u16; self.modules as usize]; + let mut z = vec![0u16; self.modules as usize]; + for &s in &rotation.xs { + x[(s / DATA_QUBITS) as usize] ^= 1 << (s % DATA_QUBITS); + } + for &s in &rotation.zs { + z[(s / DATA_QUBITS) as usize] ^= 1 << (s % DATA_QUBITS); + } + let mut c = InstructionCounts::default(); + let Some(first) = (0..self.modules as usize).find(|&m| x[m] != 0 || z[m] != 0) else { return c }; + c.inter_module = (self.modules as usize - 1 - first) as f64; + for m in first..self.modules as usize { + match costs.measurements(x[m], z[m]) { + Some(n) => { + c.in_module += f64::from(n); + c.automorphism += f64::from(costs.automorphisms(x[m], z[m]).unwrap_or(0)); + } + None => c.in_module += 2.0, + } + } + c + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn agree(have: f64, printed: f64) -> bool { + // The fits' parameters are printed to four or five digits, which moves + // a rate by less than this. + (have / printed - 1.0).abs() < 0.01 + } + + #[test] + fn the_basis_is_the_codes_basis() { + for (code, rank) in [(Code::Gross, 66), (Code::TwoGross, 138)] { + let lay = code.layout(); + let (x, z) = lay.basis(); + // Dual: X̄ᵢ anticommutes with Z̄ⱼ exactly when i = j. + for i in 0..12 { + for j in 0..12 { + assert_eq!(anticommute(&x[i], &z[j]), i == j, "{code:?} {i} {j}"); + } + } + // Each commutes with every check: an X check at α acts on αA on L + // and αB on R; a Z check on αBᵀ and αAᵀ. + let mut x_rows: Vec> = Vec::new(); + for i in 0..lay.l { + for j in 0..lay.m { + let xc = (lay.poly(&POLY_A, (i, j), false), lay.poly(&POLY_B, (i, j), false)); + let zc = (lay.poly(&POLY_B, (i, j), true), lay.poly(&POLY_A, (i, j), true)); + for op in &z { + assert!(!anticommute(&xc, op), "{code:?}"); + } + for op in &x { + assert!(!anticommute(op, &zc), "{code:?}"); + } + x_rows.push(xc.0.into_iter().chain(xc.1).collect()); + } + } + // The checks leave k = n − 2·rank = 12. + let mut have = 0; + let cols = x_rows[0].len(); + for c in 0..cols { + let Some(pivot) = (have..x_rows.len()).find(|&r| x_rows[r][c]) else { continue }; + x_rows.swap(have, pivot); + let row = x_rows[have].clone(); + for (r, other) in x_rows.iter_mut().enumerate() { + if r != have && other[c] { + other.iter_mut().zip(&row).for_each(|(a, b)| *a ^= *b); + } + } + have += 1; + } + assert_eq!(have, rank, "{code:?}"); + assert_eq!(code.parameters().0 - 2 * rank as u32, code.parameters().1, "{code:?}"); + } + } + + #[test] + fn a_shift_acts_as_the_printed_matrices() { + // The source's A_x and A_y, row by row: |v⟩ → |Av⟩, so X̄ᵢ goes to + // column i. Compared on the first block; the second follows its form. + let printed: [(Code, [[u8; 6]; 6], [[u8; 6]; 6]); 2] = [ + ( + Code::Gross, + [[0, 1, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1], [0, 0, 1, 1, 0, 0], [1, 1, 0, 1, 1, 0], [0, 1, 0, 0, 1, 0], [1, 1, 1, 1, 0, 1]], + [[1, 0, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1], [0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 0], [0, 1, 1, 0, 0, 1], [0, 0, 1, 1, 0, 1]], + ), + ( + Code::TwoGross, + [[0, 1, 1, 1, 0, 1], [1, 0, 1, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 0]], + [[1, 1, 1, 1, 1, 0], [1, 1, 0, 1, 1, 1], [0, 1, 1, 0, 0, 0], [1, 0, 0, 0, 1, 0], [1, 0, 0, 1, 1, 1], [1, 0, 0, 0, 0, 1]], + ), + ]; + for (code, ax, ay) in printed { + let lay = code.layout(); + let (x, z) = lay.basis(); + for (shift, matrix) in [((1, 0), ax), ((0, 1), ay)] { + for i in 0..6 { + let image = shift_pauli(lay, &x, &z, shift, (1 << i, 0)); + assert_eq!(image.1, 0, "{code:?}"); + for j in 0..6 { + assert_eq!(image.0 >> j & 1, u16::from(matrix[j][i]), "{code:?} {shift:?} {i} {j}"); + } + assert_eq!(image.0 >> 6, 0, "{code:?}: a shift keeps the block"); + } + } + // On the second half, the transposes: not the same matrix, as the + // source states. Its native measurements are the same either way. + for (shift, matrix) in [((1, 0), ax), ((0, 1), ay)] { + let transposed = matrix_transpose(&matrix); + for i in 0..6 { + let image = shift_pauli(lay, &x, &z, shift, (1 << (i + 6), 0)); + assert_eq!(image.0 & 0x3f, 0, "{code:?}: a shift keeps the block"); + for j in 0..6 { + assert_eq!(image.0 >> (j + 6) & 1, u16::from(transposed[j][i]), "{code:?} {shift:?} {i} {j}"); + } + } + assert_ne!(transposed, matrix, "{code:?}"); + } + assert_eq!(printed_shift_matrices(code), (ax, ay), "{code:?}"); + // A shift by x⁶ or y⁶ is the logical identity. + for shift in [(6, 0), (0, 6)] { + for i in 0..12 { + assert_eq!(shift_pauli(lay, &x, &z, shift, (1 << i, 0)), (1 << i, 0), "{code:?}"); + assert_eq!(shift_pauli(lay, &x, &z, shift, (0, 1 << i)), (0, 1 << i), "{code:?}"); + } + } + } + } + + #[test] + fn the_three_actions_reach_different_measurements() { + for code in [Code::Gross, Code::TwoGross] { + let set = |action| native_measurements_by(code, action).into_iter().map(|(p, _)| p).collect::>(); + let (ours, stated, source) = (set(ShiftAction::Operators), set(ShiftAction::Stated), set(ShiftAction::SourceTables)); + assert_eq!((ours.len(), stated.len(), source.len()), (540, 540, 540), "{code:?}"); + assert_ne!(stated, ours, "{code:?}"); + assert_ne!(source, stated, "{code:?}"); + // The first half moves the same way under the stated action: the + // measurements on qubits 1 to 6 alone agree. + let first = |v: &[ModulePauli]| v.iter().filter(|p| (p.0 | p.1) >> 6 == 0).copied().collect::>(); + assert_eq!(first(&stated), first(&ours), "{code:?}"); + } + } + + #[test] + #[cfg_attr(debug_assertions, ignore = "the search takes minutes in a debug build")] + fn the_source_s_mean_costs_come_from_taking_z_by_the_inverse() { + // Fig. 9 and §3.2 of the source: the mean in-module measurements per + // Pauli with the pivot's basis chosen and fixed, and ours from the + // operators. (code, chosen, fixed, ours chosen, ours fixed) + for (code, chosen, fixed, ours_chosen, ours_fixed) in [(Code::Gross, "18.48", "18.55", "18.526", "18.593"), (Code::TwoGross, "18.58", "18.67", "18.482", "18.561")] { + let source = PauliCosts::search_by(code, ShiftAction::SourceTables); + assert_eq!(native_rotations_by(code, ShiftAction::SourceTables).len(), 245, "{code:?}"); + assert_eq!(format!("{:.2}", source.mean_measurements()), chosen, "{code:?}"); + assert_eq!(format!("{:.2}", histogram_mean(&fixed_pivot_histogram(code, ShiftAction::SourceTables))), fixed, "{code:?}"); + assert_eq!(format!("{:.3}", code.mean_measurements()), ours_chosen, "{code:?}"); + // The action the source states, with `Z̄` by `A⁻ᵀ`, gives our costs, + // every one. + assert_eq!(PauliCosts::search_by(code, ShiftAction::Stated).histogram, code.cost_table().0, "{code:?}"); + assert_eq!(fixed_pivot_histogram(code, ShiftAction::Stated), fixed_pivot_histogram(code, ShiftAction::Operators), "{code:?}"); + let ours = fixed_pivot_histogram(code, ShiftAction::Operators); + assert_eq!(ours.iter().sum::(), (1 << 24) - 1, "{code:?}: every 12-qubit Pauli but the identity"); + assert_eq!(ours[0], 540, "{code:?}: the native measurements"); + assert_eq!(format!("{:.3}", histogram_mean(&ours)), ours_fixed, "{code:?}"); + } + } + + #[test] + fn the_native_sets_have_the_published_sizes() { + // Any logical shift is at most two generators. + assert_eq!(shift_word_lengths().iter().flatten().max(), Some(&2)); + for code in [Code::Gross, Code::TwoGross] { + let n = native_measurements(code); + assert_eq!(n.len(), 540, "{code:?}"); + // "every X_i and Z_i ... is a native measurement". + for i in 0..12 { + assert!(n.iter().any(|(p, _)| *p == (1 << i, 0)), "{code:?}"); + assert!(n.iter().any(|(p, _)| *p == (0, 1 << i)), "{code:?}"); + } + // Those with a rotation's use of the pivot: 245. + assert_eq!(native_rotations(code).len(), 245, "{code:?}"); + // Every distinct R ≠ I over all the native measurements: 510, the + // source's count. 284 occur with Q = I, 19 of them also with Q ≠ I. + let mut all: Vec = n.iter().map(|(p, _)| data_index(*p)).filter(|r| *r != 0).collect(); + all.sort_unstable(); + all.dedup(); + assert_eq!(all.len(), 510, "{code:?}"); + assert_eq!(native_rotation_census(code), (245, 284, 19), "{code:?}"); + assert_eq!(245 + 284 - 19, 510); + } + } + + #[test] + fn the_fits_give_the_printed_rates() { + // The source's table of logical error rates and failure probabilities, + // appendix on logical capability: (p, P_I, P_U, P_M, P_C). + for (p, i, u, m, c) in [ + (1e-3, 1.61e-9, 4.01e-7, 1.11e-5, 0.00201), + (1e-4, 1.44e-15, 6.07e-13, 1.01e-9, 4.81e-8), + (5e-5, 4.46e-17, 2.16e-14, 5.74e-11, 2.79e-9), + (3e-5, 3.54e-18, 1.92e-15, 5.9e-12, 3.18e-10), + (1e-5, 1.51e-20, 9.53e-18, 3.29e-14, 2.1e-12), + ] { + let g = |x| Code::Gross.tabulated_rate(x, p); + assert!(agree(g(Instruction::Idle), i), "{p} {}", g(Instruction::Idle)); + assert!(agree(g(Instruction::Automorphism), u), "{p} {}", g(Instruction::Automorphism)); + assert!(agree(g(Instruction::InModule), m), "{p} {}", g(Instruction::InModule)); + assert!(agree(g(Instruction::InterModule), c), "{p} {}", g(Instruction::InterModule)); + } + for (p, i, u) in [(1e-3, 8.2e-21, 3.25e-15), (1e-4, 5.39e-39, 1.34e-37)] { + let g = |x| Code::TwoGross.tabulated_rate(x, p); + assert!(agree(g(Instruction::Idle), i), "{p} {}", g(Instruction::Idle)); + assert!(agree(g(Instruction::Automorphism), u), "{p} {}", g(Instruction::Automorphism)); + } + // Per instruction, as the text says, these are C times smaller. + assert!(agree(Code::Gross.rate(Instruction::Idle, 1e-3).value, 1.61e-10)); + assert!(agree(Code::TwoGross.rate(Instruction::Idle, 1e-3).value, 8.2e-21 / 18.0)); + // The assumed rates stay marked, and go through their two points. + let r = Code::TwoGross.rate(Instruction::InterModule, 1e-3); + assert_eq!(r.source, RateSource::Assumed); + assert!((r.value / 1e-9 - 1.0).abs() < 1e-12); + assert!((Code::TwoGross.rate(Instruction::InterModule, 1e-4).value / 1e-18 - 1.0).abs() < 1e-12); + assert!((Code::TwoGross.rate(Instruction::InModule, 1e-4).value / 1e-20 - 1.0).abs() < 1e-12); + } + + #[test] + fn the_rates_follow_the_table_of_instructions_to_its_exponents() { + // Table of instruction properties: log₁₀ P, with its error bars. + for (code, i, p, exponent, bar) in [ + (Code::Gross, Instruction::Idle, 1e-3, -8.8, 0.2), + (Code::Gross, Instruction::Idle, 1e-4, -14.8, 0.4), + (Code::Gross, Instruction::Automorphism, 1e-3, -6.4, 0.2), + (Code::Gross, Instruction::Automorphism, 1e-4, -12.2, 0.5), + (Code::Gross, Instruction::InModule, 1e-3, -5.0, 0.1), + (Code::Gross, Instruction::InModule, 1e-4, -9.0, 0.2), + (Code::Gross, Instruction::InterModule, 1e-3, -2.7, 0.1), + (Code::Gross, Instruction::InterModule, 1e-4, -7.3, 0.3), + (Code::TwoGross, Instruction::Idle, 1e-3, -20.1, 0.5), + (Code::TwoGross, Instruction::Idle, 1e-4, -38.3, 0.9), + (Code::TwoGross, Instruction::Automorphism, 1e-3, -14.5, 0.4), + (Code::TwoGross, Instruction::Automorphism, 1e-4, -37.0, 1.0), + ] { + let have = code.tabulated_rate(i, p).log10(); + assert!((have - exponent).abs() <= bar, "{code:?} {i:?} {p}: {have} vs {exponent} ± {bar}"); + } + } + + #[test] + fn the_machine_counts_are_the_published_counts() { + let two = |n| Machine::for_logical(Code::TwoGross, TFactory::two_gross_e3(), n); + // 8138, its Ising machine. + assert_eq!(two(100).physical_qubits(), 8138); + assert_eq!(two(100).modules, 10); + // The capability table: (q, code, factory, n). + for (q, code, factory, n) in [ + (5_000, Code::Gross, TFactory::gross_e3(), 121), + (50_000, Code::TwoGross, TFactory::two_gross_e3(), 704), + (5_000, Code::Gross, TFactory::gross_e4(), 110), + (50_000, Code::Gross, TFactory::gross_e4(), 1342), + (50_000, Code::TwoGross, TFactory::two_gross_e4(), 440), + (500_000, Code::TwoGross, TFactory::two_gross_e4(), 6886), + ] { + let m = Machine::largest(code, factory, q).unwrap(); + assert_eq!(m.logical_qubits(), n, "{q} {code:?}"); + assert!(m.physical_qubits() <= q); + let bigger = Machine { modules: m.modules + 1, ..m }; + assert!(bigger.physical_qubits() > q, "{q} {code:?}"); + } + // Its Ising machine on gross codes: the text's 4817 takes a 29-qubit + // adapter; the factory it names has a 13-qubit one. + let gross = Machine::for_logical(Code::Gross, TFactory::gross_e4(), 100); + assert_eq!(gross.physical_qubits(), 4801); + assert_eq!(gross.physical_qubits() + 16, 4817); + // Adapters by the formula. + assert_eq!((Code::Gross.adapter_qubits(), Code::TwoGross.adapter_qubits()), (22, 34)); + assert_eq!(TFactory::gross_e3().adapter_qubits(), 29); + assert_eq!(TFactory::gross_e4().adapter_qubits(), 13); + assert_eq!(TFactory::two_gross_e4().adapter_qubits(), 49); + } + + #[test] + fn cultivation_times_come_from_the_published_discards() { + // At the source's rounded discards, its 351 and 2167. + assert_eq!((39.0_f64 / 0.65 + 90.0 / (1.0 - 0.69)).round(), 350.0_f64); + assert_eq!((1.54_f64 * 39.0 + 3.23 * 90.0).round(), 351.0_f64); + assert_eq!((6.67_f64 * 89.0 + 14.3 * 110.0).round(), 2167.0_f64); + // Exactly, from the discard rates of its own table. + assert!((cultivation_timesteps(39.0, 90.0, 0.35, 0.80) - 352.5).abs() < 1e-9); + assert!((cultivation_timesteps(89.0, 110.0, 0.85, 0.99) - 2243.3).abs() < 0.1); + } + + #[test] + fn capability_reproduces_the_two_gross_rows() { + // (machine, p, published N_T). The budget is 1/3 and the measurement + // rates are the source's assumed ones. + for (factory, q, p, published) in [ + (TFactory::two_gross_e3(), 50_000, 1e-3, 3_471_247.948), + (TFactory::two_gross_e4(), 50_000, 1e-4, 7_015_434_087_082_015.0), + (TFactory::two_gross_e4(), 500_000, 1e-4, 448_297_625_597_020.8), + ] { + let m = Machine::largest(Code::TwoGross, factory, q).unwrap(); + let have = m.capability(p, 1.0 / 3.0); + assert!((have / published - 1.0).abs() < 0.01, "{q} {p}: {have} vs {published}"); + } + // The gross row at p = 10⁻³ within 1%. At 10⁻⁴ the source's constant term + // is twenty times its printed factory error, so ours runs high. + let m = Machine::largest(Code::Gross, TFactory::gross_e3(), 5_000).unwrap(); + let have = m.capability(1e-3, 1.0 / 3.0); + assert!((have / 13.5958 - 1.0).abs() < 0.015, "{have}"); + let m = Machine::largest(Code::Gross, TFactory::gross_e4(), 5_000).unwrap(); + assert!(m.capability(1e-4, 1.0 / 3.0) > 2.0 * 222_089.6); + let m = Machine::largest(Code::Gross, TFactory::gross_e4().with_error(8.3e-7), 5_000).unwrap(); + assert!((m.capability(1e-4, 1.0 / 3.0) / 222_089.6 - 1.0).abs() < 0.05); + } + + #[test] + fn the_required_error_is_the_published_power_law() { + // Published: 0.0165316 at p = 10⁻³, with its counts and rates as printed. + let counts = InstructionCounts { idle: 41_636_714_298.0, automorphism: 2_294_400.0, in_module: 4_238_828.0, inter_module: 946_800.0, t_injection: 15_542_400.0 }; + assert!(agree(counts.failure_as_tabulated(Code::TwoGross, 1e-3), 0.016_531_6)); + assert!(agree(counts.failure_as_tabulated(Code::TwoGross, 1e-4), 1.653_158_8e-11)); + // Solving for 10⁻³ gives the source's 7.3220·10⁻⁴. + let (a, b) = (counts.failure_as_tabulated(Code::TwoGross, 1e-3), 1e-3); + let slope = (ln(a) - ln(counts.failure_as_tabulated(Code::TwoGross, 1e-4))) / core::f64::consts::LN_10; + let p = b * exp((ln(1e-3) - ln(a)) / slope); + assert!((p / 7.3220e-4 - 1.0).abs() < 1e-3, "{p}"); + // The gross rows, from its instruction counts. + let gross = InstructionCounts { idle: 41_634_378_765.0, automorphism: 2_270_400.0, in_module: 4_215_240.0, inter_module: 946_800.0, t_injection: 15_542_400.0 }; + for (p, want) in [(1e-3, 33_258.93), (1e-4, 0.797_450_6), (5e-5, 0.046_248_8), (3e-5, 0.005_268_6), (1e-5, 3.476_67e-5)] { + assert!(agree(gross.failure_as_tabulated(Code::Gross, p), want), "{p}: {}", gross.failure_as_tabulated(Code::Gross, p)); + } + // Bisection on our per-instruction rates finds a sensible bound for a + // machine at the assumed two-gross rates, factory error aside. + let m = Machine::for_logical(Code::TwoGross, TFactory::two_gross_e3().with_error(0.0), 200); + let p = m.required_error(1e8, 1.0 / 3.0).unwrap(); + let at = 1e8 * m.rotation().failure(Code::TwoGross, 0.0, p); + assert!((at / (1.0 / 3.0) - 1.0).abs() < 1e-6, "{at}"); + assert!((7e-4..9e-4).contains(&p), "{p}"); + } + + #[test] + fn the_ising_links_depend_on_the_factory_s_end() { + // The source prints neither the factory's end of the line nor the + // sites' order. Sites in order: after the last module 946,800, its + // count; before the first 781,200. + for code in [Code::Gross, Code::TwoGross] { + let m = Machine::for_logical(code, TFactory::two_gross_e3(), 100); + let layer = ising_layer(10); + let total = |last: bool| 400 * layer.iter().map(|r| m.links(r, last)).sum::(); + assert_eq!((total(true), total(false)), (946_800, 781_200), "{code:?}"); + } + } + + #[test] + fn the_ising_layer_has_the_published_shape() { + let layer = ising_layer(10); + assert_eq!(layer.len(), 460); + // 80 steps of five second-order formulas: 184,000 rotations. + assert_eq!(80 * 5 * layer.len(), 184_000); + } + + /// The whole search takes seconds in a release build and minutes in a debug + /// one. The debug build checks its tables' arithmetic only. + #[test] + fn the_cost_tables_add_up() { + for code in [Code::Gross, Code::TwoGross] { + let (h, _) = code.cost_table(); + assert_eq!(h.iter().sum::(), (1 << 22) - 1, "{code:?}"); + assert!((18.0..19.0).contains(&code.mean_measurements())); + assert!((17.0..20.0).contains(&code.mean_automorphisms())); + } + } + + #[test] + #[cfg_attr(debug_assertions, ignore = "the search takes minutes in a debug build")] + fn the_cost_tables_are_what_the_search_finds() { + for code in [Code::Gross, Code::TwoGross] { + let costs = PauliCosts::search(code); + let (h, autos) = code.cost_table(); + assert_eq!(costs.histogram, h, "{code:?}"); + assert_eq!(costs.total_automorphisms(), autos, "{code:?}"); + assert_eq!(costs.measurements(0, 0), None); + assert!((costs.mean_measurements() - code.mean_measurements()).abs() < 1e-12); + // The Ising program's inter-module measurements, exactly: 946,800, + // with the factory after the last module (the end chosen to match). + let m = Machine::for_logical(code, TFactory::two_gross_e3(), 100); + let inter: f64 = ising_layer(10).iter().map(|r| m.compile_rotation(&costs, r).inter_module).sum(); + assert_eq!(400.0 * inter, 946_800.0, "{code:?}"); + } + } +} diff --git a/wai/wai-quantum/tests/data/published_figures.tsv b/wai/wai-quantum/tests/data/published_figures.tsv new file mode 100644 index 000000000..2bbd77606 --- /dev/null +++ b/wai/wai-quantum/tests/data/published_figures.tsv @@ -0,0 +1,183 @@ +id source printed value rule +table1.gross.c arXiv:2506.03094 Table 1 288 288 exact +table1.gross.u arXiv:2506.03094 Table 1 90 90 exact +table1.gross.a arXiv:2506.03094 Table 1 22 22 exact +table1.two.c arXiv:2506.03094 Table 1 576 576 exact +table1.two.u arXiv:2506.03094 Table 1 158 158 exact +table1.two.a arXiv:2506.03094 Table 1 34 34 exact +table3.gross_e3.adapter arXiv:2506.03094 Table 3 29 29 exact +table3.two_e3.adapter arXiv:2506.03094 Table 3 29 29 exact +table3.gross_e4.adapter arXiv:2506.03094 Table 3 13 13 exact +table3.two_e4.adapter arXiv:2506.03094 Table 3 49 49 exact +fig10a.gross_e3_5k.n arXiv:2506.03094 Fig. 10(a) 121 121 exact +fig10a.two_e3_50k.n arXiv:2506.03094 Fig. 10(a) 704 704 exact +fig10a.gross_e4_5k.n arXiv:2506.03094 Fig. 10(a) 110 110 exact +fig10a.gross_e4_50k.n arXiv:2506.03094 Fig. 10(a) 1,342 1342 exact +fig10a.two_e4_50k.n arXiv:2506.03094 Fig. 10(a) 440 440 exact +fig10a.two_e4_500k.n arXiv:2506.03094 Fig. 10(a) 6,886 6886 exact +fig10a.gross_e3_5k.nt arXiv:2506.03094 Fig. 10(a), source files 13.5958 13.59580266807207 guard 1.365e1 +fig10a.gross_e3_5k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 13.5958 13.59580266807207 guard 1.361e1 +fig10a.gross_e3_5k.steps arXiv:2506.03094 Fig. 10(a), source files 3367.15 3367.153846 guard 3.191e3 +fig10a.two_e3_50k.nt arXiv:2506.03094 Fig. 10(a), source files 3.4712·10⁶ 3471247.948411036 guard 3.478e6 +fig10a.two_e3_50k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 3.4712·10⁶ 3471247.948411036 guard 3.478e6 +fig10a.two_e3_50k.steps arXiv:2506.03094 Fig. 10(a), source files 7057.34 7057.33765 guard 7.061e3 +fig10a.gross_e4_5k.nt arXiv:2506.03094 Fig. 10(a), source files 2.2209·10⁵ 222089.62968351436 guard 4.701e5 +fig10a.gross_e4_5k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 2.2209·10⁵ 222089.62968351436 guard 4.700e5 +fig10a.gross_e4_5k.steps arXiv:2506.03094 Fig. 10(a), source files 3090.55 3090.55115 guard 2.913e3 +fig10a.gross_e4_50k.nt arXiv:2506.03094 Fig. 10(a), source files 3.7033·10⁴ 37033.2338363515 guard 4.064e4 +fig10a.gross_e4_50k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 3.7033·10⁴ 37033.2338363515 guard 4.063e4 +fig10a.gross_e4_50k.steps arXiv:2506.03094 Fig. 10(a), source files 3135.16 3135.156775 guard 2.913e3 +fig10a.two_e4_50k.nt arXiv:2506.03094 Fig. 10(a), source files 7.0154·10¹⁵ 7015434087082015.0 guard 7.033e15 +fig10a.two_e4_50k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 7.0154·10¹⁵ 7015434087082015.0 guard 7.033e15 +fig10a.two_e4_50k.steps arXiv:2506.03094 Fig. 10(a), source files 5435.87 5435.87301 guard 5.301e3 +fig10a.two_e4_500k.nt arXiv:2506.03094 Fig. 10(a), source files 4.4830·10¹⁴ 448297625597020.8 guard 4.494e14 +fig10a.two_e4_500k.nt_undivided arXiv:2506.03094 Fig. 10(a), source files 4.4830·10¹⁴ 448297625597020.8 guard 4.494e14 +fig10a.two_e4_500k.steps arXiv:2506.03094 Fig. 10(a), source files 5436.91 5436.91476 guard 5.301e3 +table10.gross.I.1e-3 arXiv:2506.03094 Table 10 1.6·10⁻⁹ 1.6e-09 band 1.0000000000000002e-10 +table10.gross.U.1e-3 arXiv:2506.03094 Table 10 4.0·10⁻⁷ 4e-07 band 1e-08 +table10.gross.M.1e-3 arXiv:2506.03094 Table 10 1.1·10⁻⁵ 1.1e-05 band 1.0000000000000002e-06 +table10.gross.C.1e-3 arXiv:2506.03094 Table 10 2.0·10⁻³ 0.002 band 0.0001 +table10.gross.I.1e-4 arXiv:2506.03094 Table 10 1.4·10⁻¹⁵ 1.4e-15 band 1.0000000000000001e-16 +table10.gross.U.1e-4 arXiv:2506.03094 Table 10 6.1·10⁻¹³ 6.1e-13 band 1.0000000000000002e-14 +table10.gross.M.1e-4 arXiv:2506.03094 Table 10 1.0·10⁻⁹ 1e-09 band 1.0000000000000002e-10 +table10.gross.C.1e-4 arXiv:2506.03094 Table 10 4.8·10⁻⁸ 4.8e-08 band 1e-09 +table10.gross.I.5e-5 arXiv:2506.03094 Table 10 4.5·10⁻¹⁷ 4.5e-17 band 1e-18 +table10.gross.U.5e-5 arXiv:2506.03094 Table 10 2.2·10⁻¹⁴ 2.2e-14 band 1e-15 +table10.gross.M.5e-5 arXiv:2506.03094 Table 10 5.7·10⁻¹¹ 5.7e-11 band 1e-12 +table10.gross.C.5e-5 arXiv:2506.03094 Table 10 2.8·10⁻⁹ 2.8e-09 band 1.0000000000000002e-10 +table10.gross.I.3e-5 arXiv:2506.03094 Table 10 3.5·10⁻¹⁸ 3.5e-18 band 1.0000000000000001e-19 +table10.gross.U.3e-5 arXiv:2506.03094 Table 10 1.9·10⁻¹⁵ 1.9e-15 band 1.0000000000000001e-16 +table10.gross.M.3e-5 arXiv:2506.03094 Table 10 5.9·10⁻¹² 5.9e-12 band 1e-13 +table10.gross.C.3e-5 arXiv:2506.03094 Table 10 3.2·10⁻¹⁰ 3.2e-10 band 1.0000000000000001e-11 +table10.gross.I.1e-5 arXiv:2506.03094 Table 10 1.5·10⁻²⁰ 1.5e-20 band 1e-21 +table10.gross.U.1e-5 arXiv:2506.03094 Table 10 9.5·10⁻¹⁸ 9.5e-18 band 1.0000000000000001e-19 +table10.gross.M.1e-5 arXiv:2506.03094 Table 10 3.3·10⁻¹⁴ 3.3e-14 band 1e-15 +table10.gross.C.1e-5 arXiv:2506.03094 Table 10 2.1·10⁻¹² 2.1e-12 band 1e-13 +table10.two.I.1e-3 arXiv:2506.03094 Table 10 8.2·10⁻²¹ 8.2e-21 band 9.999999999999999e-23 +table10.two.U.1e-3 arXiv:2506.03094 Table 10 3.3·10⁻¹⁵ 3.3e-15 band 1.0000000000000001e-16 +table10.two.I.1e-4 arXiv:2506.03094 Table 10 5.4·10⁻³⁹ 5.4e-39 band 1e-40 +table10.two.U.1e-4 arXiv:2506.03094 Table 10 1.3·10⁻³⁷ 1.3e-37 band 1.0000000000000001e-38 +a7.gross.idle arXiv:2506.03094 §A.7 against Table 2 10^−8.8 1.584893192461111e-09 guard 1.606e-10 +a7.two.idle arXiv:2506.03094 §A.7 against Table 2 10^−20.1 7.943282347242789e-21 guard 4.517e-22 +a7.gross.shift arXiv:2506.03094 §A.7 against Table 2 10^−6.4 3.981071705534969e-07 guard 4.009e-8 +2308.table1.gross.1e-3 arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 2·10⁻⁷ 2e-07 guard 1.606e-10 +2308.table1.gross.1e-4 arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 8·10⁻¹³ 8e-13 guard 1.441e-16 +2308.table1.two.1e-3 arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 2·10⁻¹² 2e-12 guard 4.517e-22 +2308.table1.two.1e-4 arXiv:2308.07915 Table 1, against arXiv:2506.03094 §A.7 1·10⁻²² 1e-22 guard 2.993e-40 +s3.2.natives arXiv:2506.03094 §3.2 540 540 exact +s3.2.shifts arXiv:2506.03094 §1.2 2 2 exact +s3.2.rotations arXiv:2506.03094 §3.2 510 510 guard 2.450e2 +fig9.chosen.gross arXiv:2506.03094 Fig. 9 caption, right 18.48 18.48 guard 1.853e1 +fig9.chosen.two arXiv:2506.03094 Fig. 9 caption, right 18.58 18.58 guard 1.848e1 +fig9.fixed.gross arXiv:2506.03094 Fig. 9 caption, left 18.55 18.55 guard 1.859e1 +fig9.fixed.two arXiv:2506.03094 Fig. 9 caption, left 18.67 18.67 guard 1.856e1 +fig10a.surface.gross_e3_5k.n arXiv:2506.03094 Fig. 10(a), §A.10 45 45 exact +fig10a.surface.gross_e3_5k.nt arXiv:2506.03094 Fig. 10(a), §A.10 7 7 exact +fig10a.surface.gross_e3_5k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 7 7 guard 1.235e2 +fig10a.surface.gross_e3_5k.steps arXiv:2506.03094 Fig. 10(a) 381 381 exact +fig10a.surface.two_e3_50k.n arXiv:2506.03094 Fig. 10(a), §A.10 42 42 exact +fig10a.surface.two_e3_50k.nt arXiv:2506.03094 Fig. 10(a), §A.10 2.5·10⁶ 2500000.0 nearest 100000.0 +fig10a.surface.two_e3_50k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 2.5·10⁶ 2500000.0 guard 1.323e8 +fig10a.surface.two_e3_50k.steps arXiv:2506.03094 Fig. 10(a) 2,269 2269 exact +fig10a.surface.gross_e4_5k.n arXiv:2506.03094 Fig. 10(a), §A.10 21 21 exact +fig10a.surface.gross_e4_5k.nt arXiv:2506.03094 Fig. 10(a), §A.10 377,928 377928 exact +fig10a.surface.gross_e4_5k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 377,928 377928 guard 2.646e7 +fig10a.surface.gross_e4_5k.steps arXiv:2506.03094 Fig. 10(a) 115 115 exact +fig10a.surface.gross_e4_50k.n arXiv:2506.03094 Fig. 10(a), §A.10 250 250 exact +fig10a.surface.gross_e4_50k.nt arXiv:2506.03094 Fig. 10(a), §A.10 31,746 31746 exact +fig10a.surface.gross_e4_50k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 31,746 31746 guard 2.222e6 +fig10a.surface.gross_e4_50k.steps arXiv:2506.03094 Fig. 10(a) 115 115 exact +fig10a.surface.two_e4_50k.n arXiv:2506.03094 Fig. 10(a), §A.10 27 27 exact +fig10a.surface.two_e4_50k.nt arXiv:2506.03094 Fig. 10(a), §A.10 1.2·10¹⁵ 1200000000000000.0 nearest 1e14 +fig10a.surface.two_e4_50k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 1.2·10¹⁵ 1200000000000000.0 guard 2.058e17 +fig10a.surface.two_e4_50k.steps arXiv:2506.03094 Fig. 10(a) 509 509 exact +fig10a.surface.two_e4_500k.n arXiv:2506.03094 Fig. 10(a), §A.10 416 416 exact +fig10a.surface.two_e4_500k.nt arXiv:2506.03094 Fig. 10(a), §A.10 7.9·10¹³ 79000000000000.0 nearest 1e12 +fig10a.surface.two_e4_500k.nt_ours arXiv:2506.03094 Fig. 10(a), §A.10 7.9·10¹³ 79000000000000.0 guard 1.335e16 +fig10a.surface.two_e4_500k.steps arXiv:2506.03094 Fig. 10(a) 509 509 exact +s4.ising.two.q arXiv:2506.03094 §4 8,138 8138 exact +s4.ising.gross.q arXiv:2506.03094 §4 4,817 4817 guard 4.801e3 +a10.rotations arXiv:2506.03094 §A.10 184,000 184000 exact +table9.gross.nc arXiv:2506.03094 Table 9 9.4680·10⁵ 946800 guard 9.468e5 +table9.two.nc arXiv:2506.03094 Table 9 9.4680·10⁵ 946800 guard 9.468e5 +table9.gross.nm arXiv:2506.03094 Table 9, source files 4,215,240 4215240 guard 4.283e6 +table9.two.nm arXiv:2506.03094 Table 9, source files 4,238,828 4238828 guard 4.149e6 +table9.gross.nu arXiv:2506.03094 Table 9, source files 2,270,400 2270400 guard 2.729e6 +table9.two.nu arXiv:2506.03094 Table 9, source files 2,294,400 2294400 guard 2.368e6 +table9.nt arXiv:2506.03094 Table 9, source files 15,542,400 15542400 guard 1.467e7 +s4.nt arXiv:2506.03094 §4; Fig. 1(b), source files 1.9·10⁷ (Fig. 1(b): 19,036,800) 19036800 guard 1.467e7 +table9.two.ni arXiv:2506.03094 Table 9, source files 41,636,714,298 41636714298 guard 4.210e10 +table10.fail.gross.1e-3 arXiv:2506.03094 Table 10 3.33·10⁴ 33300.0 band 100.0 +table10.fail.gross.1e-4 arXiv:2506.03094 Table 10 7.97·10⁻¹ 0.797 band 0.001 +table10.fail.gross.5e-5 arXiv:2506.03094 Table 10 4.62·10⁻² 0.0462 band 0.0001 +table10.fail.gross.3e-5 arXiv:2506.03094 Table 10 5.27·10⁻³ 0.00527 band 1e-05 +table10.fail.gross.1e-5 arXiv:2506.03094 Table 10 3.48·10⁻⁵ 3.48e-05 band 1e-07 +table10.fail.two.1e-3 arXiv:2506.03094 Table 10 1.65·10⁻² 0.0165 band 0.0001 +table10.fail.two.1e-4 arXiv:2506.03094 Table 10 1.65·10⁻¹¹ 1.65e-11 band 1e-13 +table10.fail.two.factory arXiv:2506.03094 Table 10 1.65·10⁻² 0.0165 guard 3.274e-1 +a10.bound.two arXiv:2506.03094 §A.10 7.32·10⁻⁴ 0.000732 band 1e-06 +a10.bound.gross arXiv:2506.03094 §A.10 2.09·10⁻⁵ 2.09e-05 band 1e-07 +a10.surface.d9.p arXiv:2506.03094 §A.10, Eq. (96) 5.48·10⁻⁵ 5.48e-05 nearest 1e-07 +a10.surface.d9.p_ours arXiv:2506.03094 §A.10, Eq. (96) 5.48·10⁻⁵ 5.48e-05 guard 1.430e-4 +s4.surface.d9.qubits arXiv:2506.03094 §4, Fig. 10(b) 17,010 17010 guard 3.321e4 +a10.surface.d17.p arXiv:2506.03094 §A.10, Eq. (96) 5.89·10⁻⁴ 0.000589 nearest 1e-06 +a10.surface.d17.p_ours arXiv:2506.03094 §A.10, Eq. (96) 5.89·10⁻⁴ 0.000589 guard 9.446e-4 +s4.surface.d17.qubits arXiv:2506.03094 §4, Fig. 10(b) 58,263 58263 guard 1.161e5 +a10.surface.d19.p arXiv:2506.03094 §A.10, Eq. (96) 7.85·10⁻⁴ 0.000785 nearest 1e-06 +a10.surface.d19.p_ours arXiv:2506.03094 §A.10, Eq. (96) 7.85·10⁻⁴ 0.000785 guard 1.196e-3 +s4.surface.d19.qubits arXiv:2506.03094 §4, Fig. 10(b) 72,663 72663 guard 1.449e5 +r21.table3.qubits arXiv:1905.09749 Table 3 20 (megaqubits) 20000000.0 up 1000000.0 +r21.table3.hours arXiv:1905.09749 Table 3 5.1 5.1 up 0.1 +r21.table3.risk arXiv:1905.09749 Table 3 31% 0.31 up 0.01 +r21.table3.mqd arXiv:1905.09749 Table 3 4.1 4.1 up 0.1 +r21.table1.toffolis arXiv:1905.09749 Table 1 2.7 (billions) 2700000000.0 nearest 100000000.0 +r21.s2.10.factories arXiv:1905.09749 §2.10 14 14 exact +r21.title.hours arXiv:1905.09749 title 8 hours 8.0 up 1.0 +r21.s2.14.qubits arXiv:1905.09749 §2.14 "approximately 23 million" 23000000.0 guard 2.162e7 +r21.s2.12.distillation arXiv:1905.09749 §2.12 6.4% 0.064 guard 6.026e-3 +r21.job.distance.assumed arXiv:1905.09749 §2.13 (d = 27); its counts in the estimate job 27 27 exact +r21.job.distance.fitted arXiv:1905.09749 §2.13 (d = 27); its counts in the estimate job 27 27 exact +r21.job.qubits.assumed arXiv:1905.09749 Table 3; its counts in the estimate job 20 (megaqubits) 20000000.0 guard 1.891e7 +r21.job.qubits.fitted arXiv:1905.09749 Table 3; its counts in the estimate job 20 (megaqubits) 20000000.0 guard 1.891e7 +r21.job.qubits.source_rule arXiv:1905.09749 Table 3; its counts in the estimate job 20 (megaqubits) 20000000.0 guard 2.757e7 +r21.job.hours.assumed arXiv:1905.09749 Table 3; its counts in the estimate job 5.1 5.1 guard 6.071e1 +r21.job.hours.source_rule arXiv:1905.09749 Table 3; its counts in the estimate job 5.1 5.1 guard 6.971e1 +r21.job.factories arXiv:1905.09749 §2.10; its counts in the estimate job 14 CCZ per piece, 28 in all text +r21.table3.1024.megaqubits arXiv:1905.09749 Table 3 9.7 9.7 up 0.1 +r21.table3.1024.hours arXiv:1905.09749 Table 3 1.3 1.3 up 0.1 +r21.table3.3072.megaqubits arXiv:1905.09749 Table 3 38 38.0 up 1.0 +r21.table3.3072.hours arXiv:1905.09749 Table 3 12 12.0 up 1.0 +r21.table3.4096.megaqubits arXiv:1905.09749 Table 3 55 55.0 up 1.0 +r21.table3.4096.hours arXiv:1905.09749 Table 3 22 22.0 up 1.0 +r21.table3.8192.megaqubits arXiv:1905.09749 Table 3 140 140.0 up 10.0 +r21.table3.8192.hours arXiv:1905.09749 Table 3 86 86.0 up 1.0 +r21.table3.12288.megaqubits arXiv:1905.09749 Table 3 200 200.0 up 10.0 +r21.table3.12288.hours arXiv:1905.09749 Table 3 200 200.0 up 10.0 +r21.table3.16384.megaqubits arXiv:1905.09749 Table 3 270 270.0 up 10.0 +r21.table3.16384.hours arXiv:1905.09749 Table 3 350 350.0 up 10.0 +femoco.fig10.qubits arXiv:2011.03494 Fig. 10 ≈ 4·10⁶ 4000000.0 nearest 1000000.0 +femoco.s4c4.days arXiv:2011.03494 §IV.C.4 3 3.0 nearest 1.0 +femoco.s4c4.pace arXiv:2011.03494 §IV.C.4 the factories (a "tick" limit) text +femoco.s4c4.rate arXiv:2011.03494 §IV.C.4 25 25.0 down 1.0 +femoco.s4c4.distillation arXiv:2011.03494 §IV.C.4 "roughly 0.1%" 0.001 nearest 0.001 +femoco.s4c5.qubits arXiv:2011.03494 §IV.C.5 "one million" 1000000.0 nearest 1000000.0 +femoco.s4c5.days arXiv:2011.03494 §IV.C.5 1.5 1.5 nearest 0.1 +femoco.s4c5.days3 arXiv:2011.03494 §IV.C.5 3.5 3.5 guard 3.007e0 +femoco.table3.toffolis arXiv:2011.03494 Table III against §IV.C.4 5.3·10⁹ (Table III); 6.7·10⁹ (§IV.C.4) text +femoco.table3.qubits arXiv:2011.03494 Table III against §IV.C.4 2,142 (Table III); under 700 data qubits (§IV.C.4) text +femoco.job.distance arXiv:2011.03494 §IV.C.4; its counts in the estimate job 31 31 guard 2.900e1 +femoco.job.qubits.table arXiv:2011.03494 §IV.C.4; its counts in the estimate job ≈ 4·10⁶ 4000000.0 guard 8.419e6 +femoco.job.qubits.layout arXiv:2011.03494 §IV.C.4; its counts in the estimate job ≈ 4·10⁶ 4000000.0 guard 3.529e6 +femoco.job.days.table arXiv:2011.03494 §IV.C.4; its counts in the estimate job 3 3.0 guard 5.337e0 +femoco.job.days.layout arXiv:2011.03494 §IV.C.4; its counts in the estimate job 3 3.0 guard 6.751e0 +femoco.job.qubits.e4 arXiv:2011.03494 §IV.C.5; its counts in the estimate job "one million" 1000000.0 guard 9.682e5 +femoco.job.days.e4 arXiv:2011.03494 §IV.C.5; its counts in the estimate job 1.5 1.5 guard 3.492e0 +r25.qubits arXiv:2505.15917 §3.2 897,864 897864 guard 9.585e5 +r25.hours arXiv:2505.15917 §3.2 12.07 12.07 guard 1.058e1 +r25.days arXiv:2505.15917 §3.2 4.96 4.96 guard 4.305e0 +r25.logical arXiv:2505.15917 §3.2 1,409 1409 guard 1.432e3 +r25.toffolis arXiv:2505.15917 §3.2 "6.5 billion" 6500000000.0 nearest 100000000.0 +planar.distance arXiv:2211.07629 Appendix F 13 13 exact +planar.factories arXiv:2211.07629 Appendix F 18 18 exact +planar.qubits arXiv:2211.07629 Appendix F 8.72M 8720000.0 nearest 10000.0 +planar.hours arXiv:2211.07629 Appendix F 17 hours 43 mins 17.716666666666665 nearest 0.016666666666666666