diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..20151f8 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,78 @@ +name: pypi + +on: + workflow_dispatch: + push: + tags: + - "v*" + +jobs: + build-wheels: + name: Build wheels (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Build wheels + uses: pypa/cibuildwheel@v2.22.0 + env: + CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*" + CIBW_SKIP: "*-musllinux_* pp* *-win32 *_i686" + CIBW_ARCHS_MACOS: "x86_64 arm64" + CIBW_ENVIRONMENT: "CMAKE_BUILD_PARALLEL_LEVEL=2" + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse/*.whl + + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Build sdist + run: | + python -m pip install --upgrade pip build + python -m build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + + publish: + name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags/v') + needs: [build-wheels, build-sdist] + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheels-* + path: dist + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 + diff --git a/.gitignore b/.gitignore index 960fe63..0e34dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ rules.ninja # Binaries lidmas +!python/lidmas/ +!python/lidmas/__init__.py +!python/lidmas/__main__.py *.exe *.out *.a diff --git a/CMakeLists.txt b/CMakeLists.txt index 454bed6..e2921cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,13 @@ add_executable(lidmas_surface target_include_directories(lidmas_surface PRIVATE include) +# Install CLI binary into the Python package layout used by scikit-build-core. +# This enables `pip install lidmas` to provide a bundled executable invoked by +# the Python console entrypoint. +install(TARGETS lidmas + RUNTIME DESTINATION lidmas/_bin +) + if(OpenMP_CXX_FOUND) message(STATUS "OpenMP found via find_package") target_link_libraries(lidmas PRIVATE OpenMP::OpenMP_CXX) diff --git a/README.md b/README.md index 4cd6a52..01fe8f0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,23 @@ surface-code threshold experiments under discrete Pauli noise and hybrid continuous-variable (CV)-discrete noise models as the primary workflow. It also includes CSS and LDPC engine paths for comparative studies. +Install from PyPI: + +```bash +python -m pip install --upgrade lidmas +``` + +Then run: + +```bash +lidmas --help +``` + +Documentation: + +- [GitHub Pages Documentation](https://denniswayo.github.io/lidmas_cpp/) +- Read the Docs mirror is currently unavailable. + ## Statement of Need Benchmarking decoder behavior and threshold trends requires reproducible, scriptable, @@ -21,7 +38,7 @@ and inspectable simulation pipelines. LiDMaS+ provides: - deterministic Monte Carlo runs with explicit seed control, - multiple decoders under a common interface, - confidence-interval-aware threshold outputs, -- publication-ready CSV and figure workflows in `examples/`. +- numerous reproducible workflows and scripts across `examples/`. This makes it suitable for method development, reproducibility appendices, and comparative decoder studies. @@ -56,6 +73,13 @@ Primary executable: - `build/lidmas` +## Packaging Notes + +- Brand name remains **LiDMaS+**. +- PyPI package name is `lidmas`. +- CLI command is `lidmas`. +- Published wheels are CPU-oriented; CUDA builds are supported from source builds. + ### Optional CUDA build (Pauli surface_threshold sampling) ```bash @@ -202,7 +226,7 @@ For quick validation in local or CI environments: ## Hardware Integration -See [docs/hardware-integration.md](/Users/denniswayo/lidmas_cpp/docs/hardware-integration.md) for the decoder IO schema, +See [hardware-integration](https://denniswayo.github.io/lidmas_cpp/hardware-integration/) for the decoder IO schema, recommended data transport, and adapter API. ## Project Layout @@ -221,14 +245,22 @@ GitHub Releases. ## Citation If you use LiDMaS+ in academic work, cite the software release used for your -experiments (tag + commit hash). If a JOSS/arXiv record is available for your -release, cite that record directly. - -Suggested software citation format: - -```text -Wayo, D. (Year). LiDMaS+ (Version X.Y.Z) [Computer software]. -https://github.com/DennisWayo/lidmas_cpp +experiments (tag + commit hash). + +Paper reference (`paper_01`): + +![paper_01 graphic](docs/images/paper_01_graphic.png) + +```bibtex +@misc{wayo2026decoder, + title={Decoder Performance in Hybrid CV-Discrete Surface-Code Threshold Estimation Using LiDMaS+}, + author={Dennis Delali Kwesi Wayo and Chinonso Onah and Vladimir Milchakov and Leonardo Goliatt and Sven Groppe}, + year={2026}, + eprint={2603.06730}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + url={https://arxiv.org/abs/2603.06730} +} ``` ## License diff --git a/docs/images/paper_01_graphic.png b/docs/images/paper_01_graphic.png new file mode 100644 index 0000000..6a78574 Binary files /dev/null and b/docs/images/paper_01_graphic.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6205db7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["scikit-build-core>=0.9"] +build-backend = "scikit_build_core.build" + +[project] +name = "lidmas" +version = "1.1.0" +description = "LiDMaS+ (Logical Injection & Decoding Modeling System) quantum error-correction simulator" +readme = "README.md" +requires-python = ">=3.9" +license = { file = "LICENSE" } +authors = [ + { name = "Dennis Delali Kwesi Wayo" } +] +keywords = ["quantum", "qec", "surface-code", "decoder", "simulation"] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: C++", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Physics" +] + +[project.urls] +Homepage = "https://github.com/DennisWayo/lidmas_cpp" +Repository = "https://github.com/DennisWayo/lidmas_cpp" + +[project.scripts] +lidmas = "lidmas.__main__:main" + +[tool.scikit-build] +cmake.version = ">=3.16" +cmake.build-type = "Release" +wheel.packages = ["python/lidmas"] + diff --git a/python/lidmas/__init__.py b/python/lidmas/__init__.py new file mode 100644 index 0000000..9004bf9 --- /dev/null +++ b/python/lidmas/__init__.py @@ -0,0 +1,9 @@ +"""LiDMaS+ Python launcher package.""" + +from importlib.metadata import PackageNotFoundError, version + +try: + __version__ = version("lidmas") +except PackageNotFoundError: + __version__ = "0+local" + diff --git a/python/lidmas/__main__.py b/python/lidmas/__main__.py new file mode 100644 index 0000000..d0db74f --- /dev/null +++ b/python/lidmas/__main__.py @@ -0,0 +1,40 @@ +"""Console launcher for the bundled LiDMaS+ executable.""" + +from __future__ import annotations + +import os +import subprocess +import sys +from importlib.resources import files +from pathlib import Path + + +def _binary_name() -> str: + return "lidmas.exe" if os.name == "nt" else "lidmas" + + +def _binary_path() -> Path: + return Path(files("lidmas")).joinpath("_bin", _binary_name()) + + +def main() -> int: + exe = _binary_path() + if not exe.exists(): + print( + f"LiDMaS executable not found at '{exe}'. " + "Reinstall the package or build from source.", + file=sys.stderr, + ) + return 1 + + cmd = [str(exe), *sys.argv[1:]] + if os.name == "nt": + return subprocess.call(cmd) + + os.execv(cmd[0], cmd) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +