prism-q 0.35.0

Fast Rust quantum circuit simulator. OpenQASM 3.0, multiple backends, AVX2 SIMD kernels, optional CUDA and MPI, QEC tooling, Python bindings.
Documentation
name: Python Wheels

on:
  push:
    tags: ["py-v*"]
  pull_request:
    paths:
      - "bindings/python/**"
      - "docs/guides/python.md"
      - "docs/tutorials/**"
      - "src/**"
      - "Cargo.toml"
      - "Cargo.lock"
      - ".github/workflows/python.yml"
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    name: wheels-${{ matrix.platform.os }}-${{ matrix.platform.target }}
    runs-on: ${{ matrix.platform.runner }}
    strategy:
      fail-fast: false
      # The gpu feature loads the CUDA driver and NVRTC at run time, so these
      # runners build it without a toolkit and the wheel imports on hosts without
      # one. macOS has no CUDA.
      matrix:
        platform:
          - { os: linux, runner: ubuntu-latest, target: x86_64, features: --features gpu }
          - { os: linux, runner: ubuntu-24.04-arm, target: aarch64, features: --features gpu }
          - { os: macos, runner: macos-latest, target: aarch64, features: "" }
          - { os: windows, runner: windows-latest, target: x64, features: --features gpu }
    steps:
      - uses: actions/checkout@v7

      - uses: actions/setup-python@v7
        with:
          python-version: "3.12"

      - uses: dtolnay/rust-toolchain@1.99.0

      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          command: build
          target: ${{ matrix.platform.target }}
          args: --profile python-release --manifest-path bindings/python/Cargo.toml --out dist ${{ matrix.platform.features }}
          manylinux: auto

      - name: Smoke test and pytest
        shell: bash
        run: |
          python -m pip install --upgrade pip
          python -m pip install numpy pytest
          python -m pip install --no-index --find-links dist prism-q
          pytest bindings/python/tests -q

      # Compares the hand-written stub with the extension it describes. The
      # allowlist names the shapes a stub cannot state for a PyO3 class; drift in
      # a signature fails here. The stub is the same on every platform, so one
      # wheel checks it. mypy is pinned because stubtest's checks change between
      # releases, and the allowlist is written against these.
      - name: Stub check
        if: matrix.platform.os == 'linux' && matrix.platform.target == 'x86_64'
        shell: bash
        run: |
          python -m pip install mypy==1.19.1
          python -m mypy.stubtest prism_q._prism_q --allowlist bindings/python/stubtest-allowlist.txt

      - uses: actions/upload-artifact@v7
        with:
          name: wheels-${{ matrix.platform.os }}-${{ matrix.platform.target }}
          path: dist

  wheel-compatibility:
    name: Test wheel (Python ${{ matrix.python }})
    needs: build
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python: ["3.11", "3.13", "3.14"]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: ${{ matrix.python }}
      - uses: actions/download-artifact@v8
        with:
          name: wheels-linux-x86_64
          path: dist
      - name: Test the ABI3 wheel across Python versions
        run: |
          python -m pip install --upgrade pip
          python -m pip install numpy pytest
          python -m pip install --no-index --find-links dist prism-q
          python -m pytest bindings/python/tests -q

  sdist:
    name: sdist
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Build sdist
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --manifest-path bindings/python/Cargo.toml --out dist
      - uses: actions/upload-artifact@v7
        with:
          name: sdist
          path: dist

  sdist-validation:
    name: Test source distribution
    needs: sdist
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@v7
        with:
          python-version: "3.12"
      - uses: dtolnay/rust-toolchain@1.99.0
      - uses: actions/download-artifact@v8
        with:
          name: sdist
          path: ${{ runner.temp }}/sdist
      - name: Install and test outside the checkout
        working-directory: ${{ runner.temp }}
        run: |
          python -m pip install --upgrade pip
          python -m pip install "$RUNNER_TEMP"/sdist/*.tar.gz
          python - <<'PY'
          import numpy as np
          import prism_q

          circuit = prism_q.CircuitBuilder(2).h(0).cx(0, 1).build()
          probabilities = prism_q.simulate(circuit).seed(42).run().probabilities
          assert isinstance(probabilities, np.ndarray)
          assert probabilities.dtype == np.float64
          assert probabilities.shape == (4,)
          np.testing.assert_allclose(probabilities, [0.5, 0.0, 0.0, 0.5], atol=1e-12)

          state = prism_q.simulate(circuit).seed(42).state_vector()
          assert isinstance(state, np.ndarray)
          assert state.dtype == np.complex128
          assert state.shape == (4,)
          np.testing.assert_allclose(state, np.sqrt([0.5, 0.0, 0.0, 0.5]), atol=1e-12)
          PY

  publish:
    name: Publish to PyPI
    if: startsWith(github.ref, 'refs/tags/py-v')
    needs: [build, wheel-compatibility, sdist-validation]
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write
    steps:
      - uses: actions/download-artifact@v8
        with:
          path: dist
          merge-multiple: true
      - name: Publish
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist