├── tests ├── __init__.py └── test_decoder.py ├── docs ├── .readthedocs.yaml ├── .gitignore ├── requirements.txt ├── python_api.rst ├── install.rst ├── index.rst ├── Makefile ├── make.bat ├── development.rst ├── custom_lattice.rst ├── usage.rst └── conf.py ├── UnionFindPy ├── cpp │ ├── .gitignore │ ├── externals │ │ └── .gitignore │ ├── Makefile │ ├── tests │ │ ├── CMakeLists.txt │ │ └── test_LatticeFromParity.cpp │ ├── build_utils │ │ ├── format_cpp.sh │ │ └── cpp_files.py │ ├── .clang-tidy │ ├── examples │ │ ├── typedefs.hpp │ │ ├── runner_utils.hpp │ │ ├── error_utils.cpp │ │ ├── LazyDecoder.hpp │ │ ├── Lattice2D.hpp │ │ ├── CMakeLists.txt │ │ ├── runner_utils.cpp │ │ ├── LatticeCubic.hpp │ │ ├── toric_utils.hpp │ │ ├── run_uf_2d_bitflip.cpp │ │ ├── error_utils.hpp │ │ ├── run_uf_3d_bitflip.cpp │ │ └── toric_utils.cpp │ ├── include │ │ ├── utility.hpp │ │ ├── LatticeConcept.hpp │ │ ├── RootManager.hpp │ │ ├── LatticeFromParity.hpp │ │ └── Decoder.hpp │ ├── src │ │ └── utility.cpp │ ├── CMakeLists.txt │ ├── binding │ │ └── UnionFindPy.cpp │ └── .clang-format ├── __init__.py ├── _version.py └── decoder.py ├── requirements.txt ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── Makefile ├── CMakeLists.txt ├── examples ├── utils.py ├── plot_toric_threshold.py ├── bench_decoder.py └── plot_toric_threshold_noisy.py ├── .github └── workflows │ ├── wheel_macos_arm64.yml │ ├── wheel_win_x86_64.yml │ └── wheel_macos_x86_64.yml ├── README.rst ├── setup.py ├── .clang-format └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ninja 2 | numpy 3 | scipy 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _doxygen/ 3 | api/ 4 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/externals/.gitignore: -------------------------------------------------------------------------------- 1 | catch.hpp 2 | nlohmann/* 3 | -------------------------------------------------------------------------------- /UnionFindPy/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | from .decoder import Decoder 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | exhale 2 | breathe>=4.13.0 3 | sphinx==4.3.2 4 | docutils==0.17.1 5 | sphinx_rtd_theme 6 | -------------------------------------------------------------------------------- /docs/python_api.rst: -------------------------------------------------------------------------------- 1 | Python API 2 | ======================== 3 | 4 | .. automodule:: UnionFindPy.decoder 5 | :members: 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ycm_extra_conf.py 2 | release* 3 | debug* 4 | *.so 5 | *__pycache__* 6 | rel_with_dbg_info/ 7 | build/* 8 | UnionFindPy.egg-info 9 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | =============== 3 | 4 | .. include:: ../README.rst 5 | :start-after: installation-start-inclusion-marker-do-not-remove 6 | :end-before: installation-end-inclusion-marker-do-not-remove 7 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help clean format 2 | 3 | help: 4 | @echo "Please use \`make \` where is one of" 5 | @echo " clean" 6 | @echo " format" 7 | 8 | clean: 9 | rm -rf ./build/* externals/catch.hpp externals/nlohmann 10 | 11 | format: 12 | @./build_utils/format_cpp.sh 13 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(UnionFindCPP_tests) 2 | 3 | find_package(Eigen3 3.3 REQUIRED NO_MODULE) 4 | 5 | add_executable(test_LatticeFromParity "test_LatticeFromParity.cpp") 6 | target_link_libraries(test_LatticeFromParity union_find_cpp_dependency Eigen3::Eigen) 7 | add_test(NAME test_LatticeFromParity 8 | COMMAND test_LatticeFromParity) 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pybind11"] 2 | path = UnionFindPy/cpp/externals/pybind11 3 | url = https://github.com/pybind/pybind11.git 4 | [submodule "robin-map"] 5 | path = UnionFindPy/cpp/externals/robin-map 6 | url = https://github.com/Tessil/robin-map.git 7 | [submodule "UnionFindPy/cpp/examples/fmt"] 8 | path = UnionFindPy/cpp/examples/fmt 9 | url = https://github.com/fmtlib/fmt.git 10 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/build_utils/format_cpp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" # build_utils 3 | PROJECT_SOURCE_DIR="$(dirname "${SCRIPT_DIR}")" 4 | FILE_ARR=( $("${PROJECT_SOURCE_DIR}/build_utils/cpp_files.py" --include-examples --include-tests | jq -r '.[] | .name') ) 5 | echo "Formatiing ${FILE_ARR[@]}" 6 | clang-format-12 -i ${FILE_ARR[@]/%/} 7 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | sphinx: 4 | configuration: docs/conf.py 5 | 6 | python: 7 | install: 8 | - requirements: docs/requirements.txt 9 | - requirements: requirements.txt 10 | - method: pip 11 | path: . 12 | system_packages: true 13 | 14 | build: 15 | os: ubuntu-20.04 16 | tools: 17 | python: "3.8" 18 | apt_packages: 19 | - cmake 20 | - build-essential 21 | - g++-10 22 | 23 | submodules: 24 | include: all 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean format doc 2 | 3 | help: 4 | @echo "Please use \`make \` where is one of" 5 | @echo " clean" 6 | @echo " format-cpp" 7 | @echo " doc" 8 | 9 | clean: 10 | rm -rf ./build/* ./UnionFindPy.egg-info UnionFindPy/_union_find_py.cpython-* 11 | find . -type d -name __pycache__ -exec rm -r {} \+ 12 | $(MAKE) -C ./docs clean 13 | $(MAKE) -C ./UnionFindPy/cpp clean 14 | 15 | format-cpp: 16 | $(MAKE) -C ./UnionFindPy/cpp format 17 | 18 | doc: 19 | $(MAKE) -C ./docs html 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. UnionFind documentation master file, created by 2 | sphinx-quickstart on Tue Jun 15 19:32:57 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Main page 7 | ===================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | install 14 | usage 15 | custom_lattice 16 | development 17 | python_api 18 | api/library_root 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /UnionFindPy/_version.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 UnionFind++ authors 2 | # 3 | # This file is part of UnionFind++. 4 | # 5 | # UnionFind++ is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # UnionFind++ is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with UnionFind++. If not, see . 17 | 18 | __version__ = "0.3.0-dev" 19 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile clean 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | clean: 23 | rm -rf _doxygen/ api/ 24 | @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia-default-arguments-calls,-fuchsia-trailing-return,-fuchsia-overloaded-operator,-google-runtime-references,-llvmlibc-*,-readability-function-cognitive-complexity,-llvm-header-guard' 3 | WarningsAsErrors: '*,-clang-diagnostic-#pragma-messages,-bugprone-narrowing-conversions,-cppcoreguidelines-narrowing-conversions,-altera-struct-pack-align,-google-runtime-int,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-clang-analyzer-optin.portability.UnixAPI' 4 | AnalyzeTemporaryDtors: false 5 | FormatStyle: none 6 | HeaderFilterRegex: '.*\.hpp' 7 | CheckOptions: 8 | - key: readability-function-cognitive-complexity.IgnoreMacros # from clang-tidy-14 9 | value: true 10 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 11 | value: true 12 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(UnionFindPy) 4 | 5 | # read and set union_find version 6 | function(set_union_find_version VERSION_FILE_PATH) 7 | file(STRINGS ${VERSION_FILE_PATH} VERSION_FILE_STR) 8 | foreach (LINE IN LISTS VERSION_FILE_STR) 9 | if("${LINE}" MATCHES "__version__.*") 10 | set(VERSION_LINE_STR "${LINE}") 11 | endif() 12 | endforeach() 13 | 14 | string(REGEX REPLACE "__version__ = \"(.*)\"" "\\1" VERSION_STRING ${VERSION_LINE_STR}) 15 | set(VERSION_STRING ${VERSION_STRING} parent_scope) 16 | endfunction() 17 | 18 | set_union_find_version(${PROJECT_SOURCE_DIR}/UnionFindPy/_version.py) 19 | 20 | message(STATUS "union_find version ${VERSION_STRING}") 21 | set(PROJECT_VERSION ${VERSION_STRING}) 22 | 23 | 24 | if(NOT CMAKE_BUILD_TYPE) 25 | set(CMAKE_BUILD_TYPE Release) 26 | endif() 27 | 28 | # TODO: export target UnionFindCPP::header-only when imported from another 29 | 30 | # include UnionFindCPP 31 | set(BUILD_UNION_FIND_PY ON) 32 | add_subdirectory(UnionFindPy/cpp) 33 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/typedefs.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace UnionFindCPP 22 | { 23 | using ArrayXu = Eigen::Array; 24 | using ArrayXXu = Eigen::Array; 25 | } // namespace UnionFindCPP 26 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | Development 2 | ==================== 3 | 4 | This project consists of C++ implementation of `the Union-Find decoder `_ and its python binding. For C++ code, one may look ``UnionFindPy/cpp`` directory. We note that this directory itself is a complete C++ project you can build with ``CMake``. 5 | 6 | To compile C++ examples, you may run 7 | 8 | .. code-block:: shell 9 | 10 | $ cd UnionFindPy/cpp 11 | $ mkdir build && cd build 12 | $ cmake -DBUILD_EXAMPLES=ON .. 13 | $ make 14 | 15 | Other supported ``cmake`` options are ``-DENABLE_AVX=ON``, ``-DBUILD_TESTS=ON``, ``-DCLANG_TIDY``. 16 | 17 | 18 | For a contribution, I ask you install ``clang-tidy-12`` and ``clang-format-12``. You can format C++ source files with: 19 | 20 | .. code-block:: shell 21 | 22 | $ cd UnionFindPy/cpp 23 | $ make format 24 | 25 | where ``clang-tidy`` can be called with: 26 | 27 | .. code-block:: shell 28 | 29 | $ cd UnionFindPy/cpp 30 | $ mkdir build && cd build 31 | $ cmake -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON -DCLANG_TIDY=ON .. 32 | $ make 33 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/runner_utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) 23 | auto parse_args(int argc, const char* const argv[]) -> std::pair; 24 | void save_to_json(uint32_t L, double p, double avg_dur_in_microseconds, 25 | double avg_success); 26 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/include/utility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #ifndef NDEBUG 12 | #define DEBUG 13 | #endif 14 | 15 | namespace UnionFindCPP 16 | { 17 | 18 | enum class ErrorType 19 | { 20 | X, 21 | Z 22 | }; 23 | 24 | struct Edge 25 | { 26 | uint32_t u; 27 | uint32_t v; 28 | 29 | Edge(uint32_t ul, uint32_t vl) 30 | { 31 | u = std::min(ul, vl); 32 | v = std::max(ul, vl); 33 | } 34 | 35 | inline auto operator==(const Edge& rhs) const -> bool 36 | { 37 | return (u == rhs.u) && (v == rhs.v); 38 | } 39 | }; 40 | 41 | void to_json(nlohmann::json& j, const Edge& e); 42 | void from_json(const nlohmann::json& j, Edge& e); 43 | auto operator<<(std::ostream& os, const UnionFindCPP::Edge& e) -> std::ostream&; 44 | } // namespace UnionFindCPP 45 | 46 | template<> struct std::hash 47 | { 48 | auto operator()(const UnionFindCPP::Edge& e) const noexcept -> std::size_t 49 | { 50 | auto h1 = std::hash()(e.u); 51 | auto h2 = std::hash()(e.v); 52 | return h1 ^ (h2 << 1U); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/src/utility.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "utility.hpp" 18 | 19 | #include 20 | namespace UnionFindCPP 21 | { 22 | void to_json(nlohmann::json& j, const Edge& e) 23 | { 24 | j = nlohmann::json{e.u, e.v}; 25 | } 26 | void from_json(const nlohmann::json& j, Edge& e) 27 | { 28 | e = Edge(j[0], j[1]); 29 | } 30 | 31 | auto operator<<(std::ostream& os, const Edge& e) -> std::ostream& 32 | { 33 | os << "[" << e.u << ", " << e.v << "]"; 34 | return os; 35 | } 36 | } // namespace UnionFindCPP 37 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/include/LatticeConcept.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utility.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace UnionFindCPP 9 | { 10 | namespace detail 11 | { 12 | template struct is_std_array : std::false_type 13 | { 14 | }; 15 | 16 | template 17 | struct is_std_array> : std::true_type 18 | { 19 | }; 20 | 21 | template concept std_array = is_std_array::value; 22 | } // namespace detail 23 | 24 | template 25 | concept vertex_connections_result 26 | = std::convertible_to> || detail::std_array; 27 | 28 | /** 29 | * @brief Define LatticeConcept that custom Lattice classes should follow. 30 | * */ 31 | template 32 | concept LatticeConcept = requires(const T lattice, uint32_t vertex, Edge e) 33 | { 34 | { 35 | lattice.num_vertices() 36 | } -> std::convertible_to; 37 | { 38 | lattice.num_edges() 39 | } -> std::convertible_to; 40 | { 41 | lattice.vertex_connections(vertex) 42 | } -> vertex_connections_result; 43 | { 44 | lattice.vertex_connection_count(vertex) 45 | } -> std::convertible_to; 46 | { 47 | lattice.edge_idx(e) 48 | } -> std::convertible_to; 49 | }; 50 | } // namespace UnionFindCPP 51 | -------------------------------------------------------------------------------- /docs/custom_lattice.rst: -------------------------------------------------------------------------------- 1 | Implementing Custom Lattice 2 | ============================ 3 | 4 | Even though one can use our python binding for any possible lattices, implementing a lattice directly in C++ can boost decoding speeds. 5 | 6 | As our ``UnionFind`` is a template class, you can add your own custom lattice without much difficulty. 7 | A custom lattice class should implement 5 methods (implementing ``LatticeConcept``). 8 | 9 | .. code-block:: c++ 10 | 11 | class CustomLattice 12 | { 13 | public: 14 | using Vertex = int; 15 | 16 | int num_vertices(); //return number of all vertices in the lattice 17 | int num_edges(); //return number of all edges (qubits) in the lattice 18 | 19 | std::vector vertex_connections(Vertex v); //return nearest neighbor vertices 20 | 21 | int edge_idx(Edge edge); //return index of edge for a given edge 22 | int vertex_connection_count(Vertex v); //return the number of nearest neighbor vertices 23 | }; 24 | 25 | Then you can use our ``UnionFind`` template class in your C++ code as 26 | 27 | .. code-block:: c++ 28 | 29 | #include 30 | auto decoder = UnionFind(args...); 31 | 32 | All parameters of the ``UnionFind`` constructor are perfectly forwarded to the constructor of ``CustomLattice`` class. 33 | 34 | We are planning to support a Python interface to generate a custom lattice. 35 | 36 | 37 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/build_utils/cpp_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from pathlib import Path 3 | import json 4 | import sys 5 | import os 6 | 7 | FILE_EXTENSIONS = ['.hpp', '.cpp'] 8 | SOURCE_DIRS = set(['binding', 'include', 'src']) 9 | EXCLUDE_DIRS = [] 10 | PROJECT_SOURCE_DIR = Path(__file__).parent.parent.resolve() 11 | 12 | if __name__ == '__main__': 13 | include_examples = False 14 | include_tests = False 15 | 16 | for arg in sys.argv[1:]: 17 | if arg.startswith('--include-'): 18 | dirname = arg[10:] 19 | SOURCE_DIRS.add(dirname) 20 | elif arg.startswith('--exclude-'): 21 | dirname = arg[10:] 22 | SOURCE_DIRS.discard(dirname) 23 | else: 24 | print(f"Given argument {arg} is not supported.") 25 | sys.exit(1) 26 | 27 | 28 | file_list = [] 29 | for source_dir in SOURCE_DIRS: 30 | source_dir_full = PROJECT_SOURCE_DIR.joinpath(source_dir) 31 | for ext in FILE_EXTENSIONS: 32 | file_list += [p for p in source_dir_full.rglob('*' + ext)] 33 | 34 | file_list = set(file_list) 35 | 36 | for exclude_dir in EXCLUDE_DIRS: 37 | exclude_dir_full = PROJECT_SOURCE_DIR.joinpath(exclude_dir) 38 | for ext in FILE_EXTENSIONS: 39 | for exclude_file in exclude_dir_full.rglob('*' + ext): 40 | file_list.discard(exclude_file) 41 | 42 | file_list = [{'name': str(v)} for v in file_list] 43 | json.dump(file_list, sys.stdout) 44 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ================ 3 | 4 | Python interface of this project works almost the same as PyMatching. 5 | As in `the PyMatching document for decoding the toric code `_, we create stabilizers for the toric code 6 | 7 | .. code-block:: python 8 | 9 | # This code is based on PyMatching document 10 | # https://pymatching.readthedocs.io/en/stable/toric-code-example.html 11 | # Modified for the C++ UnionFind Project 12 | 13 | import numpy as np 14 | from scipy.sparse import hstack, kron, eye, csr_matrix, block_diag 15 | 16 | def repetition_code(n): 17 | ... 18 | return csr_matrix((data, (row_ind, col_ind))) 19 | 20 | def toric_code_x_stabilisers(L): 21 | ... 22 | return csr_matrix(H) 23 | 24 | and the logical operator 25 | 26 | .. code-block:: python 27 | 28 | def toric_code_x_logicals(L): 29 | ... 30 | return csr_matrix(x_logicals) 31 | 32 | Then you can call the decoder with 33 | 34 | .. code-block:: python 35 | 36 | decoder = Decoder(toric_code_x_stabilisers(L)) 37 | correction = decoder.decode(syndrome) 38 | 39 | Noisy version also works almost exactly same as PyMatching except that a syndrome array saves a result of syndrome measurement of each time-slice in row (instead of column as in PyMatching example). 40 | 41 | See code inside ``examples`` directory to see working examples. 42 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/error_utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include 18 | 19 | #include "error_utils.hpp" 20 | #include "utility.hpp" 21 | 22 | namespace UnionFindCPP 23 | { 24 | void add_measurement_noise(const uint32_t L, std::vector& syndromes, 25 | const ArrayXXu& measurement_error) 26 | { 27 | Eigen::Map syndromes_map(syndromes.data(), L * L, L); 28 | syndromes_map += measurement_error; 29 | } 30 | 31 | void layer_syndrome_diff(const uint32_t L, std::vector& syndromes) 32 | { 33 | Eigen::Map syndromes_map(syndromes.data(), L * L, L); 34 | for(int h = L - 1; h >= 1; --h) { syndromes_map.col(h) += syndromes_map.col(h - 1); } 35 | syndromes_map = syndromes_map.unaryExpr([](uint32_t x) { return x % 2; }); 36 | } 37 | } // namespace UnionFindCPP 38 | -------------------------------------------------------------------------------- /examples/utils.py: -------------------------------------------------------------------------------- 1 | # This code is based on PyMatching document 2 | # https://pymatching.readthedocs.io/en/stable/toric-code-example.html 3 | # Modified for the C++ UnionFind Project 4 | 5 | import numpy as np 6 | from scipy.sparse import hstack, kron, eye, csr_matrix, block_diag 7 | 8 | 9 | def repetition_code(n): 10 | """ 11 | Parity check matrix of a repetition code with length n. 12 | """ 13 | row_ind, col_ind = zip(*((i, j) for i in range(n) for j in (i, (i + 1) % n))) 14 | data = np.ones(2 * n, dtype=np.uint8) 15 | return csr_matrix((data, (row_ind, col_ind))) 16 | 17 | 18 | def toric_code_x_stabilisers(L): 19 | """ 20 | Sparse check matrix for the X stabilisers of a toric code with 21 | lattice size L, constructed as the hypergraph product of 22 | two repetition codes. 23 | """ 24 | Hr = repetition_code(L) 25 | H = hstack( 26 | [kron(Hr, eye(Hr.shape[1])), kron(eye(Hr.shape[0]), Hr.T)], dtype=np.uint8 27 | ) 28 | H.data = H.data % 2 29 | H.eliminate_zeros() 30 | return csr_matrix(H) 31 | 32 | 33 | def toric_code_x_logicals(L): 34 | """ 35 | Sparse binary matrix with each row corresponding to an X logical operator 36 | of a toric code with lattice size L. Constructed from the 37 | homology groups of the repetition codes using the Kunneth 38 | theorem. 39 | """ 40 | H1 = csr_matrix(([1], ([0], [0])), shape=(1, L), dtype=np.uint8) 41 | H0 = csr_matrix(np.ones((1, L), dtype=np.uint8)) 42 | x_logicals = block_diag([kron(H1, H0), kron(H0, H1)]) 43 | x_logicals.data = x_logicals.data % 2 44 | x_logicals.eliminate_zeros() 45 | return csr_matrix(x_logicals) 46 | -------------------------------------------------------------------------------- /tests/test_decoder.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from UnionFindPy import Decoder 3 | import numpy as np 4 | from scipy.sparse import csr_matrix 5 | 6 | def test_toric33(): 7 | parity_matrix = np.zeros((9, 18), dtype=np.int8) 8 | # P0 9 | parity_matrix[0,0] = 1 10 | parity_matrix[0,2] = 1 11 | parity_matrix[0,3] = 1 12 | parity_matrix[0,15] = 1 13 | # P1 14 | parity_matrix[1,0] = 1 15 | parity_matrix[1,1] = 1 16 | parity_matrix[1,4] = 1 17 | parity_matrix[1,16] = 1 18 | # P2 19 | parity_matrix[2,1] = 1 20 | parity_matrix[2,2] = 1 21 | parity_matrix[2,5] = 1 22 | parity_matrix[2,17] = 1 23 | # P3 24 | parity_matrix[3,3] = 1 25 | parity_matrix[3,6] = 1 26 | parity_matrix[3,8] = 1 27 | parity_matrix[3,9] = 1 28 | # P4 29 | parity_matrix[4,4] = 1 30 | parity_matrix[4,6] = 1 31 | parity_matrix[4,7] = 1 32 | parity_matrix[4,10] = 1 33 | # P5 34 | parity_matrix[5,5] = 1 35 | parity_matrix[5,7] = 1 36 | parity_matrix[5,8] = 1 37 | parity_matrix[5,11] = 1 38 | # P6 39 | parity_matrix[6,9] = 1 40 | parity_matrix[6,12] = 1 41 | parity_matrix[6,14] = 1 42 | parity_matrix[6,15] = 1 43 | # P7 44 | parity_matrix[7,10] = 1 45 | parity_matrix[7,12] = 1 46 | parity_matrix[7,13] = 1 47 | parity_matrix[7,16] = 1 48 | # P8 49 | parity_matrix[8,11] = 1 50 | parity_matrix[8,13] = 1 51 | parity_matrix[8,14] = 1 52 | parity_matrix[8,17] = 1 53 | 54 | parity_matrix = csr_matrix(parity_matrix) 55 | 56 | decoder = Decoder(parity_matrix) 57 | 58 | syndrom_arr = [0]*9 59 | 60 | # error = 3 61 | syndrom_arr[0] = 1 62 | syndrom_arr[3] = 1 63 | 64 | expected = [0] * 18 65 | expected[3] = 1 66 | 67 | assert np.all(decoder.decode(syndrom_arr) == expected) 68 | -------------------------------------------------------------------------------- /examples/plot_toric_threshold.py: -------------------------------------------------------------------------------- 1 | # This code is based on PyMatching document 2 | # https://pymatching.readthedocs.io/en/stable/toric-code-example.html 3 | # Modified for the C++ UnionFind Project 4 | 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | from utils import repetition_code, toric_code_x_logicals, toric_code_x_stabilisers 9 | from UnionFindPy import Decoder 10 | 11 | def num_decoding_failures(L, H, logicals, p, num_trials): 12 | decoder = Decoder(toric_code_x_stabilisers(L)) 13 | num_errors = 0 14 | for i in range(num_trials): 15 | noise = np.random.binomial(1, p, 2 * L * L) 16 | syndrome = H @ noise % 2 17 | correction = decoder.decode(syndrome) 18 | error = (noise + correction) % 2 19 | if np.any(error @ logicals.T % 2): 20 | num_errors += 1 21 | return num_errors 22 | 23 | 24 | if __name__ == "__main__": 25 | num_trials = 20000 26 | Ls = range(3, 19, 2) 27 | ps = np.linspace(0.01, 0.15, 15) 28 | np.random.seed(2) 29 | log_errors_all_L = [] 30 | for L in Ls: 31 | print("Simulating L={}...".format(L)) 32 | Hx = toric_code_x_stabilisers(L) 33 | logX = toric_code_x_logicals(L) 34 | log_errors = [] 35 | for p in ps: 36 | num_errors = num_decoding_failures(L, Hx, logX, p, num_trials) 37 | log_errors.append(num_errors / num_trials) 38 | log_errors_all_L.append(np.array(log_errors)) 39 | 40 | print(log_errors_all_L) 41 | 42 | plt.figure() 43 | for L, logical_errors in zip(Ls, log_errors_all_L): 44 | std_err = (logical_errors * (1 - logical_errors) / num_trials) ** 0.5 45 | plt.errorbar(ps, logical_errors, yerr=std_err, label="L={}".format(L)) 46 | plt.xlabel("Physical error rate") 47 | plt.ylabel("Logical error rate") 48 | plt.legend(loc=0) 49 | plt.show() 50 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/LazyDecoder.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | #include "utility.hpp" 19 | #include 20 | 21 | namespace UnionFindCPP 22 | { 23 | template class LazyDecoder 24 | { 25 | private: 26 | const Lattice lattice_; 27 | std::vector all_edges_; 28 | 29 | public: 30 | template explicit LazyDecoder(Args&&... args) : lattice_{args...} 31 | { 32 | const auto num_edges = lattice_.num_edges(); 33 | 34 | for(int i = 0; i < num_edges; ++i) 35 | { 36 | all_edges_.emplace_back(lattice_.to_edge(i)); 37 | } 38 | } 39 | 40 | auto decode(std::vector& syndromes) -> std::pair> 41 | { 42 | std::vector corrections; 43 | 44 | for(Edge edge : all_edges_) 45 | { 46 | if(syndromes[edge.u] == 1 && syndromes[edge.v] == 1) 47 | { 48 | corrections.emplace_back(edge); 49 | } 50 | } 51 | 52 | for(const auto& edge : corrections) 53 | { 54 | syndromes[edge.u] ^= 1U; 55 | syndromes[edge.v] ^= 1U; 56 | } 57 | 58 | bool success = true; 59 | for(const auto syndrome : syndromes) 60 | { 61 | if(syndrome == 1) 62 | { 63 | success = false; 64 | break; 65 | } 66 | } 67 | return std::make_pair(success, corrections); 68 | } 69 | }; 70 | } // namespace UnionFindCPP 71 | -------------------------------------------------------------------------------- /examples/bench_decoder.py: -------------------------------------------------------------------------------- 1 | # This code is based on PyMatching document 2 | # https://pymatching.readthedocs.io/en/stable/toric-code-example.html 3 | # Modified for the C++ UnionFind Project 4 | 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | from utils import repetition_code, toric_code_x_logicals, toric_code_x_stabilisers 9 | import UnionFindPy 10 | import pymatching 11 | 12 | import sys 13 | import time 14 | 15 | def num_decoding_failures(DecoderClass, H, logicals, p, num_trials): 16 | decoder = DecoderClass(H) 17 | num_errors = 0 18 | total_time = 0.0 19 | for i in range(num_trials): 20 | noise = np.random.binomial(1, p, 2 * L * L) 21 | syndrome = H @ noise % 2 22 | 23 | start_time = time.perf_counter() 24 | correction = decoder.decode(syndrome) 25 | end_time = time.perf_counter() 26 | 27 | total_time += (end_time - start_time) 28 | error = (noise + correction) % 2 29 | if np.any(error @ logicals.T % 2): 30 | num_errors += 1 31 | return total_time, num_errors 32 | 33 | 34 | if __name__ == "__main__": 35 | DEFAULT_NUM_TRIALS = 10000 36 | 37 | if len(sys.argv) not in [4, 5]: 38 | print("Usage: {} [uf | matching] L p [num_trials]".format(sys.argv[0])) 39 | print(f' Default value for num_trials is {DEFAULT_NUM_TRIALS}') 40 | 41 | if sys.argv[1].strip().lower() == 'uf': 42 | DecoderClass = UnionFindPy.Decoder 43 | elif sys.argv[1].strip().lower() == 'matching': 44 | DecoderClass = pymatching.Matching 45 | else: 46 | print("The first argument must be 'uf' or 'matching'") 47 | sys.exit(1) 48 | 49 | L = int(sys.argv[2]) 50 | p = float(sys.argv[3]) 51 | 52 | if len(sys.argv) == 5: 53 | num_trials = int(sys.argv[4]) 54 | else: 55 | num_trials = DEFAULT_NUM_TRIALS 56 | 57 | Hx = toric_code_x_stabilisers(L) 58 | logX = toric_code_x_logicals(L) 59 | total_time, num_errors = num_decoding_failures(DecoderClass, Hx, logX, p, num_trials) 60 | 61 | print('{}\t{}'.format(total_time / num_trials, num_errors / num_trials)) 62 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/Lattice2D.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | 19 | #include "toric_utils.hpp" 20 | #include "utility.hpp" 21 | 22 | namespace UnionFindCPP 23 | { 24 | class Lattice2D 25 | { 26 | private: 27 | uint32_t L_; 28 | 29 | public: 30 | using Vertex = uint32_t; 31 | 32 | explicit Lattice2D(uint32_t L) : L_{L} { } 33 | 34 | [[nodiscard]] inline auto to_vertex_index(uint32_t row, uint32_t col) const 35 | -> uint32_t 36 | { 37 | return UnionFindCPP::to_vertex_index(L_, row, 38 | col); // call function from the parent scope 39 | } 40 | 41 | [[nodiscard]] constexpr static auto vertex_connection_count(Vertex /*v*/) -> uint32_t 42 | { 43 | return 4; 44 | } 45 | 46 | [[nodiscard]] auto vertex_connections(Vertex v) const -> std::array 47 | { 48 | uint32_t row = v / L_; 49 | uint32_t col = v % L_; 50 | 51 | return { 52 | to_vertex_index(row - 1, col), 53 | to_vertex_index(row + 1, col), 54 | to_vertex_index(row, col - 1), 55 | to_vertex_index(row, col + 1), 56 | }; 57 | } 58 | 59 | [[nodiscard]] inline auto num_vertices() const -> uint32_t { return L_ * L_; } 60 | 61 | [[nodiscard]] inline auto num_edges() const -> uint32_t { return 2 * L_ * L_; } 62 | 63 | [[nodiscard]] inline auto edge_idx(const Edge& edge) const -> uint32_t 64 | { 65 | return to_edge_idx(L_, edge); 66 | } 67 | 68 | [[nodiscard]] inline auto to_edge(uint32_t edge_index) const -> Edge 69 | { 70 | return UnionFindCPP::to_edge(L_, edge_index); 71 | } 72 | }; 73 | } // namespace UnionFindCPP 74 | -------------------------------------------------------------------------------- /.github/workflows/wheel_macos_arm64.yml: -------------------------------------------------------------------------------- 1 | name: Wheel::MacOS::ARM 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | release: 8 | 9 | env: 10 | CIBW_BUILD: 'cp37-* cp38-* cp39-* cp310-*' 11 | 12 | # MacOS specific build settings 13 | CIBW_BEFORE_ALL_MACOS: | 14 | brew uninstall --force oclint 15 | brew update 16 | brew install llvm 17 | 18 | # Python build settings 19 | CIBW_BEFORE_BUILD: | 20 | pip install pybind11 ninja cmake 21 | echo "$(brew --prefix llvm)/bin" 22 | 23 | # Testing of built wheels 24 | CIBW_TEST_REQUIRES: numpy~=1.21 scipy pytest pytest-cov pytest-mock flaky 25 | 26 | CIBW_BUILD_VERBOSITY: 1 27 | 28 | MACOSX_DEPLOYMENT_TARGET: "10.14" 29 | 30 | jobs: 31 | mac-wheels-arm64: 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | os: [macos-11] 36 | arch: [arm64] 37 | name: macos-latest::arm64 38 | runs-on: ${{ matrix.os }} 39 | 40 | steps: 41 | - name: Cancel Previous Runs 42 | uses: styfle/cancel-workflow-action@0.4.1 43 | with: 44 | access_token: ${{ github.token }} 45 | 46 | - uses: actions/checkout@v2 47 | with: 48 | submodules: 'recursive' 49 | 50 | - uses: actions/setup-python@v2 51 | name: Install Python 52 | with: 53 | python-version: '3.7' 54 | 55 | - name: Install cibuildwheel 56 | run: python -m pip install cibuildwheel==2.3.0 57 | 58 | - name: Build wheels 59 | run: | 60 | python -m cibuildwheel --output-dir wheelhouse 61 | env: 62 | CIBW_ARCHS_MACOS: ${{ matrix.arch }} 63 | CXX: "/usr/local/opt/llvm/bin/clang++" 64 | 65 | - uses: actions/upload-artifact@v2 66 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main'}} 67 | with: 68 | name: ${{ runner.os }}-wheels.zip 69 | path: ./wheelhouse/*.whl 70 | 71 | 72 | upload-pypi: 73 | needs: mac-wheels-arm64 74 | runs-on: ubuntu-latest 75 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main'}} 76 | steps: 77 | - uses: actions/download-artifact@v2 78 | with: 79 | name: macOS-wheels.zip 80 | path: dist 81 | 82 | - name: Upload wheels to PyPI 83 | uses: pypa/gh-action-pypi-publish@release/v1 84 | with: 85 | user: __token__ 86 | password: ${{ secrets.PYPI_API_TOKEN }} 87 | -------------------------------------------------------------------------------- /examples/plot_toric_threshold_noisy.py: -------------------------------------------------------------------------------- 1 | # This code is based on PyMatching document 2 | # https://pymatching.readthedocs.io/en/stable/toric-code-example.html 3 | # Modified for the C++ UnionFind Project 4 | 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from utils import repetition_code, toric_code_x_logicals, toric_code_x_stabilisers 8 | from UnionFindPy import Decoder 9 | 10 | def num_decoding_failures_noisy_syndromes(H, logicals, p, q, num_trials, repetitions): 11 | num_stabilisers, num_qubits = H.shape 12 | num_errors = 0 13 | decoder = Decoder(H, repetitions) 14 | for i in range(num_trials): 15 | noise_new = (np.random.rand(repetitions, num_qubits) < p).astype(np.uint8) 16 | noise_cumulative = (np.cumsum(noise_new, axis=0) % 2).astype(np.uint8) 17 | noise_total = noise_cumulative[-1, :] 18 | syndrome = noise_cumulative @ H.T % 2 19 | syndrome_error = (np.random.rand(repetitions, num_stabilisers) < q).astype( 20 | np.uint8 21 | ) 22 | syndrome_error[-1,:] = 0 # Perfect measurements in last round to ensure even parity 23 | noisy_syndrome = (syndrome + syndrome_error) % 2 24 | # Convert to difference syndrome 25 | noisy_syndrome[1:,:] = (noisy_syndrome[1:, :] - noisy_syndrome[0:-1, :]) % 2 26 | correction = decoder.decode(noisy_syndrome) 27 | 28 | error = (noise_total + correction) % 2 29 | assert not np.any(H @ error % 2) 30 | if np.any(error @ logicals.T % 2): 31 | num_errors += 1 32 | return num_errors 33 | 34 | 35 | if __name__ == "__main__": 36 | num_trials = 5000 37 | Ls = range(8, 13, 2) 38 | ps = np.linspace(0.02, 0.04, 7) 39 | log_errors_all_L = [] 40 | for L in Ls: 41 | print("Simulating L={}...".format(L)) 42 | Hx = toric_code_x_stabilisers(L) 43 | logX = toric_code_x_logicals(L) 44 | log_errors = [] 45 | for p in ps: 46 | num_errors = num_decoding_failures_noisy_syndromes( 47 | Hx, logX, p, p, num_trials, L 48 | ) 49 | log_errors.append(num_errors / num_trials) 50 | log_errors_all_L.append(np.array(log_errors)) 51 | 52 | for L, logical_errors in zip(Ls, log_errors_all_L): 53 | std_err = (logical_errors * (1 - logical_errors) / num_trials) ** 0.5 54 | plt.errorbar(ps, logical_errors, yerr=std_err, label="L={}".format(L)) 55 | 56 | plt.yscale("log") 57 | plt.xlabel("Physical error rate") 58 | plt.ylabel("Logical error rate") 59 | plt.legend(loc=0) 60 | plt.show() 61 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(UnionFindCPP_examples) 2 | 3 | find_package(Eigen3 3.3 REQUIRED NO_MODULE) 4 | 5 | add_subdirectory(fmt EXCLUDE_FROM_ALL) 6 | 7 | find_package(MPI) 8 | if(MPI_FOUND) 9 | message("MPI found. Build MPI executables.") 10 | endif() 11 | 12 | add_library(example_utils STATIC "error_utils.cpp" "toric_utils.cpp" "runner_utils.cpp") 13 | target_link_libraries(example_utils PRIVATE union_find_cpp_dependency Eigen3::Eigen) 14 | target_link_libraries(example_utils PUBLIC fmt::fmt-header-only) 15 | 16 | # 2D Bitflip noise 17 | add_executable(run_uf_2d_bitflip "run_uf_2d_bitflip.cpp") 18 | target_link_libraries(run_uf_2d_bitflip PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen) 19 | 20 | add_executable(run_uf_2d_bitflip_lazy "run_uf_2d_bitflip.cpp") 21 | target_compile_definitions(run_uf_2d_bitflip_lazy PUBLIC USE_LAZY) 22 | target_link_libraries(run_uf_2d_bitflip_lazy PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen) 23 | 24 | # 3D Bitflip noise 25 | add_executable(run_uf_3d_bitflip "run_uf_3d_bitflip.cpp") 26 | target_link_libraries(run_uf_3d_bitflip PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen) 27 | 28 | add_executable(run_uf_3d_bitflip_lazy "run_uf_3d_bitflip.cpp") 29 | target_compile_definitions(run_uf_2d_bitflip_lazy PUBLIC USE_LAZY) 30 | target_link_libraries(run_uf_3d_bitflip_lazy PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen) 31 | 32 | if (MPI_FOUND) 33 | # 2D Bitflip noise with MPI 34 | add_executable(run_uf_2d_bitflip_mpi "run_uf_2d_bitflip.cpp") 35 | target_compile_definitions(run_uf_2d_bitflip_mpi PUBLIC USE_MPI) 36 | target_link_libraries(run_uf_2d_bitflip_mpi PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen MPI::MPI_CXX) 37 | 38 | add_executable(run_uf_2d_bitflip_lazy_mpi "run_uf_2d_bitflip.cpp") 39 | target_compile_definitions(run_uf_2d_bitflip_lazy_mpi PUBLIC USE_MPI 40 | USE_LAZY) 41 | target_link_libraries(run_uf_2d_bitflip_lazy_mpi PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen MPI::MPI_CXX) 42 | 43 | # 3D Bitflip noise with MPI 44 | add_executable(run_uf_3d_bitflip_mpi "run_uf_3d_bitflip.cpp") 45 | target_compile_definitions(run_uf_3d_bitflip_mpi PUBLIC USE_MPI) 46 | target_link_libraries(run_uf_3d_bitflip_mpi PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen MPI::MPI_CXX) 47 | 48 | add_executable(run_uf_3d_bitflip_lazy_mpi "run_uf_3d_bitflip.cpp") 49 | target_compile_definitions(run_uf_3d_bitflip_lazy_mpi PUBLIC USE_MPI 50 | USE_LAZY) 51 | target_link_libraries(run_uf_3d_bitflip_lazy_mpi PRIVATE example_utils union_find_cpp_dependency Eigen3::Eigen MPI::MPI_CXX) 52 | endif () 53 | -------------------------------------------------------------------------------- /.github/workflows/wheel_win_x86_64.yml: -------------------------------------------------------------------------------- 1 | name: Wheel::Windows::x86_64 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | release: 8 | 9 | env: 10 | CIBW_BUILD: 'cp37-* cp38-* cp39-* cp310-*' 11 | 12 | CIBW_ARCHS_WINDOWS: "AMD64" 13 | 14 | # Python build settings 15 | CIBW_BEFORE_BUILD: | 16 | pip install pybind11 ninja cmake 17 | 18 | # Testing of built wheels 19 | CIBW_TEST_REQUIRES: numpy~=1.21 pytest scipy 20 | 21 | CIBW_BEFORE_TEST: | 22 | python -c "import sys; print(sys.path)" 23 | 24 | CIBW_TEST_COMMAND: | 25 | python {project}\tests\test_decoder.py 26 | 27 | DISTUTILS_USE_SDK: 1 28 | 29 | MSSdk: 1 30 | 31 | jobs: 32 | win-wheels: 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | os: [windows-latest] 37 | name: ${{ matrix.os }} 38 | runs-on: ${{ matrix.os }} 39 | 40 | steps: 41 | - name: Cancel Previous Runs 42 | uses: styfle/cancel-workflow-action@0.4.1 43 | with: 44 | access_token: ${{ github.token }} 45 | 46 | - name: Get git branch name 47 | id: branch-name 48 | uses: tj-actions/branch-names@v5 49 | 50 | - uses: actions/checkout@v2 51 | with: 52 | submodules: 'recursive' 53 | 54 | - uses: actions/setup-python@v2 55 | name: Install Python 56 | with: 57 | python-version: '3.7' 58 | 59 | - name: Configure MSVC for amd64 # Use cl.exe as a default compiler 60 | uses: ilammy/msvc-dev-cmd@v1 61 | with: 62 | arch: amd64 63 | 64 | - name: Build wheels 65 | uses: pypa/cibuildwheel@v2.3.1 66 | 67 | - uses: actions-ecosystem/action-regex-match@v2 68 | id: rc_build 69 | with: 70 | text: ${{ github.event.pull_request.head.ref }} 71 | regex: '.*[0-9]+.[0-9]+.[0-9]+[-]?rc[0-9]+' 72 | 73 | - uses: actions/upload-artifact@v2 74 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main' || steps.rc_build.outputs.match != ''}} 75 | with: 76 | name: ${{ runner.os }}-wheels.zip 77 | path: ./wheelhouse/*.whl 78 | 79 | upload-pypi: 80 | needs: win-wheels 81 | runs-on: ubuntu-latest 82 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main'}} 83 | steps: 84 | - uses: actions/download-artifact@v2 85 | with: 86 | name: Windows-wheels.zip 87 | path: dist 88 | 89 | - name: Upload wheels to PyPI 90 | uses: pypa/gh-action-pypi-publish@release/v1 91 | with: 92 | user: __token__ 93 | password: ${{ secrets.PYPI_API_TOKEN }} 94 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/runner_utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "runner_utils.hpp" 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) 29 | auto parse_args(int argc, const char* const argv[]) -> std::pair 30 | { 31 | auto args = std::span(argv, size_t(argc)); 32 | if(argc != 3) { throw std::invalid_argument(fmt::format("Usage: {} L p", args[0])); } 33 | 34 | uint32_t L = 0; 35 | double p = 0.0; 36 | 37 | try 38 | { 39 | unsigned long Ll = std::stoul(args[1]); 40 | if(Ll > std::numeric_limits::max()) 41 | { 42 | throw std::invalid_argument("L must be in 32 bit unsigned integer"); 43 | } 44 | L = Ll; 45 | p = std::stod(args[2]); 46 | } 47 | catch(std::exception& e) 48 | { 49 | throw e; 50 | } 51 | 52 | if(!((p > 0.0) && (p < 1.0))) 53 | { 54 | throw std::invalid_argument("Error: p must be in between 0.0 and 1.0\n"); 55 | } 56 | return std::make_pair(L, p); 57 | } 58 | 59 | void save_to_json(uint32_t L, double p, double avg_dur_in_microseconds, 60 | double avg_success) 61 | { 62 | constexpr static int p_precision = 5; 63 | auto p_format = [](double p) -> long 64 | { 65 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) 66 | return std::lround(p * std::pow(10, p_precision)); 67 | }; 68 | 69 | std::string filename 70 | = fmt::format("out_L{:d}_P{:0{}d}.json", L, p_format(p), p_precision + 1); 71 | std::ofstream out_data(filename); 72 | nlohmann::json out_j; 73 | out_j["L"] = L; 74 | out_j["average_microseconds"] = double(avg_dur_in_microseconds); 75 | out_j["p"] = p; 76 | out_j["accuracy"] = double(avg_success); 77 | 78 | out_data << out_j.dump(0); 79 | } 80 | -------------------------------------------------------------------------------- /UnionFindPy/decoder.py: -------------------------------------------------------------------------------- 1 | from ._union_find_py import DecoderFromParity 2 | import logging 3 | from scipy.sparse import csr_matrix 4 | import numpy as np 5 | 6 | class Decoder: 7 | """Union-Find decoder class 8 | 9 | :param parity_matrix (scipy.sparse.csr_matrix): a parity matrix in CSR format 10 | """ 11 | 12 | _repetitions = None 13 | 14 | def __init__(self, parity_matrix, repetitions = None): 15 | """Create a decoder from a parity matrix""" 16 | 17 | if not isinstance(parity_matrix, csr_matrix): 18 | raise ValueError('Parameter parity_matrix must be a csr matrix.') 19 | 20 | if np.issubdtype(parity_matrix.dtype, int): 21 | raise ValueError('Data type of parity_matrix must be an integer, but {} is given'.format(parity_matrix.dtype)) 22 | 23 | if not np.all(parity_matrix.data == 1): 24 | raise ValueError('Any non-zero value of the partiy matrix must be 1.') 25 | 26 | if repetitions is None: 27 | self._decoder = DecoderFromParity(parity_matrix.shape[0], 28 | parity_matrix.shape[1], parity_matrix.indices, parity_matrix.indptr) 29 | else: 30 | self._repetitions = repetitions 31 | self._layer_vertex_size = parity_matrix.shape[0] 32 | self._layer_num_qubits = parity_matrix.shape[1] 33 | self._decoder = DecoderFromParity(parity_matrix.shape[0], 34 | parity_matrix.shape[1], parity_matrix.indices, parity_matrix.indptr, 35 | repetitions) 36 | 37 | def decode(self, syndrome_arr): 38 | """Decode a given syndrome array. 39 | 40 | :param syndrome_arr: for a given parity index `i`, syndrome_arr[i] must be 0 or 1. 41 | """ 42 | if isinstance(syndrome_arr, list): 43 | syndrome_arr = np.array(syndrome_arr) 44 | if syndrome_arr.size != self._decoder.num_vertices: 45 | raise ValueError("The size of syndrome_arr mismatches the size of all stabilizers") 46 | 47 | if self._repetitions is None: 48 | corrections = self._decoder.decode(syndrome_arr) 49 | self._decoder.clear() 50 | return corrections 51 | else: 52 | corrections = self._decoder.decode(syndrome_arr.flatten()) 53 | self._decoder.clear() 54 | res = np.zeros((self._layer_num_qubits,), dtype=int) 55 | for depth in range(self._repetitions): 56 | res += corrections[depth*(self._layer_num_qubits + self._layer_vertex_size):depth*(self._layer_num_qubits + self._layer_vertex_size)+self._layer_num_qubits] 57 | res %= 2 58 | return res 59 | -------------------------------------------------------------------------------- /.github/workflows/wheel_macos_x86_64.yml: -------------------------------------------------------------------------------- 1 | name: Wheel::MacOS::Intel 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | release: 8 | 9 | env: 10 | CIBW_BUILD: 'cp37-* cp38-* cp39-* cp310-*' 11 | 12 | # MacOS specific build settings 13 | CIBW_BEFORE_ALL_MACOS: | 14 | brew uninstall --force oclint 15 | brew update 16 | brew install llvm 17 | 18 | # Python build settings 19 | CIBW_BEFORE_BUILD: | 20 | pip install pybind11 ninja cmake 21 | 22 | # Testing of built wheels 23 | CIBW_TEST_REQUIRES: numpy~=1.21 pytest scipy 24 | 25 | CIBW_TEST_COMMAND: | 26 | python {project}/tests/test_decoder.py 27 | 28 | CIBW_BUILD_VERBOSITY: 1 29 | 30 | MACOSX_DEPLOYMENT_TARGET: 10.14 31 | 32 | jobs: 33 | mac-wheels-x86: 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | os: [macos-11] 38 | arch: [x86_64] 39 | name: ${{ matrix.os }} 40 | runs-on: ${{ matrix.os }} 41 | 42 | steps: 43 | - name: Cancel Previous Runs 44 | uses: styfle/cancel-workflow-action@0.4.1 45 | with: 46 | access_token: ${{ github.token }} 47 | 48 | - uses: actions/checkout@v2 49 | with: 50 | submodules: 'recursive' 51 | 52 | - uses: actions/setup-python@v2 53 | name: Install Python 54 | with: 55 | python-version: '3.7' 56 | 57 | - name: Install cibuildwheel 58 | run: python -m pip install cibuildwheel==2.3.0 59 | 60 | - name: Build wheels 61 | run: python -m cibuildwheel --output-dir wheelhouse 62 | env: 63 | CIBW_ARCHS_MACOS: ${{matrix.arch}} 64 | CXX: "/usr/local/opt/llvm/bin/clang++" 65 | 66 | - uses: actions-ecosystem/action-regex-match@v2 67 | id: rc_build 68 | with: 69 | text: ${{ github.event.pull_request.head.ref }} 70 | regex: '.*[0-9]+.[0-9]+.[0-9]+[-]?rc[0-9]+' 71 | 72 | - uses: actions/upload-artifact@v2 73 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main' || steps.rc_build.outputs.match != ''}} 74 | with: 75 | name: ${{ runner.os }}-wheels.zip 76 | path: ./wheelhouse/*.whl 77 | 78 | upload-pypi: 79 | needs: mac-wheels-x86 80 | runs-on: ubuntu-latest 81 | if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/main' }} 82 | steps: 83 | - uses: actions/download-artifact@v2 84 | with: 85 | name: macOS-wheels.zip 86 | path: dist 87 | 88 | - name: Upload wheels to PyPI 89 | uses: pypa/gh-action-pypi-publish@release/v1 90 | with: 91 | user: __token__ 92 | password: ${{ secrets.PYPI_API_TOKEN }} 93 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | UnionFind 2 | ========= 3 | 4 | |Documentation Status| |CodeFactor| 5 | 6 | Introduction 7 | ------------ 8 | 9 | C++ implementation of the Union-Find decoder 10 | `arXiv:1709:06218 `_. Python 11 | interface is also implemented using 12 | `pybind11 `_. 13 | 14 | Based on a Python implementation by Kai Meinerz. 15 | 16 | Under the LGPL lisence. 17 | 18 | This repository includes codes for 19 | `arXiv:2101.07285 `_ which explores a 20 | machine learning assisted preprocessing combined with conventional 21 | decoders such as minimum-weight perfect matching (MWPM) and the 22 | Union-Find decoder. 23 | 24 | Installation 25 | ------------ 26 | 27 | .. installation-start-inclusion-marker-do-not-remove 28 | 29 | Currently, you can install this project by cloning the source code tree and compiling it. 30 | To clone the source tree, use ``git clone`` as 31 | 32 | .. code-block:: bash 33 | 34 | git clone https://github.com/chaeyeunpark/UnionFind.git && cd UnionFind 35 | git submodule update --init --recursive 36 | 37 | Then you can compile the code using 38 | 39 | .. code-block:: bash 40 | 41 | pip install -r requirements.txt 42 | python3 setup.py install 43 | 44 | Note that a compiler with some C++20 supports (e.g. GCC version => 10 or Clang++ version => 12) is required. For example, if you are using Ubuntu 45 | 46 | .. code-block:: bash 47 | 48 | apt install -y g++-10 49 | CXX=g++-10 python3 setup.py install 50 | 51 | will work. 52 | 53 | PyPI support will be available soon. 54 | 55 | .. installation-end-inclusion-marker-do-not-remove 56 | 57 | Basic Usage 58 | ----------- 59 | 60 | .. code-block:: python 61 | 62 | from UnionFindPy import Decoder 63 | decoder = Decoder(parity_matrix) 64 | decoder.decode(syndromes) # syndromes is a list of measurment outcomes of each parity operator 65 | 66 | For details, check the `document `_ and `examples `_ directory. 67 | 68 | Notes 69 | ----- 70 | 71 | This repository does not contain an implementation of weighted 72 | Union-Find decoder. 73 | 74 | Reference 75 | --------- 76 | 77 | When you cite this repository, please use the following: 78 | 79 | .. code-block:: bibtex 80 | 81 | @misc{UnionFindCPP, 82 | author = {Chae-Yeun Park and Kai Meinerz}, 83 | title = {Open-source C++ implementation of the Union-Find decoder}, 84 | year = {2020}, 85 | publisher = {GitHub}, 86 | journal = {GitHub repository}, 87 | doi = {10.5281/zenodo.10044828}, 88 | howpublished = {\url{https://github.com/chaeyeunpark/UnionFind}} 89 | } 90 | 91 | 92 | 93 | .. |Documentation Status| image:: https://readthedocs.org/projects/unionfind/badge/?version=latest 94 | :target: https://unionfind.readthedocs.io/en/latest/?badge=latest 95 | .. |CodeFactor| image:: https://www.codefactor.io/repository/github/chaeyeunpark/unionfind/badge 96 | :target: https://www.codefactor.io/repository/github/chaeyeunpark/unionfind 97 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/LatticeCubic.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | 19 | #include "Lattice2D.hpp" 20 | #include "toric_utils.hpp" 21 | #include "utility.hpp" 22 | 23 | namespace UnionFindCPP 24 | { 25 | class LatticeCubic 26 | { 27 | private: 28 | const uint32_t L_; 29 | 30 | [[nodiscard]] inline auto to_vertex_index(uint32_t row, uint32_t col, 31 | uint32_t h) const -> uint32_t 32 | { 33 | return UnionFindCPP::to_vertex_index(L_, row, col) + h * (L_ * L_); 34 | } 35 | 36 | public: 37 | using Vertex = uint32_t; 38 | 39 | explicit LatticeCubic(uint32_t L) : L_{L} { } 40 | 41 | [[nodiscard]] inline auto getL() const -> uint32_t { return L_; } 42 | 43 | [[nodiscard]] constexpr auto vertex_connection_count(Vertex v) const -> uint32_t 44 | { 45 | uint32_t L = L_; 46 | 47 | uint32_t h = v / (L * L); 48 | 49 | if(h == L - 1 || h == 0) 50 | { 51 | return 5; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) 52 | } 53 | 54 | return 6; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) 55 | } 56 | 57 | [[nodiscard]] auto vertex_connections(Vertex v) const -> std::vector 58 | { 59 | uint32_t L = L_; 60 | 61 | uint32_t h = v / (L * L); 62 | uint32_t row = (v / L) % L; 63 | uint32_t col = v % L; 64 | 65 | auto res = std::vector{ 66 | to_vertex_index(row - 1, col, h), 67 | to_vertex_index(row + 1, col, h), 68 | to_vertex_index(row, col - 1, h), 69 | to_vertex_index(row, col + 1, h), 70 | }; 71 | if(h < L - 1) { res.emplace_back(to_vertex_index(row, col, h + 1)); } 72 | if(h > 0) { res.emplace_back(to_vertex_index(row, col, h - 1)); } 73 | 74 | return res; 75 | } 76 | 77 | [[nodiscard]] inline auto num_vertices() const -> uint32_t { return L_ * L_ * L_; } 78 | 79 | [[nodiscard]] inline auto num_edges() const -> uint32_t 80 | { 81 | return 3 * L_ * L_ * L_ - L_ * L_; 82 | } 83 | 84 | [[nodiscard]] inline auto edge_idx(const Edge& edge) const -> uint32_t 85 | { 86 | uint32_t L = L_; 87 | uint32_t uh = edge.u / (L * L); 88 | 89 | if((edge.u / (L * L)) == (edge.v / (L * L))) // edge is spacelike 90 | { 91 | return to_edge_idx(L, Edge{edge.u % (L * L), edge.v % (L * L)}) 92 | + 3 * L * L * uh; 93 | } 94 | else // NOLINT(llvm-else-after-return,readability-else-after-return) 95 | { 96 | uint32_t row = (edge.u / L) % L; 97 | uint32_t col = edge.u % L; 98 | return 3 * L * L * uh + 2 * L * L + L * row + col; 99 | } 100 | } 101 | 102 | [[nodiscard]] auto to_edge(int edge_index) const -> Edge 103 | { 104 | uint32_t L = L_; 105 | uint32_t h = edge_index / (3 * L * L); 106 | uint32_t layer_idx = edge_index % (3 * L * L); 107 | if(layer_idx >= 2 * L * L) // edge is in the time direction 108 | { 109 | auto [row, col] = UnionFindCPP::vertex_to_coord(L, layer_idx - 2 * L * L); 110 | return Edge{to_vertex_index(row, col, h), to_vertex_index(row, col, h + 1)}; 111 | } 112 | else // NOLINT(llvm-else-after-return,readability-else-after-return) 113 | { 114 | Edge e_2d = UnionFindCPP::to_edge(L, layer_idx); 115 | return Edge{e_2d.u + h * L * L, e_2d.v + h * L * L}; 116 | } 117 | } 118 | }; 119 | } // namespace UnionFindCPP 120 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(UnionFindCPP) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(LINK_WHAT_YOU_USE TRUE) 6 | set(BUILD_RPATH_USE_ORIGIN TRUE) 7 | 8 | # Available options 9 | option(ENABLE_AVX OFF) 10 | 11 | # Build options 12 | option(BUILD_EXAMPLES OFF) 13 | option(BUILD_TESTS ON) 14 | 15 | option(CLANG_TIDY OFF) 16 | 17 | ################################################################################ 18 | 19 | # Process options 20 | add_library(union_find_cpp_dependency INTERFACE) 21 | 22 | ## Process ENABLE_AVX 23 | if (ENABLE_AVX) 24 | target_compile_options(union_find_cpp_dependency INTERFACE 25 | $<$:-mavx;-mavx2;>) 26 | endif() 27 | 28 | ## Process CLANG_TIDY 29 | if (CLANG_TIDY) 30 | message(STATUS "Use Clang-Tidy") 31 | execute_process( 32 | COMMAND ${PROJECT_SOURCE_DIR}/build_utils/cpp_files.py --include-examples --include-tests --exclude-binding 33 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 34 | OUTPUT_VARIABLE UNIONFINDCPP_SOURCE_FILES) 35 | set(CMAKE_CXX_CLANG_TIDY clang-tidy-12;--line-filter=${UNIONFINDCPP_SOURCE_FILES};--extra-arg=-std=c++20) 36 | endif() 37 | 38 | ################################################################################ 39 | 40 | # Download nlohmann/json 41 | set(JSON_URL "https://github.com/nlohmann/json/releases/download/v3.10.4/json.hpp") 42 | set(JSON_DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/externals/nlohmann") 43 | set(JSON_DOWNLOAD_PATH "${JSON_DOWNLOAD_DIR}/json.hpp") 44 | 45 | if (NOT EXISTS "${JSON_DOWNLOAD_PATH}") 46 | message(STATUS "Downloading nlohmann/json.") 47 | file(MAKE_DIRECTORY "${JSON_DOWNLOAD_DIR}") 48 | file(DOWNLOAD "${JSON_URL}" "${JSON_DOWNLOAD_PATH}" STATUS JSON_DOWNLOAD_STATUS) 49 | 50 | # Separate the returned status code, and error message. 51 | list(GET JSON_DOWNLOAD_STATUS 0 STATUS_CODE) 52 | list(GET JSON_DOWNLOAD_STATUS 1 ERROR_MESSAGE) 53 | 54 | # Check if download was successful. 55 | if(${STATUS_CODE} EQUAL 0) 56 | message(STATUS "Download completed successfully!") 57 | else() 58 | message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") 59 | endif() 60 | endif() 61 | 62 | # Download Catch 63 | set(CATCH_URL "https://github.com/catchorg/Catch2/releases/download/v2.13.7/catch.hpp") 64 | set(CATCH_DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/externals") 65 | set(CATCH_DOWNLOAD_PATH "${CATCH_DOWNLOAD_DIR}/catch.hpp") 66 | 67 | if (NOT EXISTS "${CATCH_DOWNLOAD_PATH}") 68 | message(STATUS "Downloading Catch2.") 69 | file(DOWNLOAD "${CATCH_URL}" "${CATCH_DOWNLOAD_PATH}" STATUS CATCH_DOWNLOAD_STATUS) 70 | 71 | # Separate the returned status code, and error message. 72 | list(GET CATCH_DOWNLOAD_STATUS 0 STATUS_CODE) 73 | list(GET CATCH_DOWNLOAD_STATUS 1 ERROR_MESSAGE) 74 | 75 | # Check if download was successful. 76 | if(${STATUS_CODE} EQUAL 0) 77 | message(STATUS "Download completed successfully!") 78 | else() 79 | message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") 80 | endif() 81 | endif() 82 | 83 | ################################################################################ 84 | 85 | # Add robin-map 86 | add_subdirectory(externals/robin-map) 87 | 88 | target_include_directories(union_find_cpp_dependency INTERFACE "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}/externals") 89 | target_link_libraries(union_find_cpp_dependency INTERFACE tsl::robin_map) 90 | target_sources(union_find_cpp_dependency INTERFACE "src/utility.cpp") 91 | 92 | # Build Python binding 93 | if (BUILD_UNION_FIND_PY) 94 | add_subdirectory(externals/pybind11) 95 | pybind11_add_module(_union_find_py "binding/UnionFindPy.cpp") 96 | target_link_libraries(_union_find_py PRIVATE tsl::robin_map 97 | union_find_cpp_dependency) 98 | endif() 99 | 100 | if(BUILD_EXAMPLES) 101 | add_subdirectory(examples) 102 | endif() 103 | 104 | if(BUILD_TESTS) 105 | enable_testing() 106 | add_subdirectory(tests) 107 | endif() 108 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/toric_utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | 19 | #include "typedefs.hpp" 20 | #include "utility.hpp" 21 | 22 | #include 23 | 24 | namespace UnionFindCPP 25 | { 26 | inline auto is_horizontal(uint32_t L, Edge e) -> bool 27 | { 28 | return ((e.v - e.u) == 1) || ((e.v - e.u) == (L - 1)); 29 | } 30 | inline auto is_vertical(uint32_t L, Edge e) -> bool 31 | { 32 | return !is_horizontal(L, e); 33 | } 34 | inline auto lower(uint32_t L, Edge e) -> uint32_t // works only when vertical 35 | { 36 | if((e.v - e.u) == L) { return e.u; } 37 | // else 38 | return e.v; 39 | } 40 | inline auto upper(uint32_t L, Edge e) -> uint32_t 41 | { 42 | if((e.v - e.u) == L) { return e.v; } 43 | // else 44 | return e.u; 45 | } 46 | 47 | inline auto left(uint32_t /*L*/, Edge e) -> uint32_t // works only when horizontal 48 | { 49 | if((e.v - e.u) == 1) { return e.u; } 50 | // else 51 | return e.v; 52 | } 53 | inline auto right(uint32_t /*L*/, Edge e) -> uint32_t // works only when horizontal 54 | { 55 | if((e.v - e.u) == 1) { return e.v; } 56 | // else 57 | return e.u; 58 | } 59 | 60 | /** 61 | * This file contains functions utility functions for the Toric code. 62 | */ 63 | 64 | auto z_error_to_syndrome_x(uint32_t L, const ArrayXu& z_error) -> std::vector; 65 | auto x_error_to_syndrome_z(uint32_t L, const ArrayXu& x_error) -> std::vector; 66 | 67 | /* 68 | * if error_type is X, the output is error locations in the dual lattice. 69 | * @param error An array of length 2*L*L where element 1 indicates the error at the 70 | * given index 71 | * */ 72 | inline auto errors_to_syndromes(const uint32_t L, const ArrayXu& error, 73 | ErrorType error_type) -> std::vector 74 | { 75 | switch(error_type) 76 | { 77 | case ErrorType::X: 78 | return x_error_to_syndrome_z(L, error); 79 | case ErrorType::Z: 80 | return z_error_to_syndrome_x(L, error); 81 | } 82 | return {}; 83 | } 84 | 85 | constexpr auto to_vertex_index(uint32_t L, const uint32_t row, const uint32_t col) 86 | -> uint32_t 87 | { 88 | return (((row + L) % L)) * L + (col + L) % L; 89 | } 90 | 91 | inline auto vertex_to_coord(const uint32_t L, const uint32_t vidx) 92 | -> std::tuple 93 | { 94 | return std::make_tuple(vidx / L, vidx % L); 95 | } 96 | 97 | inline auto to_edge_idx(const uint32_t L, const Edge e) -> uint32_t 98 | { 99 | if(is_horizontal(L, e)) 100 | { 101 | auto u = left(L, e); 102 | const auto [row, col] = vertex_to_coord(L, u); 103 | return L * row + col + L * L; 104 | } 105 | // else 106 | { 107 | auto u = upper(L, e); 108 | const auto [row, col] = vertex_to_coord(L, u); 109 | return L * row + col; 110 | } 111 | } 112 | 113 | auto decoder_edge_to_qubit_idx(uint32_t L, Edge e, ErrorType error_type) -> uint32_t; 114 | 115 | auto to_edge(uint32_t L, uint32_t edge_index) -> Edge; 116 | 117 | class LatticeCubic; // forward def 118 | 119 | auto calc_syndromes(const LatticeCubic& lattice, const ArrayXXu& errors, 120 | ErrorType error_type) -> std::vector; 121 | 122 | void add_corrections(uint32_t L, const std::vector& corrections, ArrayXu& error, 123 | ErrorType error_type); 124 | 125 | auto logical_error(uint32_t L, const ArrayXu& error, ErrorType error_type) -> bool; 126 | 127 | auto has_logical_error(uint32_t L, ArrayXu& error_total, 128 | const std::vector& corrections, ErrorType error_type) 129 | -> bool; 130 | } // namespace UnionFindCPP 131 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/include/RootManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class RootManager 10 | { 11 | public: 12 | using Vertex = uint32_t; 13 | 14 | private: 15 | /* set of roots */ 16 | tsl::robin_set roots_; 17 | /* set of roots with odd parity */ 18 | tsl::robin_set odd_roots_; // root size comb 19 | /* key: root, value: size of the cluster */ 20 | tsl::robin_map size_; 21 | /* key: root, value: parity of the cluster */ 22 | tsl::robin_map parity_; 23 | 24 | public: 25 | class SizeProxy 26 | { 27 | private: 28 | RootManager& mgr_; 29 | Vertex root_; 30 | 31 | public: 32 | SizeProxy(RootManager& mgr, Vertex root) : mgr_{mgr}, root_{root} { } 33 | 34 | // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) 35 | operator uint32_t() const 36 | { 37 | if(!mgr_.is_root(root_)) { return 0; } 38 | const auto it = mgr_.size_.find(root_); 39 | return it->second; 40 | } 41 | 42 | auto operator=(int new_size) -> SizeProxy& 43 | { 44 | mgr_.size_[root_] = new_size; 45 | return *this; 46 | } 47 | 48 | auto operator++() -> SizeProxy& 49 | { 50 | auto it = mgr_.size_.find(root_); 51 | ++it.value(); 52 | return *this; 53 | } 54 | }; 55 | 56 | friend class SizeProxy; 57 | 58 | void initialize_roots(const std::vector& roots) 59 | { 60 | const auto n_reserve = 2 * roots.size(); 61 | roots_.reserve(n_reserve); 62 | odd_roots_.reserve(n_reserve); 63 | size_.reserve(n_reserve); 64 | parity_.reserve(n_reserve); 65 | for(const auto root : roots) 66 | { 67 | roots_.emplace(root); 68 | odd_roots_.emplace(root); 69 | size_.emplace(root, 1); 70 | parity_.emplace(root, 1); 71 | } 72 | } 73 | 74 | inline auto size(Vertex root) -> SizeProxy { return SizeProxy(*this, root); } 75 | 76 | [[nodiscard]] inline auto size(Vertex root) const -> uint32_t 77 | { 78 | if(!is_root(root)) { return 0; } 79 | const auto it = size_.find(root); 80 | return it->second; 81 | } 82 | 83 | [[nodiscard]] inline auto parity(Vertex root) const -> uint32_t 84 | { 85 | auto it = parity_.find(root); 86 | if(it == parity_.end()) { return 0; } 87 | return it->second; 88 | } 89 | 90 | [[nodiscard]] inline auto is_root(Vertex v) const -> bool 91 | { 92 | return roots_.count(v) == 1; 93 | } 94 | 95 | [[nodiscard]] inline auto is_odd_root(Vertex v) const -> bool 96 | { 97 | return odd_roots_.count(v) == 1; 98 | } 99 | 100 | // size of the cluster of root1 is larger than that of root2 101 | void merge(Vertex root1, Vertex root2) 102 | { 103 | const auto new_parity = parity(root1) + parity(root2); 104 | 105 | if((new_parity % 2) == 1) { odd_roots_.emplace(root1); } 106 | else 107 | { 108 | odd_roots_.erase(root1); 109 | } 110 | 111 | size_[root1] += size(root2); 112 | parity_[root1] = new_parity; 113 | 114 | odd_roots_.erase(root2); 115 | 116 | size_.erase(root2); 117 | parity_.erase(root2); 118 | roots_.erase(root2); 119 | } 120 | 121 | void remove(Vertex root) 122 | { 123 | odd_roots_.erase(root); 124 | roots_.erase(root); 125 | size_.erase(root); 126 | parity_.erase(root); 127 | } 128 | 129 | [[nodiscard]] auto isempty_odd_root() const -> bool { return odd_roots_.empty(); } 130 | 131 | void clear() 132 | { 133 | tsl::robin_set().swap(roots_); 134 | tsl::robin_set().swap(odd_roots_); 135 | tsl::robin_map().swap(size_); 136 | tsl::robin_map().swap(parity_); 137 | } 138 | 139 | [[nodiscard]] auto odd_roots() const& -> const tsl::robin_set& 140 | { 141 | return odd_roots_; 142 | } 143 | [[nodiscard]] auto odd_roots() && -> tsl::robin_set { return odd_roots_; } 144 | 145 | void print(std::ostream& os) const 146 | { 147 | nlohmann::json p; 148 | 149 | auto roots_j = nlohmann::json::array(); 150 | for(auto root : roots_) 151 | { 152 | roots_j.emplace_back( 153 | nlohmann::json::array({root, size_.at(root), parity_.at(root)})); 154 | } 155 | p["roots"] = roots_j; 156 | p["odd_roots"] = odd_roots_; 157 | 158 | os << p << std::endl; 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | from pathlib import Path 16 | import subprocess 17 | import json 18 | 19 | currdir = Path(__file__).resolve().parent # PROJECT_SOURCE_DIR/docs 20 | PROJECT_SOURCE_DIR = currdir.parent 21 | 22 | def obtain_cpp_files(): 23 | script_path = PROJECT_SOURCE_DIR.joinpath('UnionFindPy/cpp/build_utils/cpp_files.py') 24 | 25 | if not script_path.exists(): 26 | print('The project directory structure is corrupted.') 27 | sys.exit(1) 28 | 29 | p = subprocess.Popen([script_path], shell=False, stdin=None, 30 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 31 | out, _ = p.communicate() 32 | parsed = json.loads(out) 33 | 34 | file_list = [] 35 | for item in parsed: 36 | file_list.append('../' + str(Path(item['name']).relative_to(PROJECT_SOURCE_DIR))) 37 | return file_list 38 | 39 | CPP_FILES = obtain_cpp_files() 40 | 41 | sys.path.insert(0, PROJECT_SOURCE_DIR) 42 | sys.path.insert(0, os.path.abspath('_ext')) 43 | 44 | 45 | # -- Project information ----------------------------------------------------- 46 | 47 | project = 'UnionFind' 48 | copyright = '2021, Chae-Yeun Park' 49 | author = 'Chae-Yeun Park' 50 | 51 | 52 | # -- General configuration --------------------------------------------------- 53 | 54 | # Add any Sphinx extension module names here, as strings. They can be 55 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 56 | # ones. 57 | extensions = [ 58 | 'breathe', 59 | 'exhale', 60 | 'sphinx.ext.autodoc' 61 | ] 62 | 63 | # Setup the breathe extension 64 | breathe_projects = { 65 | "UnionFindPy": "./_doxygen/xml" 66 | } 67 | breathe_default_project = "UnionFindPy" 68 | 69 | # Setup the exhale extension 70 | exhale_args = { 71 | # These arguments are required 72 | "containmentFolder": "./api", 73 | "rootFileName": "library_root.rst", 74 | "doxygenStripFromPath": "..", 75 | # Heavily encouraged optional argument (see docs) 76 | "rootFileTitle": "C++ Library API", 77 | # Suggested optional arguments 78 | "createTreeView": True, 79 | # TIP: if using the sphinx-bootstrap-theme, you need 80 | # "treeViewIsBootstrap": True, 81 | "exhaleExecutesDoxygen": True, 82 | "exhaleDoxygenStdin": "INPUT = " + ' '.join(CPP_FILES) 83 | } 84 | 85 | # Tell sphinx what the primary language being documented is. 86 | primary_domain = 'cpp' 87 | 88 | # Tell sphinx what the pygments highlight language should be. 89 | highlight_language = 'cpp' 90 | 91 | # Add any paths that contain templates here, relative to this directory. 92 | templates_path = ['_templates'] 93 | 94 | # List of patterns, relative to source directory, that match files and 95 | # directories to ignore when looking for source files. 96 | # This pattern also affects html_static_path and html_extra_path. 97 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'UnionFindPy/cpp'] 98 | 99 | 100 | # -- Options for HTML output ------------------------------------------------- 101 | 102 | # The theme to use for HTML and HTML Help pages. See the documentation for 103 | # a list of builtin themes. 104 | # 105 | html_theme = 'sphinx_rtd_theme' 106 | 107 | # Add any paths that contain custom static files (such as style sheets) here, 108 | # relative to this directory. They are copied after the builtin static files, 109 | # so a file named "default.css" will overwrite the builtin "default.css". 110 | html_static_path = ['_static'] 111 | 112 | html_context = { 113 | "display_github": True, # Integrate GitHub 114 | "github_user": "chaeyeunpark", # Username 115 | "github_repo": "UnionFind", # Repo name 116 | "github_version": "main", # Version 117 | "conf_py_path": "/docs/", # Path in the checkout to the docs root 118 | } 119 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/run_uf_2d_bitflip.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "Decoder.hpp" 18 | #include "Lattice2D.hpp" 19 | #include "LazyDecoder.hpp" 20 | #include "error_utils.hpp" 21 | #include "runner_utils.hpp" 22 | #include "toric_utils.hpp" 23 | 24 | #include 25 | #ifdef USE_MPI 26 | #pragma message("Build with MPI") 27 | #include 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | auto main(int argc, char* argv[]) -> int 36 | { 37 | namespace chrono = std::chrono; 38 | using UnionFindCPP::Decoder, UnionFindCPP::ErrorType, UnionFindCPP::Lattice2D, 39 | UnionFindCPP::LazyDecoder, UnionFindCPP::NoiseType; 40 | 41 | const auto noise_type = NoiseType::X; 42 | const uint32_t n_iter = 1'000'000; 43 | 44 | int mpi_rank = 0; 45 | int mpi_size = 1; 46 | 47 | #ifdef USE_MPI 48 | MPI_Init(&argc, &argv); 49 | MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); 50 | MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); 51 | #endif 52 | 53 | uint32_t L = 0; 54 | double p = 0.0; 55 | try 56 | { 57 | std::tie(L, p) = parse_args(argc, argv); 58 | } 59 | catch(std::exception& e) 60 | { 61 | std::cout << e.what() << std::endl; 62 | return 1; 63 | } 64 | 65 | std::random_device rd; 66 | std::default_random_engine re{rd()}; 67 | 68 | #ifdef USE_MPI 69 | fmt::print(stderr, "Processing at rank = {}, size = {}\n", mpi_rank, mpi_size); 70 | #endif 71 | 72 | unsigned int n_success = 0U; 73 | chrono::microseconds node_dur{}; 74 | #ifdef USE_LAZY 75 | LazyDecoder lazy_decoder(L); 76 | #endif 77 | 78 | Decoder decoder(L); 79 | for(uint32_t k = mpi_rank; k < n_iter; k += mpi_size) 80 | { 81 | auto [x_errors, z_errors] = create_errors(re, decoder.num_edges(), p, noise_type); 82 | 83 | auto synd_x = errors_to_syndromes(L, x_errors, ErrorType::X); 84 | 85 | auto start = chrono::high_resolution_clock::now(); 86 | #ifdef USE_LAZY 87 | // Process lazy decoder 88 | auto [success_x, decoding_x] = lazy_decoder.decode(synd_x); 89 | if(!success_x) 90 | { 91 | decoder.clear(); 92 | auto decoding_uf = decoder.decode(synd_x); 93 | decoding_x.insert(decoding_x.end(), decoding_uf.begin(), decoding_uf.end()); 94 | } 95 | 96 | #else 97 | decoder.clear(); 98 | auto decoding_x = decoder.decode(synd_x); 99 | #endif 100 | auto end = chrono::high_resolution_clock::now(); 101 | 102 | add_corrections(L, decoding_x, x_errors, ErrorType::X); 103 | if(!logical_error(L, x_errors, ErrorType::X)) { ++n_success; } 104 | 105 | auto dur = chrono::duration_cast(end - start); 106 | node_dur += dur; 107 | } 108 | 109 | #ifdef USE_MPI 110 | MPI_Barrier(MPI_COMM_WORLD); 111 | 112 | unsigned long dur_in_microseconds = node_dur.count(); 113 | unsigned long total_dur_in_microseconds = 0; 114 | MPI_Allreduce(&dur_in_microseconds, &total_dur_in_microseconds, 1, MPI_UNSIGNED_LONG, 115 | MPI_SUM, MPI_COMM_WORLD); 116 | 117 | fmt::print("rank: {}, dur_in_microseconds: {}, total_dur_in_microsecond: {}\n", 118 | mpi_rank, dur_in_microseconds, total_dur_in_microseconds); 119 | 120 | unsigned int total_success = 0; 121 | MPI_Allreduce(&n_success, &total_success, 1, MPI_UNSIGNED, MPI_SUM, MPI_COMM_WORLD); 122 | #else 123 | unsigned long total_dur_in_microseconds = node_dur.count(); 124 | unsigned int total_success = n_success; 125 | #endif 126 | 127 | if(mpi_rank == 0) 128 | { 129 | save_to_json(L, p, static_cast(total_dur_in_microseconds) / n_iter, 130 | static_cast(total_success) / n_iter); 131 | } 132 | 133 | #ifdef USE_MPI 134 | MPI_Finalize(); 135 | #endif 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/binding/UnionFindPy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | 18 | #include "Decoder.hpp" 19 | #include "LatticeFromParity.hpp" 20 | 21 | #include "pybind11/numpy.h" 22 | #include "pybind11/pybind11.h" 23 | #include "pybind11/stl.h" 24 | 25 | #include 26 | 27 | // NOLINTBEGIN(cppcoreguidelines-*) 28 | void free_arr_uint32(void* p) 29 | { 30 | uint32_t* p_int = reinterpret_cast(p); 31 | delete[] p_int; 32 | } 33 | // NOLINTEND(cppcoreguidelines-*) 34 | 35 | namespace py = pybind11; 36 | 37 | // NOLINTNEXTLINE(cppcoreguidelines-*) 38 | PYBIND11_MODULE(_union_find_py, m) 39 | { 40 | using UnionFindFromParity = UnionFindCPP::Decoder; 41 | py::class_(m, "DecoderFromParity") 42 | .def(py::init( 43 | [](int num_parities, int num_qubits, 44 | py::array_t col_indices, 45 | py::array_t indptr) 46 | { 47 | if(num_parities <= 0) 48 | { 49 | throw std::invalid_argument( 50 | "Number of partiy operators must be larger than 0"); 51 | } 52 | if(num_qubits <= 0) 53 | { 54 | throw std::invalid_argument("Number of qubits must be larger than 0"); 55 | } 56 | // check the given dimension is correct 57 | return UnionFindFromParity(static_cast(num_parities), 58 | static_cast(num_qubits), 59 | static_cast(col_indices.request().ptr), 60 | static_cast(indptr.request().ptr)); 61 | })) 62 | .def(py::init( 63 | [](int num_parities, int num_qubits, 64 | py::array_t col_indices, 65 | py::array_t indptr, 66 | int repetitions) 67 | { 68 | if(num_parities <= 0) 69 | { 70 | throw std::invalid_argument( 71 | "Number of partiy operators must be larger than 0"); 72 | } 73 | if(num_qubits <= 0) 74 | { 75 | throw std::invalid_argument("Number of qubits must be larger than 0"); 76 | } 77 | if(repetitions <= 1) 78 | { 79 | throw std::invalid_argument("Repetitions must be larger than 1"); 80 | } 81 | // check the given dimension is correct 82 | return UnionFindFromParity(static_cast(num_parities), 83 | static_cast(num_qubits), 84 | static_cast(col_indices.request().ptr), 85 | static_cast(indptr.request().ptr), 86 | static_cast(repetitions)); 87 | })) 88 | .def("clear", &UnionFindFromParity::clear, "Clear decoder's internal data") 89 | .def_property_readonly("num_edges", &UnionFindFromParity::num_edges, 90 | "Get total number of edges (qubits) of the decoder") 91 | .def_property_readonly( 92 | "num_vertices", &UnionFindFromParity::num_vertices, 93 | "Get total number of vertices (parity operators) of the decoder") 94 | .def( 95 | "decode", 96 | [](UnionFindFromParity& decoder, 97 | std::vector syndromes) -> py::array_t 98 | { 99 | if(decoder.num_vertices() != syndromes.size()) 100 | { 101 | throw std::invalid_argument("Size of syndromes should be the same as " 102 | "the size of vertices"); 103 | } 104 | auto res = decoder.decode(syndromes); 105 | 106 | uint32_t* corrections = new uint32_t[decoder.num_edges()]; 107 | memset(corrections, 0, sizeof(uint32_t) * decoder.num_edges()); 108 | for(size_t i = 0; i < res.size(); ++i) 109 | { 110 | corrections[decoder.edge_idx(res[i])] = 1; 111 | } 112 | py::capsule free_when_done(corrections, free_arr_uint32); 113 | 114 | return py::array_t({(int64_t)decoder.num_edges()}, corrections, 115 | free_when_done); 116 | }, 117 | "Decode the given syndroms"); 118 | } 119 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/error_utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #pragma once 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include "typedefs.hpp" 24 | #include "utility.hpp" 25 | 26 | /** 27 | * This file contains lattice independent functions for generating 28 | * qubit and syndrome measurement errors. 29 | */ 30 | 31 | namespace UnionFindCPP 32 | { 33 | enum class NoiseType 34 | { 35 | Depolarizing = 0, 36 | Independent, 37 | X, 38 | Z 39 | }; 40 | 41 | template 42 | auto create_errors(RandomEngine& re, const uint32_t num_qubits, const double p, 43 | NoiseType noise_type) -> std::pair 44 | { 45 | ArrayXu x_error = ArrayXu::Zero(num_qubits); 46 | ArrayXu z_error = ArrayXu::Zero(num_qubits); 47 | std::uniform_real_distribution<> urd(0.0, 1.0); 48 | 49 | switch(noise_type) 50 | { 51 | case NoiseType::Depolarizing: 52 | { 53 | std::uniform_int_distribution<> uid(0, 2); 54 | for(int i = 0; i < num_qubits; ++i) 55 | { 56 | if(!(urd(re) < p)) { continue; } 57 | switch(uid(re)) 58 | { 59 | case 0: 60 | x_error[i] = 1; 61 | break; 62 | case 1: 63 | z_error[i] = 1; 64 | break; 65 | case 2: 66 | x_error[i] = 1; 67 | z_error[i] = 1; 68 | break; 69 | } 70 | } 71 | } 72 | break; 73 | 74 | case NoiseType::Independent: 75 | for(int i = 0; i < num_qubits; i++) { x_error[i] = (urd(re) < p) ? 1 : 0; } 76 | for(int i = 0; i < num_qubits; i++) { z_error[i] = (urd(re) < p) ? 1 : 0; } 77 | break; 78 | 79 | case NoiseType::X: 80 | for(int i = 0; i < num_qubits; i++) { x_error[i] = (urd(re) < p) ? 1 : 0; } 81 | break; 82 | 83 | case NoiseType::Z: 84 | for(int i = 0; i < num_qubits; i++) { z_error[i] = (urd(re) < p) ? 1 : 0; } 85 | break; 86 | } 87 | 88 | return std::make_pair(x_error, z_error); 89 | } 90 | 91 | template 92 | auto create_measurement_errors(RandomEngine& re, const uint32_t num_qubits, 93 | const uint32_t repetition, const double p, 94 | NoiseType noise_type) 95 | { 96 | ArrayXXu measurement_error_x = ArrayXXu::Zero(num_qubits, repetition); 97 | ArrayXXu measurement_error_z = ArrayXXu::Zero(num_qubits, repetition); 98 | std::uniform_real_distribution<> urd(0.0, 1.0); 99 | 100 | for(int h = 0; h < repetition - 1; ++h) // perfect measurement in the last round 101 | { 102 | const auto [layer_error_x, layer_error_z] 103 | = create_errors(re, num_qubits, p, noise_type); 104 | measurement_error_x.col(h) = layer_error_x; 105 | measurement_error_z.col(h) = layer_error_z; 106 | } 107 | 108 | return std::make_pair(measurement_error_x, measurement_error_z); 109 | } 110 | 111 | template 112 | auto generate_errors(const uint32_t num_qubits, const uint32_t repetition, double p, 113 | RandomEngine& re, NoiseType noise_type) 114 | { 115 | ArrayXXu qubit_errors_x = ArrayXXu::Zero(num_qubits, repetition); 116 | ArrayXXu qubit_errors_z = ArrayXXu::Zero(num_qubits, repetition); 117 | 118 | for(int r = 0; r < repetition; ++r) 119 | { 120 | auto [layer_error_x, layer_error_z] 121 | = create_errors(re, num_qubits, p, noise_type); 122 | qubit_errors_x.col(r) = layer_error_x; 123 | qubit_errors_z.col(r) = layer_error_z; 124 | } 125 | 126 | // cumsum 127 | for(int h = 1; h < repetition; ++h) 128 | { 129 | qubit_errors_x.col(h) += qubit_errors_x.col(h - 1); 130 | qubit_errors_z.col(h) += qubit_errors_z.col(h - 1); 131 | } 132 | qubit_errors_x = qubit_errors_x.unaryExpr([](uint32_t x) { return x % 2; }); 133 | qubit_errors_z = qubit_errors_z.unaryExpr([](uint32_t x) { return x % 2; }); 134 | 135 | return std::make_pair(qubit_errors_x, qubit_errors_z); 136 | } 137 | 138 | void add_measurement_noise(uint32_t L, std::vector& syndromes, 139 | const ArrayXXu& measurement_error); 140 | 141 | void layer_syndrome_diff(uint32_t L, std::vector& syndromes); 142 | 143 | } // namespace UnionFindCPP 144 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/run_uf_3d_bitflip.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "Decoder.hpp" 18 | #include "LatticeCubic.hpp" 19 | #include "LazyDecoder.hpp" 20 | #include "error_utils.hpp" 21 | #include "runner_utils.hpp" 22 | #include "toric_utils.hpp" 23 | 24 | #include 25 | #ifdef USE_MPI 26 | #pragma message("Build with MPI") 27 | #include 28 | #endif 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | auto main(int argc, char* argv[]) -> int 37 | { 38 | namespace chrono = std::chrono; 39 | using UnionFindCPP::ArrayXu, UnionFindCPP::Decoder, UnionFindCPP::ErrorType, 40 | UnionFindCPP::LatticeCubic, UnionFindCPP::LazyDecoder, UnionFindCPP::NoiseType, 41 | UnionFindCPP::add_measurement_noise, UnionFindCPP::add_corrections, 42 | UnionFindCPP::layer_syndrome_diff; 43 | 44 | const auto noise_type = NoiseType::X; 45 | const uint32_t n_iter = 1'000'000; 46 | 47 | int mpi_rank = 0; 48 | int mpi_size = 1; 49 | 50 | #ifdef USE_MPI 51 | MPI_Init(&argc, &argv); 52 | MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); 53 | MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); 54 | #endif 55 | 56 | uint32_t L = 0; 57 | double p = 0.0; 58 | try 59 | { 60 | std::tie(L, p) = parse_args(argc, argv); 61 | } 62 | catch(std::exception& e) 63 | { 64 | std::cout << e.what() << std::endl; 65 | return 1; 66 | } 67 | 68 | std::random_device rd; 69 | std::default_random_engine re{rd()}; 70 | 71 | #ifdef USE_MPI 72 | fmt::print(stderr, "Processing at rank = {}, size = {}\n", mpi_rank, mpi_size); 73 | #endif 74 | 75 | unsigned int n_success = 0U; 76 | chrono::microseconds node_dur{}; 77 | #ifdef USE_LAZY 78 | LazyDecoder lazy_decoder(L); 79 | #endif 80 | LatticeCubic lattice(L); 81 | Decoder decoder(L); 82 | for(uint32_t k = mpi_rank; k < n_iter; k += mpi_size) 83 | { 84 | auto [error_x, error_z] = generate_errors(2 * L * L, L, p, re, noise_type); 85 | 86 | auto synd_x = calc_syndromes(lattice, error_x, ErrorType::X); 87 | 88 | ArrayXu error_total_x = error_x.col(L - 1); 89 | 90 | const auto [measurement_error_x, measurement_error_z] 91 | = create_measurement_errors(re, L * L, L, p, noise_type); 92 | 93 | add_measurement_noise(L, synd_x, measurement_error_x); 94 | 95 | layer_syndrome_diff(L, synd_x); 96 | 97 | auto start = chrono::high_resolution_clock::now(); 98 | #ifdef USE_LAZY 99 | // Process lazy decoder 100 | auto [success_x, decoding_x] = lazy_decoder.decode(synd_x); 101 | if(!success_x) 102 | { 103 | decoder.clear(); 104 | auto decoding_uf = decoder.decode(synd_x); 105 | decoding_x.insert(decoding_x.end(), decoding_uf.begin(), decoding_uf.end()); 106 | } 107 | 108 | #else 109 | decoder.clear(); 110 | auto decoding_x = decoder.decode(synd_x); 111 | #endif 112 | auto end = chrono::high_resolution_clock::now(); 113 | 114 | if(!has_logical_error(L, error_total_x, decoding_x, ErrorType::X)) 115 | { 116 | ++n_success; 117 | } 118 | 119 | auto dur = chrono::duration_cast(end - start); 120 | node_dur += dur; 121 | } 122 | 123 | #ifdef USE_MPI 124 | MPI_Barrier(MPI_COMM_WORLD); 125 | 126 | unsigned long dur_in_microseconds = node_dur.count(); 127 | unsigned long total_dur_in_microseconds = 0; 128 | MPI_Allreduce(&dur_in_microseconds, &total_dur_in_microseconds, 1, MPI_UNSIGNED_LONG, 129 | MPI_SUM, MPI_COMM_WORLD); 130 | 131 | fmt::print("rank: {}, dur_in_microseconds: {}, total_dur_in_microsecond: {}\n", 132 | mpi_rank, dur_in_microseconds, total_dur_in_microseconds); 133 | 134 | unsigned int total_success = 0; 135 | MPI_Allreduce(&n_success, &total_success, 1, MPI_UNSIGNED, MPI_SUM, MPI_COMM_WORLD); 136 | #else 137 | unsigned long total_dur_in_microseconds = node_dur.count(); 138 | unsigned int total_success = n_success; 139 | #endif 140 | 141 | if(mpi_rank == 0) 142 | { 143 | save_to_json(L, p, static_cast(total_dur_in_microseconds) / n_iter, 144 | static_cast(total_success) / n_iter); 145 | } 146 | 147 | #ifdef USE_MPI 148 | MPI_Finalize(); 149 | #endif 150 | return 0; 151 | } 152 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import setuptools 4 | import sys 5 | import subprocess 6 | import shutil 7 | from pathlib import Path 8 | from setuptools import setup, Extension, find_packages 9 | from setuptools.command.build_ext import build_ext 10 | 11 | from distutils.util import get_platform 12 | 13 | class CMakeExtension(Extension): 14 | def __init__(self, name, sourcedir = ""): 15 | Extension.__init__(self, name, sources = []) 16 | self.sourcedir = Path(sourcedir).absolute() 17 | 18 | 19 | class CMakeBuild(build_ext): 20 | """ 21 | This class is built upon https://github.com/diegoferigo/cmake-build-extension/blob/master/src/cmake_build_extension/build_extension.py and https://github.com/pybind/cmake_example/blob/master/setup.py 22 | """ 23 | 24 | user_options = build_ext.user_options + [ 25 | ("define=", "D", "Define variables for CMake") 26 | ] 27 | 28 | def initialize_options(self): 29 | super().initialize_options() 30 | self.define = None 31 | 32 | def finalize_options(self): 33 | # Parse the custom CMake options and store them in a new attribute 34 | defines = [] if self.define is None else self.define.split(";") 35 | self.cmake_defines = [f"-D{define}" for define in defines] 36 | 37 | super().finalize_options() 38 | 39 | def build_extension(self, ext: CMakeExtension): 40 | extdir = str(Path(self.get_ext_fullpath(ext.name)).parent.absolute()) 41 | 42 | debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug 43 | cfg = "Debug" if debug else "Release" 44 | ninja_path = str(shutil.which('ninja')) 45 | 46 | # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON 47 | configure_args = [ 48 | "-GNinja", 49 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", 50 | f"-DPYTHON_EXECUTABLE={sys.executable}", 51 | f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm 52 | f"-DCMAKE_MAKE_PROGRAM={ninja_path}", 53 | ] 54 | configure_args += self.cmake_defines 55 | 56 | build_args = [] 57 | 58 | 59 | # Add more platform dependent options 60 | if platform.system() == "Darwin": 61 | pass 62 | elif platform.system() == "Linux": 63 | if platform.machine() == "x86_64": 64 | configure_args += [ 65 | "-DENABLE_AVX=ON" 66 | ] # Enable AVX if x64 on Linux 67 | elif platform.system() == "Windows": 68 | pass 69 | else: 70 | raise RuntimeError(f"Unsupported '{platform.system()}' platform") 71 | 72 | if not Path(self.build_temp).exists(): 73 | os.makedirs(self.build_temp) 74 | 75 | subprocess.check_call( 76 | ["cmake", str(ext.sourcedir)] + configure_args, cwd = self.build_temp 77 | ) 78 | subprocess.check_call( 79 | ["cmake", "--build", "."] + build_args, cwd = self.build_temp 80 | ) 81 | 82 | 83 | with open("UnionFindPy/_version.py") as f: 84 | version = f.readlines()[-1].split()[-1].strip("\"'") 85 | 86 | requirements = [ 87 | "ninja" 88 | ] 89 | 90 | info = { 91 | "name": "UnionFindPy", 92 | "version": version, 93 | "maintainer": "Chae-Yeun Park", 94 | "maintainer_email": "chae.yeun.park@gmail.com", 95 | "url": "https://github.com/chaeyeunpark/UnionFind", 96 | "license": "GNU Lesser General Public License v2.1", 97 | "packages": find_packages(where="."), 98 | "package_data": {"UnionFindPy": ["cpp/*"]}, 99 | "entry_points": { 100 | }, 101 | "description": "C++ implemenation of UnionFind with a python binding", 102 | "long_description": open("README.rst").read(), 103 | "long_description_content_type": "text/x-rst", 104 | "provides": ["UnionFindPy"], 105 | "install_requires": requirements, 106 | "ext_modules": [CMakeExtension("_union_find_py")], 107 | "cmdclass": {"build_ext": CMakeBuild}, 108 | "ext_package": "UnionFindPy" 109 | } 110 | 111 | classifiers = [ 112 | "Development Status :: 2 - Pre-Alpha", 113 | "Environment :: Console", 114 | "Intended Audience :: Science/Research", 115 | "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", 116 | "Natural Language :: English", 117 | "Operating System :: POSIX", 118 | "Operating System :: MacOS :: MacOS X", 119 | "Operating System :: POSIX :: Linux", 120 | "Operating System :: Microsoft :: Windows", 121 | "Programming Language :: Python", 122 | "Programming Language :: Python :: 3", 123 | "Programming Language :: Python :: 3.7", 124 | "Programming Language :: Python :: 3.8", 125 | "Programming Language :: Python :: 3.9", 126 | "Programming Language :: Python :: 3 :: Only", 127 | "Topic :: Scientific/Engineering :: Physics", 128 | ] 129 | 130 | setup(classifiers=classifiers, **(info)) 131 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: None 7 | AlignConsecutiveAssignments: None 8 | AlignConsecutiveBitFields: None 9 | AlignConsecutiveDeclarations: None 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: false 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: Empty 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: Inline 19 | AllowShortLambdasOnASingleLine: Inline 20 | AllowShortIfStatementsOnASingleLine: Never 21 | AllowShortLoopsOnASingleLine: false 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: MultiLine 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: true 29 | BinPackParameters: true 30 | BraceWrapping: 31 | AfterCaseLabel: true 32 | AfterClass: true 33 | AfterControlStatement: Always 34 | AfterEnum: true 35 | AfterFunction: true 36 | AfterNamespace: true 37 | AfterObjCDeclaration: true 38 | AfterStruct: true 39 | AfterUnion: true 40 | AfterExternBlock: true 41 | BeforeCatch: true 42 | BeforeElse: true 43 | BeforeLambdaBody: true 44 | BeforeWhile: true 45 | IndentBraces: false 46 | SplitEmptyFunction: false 47 | SplitEmptyRecord: false 48 | SplitEmptyNamespace: false 49 | BreakBeforeBinaryOperators: All 50 | BreakBeforeConceptDeclarations: false # For C++20 51 | BreakBeforeBraces: Allman 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: true 55 | BreakConstructorInitializers: BeforeColon 56 | BreakAfterJavaFieldAnnotations: false 57 | BreakStringLiterals: true 58 | ColumnLimit: 90 59 | CommentPragmas: '^ IWYU pragma:' 60 | CompactNamespaces: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | EmptyLineBeforeAccessModifier: LogicalBlock 68 | ExperimentalAutoDetectBinPacking: false 69 | FixNamespaceComments: false 70 | ForEachMacros: 71 | - foreach 72 | - Q_FOREACH 73 | - BOOST_FOREACH 74 | StatementAttributeLikeMacros: 75 | - Q_EMIT 76 | IncludeBlocks: Preserve 77 | IncludeCategories: 78 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 79 | Priority: 2 80 | SortPriority: 0 81 | CaseSensitive: false 82 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 83 | Priority: 3 84 | SortPriority: 0 85 | CaseSensitive: false 86 | - Regex: '.*' 87 | Priority: 1 88 | SortPriority: 0 89 | CaseSensitive: false 90 | IncludeIsMainRegex: '(Test)?$' 91 | IncludeIsMainSourceRegex: '' 92 | IndentCaseLabels: false 93 | IndentCaseBlocks: false 94 | IndentGotoLabels: true 95 | IndentPPDirectives: None 96 | IndentExternBlock: AfterExternBlock 97 | IndentRequires: false 98 | IndentWidth: 4 99 | IndentWrappedFunctionNames: false 100 | InsertTrailingCommas: None 101 | JavaScriptQuotes: Leave 102 | JavaScriptWrapImports: true 103 | KeepEmptyLinesAtTheStartOfBlocks: true 104 | MacroBlockBegin: '' 105 | MacroBlockEnd: '' 106 | MaxEmptyLinesToKeep: 1 107 | NamespaceIndentation: Inner 108 | ObjCBinPackProtocolList: Auto 109 | ObjCBlockIndentWidth: 4 110 | ObjCBreakBeforeNestedBlockParam: true 111 | ObjCSpaceAfterProperty: true 112 | ObjCSpaceBeforeProtocolList: true 113 | PenaltyBreakAssignment: 2 114 | PenaltyBreakBeforeFirstCallParameter: 19 115 | PenaltyBreakComment: 300 116 | PenaltyBreakFirstLessLess: 120 117 | PenaltyBreakString: 1000 118 | PenaltyBreakTemplateDeclaration: 10 119 | PenaltyExcessCharacter: 1000000 120 | PenaltyReturnTypeOnItsOwnLine: 60 121 | PenaltyIndentedWhitespace: 0 122 | PointerAlignment: Left 123 | ReflowComments: true 124 | SortIncludes: true 125 | SortJavaStaticImport: Before 126 | SortUsingDeclarations: true 127 | SpaceAfterCStyleCast: false 128 | SpaceAfterLogicalNot: false 129 | SpaceAfterTemplateKeyword: false 130 | SpaceBeforeAssignmentOperators: true 131 | SpaceBeforeCaseColon: false 132 | SpaceBeforeCpp11BracedList: false 133 | SpaceBeforeCtorInitializerColon: true 134 | SpaceBeforeInheritanceColon: true 135 | SpaceBeforeParens: Never 136 | SpaceAroundPointerQualifiers: Default 137 | SpaceBeforeRangeBasedForLoopColon: true 138 | SpaceInEmptyBlock: true 139 | SpaceInEmptyParentheses: false 140 | SpacesBeforeTrailingComments: 1 141 | SpacesInAngles: false 142 | SpacesInConditionalStatement: false 143 | SpacesInContainerLiterals: true 144 | SpacesInCStyleCastParentheses: false 145 | SpacesInParentheses: false 146 | SpacesInSquareBrackets: false 147 | SpaceBeforeSquareBrackets: false 148 | BitFieldColonSpacing: Both 149 | Standard: Latest 150 | StatementMacros: 151 | - Q_UNUSED 152 | - QT_REQUIRE_VERSION 153 | TabWidth: 4 154 | UseCRLF: false 155 | UseTab: Always 156 | WhitespaceSensitiveMacros: 157 | - STRINGIZE 158 | - PP_STRINGIZE 159 | - BOOST_PP_STRINGIZE 160 | - NS_SWIFT_NAME 161 | - CF_SWIFT_NAME 162 | ... 163 | 164 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: None 7 | AlignConsecutiveAssignments: None 8 | AlignConsecutiveBitFields: None 9 | AlignConsecutiveDeclarations: None 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: Always 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: Inline 19 | AllowShortLambdasOnASingleLine: Inline 20 | AllowShortIfStatementsOnASingleLine: WithoutElse 21 | AllowShortLoopsOnASingleLine: true 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: MultiLine 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: true 29 | BinPackParameters: true 30 | BraceWrapping: 31 | AfterCaseLabel: true 32 | AfterClass: true 33 | AfterControlStatement: Always 34 | AfterEnum: true 35 | AfterFunction: true 36 | AfterNamespace: true 37 | AfterObjCDeclaration: true 38 | AfterStruct: true 39 | AfterUnion: true 40 | AfterExternBlock: true 41 | BeforeCatch: true 42 | BeforeElse: true 43 | BeforeLambdaBody: true 44 | BeforeWhile: true 45 | IndentBraces: false 46 | SplitEmptyFunction: false 47 | SplitEmptyRecord: false 48 | SplitEmptyNamespace: false 49 | BreakBeforeBinaryOperators: All 50 | BreakBeforeConceptDeclarations: false # For C++20 51 | BreakBeforeBraces: Allman 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: true 55 | BreakConstructorInitializers: BeforeColon 56 | BreakAfterJavaFieldAnnotations: false 57 | BreakStringLiterals: true 58 | ColumnLimit: 90 59 | CommentPragmas: '^ IWYU pragma:' 60 | CompactNamespaces: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | EmptyLineBeforeAccessModifier: LogicalBlock 68 | ExperimentalAutoDetectBinPacking: false 69 | FixNamespaceComments: false 70 | ForEachMacros: 71 | - foreach 72 | - Q_FOREACH 73 | - BOOST_FOREACH 74 | StatementAttributeLikeMacros: 75 | - Q_EMIT 76 | IncludeBlocks: Preserve 77 | IncludeCategories: 78 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 79 | Priority: 2 80 | SortPriority: 0 81 | CaseSensitive: false 82 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 83 | Priority: 3 84 | SortPriority: 0 85 | CaseSensitive: false 86 | - Regex: '.*' 87 | Priority: 1 88 | SortPriority: 0 89 | CaseSensitive: false 90 | IncludeIsMainRegex: '(Test)?$' 91 | IncludeIsMainSourceRegex: '' 92 | IndentCaseLabels: false 93 | IndentCaseBlocks: false 94 | IndentGotoLabels: true 95 | IndentPPDirectives: None 96 | IndentExternBlock: AfterExternBlock 97 | IndentRequires: false 98 | IndentWidth: 4 99 | IndentWrappedFunctionNames: false 100 | InsertTrailingCommas: None 101 | JavaScriptQuotes: Leave 102 | JavaScriptWrapImports: true 103 | KeepEmptyLinesAtTheStartOfBlocks: true 104 | MacroBlockBegin: '' 105 | MacroBlockEnd: '' 106 | MaxEmptyLinesToKeep: 1 107 | NamespaceIndentation: Inner 108 | ObjCBinPackProtocolList: Auto 109 | ObjCBlockIndentWidth: 4 110 | ObjCBreakBeforeNestedBlockParam: true 111 | ObjCSpaceAfterProperty: true 112 | ObjCSpaceBeforeProtocolList: true 113 | PenaltyBreakAssignment: 2 114 | PenaltyBreakBeforeFirstCallParameter: 19 115 | PenaltyBreakComment: 300 116 | PenaltyBreakFirstLessLess: 120 117 | PenaltyBreakString: 1000 118 | PenaltyBreakTemplateDeclaration: 10 119 | PenaltyExcessCharacter: 1000000 120 | PenaltyReturnTypeOnItsOwnLine: 60 121 | PenaltyIndentedWhitespace: 0 122 | PointerAlignment: Left 123 | ReflowComments: true 124 | SortIncludes: true 125 | SortJavaStaticImport: Before 126 | SortUsingDeclarations: true 127 | SpaceAfterCStyleCast: false 128 | SpaceAfterLogicalNot: false 129 | SpaceAfterTemplateKeyword: false 130 | SpaceBeforeAssignmentOperators: true 131 | SpaceBeforeCaseColon: false 132 | SpaceBeforeCpp11BracedList: false 133 | SpaceBeforeCtorInitializerColon: true 134 | SpaceBeforeInheritanceColon: true 135 | SpaceBeforeParens: Never 136 | SpaceAroundPointerQualifiers: Default 137 | SpaceBeforeRangeBasedForLoopColon: true 138 | SpaceInEmptyBlock: true 139 | SpaceInEmptyParentheses: false 140 | SpacesBeforeTrailingComments: 1 141 | SpacesInAngles: false 142 | SpacesInConditionalStatement: false 143 | SpacesInContainerLiterals: true 144 | SpacesInCStyleCastParentheses: false 145 | SpacesInParentheses: false 146 | SpacesInSquareBrackets: false 147 | SpaceBeforeSquareBrackets: false 148 | BitFieldColonSpacing: Both 149 | Standard: Latest 150 | StatementMacros: 151 | - Q_UNUSED 152 | - QT_REQUIRE_VERSION 153 | TabWidth: 4 154 | UseCRLF: false 155 | UseTab: Always 156 | WhitespaceSensitiveMacros: 157 | - STRINGIZE 158 | - PP_STRINGIZE 159 | - BOOST_PP_STRINGIZE 160 | - NS_SWIFT_NAME 161 | - CF_SWIFT_NAME 162 | ... 163 | 164 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/examples/toric_utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "toric_utils.hpp" 18 | #include "LatticeCubic.hpp" 19 | 20 | namespace UnionFindCPP 21 | { 22 | auto z_error_to_syndrome_x(const uint32_t L, const ArrayXu& z_error) 23 | -> std::vector 24 | { 25 | std::vector syndromes_array(L * L, 0U); 26 | for(int n = 0; n < 2 * L * L; ++n) 27 | { 28 | if(z_error[n] == 0) { continue; } 29 | Edge e = to_edge(L, n); 30 | syndromes_array[e.u] ^= 1U; 31 | syndromes_array[e.v] ^= 1U; 32 | } 33 | 34 | return syndromes_array; 35 | } 36 | 37 | auto x_error_to_syndrome_z(const uint32_t L, const ArrayXu& x_error) 38 | -> std::vector 39 | { 40 | std::vector syndromes_array(L * L, 0U); 41 | for(int n = 0; n < 2 * L * L; ++n) 42 | { 43 | if(x_error[n] == 0) { continue; } 44 | Edge e = to_edge(L, n); 45 | 46 | if(is_horizontal(L, e)) 47 | { 48 | auto u = left(L, e); 49 | const auto [row, col] = vertex_to_coord(L, u); 50 | syndromes_array[u] ^= 1U; 51 | syndromes_array[to_vertex_index(L, row - 1, col)] ^= 1U; 52 | } 53 | else 54 | { 55 | auto u = lower(L, e); 56 | const auto [row, col] = vertex_to_coord(L, u); 57 | syndromes_array[u] ^= 1U; 58 | syndromes_array[to_vertex_index(L, row, col - 1)] ^= 1U; 59 | } 60 | } 61 | return syndromes_array; 62 | } 63 | 64 | auto decoder_edge_to_qubit_idx(const uint32_t L, Edge e, ErrorType error_type) -> uint32_t 65 | { 66 | switch(error_type) 67 | { 68 | case ErrorType::X: 69 | // each edge is a qubit in the dual lattice 70 | if(is_horizontal(L, e)) 71 | { 72 | auto u = left(L, e); 73 | const auto [row, col] = vertex_to_coord(L, u); 74 | return L * ((row + 1) % L) + ((col + 1) % L); 75 | } 76 | else 77 | { 78 | auto u = upper(L, e); 79 | const auto [row, col] = vertex_to_coord(L, u); 80 | return L * (row % L) + col + L * L; 81 | } 82 | case ErrorType::Z: 83 | return to_edge_idx(L, e); 84 | } 85 | __builtin_unreachable(); 86 | } 87 | 88 | auto to_edge(const uint32_t L, uint32_t edge_index) -> Edge 89 | { 90 | uint32_t row = edge_index / L; // % L is done in to_vertex_index 91 | uint32_t col = edge_index % L; 92 | if((edge_index / (L * L)) == 0) // vertical edge 93 | { 94 | return Edge(to_vertex_index(L, row, col), to_vertex_index(L, row - 1, col)); 95 | } 96 | // horizontal edge 97 | return Edge(to_vertex_index(L, row, col), to_vertex_index(L, row, col + 1)); 98 | } 99 | 100 | auto calc_syndromes(const LatticeCubic& lattice, const ArrayXXu& errors, 101 | ErrorType error_type) -> std::vector 102 | { 103 | const uint32_t L = lattice.getL(); 104 | std::vector syndromes(lattice.num_vertices()); 105 | for(uint32_t h = 0; h < L; ++h) 106 | { 107 | ArrayXu layer_error = errors.col(h); 108 | auto layer_syndrome = [L, error_type, &layer_error] 109 | { 110 | switch(error_type) 111 | { 112 | case ErrorType::Z: 113 | return z_error_to_syndrome_x(L, layer_error); 114 | case ErrorType::X: 115 | return x_error_to_syndrome_z(L, layer_error); 116 | } 117 | __builtin_unreachable(); 118 | }(); 119 | std::copy(layer_syndrome.begin(), layer_syndrome.end(), 120 | syndromes.begin() + h * L * L); 121 | } 122 | 123 | return syndromes; 124 | } 125 | 126 | void add_corrections(const uint32_t L, const std::vector& corrections, 127 | ArrayXu& error, ErrorType error_type) 128 | { 129 | for(auto e : corrections) 130 | { 131 | auto idx = decoder_edge_to_qubit_idx(L, e, error_type); 132 | error[idx] += 1; 133 | } 134 | } 135 | 136 | auto logical_error(const uint32_t L, const ArrayXu& error, ErrorType error_type) -> bool 137 | { 138 | // one may use a counting algorithm for testing logical error 139 | uint32_t sum1 = 0; 140 | uint32_t sum2 = 0; 141 | switch(error_type) 142 | { 143 | case ErrorType::X: 144 | // need to think in a dual lattice 145 | for(uint32_t u = 0; u < L * L; u += L) { sum1 += error[u]; } 146 | for(uint32_t u = L * L; u < L * L + L; ++u) { sum2 += error[u]; } 147 | break; 148 | case ErrorType::Z: 149 | for(uint32_t u = 0; u < L; ++u) { sum1 += error[u]; } 150 | for(uint32_t u = L * L; u < 2 * L * L; u += L) { sum2 += error[u]; } 151 | break; 152 | } 153 | 154 | return (sum1 % 2 == 1) || (sum2 % 2 == 1); 155 | } 156 | 157 | auto has_logical_error(uint32_t L, ArrayXu& error_total, 158 | const std::vector& corrections, ErrorType error_type) -> bool 159 | { 160 | for(auto edge : corrections) 161 | { 162 | if(edge.u / (L * L) == edge.v / (L * L)) // edge is spacelike 163 | { 164 | auto corr_edge = Edge{edge.u % (L * L), edge.v % (L * L)}; 165 | uint32_t corr_qubit = decoder_edge_to_qubit_idx(L, corr_edge, error_type); 166 | error_total[corr_qubit] += 1; 167 | } 168 | } 169 | return logical_error(L, error_total, error_type); 170 | } 171 | } // namespace UnionFindCPP 172 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/include/LatticeFromParity.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utility.hpp" 4 | 5 | #include "tsl/robin_map.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace UnionFindCPP 16 | { 17 | /** 18 | * This class implements LatticeConcept using the input of sparse parity matrix. 19 | */ 20 | class LatticeFromParity 21 | { 22 | private: 23 | uint32_t num_vertices_; 24 | uint32_t num_edges_; 25 | 26 | /* connectivity of vertices (parities). Length num_parities */ 27 | std::vector> vertex_connections_; 28 | tsl::robin_map edge_idx_; 29 | 30 | static auto construct_qubit_associated_parities(uint32_t num_parities, 31 | uint32_t num_qubits, int* col_indices, 32 | const int* indptr) 33 | -> std::vector> 34 | { 35 | /** 36 | * key: qubit_idx, value: vector of all connected vertices (parities). Length 37 | * num_qubits 38 | */ 39 | std::vector> qubit_associated_parities(num_qubits); 40 | for(uint32_t p_idx = 0; p_idx < num_parities; ++p_idx) 41 | { 42 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 43 | for(uint32_t idx = indptr[p_idx]; idx < indptr[p_idx + 1]; ++idx) 44 | { 45 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 46 | qubit_associated_parities[col_indices[idx]].push_back(p_idx); 47 | } 48 | } 49 | return qubit_associated_parities; 50 | } 51 | 52 | static auto construct_edge_idx( 53 | uint32_t num_qubits, 54 | const std::vector>& qubit_associated_parities) 55 | -> tsl::robin_map 56 | { 57 | tsl::robin_map edge_idx; 58 | for(uint32_t q_idx = 0; q_idx < num_qubits; ++q_idx) 59 | { 60 | const auto& q_parities = qubit_associated_parities[q_idx]; 61 | auto edge = Edge{q_parities[0], q_parities[1]}; 62 | auto iter = edge_idx.find(edge); 63 | 64 | if(iter == edge_idx.end()) // first appear 65 | { 66 | edge_idx.emplace(edge, q_idx); 67 | } 68 | } 69 | return edge_idx; 70 | } 71 | 72 | /** 73 | * @brief Use edge_idx_ to construct vertex_connections_ 74 | */ 75 | void construct_vertex_connections_from_edges() 76 | { 77 | vertex_connections_.resize(num_vertices_); 78 | for(const auto& [edge, qidx] : edge_idx_) 79 | { 80 | const auto [u, v] = edge; 81 | vertex_connections_[u].emplace_back(v); 82 | vertex_connections_[v].emplace_back(u); 83 | } 84 | } 85 | 86 | public: 87 | /** 88 | * @brief construct a Lattice class from a given parity matrix (CSR format) 89 | * 90 | * @param num_parities total number of parities. Same as the number of rows of the 91 | * matrix. 92 | * @param num_qubits total number of qubits. Same as the number of columns of the 93 | * matrix 94 | * @param col_indices indices[idx] indicate the column index of the element data[idx] 95 | * @param indptr indptr[row+1]-indptr[row] indicate the number of elements in the row 96 | */ 97 | LatticeFromParity(uint32_t num_parities, uint32_t num_qubits, int* col_indices, 98 | int* indptr) 99 | : num_vertices_{num_parities}, num_edges_{num_qubits} 100 | { 101 | auto qubit_associated_parities = construct_qubit_associated_parities( 102 | num_parities, num_qubits, col_indices, indptr); 103 | 104 | edge_idx_ = construct_edge_idx(num_qubits, qubit_associated_parities); 105 | construct_vertex_connections_from_edges(); 106 | } 107 | 108 | LatticeFromParity(uint32_t layer_vertex_size, uint32_t layer_num_qubits, 109 | int* col_indices, int* indptr, uint32_t repetitions) 110 | : num_vertices_{layer_vertex_size * repetitions}, 111 | num_edges_{layer_num_qubits * repetitions 112 | + layer_vertex_size * (repetitions - 1)} 113 | { 114 | if(repetitions < 2) 115 | { 116 | throw std::invalid_argument("Repetition must be greater than or equal to 2."); 117 | } 118 | 119 | auto qubit_associated_parities = construct_qubit_associated_parities( 120 | layer_vertex_size, layer_num_qubits, col_indices, indptr); 121 | auto layer_edge_idx 122 | = construct_edge_idx(layer_num_qubits, qubit_associated_parities); 123 | 124 | // Construct edge_idx_ 125 | edge_idx_.reserve(num_edges_); 126 | // Add spacelike edges 127 | for(uint32_t depth = 0; depth < repetitions; ++depth) 128 | { 129 | for(const auto& [layer_edge, q_idx] : layer_edge_idx) 130 | { 131 | auto edge = Edge{layer_edge.u + depth * layer_vertex_size, 132 | layer_edge.v + depth * layer_vertex_size}; 133 | 134 | edge_idx_.emplace(edge, 135 | q_idx + depth * (layer_vertex_size + layer_num_qubits)); 136 | } 137 | } 138 | 139 | // Add timelike edges 140 | for(uint32_t depth = 0; depth < repetitions - 1; ++depth) 141 | { 142 | for(int layer_vertex_idx = 0; layer_vertex_idx < layer_vertex_size; 143 | ++layer_vertex_idx) 144 | { 145 | auto edge = Edge{depth * layer_vertex_size + layer_vertex_idx, 146 | (depth + 1) * layer_vertex_size + layer_vertex_idx}; 147 | edge_idx_.emplace(edge, 148 | layer_vertex_idx + layer_num_qubits 149 | + (layer_num_qubits + layer_vertex_size) * depth); 150 | } 151 | } 152 | 153 | // Construct vertex_connections_ 154 | construct_vertex_connections_from_edges(); 155 | } 156 | 157 | [[nodiscard]] auto vertex_connections(uint32_t v) const 158 | -> const std::vector& 159 | { 160 | return vertex_connections_[v]; 161 | } 162 | 163 | [[nodiscard]] auto vertex_connection_count(int vertex) const -> uint32_t 164 | { 165 | return static_cast(vertex_connections_[vertex].size()); 166 | } 167 | 168 | [[nodiscard]] inline auto edge_idx(const Edge& edge) const -> uint32_t 169 | { 170 | return edge_idx_.at(edge); 171 | } 172 | 173 | [[nodiscard]] inline auto num_edges() const -> uint32_t { return num_edges_; } 174 | 175 | [[nodiscard]] inline auto num_vertices() const -> uint32_t { return num_vertices_; } 176 | 177 | [[nodiscard]] inline auto 178 | edge_idx_all() const& -> const tsl::robin_map& 179 | { 180 | return edge_idx_; 181 | } 182 | [[nodiscard]] inline auto edge_idx_all() && -> tsl::robin_map 183 | { 184 | return edge_idx_; 185 | } 186 | }; 187 | } // namespace UnionFindCPP 188 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/include/Decoder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LatticeConcept.hpp" 4 | #include "RootManager.hpp" 5 | #include "utility.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace UnionFindCPP 19 | { 20 | template class Decoder 21 | { 22 | public: 23 | using Vertex = uint32_t; 24 | using RootIterator = tsl::robin_set::const_iterator; 25 | 26 | private: 27 | const Lattice lattice_; 28 | 29 | /* index: vertex */ 30 | std::vector connection_counts_; 31 | 32 | /* index: edge index */ 33 | std::vector support_; 34 | std::deque fuse_list_; 35 | 36 | /* index: vertex */ 37 | std::vector root_of_vertex_; // root of vertex 38 | 39 | RootManager mgr_; 40 | /* key: root, value: borders */ 41 | tsl::robin_map> border_vertices_; 42 | 43 | /* Data for peeling */ 44 | std::deque peeling_edges_; 45 | 46 | void init_cluster(const std::vector& roots) 47 | { 48 | connection_counts_ = std::vector(lattice_.num_vertices(), 0); 49 | support_ = std::vector(lattice_.num_edges(), 0); 50 | mgr_.initialize_roots(roots); 51 | for(auto root : roots) { border_vertices_[root].emplace(root); } 52 | 53 | root_of_vertex_.resize(lattice_.num_vertices()); 54 | 55 | for(uint32_t u = 0; u < lattice_.num_vertices(); ++u) { root_of_vertex_[u] = u; } 56 | } 57 | 58 | void grow(Vertex root) 59 | { 60 | for(auto border_vertex : border_vertices_[root]) 61 | { 62 | for(auto v : lattice_.vertex_connections(border_vertex)) 63 | { 64 | auto edge = Edge(border_vertex, v); 65 | 66 | auto& elt = support_[lattice_.edge_idx(edge)]; 67 | if(elt == 2) { continue; } 68 | if(++elt == 2) 69 | { 70 | connection_counts_[edge.u]++; 71 | connection_counts_[edge.v]++; 72 | fuse_list_.emplace_back(edge); 73 | } 74 | } 75 | } 76 | } 77 | 78 | auto find_root(Vertex vertex) -> Vertex 79 | { 80 | Vertex tmp = root_of_vertex_[vertex]; 81 | if(tmp == vertex) { return vertex; } 82 | 83 | std::vector path; 84 | Vertex root{}; 85 | do { 86 | root = tmp; 87 | path.emplace_back(root); 88 | tmp = root_of_vertex_[root]; 89 | } while(tmp != root); 90 | 91 | // now root == (tmp = root_of_vertex_[root]) 92 | 93 | for(const auto v : path) { root_of_vertex_[v] = root; } 94 | return root; 95 | } 96 | 97 | void merge_boundary(Vertex root1, Vertex root2) 98 | { 99 | border_vertices_[root1].insert(border_vertices_[root2].cbegin(), 100 | border_vertices_[root2].cend()); 101 | 102 | for(auto vertex : border_vertices_[root2]) 103 | { 104 | if(connection_counts_[vertex] == lattice_.vertex_connection_count(vertex)) 105 | { 106 | border_vertices_[root1].erase(vertex); 107 | } 108 | } 109 | border_vertices_.erase(root2); 110 | } 111 | 112 | void fusion() 113 | { 114 | while(!fuse_list_.empty()) 115 | { 116 | Edge fuse_edge = fuse_list_.front(); 117 | fuse_list_.pop_front(); 118 | auto root1 = find_root(fuse_edge.u); 119 | auto root2 = find_root(fuse_edge.v); 120 | 121 | if(root1 == root2) 122 | { 123 | continue; // do nothing 124 | } 125 | 126 | peeling_edges_.push_back(fuse_edge); 127 | 128 | // let the size of the cluster of root1 be larger than that of root2 129 | if(mgr_.size(root1) < mgr_.size(root2)) { std::swap(root1, root2); } 130 | 131 | root_of_vertex_[root2] = root1; 132 | 133 | if(!mgr_.is_root(root2)) // if merging one is a single vertex 134 | { 135 | ++mgr_.size(root1); 136 | border_vertices_[root1].emplace(root2); 137 | } 138 | else 139 | { 140 | mgr_.merge(root1, root2); 141 | merge_boundary(root1, root2); 142 | } 143 | } 144 | } 145 | 146 | auto peeling(std::vector& syndromes) -> std::vector 147 | { 148 | std::vector corrections; 149 | 150 | // vs vector? 151 | tsl::robin_map vertex_count; 152 | 153 | for(Edge edge : peeling_edges_) 154 | { 155 | ++vertex_count[edge.u]; 156 | ++vertex_count[edge.v]; 157 | } 158 | 159 | while(!peeling_edges_.empty()) 160 | { 161 | Edge leaf_edge = peeling_edges_.back(); 162 | peeling_edges_.pop_back(); 163 | auto u = Vertex{}; 164 | auto v = Vertex{}; 165 | if(vertex_count[leaf_edge.u] == 1) 166 | { 167 | u = leaf_edge.u; 168 | v = leaf_edge.v; 169 | } 170 | else if(vertex_count[leaf_edge.v] == 1) 171 | { 172 | u = leaf_edge.v; 173 | v = leaf_edge.u; 174 | } 175 | else // not a leaf 176 | { 177 | peeling_edges_.push_front(leaf_edge); 178 | continue; 179 | } 180 | 181 | --vertex_count[u]; 182 | --vertex_count[v]; 183 | 184 | if(syndromes[u] == 1) 185 | { 186 | corrections.emplace_back(leaf_edge); 187 | syndromes[u] = 0; 188 | syndromes[v] ^= 1U; 189 | } 190 | } 191 | return corrections; 192 | } 193 | 194 | public: 195 | template explicit Decoder(Args&&... args) : lattice_{args...} { } 196 | 197 | auto decode(std::vector& syndromes) -> std::vector 198 | { 199 | assert(syndromes.size() == lattice_.num_vertices()); 200 | std::vector syndrome_vertices; 201 | for(uint32_t n = 0; n < syndromes.size(); ++n) 202 | { 203 | if((syndromes[n] % 2) != 0) { syndrome_vertices.emplace_back(n); } 204 | } 205 | 206 | init_cluster(syndrome_vertices); 207 | 208 | while(!mgr_.isempty_odd_root()) 209 | { 210 | for(auto root : mgr_.odd_roots()) { grow(root); } 211 | fusion(); 212 | } 213 | 214 | return peeling(syndromes); 215 | } 216 | 217 | [[nodiscard]] inline auto num_vertices() const -> int 218 | { 219 | return lattice_.num_vertices(); 220 | } 221 | 222 | [[nodiscard]] inline auto num_edges() const -> int { return lattice_.num_edges(); } 223 | 224 | [[nodiscard]] inline auto edge_idx(const Edge& edge) const -> int 225 | { 226 | return lattice_.edge_idx(edge); 227 | } 228 | 229 | void clear() 230 | { 231 | std::deque().swap(fuse_list_); 232 | 233 | mgr_.clear(); 234 | 235 | tsl::robin_map>().swap(border_vertices_); 236 | 237 | std::deque().swap(peeling_edges_); 238 | } 239 | }; 240 | } // namespace UnionFindCPP 241 | -------------------------------------------------------------------------------- /UnionFindPy/cpp/tests/test_LatticeFromParity.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 UnionFind++ authors 2 | // 3 | // This file is part of UnionFind++. 4 | // 5 | // UnionFind++ is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // UnionFind++ is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with UnionFind++. If not, see . 17 | #include "../examples/Lattice2D.hpp" 18 | #include "../examples/LatticeCubic.hpp" 19 | #include "LatticeConcept.hpp" 20 | #include "LatticeFromParity.hpp" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #define CATCH_CONFIG_MAIN 29 | #include "catch.hpp" 30 | 31 | using UnionFindCPP::LatticeFromParity; 32 | /** 33 | * The Lattice2D from v0.1 uses the simplest ordering. 34 | * 35 | * For example, for Lx=4, Ly=2 lattice, qubits are labeled as: 36 | * 37 | * ─┼─8─┼─9─┼───┼─ 38 | * 4 5 6 7 39 | * ─┼─0─┼─1─┼─2─┼3 40 | * 41 | * and the X stabiliers are labeles as: 42 | * 43 | * ─4───5───6───7─ 44 | * │ │ │ │ 45 | * ─0───1───2───3─ 46 | * 47 | * which means that $P_0 = X_0 X_3 X_4 X_{12}, \cdots$. 48 | */ 49 | auto toric_x_stabilizers_qubits_old(const uint32_t Lx, const uint32_t Ly, uint32_t vertex) 50 | -> std::set 51 | { 52 | uint32_t row = vertex / Lx; 53 | uint32_t col = vertex % Lx; 54 | 55 | return std::set{2 * row * Lx + col, 2 * row * Lx + (col + Lx), 56 | 2 * row * Lx + (col - 1 + Lx) % Lx, 57 | 2 * ((row - 1 + Ly) % Ly) * Lx + col + Lx}; 58 | } 59 | 60 | template 61 | auto have_same_elts(const ContainerType1& c1, const ContainerType2& c2) -> bool 62 | { 63 | static_assert(std::is_same_v, 65 | "Two container types must have the same value_type"); 66 | using T = typename ContainerType1::value_type; 67 | std::set s1(c1.begin(), c1.end()); 68 | std::set s2(c2.begin(), c2.end()); 69 | 70 | return s1 == s2; 71 | } 72 | 73 | template 75 | void check_same_lattice(const LatticeType1& lattice1, const LatticeType2& lattice2) 76 | { 77 | REQUIRE(lattice1.num_vertices() == lattice2.num_vertices()); 78 | REQUIRE(lattice1.num_edges() == lattice2.num_edges()); 79 | 80 | for(uint32_t v = 0; v < lattice1.num_vertices(); ++v) 81 | { 82 | REQUIRE(lattice1.vertex_connection_count(v) 83 | == lattice2.vertex_connection_count(v)); 84 | REQUIRE(have_same_elts(lattice1.vertex_connections(v), 85 | lattice2.vertex_connections(v))); 86 | } 87 | 88 | for(uint32_t v = 0; v < lattice1.num_vertices(); ++v) 89 | { 90 | for(uint32_t v2 : lattice2.vertex_connections(v)) 91 | { 92 | REQUIRE(lattice1.edge_idx({v, v2}) == lattice2.edge_idx({v, v2})); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * @brief generate a parity matrix for a prepetition code 99 | */ 100 | auto repetition_code(uint32_t L) -> Eigen::SparseMatrix 101 | { 102 | Eigen::SparseMatrix m(L, L); 103 | m.reserve(2 * L); 104 | for(uint32_t l = 0; l < L; ++l) 105 | { 106 | m.insert(l, l) = 1; 107 | m.insert(l, (l + 1) % L) = 1; 108 | } 109 | m.makeCompressed(); 110 | return m; 111 | } 112 | 113 | auto toric_x_stabilizers_qubits_new(const uint32_t L) 114 | { 115 | using SpMatu = Eigen::SparseMatrix; 116 | SpMatu Id(L, L); 117 | Id.setIdentity(); 118 | SpMatu Hr = repetition_code(L); 119 | SpMatu HrId = Eigen::kroneckerProduct(Hr, Id); 120 | SpMatu IdHr = Eigen::kroneckerProduct(Id, Hr.transpose()); 121 | 122 | SpMatu H(L * L, 2 * L * L); 123 | 124 | for(int k = 0; k < HrId.outerSize(); ++k) 125 | { 126 | for(SpMatu::InnerIterator it(HrId, k); it; ++it) 127 | { 128 | H.coeffRef(it.row(), it.col()) = it.value(); 129 | } 130 | } 131 | 132 | for(int k = 0; k < IdHr.outerSize(); ++k) 133 | { 134 | for(SpMatu::InnerIterator it(IdHr, k); it; ++it) 135 | { 136 | H.coeffRef(it.row(), it.col() + L * L) = it.value(); 137 | } 138 | } 139 | 140 | H.makeCompressed(); 141 | 142 | return H; 143 | } 144 | 145 | TEST_CASE("Test internal functions", "[internal]") 146 | { 147 | // Test using Lx=4, Ly=2 148 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 0) == std::set{0, 3, 4, 12}); 149 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 1) == std::set{0, 1, 5, 13}); 150 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 2) == std::set{1, 2, 6, 14}); 151 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 3) == std::set{2, 3, 7, 15}); 152 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 4) == std::set{4, 8, 11, 12}); 153 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 5) == std::set{5, 8, 9, 13}); 154 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 6) == std::set{6, 9, 10, 14}); 155 | REQUIRE(toric_x_stabilizers_qubits_old(4, 2, 7) == std::set{7, 10, 11, 15}); 156 | } 157 | /** 158 | * LatticeFromParity class only get a list of parity operators (in terms of sites) as an 159 | * input. It does not know any information in advance, but constructs a graph where 160 | * vertices are the index of the parity operator and edges are qubits. 161 | */ 162 | TEST_CASE("Test whether the constructed lattice is correct for a toric code (2D)", 163 | "[LatticeFromParity]") 164 | { 165 | std::random_device rd; 166 | std::default_random_engine re{rd()}; 167 | 168 | SECTION("Lx=4, Ly=2, old indexing") 169 | { 170 | const uint32_t Lx = 4; 171 | const uint32_t Ly = 2; 172 | 173 | std::vector col_indices; 174 | std::vector indptr; 175 | 176 | indptr.push_back(0); 177 | 178 | for(uint32_t nx = 0; nx < Lx; ++nx) 179 | { 180 | for(uint32_t ny = 0; ny < Ly; ++ny) 181 | { 182 | auto m = toric_x_stabilizers_qubits_old(Lx, Ly, nx * Ly + ny); 183 | indptr.push_back(indptr.back() + m.size()); 184 | col_indices.insert(col_indices.end(), std::make_move_iterator(m.begin()), 185 | std::make_move_iterator(m.end())); 186 | } 187 | } 188 | 189 | auto lattice 190 | = LatticeFromParity(Lx * Ly, 2 * Lx * Ly, col_indices.data(), indptr.data()); 191 | REQUIRE(have_same_elts(lattice.vertex_connections(0), 192 | std::vector{1, 3, 4})); 193 | REQUIRE(have_same_elts(lattice.vertex_connections(1), 194 | std::vector{0, 2, 5})); 195 | REQUIRE(have_same_elts(lattice.vertex_connections(2), 196 | std::vector{1, 3, 6})); 197 | REQUIRE(have_same_elts(lattice.vertex_connections(3), 198 | std::vector{0, 2, 7})); 199 | REQUIRE(have_same_elts(lattice.vertex_connections(4), 200 | std::vector{0, 5, 7})); 201 | REQUIRE(have_same_elts(lattice.vertex_connections(5), 202 | std::vector{1, 4, 6})); 203 | REQUIRE(have_same_elts(lattice.vertex_connections(6), 204 | std::vector{2, 5, 7})); 205 | REQUIRE(have_same_elts(lattice.vertex_connections(7), 206 | std::vector{3, 4, 6})); 207 | } 208 | 209 | SECTION("L=7 to 127, old indexing") 210 | { 211 | for(const uint32_t L : {7, 15, 31, 63, 127}) 212 | { 213 | auto to_vertex = [L](uint32_t row, uint32_t col) 214 | { 215 | return ((row + L) % L) * L + ((col + L) % L); 216 | }; 217 | 218 | std::vector col_indices; 219 | std::vector indptr; 220 | 221 | indptr.push_back(0); 222 | for(uint32_t nx = 0; nx < L; ++nx) 223 | { 224 | for(uint32_t ny = 0; ny < L; ++ny) 225 | { 226 | auto m = toric_x_stabilizers_qubits_old(L, L, nx * L + ny); 227 | indptr.push_back(indptr.back() + m.size()); 228 | col_indices.insert(col_indices.end(), 229 | std::make_move_iterator(m.begin()), 230 | std::make_move_iterator(m.end())); 231 | } 232 | } 233 | auto lattice 234 | = LatticeFromParity(L * L, 2 * L * L, col_indices.data(), indptr.data()); 235 | 236 | std::uniform_int_distribution col_dist(0, L - 1); 237 | for(uint32_t row = 0; row < L; ++row) 238 | { 239 | auto col = col_dist(re); 240 | 241 | uint32_t vertex = row * L + col; 242 | REQUIRE(lattice.vertex_connection_count(vertex) == 4); 243 | 244 | REQUIRE(have_same_elts(lattice.vertex_connections(vertex), 245 | std::vector{to_vertex(row - 1, col), 246 | to_vertex(row + 1, col), 247 | to_vertex(row, col + 1), 248 | to_vertex(row, col - 1)})); 249 | REQUIRE(lattice.edge_idx({to_vertex(row, col), to_vertex(row, col + 1)}) 250 | == 2 * row * L + col); 251 | REQUIRE(lattice.edge_idx({to_vertex(row, col), to_vertex(row + 1, col)}) 252 | == 2 * row * L + col + L); 253 | } 254 | } 255 | } 256 | 257 | SECTION("L=7 to 127, new indexing") 258 | { 259 | using UnionFindCPP::Lattice2D; 260 | for(uint32_t L : {7, 15, 31, 63}) 261 | { 262 | auto H = toric_x_stabilizers_qubits_new(L); 263 | auto lattice_from_H = LatticeFromParity(H.rows(), H.cols(), H.innerIndexPtr(), 264 | H.outerIndexPtr()); 265 | auto lattice2d = Lattice2D(L); 266 | 267 | check_same_lattice(lattice_from_H, lattice2d); 268 | } 269 | } 270 | } 271 | 272 | TEST_CASE("Test whether the constructed lattice is correct for a toric code (3D)", 273 | "[LatticeFromParity]") 274 | { 275 | SECTION("L=7 to 127, new indexing") 276 | { 277 | using UnionFindCPP::LatticeCubic; 278 | for(uint32_t L : {3, 7, 15}) 279 | { 280 | auto H = toric_x_stabilizers_qubits_new(L); 281 | auto lattice_from_H = LatticeFromParity(H.rows(), H.cols(), H.innerIndexPtr(), 282 | H.outerIndexPtr(), 283 | /*repetitions = */ L); 284 | auto lattice3d = LatticeCubic(L); 285 | 286 | check_same_lattice(lattice_from_H, lattice3d); 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | --------------------------------------------------------------------------------