{
 "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.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": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>qubits</th>\n",
       "      <th>runtime</th>\n",
       "      <th>error</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>8967</td>\n",
       "      <td>0 days 00:00:00.009200</td>\n",
       "      <td>0.007875</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>10927</td>\n",
       "      <td>0 days 00:00:00.006900</td>\n",
       "      <td>0.007006</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>14847</td>\n",
       "      <td>0 days 00:00:00.004600</td>\n",
       "      <td>0.006136</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>18767</td>\n",
       "      <td>0 days 00:00:00.003450</td>\n",
       "      <td>0.005702</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>22771</td>\n",
       "      <td>0 days 00:00:00.003220</td>\n",
       "      <td>0.004406</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>26607</td>\n",
       "      <td>0 days 00:00:00.002300</td>\n",
       "      <td>0.005267</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "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 qdk.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": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>qubits</th>\n",
       "      <th>runtime</th>\n",
       "      <th>error</th>\n",
       "      <th>physical_compute_qubits</th>\n",
       "      <th>physical_factory_qubits</th>\n",
       "      <th>physical_memory_qubits</th>\n",
       "      <th>factories</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>8967</td>\n",
       "      <td>0 days 00:00:00.009200</td>\n",
       "      <td>0.007875</td>\n",
       "      <td>3087</td>\n",
       "      <td>5880</td>\n",
       "      <td>0</td>\n",
       "      <td>6×T</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>10927</td>\n",
       "      <td>0 days 00:00:00.006900</td>\n",
       "      <td>0.007006</td>\n",
       "      <td>3087</td>\n",
       "      <td>7840</td>\n",
       "      <td>0</td>\n",
       "      <td>8×T</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>14847</td>\n",
       "      <td>0 days 00:00:00.004600</td>\n",
       "      <td>0.006136</td>\n",
       "      <td>3087</td>\n",
       "      <td>11760</td>\n",
       "      <td>0</td>\n",
       "      <td>12×T</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>18767</td>\n",
       "      <td>0 days 00:00:00.003450</td>\n",
       "      <td>0.005702</td>\n",
       "      <td>3087</td>\n",
       "      <td>15680</td>\n",
       "      <td>0</td>\n",
       "      <td>16×T</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>22771</td>\n",
       "      <td>0 days 00:00:00.003220</td>\n",
       "      <td>0.004406</td>\n",
       "      <td>6111</td>\n",
       "      <td>16660</td>\n",
       "      <td>0</td>\n",
       "      <td>17×T</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>26607</td>\n",
       "      <td>0 days 00:00:00.002300</td>\n",
       "      <td>0.005267</td>\n",
       "      <td>3087</td>\n",
       "      <td>23520</td>\n",
       "      <td>0</td>\n",
       "      <td>24×T</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "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=<class 'qsharp.qre.models.qec._surface_code.SurfaceCode'>, source=RootNode(), kwargs={}), physical_qubit_calculation=<built-in function sum>)\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=<class 'qsharp.qre.models.qec._surface_code.SurfaceCode'>, source=RootNode(), kwargs={}), physical_qubit_calculation=<built-in function sum>)\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": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "      <th>instruction</th>\n",
       "      <th>copies</th>\n",
       "      <th>runs</th>\n",
       "      <th>states</th>\n",
       "      <th>factory_error_rate</th>\n",
       "      <th>total_qubits</th>\n",
       "      <th>runtime_ns</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>T</td>\n",
       "      <td>6</td>\n",
       "      <td>141</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>8967</td>\n",
       "      <td>9200000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>T</td>\n",
       "      <td>8</td>\n",
       "      <td>106</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>10927</td>\n",
       "      <td>6900000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>T</td>\n",
       "      <td>12</td>\n",
       "      <td>70</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>14847</td>\n",
       "      <td>4600000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>T</td>\n",
       "      <td>16</td>\n",
       "      <td>53</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>18767</td>\n",
       "      <td>3450000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>T</td>\n",
       "      <td>17</td>\n",
       "      <td>49</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>22771</td>\n",
       "      <td>3220000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>5</td>\n",
       "      <td>T</td>\n",
       "      <td>24</td>\n",
       "      <td>35</td>\n",
       "      <td>808</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>26607</td>\n",
       "      <td>2300000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "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": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>qubits</th>\n",
       "      <th>runtime</th>\n",
       "      <th>error</th>\n",
       "      <th>physical_compute_qubits</th>\n",
       "      <th>physical_factory_qubits</th>\n",
       "      <th>physical_memory_qubits</th>\n",
       "      <th>factories</th>\n",
       "      <th>t_error_rate</th>\n",
       "      <th>logical_error_rate</th>\n",
       "      <th>t_states</th>\n",
       "      <th>num_ts_per_rotation</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>8967</td>\n",
       "      <td>0 days 00:00:00.009200</td>\n",
       "      <td>0.007875</td>\n",
       "      <td>3087</td>\n",
       "      <td>5880</td>\n",
       "      <td>0</td>\n",
       "      <td>6×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-08</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>10927</td>\n",
       "      <td>0 days 00:00:00.006900</td>\n",
       "      <td>0.007006</td>\n",
       "      <td>3087</td>\n",
       "      <td>7840</td>\n",
       "      <td>0</td>\n",
       "      <td>8×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-08</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>14847</td>\n",
       "      <td>0 days 00:00:00.004600</td>\n",
       "      <td>0.006136</td>\n",
       "      <td>3087</td>\n",
       "      <td>11760</td>\n",
       "      <td>0</td>\n",
       "      <td>12×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-08</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>18767</td>\n",
       "      <td>0 days 00:00:00.003450</td>\n",
       "      <td>0.005702</td>\n",
       "      <td>3087</td>\n",
       "      <td>15680</td>\n",
       "      <td>0</td>\n",
       "      <td>16×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-08</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>22771</td>\n",
       "      <td>0 days 00:00:00.003220</td>\n",
       "      <td>0.004406</td>\n",
       "      <td>6111</td>\n",
       "      <td>16660</td>\n",
       "      <td>0</td>\n",
       "      <td>17×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-10</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>26607</td>\n",
       "      <td>0 days 00:00:00.002300</td>\n",
       "      <td>0.005267</td>\n",
       "      <td>3087</td>\n",
       "      <td>23520</td>\n",
       "      <td>0</td>\n",
       "      <td>24×T</td>\n",
       "      <td>2.130350e-07</td>\n",
       "      <td>3.000000e-08</td>\n",
       "      <td>808</td>\n",
       "      <td>12</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "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": "[encoded data, 16120 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "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
}
