diff --git a/samples/azure-quantum/resource-estimation/estimation-dynamics.ipynb b/samples/azure-quantum/resource-estimation/estimation-dynamics.ipynb index ded643f595c3..beadef5d626d 100644 --- a/samples/azure-quantum/resource-estimation/estimation-dynamics.ipynb +++ b/samples/azure-quantum/resource-estimation/estimation-dynamics.ipynb @@ -1,447 +1,659 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Quantum dynamics resource estimation\n", - "\n", - "In this Q# notebook we demonstrate resource estimation for quantum dynamics,\n", - "specifically the simulation of an Ising model Hamiltonian on an $N \\times N$ 2D\n", - "lattice using a *fourth-order Trotter Suzuki product formula* assuming a 2D\n", - "qubit architecture with nearest-neighbor connectivity.\n", - "\n", - "First, we connect to the Azure quantum service and load the necessary packages." - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantum dynamics resource estimation\n", + "\n", + "In this Q# notebook we demonstrate resource estimation for quantum dynamics,\n", + "specifically the simulation of an Ising model Hamiltonian on an $N \\times N$ 2D\n", + "lattice using a *fourth-order Trotter Suzuki product formula* assuming a 2D\n", + "qubit architecture with nearest-neighbor connectivity.\n", + "\n", + "First, we connect to the Azure quantum service and load the necessary packages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.quantum import Workspace\n", + "from azure.quantum.target.microsoft import MicrosoftEstimator, QubitParams, QECScheme\n", + "import qsharp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace = Workspace (\n", + " resource_id = \"\",\n", + " location = \"\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.packages.add(\"Microsoft.Quantum.Numerics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background: 2D Ising model\n", + "\n", + "The Ising model is a mathematical model of ferromagnetism in a lattice (in our case a 2D square lattice) with two kinds of terms in the Hamiltonian: (i) an interaction term between adjacent sites and (ii) an external magnetic field acting at each site. For our purposes, we consider a simplified version of the model where the interaction terms have the same strength and the external field strength is the same at each site.\n", + "Formally, the Ising model Hamiltonian on an $N \\times N$ lattice we consider is formulated as:\n", + "\n", + "$$\n", + "H = \\underbrace{-J \\sum_{i, j} Z_i Z_j}_{B} + \\underbrace{g \\sum_j X_j}_{A}\n", + "$$\n", + "where $J$ is the interaction strength, $g$ is external field strength.\n", + "\n", + "The time evolution $e^{-iHt}$ for the Hamiltonian is simulated with the fourth-order product formula so that any errors in simulation are sufficiently small. Essentially, this is done by simulating the evolution for small slices of time $\\Delta$ and repeating this for `nSteps` $= t/\\Delta$ to obtain the full time evolution. The Trotter-Suzuki formula for higher orders can be recursively defined using a *fractal decomposition* as discussed in Section 3 of [[Hatanao and Suziki's survey](https://link.springer.com/chapter/10.1007/11526216_2)]. Then the fourth order formula $U_4(\\Delta)$ can be constructed using the second-order one $U_2(\\Delta)$ as follows.\n", + "$$\n", + "\\begin{aligned}\n", + "U_2(\\Delta) & = e^{-iA\\Delta/2} e^{-iB\\Delta} e^{-iA\\Delta/2}; \\\\\n", + "U_4(\\Delta) & = U_2(p\\Delta)U_2(p\\Delta)U_2((1 - 4p)\\Delta)U_2(p\\Delta)U_2(p\\Delta); \\\\\n", + "p & = (4 - 4^{1/3})^{-1}.\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "For the rest of the notebook, we will present the code that computes the time evolution in a step by step fashion.\n", + "\n", + "## Implementation\n", + "\n", + "### Helper functions\n", + "\n", + "We will allocate all qubits in the 2D lattice in a one-dimensional array. The function `GetQubitIndex` converts a qubit identified on a 2D lattice by `(row, col)` to an index in that array. We assume a snake-like order on the 2D lattice i.e., the numbering goes left-to-right on even rows and right-to-left on odd rows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "function GetQubitIndex(row : Int, col : Int, n : Int) : Int {\n", + " return row % 2 == 0 // if row is even,\n", + " ? col + n * row // move from left to right,\n", + " | (n - 1 - col) + n * row; // otherwise from right to left.\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that expanding $U_4(\\Delta)$ to express it in terms of $A, B$ gives:\n", + "$$\n", + "U_4(\\Delta) = e^{-iAp\\Delta/2} e^{-iBp\\Delta} e^{-iAp\\Delta} e^{-iBp\\Delta} e^{-iA(1 - 3p)\\Delta/2} e^{-iB(1-4p)\\Delta} e^{-iA(1 - 3p)\\Delta/2} e^{-iBp\\Delta} e^{-iAp\\Delta} e^{-iBp\\Delta} e^{-iAp\\Delta/2}\n", + "$$\n", + "\n", + "The above equation with $11$ exponential terms works for one time step. For `nSteps` $> 1$ time steps, some adjacent terms can be merged to give $10t+1$ exponential terms for $e^{-iHt}$.\n", + "\n", + "The function below creates two sequences `seqA` and `seqB` corresponding to the constant factors that will be applied with $A$ and $B$, respectively, in the exponential sequence of the above formula." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "function SetSequences(len : Int, p : Double, dt : Double, J : Double, g : Double) : (Double[], Double[]) {\n", + " // create two arrays of size `len`\n", + " mutable seqA = [0.0, size=len];\n", + " mutable seqB = [0.0, size=len];\n", + "\n", + " // pre-compute values according to exponents\n", + " let values = [\n", + " -J * p * dt,\n", + " g * p * dt,\n", + " -J * p * dt,\n", + " g * p * dt,\n", + " -J * (1.0 - 3.0 * p) * dt / 2.0,\n", + " g * (1.0 - 4.0 * p) * dt,\n", + " -J * (1.0 - 3.0 * p) * dt / 2.0,\n", + " g * p * dt,\n", + " -J * p * dt,\n", + " g * p * dt\n", + " ];\n", + "\n", + " // assign first and last value of `seqA`\n", + " set seqA w/= 0 <- -J * p * dt / 2.0;\n", + " set seqA w/= len - 1 <- -J * p * dt / 2.0;\n", + "\n", + " // assign other values to `seqA` or `seqB`\n", + " // in an alternating way\n", + " for i in 1..len - 2 {\n", + " if i % 2 == 0 {\n", + " set seqA w/= i <- values[i % 10];\n", + " }\n", + " else {\n", + " set seqB w/= i <- values[i % 10];\n", + " }\n", + " }\n", + "\n", + " return (seqA, seqB);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Quantum operations\n", + "\n", + "There are two kinds of Pauli exponentials needed for simulating the time evolution of an Ising Model:\n", + "- The transverse field $e^{-iX\\theta}$ applied to each qubit for an angle $\\theta$;\n", + "- $e^{-i (Z \\otimes Z)\\theta}$ applied to neighboring pairs of qubits in the lattice.\n", + "\n", + "The operation below applies $e^{-iX\\theta}$ on all qubits in the 2D lattice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "operation ApplyAllX(qs : Qubit[], theta : Double) : Unit {\n", + " // This applies `Rx` with an angle of `2.0 * theta` to all qubits in `qs`\n", + " // using partial application\n", + " ApplyToEach(Rx(2.0 * theta, _), qs);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next operation below applies $e^{-i(Z \\otimes Z)\\theta}$ on overlapping pairs of neighboring qubits. We decompose this term into a single qubit $e^{-iZ\\theta}$ term (implemented as an `Rz` rotation) conjugated by `CNOT`s to entangle the neighboring qubits following Section 4.2 of [[Whitfield et al.](https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441)].\n", + "\n", + "Observe that unlike the previous case, it is not possible to simultaneously apply all the rotations in one go. For example, while applying the rotation on qubits at $(0, 0)$ and $(0, 1)$, it is not possible to also apply the rotation on qubits $(0, 1)$ and $(0, 2)$. Instead, we try to apply as many rotations as possible. This is broken up as follows:\n", + "- in the horizontal (resp. vertical) direction of the 2D lattice as chosen by `dir`,\n", + "- consider pairs starting with an even (resp. odd) index as given by `grp`;\n", + "- apply the exponential to all such pairs in the lattice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "operation ApplyDoubleZ(n : Int, qs : Qubit[], theta : Double, dir : Bool, grp : Bool) : Unit {\n", + " let start = grp ? 0 | 1; // Choose either odd or even indices based on group number\n", + "\n", + " for i in 0..n - 1 {\n", + " for j in start..2..n - 2 { // Iterate through even or odd `j`s based on `grp`\n", + " // rows and cols are interchanged depending on direction\n", + " let (row, col) = dir ? (i, j) | (j, i);\n", + "\n", + " // Choose first qubit based on row and col\n", + " let ind1 = GetQubitIndex(row, col, n);\n", + " // Choose second qubit in column if direction is horizontal and next qubit in row if direction is vertical\n", + " let ind2 = dir ? GetQubitIndex(row, col + 1, n) | GetQubitIndex(row + 1, col, n);\n", + "\n", + " within {\n", + " CNOT(qs[ind1], qs[ind2]);\n", + " } apply {\n", + " Rz(2.0 * theta, qs[ind2]);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next operation puts everything together and calls the operations needed to\n", + "simulate the Ising model Hamiltonian using a fourth order product formula.\n", + "Observe that the `ApplyDoubleZ` operation is called four times for different\n", + "choices of direction and starting index to ensure all possible pairs of qubits\n", + "are appropriately considered.\n", + "\n", + "The various parameters taken in by the operation correspond to:\n", + "\n", + "- `J`, `g`: parameters by which the Hamiltonian terms are scaled.\n", + "- `N`: size of the square lattice.\n", + "- `totTime`: the number of Trotter steps.\n", + "- `dt` : the step size for the simulation, sometimes denoted as $\\Delta$.\n", + "- `eps`: the precision for arbitrary rotations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "open Microsoft.Quantum.Math;\n", + "\n", + "operation IsingModel2DSim(N : Int, J : Double, g : Double, totTime : Double, dt : Double, eps : Double) : Unit {\n", + " use qs = Qubit[N * N];\n", + " let len = Length(qs);\n", + "\n", + " let p = 1.0 / (4.0 - PowD(4.0, 1.0 / 3.0));\n", + " let t = Ceiling(totTime / dt);\n", + "\n", + " let seqLen = 10 * t + 1;\n", + "\n", + " let (seqA, seqB) = SetSequences(seqLen, p, dt, J, g);\n", + "\n", + " for i in 0..seqLen - 1 {\n", + " // for even indexes\n", + " if i % 2 == 0 {\n", + " ApplyAllX(qs, seqA[i]);\n", + " } else {\n", + " // iterate through all possible combinations for `dir` and `grp`.\n", + " for (dir, grp) in [(true, true), (true, false), (false, true), (false, false)] {\n", + " ApplyDoubleZ(N, qs, seqB[i], dir, grp);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running the experiment\n", + "\n", + "Next, we are estimating the physical resource estimates to simulate the Ising\n", + "model Hamiltonian for a $10 \\times 10$ lattice with $J = g = 1.0$, total time\n", + "$20$, step size $0.25$, and `eps` ${}=0.001$. As configurations for the\n", + "experiment we use all six pre-defined qubit parameters. As pre-defined QEC\n", + "scheme we are using `surface_code` with gate-based qubit parameters (default),\n", + "and `floquet_code` with Majorana based qubit parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "estimator = MicrosoftEstimator(workspace)\n", + "\n", + "labels = [\"Gate-based µs, 10⁻³\", \"Gate-based µs, 10⁻⁴\", \"Gate-based ns, 10⁻³\", \"Gate-based ns, 10⁻⁴\", \"Majorana ns, 10⁻⁴\", \"Majorana ns, 10⁻⁶\"]\n", + "\n", + "params = estimator.make_params(num_items=6)\n", + "params.arguments[\"N\"] = 10\n", + "params.arguments[\"J\"] = 1.0\n", + "params.arguments[\"g\"] = 1.0\n", + "params.arguments[\"totTime\"] = 20.0\n", + "params.arguments[\"dt\"] = 0.25\n", + "params.arguments[\"eps\"] = 0.001\n", + "params.items[0].qubit_params.name = QubitParams.GATE_US_E3\n", + "params.items[1].qubit_params.name = QubitParams.GATE_US_E4\n", + "params.items[2].qubit_params.name = QubitParams.GATE_NS_E3\n", + "params.items[3].qubit_params.name = QubitParams.GATE_NS_E4\n", + "params.items[4].qubit_params.name = QubitParams.MAJ_NS_E4\n", + "params.items[4].qec_scheme.name = QECScheme.FLOQUET_CODE\n", + "params.items[5].qubit_params.name = QubitParams.MAJ_NS_E6\n", + "params.items[5].qec_scheme.name = QECScheme.FLOQUET_CODE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are submitting a resource estimation job with all target parameter configurations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "job = estimator.submit(IsingModel2DSim, input_params=params)\n", + "results = job.get_results()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualizing and understanding the results\n", + "\n", + "### Result summary table" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results.summary_data_frame(labels=labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Space chart\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The distribution of physical qubits used for the execution of the algorithm instructions and the supporting T factories can provide us valuable information to guide us in applying space and time optimizations. We can visualize this distribution for each set of qubit parameters to understand the differences in physical qubit distribution for each configuration.\n", + "\n", + "To show the space chart for a configuration from our experiment, use the following syntax:\n", + "\n", + "```\n", + " # Use the index of the desired configuration you want to visualize to get the results for that configuration.\n", + " \n", + " results[].diagram.space\n", + "\n", + " # For example, this command will produce a chart showing the distribution of physical qubits for the Gate-based µs, 10⁻³ configuration set.\n", + " results[0].diagram.space \n", + "```\n", + "\n", + "Below, let's visualize the space diagrams for each configuration." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> You must run the `results[].diagram.space` command in a separate code cell per label.\n", + ">\n", + "> You cannot visualize the time and space diagrams in the same cell.\n", + ">\n", + "> If you run an algorithm which only has one configured set of qubit parameters and one result set, specifying the label would not be necessary. You could simply run `result.diagram.time`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# \"Gate-based µs, 10⁻³\"\n", + "results[0].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# \"Gate-based µs, 10⁻⁴\"\n", + "results[1].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# \"Gate-based ns, 10⁻³\"\n", + "results[2].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# \"Gate-based ns, 10⁻⁴\"\n", + "results[3].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# \"Majorana ns, 10⁻⁴\"\n", + "results[4].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " # \"Majorana ns, 10⁻⁶\"\n", + "results[5].diagram.space" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Results discussion\n", + "From the results we can observe that a large fraction of physical qubits is used\n", + "for the T factories. To understand why, it's important to remark that the\n", + "overall algorithm runtime is determined based on the number of logical\n", + "operations (also called logical cycles or logical depth). The runtime limits\n", + "the number of invocations of a single T factory. The total number of T factory\n", + "copies is computed based on the total number of required T states divided by the\n", + "number of possible invocations. Therefore, if the algorithm would run longer, a\n", + "T factory can be invoked more often, which may allow to compute all required T\n", + "states with less T factory copies.\n", + "\n", + "Since T factory fraction is high, while at the same time the physical runtime is\n", + "relatively small, this is a good opportunity for a space-time optimization based\n", + "on the logical depth. We can make an algorithm run longer, by inserting no-op\n", + "(no operation or idle) operations. We do this using the `logical_depth_factor`\n", + "constraint. For example, a value of 2 means that the number of cycles should be\n", + "twice as much, i.e., one no-op per operation; or, a value of 1.5 means that the\n", + "number of cycles is 50% more, i.e., one no-op for every two operations.\n", + "\n", + "Please note that the algorithm runtime may increase by a larger factor than the\n", + "`logical_depth_factor`. This is because no-ops also can incur logical errors,\n", + "and therefore the required logical error rate is lower, which in turn may\n", + "increase the required code distance, therefore leading a to a longer execution\n", + "time of a logical cycle.\n", + "\n", + "In the balanced implementation that is described in the paper, we increase the\n", + "logical depth by a factor of 10. We do this by updating the `params` variable.\n", + "All other parameters remain unchanged." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualizing the results further\n", + "#### Time chart\n", + "We can also visualize the time required to execute the algorithm as it relates to each T factory invocation runtime and the number of T factory invocations.\n", + "\n", + "\n", + "To show the time chart for a configuration from our experiment, use the following syntax:\n", + "\n", + "```\n", + " # Use the index of the desired configuration you want to visualize to get the results for that configuration.\n", + " \n", + " results[].diagram.time\n", + "\n", + " # For example, this command will produce a chart showing the distribution of physical qubits for the Gate-based µs, 10⁻³ configuration set.\n", + " results[0].diagram.time\n", + "```\n", + "\n", + "Below, let's visualize the space diagrams for each configuration set." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> You must run the `results[].diagram.time` command in a separate code cell per label.\n", + ">\n", + "> You cannot visualize the time and space diagrams in the same cell.\n", + ">\n", + "> If you run an algorithm which only has one configured set of qubit parameters and one result set, specifying the label would not be necessary. You could simply run `result.diagram.time`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Feel free to try out different configuration sets by changing the index. \n", + "# This example produces the time diagram for the Gate-based µs, 10⁻³ configuration set.\n", + "results[0].diagram.time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next let's run the algorithm with an increased logical depth to demonstrate the balanced implementation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "params.constraints.logical_depth_factor = 10\n", + "\n", + "job = estimator.submit(IsingModel2DSim, input_params=params)\n", + "results_balanced = job.get_results()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We print the results for the balanced implementation as a summary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results_balanced.summary_data_frame(labels=labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note how the T factory fraction is much smaller now and the number of total\n", + "physical qubits decreased as a result. The logical depth increased exactly by a\n", + "factor of 10, whereas the runtime increased by a factor of at least 10, since in\n", + "most cases the code distance is higher." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To better understand how the balanced implementation changed the space distribution and runtime of the algorithm, try visualizing the new result set using the space and time diagrams discussed above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Feel free to try out different configuration sets by changing the index. \n", + "# This example produces the time diagram for the Gate-based µs, 10⁻³ configuration set.\n", + "results_balanced[0].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Feel free to try out different configuration sets by changing the index. \n", + "# This example produces the time diagram for the Gate-based µs, 10⁻³ configuration set.\n", + "results_balanced[0].diagram.time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You could even compare the previous implementation with the balanced implementation by re-running the space and time diagrams with the original result set and comparing to the new diagrams." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Feel free to try out different configuration sets by changing the index. \n", + "# This example produces the time diagram for the Gate-based µs, 10⁻³ configuration set.\n", + "results[0].diagram.space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Feel free to try out different configuration sets by changing the index. \n", + "# This example produces the time diagram for the Gate-based µs, 10⁻³ configuration set.\n", + "results[0].diagram.time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "The numbers in the table match the numbers in the paper [Assessing requirements\n", + "for scaling quantum computers to real-world impact](https://aka.ms/AQ/RE/Paper).\n", + "Feel free to use this table as a starting point for your own experiments. For\n", + "example, you can\n", + "\n", + "* explore how the results change by modifying the operation arguments of the Ising\n", + " model instance\n", + "* explore space- and time-trade-offs by changing the value for\n", + " `logical_depth_factor`\n", + " * Visualize these trade-offs with the space and time diagrams.\n", + "* use other or customized qubit parameters" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azure.quantum import Workspace\n", - "from azure.quantum.target.microsoft import MicrosoftEstimator, QubitParams, QECScheme\n", - "import qsharp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "workspace = Workspace (\n", - " resource_id = \"\",\n", - " location = \"\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "qsharp.packages.add(\"Microsoft.Quantum.Numerics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Background: 2D Ising model\n", - "\n", - "The Ising model is a mathematical model of ferromagnetism in a lattice (in our case a 2D square lattice) with two kinds of terms in the Hamiltonian: (i) an interaction term between adjacent sites and (ii) an external magnetic field acting at each site. For our purposes, we consider a simplified version of the model where the interaction terms have the same strength and the external field strength is the same at each site.\n", - "Formally, the Ising model Hamiltonian on an $N \\times N$ lattice we consider is formulated as:\n", - "\n", - "$$\n", - "H = \\underbrace{-J \\sum_{i, j} Z_i Z_j}_{B} + \\underbrace{g \\sum_j X_j}_{A}\n", - "$$\n", - "where $J$ is the interaction strength, $g$ is external field strength.\n", - "\n", - "The time evolution $e^{-iHt}$ for the Hamiltonian is simulated with the fourth-order product formula so that any errors in simulation are sufficiently small. Essentially, this is done by simulating the evolution for small slices of time $\\Delta$ and repeating this for `nSteps` $= t/\\Delta$ to obtain the full time evolution. The Trotter-Suzuki formula for higher orders can be recursively defined using a *fractal decomposition* as discussed in Section 3 of [[Hatanao and Suziki's survey](https://link.springer.com/chapter/10.1007/11526216_2)]. Then the fourth order formula $U_4(\\Delta)$ can be constructed using the second-order one $U_2(\\Delta)$ as follows.\n", - "$$\n", - "\\begin{aligned}\n", - "U_2(\\Delta) & = e^{-iA\\Delta/2} e^{-iB\\Delta} e^{-iA\\Delta/2}; \\\\\n", - "U_4(\\Delta) & = U_2(p\\Delta)U_2(p\\Delta)U_2((1 - 4p)\\Delta)U_2(p\\Delta)U_2(p\\Delta); \\\\\n", - "p & = (4 - 4^{1/3})^{-1}.\n", - "\\end{aligned}\n", - "$$\n", - "\n", - "For the rest of the notebook, we will present the code that computes the time evolution in a step by step fashion.\n", - "\n", - "## Implementation\n", - "\n", - "### Helper functions\n", - "\n", - "We will allocate all qubits in the 2D lattice in a one-dimensional array. The function `GetQubitIndex` converts a qubit identified on a 2D lattice by `(row, col)` to an index in that array. We assume a snake-like order on the 2D lattice i.e., the numbering goes left-to-right on even rows and right-to-left on odd rows." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%qsharp\n", - "function GetQubitIndex(row : Int, col : Int, n : Int) : Int {\n", - " return row % 2 == 0 // if row is even,\n", - " ? col + n * row // move from left to right,\n", - " | (n - 1 - col) + n * row; // otherwise from right to left.\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that expanding $U_4(\\Delta)$ to express it in terms of $A, B$ gives:\n", - "$$\n", - "U_4(\\Delta) = e^{-iAp\\Delta/2} e^{-iBp\\Delta} e^{-iAp\\Delta} e^{-iBp\\Delta} e^{-iA(1 - 3p)\\Delta/2} e^{-iB(1-4p)\\Delta} e^{-iA(1 - 3p)\\Delta/2} e^{-iBp\\Delta} e^{-iAp\\Delta} e^{-iBp\\Delta} e^{-iAp\\Delta/2}\n", - "$$\n", - "\n", - "The above equation with $11$ exponential terms works for one time step. For `nSteps` $> 1$ time steps, some adjacent terms can be merged to give $10t+1$ exponential terms for $e^{-iHt}$.\n", - "\n", - "The function below creates two sequences `seqA` and `seqB` corresponding to the constant factors that will be applied with $A$ and $B$, respectively, in the exponential sequence of the above formula." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%qsharp\n", - "function SetSequences(len : Int, p : Double, dt : Double, J : Double, g : Double) : (Double[], Double[]) {\n", - " // create two arrays of size `len`\n", - " mutable seqA = [0.0, size=len];\n", - " mutable seqB = [0.0, size=len];\n", - "\n", - " // pre-compute values according to exponents\n", - " let values = [\n", - " -J * p * dt,\n", - " g * p * dt,\n", - " -J * p * dt,\n", - " g * p * dt,\n", - " -J * (1.0 - 3.0 * p) * dt / 2.0,\n", - " g * (1.0 - 4.0 * p) * dt,\n", - " -J * (1.0 - 3.0 * p) * dt / 2.0,\n", - " g * p * dt,\n", - " -J * p * dt,\n", - " g * p * dt\n", - " ];\n", - "\n", - " // assign first and last value of `seqA`\n", - " set seqA w/= 0 <- -J * p * dt / 2.0;\n", - " set seqA w/= len - 1 <- -J * p * dt / 2.0;\n", - "\n", - " // assign other values to `seqA` or `seqB`\n", - " // in an alternating way\n", - " for i in 1..len - 2 {\n", - " if i % 2 == 0 {\n", - " set seqA w/= i <- values[i % 10];\n", - " }\n", - " else {\n", - " set seqB w/= i <- values[i % 10];\n", - " }\n", - " }\n", - "\n", - " return (seqA, seqB);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Quantum operations\n", - "\n", - "There are two kinds of Pauli exponentials needed for simulating the time evolution of an Ising Model:\n", - "- The transverse field $e^{-iX\\theta}$ applied to each qubit for an angle $\\theta$;\n", - "- $e^{-i (Z \\otimes Z)\\theta}$ applied to neighboring pairs of qubits in the lattice.\n", - "\n", - "The operation below applies $e^{-iX\\theta}$ on all qubits in the 2D lattice." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%qsharp\n", - "operation ApplyAllX(qs : Qubit[], theta : Double) : Unit {\n", - " // This applies `Rx` with an angle of `2.0 * theta` to all qubits in `qs`\n", - " // using partial application\n", - " ApplyToEach(Rx(2.0 * theta, _), qs);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The next operation below applies $e^{-i(Z \\otimes Z)\\theta}$ on overlapping pairs of neighboring qubits. We decompose this term into a single qubit $e^{-iZ\\theta}$ term (implemented as an `Rz` rotation) conjugated by `CNOT`s to entangle the neighboring qubits following Section 4.2 of [[Whitfield et al.](https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441)].\n", - "\n", - "Observe that unlike the previous case, it is not possible to simultaneously apply all the rotations in one go. For example, while applying the rotation on qubits at $(0, 0)$ and $(0, 1)$, it is not possible to also apply the rotation on qubits $(0, 1)$ and $(0, 2)$. Instead, we try to apply as many rotations as possible. This is broken up as follows:\n", - "- in the horizontal (resp. vertical) direction of the 2D lattice as chosen by `dir`,\n", - "- consider pairs starting with an even (resp. odd) index as given by `grp`;\n", - "- apply the exponential to all such pairs in the lattice." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%qsharp\n", - "operation ApplyDoubleZ(n : Int, qs : Qubit[], theta : Double, dir : Bool, grp : Bool) : Unit {\n", - " let start = grp ? 0 | 1; // Choose either odd or even indices based on group number\n", - "\n", - " for i in 0..n - 1 {\n", - " for j in start..2..n - 2 { // Iterate through even or odd `j`s based on `grp`\n", - " // rows and cols are interchanged depending on direction\n", - " let (row, col) = dir ? (i, j) | (j, i);\n", - "\n", - " // Choose first qubit based on row and col\n", - " let ind1 = GetQubitIndex(row, col, n);\n", - " // Choose second qubit in column if direction is horizontal and next qubit in row if direction is vertical\n", - " let ind2 = dir ? GetQubitIndex(row, col + 1, n) | GetQubitIndex(row + 1, col, n);\n", - "\n", - " within {\n", - " CNOT(qs[ind1], qs[ind2]);\n", - " } apply {\n", - " Rz(2.0 * theta, qs[ind2]);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The next operation puts everything together and calls the operations needed to\n", - "simulate the Ising model Hamiltonian using a fourth order product formula.\n", - "Observe that the `ApplyDoubleZ` operation is called four times for different\n", - "choices of direction and starting index to ensure all possible pairs of qubits\n", - "are appropriately considered.\n", - "\n", - "The various parameters taken in by the operation correspond to:\n", - "\n", - "- `J`, `g`: parameters by which the Hamiltonian terms are scaled.\n", - "- `N`: size of the square lattice.\n", - "- `totTime`: the number of Trotter steps.\n", - "- `dt` : the step size for the simulation, sometimes denoted as $\\Delta$.\n", - "- `eps`: the precision for arbitrary rotations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%qsharp\n", - "open Microsoft.Quantum.Math;\n", - "\n", - "operation IsingModel2DSim(N : Int, J : Double, g : Double, totTime : Double, dt : Double, eps : Double) : Unit {\n", - " use qs = Qubit[N * N];\n", - " let len = Length(qs);\n", - "\n", - " let p = 1.0 / (4.0 - PowD(4.0, 1.0 / 3.0));\n", - " let t = Ceiling(totTime / dt);\n", - "\n", - " let seqLen = 10 * t + 1;\n", - "\n", - " let (seqA, seqB) = SetSequences(seqLen, p, dt, J, g);\n", - "\n", - " for i in 0..seqLen - 1 {\n", - " // for even indexes\n", - " if i % 2 == 0 {\n", - " ApplyAllX(qs, seqA[i]);\n", - " } else {\n", - " // iterate through all possible combinations for `dir` and `grp`.\n", - " for (dir, grp) in [(true, true), (true, false), (false, true), (false, false)] {\n", - " ApplyDoubleZ(N, qs, seqB[i], dir, grp);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Running the experiment\n", - "\n", - "Next, we are estimating the physical resource estimates to simulate the Ising\n", - "model Hamiltonian for a $10 \\times 10$ lattice with $J = g = 1.0$, total time\n", - "$20$, step size $0.25$, and `eps` ${}=0.001$. As configurations for the\n", - "experiment we use all six pre-defined qubit parameters. As pre-defined QEC\n", - "scheme we are using `surface_code` with gate-based qubit parameters (default),\n", - "and `floquet_code` with Majorana based qubit parameters." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "estimator = MicrosoftEstimator(workspace)\n", - "\n", - "labels = [\"Gate-based µs, 10⁻³\", \"Gate-based µs, 10⁻⁴\", \"Gate-based ns, 10⁻³\", \"Gate-based ns, 10⁻⁴\", \"Majorana ns, 10⁻⁴\", \"Majorana ns, 10⁻⁶\"]\n", - "\n", - "params = estimator.make_params(num_items=6)\n", - "params.arguments[\"N\"] = 10\n", - "params.arguments[\"J\"] = 1.0\n", - "params.arguments[\"g\"] = 1.0\n", - "params.arguments[\"totTime\"] = 20.0\n", - "params.arguments[\"dt\"] = 0.25\n", - "params.arguments[\"eps\"] = 0.001\n", - "params.items[0].qubit_params.name = QubitParams.GATE_US_E3\n", - "params.items[1].qubit_params.name = QubitParams.GATE_US_E4\n", - "params.items[2].qubit_params.name = QubitParams.GATE_NS_E3\n", - "params.items[3].qubit_params.name = QubitParams.GATE_NS_E4\n", - "params.items[4].qubit_params.name = QubitParams.MAJ_NS_E4\n", - "params.items[4].qec_scheme.name = QECScheme.FLOQUET_CODE\n", - "params.items[5].qubit_params.name = QubitParams.MAJ_NS_E6\n", - "params.items[5].qec_scheme.name = QECScheme.FLOQUET_CODE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are submitting a resource estimation job with all target parameter configurations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job = estimator.submit(IsingModel2DSim, input_params=params)\n", - "results = job.get_results()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we are presenting the experimental results using a summary table." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results.summary_data_frame(labels=labels)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From the results we can observe that a large fraction of physical qubits is used\n", - "for the T factories. To understand why, it's important to remark that the\n", - "overall algorithm runtime is determined based on the number of logical\n", - "operations (also called logical cycles or logical depth). The runtime limits\n", - "the number of invocations of a single T factory. The total number of T factory\n", - "copies is computed based on the total number of required T states divided by the\n", - "number of possible invocations. Therefore, if the algorithm would run longer, a\n", - "T factory can be invoked more often, which may allow to compute all required T\n", - "states with less T factory copies.\n", - "\n", - "Since T factory fraction is high, while at the same time the physical runtime is\n", - "relatively small, this is a good opportunity for a space-time optimization based\n", - "on the logical depth. We can make an algorithm run longer, by inserting no-op\n", - "(no operation or idle) operations. We do this using the `logical_depth_factor`\n", - "constraint. For example, a value of 2 means that the number of cycles should be\n", - "twice as much, i.e., one no-op per operation; or, a value of 1.5 means that the\n", - "number of cycles is 50% more, i.e., one no-op for every two operations.\n", - "\n", - "Please note that the algorithm runtime may increase by a larger factor than the\n", - "`logical_depth_factor`. This is because no-ops also can incur logical errors,\n", - "and therefore the required logical error rate is lower, which in turn may\n", - "increase the required code distance, therefore leading a to a longer execution\n", - "time of a logical cycle.\n", - "\n", - "In the balanced implementation that is described in the paper, we increase the\n", - "logical depth by a factor of 10. We do this by updating the `params` variable.\n", - "All other parameters remain unchanged." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params.constraints.logical_depth_factor = 10\n", - "\n", - "job = estimator.submit(IsingModel2DSim, input_params=params)\n", - "results_balanced = job.get_results()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We print the results for the balanced implementation as a summary." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results_balanced.summary_data_frame(labels=labels)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note how the T factory fraction is much smaller now and the number of total\n", - "physical qubits decreased as a result. The logical depth increased exactly by a\n", - "factor of 10, whereas the runtime increased by a factor of at least 10, since in\n", - "most cases the code distance is higher." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Next steps\n", - "\n", - "The numbers in the table match the numbers in the paper [Assessing requirements\n", - "for scaling quantum computers to real-world impact](https://aka.ms/AQ/RE/Paper).\n", - "Feel free to use this table as a starting point for your own experiments. For\n", - "example, you can\n", - "\n", - "* explore how the results change by modifying the operation arguments of the Ising\n", - " model instance\n", - "* explore space- and time-trade-offs by changing the value for\n", - " `logical_depth_factor`\n", - "* use other or customized qubit parameters" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/samples/azure-quantum/resource-estimation/estimation-qiskit.ipynb b/samples/azure-quantum/resource-estimation/estimation-qiskit.ipynb index 71482d054b0c..622261195e10 100644 --- a/samples/azure-quantum/resource-estimation/estimation-qiskit.ipynb +++ b/samples/azure-quantum/resource-estimation/estimation-qiskit.ipynb @@ -1,631 +1,673 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Getting Started with Azure Quantum Resource Estimation using Qiskit\n", - "\n", - "👋 Welcome to the Azure Quantum Resource Estimator. In this notebook we will\n", - "guide you how to estimate and analyze the physical resource estimates of a\n", - "quantum program targeted for execution based on the architecture design of a\n", - "fault-tolerant quantum computer. As a running example we are using a multiplier." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Implementing the algorithm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As a first step, we will create a sample application which will be used throughout this Resource Estimation notebook. To start, we'll import some Python packages from `azure.quantum` and `qiskit`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# These are the Python imports that we use in this Qiskit-based example\n", - "\n", - "from azure.quantum.qiskit import AzureQuantumProvider\n", - "from qiskit import QuantumCircuit, transpile\n", - "from qiskit.circuit.library import RGQFTMultiplier\n", - "from qiskit.tools.monitor import job_monitor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are creating a quantum circuit for a multiplier based on the construction presented in [arXiv:1411.5949](https://arxiv.org/abs/1411.5949) which uses the Quantum Fourier Transform to implement arithmetic. You can adjust the size of the multiplier by changing the `bitwidth` variable. The circuit generation is wrapped in a function that can be called with the bitwidth of the multiplier. The circuit will have two input registers with that bitwidth, and one output register with the size of twice the bitwidth. The function will also print some logical resource counts for the multiplier extracted directly from the quantum circuit." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def create_algorithm(bitwidth, backend):\n", - " print(f\"[INFO] Create a QFT-based multiplier with bitwidth {bitwidth}\")\n", - " \n", - " # Print a warning for large bitwidths that will require some time to generate and\n", - " # transpile the circuit.\n", - " if bitwidth > 18:\n", - " print(f\"[WARN] It will take more than one minute generate a quantum circuit with a bitwidth larger than 18\")\n", - "\n", - " circ = RGQFTMultiplier(num_state_qubits=bitwidth, num_result_qubits=2 * bitwidth)\n", - "\n", - " # One could further reduce the resource estimates by increasing the optimization_level,\n", - " # however, this will also increase the runtime to construct the algorithm. Note, that\n", - " # it does not affect the runtime for resource estimation.\n", - " print(f\"[INFO] Decompose circuit into intrinsic quantum operations\")\n", - "\n", - " # retrieve basis gates from backend\n", - " basis_gates = backend.configuration().basis_gates\n", - " circ = transpile(circ, basis_gates=basis_gates, optimization_level=0)\n", - "\n", - " # print some statistics\n", - " print(f\"[INFO] qubit count: {circ.num_qubits}\")\n", - " print(\"[INFO] gate counts\")\n", - " for gate, count in circ.count_ops().items():\n", - " print(f\"[INFO] - {gate}: {count}\")\n", - "\n", - " return circ" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Estimating the algorithm\n", - "\n", - "Let's connect to the Azure Quantum workspace and create a backend instance for\n", - "the Azure Quantum Resource Estimator. The backend acts as a target for our\n", - "quantum computing jobs. Examples for other backends include QPUs to execute\n", - "quantum programs on today's quantum computers, or simulators to simulate the\n", - "functional behavior of a quantum program. You can find examples for such\n", - "backends in other notebooks in the _Sample Gallery_." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "provider = AzureQuantumProvider (\n", - " resource_id = \"\",\n", - " location = \"\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "backend = provider.get_backend('microsoft.estimator')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we will create an instance of our algorithm using the `create_algorithm` function. You can adjust the size of the multiplier by changing the `bitwidth` variable." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bitwidth = 4\n", - "\n", - "circ = create_algorithm(bitwidth, backend)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's now estimate the physical resources for this circuit using the default assumptions. We can submit the circuit to the Resource Estimation backend using its `run` method. Afterwards, we'll run `job_monitor` to await completion." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job = backend.run(circ)\n", - "job_monitor(job)\n", - "result = job.result()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The simplest way to inspect the results of the job is to output them to the notebook. This will output a table with the overall physical resource counts. You can further inspect more details about the resource estimates by collapsing various groups which have more information. For example, if you collapse the *Logical qubit parameters* group, you can see that the quantum error correction (QEC) code distance is 15. In the last group you can see the physical qubit properties that were assumed for this estimation. For example, we see that the time to perform a single-qubit measurement and a single-qubit gate are assumed to be 100 ns and 50 ns, respectively." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you prefer a more compact version of the table, in which the descriptions are\n", - "provided by means of tooltips, you can write:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result.summary" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also programmatically access all the values that can be passed to the job execution and see which default values were assumed:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result.data()[\"jobParams\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We see that there are three input parameters that can be customized: `qubitParams`, `qecScheme`, and `errorBudget`.\n", - "\n", - "### Qubit parameters\n", - "\n", - "The first parameter `qubitParams` is used to specify qubit parameters. When\n", - "modeling the physical qubit abstractions, we distinguish between two different\n", - "physical instruction sets that are used to operate the qubits. The physical\n", - "instruction set can be either *gate-based* or *Majorana*. A gate-based\n", - "instruction set provides single-qubit measurement, single-qubit gates (incl. T\n", - " gates), and two-qubit gates. A Majorana instruction set provides a physical T\n", - " gate, single-qubit measurement and two-qubit joint measurement operations.\n", - "\n", - "Qubit parameters can be completely customized. Before we show this, we show hot\n", - "to choose from six pre-defined qubit parameters, four of which have gate-based\n", - "instruction sets and two with a Majorana instruction set. An overview of all\n", - "pre-defined qubit parameters is provided by the following table:\n", - "\n", - "| Pre-defined qubit parameters | Instruction set | References |\n", - "|------------------------------|-----------------|------------------------------------------------------------------------------------------------------------|\n", - "| `\"qubit_gate_ns_e3\"` | gate-based | [arXiv:2003.00024](https://arxiv.org/abs/2003.00024), [arXiv:2111.11937](https://arxiv.org/abs/2111.11937) |\n", - "| `\"qubit_gate_ns_e4\"` | gate-based | [arXiv:2003.00024](https://arxiv.org/abs/2003.00024), [arXiv:2111.11937](https://arxiv.org/abs/2111.11937) |\n", - "| `\"qubit_gate_us_e3\"` | gate-based | [arXiv:1701.04195](https://arxiv.org/abs/1701.04195) |\n", - "| `\"qubit_gate_us_e4\"` | gate-based | [arXiv:1701.04195](https://arxiv.org/abs/1701.04195) |\n", - "| `\"qubit_maj_ns_e4\"` | Majorana | [arXiv:1610.05289](https://arxiv.org/abs/1610.05289) |\n", - "| `\"qubit_maj_ns_e6\"` | Majorana | [arXiv:1610.05289](https://arxiv.org/abs/1610.05289) |\n", - "\n", - "Pre-defined qubit parameters can be selected by specifying the `name` field in\n", - "the `qubitParams`. If no value is provided, `\"qubit_gate_ns_e3\"` is chosen as\n", - "the default qubit parameters.\n", - "\n", - "Let's re-run resource estimation for our running example on the Majorana-based\n", - "qubit parameters `\"qubit_maj_ns_e6\"`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job = backend.run(circ,\n", - " qubitParams={\n", - " \"name\": \"qubit_maj_ns_e6\"\n", - " })\n", - "job_monitor(job)\n", - "result = job.result()\n", - "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's inspect the physical counts programmatically. For example, we can show all physical resource estimates and their breakdown using the `physicalCounts` field in the result data. This will show the logical qubit error and logical T-state error rates required to match the error budget. By default runtimes are shown in nanoseconds." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result.data()[\"physicalCounts\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also explore details about the T factory that was created to execute this algorithm." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result.data()[\"tfactory\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we are using this data to produce some explanations of how the T factories produce the required T states." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = result.data()\n", - "tfactory = data[\"tfactory\"]\n", - "breakdown = data[\"physicalCounts\"][\"breakdown\"]\n", - "producedTstates = breakdown[\"numTfactories\"] * breakdown[\"numTfactoryRuns\"] * tfactory[\"numTstates\"]\n", - "\n", - "print(f\"\"\"A single T factory produces {tfactory[\"logicalErrorRate\"]:.2e} T states with an error rate of (required T state error rate is {breakdown[\"requiredLogicalTstateErrorRate\"]:.2e}).\"\"\")\n", - "print(f\"\"\"{breakdown[\"numTfactories\"]} copie(s) of a T factory are executed {breakdown[\"numTfactoryRuns\"]} time(s) to produce {producedTstates} T states ({breakdown[\"numTstates\"]} are required by the algorithm).\"\"\")\n", - "print(f\"\"\"A single T factory is composed of {tfactory[\"numRounds\"]} rounds of distillation:\"\"\")\n", - "for round in range(tfactory[\"numRounds\"]):\n", - " print(f\"\"\"- {tfactory[\"numUnitsPerRound\"][round]} {tfactory[\"unitNamePerRound\"][round]} unit(s)\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Custom qubit parameters must completely specify all required parameters. These are the values that are\n", - "considered when the `instructionSet` is `\"GateBased\"`.\n", - "\n", - "| Field (*required) | Description |\n", - "|---------------------------------|----------------------------------------------------------------------|\n", - "| `name` | Some descriptive name for the parameters |\n", - "| `oneQubitMeasurementTime`* | Operation time for single-qubit measurement ($t_{\\rm meas}$) in ns |\n", - "| `oneQubitGateTime`* | Operation time for single-qubit Clifford gate ($t_{\\rm gate}$) in ns |\n", - "| `twoQubitGateTime` | Operation time for two-qubit Clifford gate in ns |\n", - "| `tGateTime` | Operation time for single-qubit non-Clifford gate in ns |\n", - "| `oneQubitMeasurementErrorRate`* | Error rate for single-qubit measurement |\n", - "| `oneQubitGateErrorRate`* | Error rate for single-qubit Clifford gate ($p$) |\n", - "| `twoQubitGateErrorRate` | Error rate for two-qubit Clifford gate |\n", - "| `tGateErrorRate` | Error rate to prepare single-qubit non-Clifford state ($p_T$) |\n", - "\n", - "The values for `twoQubitGateTime` and `tGateTime` default to `oneQubitGateTime`\n", - "when not specified; the values for `twoQubitGateErrorRate` and `tGateErrorRate`\n", - "default to `oneQubitGateErrorRate` when not specified.\n", - "\n", - "A minimum template for qubit parameters based on a gate-based instruction set\n", - "with all required values is:\n", - "\n", - "```json\n", - "{\n", - " \"qubitParams\": {\n", - " \"instructionSet\": \"GateBased\",\n", - " \"oneQubitMeasurementTime\":