Skip to content

Repository files navigation

mdopt — code-agnostic tensor-network (MPS-MPO) decoder for quantum error-correcting codes.

logo

codecov tests DOI Documentation Status pre-commit.ci status lint mypy CI Unitary Fund MIT license

mdopt is a python package built on top of numpy for discrete optimisation (with the main application to classical and quantum decoding) in the tensor-network (specifically, Matrix Product States / Operators) language. The intended audience includes physicists, quantum information / error correction researchers, and those interested in exploring tensor-network methods beyond traditional applications.

Installation

To install the current release, use the package manager pip.

pip install mdopt

Otherwise, you can clone the repository and use uv.

uv sync

uv sync --group dev --group test also installs the development and test tools.

A note on NumPy's BLAS on Apple silicon

On Apple silicon running macOS 14 or later, pip installs the NumPy and SciPy wheels tagged macosx_14_0_arm64, which link Apple's Accelerate framework. On mdopt's matrices (rank-deficient, with singular values spanning many orders of magnitude) Accelerate's LAPACK corrupted memory: numpy.linalg.qr died with SIGBUS on a 636x304 matrix, and numpy.linalg.svd tripped malloc's heap check. How much this showed in the decoding depended on the bond dimension. On the [[72,12,6]] bivariate-bicycle code in the natural qubit order, at chi_max=400, where the SVDs act on matrices of about 800x1600, 21 of 22 shots with a non-trivial error decoded wrongly while mdopt still reduced its SVDs by QR first, against 3 of 24 on OpenBLAS, and every unit test still passed. The same test shot decoded correctly at chi_max 64, 128 and 256. Not seeing the fault at those smaller bond dimensions is no guarantee, because it depends on the state of the heap. mdopt's SVD helpers call both libraries, and mdopt warns at import when it finds Accelerate behind either one. The same versions are also published as OpenBLAS builds, the macosx_11_0_arm64 NumPy wheel and the macosx_12_0_arm64 SciPy wheel, which install on the same machines:

pip download "numpy==$(python -c 'import numpy; print(numpy.__version__)')" \
    --platform macosx_11_0_arm64 --only-binary=:all: --no-deps -d /tmp/openblas-wheels
pip download "scipy==$(python -c 'import scipy; print(scipy.__version__)')" \
    --platform macosx_12_0_arm64 --only-binary=:all: --no-deps -d /tmp/openblas-wheels
pip install --force-reinstall --no-deps /tmp/openblas-wheels/*.whl

In a uv checkout, a reinstall like this lasts only until the next uv run or uv sync, which put the locked Accelerate wheels back. Sync against a macOS 13 target instead (adding your usual --group flags). It selects the same OpenBLAS wheels, and later uv run and uv sync calls keep them. Run it again whenever the lock moves NumPy or SciPy to a new version:

uv sync --python-platform aarch64-apple-darwin

Set MDOPT_ALLOW_ACCELERATE=1 to silence the warning if you must keep Accelerate. Use OMP_NUM_THREADS=1 (or OPENBLAS_NUM_THREADS=1) for the per-process BLAS of worker pools.

Minimal example

import logging

import numpy as np
import qecstruct as qec
from mdopt.decoding import decode_css

# The library does not configure logging; opt in to see progress with silent=False.
logging.basicConfig(level=logging.INFO)

# Define a small instance of the surface code
LATTICE_SIZE = 3
surface_code = qec.hypergraph_product(
    qec.repetition_code(LATTICE_SIZE),
    qec.repetition_code(LATTICE_SIZE),
)

# Input an error and choose decoder controls
logicals, success = decode_css(
    code=surface_code,
    error="IIXIIIIIIIIII",
    bias_prob=0.01,
    bias_type="Bitflip",
    chi_max=64,
    renormalise=True,
    contraction_strategy="Optimised",
    tolerance=1e-12,
    silent=False,
)

Decoding circuit-level noise from a detector error model

Circuit-level noise enters through a stim detector error model (DEM). Every error mechanism becomes one MPS site, each detector a parity-check (XOR) constraint and each logical observable a readout, so the same MPS-MPO machinery returns the maximum-likelihood observable flip for a sampled syndrome.

import stim
from mdopt.decoding import decode_dem, dem_to_problem

circuit = stim.Circuit.generated(
    "surface_code:rotated_memory_x",
    distance=3,
    rounds=3,
    after_clifford_depolarization=0.008,
    before_measure_flip_probability=0.008,
    after_reset_flip_probability=0.008,
)
# Maximum likelihood wants the undecomposed hyperedges, so keep decompose_errors off.
problem = dem_to_problem(
    circuit.detector_error_model(decompose_errors=False, flatten_loops=True)
)
sampler = circuit.compile_detector_sampler(seed=1)
detections, observables = sampler.sample(1, separate_observables=True)
class_masses, predicted_flips = decode_dem(problem, detections[0].astype(int), chi_max=32)
print(predicted_flips, observables[0].astype(int))

decode_dem returns one non-normalized weight per observable-flip class together with the most likely class; divide class_masses by class_masses.sum() to obtain probabilities. A materially negative, non-finite, or collapsed class-mass vector raises an ArithmeticError, but a successful contraction does not by itself certify convergence. For reliable results, decode at increasing chi_max values and require the normalized class weights and prediction to stabilize. The harnesses behind the decoder's validation (code-capacity thresholds of the surface code against minimum-weight perfect matching, a d=5 circuit-level cell decoded on a per-shot bond-dimension ladder with a calibration audit, and a reproduction of Fig. 1d of Piveteau, Chubb and Renes, PRX Quantum 5, 040303) live in examples/decoding/dem_campaign together with a README on how every number is regenerated.

Package layout

  • mdopt.mps — matrix product states in the explicit (Vidal) and canonical forms.
  • mdopt.contractor — MPS-MPO zip-up contraction with truncation.
  • mdopt.optimiser — DMRG, dephasing DMRG, and the parity-check MPO tensors applied by apply_constraints.
  • mdopt.decoding — the decoders: decode_css, decode_custom and decode_message for stabiliser and classical codes, decode_dem for detector error models.
  • mdopt.utils — truncated SVD/QR and tensor helpers.
  • mdopt.examples — the runnable campaign scripts behind the example notebooks.

The decoder modules used to live at mdopt.examples.decoding.decoding and mdopt.examples.decoding.dem. Those import paths still work, but emit a DeprecationWarning and will be removed in a future release; import from mdopt.decoding instead.

Examples

The examples folder contains full workflows that demonstrate typical use cases, such as quantum / classical LDPC code decoding, ground state search for the quantum Ising model and random quantum circuit simulation. Each example is fully documented and serves as a starting point for building your own experiments. The shell scripts in examples/decoding run the Monte Carlo campaigns behind the notebooks, locally (*.sh) or as Slurm jobs (*_cc.sh), and the detector-error-model harnesses live in examples/decoding/dem_campaign. The package has been tested on macOS and Linux (Compute Canada clusters) and does not currently support Windows.

Cite

If you happen to find mdopt useful in your work, please consider supporting development by citing it.

@article{berezutskii2025mdopt,
  title={mdopt: A code-agnostic tensor-network decoder for quantum error-correcting codes},
  author={Berezutskii, Aleksandr},
  journal={Journal of Open Source Software},
  volume={10},
  number={115},
  pages={9125},
  year={2025}
}

Contribution guidelines

If you want to contribute to mdopt, be sure to follow GitHub's contribution guidelines. This project adheres to our code of conduct. By participating, you are expected to uphold this code.

We use GitHub issues for tracking requests and bugs, please direct specific questions to the maintainers.

The mdopt project strives to abide by generally accepted best practices in open-source software development, such as:

  • apply the desired changes and resolve any code conflicts,
  • run the tests and ensure they pass,
  • build the package from source.

Developers may find the following guidelines useful:

  • Running tests. Tests are executed using pytest:

    pytest tests
  • Building documentation. Documentation is built with Sphinx. A convenience script is provided:

    ./generate_docs.sh
  • Coding style. The code is formatted with Ruff, whose style matches Black's. Please run the formatter before submitting a pull request:

    uv run --group dev ruff format .
  • Pre-commit hooks. Pre-commit hooks are configured to enforce consistent style automatically. To enable them:

    pre-commit install

License

This project is licensed under the MIT License.

Documentation

Full documentation is available at mdopt.readthedocs.io.

Releases

Packages

Used by

Contributors

Languages