diff --git a/samples/qre/0_getting_started.ipynb b/samples/qre/0_getting_started.ipynb new file mode 100644 index 0000000000..268fca6083 --- /dev/null +++ b/samples/qre/0_getting_started.ipynb @@ -0,0 +1,1073 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ab1f991f", + "metadata": {}, + "source": [ + "# Getting started with resource estimation\n", + "\n", + "## High-level overview\n", + "\n", + "The quantum resource estimator takes two inputs: an **application model** that describes the quantum computation (e.g., a Q# program), and an **architecture model** that describes the target quantum hardware (e.g., gate-based superconducting qubits with specific gate times and error rates). From the application, it generates one or more *traces*, compact representations of the instruction sequences applied to qubits. From the architecture, it derives a physical *instruction set* (ISA) specifying each gate's time, qubit cost, and error rate. Between these two layers sit configurable *transforms*: trace transforms such as gate decomposition and layout routines reshape the application trace, while ISA transforms such as quantum error correction codes and magic state factories build higher-fidelity logical instruction sets from the architecture's physical primitives.\n", + "\n", + "\n", + "\n", + "Because there are many valid choices at each layer (rooted in different QEC code distances, different factory protocols, different decomposition parameters), the estimator faces a large combinatorial design space. Rather than evaluating a single fixed configuration, it systematically explores the product of all trace and ISA variants, evaluates physical qubit counts, runtimes, and accumulated error rates for each combination, and then filters the results down to a **Pareto-optimal frontier**: the set of configurations where no other result is simultaneously better in both qubits and runtime while staying within a user-specified error budget.\n", + "\n", + "In practice, a typical estimation call composes these pieces in a single line, like `estimate(app, arch, isa_query=SurfaceCode.q() * RoundBasedFactory.q(), max_error=0.01)`, and returns an `EstimationTable` of Pareto-optimal results that can be inspected, extended with custom columns, or plotted. The remainder of this notebook walks through each step: setting up an application from Q#, choosing an architecture, running the estimation, and interpreting the results." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a73b026c", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file provides CodeMirror syntax highlighting for Q# magic cells\n// in classic Jupyter Notebooks. It does nothing in other (Jupyter Notebook 7,\n// VS Code, Azure Notebooks, etc.) environments.\n\n// Detect the prerequisites and do nothing if they don't exist.\nif (window.require && window.CodeMirror && window.Jupyter) {\n // The simple mode plugin for CodeMirror is not loaded by default, so require it.\n window.require([\"codemirror/addon/mode/simple\"], function defineMode() {\n let rules = [\n {\n token: \"comment\",\n regex: /(\\/\\/).*/,\n beginWord: false,\n },\n {\n token: \"string\",\n regex: String.raw`^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)`,\n beginWord: false,\n },\n {\n token: \"keyword\",\n regex: String.raw`(namespace|open|as|operation|function|body|adjoint|newtype|controlled|internal)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(let|set|use|borrow|mutable)\\b`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(not|and|or)\\b|(w/)`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(=)|(!)|(<)|(>)|(\\+)|(-)|(\\*)|(/)|(\\^)|(%)|(\\|)|(&&&)|(~~~)|(\\.\\.\\.)|(\\.\\.)|(\\?)`,\n beginWord: false,\n },\n {\n token: \"meta\",\n regex: String.raw`(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\\b`,\n beginWord: true,\n },\n {\n token: \"atom\",\n regex: String.raw`(true|false|Pauli(I|X|Y|Z)|One|Zero)\\b`,\n beginWord: true,\n },\n ];\n let simpleRules = [];\n for (let rule of rules) {\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(rule.regex, \"g\"),\n sol: rule.beginWord,\n });\n if (rule.beginWord) {\n // Need an additional rule due to the fact that CodeMirror simple mode doesn't work with ^ token\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(String.raw`\\W` + rule.regex, \"g\"),\n sol: false,\n });\n }\n }\n\n // Register the mode defined above with CodeMirror\n window.CodeMirror.defineSimpleMode(\"qsharp\", { start: simpleRules });\n window.CodeMirror.defineMIME(\"text/x-qsharp\", \"qsharp\");\n\n // Tell Jupyter to associate %%qsharp magic cells with the qsharp mode\n window.Jupyter.CodeCell.options_default.highlight_modes[\"qsharp\"] = {\n reg: [/^%%qsharp/],\n };\n\n // Force re-highlighting of all cells the first time this code runs\n for (const cell of window.Jupyter.notebook.get_cells()) {\n cell.auto_highlight();\n }\n });\n}\n", + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Imports\n", + "from pathlib import Path\n", + "\n", + "import qdk\n", + "from qdk import qsharp\n", + "from qdk.qre import estimate, property_name, plot_estimates\n", + "from qdk.qre.models import GateBased, SurfaceCode, RoundBasedFactory\n", + "from qdk.qre.application import QSharpApplication\n", + "\n", + "from qdk.qre.instruction_ids import T, LATTICE_SURGERY\n", + "from qdk.qre.property_keys import NUM_TS_PER_ROTATION" + ] + }, + { + "cell_type": "markdown", + "id": "5f317aee", + "metadata": {}, + "source": [ + "## Setting up an application\n", + "\n", + "In this example, we use `QSharpApplication` to define the application we want to\n", + "estimate using a Q# entry point. Other application loaders are available to\n", + "[read quantum programs from other langauges and frameworks](1_qre_input.ipynb).\n", + "First, we load an existing Q# program from the samples directory and evaluate\n", + "it. Alternatively, we could load a Q# project or evaluate the Q# program inline\n", + "in a notebook cell. We create the application instance using the `Main()` entry\n", + "point of the Q# program." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5ecb972d", + "metadata": {}, + "outputs": [], + "source": [ + "# Evaluate the Q# application into the scope\n", + "qsharp.eval(Path(\"../algorithms/Ising/Simple1dIsingOrder1.qs\").read_text())\n", + "\n", + "# The entry point in the Q# code above is called Main\n", + "app = QSharpApplication(qdk.code.Main)" + ] + }, + { + "cell_type": "markdown", + "id": "35416b3e", + "metadata": {}, + "source": [ + "We will soon use this application instance with the `estimate` method. This\n", + "will enumerate all possible traces given the application parameters and the\n", + "trace transform. But just to see some information of a trace, we can get the\n", + "first trace that is enumerated from the application and look at its required ISA\n", + "to implement it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "53268325", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
encodingarity
id
RZLOGICAL1
MEAS_ZLOGICAL1
\n", + "
" + ], + "text/plain": [ + " encoding arity\n", + "id \n", + "RZ LOGICAL 1\n", + "MEAS_Z LOGICAL 1" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get one of the generated traces (in fact, this application only generates one trace, but in general there could be multiple)\n", + "trace = next(app.enumerate_traces())\n", + "\n", + "trace.required_isa.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "242a1b9b", + "metadata": {}, + "source": [ + "The output shows the logical instructions required by this trace. In this case, the algorithm needs `RZ` (arbitrary Z-rotation) and `MEAS_Z` (measurement in the Z basis), both operating on single qubits at the logical level. The resource estimator's job is to determine how many physical qubits and how much time are needed to implement these logical operations, given the target architecture and the chosen error correction scheme.\n", + "\n", + "Note that the `QSharpApplication` only computes logical counts required for Pauli-based computation (PSSPC)." + ] + }, + { + "cell_type": "markdown", + "id": "a2383f33", + "metadata": {}, + "source": [ + "## Choosing a target architecture\n", + "\n", + "The architecture model describes the physical quantum hardware. `GateBased` defines a gate-based architecture with configurable gate and measurement times (in nanoseconds) and a physical error rate (which defaults to $10^{-4}$). Calling `provided_isa()` on the architecture returns its physical instruction set: the set of native gates the hardware supports, along with each gate's encoding, arity, qubit cost, time, and error rate." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "490eeeab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
encodingarityspacetimeerror
id
PAULI_IPHYSICAL111000.0001
SQRT_SQRT_XPHYSICAL111000.0001
SQRT_SQRT_Y_DAGPHYSICAL111000.0001
SPHYSICAL111000.0001
SQRT_X_DAGPHYSICAL111000.0001
MEAS_XPHYSICAL115000.0001
RZPHYSICAL111000.0001
T_DAGPHYSICAL111000.0001
PAULI_YPHYSICAL111000.0001
SQRT_SQRT_YPHYSICAL111000.0001
MEAS_ZPHYSICAL115000.0001
CNOTPHYSICAL221000.0001
SQRT_Y_DAGPHYSICAL111000.0001
SQRT_XPHYSICAL111000.0001
HPHYSICAL111000.0001
RYPHYSICAL111000.0001
TPHYSICAL111000.0001
PAULI_XPHYSICAL111000.0001
SQRT_SQRT_X_DAGPHYSICAL111000.0001
MEAS_YPHYSICAL115000.0001
S_DAGPHYSICAL111000.0001
SQRT_YPHYSICAL111000.0001
CZPHYSICAL221000.0001
RXPHYSICAL111000.0001
PAULI_ZPHYSICAL111000.0001
\n", + "
" + ], + "text/plain": [ + " encoding arity space time error\n", + "id \n", + "PAULI_I PHYSICAL 1 1 100 0.0001\n", + "SQRT_SQRT_X PHYSICAL 1 1 100 0.0001\n", + "SQRT_SQRT_Y_DAG PHYSICAL 1 1 100 0.0001\n", + "S PHYSICAL 1 1 100 0.0001\n", + "SQRT_X_DAG PHYSICAL 1 1 100 0.0001\n", + "MEAS_X PHYSICAL 1 1 500 0.0001\n", + "RZ PHYSICAL 1 1 100 0.0001\n", + "T_DAG PHYSICAL 1 1 100 0.0001\n", + "PAULI_Y PHYSICAL 1 1 100 0.0001\n", + "SQRT_SQRT_Y PHYSICAL 1 1 100 0.0001\n", + "MEAS_Z PHYSICAL 1 1 500 0.0001\n", + "CNOT PHYSICAL 2 2 100 0.0001\n", + "SQRT_Y_DAG PHYSICAL 1 1 100 0.0001\n", + "SQRT_X PHYSICAL 1 1 100 0.0001\n", + "H PHYSICAL 1 1 100 0.0001\n", + "RY PHYSICAL 1 1 100 0.0001\n", + "T PHYSICAL 1 1 100 0.0001\n", + "PAULI_X PHYSICAL 1 1 100 0.0001\n", + "SQRT_SQRT_X_DAG PHYSICAL 1 1 100 0.0001\n", + "MEAS_Y PHYSICAL 1 1 500 0.0001\n", + "S_DAG PHYSICAL 1 1 100 0.0001\n", + "SQRT_Y PHYSICAL 1 1 100 0.0001\n", + "CZ PHYSICAL 2 2 100 0.0001\n", + "RX PHYSICAL 1 1 100 0.0001\n", + "PAULI_Z PHYSICAL 1 1 100 0.0001" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Define a gate-based architecture: physical error rate 1e-4, gate time 100 ns, measurement 500 ns\n", + "arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)\n", + "arch.provided_isa(arch.context()).as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "436bd2b5", + "metadata": {}, + "source": [ + "The table above shows the physical gates provided by this architecture. All gates operate at the `PHYSICAL` encoding level. Single-qubit gates (e.g., `H`, `T`) each use 1 physical qubit and take 100 ns, while two-qubit gates (e.g., `CNOT`, `CZ`) use 2 qubits and also take 100 ns. Measurement (`MEAS_Z`) is slower at 500 ns. All gates share the same physical error rate of $10^{-4}$." + ] + }, + { + "cell_type": "markdown", + "id": "476a9aa1", + "metadata": {}, + "source": [ + "## Estimate\n", + "\n", + "The `estimate` function takes the application and architecture as inputs, along with an ISA query that specifies which quantum error correction codes and magic state factories to explore. The `SurfaceCode.q() * RoundBasedFactory.q()` ISA query enumerates the Cartesian product of surface code configurations (varying code distances) and round-based magic state distillation factories (varying distillation protocols). The `max_error` parameter sets the maximum tolerable error probability for the overall computation.\n", + "\n", + "The function returns an `EstimationTable` containing only Pareto-optimal results. These are configurations where no other result is simultaneously better in both physical qubits and runtime while staying within the error budget. The default columns show the total physical qubit count, the total runtime, and the accumulated error for each result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ffa0d10b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerror
0290360 days 00:00:00.0052130.003162
1302680 days 00:00:00.0044110.003162
2329280 days 00:00:00.0036090.003162
3379960 days 00:00:00.0028070.003166
4523320 days 00:00:00.0020050.003499
\n", + "
" + ], + "text/plain": [ + " qubits runtime error\n", + "0 29036 0 days 00:00:00.005213 0.003162\n", + "1 30268 0 days 00:00:00.004411 0.003162\n", + "2 32928 0 days 00:00:00.003609 0.003162\n", + "3 37996 0 days 00:00:00.002807 0.003166\n", + "4 52332 0 days 00:00:00.002005 0.003499" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Run estimation over surface code × round-based factory configurations with 1% error budget\n", + "results = estimate(app, arch, isa_query=SurfaceCode.q() * RoundBasedFactory.q(), max_error=0.01)\n", + "\n", + "# Returns a pandas DataFrame that can be used for further analysis or visualization\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "d41b7893", + "metadata": {}, + "source": [ + "Each row in the table represents a Pareto-optimal configuration. The first row uses the fewest physical qubits but has the longest runtime, while the last row is the fastest but requires the most qubits. All results stay within the specified `max_error=0.01` error budget.\n", + "\n", + "We can inspect the estimation statistics to see how large the design space was and how many configurations were explored to produce these Pareto-optimal results." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5354ea4a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "EstimationTableStats(num_traces=32, num_isas=1248, total_jobs=17424, successful_estimates=353, pareto_results=5)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.stats" + ] + }, + { + "cell_type": "markdown", + "id": "06a3c26b", + "metadata": {}, + "source": [ + "### Adding pre-configured columns to the table\n", + "\n", + "The `EstimationTable` provides convenience methods to add commonly used columns. The method `add_qubit_partition_column()` breaks down the total physical qubit count into three components: compute qubits (for logical operations), factory qubits (for magic state distillation), and memory qubits (for data storage). The method `add_factory_summary_column()` adds a human-readable summary of the magic state factories used in each result (e.g., `20×T` meaning 20 copies of a T-state factory). These numbers can help to understand whether space-time trade-offs are possible. For example, if the amount of factory qubits is relatively high compared to the total number of qubits and many factory copies are used, than it's possible to reduce the total number of qubits by letting the algorithm run longer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "621904f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactories
0290360 days 00:00:00.0052130.003162943619600020×T
1302680 days 00:00:00.0044110.003162674823520024×T
2329280 days 00:00:00.0036090.003162450828420029×T
3379960 days 00:00:00.0028070.003166271635280036×T
4523320 days 00:00:00.0020050.003499137250960052×T
\n", + "
" + ], + "text/plain": [ + " qubits runtime error physical_compute_qubits \\\n", + "0 29036 0 days 00:00:00.005213 0.003162 9436 \n", + "1 30268 0 days 00:00:00.004411 0.003162 6748 \n", + "2 32928 0 days 00:00:00.003609 0.003162 4508 \n", + "3 37996 0 days 00:00:00.002807 0.003166 2716 \n", + "4 52332 0 days 00:00:00.002005 0.003499 1372 \n", + "\n", + " physical_factory_qubits physical_memory_qubits factories \n", + "0 19600 0 20×T \n", + "1 23520 0 24×T \n", + "2 28420 0 29×T \n", + "3 35280 0 36×T \n", + "4 50960 0 52×T " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Break total qubits into compute, factory, and memory components\n", + "results.add_qubit_partition_column()\n", + "# Add a human-readable factory summary (e.g., \"20×T\")\n", + "results.add_factory_summary_column()\n", + "\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "48cac642", + "metadata": {}, + "source": [ + "### Querying properties of selected results\n", + "\n", + "Individual results can be accessed by index (e.g., `results[0]` for the first Pareto-optimal result). Each result exposes:\n", + "- `properties`: a dictionary of named metrics such as qubit counts and algorithm parameters,\n", + "- `source`: a mapping from instruction IDs to their implementation details, including error rates and timing, and\n", + "- `factories`: a mapping from instruction IDs to their magic state factory details, including the number of states produced." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e1df03eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'LOGICAL_MEMORY_QUBITS': 0, 'EVALUATION_TIME': 2032086, 'NUM_TS_PER_ROTATION': 13, 'ALGORITHM_COMPUTE_QUBITS': 9, 'PHYSICAL_COMPUTE_QUBITS': 9436, 'ALGORITHM_MEMORY_QUBITS': 0, 'LOGICAL_COMPUTE_QUBITS': 28, 'PHYSICAL_FACTORY_QUBITS': 19600}\n", + "2.1303500000000005e-07\n", + "1547\n" + ] + } + ], + "source": [ + "# Print all properties for the first result, converting numeric keys to human-readable names\n", + "print(dict((property_name(k), v) for k, v in results[0].properties.items()))\n", + "\n", + "# Get the logical error rate of the T gate instruction in the first result\n", + "print(results[0].source[T].instruction.error_rate())\n", + "\n", + "# Get the total number of T states produced by the magic state factory\n", + "print(results[0].factories[T].states)" + ] + }, + { + "cell_type": "markdown", + "id": "bf9c7111", + "metadata": {}, + "source": [ + "### Adding custom columns to the table\n", + "\n", + "Beyond the pre-configured columns, you can add arbitrary custom columns using `add_column(name, function)` or populate a column from a property key using `add_property_column(key)`. With `add_column`, the function receives an `EstimationTableEntry` and returns the value for that column. With `add_property_column`, the column is automatically populated from the entry's `properties` dictionary using the given key. These methods are useful for surfacing specific metrics, such as per-instruction error rates, factory state counts, or algorithm-level parameters, directly in the results table." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e1fcc7d3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactoriest_error_ratelogical_error_ratet_statesnum_ts_per_rotation
0290360 days 00:00:00.0052130.003162943619600020×T2.130350e-073.000000e-16154713
1302680 days 00:00:00.0044110.003162674823520024×T2.130350e-073.000000e-14154713
2329280 days 00:00:00.0036090.003162450828420029×T2.130350e-073.000000e-12154713
3379960 days 00:00:00.0028070.003166271635280036×T2.130350e-073.000000e-10154713
4523320 days 00:00:00.0020050.003499137250960052×T2.130350e-073.000000e-08154713
\n", + "
" + ], + "text/plain": [ + " qubits runtime error physical_compute_qubits \\\n", + "0 29036 0 days 00:00:00.005213 0.003162 9436 \n", + "1 30268 0 days 00:00:00.004411 0.003162 6748 \n", + "2 32928 0 days 00:00:00.003609 0.003162 4508 \n", + "3 37996 0 days 00:00:00.002807 0.003166 2716 \n", + "4 52332 0 days 00:00:00.002005 0.003499 1372 \n", + "\n", + " physical_factory_qubits physical_memory_qubits factories t_error_rate \\\n", + "0 19600 0 20×T 2.130350e-07 \n", + "1 23520 0 24×T 2.130350e-07 \n", + "2 28420 0 29×T 2.130350e-07 \n", + "3 35280 0 36×T 2.130350e-07 \n", + "4 50960 0 52×T 2.130350e-07 \n", + "\n", + " logical_error_rate t_states num_ts_per_rotation \n", + "0 3.000000e-16 1547 13 \n", + "1 3.000000e-14 1547 13 \n", + "2 3.000000e-12 1547 13 \n", + "3 3.000000e-10 1547 13 \n", + "4 3.000000e-08 1547 13 " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Error rate of the T instruction (fixed arity, so no argument needed)\n", + "results.add_column(\"t_error_rate\", lambda r: r.source[T].instruction.error_rate())\n", + "\n", + "# Error rate of the LATTICE_SURGERY instruction. This instruction has variable\n", + "# arity (it operates on a varying number of qubits), so we must pass an arity\n", + "# value to evaluate the error rate function. Arity 1 is the canonical single-\n", + "# qubit evaluation point used throughout the estimator for comparison.\n", + "results.add_column(\"logical_error_rate\", lambda r: r.source[LATTICE_SURGERY].instruction.error_rate(1))\n", + "\n", + "# Number of T states produced by the magic state factory\n", + "results.add_column(\"t_states\", lambda r: r.factories[T].states)\n", + "\n", + "# Add a column directly from a property key\n", + "results.add_property_column(NUM_TS_PER_ROTATION)\n", + "\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "6a7569b8", + "metadata": {}, + "source": [ + "### Plotting the results\n", + "\n", + "The `plot` method produces a log-log scatter plot of the Pareto frontier, with runtime on the x-axis and physical qubits on the y-axis. The `runtime_unit` parameter controls the x-axis scale (e.g., `\"ms\"`, `\"s\"`, `\"hours\"`), and `figsize` sets the figure dimensions." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "dd244938", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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iTDjFunp6Ro0aJDat2+va665RsnJydq/f79Lx9i4caOSkpIUFhYmi8Wi999/v8F+GRkZ6tmzp/z9/TVkyBBt27bNtq2goEDdu3e3ve7evbv+8Y9/uLROmNvChQsVExOjoKAgBQUFadiwYfrkk09cOgbf5dZv3rx5slgseuKJJ1x6XD57uAphxg02bNig1NRUbd26VWvWrFFNTY1++ctfqqKiosH+mzdvVk1NjUP73r17VVRU1OA+FRUVslqtysjIaLSOrKwspaWlac6cOcrNzZXVatWoUaNUXFzcsjeGq06PHj00b9485eTkaMeOHUpISNBdd92lr7/+usH+fJevPNu3b9eiRYsUExPTZD8+e3iUAbcrLi42JBkbNmxw2FZXV2dYrVbjN7/5jVFbW2tr37dvnxEaGmo8//zzFz2+JGPFihUO7YMHDzZSU1PtxgoLCzPS09MNwzCMzZs3G8nJybbtU6dONd5++21n3hquQh07djTeeOMNh3a+y1eeM2fOGH369DHWrFljxMfHG1OnTm2wH589PI0zM5dBaWmpJCkkJMRhm5eXl1auXKmdO3dqwoQJqq+v18GDB5WQkKDk5GTNmDGjRWOeO3dOOTk5SkxMtBsrMTFRW7ZskSQNHjxYX331lf7xj3+ovLxcn3zyiUaNGtWi8XDlq6urU2ZmpioqKjRs2DCH7XyXrzypqam6/fbb7f7dN4TPHp7m4+kCrnT19fV64oknNHz4cN10000N9gkLC9PatWsVFxen8ePHa8uWLUpMTNTChQtbPO6JEydUV1en0NBQu/bQ0FDt27dPkuTj46M//elPGjFihOrr6zVjxgxWAMDBnj17NGzYMFVXVyswMFArVqxQdHR0g335Ll85MjMzlZubq+3btzerP589PIkw42apqan66quvtGnTpib7RUREaNmyZYqPj1evXr20ePFiWSwWt9d355136s4773T7ODCv66+/Xnl5eSotLdXy5cuVkpKiDRs2NBpo+C6b35EjRzR16lStWbNG/v7+zd6Pzx6ewmUmN5oyZYo+/vhjrVu3Tj169Giyb1FRkSZPnqykpCRVVlZq2rRplzR2586d5e3t7TDxrqioSF27dr2kY+Pq4uvrq969e2vAgAFKT0+X1WrVyy+/3Gh/vsvml5OTo+LiYvXv318+Pj7y8fHRhg0b9Morr8jHx0d1dXUN7sdnD08hzLiBYRiaMmWKVqxYobVr1+raa69tsv+JEyc0cuRI9e3bV++9956ys7OVlZWl6dOnt7gGX19fDRgwQNnZ2ba2+vp6ZWdnNzjfAWiu+vp6nT17tsFtfJevDCNHjtSePXuUl5dn+xs4cKDuv/9+5eXlydvb22EfPnt4lKdnIF+JHnvsMSM4ONhYv369UVhYaPurrKx06FtXV2cMHDjQuO2224yzZ8/a2vPy8oyQkBDjpZdeanCMM2fOGDt37jR27txpSDJeeuklY+fOncYPP/xg65OZmWn4+fkZS5cuNfbu3WtMnjzZ6NChg3Hs2DHXv2lckWbOnGls2LDByM/PN3bv3m3MnDnTsFgsxurVqx368l2+sl1sNROfPTyJMOMGkhr8e/PNNxvsv3r1aqOqqsqhPTc31zhy5EiD+6xbt67BMVJSUuz6vfrqq0ZERITh6+trDB482Ni6deulvj1cRR588EEjMjLS8PX1Nbp06WKMHDmywSBzHt/lK1dTYcYw+OzhWTybCQAAmBpzZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgAAgKkRZgBcEW655RY98cQTHhv/F7/4hf7617+67firVq1SbGys6uvr3TYGYFaEGQAuMXHiRFksFlksFrVp00bXXnutZsyYoerqapeOs379elksFp0+fdqu/b333tMf/vAHl47VXB9++KGKioo0btw4t40xevRotWnTRm+//bbbxgDMijADwGVGjx6twsJCff/995o/f74WLVqkOXPmXJaxQ0JC1L59+8sy1s+98sormjRpkry83Puf1IkTJ+qVV15x6xiAGRFmALiMn5+funbtqvDwcCUnJysxMVFr1qyxbe/Zs6cWLFhgt09sbKyefvpp22uLxaI33nhDv/rVr9SuXTv16dNHH374oSTp0KFDGjFihCSpY8eOslgsmjhxoiTHy0w9e/bUs88+qwkTJigwMFCRkZH68MMPdfz4cd11110KDAxUTEyMduzYYVfPpk2bFBcXp7Zt2yo8PFyPP/64KioqGn3Px48f19q1a5WUlGTXbrFYtGjRIt1xxx1q166d+vbtqy1btui7777TLbfcooCAAP3rv/6rDh48aNtn165dGjFihNq3b6+goCANGDDArr6kpCTt2LHDbh8AhBkAbvLVV1/piy++kK+vr9P7PvPMM7r33nu1e/du3Xbbbbr//vtVUlKi8PBwvfvuu5Kk/fv3q7CwUC+//HKjx5k/f76GDx+unTt36vbbb9dvf/tbTZgwQQ888IByc3N13XXXacKECTIMQ5J08OBBjR49Wnfffbd2796trKwsbdq0SVOmTGl0jE2bNtnCys/94Q9/0IQJE5SXl6cbbrhB48eP16OPPqpZs2Zpx44dMgzD7tj333+/evTooe3btysnJ0czZ85UmzZtbNsjIiIUGhqqzz//3Ol/p8AVzQAAF0hJSTG8vb2NgIAAw8/Pz5BkeHl5GcuXL7f1iYyMNObPn2+3n9VqNebMmWN7Lcl48sknba/Ly8sNScYnn3xiGIZhrFu3zpBknDp1yu448fHxxtSpU+3GeuCBB2yvCwsLDUnGU089ZWvbsmWLIckoLCw0DMMwHnroIWPy5Ml2x/38888NLy8vo6qqqsH3PX/+fKNXr14O7T9/H+fHWrx4sa3tb3/7m+Hv72973b59e2Pp0qUNjnNev379jKeffrrJPsDVhjMzAFxmxIgRysvL05dffqmUlBRNmjRJd999t9PHiYmJsf1zQECAgoKCVFxcfEnHCQ0NlST9y7/8i0Pb+WPv2rVLS5cuVWBgoO1v1KhRqq+vV35+foNjVFVVyd/fv8XjV1dXq6ysTJKUlpamhx9+WImJiZo3b16Dl5Patm2rysrKi7954CpCmAHgMgEBAerdu7esVquWLFmiL7/8UosXL7Zt9/Lysl3SOa+mpsbhOBdeWpF+nH/SkiXJFx7HYrE02nb+2OXl5Xr00UeVl5dn+9u1a5cOHDig6667rsExOnfurFOnTrlk/Kefflpff/21br/9dq1du1bR0dFasWKF3TFLSkrUpUuXZrx74OpBmAHgFl5eXvr973+vJ598UlVVVZKkLl26qLCw0NanrKys0TMejTk/B6eurs51xf5T//79tXfvXvXu3dvhr7G5P/369dOxY8caDTTOioqK0rRp07R69Wr9+te/1ptvvmnbVl1drYMHD6pfv34uGQu4UhBmALjNPffcI29vb2VkZEiSEhIStGzZMn3++efas2ePUlJS5O3t7dQxIyMjZbFY9PHHH+v48eMqLy93Wb2/+93v9MUXX2jKlCnKy8vTgQMH9MEHHzQ5Abhfv37q3LmzNm/efEljV1VVacqUKVq/fr1++OEHbd68Wdu3b7ebWLx161b5+flp2LBhlzQWcKUhzABwGx8fH02ZMkV//OMfVVFRoVmzZik+Pl533HGHbr/9diUnJzd6+aYx3bt31zPPPKOZM2cqNDS0yaDhrJiYGG3YsEHffvut4uLi1K9fP82ePVthYWGN7uPt7a1JkyZd8s3svL29dfLkSU2YMEFRUVG69957NWbMGD3zzDO2Pn/72990//33q127dpc0FnClsRg/v4ANAHDKsWPHdOONNyo3N1eRkZFuGePEiRO6/vrrtWPHDl177bVuGQMwK87MAMAl6tq1qxYvXqzDhw+7bYxDhw7pz3/+M0EGaABnZgAAgKlxZgYAAJgaYQYAAJgaYQYAAJgaYQYAAJgaYQYAAJgaYQYAAJgaYQYAAJgaYQYAAJgaYQYAAJja/wMHczG9fghGPgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Log-log Pareto frontier: runtime (x) vs physical qubits (y)\n", + "plot_estimates(results, figsize=(6, 4), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "ebb0739c", + "metadata": {}, + "source": [ + "The plot visualizes the Pareto frontier: each point represents a configuration that is optimal in the sense that no other configuration achieves both fewer qubits *and* a shorter runtime. Moving left along the x-axis trades qubits for speed, while moving down trades speed for fewer qubits." + ] + }, + { + "cell_type": "markdown", + "id": "ab667cbc", + "metadata": {}, + "source": [ + "## Compare multiple estimations\n", + "\n", + "A common workflow is to compare resource estimates across different hardware assumptions. Here we sweep over three physical error rates and run a separate estimation for each. The `name` parameter labels each run so the results can be distinguished in the plot. Passing the list of `EstimationTable` objects to `plot_estimates` overlays them on a single figure with a legend." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c2e31f33", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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tbW1euutt3TkyBFJUmpqqn7yk584LYftKBjZAACgdVr6Hep2Ovjkk0/04x//WBaLRf369ZMkPf300+rWrZveeecdXX311a2vGgAA+B23O4jed999GjBggE6dOqUDBw7owIEDOnnypAYNGqTp06cbUSMAAOjA3B7ZKCgo0L59+9SlSxfHvi5dumjJkiXNtgkHAACBye2Rjb59+zbq6ClJJSUl6tOnj0eKAgAA/qNFYaO8vNzxs3TpUs2ePVubNm3SqVOndOrUKW3atEkPP/ywnn76aaPrBQAAHUyLVqMEBQU5NfKyf8S+r+F2bW2tEXUahtUoAAC0jkdXo/z973/3WGEAACCwtChsjBo1yug6AACAn3J7Ncr27dubPf7DH/6w1cW0p4YdRAEAgHHc7iAaFNR4TmnD+Rwd7cubORsAALROS79D3V76evbsWaefkpISbd68WcOGDdOWLVvaVDQAAPA/bj9GMZvNjfb96Ec/UlhYmHJycrR//36PFAYAAPyD2yMbrsTHx+vTTz/11OUAAICfcHtk4+OPP3battlsKioq0q9+9SulpaV5qi4AAOAn3A4baWlpMplMunRe6ciRI7VmzRqPFQYAAPyD22GjsLDQaTsoKEjdunVTRESEx4oCAAD+w+2wkZycbEQdAADAT7kdNp5//vkWnzt79mx3Lw8AAPyM2029evfurTNnzujcuXPq3LmzJOmbb75RZGSkunXr9u2FTSYdO3bMo8V6UsMOop999hlNvQAAcJNhTb2WLFmitLQ0HTlyRKWlpSotLdWRI0d0zTXX6Mknn1RhYaEKCwt9OmhI0syZM3X48GF99NFH3i4FAAC/5vbIxhVXXKFNmzZpyJAhTvv379+vn/70p40mkPo62pUDANA6ho1sFBUV6eLFi43219bWqri42N3LAQAAP+d22Ljxxhs1Y8YMHThwwLFv//79evDBB5WRkeHR4gAAQMfndthYs2aNEhISNHToUIWHhys8PFzDhw9XfHy8Vq9ebUSNAACgA3N76Wu3bt307rvv6vPPP9eRI0ckSf3791ffvn09XhwAAOj43A4bdldeeaWuvPJKT9YCAAD8kMfe+goAANAUwgYAADAUYQMAABiKsAEAAAzVogmiH3/8cYsvOGjQoFYXAwAA/E+LwkZaWppMJpNcdTa3HzOZTKqtrfVogUZp+CI2AABgnBa9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//79NXXqVM2YMUPz5s3Tvn37ZLPZnK595513KjExUR999JH279+vuXPnKjQ01HG8V69eio+PV35+vtt/poBfswEICFlZWbbg4GBbVFSULTw83CbJFhQUZNu0aZPjnOTkZFtubq7T5wYPHmxbtGiRY1uSbf78+Y7tyspKmyTbX//6V5vNZrP9/e9/t0mynT171uk6o0aNsj300ENO97rrrrsc20VFRTZJtgULFjj27dq1yybJVlRUZLPZbLZ7773XNn36dKfr5ufn24KCgmxVVVVN/t65ubm2733ve432X/p72O/1yiuvOPb97ne/s0VERDi2Y2JibOvWrWvyPnZDhgyxPfbYY82eAwQaRjaAADJmzBgVFBRoz549ysrKUnZ2tiZNmuT2dQYNGuT4z1FRUYqNjVVJSUmbrhMfHy9JGjhwYKN99msfPHhQ69atU3R0tONn7NixqqurU2FhYZP3qKqqUkRERKvvf/78eZWXl0uScnJydN999ykjI0O/+tWvmnxc0qlTJ507d+67f3kggBA2gAASFRWlPn36aPDgwVqzZo327NmjV155xXE8KCjI8cjC7sKFC42u0/DRgVQ//6E1Sz4bXsdkMrncZ792ZWWlZsyYoYKCAsfPwYMH9fnnn+uKK65o8h5du3bV2bNnPXL/xx57TJ988okmTJigDz74QKmpqXrrrbecrllaWqpu3bq14LcHAgdhAwhQQUFB+uUvf6n58+erqqpKktStWzcVFRU5zikvL3c5YuCKfQ5IbW2t54r9f9dcc40OHz6sPn36NPpxNfdkyJAhslgsLgOHu/r27atHHnlEW7Zs0e233661a9c6jp0/f15Hjx7VkCFDPHIvwF8QNoAAdscddyg4OFh5eXmSpBtuuEGvvfaa8vPzdejQIWVlZSk4ONitayYnJ8tkMukvf/mLzpw5o8rKSo/V+1//9V/auXOnZs2apYKCAn3++ef605/+1OwE0SFDhqhr16768MMP23TvqqoqzZo1S9u2bdOXX36pDz/8UB999JHTxNPdu3crPDxc6enpbboX4G8IG0AACwkJ0axZs/TMM8/IarVq3rx5GjVqlG655RZNmDBBt956q8vHE6707NlTixcv1ty5cxUfH99sEHDXoEGD9L//+7/67LPPdP3112vIkCFauHChevTo4fIzwcHBys7ObnOzreDgYP373/9WZmam+vbtq5/97GcaP368Fi9e7Djnd7/7ne68805FRka26V6AvzHZLn1ACwB+xmKxaMCAATpw4ICSk5MNucfXX3+tfv36ad++ferdu7ch9wA6KkY2APi9hIQEvfLKKzpx4oRh9zh+/LheeuklggbQBEY2AACAoRjZAAAAhiJsAAAAQxE2AACAoQgbAADAUIQNAABgKMIGAAAwFGEDAAAYirABAAAMRdgAAACG+j9hBkvUTmIu9gAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Compare estimates across different physical error rates\n", + "results = []\n", + "\n", + "for error_rate in [1e-3, 1e-4, 1e-5]:\n", + " arch = GateBased(error_rate=error_rate, gate_time=100, measurement_time=500)\n", + " results.append(estimate(app, arch, isa_query=SurfaceCode.q() * RoundBasedFactory.q(), max_error=0.01, name=f\"p = {error_rate:.0e}\"))\n", + "\n", + "# Overlay multiple estimation runs on a single plot\n", + "plot_estimates(results, figsize=(6, 4), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "0bb282a2", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "This notebook covered the basic workflow for quantum resource estimation. To explore further:\n", + "\n", + "- Try different Q# applications from the `samples/` directory\n", + "- Adjust the architecture parameters (e.g., different `error_rate` or `gate_time` values) to see how hardware assumptions affect the estimates\n", + "- Explore different ISA queries, such as varying the surface code distance range with `SurfaceCode.q(distance=range(3, 30, 2))`\n", + "- Compare results across multiple estimation runs using `plot_estimates`\n", + "\n", + "To learn how to estimate quantum programs written in various languages and frameworks, check out the [Importing quantum programs into QRE](1_qre_input.ipynb) notebook." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/qre/1_qre_input.ipynb b/samples/qre/1_qre_input.ipynb new file mode 100644 index 0000000000..9183ce251e --- /dev/null +++ b/samples/qre/1_qre_input.ipynb @@ -0,0 +1,652 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1db6c3f9", + "metadata": {}, + "source": [ + "# Importing quantum programs into QRE\n", + "\n", + "The Quantum Resource Estimator (QRE) can accept applications written in several quantum programming languages and frameworks. This notebook demonstrates how to import programs from **Q#**, **Cirq**, **QIR** (Quantum Intermediate Representation), and **OpenQASM**, and then run resource estimation on each of them.\n", + "\n", + "After running estimation on all four input formats, we go a step further and build a **custom application model**. This shows how to define your own `Application` subclass with configurable *trace parameters* (algorithmic hyperparameters that QRE varies automatically during estimation) and how to enrich results with custom columns derived from trace properties." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "138685c1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file provides CodeMirror syntax highlighting for Q# magic cells\n// in classic Jupyter Notebooks. It does nothing in other (Jupyter Notebook 7,\n// VS Code, Azure Notebooks, etc.) environments.\n\n// Detect the prerequisites and do nothing if they don't exist.\nif (window.require && window.CodeMirror && window.Jupyter) {\n // The simple mode plugin for CodeMirror is not loaded by default, so require it.\n window.require([\"codemirror/addon/mode/simple\"], function defineMode() {\n let rules = [\n {\n token: \"comment\",\n regex: /(\\/\\/).*/,\n beginWord: false,\n },\n {\n token: \"string\",\n regex: String.raw`^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)`,\n beginWord: false,\n },\n {\n token: \"keyword\",\n regex: String.raw`(namespace|open|as|operation|function|body|adjoint|newtype|controlled|internal)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(let|set|use|borrow|mutable)\\b`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(not|and|or)\\b|(w/)`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(=)|(!)|(<)|(>)|(\\+)|(-)|(\\*)|(/)|(\\^)|(%)|(\\|)|(&&&)|(~~~)|(\\.\\.\\.)|(\\.\\.)|(\\?)`,\n beginWord: false,\n },\n {\n token: \"meta\",\n regex: String.raw`(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\\b`,\n beginWord: true,\n },\n {\n token: \"atom\",\n regex: String.raw`(true|false|Pauli(I|X|Y|Z)|One|Zero)\\b`,\n beginWord: true,\n },\n ];\n let simpleRules = [];\n for (let rule of rules) {\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(rule.regex, \"g\"),\n sol: rule.beginWord,\n });\n if (rule.beginWord) {\n // Need an additional rule due to the fact that CodeMirror simple mode doesn't work with ^ token\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(String.raw`\\W` + rule.regex, \"g\"),\n sol: false,\n });\n }\n }\n\n // Register the mode defined above with CodeMirror\n window.CodeMirror.defineSimpleMode(\"qsharp\", { start: simpleRules });\n window.CodeMirror.defineMIME(\"text/x-qsharp\", \"qsharp\");\n\n // Tell Jupyter to associate %%qsharp magic cells with the qsharp mode\n window.Jupyter.CodeCell.options_default.highlight_modes[\"qsharp\"] = {\n reg: [/^%%qsharp/],\n };\n\n // Force re-highlighting of all cells the first time this code runs\n for (const cell of window.Jupyter.notebook.get_cells()) {\n cell.auto_highlight();\n }\n });\n}\n", + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dataclasses import dataclass, field\n", + "from pathlib import Path\n", + "from typing import Any\n", + "\n", + "import cirq\n", + "import qdk\n", + "\n", + "from qiskit.circuit.library import RGQFTMultiplier\n", + "from qiskit.qasm3.exporter import Exporter\n", + "\n", + "from qdk import qsharp\n", + "from qdk.qre import estimate, plot_estimates, Application\n", + "from qdk.qre.interop import trace_from_entry_expr\n", + "from qdk.qre.models import GateBased, RoundBasedFactory, SurfaceCode, TwoDimensionalYokedSurfaceCode\n", + "from qdk.qre.application import QSharpApplication, QIRApplication, CirqApplication, OpenQASMApplication\n", + "from qdk.qre.property_keys import custom_properties" + ] + }, + { + "cell_type": "markdown", + "id": "54a49066", + "metadata": {}, + "source": [ + "## From Q#\n", + "\n", + "Here we use the `%%qsharp` cell magic to define an inline Q# operation that performs ripple-carry addition on two registers of qubits. We then wrap the compiled entry point in a `QSharpApplication`, passing the register size as a runtime argument." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "082a75fc", + "metadata": { + "vscode": { + "languageId": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "import Std.Arithmetic.*;\n", + "\n", + "// Ripple-carry addition on two n-qubit registers\n", + "operation Program(n : Int) : Unit {\n", + " use a = Qubit[n];\n", + " use b = Qubit[n];\n", + "\n", + " RippleCarryCGIncByLE(a, b);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f9a84bf7", + "metadata": {}, + "outputs": [], + "source": [ + "# Wrap the compiled Q# entry point with a register size of 10 qubits\n", + "qsharp_app = QSharpApplication(qdk.code.Program, args=(10,)) # type: ignore" + ] + }, + { + "cell_type": "markdown", + "id": "0d5625e1", + "metadata": {}, + "source": [ + "## From Cirq\n", + "\n", + "`CirqApplication` accepts any `cirq.Circuit` directly. Here we build a 10-qubit Quantum Fourier Transform (QFT) circuit using Cirq's built-in `qft` helper and wrap it for resource estimation." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b81536f3", + "metadata": {}, + "outputs": [], + "source": [ + "# Build a 10-qubit QFT circuit and wrap it for estimation\n", + "circuit = cirq.Circuit(cirq.qft(*cirq.LineQubit.range(10)))\n", + "cirq_app = CirqApplication(circuit)" + ] + }, + { + "cell_type": "markdown", + "id": "f2f4db19", + "metadata": {}, + "source": [ + "## From QIR\n", + "\n", + "Quantum Intermediate Representation (QIR) is an LLVM-based IR for quantum programs. `QIRApplication` accepts QIR as an LLVM IR string (or bitcode). Here we load a pre-compiled QIR file for a Hidden Shift algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "24dad0bb", + "metadata": {}, + "outputs": [], + "source": [ + "# Load a pre-compiled QIR file for the Hidden Shift algorithm\n", + "qir_file = (\n", + " Path.cwd().parent.parent\n", + " / \"source\" / \"pip\" / \"tests-integration\" / \"resources\"\n", + " / \"adaptive_ri\" / \"output\" / \"HiddenShiftNISQ.ll\"\n", + ")\n", + "qir_app = QIRApplication(qir_file.read_text())" + ] + }, + { + "cell_type": "markdown", + "id": "95eccf1d", + "metadata": {}, + "source": [ + "## From OpenQASM (via Qiskit)\n", + "\n", + "`OpenQASMApplication` accepts an OpenQASM 3 source string directly. Here we use Qiskit to generate an OpenQASM 3 program for a 4-qubit RGQFT multiplier and wrap it for resource estimation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4f628862", + "metadata": {}, + "outputs": [], + "source": [ + "# Export a Qiskit circuit to OpenQASM 3\n", + "source = Exporter().dumps(RGQFTMultiplier(num_state_qubits=4))\n", + "qasm_app = OpenQASMApplication(source)" + ] + }, + { + "cell_type": "markdown", + "id": "d684e434", + "metadata": {}, + "source": [ + "## Running resource estimation\n", + "\n", + "With all four applications defined, we run resource estimation on each one to illustrate that any of the supported input formats can be used interchangeably. We use a `GateBased` architecture with 100 ns gate time and 500 ns measurement time, and query over `SurfaceCode` distances crossed with `RoundBasedFactory` protocols. The `max_error` parameter caps the total error probability at 1%.\n", + "\n", + "Each call to `estimate` returns an `EstimationTable` of Pareto-optimal results: configurations where no other result achieves both fewer physical qubits *and* a shorter runtime within the error budget. Note that the four applications implement entirely different algorithms, so the results are not directly comparable; the purpose here is to show that QRE handles all input formats uniformly." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0171b84b", + "metadata": {}, + "outputs": [], + "source": [ + "estimates = []\n", + "\n", + "# Define the target architecture: gate-based with specified timing (ns)\n", + "arch = GateBased(gate_time=100, measurement_time=500)\n", + "\n", + "# Explore surface code distances × round-based magic state factories\n", + "isa_query = SurfaceCode.q() * RoundBasedFactory.q()\n", + "\n", + "for name, app in [(\"Q# adder\", qsharp_app), (\"cirq QFT\", cirq_app), (\"QIR HiddenShift\", qir_app), (\"OpenQASM multiplier\", qasm_app)]:\n", + " estimates.append(estimate(app, arch, isa_query, max_error=0.01, name=name))" + ] + }, + { + "cell_type": "markdown", + "id": "00938f1d", + "metadata": {}, + "source": [ + "We can plot the Pareto frontiers for each application. Since the four programs implement different algorithms, the plots are not meant for cross-algorithm comparison; they simply confirm that each input format produces valid estimation results." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "08fc2a22", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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1AhTpkxB/fr1a6l6IiIiqgnRg4hcLkdwcDDi4uIqXb5582bExsZi1qxZOH/+PIKDg9GtWzdkZmYq13F2dsbFixchk8mwceNGZGRk1Fb5REREVAOiB5Hu3btj7ty56NevX6XLFy1ahLfeegvDhw9HUFAQli1bBjs7O6xatUptXXd3dwQHB+Po0aNV7q+wsBC5ubkqDyIiIhKH6EGkOkVFRTh37hy6dOmibDMzM0OXLl1w8uRJAEBGRgYePXoEAMjJycGRI0fQrFmzKrc5b948SKVS5cPb29uwb4KIiIiq9EwHkfv370OhUMDd3V2l3d3dHenp6QCA27dvIyIiAsHBwYiIiMB7772HFi1aVLnN6dOnIycnR/lITU016HsgIiKiqtX5y3fbtGmDxMREjde3traGtbW14QoiIiIijT3TPSL169eHubm52uDTjIwMeHh4iFQVERER6cszHUSsrKzQunVrxMfHK9tKS0sRHx+P9u3bi1gZERER6YPop2by8vLw119/KZ/LZDIkJibCxcUFPj4+iI2NRXR0NMLCwtCmTRssWbIEcrkcw4cPF7FqIiIi0gfRg8jZs2cRGRmpfB4bGwsAiI6Oxpo1azBo0CDcu3cPM2fORHp6OkJCQrBnzx61AazaiouLQ1xcHBQKRY22Q0RERLqTCCY+H3pubi6kUilycnLg5OQkdjlERER1hj4+Q5/pMSJERERk3BhEiIiISDQMIkRERCQaBhEiIiISDYMIERERicZkg0hcXByCgoIQHh4udilEREQmi5fv8vJdIiIinfDyXSIiIqrTGESIiIhINAwiREREJBoGESIiIhINgwgRERGJxmSDCC/fJSIiEh8v3+Xlu0RERDrh5btERERUpzGIEBERkWgYRIiIiEg0DCJEREQkGgYRIiIiEg2DCBEREYmGQYSIiIhEY7JBhBOaERERiY8TmnFCMyIiIp1wQjMiIiKq0xhEiIiISDQMIkRERCQaBhEiIiISDYMIERERiYZBhIiIiETDIEJERESiYRAhIiIi0ZhsEOHMqkREROLjzKqcWZWIiEgnnFmViIiI6jQGESIiIhINgwgRERGJhkGEiIiIRMMgQkRERKJhECEiIiLRMIgQERGRaBhEiIiISDQMIkRERCQaBhEiIiISDYMIERERicZkgwhvekdERCQ+3vSON70jIiLSCW96R0RERHUa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", + "text/plain": [ + "
" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot_estimates(estimates, figsize=(6, 4))" + ] + }, + { + "cell_type": "markdown", + "id": "8dfc16d9", + "metadata": {}, + "source": [ + "## Custom application model\n", + "\n", + "The built-in application wrappers (`QSharpApplication`, `CirqApplication`, etc.) are convenient for importing existing circuits, but sometimes you need finer control. By subclassing `Application`, you can define a *custom application model* that separates two kinds of parameters:\n", + "\n", + "- **Instance parameters** define *which* problem you are solving. They are fixed for a given estimation run. For example, the bit-size of an adder or the key length of an encryption scheme.\n", + "- **Trace parameters** are algorithmic hyperparameters that control *how* the problem is solved. QRE varies these automatically and explores all combinations during estimation. For example, the choice of adder circuit, the compute-to-memory ratio, or the eviction strategy.\n", + "\n", + "Below we build a configurable adder application that lets QRE explore different adder implementations, compute fractions, and memory-management strategies for a given bit-size, and then compare the Pareto-optimal results." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "932122cc", + "metadata": { + "vscode": { + "languageId": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "import Std.Arithmetic.*;\n", + "import Std.Convert.*;\n", + "import Std.Math.*;\n", + "import Std.ResourceEstimation.*;\n", + "\n", + "// An adder operation that we will estimate together with instance and trace parameters\n", + "operation Adder(n : Int, adder: (Qubit[], Qubit[]) => Unit, computeFraction: Double, strategy: Int) : Unit {\n", + " use a = Qubit[n];\n", + " use b = Qubit[n];\n", + "\n", + " if computeFraction > 0.0 {\n", + " // Enables an automatic memory/compute architecture with a fixed number of compute qubits\n", + " // (computed as a fraction of the total number of qubits). The strategy controls how\n", + " // compute qubits are deallocated if free compute qubits are needed (either least recently\n", + " // used or least frequently used).\n", + " EnableMemoryComputeArchitecture(Ceiling(computeFraction * IntAsDouble(n)), strategy);\n", + " }\n", + "\n", + " // Run the adder\n", + " adder(a, b);\n", + "\n", + " // Reset all qubits (if code is used in simulation)\n", + " ResetAll(a + b);\n", + "}\n", + "\n", + "// Re-export adders and strategies to use in Python, together with strings to use in displaying the results\n", + "function CGAdder(): ((Qubit[], Qubit[]) => Unit, String) { (RippleCarryCGIncByLE, \"CG\") }\n", + "function TTKAdder(): ((Qubit[], Qubit[]) => Unit, String) { (RippleCarryTTKIncByLE, \"TTK\") }\n", + "function LRU(): (Int, String) { (LeastRecentlyUsed(), \"LRU\") }\n", + "function LFU(): (Int, String) { (LeastFrequentlyUsed(), \"LFU\") }" + ] + }, + { + "cell_type": "markdown", + "id": "df9bb728", + "metadata": {}, + "source": [ + "Trace parameters are defined as a `dataclass` with `kw_only=True`. Only keyword-only fields are treated as enumerable parameters by QRE; regular fields are considered fixed. Each keyword field must specify a `domain` in its `metadata` dict; this tells QRE which values to explore during estimation.\n", + "\n", + "In the `AdderParameters` class below:\n", + "\n", + "- **`adder`** selects the adder implementation: the Craig Gidney (CG) ripple-carry adder or the Takahashi–Tani–Kunihiro (TTK) ripple-carry adder.\n", + "- **`compute_fraction`** controls the ratio of qubits allocated to active computation versus memory, passed to `EnableMemoryComputeArchitecture` in the Q# operation.\n", + "- **`strategy`** chooses the eviction policy when compute capacity is exceeded: Least Recently Used (LRU) or Least Frequently Used (LFU).\n", + "\n", + "Each field carries a default value (used when calling the application directly) and a domain (used when QRE enumerates all parameter combinations)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cd26a6b7", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass(kw_only=True)\n", + "class AdderParameters:\n", + " adder: tuple[Any, str] = field(default = qdk.code.CGAdder(), metadata={\"domain\": [qdk.code.CGAdder(), qdk.code.TTKAdder()]})\n", + " compute_fraction: float = field(default=1.0, metadata={\"domain\": [0.5, 1.0, 1.5, 2.0]})\n", + " strategy: tuple[int, str] = field(default=qdk.code.LRU(), metadata={\"domain\": [qdk.code.LRU(), qdk.code.LFU()]})" + ] + }, + { + "cell_type": "markdown", + "id": "1d8f3f9d", + "metadata": {}, + "source": [ + "The `Adder` class subclasses `Application[AdderParameters]`, binding the trace-parameter type. Its single instance parameter, `bitsize`, is a regular (non-keyword) dataclass field that defines which problem instance to estimate.\n", + "\n", + "Key details:\n", + "\n", + "- **`get_trace(parameters)`** is the core method that QRE calls once for every combination of trace parameters. It invokes the Q# `Adder` operation with the current `bitsize` and the selected `adder`, `compute_fraction`, and `strategy` values, producing a resource-estimation trace.\n", + "- **`set_property`** attaches custom metadata (the adder name, compute fraction, and strategy name) to each trace. These properties are carried through estimation and can later be surfaced as custom columns in the results table." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fe5f3ef6", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# Property keys are integers; we can use the custom_properties function to\n", + "# define custom properties that do not conflict with existing properties.\n", + "ADDER, COMPUTE_FRACTION, STRATEGY = custom_properties(3)\n", + "\n", + "@dataclass\n", + "class Adder(Application[AdderParameters]):\n", + " bitsize: int\n", + "\n", + " def __post_init__(self):\n", + " # Disable parallel trace generation since passing the Q# adder operations is not thread-safe.\n", + " self.disable_parallel_traces()\n", + "\n", + " def get_trace(self, parameters: AdderParameters):\n", + " # obtain the adder function and its name\n", + " adder_fn, adder_name = parameters.adder\n", + " # obtain the memory/compute strategy and its name\n", + " strategy, strategy_name = parameters.strategy\n", + " # generate a trace from the Q# entry point with the specified parameters\n", + " trace = trace_from_entry_expr(qdk.code.Adder, self.bitsize, adder_fn, parameters.compute_fraction, strategy)\n", + "\n", + " # Set trace properties for later analysis and display\n", + " trace.set_property(ADDER, adder_name)\n", + " trace.set_property(COMPUTE_FRACTION, parameters.compute_fraction)\n", + " trace.set_property(STRATEGY, strategy_name)\n", + "\n", + " return trace" + ] + }, + { + "cell_type": "markdown", + "id": "a466e2f2", + "metadata": {}, + "source": [ + "Because the Q# operation enables a memory/compute architecture, the estimation must account for *memory qubits* in addition to compute and factory qubits. We extend the ISA query with `TwoDimensionalYokedSurfaceCode`, which models yoked surface codes for memory qubits on top of the lattice-surgery layer provided by `SurfaceCode`.\n", + "\n", + "We also enrich the results table with additional columns:\n", + "\n", + "- **`add_factory_summary_column()`** adds a human-readable summary of the magic-state factories used (e.g., `2×T, 1×CCZ`).\n", + "- **`add_qubit_partition_column()`** breaks the total physical qubit count into compute, factory, and memory partitions.\n", + "- Three **custom property columns** (`Adder`, `Compute fraction`, `Strategy`) surface the trace properties we attached earlier, so each row shows which algorithmic configuration produced that result." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "de782d73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namequbitsruntimeerrorfactoriesphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsaddercompute_fractionstrategy
0Configurable adder140480 days 00:00:00.0079400.0095005×T744849001700TTK1.0LRU
1Configurable adder150280 days 00:00:00.0065450.0079206×T744858801700TTK1.0LFU
2Configurable adder152330 days 00:00:00.0062050.0083842×T396968010584CG0.5LRU
3Configurable adder177290 days 00:00:00.0061800.0089116×T1082958801020TTK1.5LRU
4Configurable adder187090 days 00:00:00.0050100.0072497×T1082968601020TTK1.5LFU
5Configurable adder197810 days 00:00:00.0043750.0088694×T1416139201700CG2.0LFU
6Configurable adder207610 days 00:00:00.0038050.0077275×T1416149001700CG2.0LRU
7Configurable adder318010 days 00:00:00.0018900.00338518×T14161176400TTK2.0LRU
\n", + "
" + ], + "text/plain": [ + " name qubits runtime error factories \\\n", + "0 Configurable adder 14048 0 days 00:00:00.007940 0.009500 5×T \n", + "1 Configurable adder 15028 0 days 00:00:00.006545 0.007920 6×T \n", + "2 Configurable adder 15233 0 days 00:00:00.006205 0.008384 2×T \n", + "3 Configurable adder 17729 0 days 00:00:00.006180 0.008911 6×T \n", + "4 Configurable adder 18709 0 days 00:00:00.005010 0.007249 7×T \n", + "5 Configurable adder 19781 0 days 00:00:00.004375 0.008869 4×T \n", + "6 Configurable adder 20761 0 days 00:00:00.003805 0.007727 5×T \n", + "7 Configurable adder 31801 0 days 00:00:00.001890 0.003385 18×T \n", + "\n", + " physical_compute_qubits physical_factory_qubits physical_memory_qubits \\\n", + "0 7448 4900 1700 \n", + "1 7448 5880 1700 \n", + "2 3969 680 10584 \n", + "3 10829 5880 1020 \n", + "4 10829 6860 1020 \n", + "5 14161 3920 1700 \n", + "6 14161 4900 1700 \n", + "7 14161 17640 0 \n", + "\n", + " adder compute_fraction strategy \n", + "0 TTK 1.0 LRU \n", + "1 TTK 1.0 LFU \n", + "2 CG 0.5 LRU \n", + "3 TTK 1.5 LRU \n", + "4 TTK 1.5 LFU \n", + "5 CG 2.0 LFU \n", + "6 CG 2.0 LRU \n", + "7 TTK 2.0 LRU " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isa_query = SurfaceCode.q() * RoundBasedFactory.q() * TwoDimensionalYokedSurfaceCode.q(source=SurfaceCode.q())\n", + "\n", + "results = estimate(Adder(64), arch, isa_query, max_error=0.01, name=\"Configurable adder\")\n", + "results.add_factory_summary_column()\n", + "results.add_qubit_partition_column()\n", + "results.add_property_column(ADDER, \"adder\")\n", + "results.add_property_column(COMPUTE_FRACTION, \"compute_fraction\")\n", + "results.add_property_column(STRATEGY, \"strategy\")\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "4e0ae55d", + "metadata": {}, + "source": [ + "The table above shows the Pareto-optimal configurations for a 64-bit adder. Each row represents a point on the Pareto frontier where no other configuration achieves both fewer physical qubits *and* a shorter runtime within the 1% error budget. The custom columns make it easy to see which adder implementation, compute fraction, and eviction strategy each result uses.\n", + "\n", + "We can also visualize the Pareto frontier as a scatter plot of physical qubits versus runtime:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7a33193a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.plot(figsize=(4, 4))" + ] + }, + { + "cell_type": "markdown", + "id": "57b74ab4", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This notebook demonstrated two ways to use the Quantum Resource Estimator:\n", + "\n", + "1. **Importing existing programs.** QRE accepts quantum programs from Q#, Cirq, QIR, and OpenQASM through dedicated application wrappers, making it straightforward to estimate resources for circuits written in any of these frameworks.\n", + "2. **Building a custom application model.** By subclassing `Application` and defining trace parameters as a dataclass, you can let QRE automatically explore algorithmic hyperparameters (adder choice, compute fraction, eviction strategy) and compare Pareto-optimal results across all combinations.\n", + "\n", + "For more details on inspecting and visualizing estimation results, see the [Analysing Resource Estimation Results](2_analysing_results.ipynb) notebook." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/qre/2_analysing_results.ipynb b/samples/qre/2_analysing_results.ipynb new file mode 100644 index 0000000000..3aaf2bafab --- /dev/null +++ b/samples/qre/2_analysing_results.ipynb @@ -0,0 +1,1120 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a1b0c0d0", + "metadata": {}, + "source": [ + "# Analysing Resource Estimation Results\n", + "\n", + "This notebook demonstrates how to inspect and analyse the output of the quantum resource estimator. We will:\n", + "\n", + "1. Define a Q# program and run a resource estimation that produces multiple Pareto-optimal results\n", + "2. Understand the estimation statistics\n", + "3. Access individual result entries and their properties\n", + "4. Inspect the ISA used in each result\n", + "5. Inspect the magic-state factories\n", + "6. Add custom columns and plot the Pareto frontier" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b1c0d0e0", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file provides CodeMirror syntax highlighting for Q# magic cells\n// in classic Jupyter Notebooks. It does nothing in other (Jupyter Notebook 7,\n// VS Code, Azure Notebooks, etc.) environments.\n\n// Detect the prerequisites and do nothing if they don't exist.\nif (window.require && window.CodeMirror && window.Jupyter) {\n // The simple mode plugin for CodeMirror is not loaded by default, so require it.\n window.require([\"codemirror/addon/mode/simple\"], function defineMode() {\n let rules = [\n {\n token: \"comment\",\n regex: /(\\/\\/).*/,\n beginWord: false,\n },\n {\n token: \"string\",\n regex: String.raw`^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)`,\n beginWord: false,\n },\n {\n token: \"keyword\",\n regex: String.raw`(namespace|open|as|operation|function|body|adjoint|newtype|controlled|internal)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(let|set|use|borrow|mutable)\\b`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(not|and|or)\\b|(w/)`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(=)|(!)|(<)|(>)|(\\+)|(-)|(\\*)|(/)|(\\^)|(%)|(\\|)|(&&&)|(~~~)|(\\.\\.\\.)|(\\.\\.)|(\\?)`,\n beginWord: false,\n },\n {\n token: \"meta\",\n regex: String.raw`(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\\b`,\n beginWord: true,\n },\n {\n token: \"atom\",\n regex: String.raw`(true|false|Pauli(I|X|Y|Z)|One|Zero)\\b`,\n beginWord: true,\n },\n ];\n let simpleRules = [];\n for (let rule of rules) {\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(rule.regex, \"g\"),\n sol: rule.beginWord,\n });\n if (rule.beginWord) {\n // Need an additional rule due to the fact that CodeMirror simple mode doesn't work with ^ token\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(String.raw`\\W` + rule.regex, \"g\"),\n sol: false,\n });\n }\n }\n\n // Register the mode defined above with CodeMirror\n window.CodeMirror.defineSimpleMode(\"qsharp\", { start: simpleRules });\n window.CodeMirror.defineMIME(\"text/x-qsharp\", \"qsharp\");\n\n // Tell Jupyter to associate %%qsharp magic cells with the qsharp mode\n window.Jupyter.CodeCell.options_default.highlight_modes[\"qsharp\"] = {\n reg: [/^%%qsharp/],\n };\n\n // Force re-highlighting of all cells the first time this code runs\n for (const cell of window.Jupyter.notebook.get_cells()) {\n cell.auto_highlight();\n }\n });\n}\n", + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "from qdk import qsharp\n", + "from qdk.qre import estimate, property_name, instruction_name, PSSPC, LatticeSurgery\n", + "from qdk.qre.models import GateBased, SurfaceCode, RoundBasedFactory\n", + "from qdk.qre.application import QSharpApplication\n", + "from qdk.qre.instruction_ids import T, LATTICE_SURGERY\n", + "from qdk.qre.property_keys import (\n", + " PHYSICAL_COMPUTE_QUBITS,\n", + " PHYSICAL_FACTORY_QUBITS,\n", + " PHYSICAL_MEMORY_QUBITS,\n", + " LOGICAL_COMPUTE_QUBITS,\n", + " ALGORITHM_COMPUTE_QUBITS,\n", + " NUM_TS_PER_ROTATION,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c1d0e0f0", + "metadata": {}, + "source": [ + "## 1. Define a Q# program and run estimation\n", + "\n", + "We define a Q# program that performs eight parallel 8-bit ripple-carry additions, each preceded by single-qubit rotations. The rotations are important because they introduce T-gate decomposition, which makes the PSSPC trace transform explore multiple configurations (one for each `num_ts_per_rotation` value)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d1e0f0a1", + "metadata": { + "vscode": { + "languageId": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "import Std.Arithmetic.*;\n", + "import Std.Math.*;\n", + "import Std.Convert.*;\n", + "\n", + "operation EstimateAdder() : Unit {\n", + " for _ in 1..8 {\n", + " use a = Qubit[8];\n", + " use b = Qubit[8];\n", + " for i in 0..7 {\n", + " Ry(PI() / IntAsDouble(i + 2), a[i]);\n", + " }\n", + " RippleCarryCGIncByLE(a, b);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "1a3228b0", + "metadata": {}, + "source": [ + "To further increase the variety of Pareto-optimal points, we vary the `slow_down_factor` in the `LatticeSurgery` trace transform. This factor multiplies the trace depth for space/time trade-offs." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f1a1b2c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of Pareto-optimal results: 6\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerror
089670 days 00:00:00.0092000.007875
1109270 days 00:00:00.0069000.007006
2148470 days 00:00:00.0046000.006136
3187670 days 00:00:00.0034500.005702
4227710 days 00:00:00.0032200.004406
5266070 days 00:00:00.0023000.005267
\n", + "
" + ], + "text/plain": [ + " qubits runtime error\n", + "0 8967 0 days 00:00:00.009200 0.007875\n", + "1 10927 0 days 00:00:00.006900 0.007006\n", + "2 14847 0 days 00:00:00.004600 0.006136\n", + "3 18767 0 days 00:00:00.003450 0.005702\n", + "4 22771 0 days 00:00:00.003220 0.004406\n", + "5 26607 0 days 00:00:00.002300 0.005267" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qsharp.code import EstimateAdder\n", + "\n", + "app = QSharpApplication(EstimateAdder)\n", + "arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)\n", + "\n", + "results = estimate(\n", + " app,\n", + " arch,\n", + " isa_query=SurfaceCode.q() * RoundBasedFactory.q(),\n", + " trace_query=PSSPC.q() * LatticeSurgery.q(slow_down_factor=[1.0, 1.5, 2.0, 3.0, 4.0]),\n", + " max_error=0.01,\n", + ")\n", + "\n", + "print(f\"Number of Pareto-optimal results: {len(results)}\")\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "a2b3c4d5", + "metadata": {}, + "source": [ + "Each row in the table is a Pareto-optimal configuration: no other result achieves both fewer qubits *and* a shorter runtime while staying within the error budget. The trade-off is visible: results with fewer qubits tend to have longer runtimes, and vice versa." + ] + }, + { + "cell_type": "markdown", + "id": "b3c4d5e6", + "metadata": {}, + "source": [ + "## 2. Estimation statistics\n", + "\n", + "The estimator explores a large combinatorial design space. It generates multiple *traces* from the application (one for each combination of trace-transform parameters), and multiple *ISAs* from the architecture (one for each combination of QEC code distance and factory protocol). Every (trace, ISA) pair is an estimation job.\n", + "\n", + "The `stats` attribute summarises this exploration." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b2c3d4e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Traces explored: 160\n", + "ISAs explored: 1248\n", + "Total estimation jobs: 49005\n", + "Successful estimates: 990\n", + "Pareto-optimal results: 6\n" + ] + } + ], + "source": [ + "stats = results.stats\n", + "print(f\"Traces explored: {stats.num_traces}\")\n", + "print(f\"ISAs explored: {stats.num_isas}\")\n", + "print(f\"Total estimation jobs: {stats.total_jobs}\")\n", + "print(f\"Successful estimates: {stats.successful_estimates}\")\n", + "print(f\"Pareto-optimal results: {stats.pareto_results}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c4d5e6f7", + "metadata": {}, + "source": [ + "Here is what each statistic means:\n", + "\n", + "- **Traces explored:** The number of distinct traces generated from the application. Each combination of trace-transform parameters produces a separate trace. In our case, the PSSPC transform enumerates 16 values for `num_ts_per_rotation` (5 through 20) and we specified 5 values for `slow_down_factor`, giving 16 × 5 × 2 = 160 traces (the × 2 comes from the PSSPC `ccx_magic_states` boolean, which defaults to `[True, False]`).\n", + "\n", + "- **ISAs explored:** The number of distinct instruction sets generated from the architecture. Each combination of surface code distance and factory distillation protocol produces one ISA. The estimator systematically enumerates code distances and factory configurations, producing a large set of candidate ISAs.\n", + "\n", + "- **Total estimation jobs:** The total number of (trace, ISA) pairs evaluated. In principle this would be `num_traces × num_isas`, but the estimator uses pruning strategies (based on a provenance graph) to skip ISAs that are dominated on per-instruction metrics, so the actual number is typically much smaller.\n", + "\n", + "- **Successful estimates:** The number of jobs that produced a valid result, i.e., the total error stayed within the `max_error` budget. Jobs where the error budget is exceeded are discarded.\n", + "\n", + "- **Pareto-optimal results:** The number of results retained after Pareto filtering. A result is Pareto-optimal if no other result is simultaneously better in both physical qubits and runtime. This is the final set returned in the `EstimationTable`." + ] + }, + { + "cell_type": "markdown", + "id": "d5e6f7a8", + "metadata": {}, + "source": [ + "## 3. Accessing individual results\n", + "\n", + "Results are stored in an `EstimationTable`, which behaves like a list of `EstimationTableEntry` objects. Each entry exposes:\n", + "\n", + "- **`qubits`**, **`runtime`**, **`error`:** the headline metrics\n", + "- **`properties`:** a dictionary of additional metrics (qubit partition, algorithm parameters, etc.)\n", + "- **`source`:** an instruction source graph showing how logical instructions are implemented\n", + "- **`factories`:** a dictionary mapping instruction IDs to factory results\n", + "\n", + "Note that the input *trace* itself is not part of the result (traces can be very large), but key properties derived from it, such as algorithmic qubit counts and decomposition parameters, are stored in the `properties` dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d4e5f6a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total physical qubits: 8967\n", + "Runtime: 0 days 00:00:00.009200\n", + "Error: 0.007875\n" + ] + } + ], + "source": [ + "entry = results[0]\n", + "\n", + "print(f\"Total physical qubits: {entry.qubits}\")\n", + "print(f\"Runtime: {pd.Timedelta(entry.runtime, unit='ns')}\")\n", + "print(f\"Error: {entry.error:.6f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e5f6a7b8", + "metadata": {}, + "source": [ + "### Result properties\n", + "\n", + "The `properties` dictionary carries additional metrics attached during estimation. The keys are integer identifiers; use `property_name()` to get human-readable names.\n", + "\n", + "These properties include information derived from the trace (such as algorithmic qubit counts and the number of T states per rotation used in the PSSPC decomposition) as well as information from the estimation itself (such as the physical qubit partition and evaluation time)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f6a7b8c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " LOGICAL_MEMORY_QUBITS: 0\n", + " EVALUATION_TIME: 7728507\n", + " NUM_TS_PER_ROTATION: 12\n", + " ALGORITHM_COMPUTE_QUBITS: 24\n", + " PHYSICAL_COMPUTE_QUBITS: 3087\n", + " ALGORITHM_MEMORY_QUBITS: 0\n", + " LOGICAL_COMPUTE_QUBITS: 63\n", + " PHYSICAL_FACTORY_QUBITS: 5880\n" + ] + } + ], + "source": [ + "props = {property_name(k): v for k, v in entry.properties.items()}\n", + "for name, value in props.items():\n", + " print(f\" {name}: {value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a7b8c9d0", + "metadata": {}, + "source": [ + "Properties can also be accessed by key directly using the constants from `property_keys`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8c9d0e1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Num Ts per rotation: 12\n", + "Algorithm compute qubits: 24\n", + "Logical compute qubits: 63\n", + "Physical compute qubits: 3087\n", + "Physical factory qubits: 5880\n", + "Physical memory qubits: 0\n" + ] + } + ], + "source": [ + "print(f\"Num Ts per rotation: {entry.properties.get(NUM_TS_PER_ROTATION, 'N/A')}\")\n", + "print(f\"Algorithm compute qubits: {entry.properties.get(ALGORITHM_COMPUTE_QUBITS, 'N/A')}\")\n", + "print(f\"Logical compute qubits: {entry.properties.get(LOGICAL_COMPUTE_QUBITS, 'N/A')}\")\n", + "print(f\"Physical compute qubits: {entry.properties.get(PHYSICAL_COMPUTE_QUBITS, 0)}\")\n", + "print(f\"Physical factory qubits: {entry.properties.get(PHYSICAL_FACTORY_QUBITS, 0)}\")\n", + "print(f\"Physical memory qubits: {entry.properties.get(PHYSICAL_MEMORY_QUBITS, 0)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c9d0e1f2", + "metadata": {}, + "source": [ + "### Pre-configured columns for qubit partition and factories\n", + "\n", + "The `EstimationTable` provides convenience methods to add commonly used columns. `add_qubit_partition_column()` breaks the total physical qubit count into compute, factory, and memory qubits. `add_factory_summary_column()` adds a human-readable summary of the factories used (e.g., `20×T` meaning 20 copies of a T-state factory)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d0e1f2a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactories
089670 days 00:00:00.0092000.0078753087588006×T
1109270 days 00:00:00.0069000.0070063087784008×T
2148470 days 00:00:00.0046000.006136308711760012×T
3187670 days 00:00:00.0034500.005702308715680016×T
4227710 days 00:00:00.0032200.004406611116660017×T
5266070 days 00:00:00.0023000.005267308723520024×T
\n", + "
" + ], + "text/plain": [ + " qubits runtime error physical_compute_qubits \\\n", + "0 8967 0 days 00:00:00.009200 0.007875 3087 \n", + "1 10927 0 days 00:00:00.006900 0.007006 3087 \n", + "2 14847 0 days 00:00:00.004600 0.006136 3087 \n", + "3 18767 0 days 00:00:00.003450 0.005702 3087 \n", + "4 22771 0 days 00:00:00.003220 0.004406 6111 \n", + "5 26607 0 days 00:00:00.002300 0.005267 3087 \n", + "\n", + " physical_factory_qubits physical_memory_qubits factories \n", + "0 5880 0 6×T \n", + "1 7840 0 8×T \n", + "2 11760 0 12×T \n", + "3 15680 0 16×T \n", + "4 16660 0 17×T \n", + "5 23520 0 24×T " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.add_qubit_partition_column()\n", + "results.add_factory_summary_column()\n", + "\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "e1f2a3b4", + "metadata": {}, + "source": [ + "## 4. Inspecting the ISA of an estimation result\n", + "\n", + "Each estimation result carries an *instruction source graph* that describes how logical instructions are implemented in terms of lower-level operations. The `source` attribute of each result entry provides this graph.\n", + "\n", + "Each root node in the graph is a logical instruction (e.g., `T`, `LATTICE_SURGERY`). Nodes also reference the *transform* that produced them (e.g., the surface code QEC scheme or the architecture itself), and may have child nodes representing the physical instructions they are built from." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f8a9b0c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Instruction source graph for the first result:\n", + "LATTICE_SURGERY @ SurfaceCode(crossing_prefactor=0.03, error_correction_threshold=0.01, one_qubit_gate_depth=1, two_qubit_gate_depth=4, distance=5)\n", + " CNOT @ GateBased(error_rate=0.0001, gate_time=100, measurement_time=500, two_qubit_gate_time=100)\n", + " H @ GateBased(error_rate=0.0001, gate_time=100, measurement_time=500, two_qubit_gate_time=100)\n", + " MEAS_Z @ GateBased(error_rate=0.0001, gate_time=100, measurement_time=500, two_qubit_gate_time=100)\n", + "T @ RoundBasedFactory(code_query=_ComponentQuery(component=, source=RootNode(), kwargs={}), physical_qubit_calculation=)\n", + " T @ GateBased(error_rate=0.0001, gate_time=100, measurement_time=500, two_qubit_gate_time=100)\n" + ] + } + ], + "source": [ + "print(\"Instruction source graph for the first result:\")\n", + "print(entry.source)" + ] + }, + { + "cell_type": "markdown", + "id": "a9b0c1d2", + "metadata": {}, + "source": [ + "### Accessing individual instructions\n", + "\n", + "The instruction source graph can be navigated like a dictionary using instruction IDs. For each node, you can inspect the instruction's properties (space, time, error rate) and the transform that produced it." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b0c1d2e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T instruction:\n", + " ID: T\n", + " Encoding: LOGICAL\n", + " Arity: 1\n", + " Space: 980 physical qubits\n", + " Time: 65000 ns\n", + " Error rate: 2.13e-07\n", + " Transform: RoundBasedFactory(code_query=_ComponentQuery(component=, source=RootNode(), kwargs={}), physical_qubit_calculation=)\n" + ] + } + ], + "source": [ + "if T in entry.source:\n", + " t_node = entry.source[T]\n", + " t_inst = t_node.instruction\n", + " print(\"T instruction:\")\n", + " print(f\" ID: {instruction_name(t_inst.id)}\")\n", + " print(f\" Encoding: {'LOGICAL' if t_inst.encoding == 1 else 'PHYSICAL'}\")\n", + " print(f\" Arity: {t_inst.arity}\")\n", + " print(f\" Space: {t_inst.space()} physical qubits\")\n", + " print(f\" Time: {t_inst.time()} ns\")\n", + " print(f\" Error rate: {t_inst.error_rate():.2e}\")\n", + " if t_node.transform is not None:\n", + " print(f\" Transform: {t_node.transform}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1d2e3f4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LATTICE_SURGERY instruction:\n", + " ID: LATTICE_SURGERY\n", + " Encoding: LOGICAL\n", + " Arity: None\n", + " Error rate (arity=1): 3.00e-08\n", + " Transform: SurfaceCode(crossing_prefactor=0.03, error_correction_threshold=0.01, one_qubit_gate_depth=1, two_qubit_gate_depth=4, distance=5)\n" + ] + } + ], + "source": [ + "if LATTICE_SURGERY in entry.source:\n", + " ls_node = entry.source[LATTICE_SURGERY]\n", + " ls_inst = ls_node.instruction\n", + " print(\"LATTICE_SURGERY instruction:\")\n", + " print(f\" ID: {instruction_name(ls_inst.id)}\")\n", + " print(f\" Encoding: {'LOGICAL' if ls_inst.encoding == 1 else 'PHYSICAL'}\")\n", + " # Arity is None for variable-arity instructions\n", + " print(f\" Arity: {ls_inst.arity}\")\n", + " # For variable-arity instructions, query specific arities\n", + " print(f\" Error rate (arity=1): {ls_inst.error_rate(1):.2e}\")\n", + " if ls_node.transform is not None:\n", + " print(f\" Transform: {ls_node.transform}\")" + ] + }, + { + "cell_type": "markdown", + "id": "d2e3f4a5", + "metadata": {}, + "source": [ + "## 5. Inspecting factories\n", + "\n", + "Magic-state factories distill high-fidelity resource states (e.g., T states) from noisy physical operations. The `factories` dictionary on each result entry maps instruction IDs to `FactoryResult` objects with four properties:\n", + "\n", + "- **`copies`:** the number of factory instances running in parallel\n", + "- **`runs`:** the number of distillation rounds each factory performs\n", + "- **`states`:** the total number of resource states produced\n", + "- **`error_rate`:** the output error rate per produced state" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e3f4a5b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Factories in the first result:\n", + "\n", + " T factory:\n", + " Copies: 6\n", + " Runs: 141\n", + " States: 808\n", + " Error rate: 2.13e-07\n" + ] + } + ], + "source": [ + "print(\"Factories in the first result:\")\n", + "for inst_id, factory in entry.factories.items():\n", + " print(f\"\\n {instruction_name(inst_id)} factory:\")\n", + " print(f\" Copies: {factory.copies}\")\n", + " print(f\" Runs: {factory.runs}\")\n", + " print(f\" States: {factory.states}\")\n", + " print(f\" Error rate: {factory.error_rate:.2e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f4a5b6c7", + "metadata": {}, + "source": [ + "### Comparing factories across Pareto-optimal results\n", + "\n", + "Different points on the Pareto frontier use different factory configurations. Results that favour fewer qubits typically use fewer factory copies (at the cost of longer runtime), while faster results need more factory copies running in parallel." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a5b6c7d8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
resultinstructioncopiesrunsstatesfactory_error_ratetotal_qubitsruntime_ns
00T61418082.130350e-0789679200000
11T81068082.130350e-07109276900000
22T12708082.130350e-07148474600000
33T16538082.130350e-07187673450000
44T17498082.130350e-07227713220000
55T24358082.130350e-07266072300000
\n", + "
" + ], + "text/plain": [ + " result instruction copies runs states factory_error_rate total_qubits \\\n", + "0 0 T 6 141 808 2.130350e-07 8967 \n", + "1 1 T 8 106 808 2.130350e-07 10927 \n", + "2 2 T 12 70 808 2.130350e-07 14847 \n", + "3 3 T 16 53 808 2.130350e-07 18767 \n", + "4 4 T 17 49 808 2.130350e-07 22771 \n", + "5 5 T 24 35 808 2.130350e-07 26607 \n", + "\n", + " runtime_ns \n", + "0 9200000 \n", + "1 6900000 \n", + "2 4600000 \n", + "3 3450000 \n", + "4 3220000 \n", + "5 2300000 " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "factory_data = []\n", + "for i, e in enumerate(results):\n", + " for inst_id, factory in e.factories.items():\n", + " factory_data.append({\n", + " \"result\": i,\n", + " \"instruction\": instruction_name(inst_id),\n", + " \"copies\": factory.copies,\n", + " \"runs\": factory.runs,\n", + " \"states\": factory.states,\n", + " \"factory_error_rate\": factory.error_rate,\n", + " \"total_qubits\": e.qubits,\n", + " \"runtime_ns\": e.runtime,\n", + " })\n", + "\n", + "pd.DataFrame(factory_data)" + ] + }, + { + "cell_type": "markdown", + "id": "b6c7d8e9", + "metadata": {}, + "source": [ + "## 6. Adding custom columns and plotting\n", + "\n", + "The `EstimationTable` supports adding custom columns with `add_column(name, function)` and `add_property_column(key)`. With `add_column`, the function receives an `EstimationTableEntry` and returns the value for that column. With `add_property_column`, the column is automatically populated from the entry's `properties` dictionary using the given key. These methods are useful for surfacing specific metrics, such as per-instruction error rates, factory state counts, or decomposition parameters, directly in the results table." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c7d8e9f0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
qubitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactoriest_error_ratelogical_error_ratet_statesnum_ts_per_rotation
089670 days 00:00:00.0092000.0078753087588006×T2.130350e-073.000000e-0880812
1109270 days 00:00:00.0069000.0070063087784008×T2.130350e-073.000000e-0880812
2148470 days 00:00:00.0046000.006136308711760012×T2.130350e-073.000000e-0880812
3187670 days 00:00:00.0034500.005702308715680016×T2.130350e-073.000000e-0880812
4227710 days 00:00:00.0032200.004406611116660017×T2.130350e-073.000000e-1080812
5266070 days 00:00:00.0023000.005267308723520024×T2.130350e-073.000000e-0880812
\n", + "
" + ], + "text/plain": [ + " qubits runtime error physical_compute_qubits \\\n", + "0 8967 0 days 00:00:00.009200 0.007875 3087 \n", + "1 10927 0 days 00:00:00.006900 0.007006 3087 \n", + "2 14847 0 days 00:00:00.004600 0.006136 3087 \n", + "3 18767 0 days 00:00:00.003450 0.005702 3087 \n", + "4 22771 0 days 00:00:00.003220 0.004406 6111 \n", + "5 26607 0 days 00:00:00.002300 0.005267 3087 \n", + "\n", + " physical_factory_qubits physical_memory_qubits factories t_error_rate \\\n", + "0 5880 0 6×T 2.130350e-07 \n", + "1 7840 0 8×T 2.130350e-07 \n", + "2 11760 0 12×T 2.130350e-07 \n", + "3 15680 0 16×T 2.130350e-07 \n", + "4 16660 0 17×T 2.130350e-07 \n", + "5 23520 0 24×T 2.130350e-07 \n", + "\n", + " logical_error_rate t_states num_ts_per_rotation \n", + "0 3.000000e-08 808 12 \n", + "1 3.000000e-08 808 12 \n", + "2 3.000000e-08 808 12 \n", + "3 3.000000e-08 808 12 \n", + "4 3.000000e-10 808 12 \n", + "5 3.000000e-08 808 12 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.add_column(\n", + " \"t_error_rate\",\n", + " lambda r: r.source[T].instruction.error_rate() if T in r.source else None,\n", + ")\n", + "results.add_column(\n", + " \"logical_error_rate\",\n", + " lambda r: r.source[LATTICE_SURGERY].instruction.error_rate(1) if LATTICE_SURGERY in r.source else None,\n", + ")\n", + "results.add_column(\n", + " \"t_states\",\n", + " lambda r: r.factories[T].states if T in r.factories else 0,\n", + ")\n", + "results.add_property_column(NUM_TS_PER_ROTATION)\n", + "\n", + "results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "d8e9f0a1", + "metadata": {}, + "source": [ + "### Plotting the Pareto frontier\n", + "\n", + "The `plot` method (or the standalone `plot_estimates` function) creates a log-log scatter plot of the Pareto frontier, with runtime on the x-axis and physical qubits on the y-axis. Each point represents a configuration that is optimal in the sense that no other result achieves both fewer qubits *and* a shorter runtime." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "e9f0a1b2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zr3o44WHh2vAgAHasGGDxwwbNmyo91prNGz06NHas2eP8vLy3D8DBw7U5MmTlZeXp9DQ0DrP4TOFvw0bNkz79u3z2Pb1118rKSmp3v35mwQCJNB3JwLBbt68edaWLVus/Px8a/fu3da8efMsm81mrVu3rs6+NTU11sCBA61x48ZZlZWV7u15eXlWbGys9de//rXeY5w+fdrauXOntXPnTkuS9de//tXauXOndejQIfc+q1evtiIiIqyXX37Z2rt3rzV9+nSrTZs21tGjR33/ppuZC62qwWcKf8vOzrbCwsKshQsXWvv377def/11Kyoqynrttdfq7MvfJBA4hDNwAffcc4+VlJRkhYeHWx06dLBGjx5dbzSftW7dOsvlctXZnpubax05cqTe52zatMmSVOcnPT3dY7//+Z//sRITE63w8HBr0KBBVlZW1iW9N/zgfOFsWXymuDzeffdd69prr7UiIiKsq666ylq5cmWD+/I3CQSGzbIs63Kf5QYAAAAaG65xBgAAAAwQzgAAAIABwhkAAAAwQDgDAAAABghnAAAAwADhDAAAABggnAEAAAADhDMAAABggHAGAAAADBDOANBM3XTTTZo9e3bAjn/jjTfqH//4h99e/8MPP1S/fv1UW1vrt2MAaF4IZwAIkKlTp8pms8lms6lFixbq3r27HnroIVVUVPj0OJs3b5bNZtOpU6c8tr/55pv685//7NNjmXrnnXdUVFSkSZMm+e0YY8eOVYsWLfT666/77RgAmhfCGQACaOzYsSosLNS3336rxYsXa8WKFVqwYMFlOXZsbKxat259WY71U0uXLtW0adMUEuLf/xuaOnWqli5d6tdjAGg+CGcACKCIiAh16tRJCQkJmjBhglJTU7V+/Xr34926ddOSJUs8ntOvXz899thj7t9tNpteeOEF/fKXv1RUVJR69eqld955R5J08OBBjRw5UpLUtm1b2Ww2TZ06VVLdSzW6deumJ554QlOmTFF0dLSSkpL0zjvv6NixY7r99tsVHR2tvn37aseOHR7zbN26VcOHD1fLli2VkJCgWbNmqaysrMH3fOzYMW3cuFHjx4/32G6z2bRixQrdeuutioqK0tVXX61t27bpm2++0U033aRWrVrpZz/7mQ4cOOB+zq5duzRy5Ei1bt1adrtdAwYM8Jhv/Pjx2rFjh8dzAOBiEc4AECQ+//xzffrppwoPD/f6uY8//rjuvPNO7d69W+PGjdPkyZNVUlKihIQEvfHGG5Kkffv2qbCwUM8991yDr7N48WINGzZMO3fu1C233KLf/va3mjJliu6++27l5uaqR48emjJliizLkiQdOHBAY8eO1R133KHdu3crMzNTW7du1cyZMxs8xtatW91h/FN//vOfNWXKFOXl5emqq67SXXfdpfvvv1/z58/Xjh07ZFmWx2tPnjxZXbt21fbt25WTk6N58+apRYsW7scTExMVFxenjz/+2Ot/UwD4KcIZAALovffeU3R0tCIjI3XdddepuLhYf/jDH7x+nalTp+o3v/mNevbsqSeffFKlpaXKzs5WaGioYmNjJUkdO3ZUp06dFBMT0+DrjBs3Tvfff7969eqlRx99VE6nUykpKZo4caJ69+6thx9+WF9++aWKiookSYsWLdLkyZM1e/Zs9erVSz/72c+0dOlSvfLKKw1eq33o0CHFxcXVe5nGtGnTdOedd7qPdfDgQU2ePFljxozR1VdfrQcffFCbN29273/48GGlpqbqqquuUq9evTRx4kQlJyd7vGZ8fLwOHTrk7T8pANRBOANAAI0cOVJ5eXn67LPPlJ6ermnTpumOO+7w+nX69u3r/t+tWrWS3W5XcXHxJb1OXFycJOm6666rs+3sa+/atUsvv/yyoqOj3T9jxoxRbW2t8vPz6z2Gy+VSZGTkRR+/oqJCTqdTkpSRkaF7771Xqampeuqpp+q9JKNly5YqLy+/8JsHgAsgnAEggFq1aqWePXsqOTlZq1at0meffaYXX3zR/XhISIj7soizqqqq6rzOuZcnSD9cL3wxy7Cd+zo2m63BbWdfu7S0VPfff7/y8vLcP7t27dL+/fvVo0ePeo/Rvn17nTx50ifHf+yxx/TFF1/olltu0caNG9WnTx+tXbvW4zVLSkrUoUMHg3cPAOdHOANAkAgJCdEjjzyiP/7xj3K5XJKkDh06qLCw0L2P0+ls8ExuQ85eM11TU+O7Yf+/66+/Xnv37lXPnj3r/DR0rXb//v119OjRBuPZW71799acOXO0bt06/epXv9JLL73kfqyiokIHDhxQ//79fXIsAM0b4QwAQWTixIkKDQ3VsmXLJEmjRo3Sq6++qo8//lh79uxRenq6QkNDvXrNpKQk2Ww2vffeezp27JhKS0t9Nu/DDz+sTz/9VDNnzlReXp7279+vt99++7w3B/bv31/t27fXJ598cknHdrlcmjlzpjZv3qxDhw7pk08+0fbt2z1uOszKylJERISGDh16SccCAIlwBoCgEhYWppkzZ+ovf/mLysrKNH/+fI0YMUK33nqrbrnlFk2YMKHBSyAa0qVLFz3++OOaN2+e4uLizhu13urbt6+2bNmir7/+WsOHD1f//v316KOPKj4+vsHnhIaGatq0aZf8xSShoaE6ceKEpkyZot69e+vOO+/UzTffrMcff9y9zz//+U9NnjxZUVFRl3QsAJAkm/XTi+cAAPCzo0eP6pprrlFubq6SkpL8cozjx4/ryiuv1I4dO9S9e3e/HANA88IZZwDAZdepUye9+OKLOnz4sN+OcfDgQf3tb38jmgH4DGecAQAAAAOccQYAAAAMEM4AAACAAcIZAAAAMEA4AwAAAAYIZwAAAMAA4QwAAAAYIJwBAAAAA4QzAAAAYIBwBgAAAAz8P69ZKREbF6RNAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.plot(figsize=(8, 5), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "f0a1b2c3", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "| What to inspect | How to access it |\n", + "|---|---|\n", + "| Top-level metrics | `entry.qubits`, `entry.runtime`, `entry.error` |\n", + "| Estimation statistics | `results.stats` → `.num_traces`, `.num_isas`, `.total_jobs`, `.successful_estimates`, `.pareto_results` |\n", + "| Result properties | `entry.properties[KEY]` with keys from `property_keys`; use `property_name(k)` for names |\n", + "| Instruction source graph | `entry.source[INSTRUCTION_ID]` → `.instruction`, `.transform` |\n", + "| Instruction details | `instruction.space()`, `.time()`, `.error_rate()` |\n", + "| Magic-state factories | `entry.factories[INSTRUCTION_ID]` → `.copies`, `.runs`, `.states`, `.error_rate` |\n", + "| Qubit partition | `results.add_qubit_partition_column()` or via property keys |\n", + "| Custom table columns | `results.add_column(name, function)` or `results.add_property_column(key)` |\n", + "| Plotting | `results.plot()` or `plot_estimates(results)` |\n", + "\n", + "Note that the input *trace* is not included in the results (traces can be very large), but key properties derived from it, such as algorithmic qubit counts and decomposition parameters, are available through the `properties` dictionary." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/qre/3_building_your_own_models.ipynb b/samples/qre/3_building_your_own_models.ipynb new file mode 100644 index 0000000000..64c172e20f --- /dev/null +++ b/samples/qre/3_building_your_own_models.ipynb @@ -0,0 +1,1136 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "226dfb5f", + "metadata": {}, + "source": [ + "# Exploring Hypothetical Hardware\n", + "\n", + "The [previous notebooks](0_getting_started.ipynb) showed how to use the quantum\n", + "resource estimator with built-in models like `GateBased`, `SurfaceCode`, and\n", + "`Litinski19Factory`. These models capture well-studied hardware assumptions,\n", + "but what if your hardware doesn't match them?\n", + "\n", + "In this notebook we build **custom models** to answer three \"what if\" questions:\n", + "\n", + "- **Architecture model:** What if my qubits had different gate speeds and error rates?\n", + "- **QEC ISA transform:** What if I had a QEC code with a higher threshold or lower overhead?\n", + "- **Factory transform:** What if my magic state distillation protocol were different?\n", + "\n", + "Each model plugs into the estimator's layered architecture. You implement a\n", + "small Python class and the estimator takes care of the rest: enumerating\n", + "configurations, pruning infeasible ones, and finding the Pareto-optimal results.\n", + "\n", + "We finish by composing all three custom layers into a full estimation stack and\n", + "comparing it against the built-in models." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5dafb171", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file provides CodeMirror syntax highlighting for Q# magic cells\n// in classic Jupyter Notebooks. It does nothing in other (Jupyter Notebook 7,\n// VS Code, Azure Notebooks, etc.) environments.\n\n// Detect the prerequisites and do nothing if they don't exist.\nif (window.require && window.CodeMirror && window.Jupyter) {\n // The simple mode plugin for CodeMirror is not loaded by default, so require it.\n window.require([\"codemirror/addon/mode/simple\"], function defineMode() {\n let rules = [\n {\n token: \"comment\",\n regex: /(\\/\\/).*/,\n beginWord: false,\n },\n {\n token: \"string\",\n regex: String.raw`^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)`,\n beginWord: false,\n },\n {\n token: \"keyword\",\n regex: String.raw`(namespace|open|as|operation|function|body|adjoint|newtype|controlled|internal)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(if|elif|else|repeat|until|fixup|for|in|return|fail|within|apply)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(Adjoint|Controlled|Adj|Ctl|is|self|auto|distribute|invert|intrinsic)\\b`,\n beginWord: true,\n },\n {\n token: \"keyword\",\n regex: String.raw`(let|set|use|borrow|mutable)\\b`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(not|and|or)\\b|(w/)`,\n beginWord: true,\n },\n {\n token: \"operatorKeyword\",\n regex: String.raw`(=)|(!)|(<)|(>)|(\\+)|(-)|(\\*)|(/)|(\\^)|(%)|(\\|)|(&&&)|(~~~)|(\\.\\.\\.)|(\\.\\.)|(\\?)`,\n beginWord: false,\n },\n {\n token: \"meta\",\n regex: String.raw`(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit)\\b`,\n beginWord: true,\n },\n {\n token: \"atom\",\n regex: String.raw`(true|false|Pauli(I|X|Y|Z)|One|Zero)\\b`,\n beginWord: true,\n },\n ];\n let simpleRules = [];\n for (let rule of rules) {\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(rule.regex, \"g\"),\n sol: rule.beginWord,\n });\n if (rule.beginWord) {\n // Need an additional rule due to the fact that CodeMirror simple mode doesn't work with ^ token\n simpleRules.push({\n token: rule.token,\n regex: new RegExp(String.raw`\\W` + rule.regex, \"g\"),\n sol: false,\n });\n }\n }\n\n // Register the mode defined above with CodeMirror\n window.CodeMirror.defineSimpleMode(\"qsharp\", { start: simpleRules });\n window.CodeMirror.defineMIME(\"text/x-qsharp\", \"qsharp\");\n\n // Tell Jupyter to associate %%qsharp magic cells with the qsharp mode\n window.Jupyter.CodeCell.options_default.highlight_modes[\"qsharp\"] = {\n reg: [/^%%qsharp/],\n };\n\n // Force re-highlighting of all cells the first time this code runs\n for (const cell of window.Jupyter.notebook.get_cells()) {\n cell.auto_highlight();\n }\n });\n}\n", + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dataclasses import KW_ONLY, dataclass, field\n", + "from math import ceil\n", + "from typing import Generator\n", + "\n", + "import qdk\n", + "from qdk import qsharp\n", + "from qdk.qre import (\n", + " Architecture,\n", + " ISAContext,\n", + " ConstraintBound,\n", + " ISA,\n", + " ISARequirements,\n", + " ISATransform,\n", + " LOGICAL,\n", + " PHYSICAL,\n", + " constraint,\n", + " estimate,\n", + " linear_function,\n", + " plot_estimates,\n", + ")\n", + "from qdk.qre.models import GateBased, SurfaceCode, Litinski19Factory\n", + "from qdk.qre.application import QSharpApplication\n", + "from qdk.qre.instruction_ids import (\n", + " H, S, S_DAG, T, T_DAG, CNOT, CZ, MEAS_X, MEAS_Y, MEAS_Z, LATTICE_SURGERY,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4ed9261a", + "metadata": {}, + "source": [ + "## Setup and baseline\n", + "\n", + "We start with a simple Q# program and a baseline estimation using the built-in\n", + "models. This gives us a reference point to compare against later." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ccd60531", + "metadata": { + "vscode": { + "languageId": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "import Std.Arithmetic.*;\n", + "import Std.Math.*;\n", + "import Std.Convert.*;\n", + "\n", + "/// An 8-bit adder with single-qubit rotations.\n", + "/// The rotations introduce T gates via synthesis, which exercises\n", + "/// the magic state factory during resource estimation.\n", + "operation AdderWithRotations() : Unit {\n", + " use a = Qubit[8];\n", + " use b = Qubit[8];\n", + " for i in 0..7 {\n", + " Ry(PI() / IntAsDouble(i + 2), a[i]);\n", + " }\n", + " RippleCarryCGIncByLE(a, b);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "41bfdcd6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namequbitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactories
0Built-in models95670 days 00:00:00.0002250.0073123087648008×T
1Built-in models132210 days 00:00:00.0001410.009416107112150015×T
\n", + "
" + ], + "text/plain": [ + " name qubits runtime error \\\n", + "0 Built-in models 9567 0 days 00:00:00.000225 0.007312 \n", + "1 Built-in models 13221 0 days 00:00:00.000141 0.009416 \n", + "\n", + " physical_compute_qubits physical_factory_qubits physical_memory_qubits \\\n", + "0 3087 6480 0 \n", + "1 1071 12150 0 \n", + "\n", + " factories \n", + "0 8×T \n", + "1 15×T " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "app = QSharpApplication(qdk.code.AdderWithRotations)\n", + "\n", + "# Baseline: built-in architecture, QEC, and factory\n", + "baseline_arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)\n", + "baseline = estimate(\n", + " app, baseline_arch,\n", + " isa_query=SurfaceCode.q() * Litinski19Factory.q(),\n", + " max_error=0.01,\n", + " name=\"Built-in models\",\n", + ")\n", + "baseline.add_qubit_partition_column()\n", + "baseline.add_factory_summary_column()\n", + "baseline.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "ef447c3b", + "metadata": {}, + "source": [ + "## Building a custom architecture model\n", + "\n", + "An `Architecture` defines the physical instruction set of a quantum computer:\n", + "the native operations the hardware can perform, each with a gate time and error\n", + "rate.\n", + "\n", + "The built-in `GateBased` architecture uses a single error rate for all gates.\n", + "In practice, two-qubit gates are typically noisier and slower than single-qubit\n", + "gates, and measurement fidelities may differ from gate fidelities. Let's build\n", + "a more flexible architecture that models these differences.\n", + "\n", + "To implement a custom architecture, subclass `Architecture` and implement one\n", + "method: `provided_isa`. This method receives an `ISAContext` and uses it to\n", + "register each physical instruction." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5e98f44a", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class ParameterizedArchitecture(Architecture):\n", + " \"\"\"\n", + " A configurable architecture with separate timing and error parameters for\n", + " single-qubit gates, two-qubit gates, and measurements.\n", + "\n", + " Unlike the built-in ``GateBased`` model (which uses one error rate for all\n", + " operations), this model lets you specify different error rates for each gate\n", + " class, reflecting hardware where two-qubit gates are noisier than\n", + " single-qubit gates.\n", + " \"\"\"\n", + "\n", + " _: KW_ONLY\n", + " single_qubit_gate_time: int\n", + " two_qubit_gate_time: int\n", + " measurement_time: int\n", + " single_qubit_error_rate: float\n", + " two_qubit_error_rate: float\n", + " measurement_error_rate: float\n", + "\n", + " def provided_isa(self, ctx: ISAContext) -> ISA:\n", + " instructions = []\n", + "\n", + " # Single-qubit gates: Clifford gates and T gates\n", + " for gate_id in [H, S, S_DAG, T, T_DAG]:\n", + " instructions.append(\n", + " ctx.add_instruction(\n", + " gate_id,\n", + " encoding=PHYSICAL,\n", + " arity=1,\n", + " time=self.single_qubit_gate_time,\n", + " error_rate=self.single_qubit_error_rate,\n", + " )\n", + " )\n", + "\n", + " # Measurements\n", + " for meas_id in [MEAS_X, MEAS_Y, MEAS_Z]:\n", + " instructions.append(\n", + " ctx.add_instruction(\n", + " meas_id,\n", + " encoding=PHYSICAL,\n", + " arity=1,\n", + " time=self.measurement_time,\n", + " error_rate=self.measurement_error_rate,\n", + " )\n", + " )\n", + "\n", + " # Two-qubit entangling gates\n", + " for gate_id in [CNOT, CZ]:\n", + " instructions.append(\n", + " ctx.add_instruction(\n", + " gate_id,\n", + " encoding=PHYSICAL,\n", + " arity=2,\n", + " time=self.two_qubit_gate_time,\n", + " error_rate=self.two_qubit_error_rate,\n", + " )\n", + " )\n", + "\n", + " return ctx.make_isa(*instructions)" + ] + }, + { + "cell_type": "markdown", + "id": "ee910f8e", + "metadata": {}, + "source": [ + "### What if my qubits had different characteristics?\n", + "\n", + "Let's explore two hypothetical hardware scenarios:\n", + "\n", + "- **Fast but noisy:** short gate times, higher error rates (reminiscent of\n", + " some superconducting platforms)\n", + "- **Slow but clean:** longer gate times, lower error rates (reminiscent of\n", + " some trapped-ion platforms)\n", + "\n", + "We use the built-in `SurfaceCode` and `Litinski19Factory` transforms so that we\n", + "isolate the effect of the architecture." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b7f6079c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
encodingarityspacetimeerror
id
HPHYSICAL11500.0001
MEAS_XPHYSICAL112000.0005
SPHYSICAL11500.0001
TPHYSICAL11500.0001
MEAS_YPHYSICAL112000.0005
CZPHYSICAL221000.0005
S_DAGPHYSICAL11500.0001
T_DAGPHYSICAL11500.0001
MEAS_ZPHYSICAL112000.0005
CNOTPHYSICAL221000.0005
\n", + "
" + ], + "text/plain": [ + " encoding arity space time error\n", + "id \n", + "H PHYSICAL 1 1 50 0.0001\n", + "MEAS_X PHYSICAL 1 1 200 0.0005\n", + "S PHYSICAL 1 1 50 0.0001\n", + "T PHYSICAL 1 1 50 0.0001\n", + "MEAS_Y PHYSICAL 1 1 200 0.0005\n", + "CZ PHYSICAL 2 2 100 0.0005\n", + "S_DAG PHYSICAL 1 1 50 0.0001\n", + "T_DAG PHYSICAL 1 1 50 0.0001\n", + "MEAS_Z PHYSICAL 1 1 200 0.0005\n", + "CNOT PHYSICAL 2 2 100 0.0005" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fast_noisy = ParameterizedArchitecture(\n", + " single_qubit_gate_time=50,\n", + " two_qubit_gate_time=100,\n", + " measurement_time=200,\n", + " single_qubit_error_rate=1e-4,\n", + " two_qubit_error_rate=5e-4,\n", + " measurement_error_rate=5e-4,\n", + ")\n", + "\n", + "slow_clean = ParameterizedArchitecture(\n", + " single_qubit_gate_time=500,\n", + " two_qubit_gate_time=1000,\n", + " measurement_time=1000,\n", + " single_qubit_error_rate=1e-6,\n", + " two_qubit_error_rate=5e-6,\n", + " measurement_error_rate=1e-5,\n", + ")\n", + "\n", + "# Inspect the physical ISA provided by the fast-but-noisy architecture\n", + "fast_noisy.provided_isa(fast_noisy.context()).as_frame()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "de4ec193", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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rsLCQoWFhXm7HAAAAJzF2bzm8hzbwsJCFRQUOLRFRkbq0KFDKioqcr1SAAAAwA1cDra33367Pvroo3Lt//3vf3X77be7pSgAAADAVS4H21WrVqlXr17l2nv27KlVq1a5pSgAAADAVS4H25KSEp08ebJce2lpqY4dO+aWogAAAABXuRxsO3furDfffLNc++uvv66OHTu6pSgAAADAVS4/7mvixIlKTk7Whg0b1KdPH0nSokWLlJWVpYULF7q9QAAAAMAZLp+xTUpK0ooVKxQbG6v//ve/+vzzz9WiRQtt3LhR3bp180SNAAAAwHm55Tm2l6KMjAxlZGTIZrNp+/btPMcWAACgmnL2ObZOBduioiL7Rs73rNpLLRzyggYAAIDqzdm85tQc24iICOXm5qpBgwaqU6dOhW8YMwxDFotFNput6lUDAAAAVeRUsP3+++8VGRkpSVq8eLFHCwIAAACqosbOsT2NqQgAAADVm7N5zeWnIixYsEDLli2zf8/IyFC7du1055136tChQ1WrFgAAALhALgfbxx57zH4D2aZNmzRmzBj17dtXOTk5GjNmjNsLBAAAAJzh8gsacnJyFBcXJ0maM2eO+vXrp+eee07r1q1T37593V4gAAAA4AyXz9j6+/vr6NGjkqTvvvtOf/7znyVJkZGR530UGAAAAOApLp+xveaaazRmzBglJSVp9erVmj17tiRp+/btiomJcXuBAAAAgDNcPmM7depU1apVS5988ommTZumxo0bS5K+/vprpaamur1AAAAAwBk87ovHfQEAAFRrHnvcV48ePTRr1iwdO3bsggoEAAAA3MnlYNu+fXs9+uijio6O1j333KOVK1d6oi6Py8jIUFxcnBITE71dCgAAANygSlMRTp48qfnz52vmzJn6+uuv1aJFCw0fPlyDBw9WVFSUJ+r0GKYiAAAAVG8em4ogSbVq1dLNN9+szz77THv37tWdd96pp556SrGxserfv7++//77KhcOAAAAVEWVgu1pq1ev1vjx4/Xyyy+rQYMGGjdunOrVq6cbbrhBjz76qLtqBAAAAM7L5akI+fn5eu+99zR9+nTt2LFD/fr10913362UlBRZLBZJ0rJly5Samqri4mKPFO1OTEUAAACo3pzNay6/oCEmJkaXX365hg8frrS0NNWvX79cn/j4eG7KAgAAwEXlcrBdtGiRunXr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OsbSpUvVr18/NWrUSBaLRfPmzauwX0ZGhpo1a6bAwEB16dJFq1evdmsdqJpp06YpPj5eYWFhCgsLU9euXfX111+7dQyOEdc8//zzslgsGj16tFu3y+9gDr///rvuuusu1a1bV0FBQWrbtq3WrFnjtu1znFzaCLYwtSVLlmjEiBFauXKlvv32W5WWlurPf/6zrFZrhf2XL1+u0tLScu1btmzR/v37K1zHarUqISFBGRkZldYxe/ZsjRkzRuPHj9e6deuUkJCglJQU5efn2/u0a9dObdq0KffZt2+fi3sNV8TExOj555/X2rVrtWbNGvXu3Vs33nijfvrppwr7c4x4VlZWlt544w3Fx8efsx+/Q8106NAhJSUlyc/PT19//bW2bNmil19+WRERERX25zipgQygBsnPzzckGUuWLCm3zGazGQkJCcYtt9xinDx50t7+888/G1FRUcYLL7xw3u1LMubOnVuuvXPnzsaIESMcxmrUqJGRnp7uUv2LFy82BgwY4NI6cF1ERITx9ttvl2vnGPGsI0eOGFdccYXx7bffGj169DAefvjhCvvxO9RcTzzxhHHNNdc41ZfjpGbijC1qlMLCQklSZGRkuWU+Pj766quvtH79eg0ZMkRlZWX69ddf1bt3b/Xv31+PP/54lcY8ceKE1q5dq+TkZIexkpOTtWLFiqrtCDzCZrPpo48+ktVqVdeuXcst5xjxrBEjRuj66693+DtUhN+h5po/f746deqkgQMHqkGDBmrfvr3eeuutCvtynNRMtbxdAHCxlJWVafTo0UpKSlKbNm0q7NOoUSN9//336tatm+68806tWLFCycnJmjZtWpXHPXjwoGw2m6Kiohzao6Ki9PPPPzu9neTkZG3YsEFWq1UxMTH6+OOPKwxfcN2mTZvUtWtXHT9+XKGhoZo7d67i4uIq7Msx4hkfffSR1q1bp6ysLKf68zvUTL/99pumTZumMWPG6O9//7uysrI0atQo+fv7a+jQoeX6c5zUPARb1BgjRozQ5s2btWzZsnP2a9Kkid577z316NFDl112md555x1ZLJaLVGXlvvvuO2+XYFqtWrVSdna2CgsL9cknn2jo0KFasmRJpeGWY8S99uzZo4cffljffvutAgMDnV6P36HmKSsrU6dOnfTcc89Jktq3b6/Nmzfr9ddfrzDYShwnNQ1TEVAjjBw5Ul988YUWL16smJiYc/bdv3+/7r33XvXr109Hjx7V3/72twsau169evL19S13o8L+/fsVHR19QduGe/j7+6tFixbq2LGj0tPTlZCQoMmTJ1fan2PEvdauXav8/Hx16NBBtWrVUq1atbRkyRJNmTJFtWrVks1mq3A9foeap2HDhuX+D2fr1q21e/fuStfhOKlZCLYwNcMwNHLkSM2dO1fff/+9mjdvfs7+Bw8eVJ8+fdS6dWt9+umnWrRokWbPnq1HH320yjX4+/urY8eOWrRokb2trKxMixYt4rJTNVVWVqaSkpIKl3GMuF+fPn20adMmZWdn2z+dOnXSoEGDlJ2dLV9f33Lr8DvUTElJSeUe2bh9+3Y1bdq0wv4cJzWQt+9eAzzpgQceMMLDw43MzEwjNzfX/jl69Gi5vjabzejUqZPRt29fo6SkxN6enZ1tREZGGq+88kqFYxw5csRYv369sX79ekOS8corrxjr1683du3aZe/z0UcfGQEBAcaMGTOMLVu2GPfee69Rp04dIy8vz/07DZeMHTvWWLJkiZGTk2Ns3LjRGDt2rGGxWIyFCxeW68sxcvGc76kI/A410+rVq41atWoZzz77rLFjxw7jgw8+MIKDg43333+/XF+Ok5qJYAtTk1ThZ/r06RX2X7hwoXHs2LFy7evWrTP27NlT4TqLFy+ucIyhQ4c69HvttdeMJk2aGP7+/kbnzp2NlStXXujuwQ2GDx9uNG3a1PD39zfq169v9OnTp8JQexrHyMVxrmBrGPwONdnnn39utGnTxggICDCuvPJK480336y0L8dJzWMxDMO4GGeGAQAAAE9iji0AAABMgWALAAAAUyDYAgAAwBQItgAAADAFgi0AAABMgWALAAAAUyDYAgAAwBQItgAAADAFgi0AAABMgWALANVYz549NXr0aK+N3717d3344Yce2/6CBQvUrl07lZWVeWwMADUHwRYAziEtLU0Wi0UWi0V+fn5q3ry5Hn/8cR0/ftyt42RmZspisejw4cMO7Z9++qn++c9/unUsZ82fP1/79+/X7bff7rExUlNT5efnpw8++MBjYwCoOQi2AHAeqampys3N1W+//aZXX31Vb7zxhsaPH39Rxo6MjFTt2rUvylhnmzJlioYNGyYfH8/+qyItLU1Tpkzx6BgAagaCLQCcR0BAgKKjoxUbG6v+/fsrOTlZ3377rX15s2bNNGnSJId12rVrpwkTJti/WywWvf3227rpppsUHBysK664QvPnz5ck7dy5U7169ZIkRUREyGKxKC0tTVL5qQjNmjXTxIkTNWTIEIWGhqpp06aaP3++Dhw4oBtvvFGhoaGKj4/XmjVrHOpZtmyZunXrpqCgIMXGxmrUqFGyWq2V7vOBAwf0/fffq1+/fg7tFotFb7zxhm644QYFBwerdevWWrFihX755Rf17NlTISEhuvrqq/Xrr7/a19mwYYN69eql2rVrKywsTB07dnSor1+/flqzZo3DOgBQFQRbAHDB5s2b9eOPP8rf39/ldZ955hndeuut2rhxo/r27atBgwapoKBAsbGxmjNnjiRp27Ztys3N1eTJkyvdzquvvqqkpCStX79e119/vQYPHqwhQ4borrvu0rp163T55ZdryJAhMgxDkvTrr78qNTVVAwYM0MaNGzV79mwtW7ZMI0eOrHSMZcuW2YPr2f75z39qyJAhys7O1pVXXqk777xT9913n8aNG6c1a9bIMAyHbQ8aNEgxMTHKysrS2rVrNXbsWPn5+dmXN2nSRFFRUfrhhx9c/psCwJkItgBwHl988YVCQ0MVGBiotm3bKj8/X4899pjL20lLS9Mdd9yhFi1a6LnnnlNxcbFWr14tX19fRUZGSpIaNGig6OhohYeHV7qdvn376r777tMVV1yhp59+WkVFRUpMTNTAgQPVsmVLPfHEE9q6dav2798vSUpPT9egQYM0evRoXXHFFbr66qs1ZcoUzZo1q9K5wrt27VJUVFSF0xCGDRumW2+91T7Wzp07NWjQIKWkpKh169Z6+OGHlZmZae+/e/duJScn68orr9QVV1yhgQMHKiEhwWGbjRo10q5du1z9kwKAA4ItAJxHr169lJ2drVWrVmno0KEaNmyYBgwY4PJ24uPj7f8cEhKisLAw5efnX9B2oqKiJElt27Yt13Z62xs2bNCMGTMUGhpq/6SkpKisrEw5OTkVjnHs2DEFBgZWefzjx4+rqKhIkjRmzBjdfffdSk5O1vPPP1/hlIOgoCAdPXr0/DsPAOdAsAWA8wgJCVGLFi2UkJCgd999V6tWrdI777xjX+7j42O/7H9aaWlpue2cefldOjVftSqPuTpzOxaLpdK209suLi7Wfffdp+zsbPtnw4YN2rFjhy6//PIKx6hXr54OHTrklvEnTJign376Sddff72+//57xcXFae7cuQ7bLCgoUP369Z3YewCoHMEWAFzg4+Ojv//973ryySd17NgxSVL9+vWVm5tr71NUVFTpmdDKnJ6za7PZ3Ffs/+nQoYO2bNmiFi1alPtUNle4ffv2ysvLqzTcuqply5b629/+poULF+rmm2/W9OnT7cuOHz+uX3/9Ve3bt3fLWABqLoItALho4MCB8vX1VUZGhiSpd+/eeu+99/TDDz9o06ZNGjp0qHx9fV3aZtOmTWWxWPTFF1/owIEDKi4udlu9TzzxhH788UeNHDlS2dnZ2rFjhz777LNz3jzWvn171atXT8uXL7+gsY8dO6aRI0cqMzNTu3bt0vLly5WVleVwU9rKlSsVEBCgrl27XtBYAECwBQAX1apVSyNHjtSLL74oq9WqcePGqUePHrrhhht0/fXXq3///pVe4q9M48aN9cwzz2js2LGKioo6Z+h0VXx8vJYsWaLt27erW7duat++vZ5++mk1atSo0nV8fX01bNiwC35xgq+vr/744w8NGTJELVu21K233qrrrrtOzzzzjL3Pf/7zHw0aNEjBwcEXNBYAWIyzJ4YBACApLy9Pf/rTn7Ru3To1bdrUI2McPHhQrVq10po1a9S8eXOPjAGg5uCMLQCgQtHR0XrnnXe0e/duj42xc+dO/fvf/ybUAnALztgCAADAFDhjCwAAAFMg2AIAAMAUCLYAAAAwBYItAAAATIFgCwAAAFMg2AIAAMAUCLYAAAAwBYItAAAATIFgCwAAAFP4/6B/FjWe/X9dAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isa_query = SurfaceCode.q() * Litinski19Factory.q()\n", + "\n", + "results = [\n", + " estimate(app, fast_noisy, isa_query, max_error=0.01, name=\"Fast but noisy\"),\n", + " estimate(app, slow_clean, isa_query, max_error=0.01, name=\"Slow but clean\"),\n", + " baseline,\n", + "]\n", + "\n", + "plot_estimates(results, figsize=(8, 5), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "f4cd8012", + "metadata": {}, + "source": [ + "## Building a custom QEC ISA transform\n", + "\n", + "An `ISATransform` maps one instruction set to another. A QEC transform\n", + "consumes physical instructions and produces logical instructions by encoding\n", + "qubits in an error-correcting code.\n", + "\n", + "The key relationships in any QEC code are:\n", + "\n", + "- **Space**: How many physical qubits per logical qubit? (Depends on code\n", + " distance.)\n", + "- **Time**: How long does one logical operation take? (Depends on syndrome\n", + " extraction cycles.)\n", + "- **Error**: What is the logical error rate? (Depends on physical error rate,\n", + " code distance, and the code's threshold.)\n", + "\n", + "To implement a QEC transform, subclass `ISATransform` and implement two\n", + "methods:\n", + "\n", + "1. `required_isa`: a **static method** that declares what physical\n", + " instructions the transform needs. Because it is static, it cannot reference\n", + " instance fields (such as the error-correction threshold); instead, you\n", + " specify fixed bounds using `ConstraintBound`. `ConstraintBound` supports\n", + " comparisons like `.lt(value)` (less than), `.le(value)`, `.eq(value)`,\n", + " `.gt(value)`, and `.ge(value)`.\n", + "2. `provided_isa`: computes the logical instruction properties from the\n", + " physical ones and yields one or more ISAs.\n", + "\n", + "The `distance` field uses `metadata={\"domain\": range(3, 22, 2)}` to tell the\n", + "estimator to automatically search over odd distances from 3 to 21. The\n", + "estimator evaluates all distances and keeps only the Pareto-optimal ones." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3f829830", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class GenericQEC(ISATransform):\n", + " \"\"\"\n", + " A configurable QEC code model with tunable threshold, overhead,\n", + " and error suppression.\n", + "\n", + " This transform consumes physical H, CNOT, and MEAS_Z instructions and\n", + " produces a logical LATTICE_SURGERY instruction. The resource formulas\n", + " are parameterized so you can model different QEC code families.\n", + "\n", + " The logical error rate follows:\n", + "\n", + " error = crossing_prefactor × (p_physical / threshold) ^ ⌊(d+1)/2⌋\n", + "\n", + " Space per data qubit is:\n", + "\n", + " qubits_per_data_qubit × d²\n", + " \"\"\"\n", + "\n", + " crossing_prefactor: float = 0.03\n", + " error_correction_threshold: float = 0.01\n", + " qubits_per_data_qubit: int = 2\n", + " syndrome_extraction_depth: int = 4\n", + " _: KW_ONLY\n", + " distance: int = field(default=3, metadata={\"domain\": range(3, 22, 2)})\n", + "\n", + " @staticmethod\n", + " def required_isa() -> ISARequirements:\n", + " # required_isa is static, so we cannot reference instance fields here.\n", + " # Instead, we use ConstraintBound to express fixed constraints on the\n", + " # implementation ISA. ConstraintBound supports .lt(), .le(), .eq(),\n", + " # .gt(), and .ge() comparisons.\n", + " return ISARequirements(\n", + " constraint(H, error_rate=ConstraintBound.lt(0.01)),\n", + " constraint(CNOT, arity=2, error_rate=ConstraintBound.lt(0.01)),\n", + " constraint(MEAS_Z, error_rate=ConstraintBound.lt(0.01)),\n", + " )\n", + "\n", + " def provided_isa(\n", + " self, impl_isa: ISA, ctx: ISAContext\n", + " ) -> Generator[ISA, None, None]:\n", + " cnot = impl_isa[CNOT]\n", + " h = impl_isa[H]\n", + " meas_z = impl_isa[MEAS_Z]\n", + "\n", + " # Physical error rate is the worst case across all required gates\n", + " physical_error_rate = max(\n", + " cnot.expect_error_rate(),\n", + " h.expect_error_rate(),\n", + " meas_z.expect_error_rate(),\n", + " )\n", + "\n", + " d = self.distance\n", + "\n", + " # Space: physical qubits per data qubit, scaled by distance².\n", + " #\n", + " # Because LATTICE_SURGERY has variable arity (it can operate on any\n", + " # number of logical qubits), space and error_rate must be provided as\n", + " # functions of arity rather than as fixed numbers. The estimator\n", + " # provides several helpers for this:\n", + " #\n", + " # linear_function(slope) → f(n) = slope × n\n", + " # constant_function(value) → f(n) = value\n", + " # block_linear_function(k, s, o) → f(n) = s × ⌈n/k⌉ + o\n", + " # generic_function(callable) → f(n) = callable(n)\n", + " #\n", + " # For QEC codes, resources scale linearly with the number of logical\n", + " # qubits, so linear_function is the natural choice.\n", + " space = linear_function(self.qubits_per_data_qubit * d**2)\n", + "\n", + " # Time: syndrome extraction cycle × code distance\n", + " code_cycle_time = (\n", + " h.expect_time()\n", + " + self.syndrome_extraction_depth * cnot.expect_time()\n", + " + meas_z.expect_time()\n", + " )\n", + " time = code_cycle_time * d\n", + "\n", + " # Error: exponential suppression below threshold\n", + " logical_error = self.crossing_prefactor * (\n", + " (physical_error_rate / self.error_correction_threshold)\n", + " ** ((d + 1) // 2)\n", + " )\n", + " error = linear_function(logical_error)\n", + "\n", + " yield ctx.make_isa(\n", + " ctx.add_instruction(\n", + " LATTICE_SURGERY,\n", + " encoding=LOGICAL,\n", + " arity=None,\n", + " space=space,\n", + " time=time,\n", + " error_rate=error,\n", + " # transform=self records that this instruction was produced by\n", + " # this QEC transform instance, and source=[...] records which\n", + " # physical instructions it was derived from. Together, they\n", + " # form the provenance chain that you can inspect in the\n", + " # estimation results (see notebook 2).\n", + " transform=self,\n", + " source=[cnot, h, meas_z],\n", + " # Extra keyword arguments (like distance) are stored as\n", + " # properties on the instruction. This lets you retrieve the\n", + " # code distance used for each Pareto-optimal result when\n", + " # analysing the estimation output. Further, the property can be\n", + " # required and read by a parent ISATransform to create new\n", + " # logical instructions based on this one.\n", + " distance=d,\n", + " ),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "2981ec35", + "metadata": {}, + "source": [ + "### What if we had a better QEC code?\n", + "\n", + "Let's compare two configurations:\n", + "\n", + "- **Surface-code-like:** threshold 1%, prefactor 0.03, ~2d² physical qubits\n", + " per logical qubit (the `GenericQEC` defaults).\n", + "- **Optimistic code:** threshold 3%, prefactor 0.01, ~d² qubits\n", + " per logical qubit (a hypothetical code with better parameters).\n", + "\n", + "We use the built-in `GateBased` architecture and `Litinski19Factory` to isolate\n", + "the effect of the QEC code. Because `GenericQEC` is a dataclass, we can pass\n", + "the non-default parameters directly to `.q()`. The estimator fixes those values\n", + "while still searching over the `distance` hyperparameter." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "175568cb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAs4AAAHCCAYAAAD/xk/KAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAATTdJREFUeJzt3Xt8z/X///H7e+O988aYzeyEIZJNTiEJK1Q++KgUn4wKFeUQRZ9C3/iITlI+dHAopVRCnw6iOYscR+WsOe9AY7ODme31+8PPO2/beL3Zem92u14u78ul9/P5ej2fj/fbxbp77fl8vSyGYRgCAAAAcEUuzi4AAAAAKAsIzgAAAIAJBGcAAADABIIzAAAAYALBGQAAADCB4AwAAACYQHAGAAAATKjg7AJuZPn5+Tp+/Lh8fHxksVicXQ4AAAAuYxiGzpw5o+DgYLm4XPmaMsG5BB0/flyhoaHOLgMAAABXceTIEYWEhFzxGIJzCfLx8ZF04Q/C19fXydUAAADgcunp6QoNDbXltishOJegi8szfH19Cc4AAAClmJlltWwOBAAAAEwgOAMAAAAmEJwBAAAAE1jjDABAGZeXl6fc3FxnlwGUShUrVpSrq2uxjEVwBgCgjDIMQ0lJSTp9+rSzSwFKtUqVKikoKOi6n6tBcAYAoIy6GJqrVasmT09PHrYFXMYwDGVlZSklJUWSVL169esaj+AMAEAZlJeXZwvNVapUcXY5QKnl4eEhSUpJSVG1atWua9kGmwMBACiDLq5p9vT0dHIlQOl38e/J9e4FIDgDAFCGsTwDuLri+ntCcAYAAABMIDjDYelnc5WYll1oX2JattLPckskAABw4yE4wyHpZ3MVO2ujer63QcdP24fn46ez1fO9DYqdtZHwDABwmvfff1+hoaFycXHRlClTnF1Osbrzzjs1dOjQ6xpj3Lhxio6Otr3v27evunXrVqxz3KgIznBIZs55/ZlxTodTs/TQ+3+F5+Ons/XQ+xt0ODVLf2acU2bOeSdXCgC4Gmf9BvHEiRN68sknFRYWJjc3NwUFBaljx45at27ddY+dnp6uwYMH6/nnn9exY8c0YMCAYqj4xvb2229rzpw5JT7PRx99pGbNmsnT01M+Pj5q27atvv32W7tjVq5cKYvFUugrKSnJdlx6err+/e9/66abbpK7u7uCgoIUExOjr7/+WoZhlNhnIDjDIdX9PDSzX0OFVM2xhecth1JtoTmkao5m9muo6n4ezi4VAHAFzvwNYo8ePbRt2zZ99NFH2rt3r7755hvdeeed+vPPP695TMMwdP78eR0+fFi5ubm69957Vb16de46YoKfn58qVapUonOMGDFCAwcOVM+ePbVjxw5t3LhRt99+u7p27ap33323wPF79uxRYmKi3atatWqSpNOnT6tVq1b6+OOPNXr0aG3dulWrV69Wz5499dxzzyktLa3EPgfBGQ45c+6Mxm0aKs/w923hucf09bbQ7Bn+vsZtGqoz5844u1QAwBU46zeIp0+f1po1azRp0iS1a9dO4eHhat68uUaPHq1//OMfkqSDBw/KYrEoPj7e7jyLxaKVK1dK+uvK5A8//KAmTZrIzc1Nn3zyiW655RZJUq1atWSxWHTw4EEdOHBAXbt2VWBgoLy9vdWsWTP99NNPdnXl5OTo+eefV2hoqNzc3BQZGamZM2fa+n/77Td17txZ3t7eCgwM1COPPKKTJ09e8bNebcxVq1apefPmcnNzU/Xq1TVq1CidP//X952Zmak+ffrI29tb1atX1xtvvFHoHCNGjFCNGjXk5eWlFi1a2L4jsy5fqnG57777Tn5+fvr0008lSUeOHNGDDz6oSpUqyd/fX127dtXBgweLPH/Dhg1644039Nprr2nEiBGKjIxU/fr1NWHCBA0dOlTDhw/XkSNH7M6pVq2agoKC7F4uLhdi6wsvvKCDBw/ql19+UWxsrBo0aKC6deuqf//+io+Pl7e3t0Of3xEEZzgkMzdTqdmpSsw6Jmvoe7JUOC1JslQ4LWvoe0rMOqbU7FRl5mY6t1AAwBVV9/PQ5wNuU5i/Z6G/QQzz99TnA24r9t8gent7y9vbW4sWLVJOTs51jzdq1Ci9+uqr2rVrl+666y5bIN64caMSExMVGhqqjIwM3XPPPYqLi9O2bdvUqVMndenSRYcPH7aN06dPH3322WeaOnWqdu3apffee88WwE6fPq327durcePG2rx5s5YsWaLk5GQ9+OCDV6ztSmMeO3ZM99xzj5o1a6bt27dr+vTpmjlzpsaPH287f+TIkVq1apUWL16spUuXauXKldq6davdHIMHD9b69ev1+eefa8eOHXrggQfUqVMn7du377q/W0maN2+eHn74YX366afq3bu3cnNz1bFjR/n4+GjNmjVat26dvL291alTJ507d67QMT777DN5e3tr4MCBBfqeffZZ5ebmasGCBabqyc/P1+eff67evXsrODi4QL+3t7cqVCjB5/sZKDFpaWmGJCMtLc3ZpRSrxIxE464vOhoN5zQ0Grx/h1Fz3DSjwft3GA3nNDTu+qKjkZiR6OwSAeCGl52dbezcudPIzs6+rnGOncoy2kxaboQ//63t1WbScuPYqaxiqrSgr776yqhcubLh7u5utGrVyhg9erSxfft2W39CQoIhydi2bZut7dSpU4YkY8WKFYZhGMaKFSsMScaiRYvsxt62bZshyUhISLhiDTfffLPxzjvvGIZhGHv27DEkGcuWLSv02FdeecW4++677dqOHDliSDL27NlT6DlXG/OFF14w6tWrZ+Tn59vapk2bZnh7ext5eXnGmTNnDKvVanzxxRe2/j///NPw8PAwhgwZYhiGYRw6dMhwdXU1jh07Zjd2hw4djNGjRxf52ceOHWtERUXZ3sfGxhpdu3a1vW/btq0xZMgQ49133zX8/PyMlStX2vrmzp1boO6cnBzDw8PD+PHHHwudr1OnTnbzXc7X19d48sknDcP468/Vy8vL7tWgQQPDMAwjOTnZkGS8+eabRY5XmCv9fXEkr/HIbTgsP9dPWYcGKN/vbblYU+UVMf1C+zl/ZZ0YoPxcPydXCAAwK7iSh97qGaUe09fb2t7qGaXgSiW3V6VHjx669957tWbNGm3YsEE//PCDJk+erA8//FB9+/Z1aKymTZte9ZiMjAyNGzdO3333nRITE3X+/HllZ2fbrjjHx8fL1dVVbdu2LfT87du3a8WKFYUuAThw4IA2bdpkdzX1hx9+UGJi4hXH3LVrl1q2bGn3YI7WrVsrIyNDR48e1alTp3Tu3Dm1aNHC1u/v76969erZ3v/666/Ky8tT3bp17cbOycmxPYb90pr/9a9/acaMGUV+T5f66quvlJKSonXr1qlZs2Z238X+/fvl4+Njd/zZs2d14MCBIsczrrJhz2q12r1fs2aN3RwVK1Y0NU5JIzjDIYlpF9a+HU11U7BrH53xn2Lr88voo6Mn3fTQ+xs0f2Dx/3oPAFD8jp/O1rD52+3ahs3frs8H3Fai4dnd3V133XWX7rrrLr300kt6/PHHNXbsWPXt29e2lvXSkFTUo5K9vLyuOteIESO0bNkyvf7664qMjJSHh4fuv/9+29ICD48rf86MjAx16dJFkyZNKtBXvXp15efn2wXcGjVqFFhDXRIyMjLk6uqqLVu2yNXV1a7vYmC+dJ24r6+v6bEbN26srVu3atasWWratKkt4GdkZKhJkya29c6XCggIKHSsOnXqaO3atTp37lyBgHz8+HGlp6cXCP81a9YsdMNiQECAKlWqpN27d5v+LMWJNc5wiJdbBVXxtiqkao68Q7606/MO+VIhVXNUxdsqLzf+TQYApd2lGwHD/D214MmWdmueL7/bRklq0KCBMjMv7I+5GMASExNt/ZcGQEetW7dOffv2Vffu3XXLLbcoKCjIbjPbLbfcovz8fK1atarQ82+99Vb9/vvvioiIUGRkpN3Ly8tLPj4+dm0eHh5XHbN+/fpav3693T8O1q1bJx8fH4WEhKh27dqqWLGifvnlF1v/qVOntHfvXtv7xo0bKy8vTykpKQXqCgoKkiS7tot3pTCjdu3aWrFihRYvXqynn37a7rvYt2+fqlWrVmBOP7/Cf+P88MMPKyMjQ++9916Bvtdff13u7u7q2bOnqbpcXFz00EMP6dNPP9Xx48cL9GdkZNhtsCxuBGc4xNe9oiY/FCbP8PeVmHVMId4hmtt5rkK8Q5SYdUye4e9r8kNh8nWv6OxSAQBXcPE3iJduBGwS7l9gw2BR93m+Vn/++afat2+vTz75RDt27FBCQoK+/PJLTZ48WV27dpV04QrwbbfdZtv0t2rVKr344ovXPGedOnX09ddfKz4+Xtu3b1evXr2Un59v64+IiFBsbKweffRRLVq0SAkJCVq5cqW++OILSdKgQYOUmpqqhx9+WJs2bdKBAwf0448/ql+/fsrLyyt0zquN+dRTT+nIkSN6+umntXv3bi1evFhjx47V8OHD5eLiIm9vbz322GMaOXKkli9frt9++83uarwk1a1bV71791afPn309ddfKyEhQRs3btTEiRP13XffXfP3den4K1as0IIFC2wPROndu7eqVq2qrl27as2aNbbP9cwzz+jo0aOFjtOyZUsNGTJEI0eO1BtvvKEDBw5o9+7devHFFzV16lR98MEHtqUlF6WkpCgpKcnudfG3DhMmTFBoaKhatGihjz/+WDt37tS+ffs0a9YsNW7cWBkZGdf92Yvk0MpqOORG3ByYmJFodPqqk9FwTkOj01edbBsBi2oHAJSM690cmJZ9zug2bW2hGwEvbhjsNm2tkZZ9rjjKtTl79qwxatQo49ZbbzX8/PwMT09Po169esaLL75oZGX9VcfOnTuNli1bGh4eHkZ0dLSxdOnSQjcHnjp1ym78wjYHJiQkGO3atTM8PDyM0NBQ491337VtgLsoOzvbGDZsmFG9enXDarUakZGRxqxZs2z9e/fuNbp3725UqlTJ8PDwMG666SZj6NChdpvkLne1MVeuXGk0a9bMsFqtRlBQkPH8888bubm5tv4zZ84Y//rXvwxPT08jMDDQmDx5coG6z507Z4wZM8aIiIgwKlasaFSvXt3o3r27sWPHjiLrMrs58KKdO3ca1apVM4YPH24YhmEkJiYaffr0MapWrWq4ubkZtWrVMvr373/VvDNz5kyjSZMmhru7uyHJsFqtxqpVq+yOufjnWthr/fr1tuNOnz5tjBo1yqhTp45htVqNwMBAIyYmxli4cGGhfybFtTnQYhhOXmV9A0tPT5efn5/S0tIcWldUmp05d0ZP/PSEUrNTNbvTbAV5Bdn6kjKT1G9JP/l7+GtGzAz5WH2uMBIA4HqcPXtWCQkJqlmzptzd3a9pjPSzucrMOV/onpTEtGx5uVXgN4goEQcPHlTbtm3VsmVLffrppwXWaBe3K/19cSSvsRAVDvGx+mhGzAxl5mbahWZJCvIK0uxOs+VV0YvQDABlgK97xSKDMRu8UZIiIiK0cuVKffTRR4qPj1eTJk2cXZIpBGc4zMfqU2QwvjxMAwAAFKZmzZoaN26cs8twCJsDAQAAABMIzgAAAIAJBGcAAADABIIzAAAAYALBGQAAADCB4AwAAACYQHAGAAA3nHHjxik6Ovq6xjh48KAsFovi4+OveYy+ffuqW7du11UHSg+CMwAA+NsdOXJEjz76qIKDg2W1WhUeHq4hQ4bozz//dHgsi8WiRYsW2bWNGDFCcXFx11VjaGioEhMT1bBhw6seW1TIfvvttzVnzpzrqsPsd3XnnXfKYrEUeD3xxBN2x61YsUL33HOPqlSpIk9PTzVo0EDPPvusjh07dl11lgcEZwAAyquzaVJaEWEp7diF/hLwxx9/qGnTptq3b58+++wz7d+/XzNmzFBcXJxatmyp1NTU657D29tbVapUua4xXF1dFRQUpAoVrv15cX5+fqpUqdI1n+/od9W/f38lJibavSZPnmzrf++99xQTE6OgoCAtWLBAO3fu1IwZM5SWlqY33njjmussNwyUmLS0NEOSkZaW5uxSAAA3mOzsbGPnzp1Gdnb2NQ5w2jA+6GAYUxoZxukj9n2nj1xo/6DDheOKWadOnYyQkBAjKyvLrj0xMdHw9PQ0nnjiCVtbeHi48X//93/GQw89ZHh6ehrBwcHGu+++a9cvyfYKDw83DMMwxo4da0RFRdmOi42NNbp27WpMmDDBqFatmuHn52e8/PLLRm5urjFixAijcuXKRo0aNYxZs2bZzklISDAkGdu2bTMMwzBSU1ONXr16GVWrVjXc3d2NyMhI2/GX1iDJaNu2rd28F+Xl5RmTJk0yateubVitViM0NNQYP358sXxXbdu2NYYMGVLkWEeOHDGsVqsxdOjQQvtPnTpV5Lll3ZX+vjiS17jiDABAeZSTIWWekE4dlObcK6UdvdCedvTC+1MHL/TnZBTrtKmpqfrxxx/11FNPycPDw64vKChIvXv31vz582UYhq39tddeU1RUlLZt26ZRo0ZpyJAhWrZsmSRp06ZNkqTZs2crMTHR9r4wy5cv1/Hjx7V69Wq9+eabGjt2rO677z5VrlxZv/zyi5544gkNHDhQR48eLfT8l156STt37tQPP/ygXbt2afr06apataokaePGjZKkn376SYmJifr6668LHWP06NF69dVXbWPNmzdPgYGBxfZdXcmXX36pc+fO6bnnniu0/3qujJcXBGcAAMojvxpS3++kyhF/hefDv/wVmitHXOj3q1Gs0+7bt0+GYah+/fqF9tevX1+nTp3SiRMnbG2tW7fWqFGjVLduXT399NO6//779dZbb0mSAgICJF0IfUFBQbb3hfH399fUqVNVr149Pfroo6pXr56ysrL0wgsvqE6dOho9erSsVqvWrl1b6PmHDx9W48aN1bRpU0VERCgmJkZdunSxq6NKlSoKCgqSv79/gfPPnDmjt99+W5MnT1ZsbKxq166t22+/XY8//nixfVf//e9/5e3tbff69NNPbeP5+vqqevXqRX5HuLJrX7QDAADKNr+QC+H4YliedfeFdltoDimxqc1eJZWkli1bFng/ZcoUh+e8+eab5eLy1zXDwMBAu41/rq6uqlKlilJSUgo9/8knn1SPHj20detW3X333erWrZtatWplev5du3YpJydHHTp0cKjuq31XVqvV9t+9e/fWv//9b7v+i1e0DcOQxWJxaG7Y44ozAADlmV+I1P19+7bu75dYaI6MjJTFYtGuXbsK7d+1a5cqV658xSvH16pixYp27y0WS6Ft+fn5hZ7fuXNnHTp0SMOGDdPx48fVoUMHjRgxwvT8ly+3uBoz31VAQIDdEgs/Pz9FRkbavXx8fCRJdevWVVpamhITEx2qA38hOAMAUJ6lHZUWDrBvWzjgrzXPxaxKlSq666679N///lfZ2dl2fUlJSfr000/Vs2dPuyujGzZssDtuw4YNdssXKlasqLy8vBKp93IBAQGKjY3VJ598oilTpuj99y/8o+PiVd8r1VGnTh15eHiYvk2eme+qb9++pmu///77ZbVa7e6ycanTp0+bHqu8IjgDAFBeXboRsHKE9OhS+zXPJRSe3333XeXk5Khjx45avXq1jhw5oiVLluiuu+5SjRo1NGHCBLvj161bp8mTJ2vv3r2aNm2avvzySw0ZMsTWHxERobi4OCUlJenUqVMlUrMkjRkzRosXL9b+/fv1+++/69tvv7UF+GrVqsnDw0NLlixRcnKy0tIK3srP3d1dzz//vJ577jl9/PHHOnDggDZs2KCZM2cWOeeVvqu6detqzJgxdsdnZWUpKSnJ7nXxOwkNDdVbb72lt99+W4899phWrVqlQ4cOad26dRo4cKBeeeWVYvy2bkwEZwAAyqO0YwU3Aoa1KLhhsKj7PF+HOnXqaPPmzapVq5YefPBB1a5dWwMGDFC7du20fv36Ahvrnn32WW3evFmNGzfW+PHj9eabb6pjx462/jfeeEPLli1TaGioGjduXOz1XmS1WjV69Gg1atRId9xxh1xdXfX5559LkipUqKCpU6fqvffeU3BwsLp27VroGC+99JKeffZZjRkzRvXr11fPnj2LXFMtXfiuNm3aZPuuwsPD1blzZ9WtW1fr1q2Tt7e33fEffPCBqlevbvd6+OGHbf1PPfWUli5dqmPHjql79+666aab9Pjjj8vX19ehZSfllcVwZHU+HJKeni4/Pz+lpaXJ19fX2eUAAG4gZ8+eVUJCgmrWrCl3d/drGCBN+qTHhVvOXb4R8OKVaK8A6V8LJHe/4ivcQRERERo6dKiGDh3qtBpKm7Fjx+rNN9/UsmXLdNtttzm7nDLhSn9fHMlr3FUDAIDyyN3vQijOySh4yzm/EKnv95Kbt1NDMwr38ssvKyIiQhs2bFDz5s3t7hSCkkVwBgCgvHL3KzoYF/P9m1G8+vXr5+wSyiWCMwAAKLUOHjzo7BIAG67tAwAAACYQnAEAAAATCM4AAACACQRnAAAAwASCMwAAAGACwRkAAAAwgeAMAADKpDlz5qhSpUq29+PGjVN0dLTD46xcuVIWi0WnT58uttpK0rXUGxERoSlTppRYTeUFwRkAAPyt+vbtK4vFYntVqVJFnTp10o4dOxwap2fPntq7d+8V5+nWrdtVx2nVqpUSExPl58dTEnFlBGcAAMqpM+fOKCkzqdC+pMwknTl3psTm7tSpkxITE5WYmKi4uDhVqFBB9913n0NjeHh4qFq1atddi9VqVVBQkCwWy3WPhRsbwRkAgHLozLkzeuKnJ9RvSb8C4TkpM0n9lvTTEz89UWLh2c3NTUFBQQoKClJ0dLRGjRqlI0eO6MSJE5IKX44QHx8vi8Vie5rg5Us1LjVu3Dh99NFHWrx4se3K9sqVKws99vK5Lo77448/qn79+vL29rYF/aJcHOPHH39U48aN5eHhofbt2yslJUU//PCD6tevL19fX/Xq1UtZWVm283JycvTMM8+oWrVqcnd31+23365NmzbZjf3999+rbt268vDwULt27Qp9muLatWvVpk0beXh4KDQ0VM8884wyMzMLrdUwDI0bN05hYWFyc3NTcHCwnnnmmSI/G/5CcAYAoBzKzM1UanaqjmYctQvPF0Pz0YyjSs1OVWZu4eGrOGVkZOiTTz5RZGSkqlSpUixjjhgxQg8++KDdle1WrVqZPj8rK0uvv/665s6dq9WrV+vw4cMaMWLEVc8bN26c3n33Xf388886cuSIHnzwQU2ZMkXz5s3Td999p6VLl+qdd96xHf/cc89pwYIF+uijj7R161ZFRkaqY8eOSk1NlSQdOXJE//znP9WlSxfFx8fr8ccf16hRo+zmPHDggDp16qQePXpox44dmj9/vtauXavBgwcXWuOCBQv01ltv6b333tO+ffu0aNEi3XLLLaa/m/KM4AwAQDkU5BWk2Z1mK8Q7xBae41PibaE5xDtEszvNVpBXUInM/+2338rb21ve3t7y8fHRN998o/nz58vFpXiiibe3tzw8POyubFutVtPn5+bmasaMGWratKluvfVWDR48WHFxcVc9b/z48WrdurUaN26sxx57TKtWrdL06dPVuHFjtWnT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5Ur9+OOPBY7l50YJcvZtPQCgvJFU6Gv27NmFHr906VIjOzu7QPvWrVuNI0eOFHrOihUrCp0jNjbW7rh33nnHCAsLM6xWq9G8eXNjw4YN1/vxAJSARx991AgPDzesVqsREBBgdOjQwVi6dGmRx/Nzo2RYDMMw/v64DgAAAJQtrHEGAAAATCA4AwAAACYQnAEAAAATCM4AAACACQRnAAAAwASCMwAAAGACwRkAAAAwgeAMAAAAmEBwBgAAAEwgOANAOXXnnXdq6NChTpv/jjvu0Lx580ps/CVLlig6Olr5+fklNgeA8oXgDABO0rdvX1ksFlksFlWsWFE1a9bUc889p7NnzxbrPCtXrpTFYtHp06ft2r/++mu98sorxTqXWd98842Sk5P10EMPldgcnTp1UsWKFfXpp5+W2BwAyheCMwA4UadOnZSYmKg//vhDb731lt577z2NHTv2b5nb399fPj4+f8tcl5s6dar69esnF5eS/d9Q3759NXXq1BKdA0D5QXAGACdyc3NTUFCQQkND1a1bN8XExGjZsmW2/oiICE2ZMsXunOjoaI0bN8723mKx6MMPP1T37t3l6empOnXq6JtvvpEkHTx4UO3atZMkVa5cWRaLRX379pVUcKlGRESExo8frz59+sjb21vh4eH65ptvdOLECXXt2lXe3t5q1KiRNm/ebFfP2rVr1aZNG3l4eCg0NFTPPPOMMjMzi/zMJ06c0PLly9WlSxe7dovFovfee0/33XefPD09Vb9+fa1fv1779+/XnXfeKS8vL7Vq1UoHDhywnbN9+3a1a9dOPj4+8vX1VZMmTezq69KlizZv3mx3DgBcK4IzAJQSv/32m37++WdZrVaHz3355Zf14IMPaseOHbrnnnvUu3dvpaamKjQ0VAsWLJAk7dmzR4mJiXr77beLHOett95S69attW3bNt1777165JFH1KdPH/3rX//S1q1bVbt2bfXp00eGYUiSDhw4oE6dOqlHjx7asWOH5s+fr7Vr12rw4MFFzrF27VpbML7cK6+8oj59+ig+Pl433XSTevXqpYEDB2r06NHavHmzDMOwG7t3794KCQnRpk2btGXLFo0aNUoVK1a09YeFhSkwMFBr1qxx+DsFgMsRnAHAib799lt5e3vL3d1dt9xyi1JSUjRy5EiHx+nbt68efvhhRUZG6j//+Y8yMjK0ceNGubq6yt/fX5JUrVo1BQUFyc/Pr8hx7rnnHg0cOFB16tTRmDFjlJ6ermbNmumBBx5Q3bp19fzzz2vXrl1KTk6WJE2cOFG9e/fW0KFDVadOHbVq1UpTp07Vxx9/XORa7UOHDikwMLDQZRr9+vXTgw8+aJvr4MGD6t27tzp27Kj69etryJAhWrlype34w4cPKyYmRjfddJPq1KmjBx54QFFRUXZjBgcH69ChQ45+pQBQAMEZAJyoXbt2io+P1y+//KLY2Fj169dPPXr0cHicRo0a2f7by8tLvr6+SklJua5xAgMDJUm33HJLgbaLY2/fvl1z5syRt7e37dWxY0fl5+crISGh0Dmys7Pl7u5+zfOfPXtW6enpkqThw4fr8ccfV0xMjF599dVCl2R4eHgoKyvr6h8eAK6C4AwATuTl5aXIyEhFRUVp1qxZ+uWXXzRz5kxbv4uLi21ZxEW5ubkFxrl0eYJ0Yb3wtdyG7dJxLBZLkW0Xx87IyNDAgQMVHx9ve23fvl379u1T7dq1C52jatWqOnXqVLHMP27cOP3++++69957tXz5cjVo0EALFy60GzM1NVUBAQEmPj0AXBnBGQBKCRcXF73wwgt68cUXlZ2dLUkKCAhQYmKi7Zj09PQir+QW5eKa6by8vOIr9v+79dZbtXPnTkVGRhZ4FbVWu3HjxkpKSioyPDuqbt26GjZsmJYuXap//vOfmj17tq3v7NmzOnDggBo3blwscwEo3wjOAFCKPPDAA3J1ddW0adMkSe3bt9fcuXO1Zs0a/frrr4qNjZWrq6tDY4aHh8tisejbb7/ViRMnlJGRUWz1Pv/88/r55581ePBgxcfHa9++fVq8ePEVNwc2btxYVatW1bp1665r7uzsbA0ePFgrV67UoUOHtG7dOm3atMlu0+GGDRvk5uamli1bXtdcACARnAGgVKlQoYIGDx6syZMnKzMzU6NHj1bbtm1133336d5771W3bt2KXAJRlBo1aujll1/WqFGjFBgYeMVQ66hGjRpp1apV2rt3r9q0aaPGjRtrzJgxCg4OLvIcV1dX9evX77ofTOLq6qo///xTffr0Ud26dfXggw+qc+fOevnll23HfPbZZ+rdu7c8PT2vay4AkCSLcfniOQAASlhSUpJuvvlmbd26VeHh4SUyx8mTJ1WvXj1t3rxZNWvWLJE5AJQvXHEGAPztgoKCNHPmTB0+fLjE5jh48KD++9//EpoBFBuuOAMAAAAmcMUZAAAAMIHgDAAAAJhAcAYAAABMIDgDAAAAJhCcAQAAABMIzgAAAIAJBGcAAADABIIzAAAAYALBGQAAADDh/wEL++SoBnGhLgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)\n", + "\n", + "# Surface-code-like parameters (the defaults)\n", + "surface_like = GenericQEC.q() * Litinski19Factory.q()\n", + "\n", + "# Optimistic hypothetical code: higher threshold, lower overhead\n", + "optimistic = GenericQEC.q(\n", + " crossing_prefactor=0.01,\n", + " error_correction_threshold=0.03,\n", + " qubits_per_data_qubit=1,\n", + ") * Litinski19Factory.q()\n", + "\n", + "results = [\n", + " estimate(app, arch, surface_like, max_error=0.01, name=\"Surface-code-like QEC\"),\n", + " estimate(app, arch, optimistic, max_error=0.01, name=\"Optimistic QEC\"),\n", + " baseline,\n", + "]\n", + "\n", + "plot_estimates(results, figsize=(8, 5), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "6229d30a", + "metadata": {}, + "source": [ + "## Building a custom factory transform\n", + "\n", + "A factory transform produces high-fidelity magic states (typically T states) by\n", + "consuming lower-level instructions. It models the distillation process: take\n", + "many noisy copies of a state, run a distillation protocol, and output fewer\n", + "copies with lower error.\n", + "\n", + "The built-in `Litinski19Factory` uses pre-computed protocol data from\n", + "[arXiv:1905.06903](https://arxiv.org/abs/1905.06903). Each entry in the table\n", + "specifies an output error rate, a space cost in physical qubits (which already\n", + "accounts for the QEC overhead inside the factory), and a time cost in syndrome\n", + "extraction cycles.\n", + "\n", + "We can build a custom factory that starts from this data and explores \"what if\"\n", + "scenarios by scaling the space and cycle costs:\n", + "\n", + "- `space_factor < 1`: What if the factory were more compact?\n", + "- `cycle_factor < 1`: What if distillation were faster?\n", + "\n", + "This lets you study how improvements in factory design propagate through the\n", + "full resource estimate, without having to derive the protocols from scratch." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3f4951e1", + "metadata": {}, + "outputs": [], + "source": [ + "# Reference data from Litinski (arXiv:1905.06903), Table 1.\n", + "# Assumes Clifford and T error rates at most 1e-4.\n", + "# Each tuple: (output_error_rate, space_in_physical_qubits, cycles)\n", + "_LITINSKI_TABLE = [\n", + " (4.4e-8, 810, 18.1),\n", + " (1.5e-9, 762, 36.2),\n", + " (9.3e-10, 1150, 18.1),\n", + " (1.9e-11, 2070, 30.0),\n", + " (2.4e-15, 16400, 90.3),\n", + " (6.3e-25, 18600, 67.8),\n", + "]\n", + "\n", + "\n", + "@dataclass\n", + "class ScaledLitinskiFactory(ISATransform):\n", + " \"\"\"\n", + " A factory transform based on the Litinski (2019) distillation protocols,\n", + " with configurable scaling factors for space and cycle costs.\n", + "\n", + " The base data is taken directly from Table 1 in arXiv:1905.06903.\n", + " Each entry specifies a distillation protocol's output error rate, space\n", + " footprint (in physical qubits), and time (in syndrome extraction cycles).\n", + " The space already includes the QEC overhead within the factory itself.\n", + "\n", + " Scaling factors let you explore hypothetical improvements:\n", + "\n", + " - ``space_factor=0.5`` models a factory that uses half the qubits.\n", + " - ``cycle_factor=0.5`` models a factory that runs twice as fast.\n", + "\n", + " Note:\n", + " The error rates are kept fixed when scaling. In practice, changing\n", + " space or cycles would affect error rates too, but modelling that\n", + " relationship requires protocol-specific analysis.\n", + " \"\"\"\n", + "\n", + " space_factor: float = 1.0\n", + " cycle_factor: float = 1.0\n", + "\n", + " @staticmethod\n", + " def required_isa() -> ISARequirements:\n", + " return ISARequirements(\n", + " constraint(T, error_rate=ConstraintBound.le(1e-4)),\n", + " constraint(H, error_rate=ConstraintBound.le(1e-4)),\n", + " constraint(CNOT, arity=2, error_rate=ConstraintBound.le(1e-4)),\n", + " constraint(MEAS_Z, error_rate=ConstraintBound.le(1e-4)),\n", + " )\n", + "\n", + " def provided_isa(\n", + " self, impl_isa: ISA, ctx: ISAContext\n", + " ) -> Generator[ISA, None, None]:\n", + " cnot = impl_isa[CNOT]\n", + " h = impl_isa[H]\n", + " meas_z = impl_isa[MEAS_Z]\n", + " t = impl_isa[T]\n", + "\n", + " # Syndrome extraction time from the physical ISA\n", + " syndrome_extraction_time = (\n", + " 4 * cnot.expect_time() + h.expect_time() + meas_z.expect_time()\n", + " )\n", + "\n", + " for error_rate, base_space, base_cycles in _LITINSKI_TABLE:\n", + " # Apply scaling factors to space and time\n", + " space = ceil(base_space * self.space_factor)\n", + " time = ceil(syndrome_extraction_time * base_cycles * self.cycle_factor)\n", + "\n", + " yield ctx.make_isa(\n", + " ctx.add_instruction(\n", + " T,\n", + " encoding=LOGICAL,\n", + " arity=1,\n", + " space=space,\n", + " time=time,\n", + " error_rate=error_rate,\n", + " transform=self,\n", + " source=[cnot, h, meas_z, t],\n", + " ),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "739582e7", + "metadata": {}, + "source": [ + "### What if the factory were more compact or faster?\n", + "\n", + "Let's compare three scenarios, all using the built-in `SurfaceCode` for QEC:\n", + "\n", + "- **Litinski baseline:** the built-in `Litinski19Factory` (our reference).\n", + "- **Compact factory:** `space_factor=0.5`: the factory uses half the physical\n", + " qubits (perhaps due to better routing or a denser layout).\n", + "- **Fast factory:** `cycle_factor=0.5`: the factory completes in half the\n", + " cycles (perhaps due to a faster syndrome extraction scheme)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "1de231fb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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RPRsGBgYPPWV5hIMzkREVOfJ5fIq+xcnET2/eHMgEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkwVMHZ6VSCYVCgTt37lRFPURERERENZLWwXnixIlYt24dgIehuXPnzmjZsiXc3d0RGxtb1fUREREREdUIWgfnn3/+Gf7+/gCA3377DSkpKTh//jwmTZqEDz/8sMoLJCIiIiKqCbQOzllZWXBycgIA7Nq1C/3790fjxo3xzjvv4MyZM1VeYE1QWFgIT09PTJkyRdelEBEREZGOaB2cHR0dkZiYCKVSiejoaLz88ssAHoZLuVxe5QXWBPPmzcMLL7yg6zKIiIiISIe0Ds7Dhw/HgAED0Lx5c8hkMgQFBQEAjh49iiZNmlR5gbp28eJFnD9/HiEhIbouhYiIiIh0SOvgPGfOHHz33XcYNWoUDh8+DCMjIwCAXC7HjBkzqrzAp3Hw4EGEhobCxcUFMpkMO3bsKNVn5cqV8PLygrGxMdq1a4djx45pbJ8yZQoWLFjwjComIiIioppK6+C8YcMGhIaGYtKkSXBzc1O3v/XWW8jJyanS4p5WQUEB/P39sXLlyjK3R0VFYfLkyZg9ezZOnjwJf39/BAcHIzMzEwDwyy+/oHHjxmjcuLGk+e7du4fc3FyNFxERERHVDTIhhNBmB7lcjrS0NDg4OGi0//vvv3BwcIBSqazSAquKTCbD9u3b0bdvX3Vbu3bt0KZNG6xYsQIAoFKp4O7ujnHjxmHGjBmY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+N3oukmb3z4nIiIiomdPm7xWY9Y4ExERERHVZAzOREREREQSMDgTEREREUnA4ExEREREJAGDMxERERGRBAzOtVze/TykF6SXuS29IB159/OecUVEREREdRODcy2Wdz8P7+59F8Ojh5cKz+kF6RgePRzv7n2X4ZmIiIioCjA412IFxQW4XXQb1/Ova4TnktB8Pf86bhfdRkFxgY4rJSIiIqr9GJxrMSczJ0T0jICbuZs6PCsyFerQ7GbuhoieEXAyc9J1qURERES1Hn85sBo9q18OfPQKcwmGZiIiIqIn4y8HPmeczJywoNMCjbYFnRYwNBMRERFVIQbnOiC9IB0z/5qp0Tbzr5nlPm2DiIiIiLTH4FzLPbpMw83cDRtDNmqseWZ4JiIiIqoaDM612OOhOaJnBAIcAkrdMMjwTERERPT0GJxrMTMDM9iY2JS6EfDRp23YmNjAzMBMx5USERER1X58qkY1ehZP1ci7n4eC4oIybwRML0iHmYEZLAwtqmVuIiIiotpOm7ym/4xqompiYWhRbjDmUzWIiIiIqg6XahARERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREZEEDM5ERERERBIwOBMRERERScDgTEREREQkAYMzEREREelUXu4NpKcrytyWnq5AXu6NZ1tQORiciYiIiEhn8nJv4N2toRi+awjS005pbEtPO4Xhu4bg3a2hNSI8MzgTERERkc4UFN7CbVGM63Jg+B9D1eE5Pe0Uhv8xFNflwG1RjILCWzqulMGZiIiIiHTIySkAESEb4KaEOjwrzvygDs1uSiAiZAOcnAJ0XSqDMxERERHplpNzoEZ4HnJyoWZodg7UdYkAGJyJiIiIqAZwcg7EgjYzNNoWtJlRY0IzwOBMRERERDVAetopzDy+UKNt5vGFpW4Y1CUGZyIiIiLSqUdvBHRTAhtbztBY81xTwjODMxERERHpTHq6otSNgAEtBpe6YbC85zw/SwzORERERKQzZqb2sJEZlLoR8NEbBm1kBjAztddxpYBMCCF0XURdlZubCysrK+Tk5MDS0lLX5RARERHVSHm5N1BQeKvM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", + "text/plain": [ + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)\n", + "\n", + "# Litinski baseline (unscaled, should closely match built-in Litinski19Factory)\n", + "litinski_baseline = SurfaceCode.q() * ScaledLitinskiFactory.q()\n", + "\n", + "# Compact factory: half the physical qubits\n", + "compact = SurfaceCode.q() * ScaledLitinskiFactory.q(space_factor=0.5)\n", + "\n", + "# Fast factory: half the cycles\n", + "fast = SurfaceCode.q() * ScaledLitinskiFactory.q(cycle_factor=0.5)\n", + "\n", + "results = [\n", + " estimate(app, arch, litinski_baseline, max_error=0.01, name=\"Litinski baseline\"),\n", + " estimate(app, arch, compact, max_error=0.01, name=\"Compact factory (0.5× space)\"),\n", + " estimate(app, arch, fast, max_error=0.01, name=\"Fast factory (0.5× cycles)\"),\n", + " baseline,\n", + "]\n", + "\n", + "plot_estimates(results, figsize=(8, 5), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "c5da8f59", + "metadata": {}, + "source": [ + "## Composing a full custom stack\n", + "\n", + "Now that we have all three custom layers, let's compose them into a complete\n", + "estimation stack. The `*` operator creates a Cartesian product over all\n", + "hyperparameter configurations:\n", + "\n", + "- `GenericQEC` searches over code distances 3–21\n", + "- `ScaledLitinskiFactory` produces multiple factory configurations from\n", + " the Litinski protocol table\n", + "\n", + "The estimator evaluates all combinations and returns only the Pareto-optimal\n", + "results." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "bba890f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namequbitsruntimeerrorphysical_compute_qubitsphysical_factory_qubitsphysical_memory_qubitsfactories
0Full custom stack31510 days 00:00:00.0002990.0067903150101×T
1Full custom stack96300 days 00:00:00.0002925000.0073123150648008×T
2Full custom stack132840 days 00:00:00.0001833000.009416113412150015×T
\n", + "
" + ], + "text/plain": [ + " name qubits runtime error \\\n", + "0 Full custom stack 3151 0 days 00:00:00.000299 0.006790 \n", + "1 Full custom stack 9630 0 days 00:00:00.000292500 0.007312 \n", + "2 Full custom stack 13284 0 days 00:00:00.000183300 0.009416 \n", + "\n", + " physical_compute_qubits physical_factory_qubits physical_memory_qubits \\\n", + "0 3150 1 0 \n", + "1 3150 6480 0 \n", + "2 1134 12150 0 \n", + "\n", + " factories \n", + "0 1×T \n", + "1 8×T \n", + "2 15×T " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Full custom stack: custom architecture + custom QEC + custom factory\n", + "custom_arch = ParameterizedArchitecture(\n", + " single_qubit_gate_time=100,\n", + " two_qubit_gate_time=200,\n", + " measurement_time=400,\n", + " single_qubit_error_rate=5e-5,\n", + " two_qubit_error_rate=1e-4,\n", + " measurement_error_rate=1e-4,\n", + ")\n", + "\n", + "custom_results = estimate(\n", + " app, custom_arch,\n", + " isa_query=GenericQEC.q() * ScaledLitinskiFactory.q(),\n", + " max_error=0.01,\n", + " name=\"Full custom stack\",\n", + ")\n", + "\n", + "custom_results.add_qubit_partition_column()\n", + "custom_results.add_factory_summary_column()\n", + "custom_results.as_frame()" + ] + }, + { + "cell_type": "markdown", + "id": "3ce953fa", + "metadata": {}, + "source": [ + "> **Why does the first result show only 1 factory qubit?**\n", + ">\n", + "> When the physical T-gate error rate is low enough, the estimator may find that\n", + "> it can meet the overall error budget *without* distillation, using the raw\n", + "> physical T gate directly. In that case the \"factory\" is just a single physical\n", + "> qubit producing T states one at a time, which is why the qubit count is so\n", + "> small. The trade-off is a much longer runtime (thousands of serial T-gate\n", + "> executions instead of a few batched factory runs). Subsequent rows in the\n", + "> table use the `ScaledLitinskiFactory` and show the more typical pattern of\n", + "> dedicating thousands of physical qubits to a distillation factory in exchange\n", + "> for faster execution." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "c65d0684", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot_estimates([custom_results, baseline], figsize=(8, 5), runtime_unit=\"ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "ae29b99a", + "metadata": {}, + "source": [ + "## Key takeaways\n", + "\n", + "1. **Three extension points.** The estimator's model stack has three layers:\n", + " `Architecture`, QEC `ISATransform`, and factory `ISATransform`, each of\n", + " which can be replaced independently.\n", + "\n", + "2. **Implement two methods.** Every custom QEC or factory model is a dataclass\n", + " that implements `required_isa` (what physical instructions it needs) and\n", + " `provided_isa` (what logical instructions it provides, with resource\n", + " formulas). A custom architecture implements only `provided_isa`.\n", + "\n", + "3. **Automatic search.** Mark hyperparameters as `KW_ONLY` fields with a\n", + " `metadata={\"domain\": ...}` and the estimator searches over them\n", + " automatically, keeping only the Pareto-optimal configurations.\n", + "\n", + "4. **Mix and match.** Custom models compose freely with built-in ones using the\n", + " `*` operator. You can replace one layer while keeping the others at their\n", + " defaults, which is useful for isolating the effect of a single modeling choice.\n", + "\n", + "5. **Provenance tracking.** Pass `transform=self` and `source=[...]` when\n", + " adding instructions to preserve the link between physical and logical\n", + " instructions. This lets you inspect the full derivation chain in the\n", + " results (see [Analysing Results](2_analysing_results.ipynb))." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/qre/README.md b/samples/qre/README.md new file mode 100644 index 0000000000..0baacbda02 --- /dev/null +++ b/samples/qre/README.md @@ -0,0 +1,44 @@ +# Quantum Resource Estimation Samples + +This directory contains a series of Jupyter notebooks that teach you how to use +the **Quantum Resource Estimator (QRE)** from the `qdk[qre]` Python package. The +estimator takes a quantum program and a hardware model and returns +Pareto-optimal configurations that trade off physical qubit count against +runtime within a given error budget. + +## Who is this for? + +These notebooks are aimed at quantum software engineers and researchers who want +to: + +- Understand the physical cost of running a quantum algorithm on various architectural assumptions. +- Compare resource requirements across different error-correction codes, + magic-state factories, and hardware assumptions. +- Build custom hardware and protocol models and plug them into the estimator. + +Familiarity with basic quantum computing concepts (qubits, gates, error +correction) is helpful but not required; each notebook is self-contained and +introduces the relevant ideas as it goes. + +## Suggested reading order + +| # | Notebook | What you will learn | +|---|----------|---------------------| +| 0 | [Getting Started](0_getting_started.ipynb) | End-to-end workflow: define a Q# application, choose a target architecture, run the estimator, inspect the Pareto frontier, and compare runs across different error rates. | +| 1 | [Importing Quantum Programs](1_qre_input.ipynb) | How to import programs from Q#, Cirq, QIR, and OpenQASM, and how to build a custom `Application` subclass with trace parameters that QRE explores automatically. | +| 2 | [Analysing Results](2_analysing_results.ipynb) | Deep dive into the estimation output: statistics, result properties, the instruction source graph, magic-state factories, custom table columns, and Pareto-frontier plots. | +| 3 | [Building Your Own Models](3_building_your_own_models.ipynb) | How to write custom `Architecture`, QEC, and factory models to explore hypothetical hardware, and how to compose them with built-in models. | + +Start with notebook 0 to learn the core concepts and API. Notebooks 1 and 2 +can be read in either order depending on whether you need to bring in programs +from other frameworks first or want to understand the output in more detail. +Notebook 3 is the most advanced and assumes familiarity with the material in the +earlier notebooks. + +## Prerequisites + +Install the `qdk` Python package with the `qre` extras: + +```bash +pip install qdk[qre] +``` diff --git a/samples/qre/overview.svg b/samples/qre/overview.svg new file mode 100644 index 0000000000..8850e840ae --- /dev/null +++ b/samples/qre/overview.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Application + → generates traces + + + + Architecture + → provides physical ISA + + + + + + + Trace Transforms + → multiple trace variants + + + + + + + ISA Transforms + → multiple ISA variants + + + + + + + + + + Design Space + Exploration + Evaluate all trace × ISA + combinations, filter by + error budget + + + + + + + Pareto Frontier + + + + + + Runtime → + Qubits → + + + + + + + + + + + + + + + + + + + + Pareto-optimal + + Dominated +