From f9eb191ec22541620e0587b7901f5a33259e7d72 Mon Sep 17 00:00:00 2001 From: lucas Date: Thu, 2 Nov 2023 21:44:40 +0100 Subject: [PATCH 001/115] init commit --- .../analog-information-decoding/.gitignore | 134 +++ .../qecc/analog-information-decoding/LICENSE | 21 + .../analog-information-decoding/README.md | 52 ++ .../analog-information-decoding/__init__.py | 11 + .../code_construction/__init__.py | 0 .../code_construction/code_constructor.py | 239 ++++++ .../code_construction/compute_distances.sh | 53 ++ .../code_construction/compute_distances_3D.sh | 13 + .../sparse_code_constructor.py | 226 +++++ .../codes/lifted_product/lp_l=16_hx.npz | Bin 0 -> 4580 bytes .../codes/lifted_product/lp_l=16_hz.npz | Bin 0 -> 4568 bytes .../codes/lifted_product/lp_l=21_hx.npz | Bin 0 -> 5849 bytes .../codes/lifted_product/lp_l=21_hz.npz | Bin 0 -> 5781 bytes .../codes/lifted_product/lp_l=30_hx.npz | Bin 0 -> 8007 bytes .../codes/lifted_product/lp_l=30_hz.npz | Bin 0 -> 7926 bytes .../qecc/analog-information-decoding/setup.py | 21 + .../simulators/__init__.py | 0 .../simulators/analog_tannergraph_decoding.py | 536 ++++++++++++ .../simulators/memory_experiment_v2.py | 173 ++++ .../simulators/quasi_single_shot_v2.py | 301 +++++++ .../simulators/simulation.py | 803 ++++++++++++++++++ .../test/test_memory_experiment.py | 61 ++ .../test/test_simulation_utils.py | 197 +++++ .../utils/__init__.py | 2 + .../utils/data_utils.py | 597 +++++++++++++ .../utils/simulation_utils.py | 290 +++++++ 26 files changed, 3730 insertions(+) create mode 100644 src/mqt/qecc/analog-information-decoding/.gitignore create mode 100644 src/mqt/qecc/analog-information-decoding/LICENSE create mode 100644 src/mqt/qecc/analog-information-decoding/README.md create mode 100644 src/mqt/qecc/analog-information-decoding/__init__.py create mode 100644 src/mqt/qecc/analog-information-decoding/code_construction/__init__.py create mode 100644 src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py create mode 100644 src/mqt/qecc/analog-information-decoding/code_construction/compute_distances.sh create mode 100644 src/mqt/qecc/analog-information-decoding/code_construction/compute_distances_3D.sh create mode 100644 src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hx.npz create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hz.npz create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=21_hx.npz create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=21_hz.npz create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hx.npz create mode 100644 src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hz.npz create mode 100644 src/mqt/qecc/analog-information-decoding/setup.py create mode 100644 src/mqt/qecc/analog-information-decoding/simulators/__init__.py create mode 100644 src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py create mode 100644 src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py create mode 100644 src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py create mode 100644 src/mqt/qecc/analog-information-decoding/simulators/simulation.py create mode 100644 src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py create mode 100644 src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py create mode 100644 src/mqt/qecc/analog-information-decoding/utils/__init__.py create mode 100644 src/mqt/qecc/analog-information-decoding/utils/data_utils.py create mode 100644 src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py diff --git a/src/mqt/qecc/analog-information-decoding/.gitignore b/src/mqt/qecc/analog-information-decoding/.gitignore new file mode 100644 index 00000000..10db0985 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/.gitignore @@ -0,0 +1,134 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +*.mtx +*.json +ldpc2/* +generated_codes/* +results/* diff --git a/src/mqt/qecc/analog-information-decoding/LICENSE b/src/mqt/qecc/analog-information-decoding/LICENSE new file mode 100644 index 00000000..dce62483 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Lucas Berent, Timo Hillmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/mqt/qecc/analog-information-decoding/README.md b/src/mqt/qecc/analog-information-decoding/README.md new file mode 100644 index 00000000..cd4a94c2 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/README.md @@ -0,0 +1,52 @@ +# Analog Information Decoding + +This submodule provides means to conduct decoding simulations for quantum CSS codes with +analog syndrome information as proposed in the corresponding paper [1]. +Proper integration and setup is a work in progress. +The main functionality is provided in the `simulators` module, which contains the main classes +that can be used to conduct several types of simulations: + +* `ATD_Simulator`: Analog Tanner graph decoding, +* `Single-Shot Simulator`: Analog Single-Shot decoding with meta checks. +* `QSS_Simulator`: Quasi-Single-Shot decoding, and + +Moreover, `memory_experiment` contains methods for analog overlapping window decoding, in +particular the `decode_multiround` method. + +## Results + +The `results` directory contains the results used in the paper [1]. + +## Codes + +The `codes` directory contains the parity-check matrices of the codes used in the paper [1]. +Three dimensional toric codes can either be constructed with the hypergraph product construction +or with a library, e.g., panqec [3]. +### Code construction + +The `code_construction` directory contains the code used to construct higher-dimensional hypergraph +product codes and used the `compute_distances.sh` script to automatically compute bounds on the +distances of the constructed codes using the GAP library QDistRnd. + +## Utils + +### Plotting + +The `plotting` directory contains the code used to plot the results in the paper [1]. + +### Data Utils + +We have implemented several utility functions as part of this package that might be of independent +interest for decoding simulations and data analysis. These are provided in the `data_utils` module +in the `utils` package. + +## Dependencides + +The used BP+OSD implementation, as well as our implementation of the SSMSA decoder are provided +in the LDPC2 package a preliminary beta version of which is available on Github [2]. + +## References + +- [1]: L. Berent, T. Hillmann et al.: https://arxiv.org/TODO +- [2]: J. Roffe et al. https://github.com/quantumgizmos/ldpc/tree/ldpc_v2 +- [3]: E. Huang et al. https://github.com/panqec/panqec diff --git a/src/mqt/qecc/analog-information-decoding/__init__.py b/src/mqt/qecc/analog-information-decoding/__init__.py new file mode 100644 index 00000000..7f739451 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/__init__.py @@ -0,0 +1,11 @@ +from simulators.analog_tannergraph_decoding import AnalogTannergraphDecoder, SoftInfoDecoder, ATD_Simulator +from simulators.quasi_single_shot_v2 import QSS_SimulatorV2 +from simulators.simulation import Single_Shot_Simulator + +__all__ = [ + "AnalogTannergraphDecoder", + "SoftInfoDecoder", + "ATD_Simulator", + "QSS_SimulatorV2", + "Single_Shot_Simulator", +] \ No newline at end of file diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/__init__.py b/src/mqt/qecc/analog-information-decoding/code_construction/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py b/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py new file mode 100644 index 00000000..7ae2c98a --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py @@ -0,0 +1,239 @@ +import ldpc.code_util +import numpy as np +from bposd.hgp import hgp +import scipy.io as sio +from scipy import sparse +from ldpc import mod2 +from ldpc.codes import ring_code +import os +import json +import subprocess +import scipy.sparse as scs + + +class HD_HGP: + def __init__(self, boundaries): + self.boundaries = boundaries + + +def is_all_zeros(array): + return not np.any(array) + + +def run_checks_scipy(d_1, d_2, d_3, d_4): + sd_1 = scs.csr_matrix(d_1) + sd_2 = scs.csr_matrix(d_2) + sd_3 = scs.csr_matrix(d_3) + sd_4 = scs.csr_matrix(d_4) + + if not ( + is_all_zeros((sd_1 * sd_2).todense() % 2) + and is_all_zeros((sd_2 * sd_3).todense() % 2) + and is_all_zeros((sd_3 * sd_4).todense() % 2) + ): + raise Exception( + "Error generating 4D code, boundary maps do not square to zero" + ) + + +def generate_4D_product_code(A_1, A_2, A_3, P, checks=True): + r, c = P.shape + id_r = np.identity(r, dtype=np.int32) + id_c = np.identity(c, dtype=np.int32) + id_n0 = np.identity(A_1.shape[0], dtype=np.int32) + id_n1 = np.identity(A_2.shape[0], dtype=np.int32) + id_n2 = np.identity(A_3.shape[0], dtype=np.int32) + id_n3 = np.identity(A_3.shape[1], dtype=np.int32) + + d_1 = np.hstack((np.kron(A_1, id_r), np.kron(id_n0, P))) + + x = np.hstack((np.kron(A_2, id_r), np.kron(id_n1, P))) + y = np.kron(A_1, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + z = np.hstack((np.zeros(dims, dtype=np.int32), y)) + d_2 = np.vstack((x, z)) + + x = np.hstack((np.kron(A_3, id_r), np.kron(id_n2, P))) + y = np.kron(A_2, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + z = np.hstack((np.zeros(dims, dtype=np.int32), y)) + d_3 = np.vstack((x, z)) + + d_4 = np.vstack((np.kron(id_n3, P), np.kron(A_3, id_c))) + + if checks: + run_checks_scipy(d_1, d_2, d_3, d_4) + + return d_1, d_2, d_3, d_4 + + +def generate_3D_product_code(A_1, A_2, P): + r, c = P.shape + id_r = np.identity(r, dtype=np.int32) + id_c = np.identity(c, dtype=np.int32) + id_n0 = np.identity(A_1.shape[0], dtype=np.int32) + id_n1 = np.identity(A_2.shape[0], dtype=np.int32) + id_n2 = np.identity(A_2.shape[1], dtype=np.int32) + + d_1 = np.hstack((np.kron(A_1, id_r), np.kron(id_n0, P))) + + x = np.hstack((np.kron(A_2, id_r), np.kron(id_n1, P))) + y = np.kron(A_1, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + z = np.hstack((np.zeros(dims, dtype=np.int32), y)) + d_2 = np.vstack((x, z)) + + d_3 = np.vstack((np.kron(id_n2, P), np.kron(A_2, id_c))) + + if not (is_all_zeros(d_1 @ d_2 % 2) and is_all_zeros(d_2 @ d_3 % 2)): + raise Exception( + "Error generating 3D code, boundary maps do not square to zero" + ) + + return d_1, d_2, d_3 + + +def create_outpath(codename: str) -> str: + path = f"generated_codes/{codename}/" + + if not os.path.exists(path): + os.makedirs(path) + + return path + + +def save_code(hx, hz, mx, mz, codename, lx=None, lz=None): + path = create_outpath(codename) + + Ms = [hx, hz, mx, mz, lx, lz] + names = ["hx", "hz", "mx", "mz", "lx", "lz"] + for mat, name in zip(Ms, names): + if type(mat) != type(None): + np.savetxt(path + name + ".txt", mat, fmt="%i") + sio.mmwrite( + path + name + ".mtx", + sparse.coo_matrix(mat), + comment="Field: GF(2)", + ) + + +def run_compute_distances(codename): + path = "generated_codes/" + codename + subprocess.run(["bash", "compute_distances.sh", path]) + + +def _compute_distances(hx, hz, codename): + run_compute_distances(codename) + code_dict = {} + m, n = hx.shape + codeK = n - mod2.rank(hx) - mod2.rank(hz) + with open(f"generated_codes/{codename}/info.txt") as f: + code_dict = dict( + line[: line.rfind("#")].split(" = ") + for line in f + if not line.startswith("#") and line.strip() + ) + + code_dict["n"] = n + code_dict["k"] = codeK + code_dict["dX"] = int(code_dict["dX"]) + code_dict["dZ"] = int(code_dict["dZ"]) + code_dict["dMX"] = int(code_dict["dMX"]) + code_dict["dMZ"] = int(code_dict["dMZ"]) + + print("Code properties:", code_dict) + with open(f"generated_codes/{codename}/code_params.txt", "w") as file: + file.write(json.dumps(code_dict)) + + return + + +def _compute_logicals(hx, hz): + def compute_lz(hx, hz): + # lz logical operators + # lz\in ker{hx} AND \notin Im(Hz.T) + + ker_hx = mod2.nullspace(hx) # compute the kernel basis of hx + im_hzT = mod2.row_basis(hz) # compute the image basis of hz.T + + # in the below we row reduce to find vectors in kx that are not in the image of hz.T. + log_stack = np.vstack([im_hzT, ker_hx]) + pivots = mod2.row_echelon(log_stack.T)[3] + log_op_indices = [ + i for i in range(im_hzT.shape[0], log_stack.shape[0]) if i in pivots + ] + log_ops = log_stack[log_op_indices] + return log_ops + + lx = compute_lz(hz, hx) + lz = compute_lz(hx, hz) + return lx, lz + + +def create_code( + constructor: str, + seed_codes: list, + codename: str, + compute_distance: bool = False, + compute_logicals: bool = False, + lift_parameter=None, + checks: bool = False, +): + # Construct initial 2 dim code + if constructor == "hgp": + code = hgp(seed_codes[0], seed_codes[1]) + else: + raise ValueError( + f"No constructor specified or the specified constructor {constructor} not implemented." + ) + + # Extend to 3D HGP + A1 = code.hx + A2 = code.hz.T + res = generate_3D_product_code(A1, A2, seed_codes[2]) + + # Build 4D HGP code + mx, hx, hzT, mzT = generate_4D_product_code( + *res, seed_codes[3], checks=checks + ) + + hz = hzT.T + mz = mzT.T + + # Perform checks + if np.any(hzT @ mzT % 2) or np.any(hx @ hzT % 2) or np.any(mx @ hx % 2): + raise Exception("err") + save_code(hx, hz, mx, mz, codename) + + if compute_logicals: + lx, lz = _compute_logicals(hx, hz) + save_code(hx, hz, mx, mz, codename, lx=lx, lz=lz) + + else: + save_code(hx, hz, mx, mz, codename) + + if compute_distance: + _compute_distances(hx, hz, codename) + return + + +if __name__ == "__main__": + for d in range(3, 8): + seed_codes = [ + ldpc.codes.ring_code(d), + ldpc.codes.ring_code(d), + ldpc.codes.ring_code(d), + ldpc.codes.ring_code(d), + ] + + constructor = "hgp" + codename = f"4D_toric_{d:d}" + compute_distance = False + compute_logicals = True + create_code( + constructor, + seed_codes, + codename, + compute_distance, + compute_logicals, + ) diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances.sh b/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances.sh new file mode 100644 index 00000000..2b6e4b5a --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances.sh @@ -0,0 +1,53 @@ +#!/bin/bash +path=$1 + +gap -q << EOF > $path/info.txt +LoadPackage("QDistRnd");; +hx:=ReadMTXE("$path/hx.mtx");; + +hz:=ReadMTXE("$path/hz.mtx");; + +Print("dZ = ", DistRandCSS(hx[3], hz[3], 150, 0,2), "\n"); +Print("dX = ", DistRandCSS(hz[3], hx[3], 150, 0,2), "\n"); + +file := IO_File("$path/mx.txt"); +lines := IO_ReadLines(file); +parityCheckMatrix := []; + +for l in lines do + row := ReplacedString(l, "\n", ""); + r := SplitString(row, " "); + newr := []; + for b in r do + c := Int(b); + Add(newr,c); + od; + Add(parityCheckMatrix, newr); +od; + +xcode := CheckMatCode(parityCheckMatrix, GF(2)); + +file := IO_File("$path/mz.txt"); +lines := IO_ReadLines(file); +parityCheckMatrix := []; + +for l in lines do + row := ReplacedString(l, "\n", ""); + r := SplitString(row, " "); + newr := []; + for b in r do + c := Int(b); + Add(newr,c); + od; + Add(parityCheckMatrix, newr); +od; + +zcode := CheckMatCode(parityCheckMatrix, GF(2)); + + + +# Print("dMx = ", MinimumDistance(xcode), "\n"); only works for small codes quickly +Print("dMx = ", MinimumDistanceLeon(xcode), "\n"); #https://gap-packages.github.io/guava/doc/chap4_mj.html#X8170B52D7C154247:~:text=4.8%2D4%20MinimumDistanceLeon +Print("dMz = ", MinimumDistanceLeon(zcode), "\n"); + +EOF diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances_3D.sh b/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances_3D.sh new file mode 100644 index 00000000..45fe4d5a --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/code_construction/compute_distances_3D.sh @@ -0,0 +1,13 @@ +#!/bin/bash +path=$1 + +gap -q << EOF > $path/info.txt +LoadPackage("QDistRnd");; +hx:=ReadMTXE("$path/hx.mtx");; + +hz:=ReadMTXE("$path/hz.mtx");; + +Print("dZ = ", DistRandCSS(hx[3], hz[3], 150, 0,2), "\n"); +Print("dX = ", DistRandCSS(hz[3], hx[3], 150, 0,2), "\n"); + +EOF diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py b/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py new file mode 100644 index 00000000..4f7c8203 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py @@ -0,0 +1,226 @@ +import numpy as np +from bposd.hgp import hgp +import scipy.io as sio +from ldpc import mod2 +from scipy import sparse +import code_constructor +import os +import json +import subprocess +from scipy.sparse import coo_matrix, csr_matrix + + +class HD_HGP: + def __init__(self, boundaries): + self.boundaries = boundaries + + +def is_all_zeros(array): + return not np.any(array) + + +def sparse_all_zeros(mat: csr_matrix): + mat.data %= 2 + return mat.sum() == 0 + + +def run_checks_scipy( + d_1: csr_matrix, d_2: csr_matrix, d_3: csr_matrix, d_4: csr_matrix +): + if not ( + sparse_all_zeros(d_1 @ d_2) + and sparse_all_zeros(d_2 @ d_3) + and sparse_all_zeros(d_3 @ d_4) + ): + raise Exception( + "Error generating 4D code, boundary maps do not square to zero" + ) + + +def generate_4D_product_code( + A_1: csr_matrix, + A_2: csr_matrix, + A_3: csr_matrix, + P: csr_matrix, + checks=True, +): + r, c = P.shape + + id_r = sparse.identity(r, dtype=int) + id_c = sparse.identity(c, dtype=int) + id_n0 = sparse.identity(A_1.shape[0], dtype=int) + id_n1 = sparse.identity(A_2.shape[0], dtype=int) + id_n2 = sparse.identity(A_3.shape[0], dtype=int) + id_n3 = sparse.identity(A_3.shape[1], dtype=int) + + d_1 = sparse.hstack((sparse.kron(A_1, id_r), sparse.kron(id_n0, P))) + + x = sparse.hstack((sparse.kron(A_2, id_r), sparse.kron(id_n1, P))) + y = sparse.kron(A_1, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + nmat = csr_matrix(np.zeros(dims)) + z = sparse.hstack((nmat, y)) + d_2 = sparse.vstack((x, z)) + + x = sparse.hstack((sparse.kron(A_3, id_r), sparse.kron(id_n2, P))) + y = sparse.kron(A_2, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + mat = csr_matrix(np.zeros(dims)) + z = sparse.hstack([mat, y]) + d_3 = sparse.vstack((x, z)) + + d_4 = sparse.vstack((sparse.kron(id_n3, P), sparse.kron(A_3, id_c))) + + if checks: + run_checks_scipy(d_1, d_2, d_3, d_4) + + return d_1, d_2, d_3, d_4 + + +def generate_3D_product_code(A_1: csr_matrix, A_2: csr_matrix, P: csr_matrix): + r, c = P.shape + + id_r = sparse.identity(r, dtype=int) + id_c = sparse.identity(c, dtype=int) + id_n0 = sparse.identity(A_1.shape[0], dtype=int) + id_n1 = sparse.identity(A_2.shape[0], dtype=int) + id_n2 = sparse.identity(A_2.shape[1], dtype=int) + + d_1 = sparse.hstack((sparse.kron(A_1, id_r), sparse.kron(id_n0, P))) + + x = sparse.hstack((sparse.kron(A_2, id_r), sparse.kron(id_n1, P))) + y = sparse.kron(A_1, id_c) + dims = (y.shape[0], x.shape[1] - y.shape[1]) + z = sparse.hstack((csr_matrix(np.zeros(dims), dtype=int), y)) + d_2 = sparse.vstack((x, z)) + + d_3 = sparse.vstack((sparse.kron(id_n2, P), sparse.kron(A_2, id_c))) + + if not (sparse_all_zeros(d_1 @ d_2) and sparse_all_zeros(d_2 @ d_3)): + raise Exception( + "Error generating 3D code, boundary maps do not square to zero" + ) + + return d_1, d_2, d_3 # mx, hx, hzT # hx, hzT, mzT + + +def create_outpath(codename: str) -> str: + path = f"/codes/generated_codes/{codename}/" + + if not os.path.exists(path): + os.makedirs(path) + + return path + + +def save_code(hx, hz, mz, codename, lx=None, lz=None): + path = create_outpath(codename) + + Ms = [hx, hz, mz, lx, lz] + names = ["hx", "hz", "mz", "lx", "lz"] + for mat, name in zip(Ms, names): + if type(mat) != type(None): + path_str = path + name + try: + np.savetxt(path_str + ".txt", mat.todense(), fmt="%i") + except: + np.savetxt(path_str + ".txt", mat, fmt="%i") + sio.mmwrite( + path_str + ".mtx", + coo_matrix(mat), + comment="Field: GF(2)", + field="integer", + ) + + +def run_compute_distances(codename): + path = "/codes/generated_codes/" + codename + subprocess.run(["bash", "compute_distances_3D.sh", path]) + + +def _compute_distances(hx, hz, codename): + run_compute_distances(codename) + code_dict = {} + _, n = hx.shape + codeK = n - mod2.rank(hx) - mod2.rank(hz) + with open( + f"/codes/generated_codes/{codename}/info.txt") as f: + code_dict = dict( + line[: line.rfind("#")].split(" = ") + for line in f + if not line.startswith("#") and line.strip() + ) + + code_dict["n"] = n + code_dict["k"] = codeK + code_dict["dX"] = int(code_dict["dX"]) + code_dict["dZ"] = int(code_dict["dZ"]) + + print("Code properties:", code_dict) + with open( + f"/codes/generated_codes/{codename}/code_params.txt", + "w") as file: + file.write(json.dumps(code_dict)) + + return + + +def _store_code_params(hx, hz, codename): + code_dict = {} + hx, hz = hx.todense(), hz.todense() + m, n = hx.shape + codeK = n - mod2.rank(hx) - mod2.rank(hz) + code_dict["n"] = n + code_dict["k"] = codeK + with open( + f"/codes/generated_codes/{codename}/code_params.txt", + "w") as file: + file.write(json.dumps(code_dict)) + return + + +def create_code( + constructor: str, + seed_codes: list, + codename: str, + compute_distance: bool = False, + compute_logicals: bool = False, + lift_parameter=None, + checks: bool = False, +): + # Construct initial 2 dim code + if constructor == "hgp": + code = hgp(seed_codes[0], seed_codes[1]) + else: + raise ValueError( + f"No constructor specified or the specified constructor {constructor} not implemented." + ) + + # Extend to 3D HGP + A1 = sparse.csr_matrix(code.hx) + A2 = sparse.csr_matrix(code.hz.T) + res = generate_3D_product_code(A1, A2, sparse.csr_matrix(seed_codes[2])) + hx, hzT, mzT = res + # Build 4D HGP code + # res = [csr_matrix(A) for A in res] + + # mx, hx, hzT, mzT = generate_4D_product_code( + # *res, csr_matrix(seed_codes[3]), checks=checks + # ) + + hz = hzT.transpose() + mz = mzT.transpose() + if compute_logicals: + lx, lz = code_constructor._compute_logicals(hx.todense(), hz.todense()) + save_code(hx=hx, hz=hz, mz=mz, codename=codename, lx=lx, lz=lz) + + # else: + # save_code(hx, hz, mx, mz, codename) + + if compute_distance: + _compute_distances(hx.todense(), hz.todense(), codename) + + else: + _store_code_params(hx.todense(), hz.todense(), codename) + return + diff --git a/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hx.npz b/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hx.npz new file mode 100644 index 0000000000000000000000000000000000000000..403d058df0dfec8fa7bedfe39e8c5ca7f94f9794 GIT binary patch literal 4580 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 literal 0 HcmV?d00001 diff --git a/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hz.npz b/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=16_hz.npz new file mode 100644 index 0000000000000000000000000000000000000000..846160d19ef7f835880b10c4b039380d44d243b1 GIT binary patch literal 4568 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 Gr2haI@>8h* literal 0 HcmV?d00001 diff --git a/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=21_hx.npz b/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=21_hx.npz new file mode 100644 index 0000000000000000000000000000000000000000..bd126d8995b206e31535069466466028fb65badc GIT binary patch literal 5849 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 N;BP!wIcpp_`ag!18b<&C literal 0 HcmV?d00001 diff --git a/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hx.npz b/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hx.npz new file mode 100644 index 0000000000000000000000000000000000000000..52a957a5d5c02a81ba7cbd02097f746b8370eab1 GIT binary patch literal 8007 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 A@Bjb+ literal 0 HcmV?d00001 diff --git a/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hz.npz b/src/mqt/qecc/analog-information-decoding/codes/lifted_product/lp_l=30_hz.npz new file mode 100644 index 0000000000000000000000000000000000000000..b48dce9e76bca7dcddad39e702affeda9b4fcd57 GIT binary patch literal 7926 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 MgPA`Bn3dK40SkDvRsaA1 literal 0 HcmV?d00001 diff --git a/src/mqt/qecc/analog-information-decoding/setup.py b/src/mqt/qecc/analog-information-decoding/setup.py new file mode 100644 index 00000000..30596398 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup, find_packages + +setup( + name="AnalogInformationDecoding", + version="0.1", + packages=find_packages(), + install_requires=[ + "numpy", + "scipy", + "numba", + "matplotlib", + "bposd", + "ldpc", + ], + author="Lucas Berent, Timo Hillmann", + author_email="lucas.berent@tum.de, timo.hillmann@rwth-aachen.de", + description="Analog Information Decoding techniques", + license="MIT", + keywords="bosonic decoders", + url="", + ) \ No newline at end of file diff --git a/src/mqt/qecc/analog-information-decoding/simulators/__init__.py b/src/mqt/qecc/analog-information-decoding/simulators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py b/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py new file mode 100644 index 00000000..d543fa5d --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py @@ -0,0 +1,536 @@ +import json +import numpy as np +import utils.simulation_utils as simulation_utils +from ldpc import bposd_decoder +from ldpc import bp_decoder +from utils.data_utils import ( + calculate_error_rates, + BpParams, + create_outpath, + is_converged, +) +import os +from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder +from ldpc2.bp_decoder import SoftInfoBpDecoder + + +def create_outpath( + experiment: str = "atd", + data_err_rate: float = None, + sigma: float = None, + bp_params: BpParams = None, + codename: str = None, + bias: list = None, + overwrite: bool = False, + id: int = 0, + **kwargs): + """ Create output path from input parameters. """ + + path = f"results/{experiment:s}/" + + path += f"bias={bias[0]}_{bias[1]}_{bias[2]}/" + + path += f"bp_{bp_params.bp_method}/" + + path += f"{bp_params.osd_method}/" + + path += f"osd_order_{bp_params.osd_order}/" + + path += f"max_bp_iter_{bp_params.max_bp_iter}/" + + path += f"ms_scaling_factor_{bp_params.ms_scaling_factor}/" + + path += f"schedule_{bp_params.schedule}/" + + path += f"cutoff_{bp_params.cutoff:.1f}/" + + path += f"lp_{codename:s}/" + + path += f"per_{data_err_rate:.3e}_sigma_{sigma:.3e}/" + + if not os.path.exists(path): + os.makedirs(path, exist_ok=True) + + if overwrite == False: + f_loc = path + f"id_{id}.json" + while os.path.exists(f_loc): + id += 1 + f_loc = path + f"id_{id}.json" + else: + f_loc = path + f"id_{id}.json" + + while not os.path.exists(f_loc): + open(f_loc, "w").close() + + return f_loc + + +class SoftInfoDecoder: + """ + Soft Information Decoder + + Plug and play solution for soft information decoding that can be + interchanged with `AnalogTannergraphDecoder` in `ATD_Simulator`. + + """ + + def __init__( + self, + H: np.ndarray, + bp_params: BpParams, + error_channel: np.ndarray, + sigma: float = None, + ser: float = None, + ): + self.m, self.n = H.shape + self.sigma = sigma + self.H = H + self.bp_params = bp_params + self.syndr_err_rate = ser + self.error_channel = error_channel + + if self.sigma is None: + if self.syndr_err_rate is None: + raise ValueError("Either sigma or ser must be specified") + else: + self.sigma = simulation_utils.get_sigma_from_syndr_er( + self.syndr_err_rate + ) + else: + if self.syndr_err_rate is not None: + raise ValueError("Only one of sigma or ser must be specified") + + if self.error_channel is None: + raise ValueError("error_channel must be specified") + + self.bp_decoder = SoftInfoBpOsdDecoder( + pcm=self.H, + error_channel=error_channel, + sigma=self.sigma, + max_iter=self.bp_params.max_bp_iter, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + omp_thread_count=self.bp_params.omp_thread_count, + random_serial_schedule=self.bp_params.random_serial_schedule, + serial_schedule_order=self.bp_params.serial_schedule_order, + osd_method=self.bp_params.osd_method, + osd_order=self.bp_params.osd_order, + bp_method=self.bp_params.bp_method, + schedule=self.bp_params.schedule, + cutoff=self.bp_params.cutoff, + ) + + self.bp_decoder = SoftInfoBpDecoder( + pcm=self.H, + error_channel=error_channel, + sigma=self.sigma, + max_iter=self.bp_params.max_bp_iter, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + bp_method=self.bp_params.bp_method, + cutoff=self.bp_params.cutoff, + ) + + def decode(self, analog_syndrome: np.ndarray) -> np.ndarray: + """Decode a given analog syndrome.""" + return self.bp_decoder.decode(analog_syndrome) + + +class AnalogTannergraphDecoder: + """Analog Tannergraph decoder + Builds the analog tanner graph and uses this as decoding graph. + """ + + def __init__( + self, + H: np.ndarray, + bp_params: BpParams, + error_channel: np.ndarray, + sigma: float = None, + ser: float = None, + ): + self.m, self.n = H.shape + self.sigma = sigma + self.H = H + self.bp_params = bp_params + self.atg = get_analog_pcm(H) + self.syndr_err_rate = ser + self.error_channel = error_channel + + if self.sigma is None: + if self.syndr_err_rate is None: + raise ValueError("Either sigma or ser must be specified") + else: + self.sigma = simulation_utils.get_sigma_from_syndr_er( + self.syndr_err_rate + ) + else: + if self.syndr_err_rate is not None: + raise ValueError("Only one of sigma or ser must be specified") + + if self.error_channel is None: + raise ValueError("error_channel must be specified") + + self.bp_decoder = bposd_decoder( + parity_check_matrix=self.atg, + channel_probs=np.hstack( + (self.error_channel, np.zeros(self.m)) + ), # initd as dummy for now + max_iter=self.bp_params.max_bp_iter, + bp_method=self.bp_params.bp_method, + osd_order=self.bp_params.osd_order, + osd_method=self.bp_params.osd_method, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + schedule=self.bp_params.schedule, + omp_thread_count=self.bp_params.omp_thread_count, + random_serial_schedule=self.bp_params.random_serial_schedule, + serial_schedule_order=self.bp_params.serial_schedule_order, + ) + + self.bp_decoder = bp_decoder( + parity_check_matrix=self.atg, + channel_probs=np.hstack( + (self.error_channel, np.zeros(self.m)) + ), # initd as dummy for now + max_iter=self.bp_params.max_bp_iter, + bp_method=self.bp_params.bp_method, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + ) + + def _set_analog_syndrome(self, analog_syndrome: np.ndarray) -> None: + """ + Initializes the error channel of the BP decoder s.t. the virtual nodes are initialized with the + analog syndrome LLRs on decoding initialization. + :param analog_syndrome: the analog syndrome values to initialize the virtual nodes with + """ + new_channel = np.hstack( + ( + self.bp_decoder.channel_probs[: self.n], + simulation_utils.get_virtual_check_init_vals( + analog_syndrome, self.sigma + ), + ) + ) + self.bp_decoder.update_channel_probs(new_channel) + + def decode(self, analog_syndrome: np.ndarray) -> np.ndarray: + """Decode a given analog syndrome.""" + self._set_analog_syndrome(analog_syndrome) + return self.bp_decoder.decode( + simulation_utils.get_binary_from_analog(analog_syndrome) + ) + + +class ATD_Simulator: + def __init__( + self, + Hx: np.ndarray, + Lx: np.ndarray, + Hz: np.ndarray, + Lz: np.ndarray, + codename: str, + seed: int, + bp_params: BpParams, + data_err_rate: float, + syndr_err_rate: float = None, + sigma: float = None, + bias=None, + experiment: str = "atd", + decoding_method: str = "atd", + **kwargs, + ) -> None: + if bias is None: + bias = [1.0, 1.0, 1.0] + simulation_utils.set_seed(seed) + self.Hx = Hx + self.Lx = Lx + self.Hz = Hz + self.Lz = Lz + self.codename = codename + self.data_err_rate = data_err_rate + self.bias = bias + self.experiment = experiment + self.decoding_method = decoding_method + self.sigma = sigma + + self.eff_x_err_rate = 0 + self.eff_z_err_rate = 0 + + if sigma is None: + if syndr_err_rate is None: + raise ValueError("Either sigma or ser must be specified") + else: + self.syndr_err_rate = syndr_err_rate + synd_err_channel = simulation_utils.error_channel_setup( + error_rate=self.syndr_err_rate, + xyz_error_bias=self.bias, + N=1, + ) + + else: + if syndr_err_rate is not None: + raise ValueError("Only one of sigma or ser must be specified") + + self.syndr_err_rate = simulation_utils.get_error_rate_from_sigma( + sigma + ) + synd_err_channel = simulation_utils.error_channel_setup( + error_rate=self.syndr_err_rate, + xyz_error_bias=self.bias, + N=1, + ) + + x_synd_err_rate = ( + synd_err_channel[0][0] + synd_err_channel[1][0] + ) # x + y errors, 1st bit only + z_synd_err_rate = ( + synd_err_channel[2][0] + synd_err_channel[1][0] + ) # z + y errors, 1st bit only + self.x_sigma = simulation_utils.get_sigma_from_syndr_er(x_synd_err_rate) + self.z_sigma = simulation_utils.get_sigma_from_syndr_er(z_synd_err_rate) + + self.bp_params = bp_params + self.save_interval = kwargs.get("save_interval", 1_000) + self.eb_precission = kwargs.get("eb_precission", 1e-1) + self.input_values = self.__dict__.copy() + + self.n = Hx.shape[1] + self.code_params = eval( + open(f"generated_codes/code/code_params.txt").read() + ) + del self.input_values["Hx"] + del self.input_values["Lx"] + del self.input_values["Hz"] + del self.input_values["Lz"] + self.outfile = create_outpath(**self.input_values) + + # setup decoders + if self.decoding_method == "atd": + Decoder = AnalogTannergraphDecoder + + elif self.decoding_method == "softinfo": + Decoder = SoftInfoDecoder + + # single-sided error only, no bias + self.full_error_channel = simulation_utils.error_channel_setup( + error_rate=self.data_err_rate, + xyz_error_bias=self.bias, + N=self.n, + ) + self.x_decoder = Decoder( + error_channel=self.full_error_channel[0] + + self.full_error_channel[1], # x + y errors + H=self.Hz, + sigma=self.x_sigma, + bp_params=self.bp_params, + ) + self.z_decoder = Decoder( + error_channel=self.full_error_channel[2] + + self.full_error_channel[1], # z + y errors + H=self.Hx, + sigma=self.z_sigma, + bp_params=self.bp_params, + ) + self.x_bp_iterations = 0 + self.z_bp_iterations = 0 + + def single_sample(self): + """Samples and error and decodes once. Returns if the decoding round was successful separately for each side.""" + residual_err = [ + np.zeros(self.n).astype(np.int32), + np.zeros(self.n).astype(np.int32), + ] # no residual error + ( + x_err, + z_err, + ) = simulation_utils.generate_err( # no residual error, only one side needed + N=self.n, + channel_probs=self.full_error_channel, + residual_err=residual_err, + ) + + x_perf_syndr = (self.Hz @ x_err) % 2 + x_noisy_syndr = simulation_utils.get_noisy_analog_syndrome( + sigma=self.x_sigma, perfect_syndr=x_perf_syndr + ) + x_decoding = self.x_decoder.decode(x_noisy_syndr)[: self.n] + self.x_bp_iterations += self.x_decoder.bp_decoder.iter + x_residual = (x_err + x_decoding) % 2 + + z_perf_syndr = (self.Hx @ z_err) % 2 + z_noisy_syndr = simulation_utils.get_noisy_analog_syndrome( + sigma=self.z_sigma, perfect_syndr=z_perf_syndr + ) + z_decoding = self.z_decoder.decode(z_noisy_syndr)[: self.n] + self.z_bp_iterations += self.z_decoder.bp_decoder.iter + z_residual = (z_err + z_decoding) % 2 + + return not simulation_utils.is_logical_err( + self.Lz, x_residual + ), not simulation_utils.is_logical_err(self.Lx, z_residual) + + def run(self, samples): + x_success_cnt = 0 + z_success_cnt = 0 + for runs in range(1, samples + 1): + out = self.single_sample() + + x_success_cnt += int(out[0]) + z_success_cnt += int(out[1]) + if runs % self.save_interval == 1: + self.save_results(x_success_cnt, z_success_cnt, runs) + + # check convergence only once during each save interval + if is_converged( + x_success_cnt, + z_success_cnt, + runs, + self.code_params, + self.eb_precission, + ): + print("Result has converged.") + break + + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( + x_success_cnt, runs, self.code_params + ) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( + z_success_cnt, runs, self.code_params + ) + output = { + "code_K": self.code_params["k"], + "code_N": self.code_params["n"], + "nr_runs": runs, + "pers": self.data_err_rate, + "x_sigma": self.x_sigma, + "z_sigma": self.z_sigma, + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + "bp_params": self.bp_params, + "x_bp_iterations": self.x_bp_iterations / runs, + "z_bp_iterations": self.z_bp_iterations / runs, + } + + self.save_results( + x_success_cnt, z_success_cnt, runs + ) # save final results + + return output + + def save_results(self, x_success_cnt: int, z_success_cnt: int, runs: int): + """Compute error rates and error bars and save output dict.""" + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( + x_success_cnt, runs, self.code_params + ) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( + z_success_cnt, runs, self.code_params + ) + output = { + "code_K": self.code_params["k"], + "code_N": self.code_params["n"], + "nr_runs": runs, + "pers": self.data_err_rate, + "x_sigma": self.x_sigma, + "z_sigma": self.z_sigma, + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + "bp_params": self.bp_params, + "x_bp_iterations": self.x_bp_iterations / runs, + "z_bp_iterations": self.z_bp_iterations / runs, + } + + output.update(self.input_values) + with open(self.outfile, "w") as f: + json.dump( + output, + f, + ensure_ascii=False, + indent=4, + default=lambda o: o.__dict__, + ) + return output + + +def get_analog_pcm(H: np.ndarray): + """ + constructs apcm = [H | I_m] where I_m is the m x m identity matrix + """ + return np.hstack((H, np.identity(H.shape[0], dtype=np.int32))) + + +import matplotlib.pyplot as plt + +if __name__ == "__main__": + """ example simulation script """ + if os.getcwd().split("/")[-1] == "simulators": + os.chdir("..") + code_path = "generated_codes/lp/" + s = np.linspace(0.10, 0.4, 11) + p = 0.05 + for bp_method in [1]: + for decoder in ["atd"]: + for c in [16, 21, 30]: + Hx = np.loadtxt(code_path + str(c) + "_hx.txt", dtype=int) + Hz = np.loadtxt(code_path + str(c) + "_hz.txt", dtype=int) + Lx = np.loadtxt(code_path + str(c) + "_lx.txt", dtype=int) + Lz = np.loadtxt(code_path + str(c) + "_lz.txt", dtype=int) + lers = [] + ebs = [] + for sigma in s: + print(sigma) + sim = ATD_Simulator( + Hx=Hx, + Lx=Lx, + Hz=Hz, + Lz=Lz, + codename=str(c), + data_err_rate=p, + sigma=sigma, + seed=666, + bp_params=BpParams( + max_bp_iter=100, + bp_method=bp_method, + osd_order=10, + osd_method="osd_cs", + ms_scaling_factor=0.75, + cutoff=5., + ), + decoding_method=decoder, + ) + out = sim.run(samples=1500) + + ler = out["z_ler"] * (1 - out["x_ler"]) + out["x_ler"] * (1 - out["z_ler"]) + out["x_ler"] * out[ + "z_ler"] + ler_eb = out["z_ler_eb"] * (1 - out["x_ler_eb"]) + out["x_ler_eb"] * (1 - out["z_ler_eb"]) + out[ + "x_ler_eb"] * out["z_ler_eb"] + lers.append(ler) + ebs.append(ler_eb) + + plt.errorbar( + s, + lers, + ebs, + label=f"l={c}, {decoder} {bp_method}", + marker="o", + linestyle="solid", + ) + + plt.legend() + plt.xlabel("sigma") + plt.ylabel("LER") + plt.yscale("log") + plt.show() diff --git a/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py b/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py new file mode 100644 index 00000000..d47b50e6 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py @@ -0,0 +1,173 @@ +import numpy as np +from pymatching import Matching +from utils.simulation_utils import ( + is_logical_err, + get_virtual_check_init_vals, + get_binary_from_analog, + get_noisy_analog_syndrome, + get_sigma_from_syndr_er, +) +from scipy.sparse import csr_matrix, hstack, eye, block_diag + + +def build_multiround_pcm(pcm, repetitions, format="csr"): + """Builds the multiround parity-check matrix as described in the paper. + + Each row corresponds to a round of measurements, the matrix for r repetitions has the form + H3D := (H_diag | id_r), where H_diag is the r-diagonal matrix containing the parity-check matrix H and + id_diag is a staircase block matrix containing two m-identity matrices in each row and column except the first one. + """ + if not isinstance(pcm, csr_matrix): + pcm = csr_matrix(pcm) + + pcm_rows, pcm_cols = pcm.shape + + # Construct the block of PCMs + H_3DPCM = block_diag([pcm] * (repetitions + 1), format=format) + + # Construct the block of identity matrices + H_3DID_diag = block_diag( + [eye(pcm_rows, format=format)] * (repetitions + 1), format=format) + + # Construct the block of identity matrices + H_3DID_offdiag = eye(pcm_rows * (repetitions + 1), + k=-pcm_rows, format=format) + + # Construct the block of identity matrices + H_3DID = H_3DID_diag + H_3DID_offdiag + + # # hstack the two blocks + H_3D = hstack([H_3DPCM, H_3DID], format=format) + + return H_3D + + +def move_syndrome(syndrome, data_type=np.int32): + """Slides the window one region up, i.e., the syndrome of the first half is overwritten by the second half.""" + T = int(syndrome.shape[1] / 2) # number of rounds in each region + + # zero likes syndrome + new_syndrome = np.zeros(syndrome.shape, dtype=data_type) + + # move the syndromes from the Tth round to the 0th round + new_syndrome[:, :T] = syndrome[:, T:] + return new_syndrome + + +def get_updated_decoder(decoding_method: str, + decoder, + new_channel, + H3D=None): + """ Updates the decoder with the new channel information and returns the updated decoder object.""" + if decoding_method == "bposd": + decoder.update_channel_probs(new_channel) + return decoder + elif decoding_method == "matching": + weights = np.clip( + np.log((1 - new_channel) / new_channel), + a_min=-16777215, + a_max=16777215, + ) + return Matching(H3D, weights=weights) + else: + raise ValueError("Unknown decoding method", decoding_method) + + +def decode_multiround( + syndrome: np.ndarray, + H: np.ndarray, + decoder, + channel_probs: np.ndarray, # needed for matching decoder does not have an update weights method + repetitions: int, + last_round=False, + analog_syndr=None, + check_block_size: int = 0, + sigma: float = 0.0, + H3D: np.ndarray = None, # needed for matching decoder + decoding_method: str = "bposd" # bposd or matching +): + """Overlapping window decoding. + First, we compute the difference syndrome from the recorded syndrome of each measurement round for all measurement + rounds of the current window (consisting of two regions with equal size). + Then, we apply the correction returned from the decoder on the first region (commit region). + We then propagate the syndrome through the whole window (i.e., to the end of the second region). + """ + analog_tg = analog_syndr is not None + # convert syndrome to difference syndrome + diff_syndrome = syndrome.copy() + diff_syndrome[:, 1:] = (syndrome[:, 1:] - syndrome[:, :-1]) % 2 + bp_iter = 0 + + region_size = repetitions // 2 # assumes repetitions is even + + if analog_tg: + # If we have analog information, we use it to initialize the time-like syndrome nodes, which are defined + # in the block of the H3D matrix after the diagonal H block. + analog_init_vals = get_virtual_check_init_vals( + analog_syndr.flatten("F"), sigma + ) + + new_channel = np.hstack( + (channel_probs[:check_block_size], analog_init_vals) + ) + + # in the last round, we have a perfect syndrome round to make sure we're in the codespace + if last_round: + new_channel[-H.shape[0]:] = 1e-15 + + decoder = get_updated_decoder(decoding_method, decoder, new_channel, H3D) + + else: + if last_round: + new_channel = np.copy(channel_probs) + new_channel[-H.shape[0]:] = 1e-15 + + decoder = get_updated_decoder(decoding_method, decoder, new_channel, H3D) + + decoded = decoder.decode(diff_syndrome.flatten("F")) + + if decoding_method == "bposd": + bp_iter = decoder.iter + + # extract space correction, first repetitions * n entires + space_correction = ( + decoded[: H.shape[1] * repetitions] + .reshape((repetitions, H.shape[1])) + .T + ) + # extract time correction + + if last_round == False: + # this corresponds to the decoding on the second block of the H3D matrix + time_correction = ( + decoded[H.shape[1] * repetitions:] + .reshape((repetitions, H.shape[0])) + .T + ) + + # append time correction with zeros + time_correction = np.hstack( + (time_correction, np.zeros((H.shape[0], 1), dtype=np.int32)) + ) + + # correct only in the commit region + decoded = (np.cumsum(space_correction, 1) % 2)[:, region_size - 1] + + # get the syndrome according to the correction + corr_syndrome = (H @ decoded) % 2 + + # propagate the syndrome correction through the tentative region + syndrome[:, region_size:] = ( + (syndrome[:, region_size:] + corr_syndrome[:, None]) % 2 + ).astype(np.int32) + + # apply the time correction of round region_size - 1 to the syndrome at the beginning of the tentative region + syndrome[:, region_size] = ( + (syndrome[:, region_size] + time_correction[:, region_size - 1]) % 2 + ).astype(np.int32) + + else: + # correct in the commit and tentative region as the last round stabilizer is perfect + decoded = (np.cumsum(space_correction, 1) % 2)[:, -1] + + return decoded.astype(np.int32), syndrome, analog_syndr, bp_iter diff --git a/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py b/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py new file mode 100644 index 00000000..39d78954 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py @@ -0,0 +1,301 @@ +from typing import List, Optional +import numpy as np +from utils.data_utils import _check_convergence, create_outpath, BpParams +from simulators.memory_experiment_v2 import ( + build_multiround_pcm, + move_syndrome, decode_multiround, +) +from bposd import bposd_decoder +from pymatching import Matching +from utils.simulation_utils import ( + is_logical_err, + save_results, + error_channel_setup, + set_seed, + generate_err, + generate_syndr_err, + get_sigma_from_syndr_er, + get_noisy_analog_syndrome, + get_binary_from_analog, +) + + +class QSS_SimulatorV2: + def __init__( + self, + H: np.ndarray, + per: float, + ser: float, + L: np.ndarray, + bias: List[float], + codename: str, + bp_params: Optional[BpParams], + decoding_method: str = "bposd", # bposd or matching + check_side: str = "X", + seed: int = 666, + analog_tg: bool = False, + repetitions: int = 0, + rounds: int = 0, + experiment: str = "qss", + **kwargs, + ) -> None: + """ + + :param H: parity-check matrix of code + :param per: physical data error rate + :param ser: syndrome error rate + :param L: logical matrix + :param bias: bias array + :param codename: name of the code + :param bp_params: BP decoder parameters + :param check_side: side of the check (X or Z) + :param seed: random seed + :param analog_tg: switch analog decoding on/off + :param repetitions: number of total syndrome measurements, i.e., total time steps. Must be even. + :param rounds: number of decoding runs, i.e., number of times we slide the window - 1 + :param experiment: name of experiment, for outpath creation + :param kwargs: + """ + self.H = H + self.data_err_rate = per + self.syndr_err_rate = ser + self.check_side = check_side + self.L = L + self.bias = bias + self.codename = codename + self.bp_params = bp_params + self.decoding_method = decoding_method + self.save_interval = kwargs.get("save_interval", 50) + self.eb_precission = kwargs.get("eb_precission", 1e-2) + self.analog_tg = analog_tg + self.repetitions = repetitions + if repetitions % 2 != 0: + raise ValueError("repetitions must be even") + + if self.decoding_method not in ["bposd", "matching"]: + raise ValueError("Decoding method must be either bposd or matching") + + if self.repetitions % 2 != 0: + raise ValueError("Repetitions must be even!") + + self.rounds = rounds + self.experiment = experiment + set_seed(seed) + # load code parameters + self.code_params = eval( + open(f"generated_codes/{codename}/code_params.txt").read() + ) + self.input_values = self.__dict__.copy() + + self.outfile = create_outpath(**self.input_values) + + # Remove Arrays + del self.input_values["H"] + del self.input_values["L"] + + self.num_checks, self.num_qubits = self.H.shape + + self.x_bit_chnl, self.y_bit_chnl, self.z_bit_chnl = error_channel_setup( + error_rate=self.data_err_rate, + xyz_error_bias=bias, + N=self.num_qubits, + ) + self.x_syndr_err_chnl, self.y_syndr_err_chnl, self.z_syndr_err_chnl = error_channel_setup( + error_rate=self.syndr_err_rate, + xyz_error_bias=bias, + N=self.num_checks, + ) + if self.check_side == "X": + self.err_idx = 1 + # Z bit/syndrome errors + self.data_err_channel = self.y_bit_chnl + self.z_bit_chnl + self.syndr_err_channel = 1.0 * (self.z_syndr_err_chnl + self.y_syndr_err_chnl) + else: + # we have X errors on qubits + self.err_idx = 0 + # X bit/syndrome errors + self.data_err_channel = self.x_bit_chnl + self.y_bit_chnl + self.syndr_err_channel = 1.0 * (self.x_syndr_err_chnl + self.y_syndr_err_chnl) + + # initialize the multiround parity-check matrix as described in the paper + self.H3D = build_multiround_pcm( + self.H, self.repetitions - 1, + ) + + # the number of columns of the diagonal check matrix of the H3D matrix + self.check_block_size = self.num_qubits * (self.repetitions) + + channel_probs = np.zeros(self.H3D.shape[1]) + # The bits corresponding to the columns of the diagonal H-bock of H3D are initialized with the bit channel + channel_probs[: self.check_block_size] = np.array( + self.data_err_channel.tolist() * (self.repetitions) + ) + + # The remaining bits (corresponding to the identity block of H3D) are initialized with the syndrome error channel + channel_probs[self.check_block_size:] = np.array( + self.syndr_err_channel.tolist() * (self.repetitions) + ) + + # If we do ATG decoding, initialize sigma (syndrome noise strength) + if self.analog_tg: + self.sigma = get_sigma_from_syndr_er( + self.syndr_err_channel[0] # x/z + y + ) # assumes all sigmas are the same + else: + self.sigma = None + self.bp_iterations = 0 + if self.decoding_method == "bposd": + self.decoder = bposd_decoder( + parity_check_matrix=self.H3D, + channel_probs=channel_probs, + max_iter=self.bp_params.max_bp_iter, + bp_method=self.bp_params.bp_method, + osd_order=self.bp_params.osd_order, + osd_method=self.bp_params.osd_method, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + ) + elif self.decoding_method == "matching": + weights = np.log((1 - channel_probs) / channel_probs) + self.decoder = Matching(self.H3D, weights=weights) + self.channel_probs = channel_probs + + def _decode_multiround( + self, + syndrome_mat: np.ndarray, + analog_syndr_mat: np.ndarray, + last_round: bool = False, + ) -> np.ndarray: + return decode_multiround( + syndrome=syndrome_mat, + H=self.H, + decoder=self.decoder, + repetitions=self.repetitions, + last_round=last_round, + analog_syndr=analog_syndr_mat, + check_block_size=self.check_block_size, + sigma=self.sigma, + H3D=self.H3D if self.decoding_method == "matching" else None, # avoid passing matrix in case not needed + channel_probs=self.channel_probs, + decoding_method=self.decoding_method, + ) + + def _single_sample(self) -> int: + # prepare fresh syndrome matrix and error vector + # each column == measurement result of a single timestep + syndrome_mat = np.zeros( + (self.num_checks, self.repetitions), dtype=np.int32 + ) + analog_syndr_mat = None + + if self.analog_tg: + analog_syndr_mat = np.zeros( + (self.num_checks, self.repetitions), dtype=np.float64 + ) + + err = np.zeros(self.num_qubits, dtype=np.int32) + cnt = 0 # counter for syndrome_mat + + for round in range(self.rounds): + residual_err = [np.copy(err), np.copy(err)] + err = generate_err( + N=self.num_qubits, + channel_probs=[ + self.x_bit_chnl, + self.y_bit_chnl, + self.z_bit_chnl, + ], + residual_err=residual_err, + )[self.err_idx] # only first or last vector needed, depending on side (X or Z) + noiseless_syndrome = (self.H @ err) % 2 + + # add syndrome error + if round != (self.rounds - 1): + if self.analog_tg: + analog_syndrome = get_noisy_analog_syndrome( + noiseless_syndrome, self.sigma + ) + syndrome = get_binary_from_analog( + analog_syndrome + ) + else: + syndrome_error = generate_syndr_err(self.syndr_err_channel) + syndrome = (noiseless_syndrome + syndrome_error) % 2 + else: # last round is perfect + syndrome = np.copy(noiseless_syndrome) + analog_syndrome = get_noisy_analog_syndrome( + noiseless_syndrome, 0.0 + ) # no noise + + # fill the corresponding column of the syndrome/analog syndrome matrix + syndrome_mat[:, cnt] += syndrome + if self.analog_tg: + analog_syndr_mat[:, cnt] += analog_syndrome + + cnt += 1 # move to next column of syndrome matrix + + if (cnt == self.repetitions): # if we have filled the syndrome matrix, decode + if round != (self.rounds - 1): # if not last round, decode and move syndrome + cnt = (self.repetitions // 2) # reset counter to start of tentative region + + # the correction is only the correction of the commit region + ( + corr, + syndrome_mat, + analog_syndr_mat, + bp_iters + ) = self._decode_multiround( + syndrome_mat, + analog_syndr_mat, + last_round=False, + ) + # we compute the average for all rounds since this equals a single sample + self.bp_iterations += bp_iters / self.rounds + err = (err + corr) % 2 + syndrome_mat = move_syndrome(syndrome_mat) + if self.analog_tg: + analog_syndr_mat = move_syndrome( + analog_syndr_mat, data_type=np.float64 + ) + + else: # if we are in the last round, decode and stop + # the correction is the correction of the commit and tentative region + ( + corr, + syndrome_mat, + analog_syndr_mat, + bp_iters + ) = self._decode_multiround( + syndrome_mat, + analog_syndr_mat, + last_round=True, + ) + self.bp_iterations += bp_iters / self.rounds + err = (err + corr) % 2 + return int(not is_logical_err(self.L, err)) + + def _save_results(self, success_cnt: int, samples: int) -> dict: + return save_results( + success_cnt=success_cnt, + nr_runs=samples, + p=self.data_err_rate, + s=self.syndr_err_rate, + input_vals=self.input_values, + outfile=self.outfile, + code_params=self.code_params, + err_side="z" if self.check_side == "X" else "x", + bp_iterations=self.bp_iterations, + bp_params=self.bp_params + ) + + def run(self, samples: int = 1): + """Returns single data point""" + success_cnt = 0 + for run in range(1, samples + 1): + success_cnt += self._single_sample() + if run % self.save_interval == 1: + self._save_results(success_cnt, run) + if _check_convergence( + success_cnt, run, self.code_params, self.eb_precission): + print("Converged") + break + return self._save_results(success_cnt, run) \ No newline at end of file diff --git a/src/mqt/qecc/analog-information-decoding/simulators/simulation.py b/src/mqt/qecc/analog-information-decoding/simulators/simulation.py new file mode 100644 index 00000000..7391b862 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/simulators/simulation.py @@ -0,0 +1,803 @@ +from utils.simulation_utils import * +from ldpc2 import bposd_decoder +from utils.data_utils import ( + calculate_error_rates, + is_converged, + replace_inf, + BpParams, +) # , create_outpath +from utils.data_utils import create_outpath as get_outpath +from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder +from timeit import default_timer as timer + + +class Single_Shot_Simulator: + def __init__( + self, + codename: str, + per: float, + ser: float, + single_stage: bool, + seed: int, + bias: list, + x_meta: bool, + z_meta: bool, + sus_th_depth: int, + analog_info: bool = False, + cutoff: int = 0, + analog_tg: bool = False, + bp_params: BpParams = None, + **kwargs, + ) -> None: + set_seed(seed) + self.codename = codename + self.data_err_rate = per + self.syndr_err_rate = ser + self.bias = bias + self.x_meta = x_meta + self.z_meta = z_meta + self.sus_th_depth = sus_th_depth + self.bp_params = bp_params + self.seed = seed + self.single_stage = single_stage + self.analog_info = analog_info + self.cutoff = cutoff + self.save_interval = kwargs.get("save_interval", 50) + self.eb_precission = kwargs.get("eb_precission", 1e-1) + self.analog_tg = analog_tg + self.x_bp_iters = 0 + self.z_bp_iters = 0 + # self.code_path = f"generated_codes/{codename}" + self.code_path = f"/codes/generated_codes/{codename}" + # Load code params + self.code_params = eval( + open(f"{self.code_path}/code_params.txt").read() + ) + + self.input_values = self.__dict__.copy() + self.outfile = get_outpath(**self.input_values) + + # Set parity check matrices + self.Hx = np.loadtxt( + f"{self.code_path}/hx.txt", dtype=np.int32 + ) + self.Hz = np.loadtxt( + f"{self.code_path}/hz.txt", dtype=np.int32 + ) + if self.x_meta: + self.Mx = np.loadtxt( + f"{self.code_path}/mx.txt", dtype=np.int32 + ) + else: + self.Mx = None + if self.z_meta: + self.Mz = np.loadtxt( + f"{self.code_path}/mz.txt", dtype=np.int32 + ) + else: + self.Mz = None + + self.n = self.Hx.shape[ + 1 + ] # m==shape[0] ambiguous for Hx, Hz, better use their shape directly + + self.check_input() + + # load logicals if possible + try: + self.lx = np.loadtxt( + f"{self.code_path}/lx.txt", dtype=np.int32 + ) + self.lz = np.loadtxt( + f"{self.code_path}/lz.txt", dtype=np.int32 + ) + self._check_logicals = True + except: + self._check_logicals = False + + ( + self.x_bit_err_channel, + self.y_bit_err_channel, + self.z_bit_err_channel, + ) = error_channel_setup(self.data_err_rate, self.bias, self.n) + # encapsulates all bit error channels + self.data_error_channel = [ + self.x_bit_err_channel, + self.y_bit_err_channel, + self.z_bit_err_channel, + ] + + # now setup syndrome error channels. + # This needs two calls since the syndromes may have different lengths + # first vector ([0]) is x channel probability vector + # by our convention the syndrome is named after the error. + full_x_syndr_err_chnl = error_channel_setup( + self.syndr_err_rate, self.bias, self.Hz.shape[0] + ) + full_z_syndr_err_chnl = error_channel_setup( + self.syndr_err_rate, self.bias, self.Hx.shape[0] + ) + self.x_syndr_error_channel = full_x_syndr_err_chnl[0] + full_x_syndr_err_chnl[1] # x+y + self.z_syndr_error_channel = full_z_syndr_err_chnl[2] + full_z_syndr_err_chnl[1] # z+y + + # if we want to decode with analog syndrome noise and an analog decoder + if self.analog_info or self.analog_tg: + self.sigma_x = get_sigma_from_syndr_er(self.x_syndr_error_channel[0]) + self.sigma_z = get_sigma_from_syndr_er(self.z_syndr_error_channel[0]) + else: + self.sigma_x = None + self.sigma_z = None + + # if we want to decode with the analog tanner graph method construct respective matrices. + # These are assumed to exist in the *_setup() methods + if self.analog_tg: + self.x_apcm, self.z_apcm = self.construct_analog_pcms() + + if self.single_stage: + self._single_stage_setup() + else: + self._two_stage_setup() + + + self._total_decoding_time = 0.0 + + def check_input(self): + """Check initialization parameters for consistency.""" + if self.analog_tg == True and self.analog_info == True: + raise ValueError("analog_tg and analog_info cannot be both True") + + def _single_sample( + self, + ): + """ + Simulates a single sample for a given sustainable threshold depth. + :return: + """ + residual_err = [ + np.zeros(self.n).astype(np.int32), # X-residual error part + np.zeros(self.n).astype(np.int32), # Z-residual error part + ] + + # for single shot simulation we have sus_th_depth number of 'noisy' simulations (residual error carried over) + # followed by a single round of perfect syndrome extraction after the sustainable threshold loop + for round in range(self.sus_th_depth): + x_err, z_err = generate_err( + N=self.n, + channel_probs=self.data_error_channel, + residual_err=residual_err, + ) + # by our convention, we call the syndrome after the error that is occured + # however, the check_error_rate depends on which check errors, + # hence is named after the check matrix. + x_syndrome = self.Hz @ x_err % 2 + z_syndrome = self.Hx @ z_err % 2 + + x_syndrome_w_err, z_syndrome_w_err = self._get_noisy_syndrome( + x_syndrome, z_syndrome + ) + + # collect total decoding time + start = timer() + if self.single_stage: + x_decoded, z_decoded = self._single_stage_decoding( + x_syndrome_w_err, z_syndrome_w_err + ) + else: + x_decoded, z_decoded = self._two_stage_decoding( + x_syndrome_w_err, z_syndrome_w_err + ) + end = timer() + self._total_decoding_time += end - start + + residual_err = [ + np.array( + (x_err + x_decoded) % 2, dtype=np.int32 + ), # np conversion needed to avoid rt error + np.array((z_err + z_decoded) % 2, dtype=np.int32), + ] + + # perfect measurement round at the end + x_err, z_err = generate_err( + N=self.n, + channel_probs=self.data_error_channel, + residual_err=residual_err, + ) + + # X-syndrome: sx = Hz * ex + # Z-syndrome: sz = Hx * ez + x_syndrome = self.Hz @ x_err % 2 + z_syndrome = self.Hx @ z_err % 2 + + start = timer() + x_decoded = self.x_bpd.decode(x_syndrome) + z_decoded = self.z_bpd.decode(z_syndrome) + end = timer() + self._total_decoding_time += end - start + + # residual[0]: X-residual error, residual[1]: Z-residual error + residual_err = [(x_err + x_decoded) % 2, (z_err + z_decoded) % 2] + + x_residual_err = residual_err[0] + z_residual_err = residual_err[1] + + # check for logical errors + # check if residual X-error commutes with Z-logical and vice versa + # equivalently, check if residual X-error is in rowspace of Hz and vice versa + if self._check_logicals: + is_x_logical_error = is_logical_err(self.lz, x_residual_err) + is_z_logical_error = is_logical_err(self.lx, z_residual_err) + else: + is_x_logical_error = check_logical_err_h( + self.Hz, x_err, x_residual_err + ) + is_z_logical_error = check_logical_err_h( + self.Hx, z_err, z_residual_err + ) + + return is_x_logical_error, is_z_logical_error + + def _get_noisy_syndrome(self, x_syndrome, z_syndrome): + if self.syndr_err_rate != 0.0: + if ( + self.analog_info or self.analog_tg + ): # analog syndrome error with converted sigma + x_syndrome_w_err = get_noisy_analog_syndrome( + perfect_syndr=x_syndrome, sigma=self.sigma_x + ) + z_syndrome_w_err = get_noisy_analog_syndrome( + perfect_syndr=z_syndrome, sigma=self.sigma_z + ) + else: # usual pauli error channel syndrome error + x_syndrome_err = generate_syndr_err( + channel_probs=self.x_syndr_error_channel + ) + x_syndrome_w_err = (x_syndrome + x_syndrome_err) % 2 + z_syndrome_err = generate_syndr_err( + channel_probs=self.z_syndr_error_channel + ) + z_syndrome_w_err = (z_syndrome + z_syndrome_err) % 2 + else: + x_syndrome_w_err = np.copy(x_syndrome) + z_syndrome_w_err = np.copy(z_syndrome) + + return x_syndrome_w_err, z_syndrome_w_err + + def _single_stage_decoding(self, x_syndrome_w_err, z_syndrome_w_err): + """Single stage decoding of the given syndromes + If meta checks are activated, we apply the single-stage decoding method. + This is complemented by either analog_tg decoding or analog_info decoding (or standard decoding) of the + single stage matrix. + + Otherwise, we apply the decoding methods to the standard check matrices, hence the meta code is not used. + """ + # for convenience + x_err_channel = self.x_bit_err_channel + self.y_bit_err_channel + z_err_channel = self.z_bit_err_channel + self.y_bit_err_channel + + if self.x_meta: + z_decoded, self.z_bp_iters = self._decode_ss_with_meta( + syndrome_w_err=z_syndrome_w_err, + M=self.Mx, + ss_bpd=self.ss_z_bpd, + bit_err_channel=z_err_channel, + sigma=self.sigma_z, + ) + else: # do not use x meta checks, just decode with noisy syndrome on standard check matrix + z_decoded, self.z_bp_iters = self._decode_ss_no_meta( + syndrome_w_err=z_syndrome_w_err, + analog_tg_decoder=self.z_abpd, + standard_decoder=self.z_bpd, + bit_err_channel=z_err_channel, + sigma=self.sigma_z, + ) + + if self.z_meta: + x_decoded, self.x_bp_iters = self._decode_ss_with_meta( + syndrome_w_err=x_syndrome_w_err, + M=self.Mz, + ss_bpd=self.ss_x_bpd, + bit_err_channel=x_err_channel, + sigma=self.sigma_x, + ) + else: # do not use x meta checks, just decode with noisy syndrome on check matrix or analog_tg method + x_decoded, self.x_bp_iters = self._decode_ss_no_meta( + syndrome_w_err=x_syndrome_w_err, + analog_tg_decoder=self.x_abpd, + standard_decoder=self.x_bpd, + bit_err_channel=x_err_channel, + sigma=self.sigma_x, + ) + return x_decoded, z_decoded + + def _decode_ss_no_meta( + self, + syndrome_w_err, + analog_tg_decoder, + standard_decoder, + bit_err_channel, + sigma, + ): + """Decoding of syndrome without meta checks. + In case analog_tg is active, we use the analog tanner graph decoding method, which uses the analog syndrome. + Otherwise, we decode the standard check matrix with BPOSD, or the AI decoder in case analog_info is active. + """ + if self.analog_tg: + decoded = self._analog_tg_decoding( + decoder=analog_tg_decoder, + hard_syndrome=get_binary_from_analog(syndrome_w_err), + analog_syndrome=syndrome_w_err, + bit_err_channel=bit_err_channel, + sigma=sigma, + ) + iter = analog_tg_decoder.iter + else: + decoded = standard_decoder.decode(syndrome_w_err) + iter = standard_decoder.iter + return decoded, iter + + def _decode_ss_with_meta( + self, syndrome_w_err, ss_bpd, M, bit_err_channel, sigma + ): + """Single-Stage decoding for given syndrome. + + If analog_tg is active, we use the analog tanner graph decoding method, which uses the analog syndrome to + initialize the virtual nodes s.t. they contain the soft info. + + If analog_info is active, we use the analog_info decoder on the single-stage matrix. + + Otherwise, standard single-stage decoding is applied + """ + if self.analog_tg: + decoded = self._ss_analog_tg_decoding( + decoder=ss_bpd, + analog_syndrome=syndrome_w_err, + M=M, + bit_err_channel=bit_err_channel, + sigma=sigma, + ) + else: + if self.analog_info: + meta_bin = ( + M @ get_binary_from_analog(syndrome_w_err) + ) % 2 + meta_syndr = get_signed_from_binary( + meta_bin + ) # for AI decoder we need {-1,+1} syndrome as input + else: + meta_syndr = (M @ syndrome_w_err) % 2 + + ss_syndr = np.hstack((syndrome_w_err, meta_syndr)) + # only first n bit are data, the other are virtual nodes and can be discarded for estimate + decoded = ss_bpd.decode(ss_syndr)[: self.n] + return decoded, ss_bpd.iter + + def _ss_analog_tg_decoding( + self, decoder, analog_syndrome, M, bit_err_channel, sigma: float + ): + """Decodes the noisy analog syndrome using the single stage analog tanner graph and BPOSD, i.e., + combines single-stage and analog tanner graph method. + In the standard single-stage method, BP is initialized with the bit channel + the syndrome channel. + In order for the virtual nodes to contain the analog info, we adapt the syndrome channel by computing + the 'analog channel' given the analog syndrome. + """ + + analog_channel = get_virtual_check_init_vals(analog_syndrome, sigma) + decoder.update_channel_probs( + np.hstack((bit_err_channel, analog_channel)) + ) + + bin_syndr = get_binary_from_analog( + analog_syndrome + ) # here we need to threshold since syndrome is analog + meta_syndr = (M @ bin_syndr) % 2 + ss_syndr = np.hstack((bin_syndr, meta_syndr)) + decoded = decoder.decode(ss_syndr)[: self.n] # only first n bit are data + + return decoded + + def _two_stage_decoding(self, x_syndrome_w_err, z_syndrome_w_err): + """Two stage decoding of single shot code + Meta checks on either side (or both) can be deactivated. + + If the meta checks for a side are activated the syndrome is thresholded and repaired using the meta code. + + If analog_tg is used for decoding, the analog syndrome is used to initialize the BP decoder and the + thresholded (repaired) syndrome is used as input. + + In case analog_info is activated x_bpd/z_bpd have been set accordinlgy in the setup() method. + """ + if self.x_meta: + # Mx corrects failures of Hx and Mz corrects failures of Mz + # thus, meta syndrome of z_syndrome = Mx*z_syndrome where z_syndrome = Hx*z_error + z_syndrome_repaired = self._meta_code_decoding( + syndrome_w_err=z_syndrome_w_err, + M=self.Mx, + m_bp=self.mz_bp, + sigma=self.sigma_z, + ) + else: + z_syndrome_repaired = np.copy(z_syndrome_w_err) + + if self.z_meta: + x_syndrome_repaired = self._meta_code_decoding( + syndrome_w_err=x_syndrome_w_err, + M=self.Mz, + m_bp=self.mx_bp, + sigma=self.sigma_x, + ) + else: + x_syndrome_repaired = np.copy(x_syndrome_w_err) + + if ( + self.analog_tg + ): # decode with analog tg method - update channel probs to init analog nodes + x_decoded = self._analog_tg_decoding( + decoder=self.x_abpd, + hard_syndrome=x_syndrome_repaired, + analog_syndrome=x_syndrome_w_err, + bit_err_channel=self.z_bit_err_channel + self.y_bit_err_channel, + sigma=self.sigma_x, + ) + z_decoded = self._analog_tg_decoding( + decoder=self.z_abpd, + hard_syndrome=z_syndrome_repaired, + analog_syndrome=z_syndrome_w_err, + bit_err_channel=self.x_bit_err_channel + self.y_bit_err_channel, + sigma=self.sigma_z, + ) + else: + x_decoded = self.x_bpd.decode(x_syndrome_repaired) + z_decoded = self.z_bpd.decode(z_syndrome_repaired) + + return x_decoded, z_decoded + + def _meta_code_decoding(self, syndrome_w_err, M, m_bp, sigma): + """Decodes the noisy syndrome using the meta code + If analog_info or analog_tg is activated the analog syndrome is thresholded to binary to be able to use + the meta code. The binary syndrome is repaired according to the meta code and a binary, + repaired syndrome is returned. + """ + if self.analog_info or self.analog_tg: + syndrome_w_err = get_binary_from_analog(syndrome_w_err) + + meta_syndrome = (M @ syndrome_w_err) % 2 + meta_decoded = m_bp.decode(meta_syndrome) + syndrome_w_err += meta_decoded # repair syndrome + syndrome_w_err %= 2 + + return syndrome_w_err + + def _analog_tg_decoding( + self, decoder, analog_syndrome, hard_syndrome, bit_err_channel, sigma + ): + """Decodes the noisy analog syndrome using the analog tanner graph and BPOSD + First, the channel probabilities need to be set according to the analog syndrome s.t. the decoder is + initialized properly. + Only the first n bits of the decoding estimate returned by the decoder are true estimates, + the other bits are acting as 'virtual checks' and thus excluded from the result. + The bit_err_channel is supposed to be the error channel for the n physical bits as usual. + """ + analog_channel = get_virtual_check_init_vals(analog_syndrome, sigma) + decoder.update_channel_probs( + np.hstack((bit_err_channel, analog_channel)) + ) + + decoded = decoder.decode(hard_syndrome)[ + : self.n + ] # only first n bit are data + + return decoded + + def _single_stage_setup( + self, + ): + """ + Sets up the single stage decoding. + * BPOSD decoders for the single-stage check matrices (cf Higgot & Breuckmann) are setup in case + there is a meta code for the respective side. + * BPOSD decoders for the check matrices Hx/Hz are set up for the last, perfect round + * In case analog_tg is activated, BPOSD for the analog tanner graph is setup + * If analog_info is active, SI-decoder is used to decode single-stage matrices and standard check matrices + instead of BPOSD + """ + # X-syndrome sx = Hz*ex + # x_syndr_error == error on Hz => corrected with Mz + + # setup single stage matrices (Oscar&Niko method) H = [[H, Im], H ~ mxn, I ~ mxm, M ~ lxm + # [0, M]] + if self.z_meta: + self.ss_z_pcm = build_single_stage_pcm(self.Hz, self.Mz) + else: + self.ss_z_pcm = None + if self.x_meta: + self.ss_x_pcm = build_single_stage_pcm(self.Hx, self.Mx) + else: + self.ss_x_pcm = None + + # X-checks := (Hx|Mx) => Influenced by Z-syndrome error rate + # ss_x_bpd used to decode X bit errors using Z-side check matrices + # single shot bp operates on the single shot pcm (Hz|Mz) + # hence we need x bit error rate and x syndrome error rate + x_err_channel = self.x_bit_err_channel + self.y_bit_err_channel + if self.ss_z_pcm is not None: + self.ss_x_bpd = self.get_decoder( + pcm=self.ss_z_pcm, + channel_probs=np.hstack( + (x_err_channel, self.x_syndr_error_channel) + ), + sigma=self.sigma_x, + analog_info=self.analog_info, + ) + + if self.analog_tg: + self.x_abpd = self.get_decoder( + pcm=self.z_apcm, + # second part dummy, needs to be updated for each syndrome + channel_probs=np.hstack( + (x_err_channel, np.zeros(self.Hz.shape[0])) + ), + cutoff=self.cutoff, + analog_info=False + # sigma not needed, since we apply BPOSD + ) + else: + self.x_abpd = None + self.x_bpd = self.get_decoder( + pcm=self.Hz, + channel_probs=x_err_channel, + cutoff=self.cutoff, + analog_info=self.analog_info, + sigma=self.sigma_x, + ) + + z_err_channel = self.z_bit_err_channel + self.y_bit_err_channel + if self.ss_x_pcm is not None: + self.ss_z_bpd = self.get_decoder( + pcm=self.ss_x_pcm, + channel_probs=np.hstack( + (z_err_channel, self.z_syndr_error_channel) + ), + cutoff=self.cutoff, + analog_info=self.analog_info, + sigma=self.sigma_z, + ) + + if self.analog_tg: + self.z_abpd = self.get_decoder( + pcm=self.x_apcm, + # second part dummy, needs to be updated for each syndrome + channel_probs=np.hstack( + (z_err_channel, np.zeros(self.Hx.shape[0])) + ), + cutoff=self.cutoff, + analog_info=self.analog_info + # sigma not needed, since we apply BPOSD + ) + else: + self.z_abpd = None + self.z_bpd = self.get_decoder( + pcm=self.Hx, + channel_probs=z_err_channel, + cutoff=self.cutoff, + analog_info=self.analog_info, + sigma=self.sigma_z, + ) + + def _two_stage_setup( + self, + ): + """ + Sets up the two stage decoding. + * In case meta codes are present, BPOSD decoders for the meta codes are setup + * In case analo_tg is active, BPOSD decoders for the analog tanner graph are setup + * Additionally, BPOSD for Hx, Hz are setup for the final round + :return: + """ + # used to decode errors in Hz == x-syndrome errors with x_synd_error rate + if self.z_meta: + self.mx_bp = self.get_decoder( + pcm=self.Mz, + channel_probs=self.x_syndr_error_channel, + cutoff=self.cutoff, + analog_info=False, # =False and sigma not needed, since we don't have soft info for meta code + ) + + # used to decode errors in Hx + if self.x_meta: + self.mz_bp = self.get_decoder( + pcm=self.Mx, + channel_probs=self.z_syndr_error_channel, + cutoff=self.cutoff, + analog_info=False, # =False and sigma not needed, since we don't have soft info for meta code + ) + + x_err_channel = self.x_bit_err_channel + self.y_bit_err_channel + if self.analog_tg: + self.x_abpd = self.get_decoder( + pcm=self.z_apcm, + channel_probs=np.hstack( + (x_err_channel, np.zeros(self.Hz.shape[0])) + ), + # second part dummy, needs to be updated for each syndrome + cutoff=self.cutoff, + analog_info=self.analog_info + # sigma not needed, since we apply BPOSD + ) + self.x_bpd = self.get_decoder( + pcm=self.Hz, + channel_probs=x_err_channel, + cutoff=self.cutoff, + analog_info=self.analog_info, + sigma=self.sigma_x, + ) + + z_err_channel = self.z_bit_err_channel + self.y_bit_err_channel + if self.analog_tg: + self.z_abpd = self.get_decoder( + pcm=self.x_apcm, + # second part dummy, needs to be updated for each syndrome + channel_probs=np.hstack( + (z_err_channel, np.zeros(self.Hx.shape[0])) + ), + cutoff=self.cutoff, + analog_info=self.analog_info + # sigma not needed, since we apply BPOSD + ) + self.z_bpd = self.get_decoder( + pcm=self.Hx, + channel_probs=z_err_channel, + cutoff=self.cutoff, + analog_info=self.analog_info, + sigma=self.sigma_z + # sigma not needed, since we don't have analog info for meta code + ) + + def get_decoder( + self, + pcm, + channel_probs, + cutoff: int = 0, + sigma: float = 0.0, + analog_info: bool = False, + ): + """Initialize decoder objects + If analog_info is activated, the SoftInfoBpDecoder is used instead of the BPOSD decoder. + Note that analog_info and analog_tg cannot be used simultaneously. + """ + if analog_info: + return SoftInfoBpOsdDecoder( + pcm=pcm, + error_channel=channel_probs, + max_iter=self.bp_params.max_bp_iter, + bp_method=self.bp_params.bp_method, + osd_order=self.bp_params.osd_order, + osd_method=self.bp_params.osd_method, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + cutoff=cutoff, + sigma=sigma, + ) + else: + return bposd_decoder( + parity_check_matrix=pcm, + channel_probs=channel_probs, + max_iter=self.bp_params.max_bp_iter, + bp_method=self.bp_params.bp_method, + osd_order=self.bp_params.osd_order, + osd_method=self.bp_params.osd_method, + ms_scaling_factor=self.bp_params.ms_scaling_factor, + ) + + def construct_analog_pcms(self): + """ + constructs apcm = [H | I_m] where I_m is the m x m identity matrix + """ + return np.hstack( + [self.Hx, np.identity(self.Hx.shape[0], dtype=np.int32)] + ), np.hstack([self.Hz, np.identity(self.Hz.shape[0], dtype=np.int32)]) + + def save_results(self, + x_success_cnt: int, + z_success_cnt: int, + runs: int, + x_bp_iters: np.ndarray, + z_bp_iters: np.ndarray): + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( + x_success_cnt, runs, self.code_params + ) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( + z_success_cnt, runs, self.code_params + ) + + output = { + "code_K": self.code_params["k"], + "code_N": self.code_params["n"], + "nr_runs": runs, + "pers": self.data_err_rate, + "sers": self.syndr_err_rate, + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + "avg_x_bp_iter": x_bp_iters / runs, + "avg_z_bp_iter": z_bp_iters / runs, + "decoding_time": self._total_decoding_time, + } + + output.update(self.input_values) + output["bias"] = replace_inf(output["bias"]) + with open(self.outfile, "w") as f: + json.dump( + output, + f, + ensure_ascii=False, + indent=4, + default=lambda o: o.__dict__, + ) + return output + + def run(self, samples: int): + x_success_cnt = 0 + z_success_cnt = 0 + x_bp_iters = 0 + z_bp_iters = 0 + for runs in range(1, samples + 1): + out = self._single_sample() + if not out[0]: + x_success_cnt += 1 + if not out[1]: + z_success_cnt += 1 + x_bp_iters += self.x_bp_iters + z_bp_iters += self.z_bp_iters + + if runs % self.save_interval == 0: + self.save_results(x_success_cnt, z_success_cnt, runs, + x_bp_iters=x_bp_iters, z_bp_iters=z_bp_iters) + if is_converged( + x_success_cnt, + z_success_cnt, + runs, + self.code_params, + self.eb_precission, + ): + print("Result has converged.") + break + + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( + x_success_cnt, runs, self.code_params + ) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( + z_success_cnt, runs, self.code_params + ) + avg_x_bp_iter = x_bp_iters / runs + avg_z_bp_iter = z_bp_iters / runs + output = { + "code_K": self.code_params["k"], + "code_N": self.code_params["n"], + "nr_runs": runs, + "pers": self.data_err_rate, + "sers": self.syndr_err_rate, + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + "avg_x_bp_iters": avg_x_bp_iter, + "avg_z_bp_iters": avg_z_bp_iter, + "decoding_time": self._total_decoding_time, + } + + output.update(self.input_values) + + self.save_results(x_success_cnt, z_success_cnt, runs, x_bp_iters=x_bp_iters, z_bp_iters=z_bp_iters) + return output \ No newline at end of file diff --git a/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py b/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py new file mode 100644 index 00000000..cbcf43fd --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py @@ -0,0 +1,61 @@ +import numpy as np +from scipy.sparse import csr_matrix, hstack, vstack, diags, eye, block_diag +from simulators.memory_experiment_v2 import build_multiround_pcm, move_syndrome + + +def test_build_mr_pcm(): + H = np.array([ + [1, 1, 0], + [0, 1, 1] + ]).astype(np.int32) + mr_pcm = build_multiround_pcm(H, 1) + np.zeros((2, 3)) + np.identity(2) + r1 = np.hstack([H, np.zeros(H.shape), np.identity(2), np.zeros((2, 2))]) + r2 = np.hstack([np.zeros(H.shape), H, np.identity(2), np.identity(2)]) + expected = np.vstack((r1, r2)) + + assert np.array_equal(mr_pcm.toarray(), expected) + + +def test_move_syndrome() -> None: + # three bit syndrome over 4 rounds + syndr = np.array([ + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 0, 1, 0] + ]) + res = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [1, 0, 0, 0] + ]) + assert np.array_equal(res, move_syndrome(syndr)) + + syndr = np.array([ + [0, 0, 1, 1], + [0, 0, 1, 1], + [0, 0, 1, 1] + ]) + res = np.array([ + [1, 1, 0, 0], + [1, 1, 0, 0], + [1, 1, 0, 0] + ]) + assert np.array_equal(res, move_syndrome(syndr)) + + syndr = np.array([ + [0, 0, 1, 1, 0, 0], + [0, 0, 1, 1, 0, 0], + [0, 0, 1, 1, 0, 0] + ]) + res = np.array([ + [1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0] + ]) + assert np.array_equal(res, move_syndrome(syndr)) + + +if __name__ == "__main__": + test_build_mr_pcm() diff --git a/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py b/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py new file mode 100644 index 00000000..e826cc5b --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py @@ -0,0 +1,197 @@ +import math + +import numpy as np +from scipy.sparse import csr_matrix, coo_matrix + +import utils +from utils import simulation_utils + + +def test_check_logical_err_h() -> None: + H = np.array( + [ + [1, 0, 0, 1, 0, 1, 1], + [0, 1, 0, 1, 1, 0, 1], + [0, 0, 1, 0, 1, 1, 1] + ]) + # check with logical + estimate = np.array([1, 0, 0, 0, 0, 0, 1]) + assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 1, 1, 0]), estimate) == True + # + # check with stabilizer + estimate2 = np.array([0, 0, 0, 0, 0, 0, 1]) + assert simulation_utils.check_logical_err_h(H, np.array([1, 1, 1, 0, 0, 0, 0]), estimate2) == False + + # check with all zeros + estimate3 = np.array([0, 0, 0, 0, 0, 0, 0]) + assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 0, 0, 0]), estimate3) == False + + +def test_is_logical_err() -> None: + # check with logical + Lsc = np.array( + [[1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) + residual = np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + assert simulation_utils.is_logical_err(Lsc, residual) == True + + # check with stabilizer + residual2 = np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert simulation_utils.is_logical_err(Lsc, residual2) == False + + # check with all zeros + residual2 = np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert simulation_utils.is_logical_err(Lsc, residual2) == False + + # check with non-min weight logical + residual3 = np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert simulation_utils.is_logical_err(Lsc, residual3) == True + + +def test_get_analog_llr() -> None: + analog_syndr = np.array([0.5, 0, 0, -1, 0, 1]) + sigma = 0.8 + + assert np.allclose(simulation_utils.get_analog_llr(analog_syndr, sigma), + np.array([1.5625, 0., 0., -3.125, 0., 3.125])) + + sigma = 0.1 + assert np.allclose(simulation_utils.get_analog_llr(analog_syndr, sigma), np.array([100, 0., 0., -200., 0., 200.])) + +def test_generate_err() -> None: + # no errors + p = 0.0 + n = 10 + ch = np.ones(n) * p + channel = [np.copy(ch), np.copy(ch), np.copy(ch)] + residual = [np.zeros(n).astype(np.int32), np.zeros(n).astype(np.int32)] + + expected = [np.zeros(n).astype(np.int32), np.zeros(n).astype(np.int32)] + assert np.array_equal(simulation_utils.generate_err(n, channel, residual), expected) + + residual[0][0] = 1 + residual[1][0] = 1 + + expected = [np.copy(residual[0]), np.copy(residual[1])] + res = simulation_utils.generate_err(n, channel, residual) + assert np.array_equal(res[0], expected[0]) and np.array_equal(res[1], expected[1]) + + +def test_get_sigma_from_syndr_er() -> None: + ser = 0.1 + + assert math.ceil(simulation_utils.get_sigma_from_syndr_er(ser)) == math.ceil(0.780304146072379) + ser = 1.0 + assert math.ceil(simulation_utils.get_sigma_from_syndr_er(ser)) == -0.0 + ser = 0.0 + assert math.ceil(simulation_utils.get_sigma_from_syndr_er(ser)) == 0.0 + + +def test_get_error_rate_from_sigma() -> None: + sigma = 0.3 + assert np.isclose([simulation_utils.get_error_rate_from_sigma(sigma)], [0.00042906]) + sigma = 0.5 + assert np.isclose([simulation_utils.get_error_rate_from_sigma(sigma)], [0.02275]) + sigma = 0.0 + assert simulation_utils.get_error_rate_from_sigma(sigma) == 0.0 + + +def test_get_virtual_check_init_vals() -> None: + noisy_syndr = np.array([0.5, 0, 0, -1, 0, 100]) + sigma = 0.8 + + assert np.allclose(simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), + np.array([1.73288206e-001, 5.00000000e-001, 5.00000000e-001, 4.20877279e-002, 5.00000000e-001, + 1.91855567e-136])) + + sigma = 0.1 + assert np.allclose(simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), + np.array([3.72007598e-44, 5.00000000e-01, 5.00000000e-01, 1.38389653e-87, 5.00000000e-01, + 0.00000000e+00])) + sigma = 0.0 + res = simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma) + + assert math.isnan(res[1]) and res[0] == 0. + + +def test_generate_syndr_err() -> None: + channel = np.array([0.0, 1.0]) + + assert np.array_equal(simulation_utils.generate_syndr_err(channel), np.array([0., 1.])) + + +def test_get_noisy_analog_syndr() -> None: + perfect_s = np.array([1, 0]) + sigma = 0.0 + + assert np.array_equal(simulation_utils.get_noisy_analog_syndrome(perfect_s, sigma), np.array([-1., 1.])) + + +def test_err_chnl_setup() -> None: + p = 0.1 + bias = [1., 1., 1.] + n = 10 + ar = np.ones(n) * p / 3 + exp = [np.copy(ar), np.copy(ar), np.copy(ar)] + res = simulation_utils.error_channel_setup(p, bias, n) + + assert np.array_equal(res, exp) + + bias = [1., 0., 0.] + ar = np.ones(n) * p + exp = [np.copy(ar), np.zeros(n), np.zeros(n)] + res = simulation_utils.error_channel_setup(p, bias, n) + + assert np.array_equal(res, exp) + + bias = [1., 1., 0.] + ar = np.ones(n) * p / 2 + exp = [np.copy(ar), np.copy(ar), np.zeros(n)] + res = simulation_utils.error_channel_setup(p, bias, n) + + assert np.array_equal(res, exp) + + bias = [np.inf, 0., 0.] + ar = np.ones(n) * p + exp = [np.copy(ar), np.zeros(n), np.zeros(n)] + res = simulation_utils.error_channel_setup(p, bias, n) + + assert np.array_equal(res, exp) + + +def test_build_ss_pcm() -> None: + H = np.array([ + [1, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, 1], + [1, 0, 0, 1]] + ) + M = np.array([ + [1, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, 1], + [1, 0, 0, 1]] + ) + id_r = np.identity(M.shape[1]) + zeros = np.zeros((M.shape[0], H.shape[1])) + exp = np.block([[H, id_r], [zeros, M]]).astype(np.int32) + assert np.array_equal(utils.build_single_stage_pcm(H, M), exp) + + +def test_get_signed_from_binary() -> None: + binary = np.array([1, 0, 0, 1, 0, 1]) + exp = np.array([-1, 1, 1, -1, 1, -1]) + + assert np.array_equal(utils.get_signed_from_binary(binary), exp) + + +def test_get_binary_from_analog() -> None: + exp = np.array([1, 0, 0, 1, 0, 1]) + analog = np.array([-1.0, 3., 1., -1., 1., -2]) + + assert np.array_equal(utils.get_binary_from_analog(analog), exp) diff --git a/src/mqt/qecc/analog-information-decoding/utils/__init__.py b/src/mqt/qecc/analog-information-decoding/utils/__init__.py new file mode 100644 index 00000000..f4a7ed23 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/utils/__init__.py @@ -0,0 +1,2 @@ +from data_utils import * +from simulation_utils import * diff --git a/src/mqt/qecc/analog-information-decoding/utils/data_utils.py b/src/mqt/qecc/analog-information-decoding/utils/data_utils.py new file mode 100644 index 00000000..6044a897 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/utils/data_utils.py @@ -0,0 +1,597 @@ +from __future__ import annotations +import numpy as np +import itertools +import os +import json +from typing import Any, Dict, List, Optional, Union +from dataclasses import fields, dataclass +from pathlib import Path + +@dataclass +class BpParams: + """ + Class to store parameters for BP decoding. + """ + + bp_method: str = "msl" + max_bp_iter: int = 30 + osd_order: int = 10 + osd_method: str = "osd_cs" + ms_scaling_factor: float = 0.75 + schedule: str = "parallel" + omp_thread_count: int = 1 + random_serial_schedule: int = 0 + serial_schedule_order: Optional[List[int]] = None + cutoff: float = np.inf + + @classmethod + def from_dict(cls, dict_): + class_fields = {f.name for f in fields(cls)} + return BpParams(**{k: v for k, v in dict_.items() if k in class_fields}) + + +def extract_settings(filename): + """ + Extracts all settings from a parameter file and returns them as a dictionary. + """ + + keyword_lists = {} + + with open(filename, 'r') as file: + for line in file: + json_data = json.loads(line.strip()) + for keyword, value in json_data.items(): + if keyword not in keyword_lists: + keyword_lists[keyword] = [] + if value not in keyword_lists[keyword]: + keyword_lists[keyword].append(value) + + return keyword_lists + + +def load_data( + input_filenames: list[str], + ) -> list[dict]: + """ + Loads data from a list of JSON files and returns it as a list of dictionaries. + """ + + data = [] + for file in input_filenames: + path = Path(file) + + try: + ldata = json.load(path.open()) + data.append(ldata) + except: + merge_json_files(path.with_suffix("")) + ldata = json.load(path.open()) + data.append(ldata) + return data + + +def calculate_error_rates(success_cnt, runs, code_params): + """ + Calculates logical error rate, logical error rate error bar, word error rate, + and word error rate error bar. + """ + logical_err_rate = 1.0 - (success_cnt / runs) + logical_err_rate_eb = np.sqrt( + (1 - logical_err_rate) * logical_err_rate / runs + ) + word_error_rate = 1.0 - (1 - logical_err_rate) ** (1 / code_params["k"]) + word_error_rate_eb = ( + logical_err_rate_eb + * ((1 - logical_err_rate_eb) ** (1 / code_params["k"] - 1)) + / code_params["k"] + ) + return ( + logical_err_rate, + logical_err_rate_eb, + word_error_rate, + word_error_rate_eb, + ) + + +def is_converged(x_success, z_success, runs, code_params, precission): + x_cond = _check_convergence( + x_success, runs, code_params, precission_cutoff=precission + ) + z_cond = _check_convergence( + z_success, runs, code_params, precission_cutoff=precission + ) + return x_cond == z_cond == True + + +def _check_convergence(success_cnt, runs, code_params, precission_cutoff): + _, _, ler, ler_eb = calculate_error_rates(success_cnt, runs, code_params) + if ler_eb != 0.0: + if ler_eb / ler < precission_cutoff: + return True + else: + return False + + +def create_outpath( + x_meta: bool = False, + z_meta: bool = False, + bias: List[float] = None, + codename: str = None, + single_stage: bool = True, + sus_th_depth: int = None, + rounds: int = None, + id: int = 0, + overwrite: bool = False, + analog_info: bool = False, + analog_tg: bool = False, + repetitions: int = None, + experiment: str = "wer_per_round", + **kwargs, +) -> str: + path = f"results/{experiment:s}/" + if analog_info: + path += "analog_info/" # ranvendraan et al analog info decoder + elif analog_tg: + path += "analog_tg/" # analog tannergraph bp + else: + path += "hard_syndrome/" # standard hard syndrome decoding only + if bias is not None: + path += f"single_stage={single_stage}/bias={bias[0]}_{bias[1]}_{bias[2]}/" + + if sus_th_depth: + path += f"sus_th_depth={sus_th_depth}/" + elif rounds: + path += f"rounds={rounds}/" + # else: + # raise ValueError( + # "Either sus_th_depth or window_count_per_run must be specified." + # ) + + if repetitions: + path += f"repetitions={repetitions}/" + + if x_meta: + path += "x-meta=true/" + else: + path += "x-meta=false/" + + if z_meta: + path += "z-meta=true/" + else: + path += "z-meta=false/" + + path += f"{codename:s}/" + + if "syndr_err_rate" not in kwargs or kwargs['syndr_err_rate'] is None: + if "sigma" in kwargs: + path += ( + f"per_{kwargs['data_err_rate']:.3e}_sigma_{kwargs['sigma']:.3e}/" + ) + if "z_sigma" in kwargs: + path += ( + f"per_{kwargs['data_err_rate']:.3e}_x_sigma_{kwargs['x_sigma']:.3e}_z_sigma_{kwargs['z_sigma']:.3e}" + ) + else: + path += ( + f"per_{kwargs['data_err_rate']:.3e}_ser_{kwargs['syndr_err_rate']:.3e}/" + ) + + if not os.path.exists(path): + os.makedirs(path, exist_ok=True) + + if overwrite == False: + f_loc = path + f"id_{id}.json" + while os.path.exists(f_loc): + id += 1 + f_loc = path + f"id_{id}.json" + + while not os.path.exists(f_loc): + open(f_loc, "w").close() + + return f_loc + + +def replace_inf(lst: list): + new_lst = [] + for item in lst: + if np.isinf(item): + new_lst.append("i") + else: + new_lst.append(item) + return new_lst + + +def product_dict(**kwargs): + """Generate a iterator of dictionaries where each dictionary is a cartesian product + of the values associated with each key in the input dictionary. + """ + keys = kwargs.keys() + vals = kwargs.values() + for instance in itertools.product(*vals): + yield dict(zip(keys, instance)) + + +def zip_dict(**kwargs): + """Create a iterator of dictionaries where each dictionary contains the zip() of the + values associated with each key in the input dictionary. + """ + return (dict(zip(kwargs.keys(), values)) for values in zip(*kwargs.values())) + +def _update_error_rates(success_cnt, runs, code_K): + """ + Calculates logical error rate, logical error rate error bar, word error rate, + and word error rate error bar. + """ + logical_err_rate = 1.0 - (success_cnt / runs) + logical_err_rate_eb = np.sqrt( + (1 - logical_err_rate) * logical_err_rate / runs + ) + word_error_rate = 1.0 - (1 - logical_err_rate) ** (1 / code_K) + word_error_rate_eb = ( + logical_err_rate_eb + * ((1 - logical_err_rate_eb) ** (1 / code_K - 1)) + / code_K + ) + return ( + logical_err_rate, + logical_err_rate_eb, + word_error_rate, + word_error_rate_eb, + ) + + +def merge_datasets(datasets: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", + "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. + + Args: + datasets (List[Dict[str, Any]]): A list of dictionaries to be merged. + + Returns: + Dict[str, Any]: A dictionary containing the merged data. + """ + if not datasets: + return {} + + # Start with a copy of the first dictionary in the list + merged_data = dict(datasets[0]) + + # Extract and add up the values for "nr_runs", "x_success_cnt", and "z_success_cnt" + for data in datasets[1:]: + merged_data["nr_runs"] += data.get("nr_runs", 0) + merged_data["x_success_cnt"] += data.get("x_success_cnt", 0) + merged_data["z_success_cnt"] += data.get("z_success_cnt", 0) + + # Update logical and word error rates based on accumulated data. + runs = merged_data["nr_runs"] + x_success_cnt = merged_data["x_success_cnt"] + z_success_cnt = merged_data["z_success_cnt"] + + x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates( + x_success_cnt, runs, merged_data["code_K"] + ) + z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates( + z_success_cnt, runs, merged_data["code_K"] + ) + + update_dict = { + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + } + merged_data.update(update_dict) + return merged_data + + +def _merge_datasets_x(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", + "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. + + Args: + datasets (List[Dict[str, Any]]): A list of dictionaries to be merged. + + Returns: + Dict[str, Any]: A dictionary containing the merged data. + """ + + datasets = _datasets.copy() + + if not datasets: + return {} + + # remove datasets that do not contain x_success_cnt + for i, data in enumerate(datasets): + if "x_success_cnt" not in data: + datasets.pop(i) + + # Start with a copy of the first dictionary in the list that contains z_success_cnt + # and remove that dict from the list + for i, data in enumerate(datasets): + if "x_success_cnt" in data: + merged_data = dict(datasets.pop(i)) + break + + # Extract and add up the values for "nr_runs", "x_success_cnt", and "z_success_cnt" + for data in datasets: + try: + merged_data["nr_runs"] += data.get("nr_runs", 0) + merged_data["x_success_cnt"] += data.get("x_success_cnt", 0) + # merged_data["z_success_cnt"] += data.get("z_success_cnt", 0) + except: + pass + + # Update logical and word error rates based on accumulated data. + runs = merged_data["nr_runs"] + x_success_cnt = merged_data["x_success_cnt"] + # z_success_cnt = merged_data["z_success_cnt"] + + x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates( + x_success_cnt, runs, merged_data["code_K"] + ) + + update_dict = { + "x_ler": x_ler, + "x_ler_eb": x_ler_eb, + "x_wer": x_wer, + "x_wer_eb": x_wer_eb, + "x_success_cnt": x_success_cnt, + } + merged_data.update(update_dict) + return merged_data + + +def _merge_datasets_z(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", + "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. + + Args: + datasets (List[Dict[str, Any]]): A list of dictionaries to be merged. + + Returns: + Dict[str, Any]: A dictionary containing the merged data. + """ + + datasets = _datasets.copy() + # print("datasets", datasets) + if not datasets: + return {} + + # remove datasets that do not contain z_success_cnt + for i, data in enumerate(datasets): + if "z_success_cnt" not in data: + datasets.pop(i) + + # print(datasets) + + # Start with a copy of the first dictionary in the list that contains z_success_cnt + # and remove that dict from the list + for i, data in enumerate(datasets): + if "z_success_cnt" in data: + merged_data = dict(datasets.pop(i)) + break + + # merged_data = dict(datasets[0]) + + # Extract and add up the values for "nr_runs", "x_success_cnt", and "z_success_cnt" + for data in datasets: + try: + merged_data["nr_runs"] += data.get("nr_runs", 0) + # merged_data["z_success_cnt"] += data.get("z_success_cnt", 0) + merged_data["z_success_cnt"] += data.get("z_success_cnt", 0) + except: + pass + + # Update logical and word error rates based on accumulated data. + runs = merged_data["nr_runs"] + # x_success_cnt = merged_data["x_success_cnt"] + z_success_cnt = merged_data["z_success_cnt"] + + z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates( + z_success_cnt, runs, merged_data["code_K"] + ) + + update_dict = { + "z_ler": z_ler, + "z_ler_eb": z_ler_eb, + "z_wer": z_wer, + "z_wer_eb": z_wer_eb, + "z_success_cnt": z_success_cnt, + } + merged_data.update(update_dict) + return merged_data + + +from json.decoder import JSONDecodeError + + +def merge_json_files(input_path: str) -> None: + """ + Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + and merges them using the `merge_datasets` function. The resulting dictionaries are + stored in a list and then dumped as a JSON file in the input folder. + + Args: + input_path (str): The path to the input folder. + + Returns: + None + """ + output_data: List[Dict[str, Any]] = [] + for folder_name in os.listdir(input_path): + folder_path = os.path.join(input_path, folder_name) + if os.path.isdir(folder_path): + data: List[Dict[str, Any]] = [] + for filename in os.listdir(folder_path): + if filename.endswith(".json"): + file_path = os.path.join(folder_path, filename) + with open(file_path, "r") as file: + try: + json_data = json.load(file) + data.append(json_data) + except JSONDecodeError: + pass + merged_data = merge_datasets(data) + if merged_data: + output_data.append(merged_data) + + # save output to parent directiory + code_name = os.path.basename(os.path.normpath(input_path)) + parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) + with open( + os.path.join(parent_dir, f"{code_name:s}.json"), "w" + ) as output_file: + json.dump(output_data, output_file, ensure_ascii=False, indent=4) + + +def merge_json_files_x(input_path: str) -> None: + """ + Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + and merges them using the `merge_datasets` function. The resulting dictionaries are + stored in a list and then dumped as a JSON file in the input folder. + + Args: + input_path (str): The path to the input folder. + + Returns: + None + """ + output_data: List[Dict[str, Any]] = [] + for folder_name in os.listdir(input_path): + folder_path = os.path.join(input_path, folder_name) + if os.path.isdir(folder_path): + data: List[Dict[str, Any]] = [] + for filename in os.listdir(folder_path): + if filename.endswith(".json"): + file_path = os.path.join(folder_path, filename) + with open(file_path, "r") as file: + try: + json_data = json.load(file) + data.append(json_data) + except JSONDecodeError: + pass + merged_data = _merge_datasets_x(data) + if merged_data: + output_data.append(merged_data) + + # save output to parent directiory + code_name = os.path.basename(os.path.normpath(input_path)) + parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) + with open( + os.path.join(parent_dir, f"{code_name:s}.json"), "w" + ) as output_file: + json.dump(output_data, output_file, ensure_ascii=False, indent=4) + + +def merge_json_files_z(input_path: str) -> None: + """ + Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + and merges them using the `merge_datasets` function. The resulting dictionaries are + stored in a list and then dumped as a JSON file in the input folder. + + Args: + input_path (str): The path to the input folder. + + Returns: + None + """ + output_data: List[Dict[str, Any]] = [] + for folder_name in os.listdir(input_path): + folder_path = os.path.join(input_path, folder_name) + if os.path.isdir(folder_path): + data: List[Dict[str, Any]] = [] + for filename in os.listdir(folder_path): + if filename.endswith(".json"): + file_path = os.path.join(folder_path, filename) + with open(file_path, "r") as file: + try: + json_data = json.load(file) + data.append(json_data) + except JSONDecodeError: + pass + merged_data = _merge_datasets_z(data) + if merged_data: + output_data.append(merged_data) + + # save output to parent directiory + code_name = os.path.basename(os.path.normpath(input_path)) + parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) + with open( + os.path.join(parent_dir, f"{code_name:s}.json"), "w" + ) as output_file: + json.dump(output_data, output_file, ensure_ascii=False, indent=4) + + +def merge_json_files_xz(input_path: str) -> None: + """ + Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + and merges them using the `merge_datasets` function. The resulting dictionaries are + stored in a list and then dumped as a JSON file in the input folder. + + Args: + input_path (str): The path to the input folder. + + Returns: + None + """ + output_data: List[Dict[str, Any]] = [] + for folder_name in os.listdir(input_path): + folder_path = os.path.join(input_path, folder_name) + if os.path.isdir(folder_path): + data: List[Dict[str, Any]] = [] + for filename in os.listdir(folder_path): + if filename.endswith(".json"): + file_path = os.path.join(folder_path, filename) + with open(file_path, "r") as file: + try: + json_data = json.load(file) + data.append(json_data) + except JSONDecodeError: + pass + # print(folder_path, filename) + # print(data) + merged_data_x = _merge_datasets_x(data) + merged_data_z = _merge_datasets_z(data) + merged_data = _combine_xz_data(merged_data_x, merged_data_z) + if merged_data: + output_data.append(merged_data) + + # save output to parent directiory + code_name = os.path.basename(os.path.normpath(input_path)) + parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) + with open( + os.path.join(parent_dir, f"{code_name:s}.json"), "w" + ) as output_file: + json.dump(output_data, output_file, ensure_ascii=False, indent=4) + + +def _combine_xz_data(xdata: Union[dict, None], zdata: Union[dict, None]) -> dict: + """ + Combine the x and z data into a single dictionary. + Before doing that, rename "runs" in each dictionary to "x_runs" and "z_runs" respectively. + + """ + + if xdata and zdata: + # print(xdata) + xdata["x_runs"] = xdata.pop("nr_runs") + zdata["z_runs"] = zdata.pop("nr_runs") + xdata.update(zdata) + return xdata + elif xdata: + xdata["x_runs"] = xdata.pop("nr_runs") + return xdata + elif zdata: + zdata["z_runs"] = zdata.pop("nr_runs") + return zdata + else: + return {} diff --git a/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py b/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py new file mode 100644 index 00000000..db1ee287 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py @@ -0,0 +1,290 @@ +import json +import numpy as np +from ldpc.mod2 import rank +from numba import njit +from numba.core.errors import ( + NumbaDeprecationWarning, + NumbaPendingDeprecationWarning, +) +import warnings +from scipy.sparse import coo_matrix, csr_matrix +from scipy.special import erfcinv, erfc + +from utils.data_utils import replace_inf, calculate_error_rates, BpParams + +warnings.simplefilter("ignore", category=NumbaDeprecationWarning) +warnings.simplefilter("ignore", category=NumbaPendingDeprecationWarning) + + +@njit +def set_seed(value): + """ + The approriate way to set seeds when numba is used. + """ + np.random.seed(value) + + +# @njit +def alist2numpy(fname): # current original implementation is buggy + alist_file = np.loadtxt(fname, delimiter=",", dtype=str) + matrix_dimensions = alist_file[0].split() + m = int(matrix_dimensions[0]) + n = int(matrix_dimensions[1]) + + mat = np.zeros((m, n), dtype=np.int32) + + for i in range(m): + columns = [] + for item in alist_file[i + 4].split(): + if item.isdigit(): + columns.append(item) + columns = np.array(columns, dtype=np.int32) + columns = columns - 1 # convert to zero indexing + mat[i, columns] = 1 + + return mat + + +# Rewrite such that call signatures of check_logical_err_h +# and check_logical_err_l are identical +@njit +def check_logical_err_h(check_matrix, original_err, decoded_estimate): + r, n = check_matrix.shape + + # compute residual err given original err + residual_err = np.zeros((n, 1), dtype=np.int32) + for i in range(n): + residual_err[i][0] = original_err[i] ^ decoded_estimate[i] + + ht = np.transpose(check_matrix) + + htr = np.append(ht, residual_err, axis=1) + + rank_ht = rank(check_matrix) # rank A = rank A.T + + rank_htr = rank(htr) + + if rank_ht < rank_htr: + return True + else: + return False + + +# L is a numpy array, residual_err is vector s.t. dimensions match +# residual_err is a logical iff it commutes with logicals of other side +# i.e., an X residal is a logical iff it commutes with at least one Z logical and +# an Z residual is a logical iff it commutes with at least one Z logical +# Hence, L must be of same type as H and of different type than residual_err +def is_logical_err(L, residual_err): + """checks if the residual error is a logical error + :returns: True if its logical error, False otherwise (is a stabilizer)""" + l_check = (L @ residual_err) % 2 + return l_check.any() # check all zeros + + +# adapted from https://github.com/quantumgizmos/bp_osd/blob/a179e6e86237f4b9cc2c952103fce919da2777c8/src/bposd/css_decode_sim.py#L430 +# and https://github.com/MikeVasmer/single_shot_3D_HGP/blob/master/sim_scripts/single_shot_hgp3d.cpp#L207 +# channel_probs = [x,y,z], residual_err = [x,z] +@njit +def generate_err(N, channel_probs, residual_err): + """ + Computes error vector with X and Z part given channel probabilities and residual error. + Assumes that residual error has two equally sized parts. + """ + error_x = residual_err[0] + error_z = residual_err[1] + channel_probs_x = channel_probs[0] + channel_probs_y = channel_probs[1] + channel_probs_z = channel_probs[2] + residual_err_x = residual_err[0] + residual_err_z = residual_err[1] + + for i in range(N): + rand = np.random.random() # this returns a random float in [0,1) + # e.g. if err channel is p = 0.3, then an error will be applied if rand < p + if ( + rand < channel_probs_z[i] + ): # if probability for z error high enough, rand < p, apply + # if there is a z error on the i-th bit, flip the bit but take residual error into account + # nothing on x part - probably redundant anyways + error_z[i] = (residual_err_z[i] + 1) % 2 + elif ( # if p = 0.3 then 0.3 <= rand < 0.6 is the same sized interval as rand < 0.3 + channel_probs_z[i] + <= rand + < (channel_probs_z[i] + channel_probs_x[i]) + ): + # X error + error_x[i] = (residual_err_x[i] + 1) % 2 + elif ( # 0.6 <= rand < 0.9 + (channel_probs_z[i] + channel_probs_x[i]) + <= rand + < (channel_probs_x[i] + channel_probs_y[i] + channel_probs_z[i]) + ): + # y error == both x and z error + error_z[i] = (residual_err_z[i] + 1) % 2 + error_x[i] = (residual_err_x[i] + 1) % 2 + + return error_x, error_z + + +@njit +def get_analog_llr(analog_syndrome: np.ndarray, sigma: float) -> np.ndarray: + """Computes analog LLRs given analog syndrome and sigma""" + return (2 * analog_syndrome) / (sigma**2) + + +def get_sigma_from_syndr_er(ser: float) -> float: + """ + For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. + :return: sigma + """ + return ( + 1 / np.sqrt(2) / (erfcinv(2 * ser)) + ) # see Eq. cref{eq:perr-to-sigma} in our paper + + +def get_error_rate_from_sigma(sigma: float) -> float: + """ + For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. + :return: sigma + """ + return 0.5 * erfc( + 1 / np.sqrt(2 * sigma**2) + ) # see Eq. cref{eq:perr-to-sigma} in our paper + + +@njit +def get_virtual_check_init_vals(noisy_syndr, sigma: float): + """ + Computes a vector of values v_i from the noisy syndrome bits y_i s.t. BP initializes the LLRs l_i of the analog nodes with the + analog info values (see paper section). v_i := 1/(e^{y_i}+1) + """ + llrs = get_analog_llr(noisy_syndr, sigma) + return 1 / (np.exp(np.abs(llrs)) + 1) + + +@njit +def generate_syndr_err(channel_probs): + error = np.zeros_like(channel_probs, dtype=np.int32) + + for i, prob in np.ndenumerate(channel_probs): + rand = np.random.random() + + if rand < channel_probs[i]: + error[i] = 1 + + return error + + +# @njit +def get_noisy_analog_syndrome( + perfect_syndr: np.ndarray, sigma: float +) -> np.array: + """Generate noisy analog syndrome vector given the perfect syndrome and standard deviation sigma (~ noise strength) + Assumes perfect_syndr has entries in {0,1}""" + + # compute signed syndrome: 1 = check satisfied, -1 = check violated + sgns = np.where( + perfect_syndr == 0, + np.ones_like(perfect_syndr), + np.full_like(perfect_syndr, -1), + ) + + # sample from Gaussian with zero mean and sigma std. dev: ~N(0, sigma_sq) + res = np.random.normal(sgns, sigma, len(sgns)) + return res + + +@njit +def error_channel_setup(error_rate, xyz_error_bias, N): + """Set up an error_channel given the physical error rate, bias, and number of bits.""" + xyz_error_bias = np.array(xyz_error_bias) + if xyz_error_bias[0] == np.inf: + px = error_rate + py = 0 + pz = 0 + elif xyz_error_bias[1] == np.inf: + px = 0 + py = error_rate + pz = 0 + elif xyz_error_bias[2] == np.inf: + px = 0 + py = 0 + pz = error_rate + else: + px, py, pz = ( + error_rate * xyz_error_bias / np.sum(xyz_error_bias) + ) # Oscar only considers X or Z errors. For reproducability remove normalization + + channel_probs_x = np.ones(N) * px + channel_probs_z = np.ones(N) * pz + channel_probs_y = np.ones(N) * py + + return channel_probs_x, channel_probs_y, channel_probs_z + + +# @njit +def build_single_stage_pcm(H, M) -> np.ndarray: + id_r = np.identity(M.shape[1]) + zeros = np.zeros((M.shape[0], H.shape[1])) + return np.block([[H, id_r], [zeros, M]]).astype(np.int32) + + +@njit +def get_signed_from_binary(binary_syndrome: np.ndarray) -> np.ndarray: + """Maps the binary vector with {0,1} entries to a vector with {-1,1} entries""" + signed_syndr = [] + for j in range(len(binary_syndrome)): + signed_syndr.append(1 if binary_syndrome[j] == 0 else -1) + return np.array(signed_syndr) + + +def get_binary_from_analog(analog_syndrome: np.ndarray) -> np.ndarray: + """Returns the thresholded binary vector. + Since in {-1,+1} notation -1 indicates a check violation, we map values <= 0 to 1 and values > 0 to 0. + """ + return np.where(analog_syndrome <= 0.0, 1, 0).astype(np.int32) + + +def save_results( + success_cnt: int, + nr_runs: int, + p: float, + s: float, + input_vals: dict, + outfile: str, + code_params, + err_side: str = "X", + bp_iterations: int = None, + bp_params: BpParams = None, +) -> dict: + ler, ler_eb, wer, wer_eb = calculate_error_rates( + success_cnt, nr_runs, code_params + ) + + output = { + "code_K": code_params["k"], + "code_N": code_params["n"], + "nr_runs": nr_runs, + "pers": p, + "sers": s, + f"{err_side}_ler": ler, + f"{err_side}_ler_eb": ler_eb, + f"{err_side}_wer": wer, + f"{err_side}_wer_eb": wer_eb, + f"{err_side}_success_cnt": success_cnt, + "avg_bp_iterations": bp_iterations / nr_runs, + "bp_params": bp_params, + } + + output.update(input_vals) + output["bias"] = replace_inf(output["bias"]) + with open(outfile, "w") as f: + json.dump( + output, + f, + ensure_ascii=False, + indent=4, + default=lambda o: o.__dict__, + ) + return output \ No newline at end of file From 068f2a47276595e1398bd9a4bb341a5ecf8cef68 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 20:57:32 +0000 Subject: [PATCH 002/115] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analog-information-decoding/README.md | 7 +- .../analog-information-decoding/__init__.py | 6 +- .../code_construction/code_constructor.py | 51 ++- .../sparse_code_constructor.py | 80 ++--- .../qecc/analog-information-decoding/setup.py | 6 +- .../simulators/analog_tannergraph_decoding.py | 238 ++++++------- .../simulators/memory_experiment_v2.py | 90 ++--- .../simulators/quasi_single_shot_v2.py | 141 ++++---- .../simulators/simulation.py | 319 +++++++----------- .../test/test_memory_experiment.py | 44 +-- .../test/test_simulation_utils.py | 178 +++++++--- .../utils/__init__.py | 2 + .../utils/data_utils.py | 184 ++++------ .../utils/simulation_utils.py | 84 ++--- 14 files changed, 615 insertions(+), 815 deletions(-) diff --git a/src/mqt/qecc/analog-information-decoding/README.md b/src/mqt/qecc/analog-information-decoding/README.md index cd4a94c2..3afc4453 100644 --- a/src/mqt/qecc/analog-information-decoding/README.md +++ b/src/mqt/qecc/analog-information-decoding/README.md @@ -6,9 +6,9 @@ Proper integration and setup is a work in progress. The main functionality is provided in the `simulators` module, which contains the main classes that can be used to conduct several types of simulations: -* `ATD_Simulator`: Analog Tanner graph decoding, -* `Single-Shot Simulator`: Analog Single-Shot decoding with meta checks. -* `QSS_Simulator`: Quasi-Single-Shot decoding, and +- `ATD_Simulator`: Analog Tanner graph decoding, +- `Single-Shot Simulator`: Analog Single-Shot decoding with meta checks. +- `QSS_Simulator`: Quasi-Single-Shot decoding, and Moreover, `memory_experiment` contains methods for analog overlapping window decoding, in particular the `decode_multiround` method. @@ -22,6 +22,7 @@ The `results` directory contains the results used in the paper [1]. The `codes` directory contains the parity-check matrices of the codes used in the paper [1]. Three dimensional toric codes can either be constructed with the hypergraph product construction or with a library, e.g., panqec [3]. + ### Code construction The `code_construction` directory contains the code used to construct higher-dimensional hypergraph diff --git a/src/mqt/qecc/analog-information-decoding/__init__.py b/src/mqt/qecc/analog-information-decoding/__init__.py index 7f739451..e2ec91c3 100644 --- a/src/mqt/qecc/analog-information-decoding/__init__.py +++ b/src/mqt/qecc/analog-information-decoding/__init__.py @@ -1,4 +1,6 @@ -from simulators.analog_tannergraph_decoding import AnalogTannergraphDecoder, SoftInfoDecoder, ATD_Simulator +from __future__ import annotations + +from simulators.analog_tannergraph_decoding import AnalogTannergraphDecoder, ATD_Simulator, SoftInfoDecoder from simulators.quasi_single_shot_v2 import QSS_SimulatorV2 from simulators.simulation import Single_Shot_Simulator @@ -8,4 +10,4 @@ "ATD_Simulator", "QSS_SimulatorV2", "Single_Shot_Simulator", -] \ No newline at end of file +] diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py b/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py index 7ae2c98a..1e413946 100644 --- a/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py +++ b/src/mqt/qecc/analog-information-decoding/code_construction/code_constructor.py @@ -1,18 +1,20 @@ +from __future__ import annotations + +import json +import os +import subprocess + import ldpc.code_util import numpy as np -from bposd.hgp import hgp import scipy.io as sio -from scipy import sparse -from ldpc import mod2 -from ldpc.codes import ring_code -import os -import json -import subprocess import scipy.sparse as scs +from bposd.hgp import hgp +from ldpc import mod2 +from scipy import sparse class HD_HGP: - def __init__(self, boundaries): + def __init__(self, boundaries) -> None: self.boundaries = boundaries @@ -31,9 +33,8 @@ def run_checks_scipy(d_1, d_2, d_3, d_4): and is_all_zeros((sd_2 * sd_3).todense() % 2) and is_all_zeros((sd_3 * sd_4).todense() % 2) ): - raise Exception( - "Error generating 4D code, boundary maps do not square to zero" - ) + msg = "Error generating 4D code, boundary maps do not square to zero" + raise Exception(msg) def generate_4D_product_code(A_1, A_2, A_3, P, checks=True): @@ -86,9 +87,8 @@ def generate_3D_product_code(A_1, A_2, P): d_3 = np.vstack((np.kron(id_n2, P), np.kron(A_2, id_c))) if not (is_all_zeros(d_1 @ d_2 % 2) and is_all_zeros(d_2 @ d_3 % 2)): - raise Exception( - "Error generating 3D code, boundary maps do not square to zero" - ) + msg = "Error generating 3D code, boundary maps do not square to zero" + raise Exception(msg) return d_1, d_2, d_3 @@ -129,9 +129,7 @@ def _compute_distances(hx, hz, codename): codeK = n - mod2.rank(hx) - mod2.rank(hz) with open(f"generated_codes/{codename}/info.txt") as f: code_dict = dict( - line[: line.rfind("#")].split(" = ") - for line in f - if not line.startswith("#") and line.strip() + line[: line.rfind("#")].split(" = ") for line in f if not line.startswith("#") and line.strip() ) code_dict["n"] = n @@ -159,11 +157,8 @@ def compute_lz(hx, hz): # in the below we row reduce to find vectors in kx that are not in the image of hz.T. log_stack = np.vstack([im_hzT, ker_hx]) pivots = mod2.row_echelon(log_stack.T)[3] - log_op_indices = [ - i for i in range(im_hzT.shape[0], log_stack.shape[0]) if i in pivots - ] - log_ops = log_stack[log_op_indices] - return log_ops + log_op_indices = [i for i in range(im_hzT.shape[0], log_stack.shape[0]) if i in pivots] + return log_stack[log_op_indices] lx = compute_lz(hz, hx) lz = compute_lz(hx, hz) @@ -183,9 +178,8 @@ def create_code( if constructor == "hgp": code = hgp(seed_codes[0], seed_codes[1]) else: - raise ValueError( - f"No constructor specified or the specified constructor {constructor} not implemented." - ) + msg = f"No constructor specified or the specified constructor {constructor} not implemented." + raise ValueError(msg) # Extend to 3D HGP A1 = code.hx @@ -193,16 +187,15 @@ def create_code( res = generate_3D_product_code(A1, A2, seed_codes[2]) # Build 4D HGP code - mx, hx, hzT, mzT = generate_4D_product_code( - *res, seed_codes[3], checks=checks - ) + mx, hx, hzT, mzT = generate_4D_product_code(*res, seed_codes[3], checks=checks) hz = hzT.T mz = mzT.T # Perform checks if np.any(hzT @ mzT % 2) or np.any(hx @ hzT % 2) or np.any(mx @ hx % 2): - raise Exception("err") + msg = "err" + raise Exception(msg) save_code(hx, hz, mx, mz, codename) if compute_logicals: diff --git a/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py b/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py index 4f7c8203..40fde022 100644 --- a/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py +++ b/src/mqt/qecc/analog-information-decoding/code_construction/sparse_code_constructor.py @@ -1,17 +1,20 @@ +from __future__ import annotations + +import json +import os +import subprocess + +import code_constructor import numpy as np -from bposd.hgp import hgp import scipy.io as sio +from bposd.hgp import hgp from ldpc import mod2 from scipy import sparse -import code_constructor -import os -import json -import subprocess from scipy.sparse import coo_matrix, csr_matrix class HD_HGP: - def __init__(self, boundaries): + def __init__(self, boundaries) -> None: self.boundaries = boundaries @@ -24,25 +27,18 @@ def sparse_all_zeros(mat: csr_matrix): return mat.sum() == 0 -def run_checks_scipy( - d_1: csr_matrix, d_2: csr_matrix, d_3: csr_matrix, d_4: csr_matrix -): - if not ( - sparse_all_zeros(d_1 @ d_2) - and sparse_all_zeros(d_2 @ d_3) - and sparse_all_zeros(d_3 @ d_4) - ): - raise Exception( - "Error generating 4D code, boundary maps do not square to zero" - ) +def run_checks_scipy(d_1: csr_matrix, d_2: csr_matrix, d_3: csr_matrix, d_4: csr_matrix): + if not (sparse_all_zeros(d_1 @ d_2) and sparse_all_zeros(d_2 @ d_3) and sparse_all_zeros(d_3 @ d_4)): + msg = "Error generating 4D code, boundary maps do not square to zero" + raise Exception(msg) def generate_4D_product_code( - A_1: csr_matrix, - A_2: csr_matrix, - A_3: csr_matrix, - P: csr_matrix, - checks=True, + A_1: csr_matrix, + A_2: csr_matrix, + A_3: csr_matrix, + P: csr_matrix, + checks=True, ): r, c = P.shape @@ -97,9 +93,8 @@ def generate_3D_product_code(A_1: csr_matrix, A_2: csr_matrix, P: csr_matrix): d_3 = sparse.vstack((sparse.kron(id_n2, P), sparse.kron(A_2, id_c))) if not (sparse_all_zeros(d_1 @ d_2) and sparse_all_zeros(d_2 @ d_3)): - raise Exception( - "Error generating 3D code, boundary maps do not square to zero" - ) + msg = "Error generating 3D code, boundary maps do not square to zero" + raise Exception(msg) return d_1, d_2, d_3 # mx, hx, hzT # hx, hzT, mzT @@ -143,12 +138,9 @@ def _compute_distances(hx, hz, codename): code_dict = {} _, n = hx.shape codeK = n - mod2.rank(hx) - mod2.rank(hz) - with open( - f"/codes/generated_codes/{codename}/info.txt") as f: + with open(f"/codes/generated_codes/{codename}/info.txt") as f: code_dict = dict( - line[: line.rfind("#")].split(" = ") - for line in f - if not line.startswith("#") and line.strip() + line[: line.rfind("#")].split(" = ") for line in f if not line.startswith("#") and line.strip() ) code_dict["n"] = n @@ -157,9 +149,7 @@ def _compute_distances(hx, hz, codename): code_dict["dZ"] = int(code_dict["dZ"]) print("Code properties:", code_dict) - with open( - f"/codes/generated_codes/{codename}/code_params.txt", - "w") as file: + with open(f"/codes/generated_codes/{codename}/code_params.txt", "w") as file: file.write(json.dumps(code_dict)) return @@ -172,29 +162,26 @@ def _store_code_params(hx, hz, codename): codeK = n - mod2.rank(hx) - mod2.rank(hz) code_dict["n"] = n code_dict["k"] = codeK - with open( - f"/codes/generated_codes/{codename}/code_params.txt", - "w") as file: + with open(f"/codes/generated_codes/{codename}/code_params.txt", "w") as file: file.write(json.dumps(code_dict)) return def create_code( - constructor: str, - seed_codes: list, - codename: str, - compute_distance: bool = False, - compute_logicals: bool = False, - lift_parameter=None, - checks: bool = False, + constructor: str, + seed_codes: list, + codename: str, + compute_distance: bool = False, + compute_logicals: bool = False, + lift_parameter=None, + checks: bool = False, ): # Construct initial 2 dim code if constructor == "hgp": code = hgp(seed_codes[0], seed_codes[1]) else: - raise ValueError( - f"No constructor specified or the specified constructor {constructor} not implemented." - ) + msg = f"No constructor specified or the specified constructor {constructor} not implemented." + raise ValueError(msg) # Extend to 3D HGP A1 = sparse.csr_matrix(code.hx) @@ -223,4 +210,3 @@ def create_code( else: _store_code_params(hx.todense(), hz.todense(), codename) return - diff --git a/src/mqt/qecc/analog-information-decoding/setup.py b/src/mqt/qecc/analog-information-decoding/setup.py index 30596398..04dd1ac8 100644 --- a/src/mqt/qecc/analog-information-decoding/setup.py +++ b/src/mqt/qecc/analog-information-decoding/setup.py @@ -1,4 +1,6 @@ -from setuptools import setup, find_packages +from __future__ import annotations + +from setuptools import find_packages, setup setup( name="AnalogInformationDecoding", @@ -18,4 +20,4 @@ license="MIT", keywords="bosonic decoders", url="", - ) \ No newline at end of file +) diff --git a/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py b/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py index d543fa5d..13e15325 100644 --- a/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py +++ b/src/mqt/qecc/analog-information-decoding/simulators/analog_tannergraph_decoding.py @@ -1,31 +1,33 @@ +from __future__ import annotations + import json +import os + import numpy as np import utils.simulation_utils as simulation_utils -from ldpc import bposd_decoder -from ldpc import bp_decoder +from ldpc import bp_decoder, bposd_decoder +from ldpc2.bp_decoder import SoftInfoBpDecoder +from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder from utils.data_utils import ( - calculate_error_rates, BpParams, + calculate_error_rates, create_outpath, is_converged, ) -import os -from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder -from ldpc2.bp_decoder import SoftInfoBpDecoder def create_outpath( - experiment: str = "atd", - data_err_rate: float = None, - sigma: float = None, - bp_params: BpParams = None, - codename: str = None, - bias: list = None, - overwrite: bool = False, - id: int = 0, - **kwargs): - """ Create output path from input parameters. """ - + experiment: str = "atd", + data_err_rate: float | None = None, + sigma: float | None = None, + bp_params: BpParams = None, + codename: str | None = None, + bias: list | None = None, + overwrite: bool = False, + id: int = 0, + **kwargs, +): + """Create output path from input parameters.""" path = f"results/{experiment:s}/" path += f"bias={bias[0]}_{bias[1]}_{bias[2]}/" @@ -51,7 +53,7 @@ def create_outpath( if not os.path.exists(path): os.makedirs(path, exist_ok=True) - if overwrite == False: + if overwrite is False: f_loc = path + f"id_{id}.json" while os.path.exists(f_loc): id += 1 @@ -66,8 +68,7 @@ def create_outpath( class SoftInfoDecoder: - """ - Soft Information Decoder + """Soft Information Decoder. Plug and play solution for soft information decoding that can be interchanged with `AnalogTannergraphDecoder` in `ATD_Simulator`. @@ -75,13 +76,13 @@ class SoftInfoDecoder: """ def __init__( - self, - H: np.ndarray, - bp_params: BpParams, - error_channel: np.ndarray, - sigma: float = None, - ser: float = None, - ): + self, + H: np.ndarray, + bp_params: BpParams, + error_channel: np.ndarray, + sigma: float | None = None, + ser: float | None = None, + ) -> None: self.m, self.n = H.shape self.sigma = sigma self.H = H @@ -91,17 +92,18 @@ def __init__( if self.sigma is None: if self.syndr_err_rate is None: - raise ValueError("Either sigma or ser must be specified") + msg = "Either sigma or ser must be specified" + raise ValueError(msg) else: - self.sigma = simulation_utils.get_sigma_from_syndr_er( - self.syndr_err_rate - ) + self.sigma = simulation_utils.get_sigma_from_syndr_er(self.syndr_err_rate) else: if self.syndr_err_rate is not None: - raise ValueError("Only one of sigma or ser must be specified") + msg = "Only one of sigma or ser must be specified" + raise ValueError(msg) if self.error_channel is None: - raise ValueError("error_channel must be specified") + msg = "error_channel must be specified" + raise ValueError(msg) self.bp_decoder = SoftInfoBpOsdDecoder( pcm=self.H, @@ -140,13 +142,13 @@ class AnalogTannergraphDecoder: """ def __init__( - self, - H: np.ndarray, - bp_params: BpParams, - error_channel: np.ndarray, - sigma: float = None, - ser: float = None, - ): + self, + H: np.ndarray, + bp_params: BpParams, + error_channel: np.ndarray, + sigma: float | None = None, + ser: float | None = None, + ) -> None: self.m, self.n = H.shape self.sigma = sigma self.H = H @@ -157,23 +159,22 @@ def __init__( if self.sigma is None: if self.syndr_err_rate is None: - raise ValueError("Either sigma or ser must be specified") + msg = "Either sigma or ser must be specified" + raise ValueError(msg) else: - self.sigma = simulation_utils.get_sigma_from_syndr_er( - self.syndr_err_rate - ) + self.sigma = simulation_utils.get_sigma_from_syndr_er(self.syndr_err_rate) else: if self.syndr_err_rate is not None: - raise ValueError("Only one of sigma or ser must be specified") + msg = "Only one of sigma or ser must be specified" + raise ValueError(msg) if self.error_channel is None: - raise ValueError("error_channel must be specified") + msg = "error_channel must be specified" + raise ValueError(msg) self.bp_decoder = bposd_decoder( parity_check_matrix=self.atg, - channel_probs=np.hstack( - (self.error_channel, np.zeros(self.m)) - ), # initd as dummy for now + channel_probs=np.hstack((self.error_channel, np.zeros(self.m))), # initd as dummy for now max_iter=self.bp_params.max_bp_iter, bp_method=self.bp_params.bp_method, osd_order=self.bp_params.osd_order, @@ -187,26 +188,21 @@ def __init__( self.bp_decoder = bp_decoder( parity_check_matrix=self.atg, - channel_probs=np.hstack( - (self.error_channel, np.zeros(self.m)) - ), # initd as dummy for now + channel_probs=np.hstack((self.error_channel, np.zeros(self.m))), # initd as dummy for now max_iter=self.bp_params.max_bp_iter, bp_method=self.bp_params.bp_method, ms_scaling_factor=self.bp_params.ms_scaling_factor, ) def _set_analog_syndrome(self, analog_syndrome: np.ndarray) -> None: - """ - Initializes the error channel of the BP decoder s.t. the virtual nodes are initialized with the + """Initializes the error channel of the BP decoder s.t. the virtual nodes are initialized with the analog syndrome LLRs on decoding initialization. - :param analog_syndrome: the analog syndrome values to initialize the virtual nodes with + :param analog_syndrome: the analog syndrome values to initialize the virtual nodes with. """ new_channel = np.hstack( ( self.bp_decoder.channel_probs[: self.n], - simulation_utils.get_virtual_check_init_vals( - analog_syndrome, self.sigma - ), + simulation_utils.get_virtual_check_init_vals(analog_syndrome, self.sigma), ) ) self.bp_decoder.update_channel_probs(new_channel) @@ -214,28 +210,26 @@ def _set_analog_syndrome(self, analog_syndrome: np.ndarray) -> None: def decode(self, analog_syndrome: np.ndarray) -> np.ndarray: """Decode a given analog syndrome.""" self._set_analog_syndrome(analog_syndrome) - return self.bp_decoder.decode( - simulation_utils.get_binary_from_analog(analog_syndrome) - ) + return self.bp_decoder.decode(simulation_utils.get_binary_from_analog(analog_syndrome)) class ATD_Simulator: def __init__( - self, - Hx: np.ndarray, - Lx: np.ndarray, - Hz: np.ndarray, - Lz: np.ndarray, - codename: str, - seed: int, - bp_params: BpParams, - data_err_rate: float, - syndr_err_rate: float = None, - sigma: float = None, - bias=None, - experiment: str = "atd", - decoding_method: str = "atd", - **kwargs, + self, + Hx: np.ndarray, + Lx: np.ndarray, + Hz: np.ndarray, + Lz: np.ndarray, + codename: str, + seed: int, + bp_params: BpParams, + data_err_rate: float, + syndr_err_rate: float | None = None, + sigma: float | None = None, + bias=None, + experiment: str = "atd", + decoding_method: str = "atd", + **kwargs, ) -> None: if bias is None: bias = [1.0, 1.0, 1.0] @@ -256,7 +250,8 @@ def __init__( if sigma is None: if syndr_err_rate is None: - raise ValueError("Either sigma or ser must be specified") + msg = "Either sigma or ser must be specified" + raise ValueError(msg) else: self.syndr_err_rate = syndr_err_rate synd_err_channel = simulation_utils.error_channel_setup( @@ -267,23 +262,18 @@ def __init__( else: if syndr_err_rate is not None: - raise ValueError("Only one of sigma or ser must be specified") + msg = "Only one of sigma or ser must be specified" + raise ValueError(msg) - self.syndr_err_rate = simulation_utils.get_error_rate_from_sigma( - sigma - ) + self.syndr_err_rate = simulation_utils.get_error_rate_from_sigma(sigma) synd_err_channel = simulation_utils.error_channel_setup( error_rate=self.syndr_err_rate, xyz_error_bias=self.bias, N=1, ) - x_synd_err_rate = ( - synd_err_channel[0][0] + synd_err_channel[1][0] - ) # x + y errors, 1st bit only - z_synd_err_rate = ( - synd_err_channel[2][0] + synd_err_channel[1][0] - ) # z + y errors, 1st bit only + x_synd_err_rate = synd_err_channel[0][0] + synd_err_channel[1][0] # x + y errors, 1st bit only + z_synd_err_rate = synd_err_channel[2][0] + synd_err_channel[1][0] # z + y errors, 1st bit only self.x_sigma = simulation_utils.get_sigma_from_syndr_er(x_synd_err_rate) self.z_sigma = simulation_utils.get_sigma_from_syndr_er(z_synd_err_rate) @@ -293,9 +283,7 @@ def __init__( self.input_values = self.__dict__.copy() self.n = Hx.shape[1] - self.code_params = eval( - open(f"generated_codes/code/code_params.txt").read() - ) + self.code_params = eval(open("generated_codes/code/code_params.txt").read()) del self.input_values["Hx"] del self.input_values["Lx"] del self.input_values["Hz"] @@ -316,15 +304,13 @@ def __init__( N=self.n, ) self.x_decoder = Decoder( - error_channel=self.full_error_channel[0] - + self.full_error_channel[1], # x + y errors + error_channel=self.full_error_channel[0] + self.full_error_channel[1], # x + y errors H=self.Hz, sigma=self.x_sigma, bp_params=self.bp_params, ) self.z_decoder = Decoder( - error_channel=self.full_error_channel[2] - + self.full_error_channel[1], # z + y errors + error_channel=self.full_error_channel[2] + self.full_error_channel[1], # z + y errors H=self.Hx, sigma=self.z_sigma, bp_params=self.bp_params, @@ -348,24 +334,20 @@ def single_sample(self): ) x_perf_syndr = (self.Hz @ x_err) % 2 - x_noisy_syndr = simulation_utils.get_noisy_analog_syndrome( - sigma=self.x_sigma, perfect_syndr=x_perf_syndr - ) + x_noisy_syndr = simulation_utils.get_noisy_analog_syndrome(sigma=self.x_sigma, perfect_syndr=x_perf_syndr) x_decoding = self.x_decoder.decode(x_noisy_syndr)[: self.n] self.x_bp_iterations += self.x_decoder.bp_decoder.iter x_residual = (x_err + x_decoding) % 2 z_perf_syndr = (self.Hx @ z_err) % 2 - z_noisy_syndr = simulation_utils.get_noisy_analog_syndrome( - sigma=self.z_sigma, perfect_syndr=z_perf_syndr - ) + z_noisy_syndr = simulation_utils.get_noisy_analog_syndrome(sigma=self.z_sigma, perfect_syndr=z_perf_syndr) z_decoding = self.z_decoder.decode(z_noisy_syndr)[: self.n] self.z_bp_iterations += self.z_decoder.bp_decoder.iter z_residual = (z_err + z_decoding) % 2 - return not simulation_utils.is_logical_err( - self.Lz, x_residual - ), not simulation_utils.is_logical_err(self.Lx, z_residual) + return not simulation_utils.is_logical_err(self.Lz, x_residual), not simulation_utils.is_logical_err( + self.Lx, z_residual + ) def run(self, samples): x_success_cnt = 0 @@ -380,21 +362,17 @@ def run(self, samples): # check convergence only once during each save interval if is_converged( - x_success_cnt, - z_success_cnt, - runs, - self.code_params, - self.eb_precission, + x_success_cnt, + z_success_cnt, + runs, + self.code_params, + self.eb_precission, ): print("Result has converged.") break - x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( - x_success_cnt, runs, self.code_params - ) - z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( - z_success_cnt, runs, self.code_params - ) + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates(x_success_cnt, runs, self.code_params) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates(z_success_cnt, runs, self.code_params) output = { "code_K": self.code_params["k"], "code_N": self.code_params["n"], @@ -417,20 +395,14 @@ def run(self, samples): "z_bp_iterations": self.z_bp_iterations / runs, } - self.save_results( - x_success_cnt, z_success_cnt, runs - ) # save final results + self.save_results(x_success_cnt, z_success_cnt, runs) # save final results return output def save_results(self, x_success_cnt: int, z_success_cnt: int, runs: int): """Compute error rates and error bars and save output dict.""" - x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( - x_success_cnt, runs, self.code_params - ) - z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( - z_success_cnt, runs, self.code_params - ) + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates(x_success_cnt, runs, self.code_params) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates(z_success_cnt, runs, self.code_params) output = { "code_K": self.code_params["k"], "code_N": self.code_params["n"], @@ -466,9 +438,7 @@ def save_results(self, x_success_cnt: int, z_success_cnt: int, runs: int): def get_analog_pcm(H: np.ndarray): - """ - constructs apcm = [H | I_m] where I_m is the m x m identity matrix - """ + """Constructs apcm = [H | I_m] where I_m is the m x m identity matrix.""" return np.hstack((H, np.identity(H.shape[0], dtype=np.int32))) @@ -507,16 +477,22 @@ def get_analog_pcm(H: np.ndarray): osd_order=10, osd_method="osd_cs", ms_scaling_factor=0.75, - cutoff=5., + cutoff=5.0, ), decoding_method=decoder, ) out = sim.run(samples=1500) - ler = out["z_ler"] * (1 - out["x_ler"]) + out["x_ler"] * (1 - out["z_ler"]) + out["x_ler"] * out[ - "z_ler"] - ler_eb = out["z_ler_eb"] * (1 - out["x_ler_eb"]) + out["x_ler_eb"] * (1 - out["z_ler_eb"]) + out[ - "x_ler_eb"] * out["z_ler_eb"] + ler = ( + out["z_ler"] * (1 - out["x_ler"]) + + out["x_ler"] * (1 - out["z_ler"]) + + out["x_ler"] * out["z_ler"] + ) + ler_eb = ( + out["z_ler_eb"] * (1 - out["x_ler_eb"]) + + out["x_ler_eb"] * (1 - out["z_ler_eb"]) + + out["x_ler_eb"] * out["z_ler_eb"] + ) lers.append(ler) ebs.append(ler_eb) diff --git a/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py b/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py index d47b50e6..c5807671 100644 --- a/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py +++ b/src/mqt/qecc/analog-information-decoding/simulators/memory_experiment_v2.py @@ -1,13 +1,11 @@ +from __future__ import annotations + import numpy as np from pymatching import Matching +from scipy.sparse import block_diag, csr_matrix, eye, hstack from utils.simulation_utils import ( - is_logical_err, get_virtual_check_init_vals, - get_binary_from_analog, - get_noisy_analog_syndrome, - get_sigma_from_syndr_er, ) -from scipy.sparse import csr_matrix, hstack, eye, block_diag def build_multiround_pcm(pcm, repetitions, format="csr"): @@ -26,20 +24,16 @@ def build_multiround_pcm(pcm, repetitions, format="csr"): H_3DPCM = block_diag([pcm] * (repetitions + 1), format=format) # Construct the block of identity matrices - H_3DID_diag = block_diag( - [eye(pcm_rows, format=format)] * (repetitions + 1), format=format) + H_3DID_diag = block_diag([eye(pcm_rows, format=format)] * (repetitions + 1), format=format) # Construct the block of identity matrices - H_3DID_offdiag = eye(pcm_rows * (repetitions + 1), - k=-pcm_rows, format=format) + H_3DID_offdiag = eye(pcm_rows * (repetitions + 1), k=-pcm_rows, format=format) # Construct the block of identity matrices H_3DID = H_3DID_diag + H_3DID_offdiag # # hstack the two blocks - H_3D = hstack([H_3DPCM, H_3DID], format=format) - - return H_3D + return hstack([H_3DPCM, H_3DID], format=format) def move_syndrome(syndrome, data_type=np.int32): @@ -54,11 +48,8 @@ def move_syndrome(syndrome, data_type=np.int32): return new_syndrome -def get_updated_decoder(decoding_method: str, - decoder, - new_channel, - H3D=None): - """ Updates the decoder with the new channel information and returns the updated decoder object.""" +def get_updated_decoder(decoding_method: str, decoder, new_channel, H3D=None): + """Updates the decoder with the new channel information and returns the updated decoder object.""" if decoding_method == "bposd": decoder.update_channel_probs(new_channel) return decoder @@ -70,21 +61,22 @@ def get_updated_decoder(decoding_method: str, ) return Matching(H3D, weights=weights) else: - raise ValueError("Unknown decoding method", decoding_method) + msg = "Unknown decoding method" + raise ValueError(msg, decoding_method) def decode_multiround( - syndrome: np.ndarray, - H: np.ndarray, - decoder, - channel_probs: np.ndarray, # needed for matching decoder does not have an update weights method - repetitions: int, - last_round=False, - analog_syndr=None, - check_block_size: int = 0, - sigma: float = 0.0, - H3D: np.ndarray = None, # needed for matching decoder - decoding_method: str = "bposd" # bposd or matching + syndrome: np.ndarray, + H: np.ndarray, + decoder, + channel_probs: np.ndarray, # needed for matching decoder does not have an update weights method + repetitions: int, + last_round=False, + analog_syndr=None, + check_block_size: int = 0, + sigma: float = 0.0, + H3D: np.ndarray = None, # needed for matching decoder + decoding_method: str = "bposd", # bposd or matching ): """Overlapping window decoding. First, we compute the difference syndrome from the recorded syndrome of each measurement round for all measurement @@ -103,24 +95,20 @@ def decode_multiround( if analog_tg: # If we have analog information, we use it to initialize the time-like syndrome nodes, which are defined # in the block of the H3D matrix after the diagonal H block. - analog_init_vals = get_virtual_check_init_vals( - analog_syndr.flatten("F"), sigma - ) + analog_init_vals = get_virtual_check_init_vals(analog_syndr.flatten("F"), sigma) - new_channel = np.hstack( - (channel_probs[:check_block_size], analog_init_vals) - ) + new_channel = np.hstack((channel_probs[:check_block_size], analog_init_vals)) # in the last round, we have a perfect syndrome round to make sure we're in the codespace if last_round: - new_channel[-H.shape[0]:] = 1e-15 + new_channel[-H.shape[0] :] = 1e-15 decoder = get_updated_decoder(decoding_method, decoder, new_channel, H3D) else: if last_round: new_channel = np.copy(channel_probs) - new_channel[-H.shape[0]:] = 1e-15 + new_channel[-H.shape[0] :] = 1e-15 decoder = get_updated_decoder(decoding_method, decoder, new_channel, H3D) @@ -130,25 +118,15 @@ def decode_multiround( bp_iter = decoder.iter # extract space correction, first repetitions * n entires - space_correction = ( - decoded[: H.shape[1] * repetitions] - .reshape((repetitions, H.shape[1])) - .T - ) + space_correction = decoded[: H.shape[1] * repetitions].reshape((repetitions, H.shape[1])).T # extract time correction - if last_round == False: + if last_round is False: # this corresponds to the decoding on the second block of the H3D matrix - time_correction = ( - decoded[H.shape[1] * repetitions:] - .reshape((repetitions, H.shape[0])) - .T - ) + time_correction = decoded[H.shape[1] * repetitions :].reshape((repetitions, H.shape[0])).T # append time correction with zeros - time_correction = np.hstack( - (time_correction, np.zeros((H.shape[0], 1), dtype=np.int32)) - ) + time_correction = np.hstack((time_correction, np.zeros((H.shape[0], 1), dtype=np.int32))) # correct only in the commit region decoded = (np.cumsum(space_correction, 1) % 2)[:, region_size - 1] @@ -157,14 +135,12 @@ def decode_multiround( corr_syndrome = (H @ decoded) % 2 # propagate the syndrome correction through the tentative region - syndrome[:, region_size:] = ( - (syndrome[:, region_size:] + corr_syndrome[:, None]) % 2 - ).astype(np.int32) + syndrome[:, region_size:] = ((syndrome[:, region_size:] + corr_syndrome[:, None]) % 2).astype(np.int32) # apply the time correction of round region_size - 1 to the syndrome at the beginning of the tentative region - syndrome[:, region_size] = ( - (syndrome[:, region_size] + time_correction[:, region_size - 1]) % 2 - ).astype(np.int32) + syndrome[:, region_size] = ((syndrome[:, region_size] + time_correction[:, region_size - 1]) % 2).astype( + np.int32 + ) else: # correct in the commit and tentative region as the last round stabilizer is perfect diff --git a/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py b/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py index 39d78954..1c4242c3 100644 --- a/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py +++ b/src/mqt/qecc/analog-information-decoding/simulators/quasi_single_shot_v2.py @@ -1,47 +1,47 @@ -from typing import List, Optional +from __future__ import annotations + import numpy as np -from utils.data_utils import _check_convergence, create_outpath, BpParams +from bposd import bposd_decoder +from pymatching import Matching from simulators.memory_experiment_v2 import ( build_multiround_pcm, - move_syndrome, decode_multiround, + decode_multiround, + move_syndrome, ) -from bposd import bposd_decoder -from pymatching import Matching +from utils.data_utils import BpParams, _check_convergence, create_outpath from utils.simulation_utils import ( - is_logical_err, - save_results, error_channel_setup, - set_seed, generate_err, generate_syndr_err, - get_sigma_from_syndr_er, - get_noisy_analog_syndrome, get_binary_from_analog, + get_noisy_analog_syndrome, + get_sigma_from_syndr_er, + is_logical_err, + save_results, + set_seed, ) class QSS_SimulatorV2: def __init__( - self, - H: np.ndarray, - per: float, - ser: float, - L: np.ndarray, - bias: List[float], - codename: str, - bp_params: Optional[BpParams], - decoding_method: str = "bposd", # bposd or matching - check_side: str = "X", - seed: int = 666, - analog_tg: bool = False, - repetitions: int = 0, - rounds: int = 0, - experiment: str = "qss", - **kwargs, + self, + H: np.ndarray, + per: float, + ser: float, + L: np.ndarray, + bias: list[float], + codename: str, + bp_params: BpParams | None, + decoding_method: str = "bposd", # bposd or matching + check_side: str = "X", + seed: int = 666, + analog_tg: bool = False, + repetitions: int = 0, + rounds: int = 0, + experiment: str = "qss", + **kwargs, ) -> None: - """ - - :param H: parity-check matrix of code + """:param H: parity-check matrix of code :param per: physical data error rate :param ser: syndrome error rate :param L: logical matrix @@ -70,21 +70,22 @@ def __init__( self.analog_tg = analog_tg self.repetitions = repetitions if repetitions % 2 != 0: - raise ValueError("repetitions must be even") + msg = "repetitions must be even" + raise ValueError(msg) if self.decoding_method not in ["bposd", "matching"]: - raise ValueError("Decoding method must be either bposd or matching") + msg = "Decoding method must be either bposd or matching" + raise ValueError(msg) if self.repetitions % 2 != 0: - raise ValueError("Repetitions must be even!") + msg = "Repetitions must be even!" + raise ValueError(msg) self.rounds = rounds self.experiment = experiment set_seed(seed) # load code parameters - self.code_params = eval( - open(f"generated_codes/{codename}/code_params.txt").read() - ) + self.code_params = eval(open(f"generated_codes/{codename}/code_params.txt").read()) self.input_values = self.__dict__.copy() self.outfile = create_outpath(**self.input_values) @@ -119,7 +120,8 @@ def __init__( # initialize the multiround parity-check matrix as described in the paper self.H3D = build_multiround_pcm( - self.H, self.repetitions - 1, + self.H, + self.repetitions - 1, ) # the number of columns of the diagonal check matrix of the H3D matrix @@ -127,14 +129,10 @@ def __init__( channel_probs = np.zeros(self.H3D.shape[1]) # The bits corresponding to the columns of the diagonal H-bock of H3D are initialized with the bit channel - channel_probs[: self.check_block_size] = np.array( - self.data_err_channel.tolist() * (self.repetitions) - ) + channel_probs[: self.check_block_size] = np.array(self.data_err_channel.tolist() * (self.repetitions)) # The remaining bits (corresponding to the identity block of H3D) are initialized with the syndrome error channel - channel_probs[self.check_block_size:] = np.array( - self.syndr_err_channel.tolist() * (self.repetitions) - ) + channel_probs[self.check_block_size :] = np.array(self.syndr_err_channel.tolist() * (self.repetitions)) # If we do ATG decoding, initialize sigma (syndrome noise strength) if self.analog_tg: @@ -160,10 +158,10 @@ def __init__( self.channel_probs = channel_probs def _decode_multiround( - self, - syndrome_mat: np.ndarray, - analog_syndr_mat: np.ndarray, - last_round: bool = False, + self, + syndrome_mat: np.ndarray, + analog_syndr_mat: np.ndarray, + last_round: bool = False, ) -> np.ndarray: return decode_multiround( syndrome=syndrome_mat, @@ -182,15 +180,11 @@ def _decode_multiround( def _single_sample(self) -> int: # prepare fresh syndrome matrix and error vector # each column == measurement result of a single timestep - syndrome_mat = np.zeros( - (self.num_checks, self.repetitions), dtype=np.int32 - ) + syndrome_mat = np.zeros((self.num_checks, self.repetitions), dtype=np.int32) analog_syndr_mat = None if self.analog_tg: - analog_syndr_mat = np.zeros( - (self.num_checks, self.repetitions), dtype=np.float64 - ) + analog_syndr_mat = np.zeros((self.num_checks, self.repetitions), dtype=np.float64) err = np.zeros(self.num_qubits, dtype=np.int32) cnt = 0 # counter for syndrome_mat @@ -211,20 +205,14 @@ def _single_sample(self) -> int: # add syndrome error if round != (self.rounds - 1): if self.analog_tg: - analog_syndrome = get_noisy_analog_syndrome( - noiseless_syndrome, self.sigma - ) - syndrome = get_binary_from_analog( - analog_syndrome - ) + analog_syndrome = get_noisy_analog_syndrome(noiseless_syndrome, self.sigma) + syndrome = get_binary_from_analog(analog_syndrome) else: syndrome_error = generate_syndr_err(self.syndr_err_channel) syndrome = (noiseless_syndrome + syndrome_error) % 2 else: # last round is perfect syndrome = np.copy(noiseless_syndrome) - analog_syndrome = get_noisy_analog_syndrome( - noiseless_syndrome, 0.0 - ) # no noise + analog_syndrome = get_noisy_analog_syndrome(noiseless_syndrome, 0.0) # no noise # fill the corresponding column of the syndrome/analog syndrome matrix syndrome_mat[:, cnt] += syndrome @@ -233,17 +221,12 @@ def _single_sample(self) -> int: cnt += 1 # move to next column of syndrome matrix - if (cnt == self.repetitions): # if we have filled the syndrome matrix, decode + if cnt == self.repetitions: # if we have filled the syndrome matrix, decode if round != (self.rounds - 1): # if not last round, decode and move syndrome - cnt = (self.repetitions // 2) # reset counter to start of tentative region + cnt = self.repetitions // 2 # reset counter to start of tentative region # the correction is only the correction of the commit region - ( - corr, - syndrome_mat, - analog_syndr_mat, - bp_iters - ) = self._decode_multiround( + (corr, syndrome_mat, analog_syndr_mat, bp_iters) = self._decode_multiround( syndrome_mat, analog_syndr_mat, last_round=False, @@ -253,18 +236,11 @@ def _single_sample(self) -> int: err = (err + corr) % 2 syndrome_mat = move_syndrome(syndrome_mat) if self.analog_tg: - analog_syndr_mat = move_syndrome( - analog_syndr_mat, data_type=np.float64 - ) + analog_syndr_mat = move_syndrome(analog_syndr_mat, data_type=np.float64) else: # if we are in the last round, decode and stop # the correction is the correction of the commit and tentative region - ( - corr, - syndrome_mat, - analog_syndr_mat, - bp_iters - ) = self._decode_multiround( + (corr, syndrome_mat, analog_syndr_mat, bp_iters) = self._decode_multiround( syndrome_mat, analog_syndr_mat, last_round=True, @@ -284,18 +260,17 @@ def _save_results(self, success_cnt: int, samples: int) -> dict: code_params=self.code_params, err_side="z" if self.check_side == "X" else "x", bp_iterations=self.bp_iterations, - bp_params=self.bp_params + bp_params=self.bp_params, ) def run(self, samples: int = 1): - """Returns single data point""" + """Returns single data point.""" success_cnt = 0 for run in range(1, samples + 1): success_cnt += self._single_sample() if run % self.save_interval == 1: self._save_results(success_cnt, run) - if _check_convergence( - success_cnt, run, self.code_params, self.eb_precission): + if _check_convergence(success_cnt, run, self.code_params, self.eb_precission): print("Converged") break - return self._save_results(success_cnt, run) \ No newline at end of file + return self._save_results(success_cnt, run) diff --git a/src/mqt/qecc/analog-information-decoding/simulators/simulation.py b/src/mqt/qecc/analog-information-decoding/simulators/simulation.py index 7391b862..f17ad0a6 100644 --- a/src/mqt/qecc/analog-information-decoding/simulators/simulation.py +++ b/src/mqt/qecc/analog-information-decoding/simulators/simulation.py @@ -1,33 +1,38 @@ -from utils.simulation_utils import * +from __future__ import annotations + +from timeit import default_timer as timer + from ldpc2 import bposd_decoder +from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder from utils.data_utils import ( + BpParams, calculate_error_rates, is_converged, replace_inf, - BpParams, -) # , create_outpath +) + +# , create_outpath from utils.data_utils import create_outpath as get_outpath -from ldpc2.bposd_decoder import SoftInfoBpOsdDecoder -from timeit import default_timer as timer +from utils.simulation_utils import * class Single_Shot_Simulator: def __init__( - self, - codename: str, - per: float, - ser: float, - single_stage: bool, - seed: int, - bias: list, - x_meta: bool, - z_meta: bool, - sus_th_depth: int, - analog_info: bool = False, - cutoff: int = 0, - analog_tg: bool = False, - bp_params: BpParams = None, - **kwargs, + self, + codename: str, + per: float, + ser: float, + single_stage: bool, + seed: int, + bias: list, + x_meta: bool, + z_meta: bool, + sus_th_depth: int, + analog_info: bool = False, + cutoff: int = 0, + analog_tg: bool = False, + bp_params: BpParams = None, + **kwargs, ) -> None: set_seed(seed) self.codename = codename @@ -50,47 +55,31 @@ def __init__( # self.code_path = f"generated_codes/{codename}" self.code_path = f"/codes/generated_codes/{codename}" # Load code params - self.code_params = eval( - open(f"{self.code_path}/code_params.txt").read() - ) + self.code_params = eval(open(f"{self.code_path}/code_params.txt").read()) self.input_values = self.__dict__.copy() self.outfile = get_outpath(**self.input_values) # Set parity check matrices - self.Hx = np.loadtxt( - f"{self.code_path}/hx.txt", dtype=np.int32 - ) - self.Hz = np.loadtxt( - f"{self.code_path}/hz.txt", dtype=np.int32 - ) + self.Hx = np.loadtxt(f"{self.code_path}/hx.txt", dtype=np.int32) + self.Hz = np.loadtxt(f"{self.code_path}/hz.txt", dtype=np.int32) if self.x_meta: - self.Mx = np.loadtxt( - f"{self.code_path}/mx.txt", dtype=np.int32 - ) + self.Mx = np.loadtxt(f"{self.code_path}/mx.txt", dtype=np.int32) else: self.Mx = None if self.z_meta: - self.Mz = np.loadtxt( - f"{self.code_path}/mz.txt", dtype=np.int32 - ) + self.Mz = np.loadtxt(f"{self.code_path}/mz.txt", dtype=np.int32) else: self.Mz = None - self.n = self.Hx.shape[ - 1 - ] # m==shape[0] ambiguous for Hx, Hz, better use their shape directly + self.n = self.Hx.shape[1] # m==shape[0] ambiguous for Hx, Hz, better use their shape directly self.check_input() # load logicals if possible try: - self.lx = np.loadtxt( - f"{self.code_path}/lx.txt", dtype=np.int32 - ) - self.lz = np.loadtxt( - f"{self.code_path}/lz.txt", dtype=np.int32 - ) + self.lx = np.loadtxt(f"{self.code_path}/lx.txt", dtype=np.int32) + self.lz = np.loadtxt(f"{self.code_path}/lz.txt", dtype=np.int32) self._check_logicals = True except: self._check_logicals = False @@ -111,12 +100,8 @@ def __init__( # This needs two calls since the syndromes may have different lengths # first vector ([0]) is x channel probability vector # by our convention the syndrome is named after the error. - full_x_syndr_err_chnl = error_channel_setup( - self.syndr_err_rate, self.bias, self.Hz.shape[0] - ) - full_z_syndr_err_chnl = error_channel_setup( - self.syndr_err_rate, self.bias, self.Hx.shape[0] - ) + full_x_syndr_err_chnl = error_channel_setup(self.syndr_err_rate, self.bias, self.Hz.shape[0]) + full_z_syndr_err_chnl = error_channel_setup(self.syndr_err_rate, self.bias, self.Hx.shape[0]) self.x_syndr_error_channel = full_x_syndr_err_chnl[0] + full_x_syndr_err_chnl[1] # x+y self.z_syndr_error_channel = full_z_syndr_err_chnl[2] + full_z_syndr_err_chnl[1] # z+y @@ -138,19 +123,18 @@ def __init__( else: self._two_stage_setup() - self._total_decoding_time = 0.0 def check_input(self): """Check initialization parameters for consistency.""" - if self.analog_tg == True and self.analog_info == True: - raise ValueError("analog_tg and analog_info cannot be both True") + if self.analog_tg is True and self.analog_info is True: + msg = "analog_tg and analog_info cannot be both True" + raise ValueError(msg) def _single_sample( - self, + self, ): - """ - Simulates a single sample for a given sustainable threshold depth. + """Simulates a single sample for a given sustainable threshold depth. :return: """ residual_err = [ @@ -160,7 +144,7 @@ def _single_sample( # for single shot simulation we have sus_th_depth number of 'noisy' simulations (residual error carried over) # followed by a single round of perfect syndrome extraction after the sustainable threshold loop - for round in range(self.sus_th_depth): + for _round in range(self.sus_th_depth): x_err, z_err = generate_err( N=self.n, channel_probs=self.data_error_channel, @@ -172,27 +156,19 @@ def _single_sample( x_syndrome = self.Hz @ x_err % 2 z_syndrome = self.Hx @ z_err % 2 - x_syndrome_w_err, z_syndrome_w_err = self._get_noisy_syndrome( - x_syndrome, z_syndrome - ) + x_syndrome_w_err, z_syndrome_w_err = self._get_noisy_syndrome(x_syndrome, z_syndrome) # collect total decoding time start = timer() if self.single_stage: - x_decoded, z_decoded = self._single_stage_decoding( - x_syndrome_w_err, z_syndrome_w_err - ) + x_decoded, z_decoded = self._single_stage_decoding(x_syndrome_w_err, z_syndrome_w_err) else: - x_decoded, z_decoded = self._two_stage_decoding( - x_syndrome_w_err, z_syndrome_w_err - ) + x_decoded, z_decoded = self._two_stage_decoding(x_syndrome_w_err, z_syndrome_w_err) end = timer() self._total_decoding_time += end - start residual_err = [ - np.array( - (x_err + x_decoded) % 2, dtype=np.int32 - ), # np conversion needed to avoid rt error + np.array((x_err + x_decoded) % 2, dtype=np.int32), # np conversion needed to avoid rt error np.array((z_err + z_decoded) % 2, dtype=np.int32), ] @@ -227,34 +203,20 @@ def _single_sample( is_x_logical_error = is_logical_err(self.lz, x_residual_err) is_z_logical_error = is_logical_err(self.lx, z_residual_err) else: - is_x_logical_error = check_logical_err_h( - self.Hz, x_err, x_residual_err - ) - is_z_logical_error = check_logical_err_h( - self.Hx, z_err, z_residual_err - ) + is_x_logical_error = check_logical_err_h(self.Hz, x_err, x_residual_err) + is_z_logical_error = check_logical_err_h(self.Hx, z_err, z_residual_err) return is_x_logical_error, is_z_logical_error def _get_noisy_syndrome(self, x_syndrome, z_syndrome): if self.syndr_err_rate != 0.0: - if ( - self.analog_info or self.analog_tg - ): # analog syndrome error with converted sigma - x_syndrome_w_err = get_noisy_analog_syndrome( - perfect_syndr=x_syndrome, sigma=self.sigma_x - ) - z_syndrome_w_err = get_noisy_analog_syndrome( - perfect_syndr=z_syndrome, sigma=self.sigma_z - ) + if self.analog_info or self.analog_tg: # analog syndrome error with converted sigma + x_syndrome_w_err = get_noisy_analog_syndrome(perfect_syndr=x_syndrome, sigma=self.sigma_x) + z_syndrome_w_err = get_noisy_analog_syndrome(perfect_syndr=z_syndrome, sigma=self.sigma_z) else: # usual pauli error channel syndrome error - x_syndrome_err = generate_syndr_err( - channel_probs=self.x_syndr_error_channel - ) + x_syndrome_err = generate_syndr_err(channel_probs=self.x_syndr_error_channel) x_syndrome_w_err = (x_syndrome + x_syndrome_err) % 2 - z_syndrome_err = generate_syndr_err( - channel_probs=self.z_syndr_error_channel - ) + z_syndrome_err = generate_syndr_err(channel_probs=self.z_syndr_error_channel) z_syndrome_w_err = (z_syndrome + z_syndrome_err) % 2 else: x_syndrome_w_err = np.copy(x_syndrome) @@ -310,12 +272,12 @@ def _single_stage_decoding(self, x_syndrome_w_err, z_syndrome_w_err): return x_decoded, z_decoded def _decode_ss_no_meta( - self, - syndrome_w_err, - analog_tg_decoder, - standard_decoder, - bit_err_channel, - sigma, + self, + syndrome_w_err, + analog_tg_decoder, + standard_decoder, + bit_err_channel, + sigma, ): """Decoding of syndrome without meta checks. In case analog_tg is active, we use the analog tanner graph decoding method, which uses the analog syndrome. @@ -335,9 +297,7 @@ def _decode_ss_no_meta( iter = standard_decoder.iter return decoded, iter - def _decode_ss_with_meta( - self, syndrome_w_err, ss_bpd, M, bit_err_channel, sigma - ): + def _decode_ss_with_meta(self, syndrome_w_err, ss_bpd, M, bit_err_channel, sigma): """Single-Stage decoding for given syndrome. If analog_tg is active, we use the analog tanner graph decoding method, which uses the analog syndrome to @@ -357,12 +317,8 @@ def _decode_ss_with_meta( ) else: if self.analog_info: - meta_bin = ( - M @ get_binary_from_analog(syndrome_w_err) - ) % 2 - meta_syndr = get_signed_from_binary( - meta_bin - ) # for AI decoder we need {-1,+1} syndrome as input + meta_bin = (M @ get_binary_from_analog(syndrome_w_err)) % 2 + meta_syndr = get_signed_from_binary(meta_bin) # for AI decoder we need {-1,+1} syndrome as input else: meta_syndr = (M @ syndrome_w_err) % 2 @@ -371,29 +327,20 @@ def _decode_ss_with_meta( decoded = ss_bpd.decode(ss_syndr)[: self.n] return decoded, ss_bpd.iter - def _ss_analog_tg_decoding( - self, decoder, analog_syndrome, M, bit_err_channel, sigma: float - ): + def _ss_analog_tg_decoding(self, decoder, analog_syndrome, M, bit_err_channel, sigma: float): """Decodes the noisy analog syndrome using the single stage analog tanner graph and BPOSD, i.e., combines single-stage and analog tanner graph method. In the standard single-stage method, BP is initialized with the bit channel + the syndrome channel. In order for the virtual nodes to contain the analog info, we adapt the syndrome channel by computing the 'analog channel' given the analog syndrome. """ - analog_channel = get_virtual_check_init_vals(analog_syndrome, sigma) - decoder.update_channel_probs( - np.hstack((bit_err_channel, analog_channel)) - ) + decoder.update_channel_probs(np.hstack((bit_err_channel, analog_channel))) - bin_syndr = get_binary_from_analog( - analog_syndrome - ) # here we need to threshold since syndrome is analog + bin_syndr = get_binary_from_analog(analog_syndrome) # here we need to threshold since syndrome is analog meta_syndr = (M @ bin_syndr) % 2 ss_syndr = np.hstack((bin_syndr, meta_syndr)) - decoded = decoder.decode(ss_syndr)[: self.n] # only first n bit are data - - return decoded + return decoder.decode(ss_syndr)[: self.n] # only first n bit are data def _two_stage_decoding(self, x_syndrome_w_err, z_syndrome_w_err): """Two stage decoding of single shot code @@ -428,9 +375,7 @@ def _two_stage_decoding(self, x_syndrome_w_err, z_syndrome_w_err): else: x_syndrome_repaired = np.copy(x_syndrome_w_err) - if ( - self.analog_tg - ): # decode with analog tg method - update channel probs to init analog nodes + if self.analog_tg: # decode with analog tg method - update channel probs to init analog nodes x_decoded = self._analog_tg_decoding( decoder=self.x_abpd, hard_syndrome=x_syndrome_repaired, @@ -467,9 +412,7 @@ def _meta_code_decoding(self, syndrome_w_err, M, m_bp, sigma): return syndrome_w_err - def _analog_tg_decoding( - self, decoder, analog_syndrome, hard_syndrome, bit_err_channel, sigma - ): + def _analog_tg_decoding(self, decoder, analog_syndrome, hard_syndrome, bit_err_channel, sigma): """Decodes the noisy analog syndrome using the analog tanner graph and BPOSD First, the channel probabilities need to be set according to the analog syndrome s.t. the decoder is initialized properly. @@ -478,27 +421,20 @@ def _analog_tg_decoding( The bit_err_channel is supposed to be the error channel for the n physical bits as usual. """ analog_channel = get_virtual_check_init_vals(analog_syndrome, sigma) - decoder.update_channel_probs( - np.hstack((bit_err_channel, analog_channel)) - ) - - decoded = decoder.decode(hard_syndrome)[ - : self.n - ] # only first n bit are data + decoder.update_channel_probs(np.hstack((bit_err_channel, analog_channel))) - return decoded + return decoder.decode(hard_syndrome)[: self.n] # only first n bit are data def _single_stage_setup( - self, + self, ): - """ - Sets up the single stage decoding. - * BPOSD decoders for the single-stage check matrices (cf Higgot & Breuckmann) are setup in case - there is a meta code for the respective side. - * BPOSD decoders for the check matrices Hx/Hz are set up for the last, perfect round - * In case analog_tg is activated, BPOSD for the analog tanner graph is setup - * If analog_info is active, SI-decoder is used to decode single-stage matrices and standard check matrices - instead of BPOSD + """Sets up the single stage decoding. + * BPOSD decoders for the single-stage check matrices (cf Higgot & Breuckmann) are setup in case + there is a meta code for the respective side. + * BPOSD decoders for the check matrices Hx/Hz are set up for the last, perfect round + * In case analog_tg is activated, BPOSD for the analog tanner graph is setup + * If analog_info is active, SI-decoder is used to decode single-stage matrices and standard check matrices + instead of BPOSD. """ # X-syndrome sx = Hz*ex # x_syndr_error == error on Hz => corrected with Mz @@ -522,9 +458,7 @@ def _single_stage_setup( if self.ss_z_pcm is not None: self.ss_x_bpd = self.get_decoder( pcm=self.ss_z_pcm, - channel_probs=np.hstack( - (x_err_channel, self.x_syndr_error_channel) - ), + channel_probs=np.hstack((x_err_channel, self.x_syndr_error_channel)), sigma=self.sigma_x, analog_info=self.analog_info, ) @@ -533,11 +467,9 @@ def _single_stage_setup( self.x_abpd = self.get_decoder( pcm=self.z_apcm, # second part dummy, needs to be updated for each syndrome - channel_probs=np.hstack( - (x_err_channel, np.zeros(self.Hz.shape[0])) - ), + channel_probs=np.hstack((x_err_channel, np.zeros(self.Hz.shape[0]))), cutoff=self.cutoff, - analog_info=False + analog_info=False, # sigma not needed, since we apply BPOSD ) else: @@ -554,9 +486,7 @@ def _single_stage_setup( if self.ss_x_pcm is not None: self.ss_z_bpd = self.get_decoder( pcm=self.ss_x_pcm, - channel_probs=np.hstack( - (z_err_channel, self.z_syndr_error_channel) - ), + channel_probs=np.hstack((z_err_channel, self.z_syndr_error_channel)), cutoff=self.cutoff, analog_info=self.analog_info, sigma=self.sigma_z, @@ -566,11 +496,9 @@ def _single_stage_setup( self.z_abpd = self.get_decoder( pcm=self.x_apcm, # second part dummy, needs to be updated for each syndrome - channel_probs=np.hstack( - (z_err_channel, np.zeros(self.Hx.shape[0])) - ), + channel_probs=np.hstack((z_err_channel, np.zeros(self.Hx.shape[0]))), cutoff=self.cutoff, - analog_info=self.analog_info + analog_info=self.analog_info, # sigma not needed, since we apply BPOSD ) else: @@ -584,10 +512,9 @@ def _single_stage_setup( ) def _two_stage_setup( - self, + self, ): - """ - Sets up the two stage decoding. + """Sets up the two stage decoding. * In case meta codes are present, BPOSD decoders for the meta codes are setup * In case analo_tg is active, BPOSD decoders for the analog tanner graph are setup * Additionally, BPOSD for Hx, Hz are setup for the final round @@ -615,12 +542,10 @@ def _two_stage_setup( if self.analog_tg: self.x_abpd = self.get_decoder( pcm=self.z_apcm, - channel_probs=np.hstack( - (x_err_channel, np.zeros(self.Hz.shape[0])) - ), + channel_probs=np.hstack((x_err_channel, np.zeros(self.Hz.shape[0]))), # second part dummy, needs to be updated for each syndrome cutoff=self.cutoff, - analog_info=self.analog_info + analog_info=self.analog_info, # sigma not needed, since we apply BPOSD ) self.x_bpd = self.get_decoder( @@ -636,11 +561,9 @@ def _two_stage_setup( self.z_abpd = self.get_decoder( pcm=self.x_apcm, # second part dummy, needs to be updated for each syndrome - channel_probs=np.hstack( - (z_err_channel, np.zeros(self.Hx.shape[0])) - ), + channel_probs=np.hstack((z_err_channel, np.zeros(self.Hx.shape[0]))), cutoff=self.cutoff, - analog_info=self.analog_info + analog_info=self.analog_info, # sigma not needed, since we apply BPOSD ) self.z_bpd = self.get_decoder( @@ -648,17 +571,17 @@ def _two_stage_setup( channel_probs=z_err_channel, cutoff=self.cutoff, analog_info=self.analog_info, - sigma=self.sigma_z + sigma=self.sigma_z, # sigma not needed, since we don't have analog info for meta code ) def get_decoder( - self, - pcm, - channel_probs, - cutoff: int = 0, - sigma: float = 0.0, - analog_info: bool = False, + self, + pcm, + channel_probs, + cutoff: int = 0, + sigma: float = 0.0, + analog_info: bool = False, ): """Initialize decoder objects If analog_info is activated, the SoftInfoBpDecoder is used instead of the BPOSD decoder. @@ -688,26 +611,17 @@ def get_decoder( ) def construct_analog_pcms(self): - """ - constructs apcm = [H | I_m] where I_m is the m x m identity matrix - """ - return np.hstack( - [self.Hx, np.identity(self.Hx.shape[0], dtype=np.int32)] - ), np.hstack([self.Hz, np.identity(self.Hz.shape[0], dtype=np.int32)]) - - def save_results(self, - x_success_cnt: int, - z_success_cnt: int, - runs: int, - x_bp_iters: np.ndarray, - z_bp_iters: np.ndarray): - x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( - x_success_cnt, runs, self.code_params - ) - z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( - z_success_cnt, runs, self.code_params + """Constructs apcm = [H | I_m] where I_m is the m x m identity matrix.""" + return np.hstack([self.Hx, np.identity(self.Hx.shape[0], dtype=np.int32)]), np.hstack( + [self.Hz, np.identity(self.Hz.shape[0], dtype=np.int32)] ) + def save_results( + self, x_success_cnt: int, z_success_cnt: int, runs: int, x_bp_iters: np.ndarray, z_bp_iters: np.ndarray + ): + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates(x_success_cnt, runs, self.code_params) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates(z_success_cnt, runs, self.code_params) + output = { "code_K": self.code_params["k"], "code_N": self.code_params["n"], @@ -756,24 +670,19 @@ def run(self, samples: int): z_bp_iters += self.z_bp_iters if runs % self.save_interval == 0: - self.save_results(x_success_cnt, z_success_cnt, runs, - x_bp_iters=x_bp_iters, z_bp_iters=z_bp_iters) + self.save_results(x_success_cnt, z_success_cnt, runs, x_bp_iters=x_bp_iters, z_bp_iters=z_bp_iters) if is_converged( - x_success_cnt, - z_success_cnt, - runs, - self.code_params, - self.eb_precission, + x_success_cnt, + z_success_cnt, + runs, + self.code_params, + self.eb_precission, ): print("Result has converged.") break - x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates( - x_success_cnt, runs, self.code_params - ) - z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates( - z_success_cnt, runs, self.code_params - ) + x_ler, x_ler_eb, x_wer, x_wer_eb = calculate_error_rates(x_success_cnt, runs, self.code_params) + z_ler, z_ler_eb, z_wer, z_wer_eb = calculate_error_rates(z_success_cnt, runs, self.code_params) avg_x_bp_iter = x_bp_iters / runs avg_z_bp_iter = z_bp_iters / runs output = { @@ -800,4 +709,4 @@ def run(self, samples: int): output.update(self.input_values) self.save_results(x_success_cnt, z_success_cnt, runs, x_bp_iters=x_bp_iters, z_bp_iters=z_bp_iters) - return output \ No newline at end of file + return output diff --git a/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py b/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py index cbcf43fd..72dd231c 100644 --- a/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py +++ b/src/mqt/qecc/analog-information-decoding/test/test_memory_experiment.py @@ -1,13 +1,11 @@ +from __future__ import annotations + import numpy as np -from scipy.sparse import csr_matrix, hstack, vstack, diags, eye, block_diag from simulators.memory_experiment_v2 import build_multiround_pcm, move_syndrome def test_build_mr_pcm(): - H = np.array([ - [1, 1, 0], - [0, 1, 1] - ]).astype(np.int32) + H = np.array([[1, 1, 0], [0, 1, 1]]).astype(np.int32) mr_pcm = build_multiround_pcm(H, 1) np.zeros((2, 3)) np.identity(2) @@ -20,40 +18,16 @@ def test_build_mr_pcm(): def test_move_syndrome() -> None: # three bit syndrome over 4 rounds - syndr = np.array([ - [0, 0, 1, 0], - [0, 0, 0, 1], - [0, 0, 1, 0] - ]) - res = np.array([ - [1, 0, 0, 0], - [0, 1, 0, 0], - [1, 0, 0, 0] - ]) + syndr = np.array([[0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) + res = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [1, 0, 0, 0]]) assert np.array_equal(res, move_syndrome(syndr)) - syndr = np.array([ - [0, 0, 1, 1], - [0, 0, 1, 1], - [0, 0, 1, 1] - ]) - res = np.array([ - [1, 1, 0, 0], - [1, 1, 0, 0], - [1, 1, 0, 0] - ]) + syndr = np.array([[0, 0, 1, 1], [0, 0, 1, 1], [0, 0, 1, 1]]) + res = np.array([[1, 1, 0, 0], [1, 1, 0, 0], [1, 1, 0, 0]]) assert np.array_equal(res, move_syndrome(syndr)) - syndr = np.array([ - [0, 0, 1, 1, 0, 0], - [0, 0, 1, 1, 0, 0], - [0, 0, 1, 1, 0, 0] - ]) - res = np.array([ - [1, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 0, 0] - ]) + syndr = np.array([[0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 0, 0]]) + res = np.array([[1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0]]) assert np.array_equal(res, move_syndrome(syndr)) diff --git a/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py b/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py index e826cc5b..cf096de7 100644 --- a/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py +++ b/src/mqt/qecc/analog-information-decoding/test/test_simulation_utils.py @@ -1,67 +1,147 @@ +from __future__ import annotations + import math import numpy as np -from scipy.sparse import csr_matrix, coo_matrix - import utils from utils import simulation_utils def test_check_logical_err_h() -> None: - H = np.array( - [ - [1, 0, 0, 1, 0, 1, 1], - [0, 1, 0, 1, 1, 0, 1], - [0, 0, 1, 0, 1, 1, 1] - ]) + H = np.array([[1, 0, 0, 1, 0, 1, 1], [0, 1, 0, 1, 1, 0, 1], [0, 0, 1, 0, 1, 1, 1]]) # check with logical estimate = np.array([1, 0, 0, 0, 0, 0, 1]) - assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 1, 1, 0]), estimate) == True + assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 1, 1, 0]), estimate) is True # # check with stabilizer estimate2 = np.array([0, 0, 0, 0, 0, 0, 1]) - assert simulation_utils.check_logical_err_h(H, np.array([1, 1, 1, 0, 0, 0, 0]), estimate2) == False + assert simulation_utils.check_logical_err_h(H, np.array([1, 1, 1, 0, 0, 0, 0]), estimate2) is False # check with all zeros estimate3 = np.array([0, 0, 0, 0, 0, 0, 0]) - assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 0, 0, 0]), estimate3) == False + assert simulation_utils.check_logical_err_h(H, np.array([0, 0, 0, 0, 0, 0, 0]), estimate3) is False def test_is_logical_err() -> None: # check with logical Lsc = np.array( - [[1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) + [ + [ + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ] + ) residual = np.array( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ) - assert simulation_utils.is_logical_err(Lsc, residual) == True + assert simulation_utils.is_logical_err(Lsc, residual) is True # check with stabilizer residual2 = np.array( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - assert simulation_utils.is_logical_err(Lsc, residual2) == False + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ) + assert simulation_utils.is_logical_err(Lsc, residual2) is False # check with all zeros residual2 = np.array( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - assert simulation_utils.is_logical_err(Lsc, residual2) == False + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ) + assert simulation_utils.is_logical_err(Lsc, residual2) is False # check with non-min weight logical residual3 = np.array( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - assert simulation_utils.is_logical_err(Lsc, residual3) == True + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ) + assert simulation_utils.is_logical_err(Lsc, residual3) is True def test_get_analog_llr() -> None: analog_syndr = np.array([0.5, 0, 0, -1, 0, 1]) sigma = 0.8 - assert np.allclose(simulation_utils.get_analog_llr(analog_syndr, sigma), - np.array([1.5625, 0., 0., -3.125, 0., 3.125])) + assert np.allclose( + simulation_utils.get_analog_llr(analog_syndr, sigma), np.array([1.5625, 0.0, 0.0, -3.125, 0.0, 3.125]) + ) sigma = 0.1 - assert np.allclose(simulation_utils.get_analog_llr(analog_syndr, sigma), np.array([100, 0., 0., -200., 0., 200.])) + assert np.allclose( + simulation_utils.get_analog_llr(analog_syndr, sigma), np.array([100, 0.0, 0.0, -200.0, 0.0, 200.0]) + ) + def test_generate_err() -> None: # no errors @@ -79,7 +159,8 @@ def test_generate_err() -> None: expected = [np.copy(residual[0]), np.copy(residual[1])] res = simulation_utils.generate_err(n, channel, residual) - assert np.array_equal(res[0], expected[0]) and np.array_equal(res[1], expected[1]) + assert np.array_equal(res[0], expected[0]) + assert np.array_equal(res[1], expected[1]) def test_get_sigma_from_syndr_er() -> None: @@ -105,36 +186,41 @@ def test_get_virtual_check_init_vals() -> None: noisy_syndr = np.array([0.5, 0, 0, -1, 0, 100]) sigma = 0.8 - assert np.allclose(simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), - np.array([1.73288206e-001, 5.00000000e-001, 5.00000000e-001, 4.20877279e-002, 5.00000000e-001, - 1.91855567e-136])) + assert np.allclose( + simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), + np.array( + [1.73288206e-001, 5.00000000e-001, 5.00000000e-001, 4.20877279e-002, 5.00000000e-001, 1.91855567e-136] + ), + ) sigma = 0.1 - assert np.allclose(simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), - np.array([3.72007598e-44, 5.00000000e-01, 5.00000000e-01, 1.38389653e-87, 5.00000000e-01, - 0.00000000e+00])) + assert np.allclose( + simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma), + np.array([3.72007598e-44, 5.00000000e-01, 5.00000000e-01, 1.38389653e-87, 5.00000000e-01, 0.00000000e00]), + ) sigma = 0.0 res = simulation_utils.get_virtual_check_init_vals(noisy_syndr, sigma) - assert math.isnan(res[1]) and res[0] == 0. + assert math.isnan(res[1]) + assert res[0] == 0.0 def test_generate_syndr_err() -> None: channel = np.array([0.0, 1.0]) - assert np.array_equal(simulation_utils.generate_syndr_err(channel), np.array([0., 1.])) + assert np.array_equal(simulation_utils.generate_syndr_err(channel), np.array([0.0, 1.0])) def test_get_noisy_analog_syndr() -> None: perfect_s = np.array([1, 0]) sigma = 0.0 - assert np.array_equal(simulation_utils.get_noisy_analog_syndrome(perfect_s, sigma), np.array([-1., 1.])) + assert np.array_equal(simulation_utils.get_noisy_analog_syndrome(perfect_s, sigma), np.array([-1.0, 1.0])) def test_err_chnl_setup() -> None: p = 0.1 - bias = [1., 1., 1.] + bias = [1.0, 1.0, 1.0] n = 10 ar = np.ones(n) * p / 3 exp = [np.copy(ar), np.copy(ar), np.copy(ar)] @@ -142,21 +228,21 @@ def test_err_chnl_setup() -> None: assert np.array_equal(res, exp) - bias = [1., 0., 0.] + bias = [1.0, 0.0, 0.0] ar = np.ones(n) * p exp = [np.copy(ar), np.zeros(n), np.zeros(n)] res = simulation_utils.error_channel_setup(p, bias, n) assert np.array_equal(res, exp) - bias = [1., 1., 0.] + bias = [1.0, 1.0, 0.0] ar = np.ones(n) * p / 2 exp = [np.copy(ar), np.copy(ar), np.zeros(n)] res = simulation_utils.error_channel_setup(p, bias, n) assert np.array_equal(res, exp) - bias = [np.inf, 0., 0.] + bias = [np.inf, 0.0, 0.0] ar = np.ones(n) * p exp = [np.copy(ar), np.zeros(n), np.zeros(n)] res = simulation_utils.error_channel_setup(p, bias, n) @@ -165,18 +251,8 @@ def test_err_chnl_setup() -> None: def test_build_ss_pcm() -> None: - H = np.array([ - [1, 1, 0, 0], - [0, 1, 1, 0], - [0, 0, 1, 1], - [1, 0, 0, 1]] - ) - M = np.array([ - [1, 1, 0, 0], - [0, 1, 1, 0], - [0, 0, 1, 1], - [1, 0, 0, 1]] - ) + H = np.array([[1, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 1], [1, 0, 0, 1]]) + M = np.array([[1, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 1], [1, 0, 0, 1]]) id_r = np.identity(M.shape[1]) zeros = np.zeros((M.shape[0], H.shape[1])) exp = np.block([[H, id_r], [zeros, M]]).astype(np.int32) @@ -192,6 +268,6 @@ def test_get_signed_from_binary() -> None: def test_get_binary_from_analog() -> None: exp = np.array([1, 0, 0, 1, 0, 1]) - analog = np.array([-1.0, 3., 1., -1., 1., -2]) + analog = np.array([-1.0, 3.0, 1.0, -1.0, 1.0, -2]) assert np.array_equal(utils.get_binary_from_analog(analog), exp) diff --git a/src/mqt/qecc/analog-information-decoding/utils/__init__.py b/src/mqt/qecc/analog-information-decoding/utils/__init__.py index f4a7ed23..ae419b6a 100644 --- a/src/mqt/qecc/analog-information-decoding/utils/__init__.py +++ b/src/mqt/qecc/analog-information-decoding/utils/__init__.py @@ -1,2 +1,4 @@ +from __future__ import annotations + from data_utils import * from simulation_utils import * diff --git a/src/mqt/qecc/analog-information-decoding/utils/data_utils.py b/src/mqt/qecc/analog-information-decoding/utils/data_utils.py index 6044a897..03e76d0f 100644 --- a/src/mqt/qecc/analog-information-decoding/utils/data_utils.py +++ b/src/mqt/qecc/analog-information-decoding/utils/data_utils.py @@ -1,17 +1,18 @@ from __future__ import annotations -import numpy as np + import itertools -import os import json -from typing import Any, Dict, List, Optional, Union -from dataclasses import fields, dataclass +import os +from dataclasses import dataclass, fields from pathlib import Path +from typing import Any + +import numpy as np + @dataclass class BpParams: - """ - Class to store parameters for BP decoding. - """ + """Class to store parameters for BP decoding.""" bp_method: str = "msl" max_bp_iter: int = 30 @@ -21,7 +22,7 @@ class BpParams: schedule: str = "parallel" omp_thread_count: int = 1 random_serial_schedule: int = 0 - serial_schedule_order: Optional[List[int]] = None + serial_schedule_order: list[int] | None = None cutoff: float = np.inf @classmethod @@ -31,13 +32,10 @@ def from_dict(cls, dict_): def extract_settings(filename): - """ - Extracts all settings from a parameter file and returns them as a dictionary. - """ - + """Extracts all settings from a parameter file and returns them as a dictionary.""" keyword_lists = {} - with open(filename, 'r') as file: + with open(filename) as file: for line in file: json_data = json.loads(line.strip()) for keyword, value in json_data.items(): @@ -51,11 +49,8 @@ def extract_settings(filename): def load_data( input_filenames: list[str], - ) -> list[dict]: - """ - Loads data from a list of JSON files and returns it as a list of dictionaries. - """ - +) -> list[dict]: + """Loads data from a list of JSON files and returns it as a list of dictionaries.""" data = [] for file in input_filenames: path = Path(file) @@ -71,19 +66,14 @@ def load_data( def calculate_error_rates(success_cnt, runs, code_params): - """ - Calculates logical error rate, logical error rate error bar, word error rate, + """Calculates logical error rate, logical error rate error bar, word error rate, and word error rate error bar. """ logical_err_rate = 1.0 - (success_cnt / runs) - logical_err_rate_eb = np.sqrt( - (1 - logical_err_rate) * logical_err_rate / runs - ) + logical_err_rate_eb = np.sqrt((1 - logical_err_rate) * logical_err_rate / runs) word_error_rate = 1.0 - (1 - logical_err_rate) ** (1 / code_params["k"]) word_error_rate_eb = ( - logical_err_rate_eb - * ((1 - logical_err_rate_eb) ** (1 / code_params["k"] - 1)) - / code_params["k"] + logical_err_rate_eb * ((1 - logical_err_rate_eb) ** (1 / code_params["k"] - 1)) / code_params["k"] ) return ( logical_err_rate, @@ -94,13 +84,9 @@ def calculate_error_rates(success_cnt, runs, code_params): def is_converged(x_success, z_success, runs, code_params, precission): - x_cond = _check_convergence( - x_success, runs, code_params, precission_cutoff=precission - ) - z_cond = _check_convergence( - z_success, runs, code_params, precission_cutoff=precission - ) - return x_cond == z_cond == True + x_cond = _check_convergence(x_success, runs, code_params, precission_cutoff=precission) + z_cond = _check_convergence(z_success, runs, code_params, precission_cutoff=precission) + return x_cond == z_cond is True def _check_convergence(success_cnt, runs, code_params, precission_cutoff): @@ -108,6 +94,7 @@ def _check_convergence(success_cnt, runs, code_params, precission_cutoff): if ler_eb != 0.0: if ler_eb / ler < precission_cutoff: return True + return None else: return False @@ -115,16 +102,16 @@ def _check_convergence(success_cnt, runs, code_params, precission_cutoff): def create_outpath( x_meta: bool = False, z_meta: bool = False, - bias: List[float] = None, - codename: str = None, + bias: list[float] | None = None, + codename: str | None = None, single_stage: bool = True, - sus_th_depth: int = None, - rounds: int = None, + sus_th_depth: int | None = None, + rounds: int | None = None, id: int = 0, overwrite: bool = False, analog_info: bool = False, analog_tg: bool = False, - repetitions: int = None, + repetitions: int | None = None, experiment: str = "wer_per_round", **kwargs, ) -> str: @@ -162,24 +149,18 @@ def create_outpath( path += f"{codename:s}/" - if "syndr_err_rate" not in kwargs or kwargs['syndr_err_rate'] is None: + if "syndr_err_rate" not in kwargs or kwargs["syndr_err_rate"] is None: if "sigma" in kwargs: - path += ( - f"per_{kwargs['data_err_rate']:.3e}_sigma_{kwargs['sigma']:.3e}/" - ) + path += f"per_{kwargs['data_err_rate']:.3e}_sigma_{kwargs['sigma']:.3e}/" if "z_sigma" in kwargs: - path += ( - f"per_{kwargs['data_err_rate']:.3e}_x_sigma_{kwargs['x_sigma']:.3e}_z_sigma_{kwargs['z_sigma']:.3e}" - ) + path += f"per_{kwargs['data_err_rate']:.3e}_x_sigma_{kwargs['x_sigma']:.3e}_z_sigma_{kwargs['z_sigma']:.3e}" else: - path += ( - f"per_{kwargs['data_err_rate']:.3e}_ser_{kwargs['syndr_err_rate']:.3e}/" - ) + path += f"per_{kwargs['data_err_rate']:.3e}_ser_{kwargs['syndr_err_rate']:.3e}/" if not os.path.exists(path): os.makedirs(path, exist_ok=True) - if overwrite == False: + if overwrite is False: f_loc = path + f"id_{id}.json" while os.path.exists(f_loc): id += 1 @@ -217,21 +198,15 @@ def zip_dict(**kwargs): """ return (dict(zip(kwargs.keys(), values)) for values in zip(*kwargs.values())) + def _update_error_rates(success_cnt, runs, code_K): - """ - Calculates logical error rate, logical error rate error bar, word error rate, + """Calculates logical error rate, logical error rate error bar, word error rate, and word error rate error bar. """ logical_err_rate = 1.0 - (success_cnt / runs) - logical_err_rate_eb = np.sqrt( - (1 - logical_err_rate) * logical_err_rate / runs - ) + logical_err_rate_eb = np.sqrt((1 - logical_err_rate) * logical_err_rate / runs) word_error_rate = 1.0 - (1 - logical_err_rate) ** (1 / code_K) - word_error_rate_eb = ( - logical_err_rate_eb - * ((1 - logical_err_rate_eb) ** (1 / code_K - 1)) - / code_K - ) + word_error_rate_eb = logical_err_rate_eb * ((1 - logical_err_rate_eb) ** (1 / code_K - 1)) / code_K return ( logical_err_rate, logical_err_rate_eb, @@ -240,9 +215,8 @@ def _update_error_rates(success_cnt, runs, code_K): ) -def merge_datasets(datasets: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", +def merge_datasets(datasets: list[dict[str, Any]]) -> dict[str, Any]: + """Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. Args: @@ -268,12 +242,8 @@ def merge_datasets(datasets: List[Dict[str, Any]]) -> Dict[str, Any]: x_success_cnt = merged_data["x_success_cnt"] z_success_cnt = merged_data["z_success_cnt"] - x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates( - x_success_cnt, runs, merged_data["code_K"] - ) - z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates( - z_success_cnt, runs, merged_data["code_K"] - ) + x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates(x_success_cnt, runs, merged_data["code_K"]) + z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates(z_success_cnt, runs, merged_data["code_K"]) update_dict = { "x_ler": x_ler, @@ -291,9 +261,8 @@ def merge_datasets(datasets: List[Dict[str, Any]]) -> Dict[str, Any]: return merged_data -def _merge_datasets_x(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", +def _merge_datasets_x(_datasets: list[dict[str, Any]]) -> dict[str, Any]: + """Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. Args: @@ -302,7 +271,6 @@ def _merge_datasets_x(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: Returns: Dict[str, Any]: A dictionary containing the merged data. """ - datasets = _datasets.copy() if not datasets: @@ -334,9 +302,7 @@ def _merge_datasets_x(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: x_success_cnt = merged_data["x_success_cnt"] # z_success_cnt = merged_data["z_success_cnt"] - x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates( - x_success_cnt, runs, merged_data["code_K"] - ) + x_ler, x_ler_eb, x_wer, x_wer_eb = _update_error_rates(x_success_cnt, runs, merged_data["code_K"]) update_dict = { "x_ler": x_ler, @@ -349,9 +315,8 @@ def _merge_datasets_x(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: return merged_data -def _merge_datasets_z(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", +def _merge_datasets_z(_datasets: list[dict[str, Any]]) -> dict[str, Any]: + """Merges a list of dictionaries into a single dictionary. The values for the fields "nr_runs", "x_success_cnt" and "z_success_cnt" are extracted from each dictionary and added together. Args: @@ -360,7 +325,6 @@ def _merge_datasets_z(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: Returns: Dict[str, Any]: A dictionary containing the merged data. """ - datasets = _datasets.copy() # print("datasets", datasets) if not datasets: @@ -396,9 +360,7 @@ def _merge_datasets_z(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: # x_success_cnt = merged_data["x_success_cnt"] z_success_cnt = merged_data["z_success_cnt"] - z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates( - z_success_cnt, runs, merged_data["code_K"] - ) + z_ler, z_ler_eb, z_wer, z_wer_eb = _update_error_rates(z_success_cnt, runs, merged_data["code_K"]) update_dict = { "z_ler": z_ler, @@ -415,8 +377,7 @@ def _merge_datasets_z(_datasets: List[Dict[str, Any]]) -> Dict[str, Any]: def merge_json_files(input_path: str) -> None: - """ - Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + """Iterates through all subfolders in the input folder, loads all JSON files in each subfolder and merges them using the `merge_datasets` function. The resulting dictionaries are stored in a list and then dumped as a JSON file in the input folder. @@ -426,15 +387,15 @@ def merge_json_files(input_path: str) -> None: Returns: None """ - output_data: List[Dict[str, Any]] = [] + output_data: list[dict[str, Any]] = [] for folder_name in os.listdir(input_path): folder_path = os.path.join(input_path, folder_name) if os.path.isdir(folder_path): - data: List[Dict[str, Any]] = [] + data: list[dict[str, Any]] = [] for filename in os.listdir(folder_path): if filename.endswith(".json"): file_path = os.path.join(folder_path, filename) - with open(file_path, "r") as file: + with open(file_path) as file: try: json_data = json.load(file) data.append(json_data) @@ -447,15 +408,12 @@ def merge_json_files(input_path: str) -> None: # save output to parent directiory code_name = os.path.basename(os.path.normpath(input_path)) parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) - with open( - os.path.join(parent_dir, f"{code_name:s}.json"), "w" - ) as output_file: + with open(os.path.join(parent_dir, f"{code_name:s}.json"), "w") as output_file: json.dump(output_data, output_file, ensure_ascii=False, indent=4) def merge_json_files_x(input_path: str) -> None: - """ - Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + """Iterates through all subfolders in the input folder, loads all JSON files in each subfolder and merges them using the `merge_datasets` function. The resulting dictionaries are stored in a list and then dumped as a JSON file in the input folder. @@ -465,15 +423,15 @@ def merge_json_files_x(input_path: str) -> None: Returns: None """ - output_data: List[Dict[str, Any]] = [] + output_data: list[dict[str, Any]] = [] for folder_name in os.listdir(input_path): folder_path = os.path.join(input_path, folder_name) if os.path.isdir(folder_path): - data: List[Dict[str, Any]] = [] + data: list[dict[str, Any]] = [] for filename in os.listdir(folder_path): if filename.endswith(".json"): file_path = os.path.join(folder_path, filename) - with open(file_path, "r") as file: + with open(file_path) as file: try: json_data = json.load(file) data.append(json_data) @@ -486,15 +444,12 @@ def merge_json_files_x(input_path: str) -> None: # save output to parent directiory code_name = os.path.basename(os.path.normpath(input_path)) parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) - with open( - os.path.join(parent_dir, f"{code_name:s}.json"), "w" - ) as output_file: + with open(os.path.join(parent_dir, f"{code_name:s}.json"), "w") as output_file: json.dump(output_data, output_file, ensure_ascii=False, indent=4) def merge_json_files_z(input_path: str) -> None: - """ - Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + """Iterates through all subfolders in the input folder, loads all JSON files in each subfolder and merges them using the `merge_datasets` function. The resulting dictionaries are stored in a list and then dumped as a JSON file in the input folder. @@ -504,15 +459,15 @@ def merge_json_files_z(input_path: str) -> None: Returns: None """ - output_data: List[Dict[str, Any]] = [] + output_data: list[dict[str, Any]] = [] for folder_name in os.listdir(input_path): folder_path = os.path.join(input_path, folder_name) if os.path.isdir(folder_path): - data: List[Dict[str, Any]] = [] + data: list[dict[str, Any]] = [] for filename in os.listdir(folder_path): if filename.endswith(".json"): file_path = os.path.join(folder_path, filename) - with open(file_path, "r") as file: + with open(file_path) as file: try: json_data = json.load(file) data.append(json_data) @@ -525,15 +480,12 @@ def merge_json_files_z(input_path: str) -> None: # save output to parent directiory code_name = os.path.basename(os.path.normpath(input_path)) parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) - with open( - os.path.join(parent_dir, f"{code_name:s}.json"), "w" - ) as output_file: + with open(os.path.join(parent_dir, f"{code_name:s}.json"), "w") as output_file: json.dump(output_data, output_file, ensure_ascii=False, indent=4) def merge_json_files_xz(input_path: str) -> None: - """ - Iterates through all subfolders in the input folder, loads all JSON files in each subfolder + """Iterates through all subfolders in the input folder, loads all JSON files in each subfolder and merges them using the `merge_datasets` function. The resulting dictionaries are stored in a list and then dumped as a JSON file in the input folder. @@ -543,15 +495,15 @@ def merge_json_files_xz(input_path: str) -> None: Returns: None """ - output_data: List[Dict[str, Any]] = [] + output_data: list[dict[str, Any]] = [] for folder_name in os.listdir(input_path): folder_path = os.path.join(input_path, folder_name) if os.path.isdir(folder_path): - data: List[Dict[str, Any]] = [] + data: list[dict[str, Any]] = [] for filename in os.listdir(folder_path): if filename.endswith(".json"): file_path = os.path.join(folder_path, filename) - with open(file_path, "r") as file: + with open(file_path) as file: try: json_data = json.load(file) data.append(json_data) @@ -568,19 +520,15 @@ def merge_json_files_xz(input_path: str) -> None: # save output to parent directiory code_name = os.path.basename(os.path.normpath(input_path)) parent_dir = os.path.abspath(os.path.join(input_path, os.pardir)) - with open( - os.path.join(parent_dir, f"{code_name:s}.json"), "w" - ) as output_file: + with open(os.path.join(parent_dir, f"{code_name:s}.json"), "w") as output_file: json.dump(output_data, output_file, ensure_ascii=False, indent=4) -def _combine_xz_data(xdata: Union[dict, None], zdata: Union[dict, None]) -> dict: - """ - Combine the x and z data into a single dictionary. +def _combine_xz_data(xdata: dict | None, zdata: dict | None) -> dict: + """Combine the x and z data into a single dictionary. Before doing that, rename "runs" in each dictionary to "x_runs" and "z_runs" respectively. """ - if xdata and zdata: # print(xdata) xdata["x_runs"] = xdata.pop("nr_runs") diff --git a/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py b/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py index db1ee287..230667e9 100644 --- a/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py +++ b/src/mqt/qecc/analog-information-decoding/utils/simulation_utils.py @@ -1,4 +1,8 @@ +from __future__ import annotations + import json +import warnings + import numpy as np from ldpc.mod2 import rank from numba import njit @@ -6,11 +10,8 @@ NumbaDeprecationWarning, NumbaPendingDeprecationWarning, ) -import warnings -from scipy.sparse import coo_matrix, csr_matrix -from scipy.special import erfcinv, erfc - -from utils.data_utils import replace_inf, calculate_error_rates, BpParams +from scipy.special import erfc, erfcinv +from utils.data_utils import BpParams, calculate_error_rates, replace_inf warnings.simplefilter("ignore", category=NumbaDeprecationWarning) warnings.simplefilter("ignore", category=NumbaPendingDeprecationWarning) @@ -18,9 +19,7 @@ @njit def set_seed(value): - """ - The approriate way to set seeds when numba is used. - """ + """The approriate way to set seeds when numba is used.""" np.random.seed(value) @@ -64,10 +63,7 @@ def check_logical_err_h(check_matrix, original_err, decoded_estimate): rank_htr = rank(htr) - if rank_ht < rank_htr: - return True - else: - return False + return rank_ht < rank_htr # L is a numpy array, residual_err is vector s.t. dimensions match @@ -76,8 +72,9 @@ def check_logical_err_h(check_matrix, original_err, decoded_estimate): # an Z residual is a logical iff it commutes with at least one Z logical # Hence, L must be of same type as H and of different type than residual_err def is_logical_err(L, residual_err): - """checks if the residual error is a logical error - :returns: True if its logical error, False otherwise (is a stabilizer)""" + """Checks if the residual error is a logical error + :returns: True if its logical error, False otherwise (is a stabilizer). + """ l_check = (L @ residual_err) % 2 return l_check.any() # check all zeros @@ -87,8 +84,7 @@ def is_logical_err(L, residual_err): # channel_probs = [x,y,z], residual_err = [x,z] @njit def generate_err(N, channel_probs, residual_err): - """ - Computes error vector with X and Z part given channel probabilities and residual error. + """Computes error vector with X and Z part given channel probabilities and residual error. Assumes that residual error has two equally sized parts. """ error_x = residual_err[0] @@ -102,16 +98,12 @@ def generate_err(N, channel_probs, residual_err): for i in range(N): rand = np.random.random() # this returns a random float in [0,1) # e.g. if err channel is p = 0.3, then an error will be applied if rand < p - if ( - rand < channel_probs_z[i] - ): # if probability for z error high enough, rand < p, apply + if rand < channel_probs_z[i]: # if probability for z error high enough, rand < p, apply # if there is a z error on the i-th bit, flip the bit but take residual error into account # nothing on x part - probably redundant anyways error_z[i] = (residual_err_z[i] + 1) % 2 elif ( # if p = 0.3 then 0.3 <= rand < 0.6 is the same sized interval as rand < 0.3 - channel_probs_z[i] - <= rand - < (channel_probs_z[i] + channel_probs_x[i]) + channel_probs_z[i] <= rand < (channel_probs_z[i] + channel_probs_x[i]) ): # X error error_x[i] = (residual_err_x[i] + 1) % 2 @@ -129,35 +121,28 @@ def generate_err(N, channel_probs, residual_err): @njit def get_analog_llr(analog_syndrome: np.ndarray, sigma: float) -> np.ndarray: - """Computes analog LLRs given analog syndrome and sigma""" + """Computes analog LLRs given analog syndrome and sigma.""" return (2 * analog_syndrome) / (sigma**2) def get_sigma_from_syndr_er(ser: float) -> float: + """For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. + :return: sigma. """ - For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. - :return: sigma - """ - return ( - 1 / np.sqrt(2) / (erfcinv(2 * ser)) - ) # see Eq. cref{eq:perr-to-sigma} in our paper + return 1 / np.sqrt(2) / (erfcinv(2 * ser)) # see Eq. cref{eq:perr-to-sigma} in our paper def get_error_rate_from_sigma(sigma: float) -> float: + """For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. + :return: sigma. """ - For analog Cat syndrome noise we need to convert the syndrome error model as described in the paper. - :return: sigma - """ - return 0.5 * erfc( - 1 / np.sqrt(2 * sigma**2) - ) # see Eq. cref{eq:perr-to-sigma} in our paper + return 0.5 * erfc(1 / np.sqrt(2 * sigma**2)) # see Eq. cref{eq:perr-to-sigma} in our paper @njit def get_virtual_check_init_vals(noisy_syndr, sigma: float): - """ - Computes a vector of values v_i from the noisy syndrome bits y_i s.t. BP initializes the LLRs l_i of the analog nodes with the - analog info values (see paper section). v_i := 1/(e^{y_i}+1) + """Computes a vector of values v_i from the noisy syndrome bits y_i s.t. BP initializes the LLRs l_i of the analog nodes with the + analog info values (see paper section). v_i := 1/(e^{y_i}+1). """ llrs = get_analog_llr(noisy_syndr, sigma) return 1 / (np.exp(np.abs(llrs)) + 1) @@ -167,7 +152,7 @@ def get_virtual_check_init_vals(noisy_syndr, sigma: float): def generate_syndr_err(channel_probs): error = np.zeros_like(channel_probs, dtype=np.int32) - for i, prob in np.ndenumerate(channel_probs): + for i, _prob in np.ndenumerate(channel_probs): rand = np.random.random() if rand < channel_probs[i]: @@ -177,12 +162,10 @@ def generate_syndr_err(channel_probs): # @njit -def get_noisy_analog_syndrome( - perfect_syndr: np.ndarray, sigma: float -) -> np.array: +def get_noisy_analog_syndrome(perfect_syndr: np.ndarray, sigma: float) -> np.array: """Generate noisy analog syndrome vector given the perfect syndrome and standard deviation sigma (~ noise strength) - Assumes perfect_syndr has entries in {0,1}""" - + Assumes perfect_syndr has entries in {0,1}. + """ # compute signed syndrome: 1 = check satisfied, -1 = check violated sgns = np.where( perfect_syndr == 0, @@ -191,8 +174,7 @@ def get_noisy_analog_syndrome( ) # sample from Gaussian with zero mean and sigma std. dev: ~N(0, sigma_sq) - res = np.random.normal(sgns, sigma, len(sgns)) - return res + return np.random.normal(sgns, sigma, len(sgns)) @njit @@ -232,7 +214,7 @@ def build_single_stage_pcm(H, M) -> np.ndarray: @njit def get_signed_from_binary(binary_syndrome: np.ndarray) -> np.ndarray: - """Maps the binary vector with {0,1} entries to a vector with {-1,1} entries""" + """Maps the binary vector with {0,1} entries to a vector with {-1,1} entries.""" signed_syndr = [] for j in range(len(binary_syndrome)): signed_syndr.append(1 if binary_syndrome[j] == 0 else -1) @@ -255,12 +237,10 @@ def save_results( outfile: str, code_params, err_side: str = "X", - bp_iterations: int = None, + bp_iterations: int | None = None, bp_params: BpParams = None, ) -> dict: - ler, ler_eb, wer, wer_eb = calculate_error_rates( - success_cnt, nr_runs, code_params - ) + ler, ler_eb, wer, wer_eb = calculate_error_rates(success_cnt, nr_runs, code_params) output = { "code_K": code_params["k"], @@ -287,4 +267,4 @@ def save_results( indent=4, default=lambda o: o.__dict__, ) - return output \ No newline at end of file + return output From 43c18bb8ebfcf8610b239747f29dddbe4ff6bb36 Mon Sep 17 00:00:00 2001 From: lucas Date: Thu, 2 Nov 2023 22:57:54 +0100 Subject: [PATCH 003/115] add link to raw simulation data --- .../results/raw-data-reference.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt diff --git a/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt b/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt new file mode 100644 index 00000000..c65b6695 --- /dev/null +++ b/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt @@ -0,0 +1,5 @@ + +The raw simulation data used in the paper is made publicly available via Zenodo: https://doi.org/10.5281/zenodo.10067708 +(https://zenodo.org/records/10067708) + +This will be referenced in a final version of the paper directly. \ No newline at end of file From b2c92dddd0cb5edc40b6be06bc359b47c8a0c38c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 21:58:50 +0000 Subject: [PATCH 004/115] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analog-information-decoding/results/raw-data-reference.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt b/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt index c65b6695..90bf1e25 100644 --- a/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt +++ b/src/mqt/qecc/analog-information-decoding/results/raw-data-reference.txt @@ -2,4 +2,4 @@ The raw simulation data used in the paper is made publicly available via Zenodo: https://doi.org/10.5281/zenodo.10067708 (https://zenodo.org/records/10067708) -This will be referenced in a final version of the paper directly. \ No newline at end of file +This will be referenced in a final version of the paper directly. From 7836157ab9f04a767c0de40598322a1d1abfe4c3 Mon Sep 17 00:00:00 2001 From: lucas Date: Fri, 3 Nov 2023 08:00:50 +0100 Subject: [PATCH 005/115] add bp parameter plots --- .../A comparison of BPOSD Settings.pdf | Bin 0 -> 366626 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/mqt/qecc/analog-information-decoding/results/bp-parameter-tests/A comparison of BPOSD Settings.pdf diff --git a/src/mqt/qecc/analog-information-decoding/results/bp-parameter-tests/A comparison of BPOSD Settings.pdf b/src/mqt/qecc/analog-information-decoding/results/bp-parameter-tests/A comparison of BPOSD Settings.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5b18c01d0dcbc0906bf34a7e18cc76a2bbcd723d GIT binary patch literal 366626 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

98iestP*XO^rq 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

}|x_3*EXujo{AbS-4jbPb)Sb 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⁡>EX4NkHiD| zH+nXi1MNl6EqcMw5RM}Jwo}YmzI1uDHA}x$CE?kG46dk?-t?-8#!n8LmF75+N1Vg; z39wy}qE-T~1RZ2gq;}NLd3v(ipa(7E1d=OCG&{5_>DL2uN` z$`Bo5%?S$5AT72&<})$W3nl{snaNLf-pPVKA6A;Fw3{Ct zVHgi(EbP%kR+po>l@(pYR8WYeH#)~%fIIw2zJ4GnLCtHk-YE!};oD9^f&9wE zR7rqNsVH(!c_P^RJyX{PA6AQAz|rY*vc67DVd78n4v#Q_eeFsJC|ARw0_gjI2|Ss= zLSbAbs(LeimyB09D>Gg3W!^w+FrE2?7I$~eE4h}!#LXQkrT7kn5fA*y_@&wOoJH17 zKWsGl6!|G@OQeGF9NEaWI7&iGCzdtxptdg&=ud0U=f?QHZZ+CInDfyGUQRkVQPE|u z`R%j^rHoWGLO}!B2q3Z#$Hrm>Gg@-{yF3nu4vao=*F=V+NtxZjo3X*g=D&GduZqU) z6W>_%+9t84t_;i84w3U+{S-=@A3fj8us6D<^2DBg12YK$U$Y0m==e2E^}(Pv@8`zq zsAwbYN}fDR4fwip1npPpGISYWo8;1)82(;9_%bR+;L2T@(jrJ}yILkL*mivT!01ht z2}*iDGsZoT)Xs8KN$i!a;S$m|F&lMToQNE|V|VKFzgw;<)s#ooK}B>#aB>CmR zL8iii3>Dz0K#1B}4z~rG6deV+G}3jz7ew`D_ThdSnLy`CAroa`agdM6hMn$n+VN8l zAb)ilnXW(8{zAgPmCq0r4Ko*(d@-{Vqf0rrGDkL>R;_f@|Ey0_nlekZ-#J-6Rv=9? zeb6dn6Zn(|#q9s0f6WG#18Zs_JR*PCVP$&)u0t-slvV&^600M z-mxHFmQ>(X_hD1zMfSI?PV0zi+1+gDw}R6mbLJ%fKbG1b)#_w)-mAJ@0ezBqb6617 zggS42P|>Rj=3_^#G>tt+!r@F& z@{3+MU%SGY3O>EvE#LpF_r`%3_Vsi4yLN99g06bMXNICypAvclO4+<{Fs>brWzelD zTx^5vpRskcR&%O>@;*Jj5M~&dzQ-WeHeIn$h1 zHv;57mavk&0dsnE_?!daY8cd#vq#qx$o%t1n?mC>uo&KM_*PnnIMd;0KW1O?+c_oi zwa~TB1ddEE*iJ6EUc;*LzYYY^!?X%8P0fV3j0x}&i2c~>(}|CkK5Ba=r*g*YBVs?uzvu zlj?Y(#WNi6qeV^`W0^K}CW|?>+p<#87E`W8q@)HLQP ztq!!n$%!;PDG_z${eHhWThZ5qlVD%p z4OAnJFG$Y_-@p3ehSz(Sspq%IZi(gd?pK8qI>CFQn${mh2VeS9tcqUI<75$Xnn+t& z-Gt+gf05Pz%hz=jDx`~3Aw`mzuUZ9F`@78Y{8~19q>7=R8rodfE~RekuFTfj9RoWY zUt-sOabW~~CVyTbjq_WR-cHRy2lmDW^hPEp(~3(e28(C&7d2cCKCRY$AdQw2N=Zx# z?)@Ny=9&0}Tu*heB*dGf7B>?-KV_VASq8Cr3q?7@|Umo zDiQYkh#3OK{|LPI{1Xh}d?$oe1~i9Pv9^w{~+eQM=F?}o&bNC^C3!I z{Dy@6o`C@=i?Z^~x+N`&h2F3I%CAU+XJt5@K{%H?V|+jU5G4uI?_kscfy?8tTgWDa zb1X2H_WHGmN$dk3a{PCW94mVvw-{!8&mV8&AjJ+E%HxPaC1F@6HI*riOo1}j9by^nuNj!7s^M$t29eihD>Cc84rfj?-4HB!0GqqmeKoiYci zWinV1flerH_|Rgq^i}>%d!6_0mGfSix{)qYZP6PoK-XS-`$ezQY3l*rm-<*z9u4XB z2V2J^f)MMqAGjRm#>;A>Nu+S(@3&h^tg3Q~Rk?yocd|C&IYo)7<^%>FWG+O(r^#D@ zL8gup9Mc135G0qIRcRQeWSaG0kBId3oQj&@bfU;pzSOvu-4&1T10Zccp^h6rO{m;v zQS4n{&S~4cX<0MFb7t(cJLzv z#!v3pD&r;;R1m(?JrC1AbO;0&g~zn}cYJ{PB+no}n=DS@({&VUy;o;~pS1~hB*qEQ zrm?uq>lfzL0x_bCzH#S}m<)_0MB?HSk_um}=MM>K{U~mo4ANK|G9R;Rw}Wq0RC?Qa zjMv)bnk@ami#>x3o91z2QLZ>mw316#20uCWi$xn*@r!ihBGEjzm2+{b<|4Dz6VXO; zG#%CKw3pip5>#aga7n=${QZpQ4pu0>+2Lk# z1|>B%lDU=~OK2E%LBC6(gW(qF77K1JR}xneHozunf7D*nO@*U{db%!M z{eE-9omE2aQtzV`HfOFUQT9RMrPXIc3oRq&OE#-$&NBxZSw(aJJopM}_F3x?G(uN< z>OM4FOc4=>d=>8K&ZYD%@2=53H&eQX8##4w)En~yji_PH84~QrpZu!!Cc|o}KX5@W z+4pjoard56;kA4HT--pm-+3=5MZtua!R$g5jJueh_hIAYBSk+1nYZ`kMudzTgyiGL zSr+JqV_trG6#{rW)kRmh*cV?Wr~-b9LLi(5(E=7 z6r*}{s#rNY0-rI<#RuzPy-V2R#o^)M{OLWD{=*kLiKV5bN8H-`fPao#;Kj_8|Ds!& z7y*y~TI$_C@DN2!{vgVAY}_Nm2Z-=dk(;xxO)TY*fe8EzVP7FWJOToZc6Z0Xp?4tX zf<(y;jq%MVn}y@5;8qDJXvU1DMY=UHHQj^_zw%E6kv~N1TOw*y7m%IXhVndpFVx!V zj!iEu+3+J9#i$ENZa9p4Ee(3?Y6NR+KEYX`@#HNWIc#JBuu_~pdz z`vEHQw;V5_5dyqdaMXwVB3#O{vOSZ)Gc)~s5*o`5KiM{BO1z!2y30|_;X0J9(?N=% z?bc-1*md{8Yndyw)UPYB!*`-#fRtV8;1cOXmYjCDD}UVK`+;g63@cd<1c^H^O9jhR zSKni!wdc5gD8sau{>Jffjyh~yY{~% zpV~YJU?Q(`l`!_uQ3r2H3i{VqPVeVg4C#^7IfSbu;dxO+a~} zdR6>WHVQnAbhWPpRocM5d;1dQ(B!;4KgH>^g(dwLmlu~Od)DSLCZ-N@C zyX4!{XX&gJypn%I49+J$yb5`Fd%j!s5{P*s%SA3RqmD|uCr`|tBaj|Xhd+&> z-a`{;R&yR$%qR~J5UIoInnC>pd03hh(ZMC&&D6+iLt89ef&RPQcC@urS(>@Ij7eNDYMDeE&Qfl!dN5Ic4zv z`ib>i5;RlnDK2kHKM(#N67h$@WSS8aOsSZaZIw@99tGbF&HBrH#}d~ zCo}b!&0-4OY=G`)4p9HP6ZQ0!?1-+joJjv_JDY!v5G86ft7lW=Z`-o*1u&$^d5i1HFXHb8ijdlYCrl=Egy@G zjXeqYT)@9mox*vYCg`x?_nW+A0wMO@D5clvgy~1~Bc^QG(l2&e;Wp#f~{6 zJioMiII~n8^c)J1*T6f0ag?G`NEImsMN$PRg&ZiQ{2rGkm}IfM#pm{!C*r0X$f!m& zs)RelUe`;q>~y^p;vwB=gIJ3|#_D|EHZ$8W@8=<6^qIrPDn!=KeSN74B7l+$k<83~ zqvv4?oZ}uI9vW9g!|OCMnjydd75Y}SNFfct0)GL-tS|_5rNq7Pz5Lgn!WSNMT@A{= z>Kfc&=gH(~`L7}gE`D73ywwlybiUC8^x*zr?>w*vHY1dLbPfLW=*8+EKiAJ3=yzx& z1&Z4c!s*a2Fd+~H1%-YwRy>*1FabXFq!1MH$-yO|S4c{+vB8bDUwdP(47TlAo}tl+ zc_x7(2C($cW?v)d2J|-`4nz_vD=Q@n_MrSmgfm-OB!>4mysmBLM7amE zs@aA?Z0DNqpWhaT8|o!06p7{tM#jb`JyM^HqE!45-alM3!m~Cbgol=^AQ54rop1el z*8(O&u3x#=Q{-b8Hwmvv2#}W`QF;?Z6lhYwR{((UYmg6tSt^RPjY;T+d z)syzt_T)!*5G_0~c=qZ_7zD0g0`{AM?F1;Zv3(|v16a;qz|p$dhwvVo8iquCtSz?W zk!tGu7s2nfCNJyS-t#jr%^J*tUqVic^(a73P>BL`Jkf4 z)Cf|Jy7^#?rtnck|6%niGW3Bn>0TX$S+O@7j_-IG)(vQ=z`L3d8|&vd1|lhFX=tWl zGSnUoPX2BGnbq5NiL|RI3*1Won+y3r`JW!oAK>F3jZgn+Q#Y9Ke~{ap4q^Wb+bPHY zpSDvjZdUI9h3)iTtX@okix>{l5RUi>CM{OVQPfn_Jh%vYDZKA^7?KhqH1wGU8!Cn) zvYN>`Y81aOzBR7A1-OwZQ+KP+Ki;9a4%7yRJ-oO1_b~ImAuGb}3^>FrQ z%=G^?qxUg2;Vss|TF2dcP=h;=0}qnnxSfuikK(0!W){7WR0Nd6?9ryZ`#Hsasg`DpU>rsnqzF8J^QqnE zw`Z0Sb0)7N+*W1NS~`b0Mo>E-)D~NX0B0?}@iuTq#X_(^YRU^xr3tUM{hIt09O)?l z9Fs?aQm&JHI=F1YU9K61p!EfKW_gv_#A4Iby88;P=(woFNq_~N{2Vw+fSKq3T(MBQ z*`jgKd`}Jz{|A^A$azme9@|u!0GfMu7PIFJ(0PS{eGJ@%G@a=k=5#|qkQ7Mgw8R;O z4G0-yc>~6^!I-Cv78m7KApmh7zYGlVo9nGEr@pA7BB@@p5tz8~wbcs`*HqhnkCHm5<10(G9RqPCbYp_%?>tLLV{cMus3S ze;R(P>(6hN@#tK51@z7VF=0*4z%j`nuX?CS1?ZW@+@-SroX9|R06s!dz_AKGG64Vh z{$ha8_wIzJ0(g;c0*VhP+sxc|;-pzCI~lghQ$D8dnX_fpw|qmkIHy3 zs>T}0CIn19VlfsFck%be0c1KkJ^eW@PY}2iIh5TuFpA+1FMq>f12VnwubWg@Cx`fR zmGn)3-ZY{1Pkte^L@?gXEbEx-EEDtz_Bk8?u*Mifr)PiSj~e+ZyLtmxu?SZtRl50JF?Z&~Jmg4FN&)B4q9;s?yTT zs{h=Qf4%U0u&`1%)oA?#Hj+y8wG8?HBcNL(56d829)M1Zz}qsZ`xv6Yi(tV}Pv~N( znWt^neswZm{w4eG$3!9krjmB;3CHGPozOFx#rL>MTC~S}=q|Cb>p`*`6vltCxCRUQ zGTLYj)3xO^o0Ys|aMf@c^=*;6 z%+KTKB@H=_GkA39kx>kVGdsB9U&aOhNdT~IeXo-SoMahPueLrqwNCHbPr$e*lMsR1 z^|qJS6i>dk6*aBJZan=u`?^ByqKt*##D!*JU=3lSCy1~Lc(&1o^tYZ!6+k5tx6{%< zJW;>e6M?~KYumW=o_yBaMJ6$&GYLH{0a-Z9K@#HlC9IGfBqc+LV$1j5=2(YF; z&eniu&+TM+p6xwA?p~&J-80{{?!?QJ{u{abc80In=VX~hSpQ*7=UyzE*VAO^06S9e z)2;Xt4n8rDKq+pk>mOX-{Zn4}Pago%oJBQ4jDu7&i2tiQo4VF z=|6Z~0RgY?+eau=+|+uM9PI376PdE*Ly--?Y4V^!Z8mpQ>*A!r-vtTf1HOh}r0cd% z?cfC0`9u{rf{%)LCjqs}GBA`!0f_~QfN+L_48$eGu~k)31qk-mVY z(kW+wEF3wgA9h#2FL-!v1vb-=C-W}jsQl^F`0>DS_K_bKyVQ&qHJ`|K7|Hpr+GOZh z7p~MMGu*GrxKo4o#af3Kj~-fS8fiz6v^sX&d&U(#_s!rhy3S9?rWq;(MVArjqzr5H z-%U#bKKFJQHWdP9r_Li=*T5eR(ZU*GB3n4y4o~DQh!48)GEXN?<^y?blJcWIupp z4{LVaU!tFqj{Gbu{{;6JuiIwWhxEwT{TrU}sI*pT)k*c^!)!f?uWG?lru`;Uf`f)c zI!r~qs^F*t-hE)|?IHWQvH&@}GS5f*wf?5|`WuTO+47Y9-CF`J}mEg`|^h- z);($)nY6MrI!3&-#}zl02|J=66E(q7`i6B=BN2TI45QdpNg+}nRjcBtq(^yTo~c^p zx4X3rD3Cz@DwH8sIlfc=pG-G?UfjD|jE41OJ4>Oj$-?MmE!}p}f3kQ4Y zu!-Sf)CQKYfP1st5PFOuHq_0VxZr?tf`k$9Q2eeTE9)trX3#kpf5 z{E&MdK%CQ-N0viY1cQ^jgzi8dk>wpJTA=fn$6U1o-`dW@q6`N@OpCI7?fBhykXO;a zDH%TRu+Ytwhy#oRu*AutB}cY9D0wZJrfk}1+zd9c-ngUV5m3BWSYay-b}92!$(KuJ zi~XI#H71in;U$whR-HX){)U;0QvWqTF2z0(9xOA+PfOx3JV`*G1qSkPx2*&UZdIw%uomA+g8)hNlzt*@kS2vVhgkrR$$cA zG?_5>H%hp*07f3&FSOTG`34qmeSiPB)W9L)qkrKf-DHrbkS+99=<;JV`k@zrRY2O; zrr#=sTO)@uY~A!eZLWVNL(g)Z5IIbR=XqVjpN9SVPAeg9_89f!Zcw6;yz0XEcCqv4Ywnu-Yu%6T9BX%>+B38W{?I`4bCFK6MP2aG(?er z+6Z;05%eO5A@uBLt$~?0;k+orPGzt=$a3LZkVCk%e0=B*6=S)ya(?j+MMx*c7(2hE zybP<21yDU)7Q2!3Z8H=1cL-H5@*;x+l+3!#)$K0S=i2%pdt^x3T+2Xi^kDFhl4pr- zcrtTAoP$#VTPbpDp3&^=xtgTuH>{xL0H!C*6isDj-FVV6hoW!^89MyEacH!hfdghD zI)9nF_xA}E{=|_zD($SB1ZJi7uko2Jg*?2Q2blMH9qtoyf_?It;uAigD8?fNuO<7t z!jZbaCync-vO)5zJr@AX3a~oHWk_hSBI8|#Tt*52fxwTZp~M!6AH^cPP(B4sp&BZ& zG;NtlVH%f^-}Ks8yaCEu_$osb&O&x`R#_PT3ME-zt>_}4ct^j}n{OME!DCNA0r|m7 zMw!izGbwNt^&iw-nXe7sI-qq{_xxG<78GBvf8VuXJiG3fR%H2oWL=8JB#~@(W0=GA zc?nP;!>At_T=26){Z)!Xx2s04rRAasX=8Mxk%vj zvs^sNOnoctlNf#MJPg7qy52Iw^H53yl#gJVn0HM60bi62${)K8$)u3{H(a;J#^4~e zIrFvKXXd71MA{5r*#K=nmC5vLmtpuaNkemh^Fc|uCi~?7f4dG zH0f|~=uCV}d09H-vFaJIt}81@M#@(<*1dDpYR2L;amux$c;sk#=Hr?{@#f2^sV~1E zC!LeY8*G4=6?;FmWG1406vbPDm(GHsKBFg6cP>!?4g0LnZ~5PFb$9o_OZ^+GE5b#f ze26`(NS;|kXaNWHDNr6aPizUnc*-wP43)|L(2NBrTNY<(MDj@*T=46jF(Xn$gE%$5 zuPTR8P}P;FW%Rd!W=6*~1K84N1EONJ;%to|3x+D&?6Yq8u|Qe440HuGGAIBV=-)!2 zz0~#Oh4moeFTYw=ByD|eL0<<_&E4U+vVow zqV#KHKL1?tRFJ|;E>kM!FX|lUg$D1^sp8q%DQ+Yv9YF3x>3(MEC?dr^t%+7UmKaCL zRjQ#e8!iPup4K-gWj#SJt)2OKdE=sb3{8-%lp|50jD#}9JRCsG&ita;(U~>dQR3$t zaA=)(JMl5EE9&C5GAOMVd{Z&DT4|o_;&_yTh0mX~U~3nE$;chmev^eploq&W-Mecl z7^p2G3)s71PhaoP99e`162d+5cb{gzbbF!ifh1tc)KylmFy#g)z6`Z2VwVAO-@n

l&8KvS*@PgRmN=1WULnGsg|niF@FeuW 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

0uG-D*`x@R{aLREFE=CV!% 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 x@Ejl3PR*D! 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