diff --git a/examples/logical_error_rates/8_decoding_tqec_circuits.ipynb b/examples/logical_error_rates/8_decoding_tqec_circuits.ipynb
new file mode 100644
index 000000000..6b873212f
--- /dev/null
+++ b/examples/logical_error_rates/8_decoding_tqec_circuits.ipynb
@@ -0,0 +1,8257 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%capture\n",
+ "%pip install qldpc\n",
+ "%pip install matplotlib\n",
+ "%pip install tqec"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Decoding TQEC Circuits\n",
+ "\n",
+ "[TQEC](https://github.com/tqec/tqec) compiles lattice surgery operations into surface-code-protected physical circuits. This notebook demonstrates using a qLDPC decoder to decode circuits compiled and simulated by TQEC. This notebook is adapted from the [TQEC notebooks](https://github.com/tqec/tqec/tree/main/docs/gallery)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Construction\n",
+ "\n",
+ "`tqec` provides builtin functions for constructing some example computations. You can also define your own lattice surgery operations using `tqec.BlockGraph`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from tqec import Basis\n",
+ "from tqec.gallery import memory, stability, move_rotation, cz, cnot, three_cnots, steane_encoding\n",
+ "\n",
+ "block_graph = cnot(Basis.Z)\n",
+ "block_graph.view_as_html()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can use the `find_correlation_surfaces()` method to identify a generating set of correlation surfaces (observables), and then visualize them using the `view_as_html()` method. We show one of them below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "correlation_surfaces = block_graph.find_correlation_surfaces()\n",
+ "block_graph.view_as_html(\n",
+ " pop_faces_at_directions=(\"-Y\",),\n",
+ " show_correlation_surface=correlation_surfaces[0],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Simulation\n",
+ "\n",
+ "Here we simulate all observables under uniform depolarizing noise model using qLDPC's decoders. TQEC's `start_simulation_using_sinter` is a wrapper around its compiler and `sinter`, so it accepts compilation arguments and `sinter`'s simulation arguments. Arguments like `save_resume_filepath` may come in handy when the simulation is computationally intensive."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "nbsphinx": "hidden"
+ },
+ "outputs": [],
+ "source": [
+ "import numpy\n",
+ "import sinter\n",
+ "from tqec import NoiseModel\n",
+ "from tqec.simulation.simulation import start_simulation_using_sinter\n",
+ "\n",
+ "from qldpc import decoders\n",
+ "\n",
+ "physical_error_rates = list(numpy.logspace(-4, -2, 5))\n",
+ "distances = list(range(3, 8, 2))\n",
+ "\n",
+ "\n",
+ "def simulate(decoders: dict[str, sinter.Decoder]) -> list[list[list[sinter.TaskStats]]]:\n",
+ " # TQEC's `start_simulation_using_sinter` does not support assigning decoders based\n",
+ " # on the distance, so for sliding window decoders, we need to run the simulations\n",
+ " # individually for each code distance and assign the corresponding decoder.\n",
+ " return [\n",
+ " start_simulation_using_sinter(\n",
+ " block_graph,\n",
+ " [d // 2],\n",
+ " physical_error_rates,\n",
+ " NoiseModel.uniform_depolarizing,\n",
+ " manhattan_radius=2,\n",
+ " observables=correlation_surfaces,\n",
+ " max_shots=100_000,\n",
+ " max_errors=500,\n",
+ " decoders=[f\"{d=}\"],\n",
+ " custom_decoders=decoders,\n",
+ " print_progress=True,\n",
+ " )\n",
+ " for d in distances\n",
+ " ]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Regular decoders don't need to be defined for each distance.\n",
+ "# Here, it's for code reuse with sliding window decoders later\n",
+ "custom_decoders = {f\"{d=}\": decoders.SinterDecoder(with_MWPM=True) for d in distances}\n",
+ "\n",
+ "mwpm_stats = simulate(custom_decoders)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Plotting\n",
+ "We plot the logical error rate of each observable separately. Note that since we simulated each distance separately, in the for-loop below, we rearrange them to be grouped by the observables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "import sinter\n",
+ "from tqec.simulation.plotting.inset import plot_observable_as_inset\n",
+ "\n",
+ "\n",
+ "zx_graph = block_graph.to_zx_graph() # for highlighting the correlation surfaces in the ZX diagram\n",
+ "\n",
+ "\n",
+ "def plot(stats: list[list[list[sinter.TaskStats]]]):\n",
+ " for i, stat in enumerate(zip(*stats)):\n",
+ " _, ax = plt.subplots()\n",
+ " sinter.plot_error_rate(\n",
+ " ax=ax,\n",
+ " stats=sum(stat, []),\n",
+ " x_func=lambda s: s.json_metadata[\"p\"],\n",
+ " failure_units_per_shot_func=lambda s: s.json_metadata[\"d\"],\n",
+ " group_func=lambda s: s.json_metadata[\"d\"],\n",
+ " )\n",
+ " plot_observable_as_inset(ax, zx_graph, correlation_surfaces[i])\n",
+ " ax.grid(axis=\"both\")\n",
+ " ax.legend()\n",
+ " ax.loglog()\n",
+ " ax.set_xlabel(\"Physical Error Rate\")\n",
+ " ax.set_ylabel(\"Logical Error Rate\")\n",
+ "\n",
+ "plot(mwpm_stats)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sliding Window Decoding\n",
+ "\n",
+ "Now we switch to sliding window decoders."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We define the sliding window decoder for each code distance\n",
+ "custom_decoders = {\n",
+ " f\"{d=}\": decoders.SlidingWindowDecoder(\n",
+ " d,\n",
+ " d // 2,\n",
+ " with_MWPM=True,\n",
+ " )\n",
+ " for d in distances\n",
+ "}\n",
+ "\n",
+ "sliding_window_mwpm_stats = simulate(custom_decoders)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plot(sliding_window_mwpm_stats)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "qldpc-dev3.13",
+ "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.13.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/pyproject.toml b/pyproject.toml
index 44ec82cc4..628db88e9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,46 +6,44 @@ readme = "README.md"
license = "Apache-2.0"
authors = [{ name = "Michael A. Perlin", email = "mika.perlin@gmail.com" }]
keywords = [
- "quantum computing",
- "quantum error correction",
- "low density parity check codes",
- "LDPC",
+ "quantum computing",
+ "quantum error correction",
+ "low density parity check codes",
+ "LDPC",
]
classifiers = [
- "Development Status :: 3 - Alpha",
- "Intended Audience :: Science/Research",
- "Natural Language :: English",
- "Topic :: Scientific/Engineering :: Mathematics",
- "Topic :: Scientific/Engineering :: Physics",
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Science/Research",
+ "Natural Language :: English",
+ "Topic :: Scientific/Engineering :: Mathematics",
+ "Topic :: Scientific/Engineering :: Physics",
]
requires-python = ">=3.10"
dependencies = [
- "cvxpy>=1.3.2",
- "diskcache>=5.0.0",
- "galois>=0.4.2",
- "ldpc>=2.4.1",
- "networkx>=2.6.2",
- "numpy>=1.24.0",
- "platformdirs>=4.0.0",
- "pymatching>=2.1.0",
- "pyperclip>=1.11.0",
- "scipy>=1.14.1",
- "sinter>=1.15.0",
- "stim>=1.16.dev1768963940",
- "sympy>=1.12",
+ "cvxpy>=1.3.2",
+ "diskcache>=5.0.0",
+ "galois>=0.4.2",
+ "ldpc>=2.4.1",
+ "networkx>=2.6.2",
+ "numpy>=1.24.0",
+ "platformdirs>=4.0.0",
+ "pymatching>=2.1.0",
+ "pyperclip>=1.11.0",
+ "scipy>=1.14.1",
+ "sinter>=1.15.0",
+ "stim>=1.16.dev1768963940,<1.16.dev1776477515", # 1.16.dev1776477515 doesn't have pre-built wheels for Python 3.12
+ "sympy>=1.12",
]
[project.optional-dependencies]
dev = [
- "checks-superstaq>=0.5.62",
- "jupyter>=1.1.1",
- "python-lsp-server[all]>=1.14.0",
- "relay-bp[stim]==0.2.1",
-]
-relay-bp = [
- "relay-bp[stim]==0.2.1",
+ "checks-superstaq>=0.5.62",
+ "jupyter>=1.1.1",
+ "python-lsp-server[all]>=1.14.0",
+ "relay-bp[stim]==0.2.1",
]
+relay-bp = ["relay-bp[stim]==0.2.1"]
[project.urls]
Repository = "https://github.com/qLDPCOrg/qLDPC"
@@ -75,7 +73,7 @@ no_implicit_optional = true
[tool.pytest.ini_options]
addopts = "--disable-socket" # forbid tests from making network calls
filterwarnings = [
- 'ignore:(?s).*The problem is either infeasible or unbounded.*:UserWarning', # from cvxpy
+ 'ignore:(?s).*The problem is either infeasible or unbounded.*:UserWarning', # from cvxpy
]
[tool.coverage.report]