diff --git a/docs/tutorials/repetition-codes.ipynb b/docs/tutorials/repetition-codes.ipynb index 0213808d271..5255b2e1f5e 100644 --- a/docs/tutorials/repetition-codes.ipynb +++ b/docs/tutorials/repetition-codes.ipynb @@ -15,17 +15,56 @@ "description: This tutorial demonstrates how to build basic repetition codes using IBM dynamic circuits, an example of basic quantum error correction (QEC).\n", "---\n", "\n", + "{/* cspell:ignore pcov REPCODE creference qreference csyndrome qsyndrome qdata fontsize ytick labelsize xtick */}\n", "\n", "# Repetition codes\n", - "*Usage estimate: less than 1 minute on a Heron processor (NOTE: This is an estimate only. Your runtime might vary.)*\n", + "*Usage estimate: less than 10 seconds on a Heron processor (NOTE: This is an estimate only. Your runtime might vary.)*" + ] + }, + { + "cell_type": "markdown", + "id": "4048d4c9", + "metadata": {}, + "source": [ + "## Learning outcomes\n", + "\n", + "After completing this tutorial, you can expect to understand the following information:\n", "\n", + "- How to implement a bit-flip error correction code using dynamic circuits\n", + "- How to measure stabilizers to detect quantum errors without destroying encoded information\n", + "- How to evaluate the performance of quantum error correction by comparing corrected and uncorrected results" + ] + }, + { + "cell_type": "markdown", + "id": "9012c057", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "It is recommended that you familiarize yourself with these topics:\n", + "\n", + "- [Dynamic circuits](/docs/guides/classical-feedforward-and-control-flow)\n", + "- [Quantum error correction fundamentals](https://arxiv.org/abs/0905.2794)" + ] + }, + { + "cell_type": "markdown", + "id": "0d182acd", + "metadata": {}, + "source": [ "## Background\n", "\n", "To enable real-time quantum error correction (QEC), you need to be able to dynamically control quantum program flow during execution so that quantum gates can be conditioned on measurement results. This tutorial runs the bit-flip code, which is a very simple form of QEC. It demonstrates a dynamic quantum circuit that can protect an encoded qubit from a single bit-flip error, and then evaluates the bit-flip code performance.\n", "\n", "You can exploit additional ancilla qubits and entanglement to measure *stabilizers* that do not transform encoded quantum information, while still informing you of some classes of errors that might have occurred. A quantum stabilizer code encodes $k$ logical qubits into $n$ physical qubits. Stabilizer codes critically focus on correcting a discrete error set with support from the Pauli group $\\Pi^n$.\n", "\n", - "For more information about QEC, refer to [Quantum Error Correction for Beginners](https://arxiv.org/abs/0905.2794)." + "\n", + "In this tutorial, we demonstrate the bit-flip code using a simple quantum memory experiment. We will prepare the encoded qubit in the logical state $|\\bar{1}\\rangle \\equiv |111\\rangle$ and then implement multiple cycles of idle time (to accrue errors) followed by error detection and correction. We then quantify the probability of a logical error as a function of the number of cycles (time), where the logical error probability is the probability of finding the qubits in a state that doesn't recover the $|\\bar{1}\\rangle$ after final measurement (concretely, states corresponding to an error are $|000\\rangle$, $|001\\rangle$, $|010\\rangle$, and $|100\\rangle$).\n", + "\n", + "We will compare the error rate versus time to the error rates of individual, unencoded qubits, and also to use of the repetition code where we only detect and correct errors after final measurement, but not dynamically during the circuit.\n", + "\n", + "Note: the repetition code only allows for correction of bit-flip errors and therefore is not a complete error correction code. However, because of its simplicity, it is a good starting point to illustrate how to implement error correction on a quantum computer. The memory experiment below only tests for a single type of error (a $|\\bar{1}\\rangle$ decaying to $|\\bar{0}\\rangle$) and technically only demonstrates the protection of classical information." ] }, { @@ -49,6 +88,29 @@ "## Setup" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "71cb35d9", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import seaborn as sns\n", + "import matplotlib.pyplot as plt\n", + "from scipy.optimize import curve_fit\n", + "\n", + "sns.set()\n", + "plt.rc(\"xtick\", labelsize=20)\n", + "plt.rc(\"ytick\", labelsize=20)\n", + "plt.rc(\"lines\", linewidth=3)\n", + "plt.rc(\"font\", size=20)\n", + "plt.rc(\"legend\", fontsize=\"large\")\n", + "plt.rc(\"axes\", labelsize=20)\n", + "plt.rcParams[\"figure.figsize\"] = 15, 6\n", + "plt.rcParams[\"legend.title_fontsize\"] = 25" + ] + }, { "cell_type": "code", "execution_count": null, @@ -69,7 +131,6 @@ "\n", "# Qiskit Runtime\n", "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler\n", - "\n", "from qiskit_ibm_runtime.circuit import MidCircuitMeasure\n", "\n", "service = QiskitRuntimeService()" @@ -77,20 +138,42 @@ }, { "cell_type": "markdown", - "id": "4d01e8d3", + "id": "8af23cd0", "metadata": {}, "source": [ - "## Step 1. Map classical inputs to a quantum problem" + "### Step 1: Map classical inputs to a quantum problem" ] }, { "cell_type": "markdown", - "id": "cdee0b18", + "id": "02427c7e", "metadata": {}, "source": [ - "### Build a bit-flip stabilizer circuit\n", - "\n", - "The bit-flip code is among the simplest examples of a stabilizer code. It protects the state against a single bit-flip (X) error on any of the encoding qubits. Consider the action of bit-flip error $X$, which maps $|0\\rangle \\rightarrow |1\\rangle$ and $|1\\rangle \\rightarrow |0\\rangle$ on any of our qubits, then we have $\\epsilon = \\{E_0, E_1, E_2 \\} = \\{IIX, IXI, XII\\}$. The code requires five qubits: three are used to encode the protected state, and the remaining two are used as stabilizer measurement ancillas." + "#### Choose a backend\n", + "To detect errors during the circuit, we need to choose a backend that has access to the `MidCircuitMeasure` method (see the [documentation](/docs/guides/execute-dynamic-circuits#mid-circuit-measurements))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1512c805", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# You can see all backends that support mid-circuit measurements by running the following code.\n", + "service.backends(filters=lambda b: \"measure_2\" in b.supported_instructions)" ] }, { @@ -98,54 +181,91 @@ "execution_count": null, "id": "b588703a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ibm_boston\n" + ] + } + ], "source": [ - "# Choose the least busy backend that supports `measure_2`.\n", + "# Choose the least busy backend that supports mid-circuit measurements (`measure_2`).\n", "\n", "backend = service.least_busy(\n", " filters=lambda b: \"measure_2\" in b.supported_instructions,\n", " operational=True,\n", " simulator=False,\n", " dynamic_circuits=True,\n", - ")" + ")\n", + "# backend = service.backend(backend_name) # alternatively, you could choose a specific backend\n", + "print(backend.name)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "606dff18", + "cell_type": "markdown", + "id": "09b920c2", "metadata": {}, - "outputs": [], "source": [ - "qreg_data = QuantumRegister(3)\n", - "qreg_measure = QuantumRegister(2)\n", - "creg_data = ClassicalRegister(3, name=\"data\")\n", - "creg_syndrome = ClassicalRegister(2, name=\"syndrome\")\n", - "state_data = qreg_data[0]\n", - "ancillas_data = qreg_data[1:]\n", + "#### Build a sequence of bit-flip stabilizer circuits implementing multiple rounds of error detection and correction\n", "\n", + "The bit-flip code is among the simplest examples of a stabilizer code. It protects the state against a single bit-flip (X) error on any of the encoding qubits. Consider the action of bit-flip error $X$, which maps $|0\\rangle \\rightarrow |1\\rangle$ and $|1\\rangle \\rightarrow |0\\rangle$ on any of our qubits, then we have $\\epsilon = \\{E_0, E_1, E_2 \\} = \\{IIX, IXI, XII\\}$. The code requires five qubits: three are used to encode the protected state (the \"data qubits\"), and the remaining two are used as stabilizer measurement ancillas.\n", "\n", - "def build_qc():\n", + "Below you will build circuits that (1) prepare the data qubits in the logical $|\\bar{1} \\rangle$ state, then (2) run multiple cycles of a $5 \\mu s$ delay followed by error correction (including reset of the syndrome qubits), and (3) read out the state of the data qubits.\n", + "\n", + "We will also test the lifetime of the $|1\\rangle$ state without using error correction by including three reference qubits that we prepare in the $|1\\rangle$ state, leave idle, and then read out." + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "id": "d36817e7", + "metadata": {}, + "outputs": [], + "source": [ + "def build_qc(\n", + " qreg_data,\n", + " qreg_syndrome,\n", + " creg_data,\n", + " creg_syndrome,\n", + " qreg_ref=None,\n", + " creg_ref=None,\n", + "):\n", " \"\"\"Build a typical error correction circuit\"\"\"\n", - " return QuantumCircuit(qreg_data, qreg_measure, creg_data, creg_syndrome)\n", + " if qreg_ref:\n", + " return QuantumCircuit(\n", + " qreg_data,\n", + " qreg_syndrome,\n", + " creg_data,\n", + " creg_syndrome,\n", + " qreg_ref,\n", + " creg_ref,\n", + " )\n", + " else:\n", + " return QuantumCircuit(\n", + " qreg_data, qreg_syndrome, creg_data, creg_syndrome\n", + " )\n", + "\n", + "\n", + "def encode_bit_flip(circuit, qreg_data, qreg_ref=None) -> QuantumCircuit:\n", + " \"\"\"Encode bit-flip. This is done by simply adding a cx\"\"\"\n", "\n", + " for q in qreg_data:\n", + " circuit.x(q)\n", "\n", - "def initialize_qubits(circuit: QuantumCircuit):\n", - " \"\"\"Initialize qubit to |1>\"\"\"\n", - " circuit.x(qreg_data[0])\n", - " circuit.barrier(qreg_data)\n", - " return circuit\n", + " if qreg_ref:\n", + " for q in qreg_ref:\n", + " circuit.x(q)\n", "\n", + " circuit.barrier()\n", "\n", - "def encode_bit_flip(circuit, state, ancillas) -> QuantumCircuit:\n", - " \"\"\"Encode bit-flip. This is done by simply adding a cx\"\"\"\n", - " for ancilla in ancillas:\n", - " circuit.cx(state, ancilla)\n", - " circuit.barrier(state, *ancillas)\n", " return circuit\n", "\n", "\n", - "def measure_syndrome_bit(circuit, qreg_data, qreg_measure, creg_measure):\n", + "def measure_syndrome_bit(\n", + " circuit, qreg_data, qreg_syndrome, creg_syndrome, qreg_ref=None\n", + "):\n", " \"\"\"\n", " Measure the syndrome by measuring the parity.\n", " We reset our ancilla qubits after measuring the stabilizer\n", @@ -155,19 +275,21 @@ " avoid another round of qubit measurement if we used\n", " the `reset` instruction.\n", " \"\"\"\n", - " circuit.cx(qreg_data[0], qreg_measure[0])\n", - " circuit.cx(qreg_data[1], qreg_measure[0])\n", - " circuit.cx(qreg_data[0], qreg_measure[1])\n", - " circuit.cx(qreg_data[2], qreg_measure[1])\n", - " circuit.barrier(*qreg_data, *qreg_measure)\n", - " circuit.append(MidCircuitMeasure(), [qreg_measure[0]], [creg_measure[0]])\n", - " circuit.append(MidCircuitMeasure(), [qreg_measure[1]], [creg_measure[1]])\n", - "\n", - " with circuit.if_test((creg_measure[0], 1)):\n", - " circuit.x(qreg_measure[0])\n", - " with circuit.if_test((creg_measure[1], 1)):\n", - " circuit.x(qreg_measure[1])\n", - " circuit.barrier(*qreg_data, *qreg_measure)\n", + " circuit.cx(qreg_data[0], qreg_syndrome[0])\n", + " circuit.cx(qreg_data[1], qreg_syndrome[0])\n", + " circuit.cx(qreg_data[0], qreg_syndrome[1])\n", + " circuit.cx(qreg_data[2], qreg_syndrome[1])\n", + " circuit.barrier()\n", + "\n", + " for q_measure, c_measure in zip(qreg_syndrome, creg_syndrome):\n", + " circuit.append(MidCircuitMeasure(), [q_measure], [c_measure])\n", + "\n", + " with circuit.if_test((creg_syndrome[0], 1)):\n", + " circuit.x(qreg_syndrome[0])\n", + " with circuit.if_test((creg_syndrome[1], 1)):\n", + " circuit.x(qreg_syndrome[1])\n", + "\n", + " circuit.barrier()\n", " return circuit\n", "\n", "\n", @@ -179,61 +301,105 @@ " circuit.x(qreg_data[1])\n", " with circuit.if_test((creg_syndrome, 2)):\n", " circuit.x(qreg_data[2])\n", - " circuit.barrier(qreg_data)\n", + " circuit.barrier()\n", " return circuit\n", "\n", "\n", - "def apply_final_readout(circuit, qreg_data, creg_data):\n", + "def apply_final_readout(\n", + " circuit, qreg_data, creg_data, qreg_ref=None, creg_ref=None\n", + "):\n", " \"\"\"Read out the final measurements\"\"\"\n", - " circuit.barrier(qreg_data)\n", + "\n", + " circuit.barrier()\n", + " if qreg_ref:\n", + " circuit.measure(qreg_ref, creg_ref)\n", " circuit.measure(qreg_data, creg_data)\n", + "\n", " return circuit" ] }, { "cell_type": "code", "execution_count": null, - "id": "dbe02949", + "id": "0e8ea0be", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, - "execution_count": 5, + "execution_count": 195, "metadata": {}, "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ - "def build_error_correction_sequence(apply_correction: bool) -> QuantumCircuit:\n", - " circuit = build_qc()\n", - " circuit = initialize_qubits(circuit)\n", - " circuit = encode_bit_flip(circuit, state_data, ancillas_data)\n", - " circuit = measure_syndrome_bit(\n", - " circuit, qreg_data, qreg_measure, creg_syndrome\n", + "def build_error_correction_sequence(\n", + " num_cycles, cycles_per_circuit, nq_ref=3, delay=None\n", + ") -> QuantumCircuit:\n", + " circuits = []\n", + " reps = []\n", + "\n", + " qreg_data = QuantumRegister(3, name=\"qdata\")\n", + " qreg_syndrome = QuantumRegister(2, name=\"qsyndrome\")\n", + " creg_data = ClassicalRegister(3, name=\"cdata\")\n", + " creg_syndrome = ClassicalRegister(2, name=\"csyndrome\")\n", + " qreg_ref = QuantumRegister(nq_ref, name=\"qreference\")\n", + " creg_ref = ClassicalRegister(nq_ref, name=\"creference\")\n", + "\n", + " circuit = build_qc(\n", + " qreg_data,\n", + " qreg_syndrome,\n", + " creg_data,\n", + " creg_syndrome,\n", + " qreg_ref=qreg_ref,\n", + " creg_ref=creg_ref,\n", " )\n", + " circuit = encode_bit_flip(circuit, qreg_data, qreg_ref=qreg_ref)\n", "\n", - " if apply_correction:\n", + " circuit_n = circuit.copy()\n", + " circuit_n = apply_final_readout(\n", + " circuit_n, qreg_data, creg_data, qreg_ref=qreg_ref, creg_ref=creg_ref\n", + " )\n", + " circuits.append(circuit_n)\n", + " reps.append(0)\n", + "\n", + " for i in range(1, num_cycles + 1):\n", + " if delay:\n", + " circuit.delay(delay, unit=\"us\")\n", + " circuit.barrier()\n", + " circuit = measure_syndrome_bit(\n", + " circuit,\n", + " qreg_data,\n", + " qreg_syndrome,\n", + " creg_syndrome,\n", + " qreg_ref=qreg_ref,\n", + " )\n", " circuit = apply_correction_bit(circuit, qreg_data, creg_syndrome)\n", - "\n", - " circuit = apply_final_readout(circuit, qreg_data, creg_data)\n", - " return circuit\n", - "\n", - "\n", - "circuit = build_error_correction_sequence(apply_correction=True)\n", - "circuit.draw(output=\"mpl\", style=\"iqp\", cregbundle=False)" + " circuit_n = circuit.copy()\n", + " if i % cycles_per_circuit == 0:\n", + " circuit_n = apply_final_readout(\n", + " circuit_n,\n", + " qreg_data,\n", + " creg_data,\n", + " qreg_ref=qreg_ref,\n", + " creg_ref=creg_ref,\n", + " )\n", + " circuits.append(circuit_n)\n", + " reps.append(i)\n", + "\n", + " return circuits, np.array(reps)\n", + "\n", + "\n", + "num_cycles = 40\n", + "cycles_per_circuit = 4\n", + "nq_ref = 3\n", + "circuits, rep_array = build_error_correction_sequence(\n", + " num_cycles, cycles_per_circuit, nq_ref=3, delay=5\n", + ")\n", + "circuits[1].draw(output=\"mpl\", cregbundle=False, fold=50)" ] }, { @@ -241,9 +407,9 @@ "id": "609c0c47", "metadata": {}, "source": [ - "## Step 2. Optimize the problem for quantum execution\n", + "### Step 2: Optimize the problem for quantum hardware execution\n", "\n", - "To reduce the total job execution time, Qiskit primitives only accept circuits and observables that conforms to the instructions and connectivity supported by the target system (referred to as instruction set architecture (ISA) circuits and observables). [Learn more about transpilation](/docs/guides/transpile)." + "To reduce the total job execution time, Qiskit primitives only accept circuits and observables that conform to the instructions and connectivity supported by the target system (referred to as instruction set architecture (ISA) circuits and observables). [Learn more about transpilation](/docs/guides/transpile)." ] }, { @@ -256,155 +422,223 @@ } }, "source": [ - "### Generate ISA circuits" + "#### Generate ISA circuits" + ] + }, + { + "cell_type": "markdown", + "id": "cc8f818f", + "metadata": {}, + "source": [ + "We will start by finding an initial layout (as in, a selection of physical qubits to map our circuits to) by transpiling the longest of our circuits using the preset pass manager with optimization level 3." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 196, "id": "67b55eef", "metadata": { "slideshow": { "slide_type": "-" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", "\n", - "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n", - "isa_circuit = pm.run(circuit)\n", - "\n", - "isa_circuit.draw(\"mpl\", style=\"iqp\", idle_wires=False)" + "pm = generate_preset_pass_manager(backend=backend, optimization_level=3)\n", + "isa_circuit_ref = pm.run(circuits[-1])" ] }, { "cell_type": "code", - "execution_count": 7, - "id": "67acea4f", + "execution_count": 197, + "id": "3e74193a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[56, 44, 62, 43, 63, 22, 48, 67]\n" + ] + } + ], "source": [ - "no_correction_circuit = build_error_correction_sequence(\n", - " apply_correction=False\n", + "init_layout = isa_circuit_ref.layout.initial_index_layout(\n", + " filter_ancillas=True\n", ")\n", - "\n", - "isa_no_correction_circuit = pm.run(no_correction_circuit)" + "print(init_layout)" ] }, { "cell_type": "markdown", - "id": "bcd61a1f", + "id": "eaca173c", "metadata": {}, "source": [ - "## Step 3. Execute using Qiskit primitives" + "For the reference qubits that we will compare our logical quantum memory to, we will choose them to be the best available qubits in terms of amplitude damping coherence time ($T_1$)." ] }, { - "cell_type": "markdown", - "id": "e68d10d2", + "cell_type": "code", + "execution_count": null, + "id": "2aedd3cb", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[56, 44, 62, 43, 63, 143, 131, 31]\n" + ] + } + ], "source": [ - "Run the version with correction applied and one without correction." + "# get all qubits ordered by T1\n", + "t1_data = []\n", + "for i in range(backend.num_qubits):\n", + " try:\n", + " t1_us = backend.properties().t1(i) * 1e6\n", + " except Exception:\n", + " t1_us = 0.0\n", + " t1_data.append((i, t1_us))\n", + "\n", + "t1_data_sorted = sorted(t1_data, key=lambda x: x[1], reverse=True)\n", + "\n", + "# exclude the qubits we have already mapped the error correcting code to\n", + "t1_data_sorted = [\n", + " t1_data for t1_data in t1_data_sorted if t1_data[0] not in init_layout[:5]\n", + "]\n", + "\n", + "# use the best qubits in terms of T1 for the reference qubits\n", + "init_layout = init_layout[:5] + [t1_data[0] for t1_data in t1_data_sorted[:3]]\n", + "print(init_layout)" ] }, { "cell_type": "code", "execution_count": null, - "id": "d53319ba", + "id": "d6a7766a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Amplitude damping decoherence times code data qubits:\n", + "qubit 56: T1 = 322 mus\n", + "qubit 44: T1 = 263 mus\n", + "qubit 62: T1 = 290 mus\n", + "\n", + "Amplitude damping decoherence times reference qubits:\n", + "qubit 143: T1 = 442 mus\n", + "qubit 131: T1 = 410 mus\n", + "qubit 31: T1 = 401 mus\n" + ] + } + ], "source": [ - "sampler_no_correction = Sampler(backend)\n", - "job_no_correction = sampler_no_correction.run(\n", - " [isa_no_correction_circuit], shots=1000\n", - ")\n", - "result_no_correction = job_no_correction.result()[0]" + "# These are the resulting T1 times\n", + "properties = backend.properties()\n", + "print(\"Amplitude damping decoherence times for code data qubits:\")\n", + "for q in init_layout[:3]:\n", + " t1 = properties.t1(q)\n", + " print(f\"qubit {q}: T1 = {t1 * 1e6:.0f} mus\")\n", + "\n", + "print(\"\\nAmplitude damping decoherence times for reference qubits:\")\n", + "for q in init_layout[-3:]:\n", + " t1 = properties.t1(q)\n", + " print(f\"qubit {q}: T1 = {t1 * 1e6:.0f} mus\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "df7421d0", - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, + "id": "ee5da865", + "metadata": {}, "outputs": [], "source": [ - "sampler_with_correction = Sampler(backend)\n", - "\n", - "job_with_correction = sampler_with_correction.run([isa_circuit], shots=1000)\n", - "result_with_correction = job_with_correction.result()[0]" + "# now we transpile all circuits to this initial layout; this way each circuit is run on the same qubits and we can make a fair comparison\n", + "pm = generate_preset_pass_manager(\n", + " backend=backend,\n", + " optimization_level=3,\n", + " initial_layout=init_layout,\n", + ")\n", + "isa_circuits = pm.run(circuits)" ] }, { "cell_type": "code", - "execution_count": 10, - "id": "1cba37f5", + "execution_count": 180, + "id": "67acea4f", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data (no correction):\n", - "{'111': 878, '011': 42, '110': 35, '101': 40, '100': 1, '001': 2, '000': 2}\n", - "Syndrome (no correction):\n", - "{'00': 942, '10': 33, '01': 22, '11': 3}\n" - ] + "data": { + "text/plain": [ + "\"Output" + ] + }, + "execution_count": 180, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "print(f\"Data (no correction):\\n{result_no_correction.data.data.get_counts()}\")\n", - "print(\n", - " f\"Syndrome (no correction):\\n{result_no_correction.data.syndrome.get_counts()}\"\n", - ")" + "isa_circuits[1].draw(\"mpl\", cregbundle=False, fold=50)" + ] + }, + { + "cell_type": "markdown", + "id": "bcd61a1f", + "metadata": {}, + "source": [ + "### Step 3: Execute using Qiskit primitives" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d53319ba", + "metadata": {}, + "outputs": [], + "source": [ + "sampler = Sampler(mode=backend)\n", + "\n", + "sampler.options.environment.job_tags = [\"TUT-REPCODE\"]\n", + "sampler.options.max_execution_time = 600 # this workload is expected to be under 10s, but it is generally a good habit to set a max execution time (here 600s = 10m)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "437397a5", + "metadata": {}, + "outputs": [], + "source": [ + "job = sampler.run(isa_circuits, shots=1000)\n", + "print(job.job_id())" ] }, { "cell_type": "code", - "execution_count": 11, - "id": "7b7697f2", + "execution_count": 201, + "id": "b1025c42", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data (corrected):\n", - "{'111': 889, '110': 25, '000': 11, '011': 45, '101': 17, '010': 10, '001': 2, '100': 1}\n", - "Syndrome (corrected):\n", - "{'00': 929, '01': 39, '10': 20, '11': 12}\n" - ] + "data": { + "text/plain": [ + "'DONE'" + ] + }, + "execution_count": 201, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "print(f\"Data (corrected):\\n{result_with_correction.data.data.get_counts()}\")\n", - "print(\n", - " f\"Syndrome (corrected):\\n{result_with_correction.data.syndrome.get_counts()}\"\n", - ")" + "job.status()" ] }, { @@ -412,95 +646,150 @@ "id": "1b652319", "metadata": {}, "source": [ - "## Step 4. Post-process, return result in classical format\n", + "### Step 4: Post-process and return result in desired classical format\n", "\n", - "You can see that the bit flip code detected and corrected many errors, resulting in fewer errors overall." + "We will now compare the error rates vs. time between the logical memory using the 3-qubit repetition code on the one hand, and the individual unencoded reference qubits on the other hand." ] }, { "cell_type": "code", - "execution_count": 12, - "id": "fa59fb42", - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, + "execution_count": 202, + "id": "ea871eb9", + "metadata": {}, "outputs": [], "source": [ - "def decode_result(data_counts, syndrome_counts):\n", - " shots = sum(data_counts.values())\n", - " success_trials = data_counts.get(\"000\", 0) + data_counts.get(\"111\", 0)\n", - " failed_trials = shots - success_trials\n", - " error_correction_events = shots - syndrome_counts.get(\"00\", 0)\n", - " print(\n", - " f\"Bit flip errors were detected/corrected on {error_correction_events}/{shots} trials.\"\n", - " )\n", - " print(\n", - " f\"A final parity error was detected on {failed_trials}/{shots} trials.\"\n", - " )" + "results = job.result()" ] }, { "cell_type": "code", - "execution_count": 13, - "id": "5b1ff3a3", + "execution_count": null, + "id": "f27099e5", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Completed bit code experiment data measurement counts (no correction): {'111': 878, '011': 42, '110': 35, '101': 40, '100': 1, '001': 2, '000': 2}\n", - "Completed bit code experiment syndrome measurement counts (no correction): {'00': 942, '10': 33, '01': 22, '11': 3}\n", - "Bit flip errors were detected/corrected on 58/1000 trials.\n", - "A final parity error was detected on 120/1000 trials.\n" - ] - } - ], + "outputs": [], "source": [ - "# non-corrected marginalized results\n", - "data_result = result_no_correction.data.data.get_counts()\n", - "marginalized_syndrome_result = result_no_correction.data.syndrome.get_counts()\n", + "def correct_counts(counts_dict):\n", + " \"\"\"\n", + " Corrects the measured logical qubit encoded in the repetition code using majority vote\n", + " \"\"\"\n", "\n", - "print(\n", - " f\"Completed bit code experiment data measurement counts (no correction): {data_result}\"\n", - ")\n", - "print(\n", - " f\"Completed bit code experiment syndrome measurement counts (no correction): {marginalized_syndrome_result}\"\n", - ")\n", - "decode_result(data_result, marginalized_syndrome_result)" + " result = {\"000\": 0, \"111\": 0}\n", + " for bitstring, count in counts_dict.items():\n", + " key = \"111\" if bitstring.count(\"1\") > 1 else \"000\"\n", + " result[key] += count\n", + " return result\n", + "\n", + "\n", + "accuracy = [] # logical qubit\n", + "accuracies_ref = np.zeros(\n", + " (len(results), nq_ref)\n", + ") # accuracies on individual reference qubits\n", + "\n", + "for n, pub_result in enumerate(results):\n", + " # logical accuracy (one minus error probability) for active error correction with repetition code\n", + " counts = pub_result.data.cdata.get_counts()\n", + " shots = sum(counts.values())\n", + " counts_corrected = correct_counts(counts)\n", + " accuracy.append(counts_corrected.get(\"111\", 0) / shots)\n", + "\n", + " # accuracy for individual physical reference qubits without any error correction\n", + " for i in range(nq_ref):\n", + " counts = pub_result.data.creference.slice_bits(i).get_counts()\n", + " accuracies_ref[n, i] = counts.get(\"1\", 0) / shots\n", + "\n", + "accuracy = np.array(accuracy)" ] }, { "cell_type": "code", - "execution_count": 14, - "id": "7f1c2d48", + "execution_count": null, + "id": "72ad282b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Completed bit code experiment data measurement counts (corrected): {'111': 889, '110': 25, '000': 11, '011': 45, '101': 17, '010': 10, '001': 2, '100': 1}\n", - "Completed bit code experiment syndrome measurement counts (corrected): {'00': 929, '01': 39, '10': 20, '11': 12}\n", - "Bit flip errors were detected/corrected on 71/1000 trials.\n", - "A final parity error was detected on 100/1000 trials.\n" + "Best-fit effective T1 = 1397 us\n" ] + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "# corrected marginalized results\n", - "corrected_data_result = result_with_correction.data.data.get_counts()\n", - "corrected_syndrome_result = result_with_correction.data.syndrome.get_counts()\n", + "def error_proba_t1(N, t1):\n", + " \"\"\"\n", + " Exponential fitting function for amplitude damping vs. number of cycles\n", + " \"\"\"\n", + "\n", + " t_cycle = 7.3e-6 # approximate time per cycle = 5 mus delay + 2.3 mus for error correction\n", + " return 1 - np.exp(-t_cycle * N / t1)\n", "\n", - "print(\n", - " f\"Completed bit code experiment data measurement counts (corrected): {corrected_data_result}\"\n", + "\n", + "fig, ax = plt.subplots(1, 1, figsize=(15, 5))\n", + "ax.plot(\n", + " rep_array, (1.0 - accuracy) * 100.0, \"ko-\", linewidth=3, label=\"rep code\"\n", ")\n", - "print(\n", - " f\"Completed bit code experiment syndrome measurement counts (corrected): {corrected_syndrome_result}\"\n", + "\n", + "for i in range(nq_ref):\n", + " accuracy_1q = accuracies_ref[:, i]\n", + " if i == 0:\n", + " ax.plot(\n", + " rep_array,\n", + " (1.0 - accuracy_1q) * 100.0,\n", + " \"go-\",\n", + " linewidth=1,\n", + " label=\"1q reference\",\n", + " )\n", + " else:\n", + " ax.plot(rep_array, (1.0 - accuracy_1q) * 100.0, \"go-\", linewidth=1)\n", + "\n", + "params_bf, pcov = curve_fit(\n", + " error_proba_t1, rep_array, 1.0 - accuracy, bounds=([0, 5e-3])\n", ")\n", - "decode_result(corrected_data_result, corrected_syndrome_result)" + "t1_bf = params_bf[0]\n", + "print(f\"Best-fit effective T1 = {t1_bf * 1e6:.0f} us\")\n", + "error_prob_bf = error_proba_t1(\n", + " rep_array, t1_bf\n", + ") # np.array([1 - np.exp(-t_cycle * nt/t1) for nt in rep_array])\n", + "ax.plot(rep_array, error_prob_bf * 100.0, \"k--\", linewidth=1)\n", + "\n", + "ax.set_xlabel(\"error correction rounds\")\n", + "ax.set_ylabel(\"error [%]\")\n", + "ax.set_ylim(bottom=0)\n", + "ax.legend(fontsize=15);" + ] + }, + { + "cell_type": "markdown", + "id": "49c2f443", + "metadata": {}, + "source": [ + "We clearly see an improved lifetime of the $|\\bar{1}\\rangle$ state, even compared to the best (unencoded) physical qubits. However, keep in mind that this experiment just corrects one type of error, namely bit-flips. Can you improve the lifetime of the logical qubit? You might want to explore optimizing the delay time, scaling up the size of the repetition code beyond $n=3$, etc." + ] + }, + { + "cell_type": "markdown", + "id": "next_steps", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "If you found this work interesting, you might be interested in the following material:\n", + "\n", + "\n", + "\n", + "- [Foundations of quantum error correction course](/learning/courses/foundations-of-quantum-error-correction) - learn more about quantum error correction\n", + "- [Low-overhead error detection with spacetime codes tutorial](/docs/tutorials/ghz-spacetime-codes) - learn how to use Pauli checks to detect errors and post-select samples\n", + "\n", + "" ] }, { diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/0e8ea0be-0.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/0e8ea0be-0.avif new file mode 100644 index 00000000000..4926528d8da Binary files /dev/null and b/public/docs/images/tutorials/repetition-codes/extracted-outputs/0e8ea0be-0.avif differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/67acea4f-0.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/67acea4f-0.avif new file mode 100644 index 00000000000..55e35003927 Binary files /dev/null and b/public/docs/images/tutorials/repetition-codes/extracted-outputs/67acea4f-0.avif differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-0.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-0.avif deleted file mode 100644 index 77e7c6d0c34..00000000000 Binary files a/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-1.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-1.avif deleted file mode 100644 index 77e7c6d0c34..00000000000 Binary files a/public/docs/images/tutorials/repetition-codes/extracted-outputs/67b55eef-1.avif and /dev/null differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/72ad282b-1.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/72ad282b-1.avif new file mode 100644 index 00000000000..bf79862f0ec Binary files /dev/null and b/public/docs/images/tutorials/repetition-codes/extracted-outputs/72ad282b-1.avif differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-0.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-0.avif deleted file mode 100644 index 97c0600aabc..00000000000 Binary files a/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-1.avif b/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-1.avif deleted file mode 100644 index 97c0600aabc..00000000000 Binary files a/public/docs/images/tutorials/repetition-codes/extracted-outputs/dbe02949-1.avif and /dev/null differ