From c66e4a128c70ce9f14e0689dfd9a3b8329d224e8 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Wed, 19 Aug 2026 23:02:01 +0800 Subject: [PATCH 01/10] Min Python version --- .github/workflows/unittests.yml | 114 +++++++++++++++++++++++++++----- CONTRIBUTING.md | 25 +++++-- README.md | 2 + docs/ci-testing.md | 26 ++++++-- docs/mpmc-compatibility.md | 23 +++---- makefile | 15 +++++ pyproject.toml | 25 ++++++- test/test_accumulate_data.py | 7 ++ 8 files changed, 200 insertions(+), 37 deletions(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 14342777c..529529dcd 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -21,23 +21,19 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: + # These versions are real: python-version is passed to + # setup-miniconda and asserted by the "Verify interpreter" step. + # Floor is 3.10 -- the `test` extra needs pytest >= 9.0.3 and + # parsl >= 2026.01.05, which both require 3.10+. Older interpreters + # run in the core-tests job below. Each version appears once; the + # full install set resolves on all three OSes for 3.10-3.14. include: - - os: macos-latest - python-version: '3.5' - - os: macos-latest - python-version: '3.8' - os: macos-latest python-version: '3.11' - os: macos-latest python-version: '3.14' - - os: ubuntu-latest - python-version: '3.6' - - os: ubuntu-latest - python-version: '3.9' - os: ubuntu-latest python-version: '3.12' - - os: windows-latest - python-version: '3.7' - os: windows-latest python-version: '3.10' - os: windows-latest @@ -48,9 +44,26 @@ jobs: - uses: conda-incubator/setup-miniconda@v3 with: miniconda-version: "latest" - auto-activate-base: true + python-version: ${{ matrix.python-version }} + channels: conda-forge + auto-activate-base: false conda-remove-defaults: true - use-only-tar-bz2: true + + # Guards the failure mode this matrix used to have: the job name claimed a + # Python version while every job actually ran the conda base interpreter. + # Steps that touch Python must use a profile-loading shell (bash -el / pwsh) + # or they will see base rather than the activated env. + - name: Verify interpreter matches the matrix (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: | + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + + - name: Verify interpreter matches the matrix (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" # ----------------------------------------------------------- # Clean old coverage files @@ -194,21 +207,25 @@ jobs: - name: Build and cache wheels (Linux) if: runner.os == 'Linux' + shell: bash -el {0} run: | - python -m pip wheel -w ./.wheels .[test,test_torch,test_gpytorch,test_botorch,test_umbridge] || true + python -m pip wheel -w ./.wheels ".[test,test_torch,test_gpytorch,test_botorch,test_umbridge]" || true - name: Install Python dependencies (Linux) if: runner.os == 'Linux' + shell: bash -el {0} run: | pip install --find-links ./.wheels -e ".[test,test_torch,test_gpytorch,test_botorch,test_umbridge]" - name: Build and cache wheels (macOS) if: runner.os == 'macOS' + shell: bash -el {0} run: | - python -m pip wheel -w ./.wheels .[test,test_torch,test_gpytorch,test_botorch] || true + python -m pip wheel -w ./.wheels ".[test,test_torch,test_gpytorch,test_botorch]" || true - name: Install Python dependencies (macOS) if: runner.os == 'macOS' + shell: bash -el {0} run: | pip install --find-links ./.wheels -e ".[test,test_torch,test_gpytorch,test_botorch]" @@ -227,5 +244,72 @@ jobs: # ----------------------------------------------------------- # Run unittests for Python source files # ----------------------------------------------------------- - - name: Run unittests (parallel) + - name: Run unittests (parallel, Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make unittests + + - name: Run unittests (parallel, Windows) + if: runner.os == 'Windows' + shell: pwsh run: make unittests + + # Interpreters below the `test` extra's 3.10 floor. `unittests_core` runs + # test/test_*.py against the slim `test_core` extra; modules needing an + # optional stack skip themselves via pytest.importorskip. + # `strict: false` versions are probes -- qmcpy's source is 3.6-clean but + # `qmctoolscl` fails to build below 3.9, so they warn instead of failing. + # This measures the real floor each run: if qmctoolscl starts building on + # 3.8, that job goes green and can be promoted to strict. + core-tests: + name: Core Unit Tests (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - python-version: '3.6' + strict: false + - python-version: '3.7' + strict: false + - python-version: '3.8' + strict: false + - python-version: '3.9' + strict: true + steps: + - uses: actions/checkout@v4 + + - uses: conda-incubator/setup-miniconda@v3 + with: + miniconda-version: "latest" + python-version: ${{ matrix.python-version }} + channels: conda-forge + auto-activate-base: false + activate-environment: qmcpy-core + conda-remove-defaults: true + + - name: Report interpreter + shell: bash -el {0} + run: python -VV + + - name: Install qmcpy and minimal test dependencies + id: install + shell: bash -el {0} + continue-on-error: ${{ !matrix.strict }} + run: pip install -e ".[test_core]" + + - name: Run core unit tests + if: steps.install.outcome == 'success' + shell: bash -el {0} + run: make unittests_core + + - name: Report interpreter below the installable floor + if: steps.install.outcome != 'success' + shell: bash -el {0} + run: | + echo "::warning title=Python ${{ matrix.python-version }} not installable::\ + qmcpy could not be installed on Python ${{ matrix.python-version }}. \ + The qmcpy source itself is 3.6-clean; the blocker is a dependency -- \ + qmctoolscl fails to build on 3.8 and below, and on 3.6 the test_core \ + pins (scikit-learn, pandas) do not resolve either. See the install log \ + above and 'Minimum Python Version by Role' in CONTRIBUTING.md." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4dc7a59a..6b87196d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,10 +60,7 @@ While `dev` contains the most complete set of install dependencies, a number of pip install -e ".[dev]" ~~~ -The `dev` extra includes QMCPy's PyPI-hosted MPMC dependencies. MPMC additionally -requires a platform-specific `pyg_lib` wheel that is not available from PyPI. -After installing `dev`, let the QMCPy installer select the wheel page matching -the installed PyTorch build: +The `dev` extra includes QMCPy's PyPI-hosted MPMC dependencies. MPMC additionally requires a platform-specific `pyg_lib` wheel that is not available from PyPI. After installing `dev`, let the QMCPy installer select the wheel page matching the installed PyTorch build: ~~~bash qmcpy-install-mpmc @@ -76,6 +73,26 @@ pip install -e ".[mpmc]" qmcpy-install-mpmc ~~~ +### Minimum Python Version by Role + +`requires-python` covers a bare install; the optional dependency groups in `pyproject.toml` raise it. Each row shows the strictest floor among that role's pinned dependencies. + +| Role | Install command | Binding dependency | Minimum Python | +|---|---|---|---| +| Application user | `pip install qmcpy` | f-strings in the `qmcpy` source | 3.6 declared, 3.9 real\* | +| + torch / GP features | `pip install "qmcpy[torch,gpytorch,mpmc]"` | `torch >= 2.2.0` | 3.8 | +| + Bayesian optimization | `pip install "qmcpy[botorch]"` | `botorch >= 0.10.0` | 3.9 | +| Course instructor (`class`) | `pip install -e ".[class]"` | `arviz >= 0.17`, `matplotlib >= 3.9.0`, `statsmodels >= 0.14.3` | 3.9 | +| Test developer | `pip install -e ".[test]"` | `pytest >= 9.0.3`, `parsl >= 2026.01.05` | 3.10 | +| Documentation developer | `pip install -e ".[docs]"` | inherits `test`; `pylint >= 4.0.5` | 3.10 | +| Release / core developer | `pip install -e ".[dev]"` | inherits `docs` / `test` | 3.10 | + +Using `qmcpy` needs 3.6+; contributing code, running tests, or building docs needs **3.10+**. We recommend 3.13 for development. + +\*`requires-python` is `>= 3.6` because the source uses f-strings and no later syntax, but the lowest interpreter that can complete `pip install qmcpy` today is **3.9**: `qmctoolscl` ships wheels only for CPython 3.12, and its sdist build fails on 3.8 and below, where setuptools rejects its package metadata. + +CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job runs `make unittests_core` (slim `test_core` extra, no notebook stack) on 3.6-3.9, where 3.9 must pass and 3.6-3.8 are probes that warn if installation fails. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system, with the interpreter pinned from the matrix and asserted before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. + ## 📚 Using `qmcpy` In Courses (`class` Extra) `qmcpy` provides a `class` optional dependency group that installs a complete teaching environment (JupyterLab, plotting, statistics, and utilities) in addition to `qmcpy` itself. diff --git a/README.md b/README.md index b55375193..717482e1d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ The [QMCPy documentation](https://QMCSoftware.github.io/QMCSoftware/) contains a pip install qmcpy ``` +Requires Python >= 3.6 as declared, though 3.9 is the lowest version that installs today. Contributing code, running tests, or building the documentation requires Python >= 3.10 — see the [Minimum Python Version by Role](https://qmcsoftware.github.io/QMCSoftware/CONTRIBUTING/#minimum-python-version-by-role) table in the contributing guidelines for the full breakdown. + To install from source, please see the [contributing guidelines](https://qmcsoftware.github.io/QMCSoftware/CONTRIBUTING/). ## Citation diff --git a/docs/ci-testing.md b/docs/ci-testing.md index 5f33765b4..f7a487cf9 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -6,9 +6,10 @@ This page summarizes QMCPy's current GitHub Actions CI layout. | Workflow | Trigger | Runner / Python | Main work | |---|---|---|---| -| `alltests.yml` | Feature-branch `push` | `ubuntu`, Python `3.13` | | -| `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` | | -| `unittests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.5` to `3.14` | | +| `alltests.yml` | Feature-branch `push` | `ubuntu`, Python `3.13` | | +| `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; branch name ending in `choi`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` | | +| `unittests.yml` (`tests` job) | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.10` to `3.14` | | +| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.6` to `3.9` | | | `docs.yml` | `push` to `master` | `ubuntu`, Python `3.13` | | | `pep8.yml` | `push` to `develop` or `master`; `workflow_dispatch` | `ubuntu`, Python `3.13` | | | `pypi-stats.yml` | Weekly schedule; `workflow_dispatch` | `ubuntu`, Python `3.13` | | @@ -18,13 +19,28 @@ There is no nightly CI schedule. ## Policy - Linux is the default feedback path and runs on every push. -- macOS and Windows in `alltests.yml` are reserved for `develop`/`master` pushes, pull requests into those branches, and manual runs. +- macOS and Windows in `alltests.yml` are reserved for `develop`/`master` pushes, pull requests into those branches, branches whose name ends in `choi`, and manual runs. - `concurrency` cancels superseded runs in both workflows; in `alltests.yml`, `push` and `pull_request` use separate groups so a PR does not inherit cancelled sibling checks from a same-SHA push. -- `alltests.yml` pins Miniconda base Python to `3.13`; `unittests.yml` still uses the base environment without explicitly passing `matrix.python-version` into `setup-miniconda`. +- `unittests.yml` passes `matrix.python-version` to `setup-miniconda` and asserts the running interpreter before testing, so its version labels are real. `alltests.yml` does **not**: it declares Python `3.13` but never passes it, so those jobs run whatever the Miniconda base ships. - Booktests are skipped on feature-branch pushes and run only in the full sweep. +- `unittests.yml` is tiered: the `tests` job installs the full `test` extra (which needs Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), while `core-tests` installs the slim `test_core` extra so the published `requires-python` floor is exercised. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. - UMBridge doctests run only on Linux full sweeps with Docker available. +- MPMC steps in `alltests.yml` are **not** OS-gated: they run on every OS the matrix selects. See [MPMC Coverage by OS](#mpmc-coverage-by-os). - `workflow_dispatch` means manually triggered workflow. +## MPMC Coverage by OS + +MPMC needs a platform-specific `pyg_lib` wheel that PyPI does not carry, installed separately by `qmcpy-install-mpmc`. Only `alltests.yml` does that, and its MPMC steps carry no `if: runner.os` condition, so they run on every OS the matrix selects. + +| Workflow / trigger | Python | Ubuntu | macOS | Windows | +|---|---|---|---|---| +| `alltests.yml`, full sweep | `3.13` | Run | Run | Run | +| `alltests.yml`, feature-branch `push` | `3.13` | Run | Not in matrix | Not in matrix | +| `unittests.yml` (`tests`) | `3.10`-`3.14` | Skipped | Skipped | Skipped | +| `unittests.yml` (`core-tests`) | `3.6`-`3.9` | Skipped | Not in matrix | Not in matrix | + +"Run" covers both the MPMC doctests (`make doctests_mpmc`) and the MPMC unit tests in `test/test_dd_mpmc.py`. `unittests.yml` never calls `qmcpy-install-mpmc`, so those tests skip there via `pytest.importorskip("pyg_lib")` and its jobs pass without exercising MPMC — treat `alltests.yml` as the only source of MPMC signal. See [mpmc-compatibility.md](mpmc-compatibility.md) for the version-support policy behind this split. + ## Related Docs - [tests.md](tests.md): local Makefile targets and coverage commands. diff --git a/docs/mpmc-compatibility.md b/docs/mpmc-compatibility.md index 92d6f1526..5d6b2a12c 100644 --- a/docs/mpmc-compatibility.md +++ b/docs/mpmc-compatibility.md @@ -7,7 +7,7 @@ - Treat MPMC as an optional feature, not part of the minimum QMCPy dependency set. - Prefer `pyg_lib` plus `torch-geometric`; do not require `torch-cluster` as a separate dependency. - For reproducible local work and future CI pinning, prefer a modern PyTorch line with matching `data.pyg.org` wheels installed by `qmcpy-install-mpmc`. -- Keep older Python jobs in `unittests.yml` for core QMCPy coverage, but do not require them to run MPMC. +- `unittests.yml` runs the full suite on `3.10`-`3.14` plus a slim `core-tests` tier on `3.6`-`3.9` (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)); neither installs MPMC. ## Support Policy @@ -17,7 +17,8 @@ | `3.13` | Target | Supported | `torch >= 2.10`, `torch-geometric >= 2.6.1`, `pyg_lib >= 0.6.0` | Run MPMC doctests and unit tests | | `3.12` | Target | Supported | `torch >= 2.10`, `torch-geometric >= 2.6.1`, `pyg_lib >= 0.6.0` | Run MPMC doctests and unit tests | | `3.10` to `3.11` | Best effort | Not a release blocker for MPMC | May work with matching PyTorch / PyG wheels, but not required by current CI policy | Optional manual testing only | -| `3.5` to `3.9` | Legacy core-package coverage only | Not supported for MPMC | Do not spend CI budget trying to keep MPMC running here | No MPMC doctests or unit tests | + +Python `3.6` to `3.9` is covered only by the slim `core-tests` tier, which never installs MPMC's PyTorch Geometric stack (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)). The distinction is intentional: @@ -26,27 +27,25 @@ The distinction is intentional: ## CI Policy -The current CI split should be: +The current CI split is: + +- `alltests.yml`: the only workflow that installs the MPMC stack (`qmcpy-install-mpmc`) and runs `make doctests_mpmc` plus the MPMC unit tests, on Python `3.13`. The steps are not OS-gated: Ubuntu alone on feature-branch pushes, all three OSes on full sweeps. +- `unittests.yml`: `3.10`-`3.14` on all three OSes, plus a `core-tests` tier on Ubuntu for `3.6`-`3.9`. Neither calls `qmcpy-install-mpmc`, so `test/test_dd_mpmc.py` skips throughout via `pytest.importorskip("pyg_lib")`. This workflow gives **no** MPMC coverage. -- `alltests.yml`: full-sweep validation on Linux, macOS, and Windows for Python `3.13`, including `make doctests_mpmc` and the standard unit-test suite. -- `unittests.yml`: a broader version sampler for the repository, with explicit MPMC jobs on Python `3.12`, `3.13`, and `3.14`. -- Older `unittests.yml` jobs: keep them for core QMCPy regressions, but do not require MPMC there. +See [MPMC Coverage by OS](ci-testing.md#mpmc-coverage-by-os) for the per-operating-system breakdown. -This gives one place to enforce modern MPMC compatibility without forcing the entire repository to abandon older Python jobs immediately. +This keeps MPMC enforcement in one place. The trade-off: MPMC regressions are invisible to `unittests.yml`, so raising MPMC coverage means adding a job to `alltests.yml`, not widening the `unittests.yml` matrix. ## Local Developer Commands -Install the usual test and MPMC extras first, then add the platform-specific -PyG runtime with QMCPy's installed helper command: +Install the usual test and MPMC extras first, then add the platform-specific PyG runtime with QMCPy's installed helper command: ```bash python -m pip install -e ".[test,test_torch,test_gpytorch,test_botorch,mpmc]" qmcpy-install-mpmc ``` -The `mpmc` extra contains dependencies available from PyPI. The helper handles -`pyg_lib` separately because its wheel page depends on the installed PyTorch -version and accelerator build, which standard project metadata cannot select. +The `mpmc` extra contains dependencies available from PyPI. The helper handles `pyg_lib` separately because its wheel page depends on the installed PyTorch version and accelerator build, which standard project metadata cannot select. Then run the MPMC-specific checks: diff --git a/makefile b/makefile index 57e4f66e6..c81bea6df 100644 --- a/makefile +++ b/makefile @@ -122,6 +122,21 @@ unittests: ensure_artifacts --no-header \ test/ -W ignore::DeprecationWarning +# Core unit tests only: skips test/booktests/ (needs the notebook stack); other +# modules self-skip via pytest.importorskip. Pairs with the `test_core` extra so +# interpreters at the `requires-python` floor can run this. Unlike `unittests` +# this omits -x: on a compatibility run the full list of failures is the point. +unittests_core: ensure_artifacts + @mkdir -p $(UNIT_COV_DIR) + COVERAGE_FILE=$(UNIT_COV_DIR)/.coverage \ + python -m pytest $(PYTEST_XDIST) $(PYTEST_EXTRA_ARGS) \ + --cov=qmcpy \ + --cov-report term \ + --cov-report json:$(UNIT_COV_DIR)/coverage.json \ + --no-header -rs \ + --ignore=test/booktests \ + test/ -W ignore::DeprecationWarning + tests_no_docker_no_mpmc: doctests_no_docker_no_mpmc unittests coverage ########################################################## diff --git a/pyproject.toml b/pyproject.toml index dac48d235..df417488f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,15 @@ classifiers= [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", + # 3.6-3.8 are omitted deliberately: `requires-python` still allows them + # (the source is 3.6-clean) but qmctoolscl does not build there, so + # advertising support would be false. See CONTRIBUTING.md. + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering :: Mathematics", ] readme = "README.md" @@ -45,7 +54,9 @@ keywords=[ ] license = {file = "LICENSE"} dynamic = ["version"] -requires-python = ">= 3.5" +# Bare-install floor: the source uses f-strings (3.6+). Extras raise this, and +# qmctoolscl does not build below 3.9 -- see CONTRIBUTING.md for the full table. +requires-python = ">= 3.6" dependencies = [ "numpy >= 1.17.0", "scipy >= 1.1.0", @@ -92,6 +103,18 @@ test = [ "nbconvert >= 7.2.9", "pytest-xdist >= 3.8.0", ] +# Minimal set for `make unittests_core` (test/test_*.py only). Omits the +# notebook/booktest stack (parsl, testbook, ...) whose 3.10 floor would +# otherwise force every unit-test job onto 3.10+, so CI can exercise the +# published `requires-python` floor. See CONTRIBUTING.md. +test_core = [ + "pytest >= 7.0", + "pytest-cov >= 4.0", + "pytest-xdist >= 3.0", + "scikit-learn >= 1.0.0", + "pandas >= 1.3.0", + "pyyaml >= 6.0", + ] test_torch = [ "torch >= 2.7.0, < 2.13", # kept in sync with the mpmc extra: PyG pyg_lib wheels stop at torch 2.12 ] diff --git a/test/test_accumulate_data.py b/test/test_accumulate_data.py index 8de9d3ebd..dc17f4f13 100644 --- a/test/test_accumulate_data.py +++ b/test/test_accumulate_data.py @@ -2,8 +2,15 @@ from unittest.mock import patch import numpy as np +import pytest from qmcpy import CubBayesNetG, DigitalNetB2, Keister + +# `pf_gp_ci` imports torch and gpytorch at module level, so skip rather than fail +# collection where those optional stacks are absent (as test_dd_mpmc.py does). +pytest.importorskip("torch") +pytest.importorskip("gpytorch") + from qmcpy.stopping_criterion.pf_gp_ci import PFGPCIData From 500e50fb2bfd72a29b6b6556baccaffb9a8cc731 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Wed, 19 Aug 2026 23:12:38 +0800 Subject: [PATCH 02/10] Pass Python version to environment --- .github/workflows/alltests.yml | 131 ++++++++++++++++++++++++++++----- docs/ci-testing.md | 2 +- 2 files changed, 114 insertions(+), 19 deletions(-) diff --git a/.github/workflows/alltests.yml b/.github/workflows/alltests.yml index a2b8f551c..5c2cfa2b9 100644 --- a/.github/workflows/alltests.yml +++ b/.github/workflows/alltests.yml @@ -68,9 +68,25 @@ jobs: - uses: conda-incubator/setup-miniconda@v3 with: miniconda-version: "latest" - auto-activate-base: true + python-version: ${{ matrix.python-version }} + channels: conda-forge + auto-activate-base: false conda-remove-defaults: true - use-only-tar-bz2: true + + # Without python-version above, matrix.python-version reached only the job + # name and every job ran the conda base interpreter. Steps touching Python + # must use a profile-loading shell (bash -el / pwsh) to see the env. + - name: Verify interpreter matches the matrix (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: | + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + + - name: Verify interpreter matches the matrix (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" # ----------------------------------------------------------- # Clean old coverage files @@ -222,18 +238,22 @@ jobs: - name: Build and cache wheels (Linux) if: runner.os == 'Linux' + shell: bash -el {0} run: | - python -m pip wheel -w ./.wheels .[test,test_torch,test_gpytorch,test_botorch,test_umbridge] || true + python -m pip wheel -w ./.wheels ".[test,test_torch,test_gpytorch,test_botorch,test_umbridge]" || true - name: Install Python dependencies (Linux) if: runner.os == 'Linux' + shell: bash -el {0} run: | pip install --find-links ./.wheels -e ".[test,test_torch,test_gpytorch,test_botorch,test_umbridge]" - name: Build and cache wheels (macOS) if: runner.os == 'macOS' + shell: bash -el {0} run: | - python -m pip wheel -w ./.wheels .[test,test_torch,test_gpytorch,test_botorch] || true + python -m pip wheel -w ./.wheels ".[test,test_torch,test_gpytorch,test_botorch]" || true - name: Install Python dependencies (macOS) if: runner.os == 'macOS' + shell: bash -el {0} run: | pip install --find-links ./.wheels -e ".[test,test_torch,test_gpytorch,test_botorch]" - name: Build and cache wheels (Windows) @@ -246,12 +266,25 @@ jobs: shell: pwsh run: | pip install --find-links ./.wheels -e '.[test,test_torch,test_gpytorch,test_botorch]' - - name: Install MPMC dependencies - run: | - qmcpy-install-mpmc - - name: Validate MPMC dependencies - run: | - python -c "import torch, pyg_lib, torch_geometric; print(f'torch={torch.__version__}'); print('MPMC dependencies ready')" + - name: Install MPMC dependencies (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: qmcpy-install-mpmc + + - name: Install MPMC dependencies (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: qmcpy-install-mpmc + + - name: Validate MPMC dependencies (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: python -c "import torch, pyg_lib, torch_geometric; print(f'torch={torch.__version__}'); print('MPMC dependencies ready')" + + - name: Validate MPMC dependencies (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: python -c "import torch, pyg_lib, torch_geometric; print(f'torch={torch.__version__}'); print('MPMC dependencies ready')" # ----------------------------------------------------------- # Install minimal LaTeX required by Jupyter notebooks (OS-specific) # ----------------------------------------------------------- @@ -393,12 +426,60 @@ jobs: # ----------------------------------------------------------- # Run doctests (OS-specific) # ----------------------------------------------------------- - - run: pip freeze - - run: make doctests_minimal - - run: make doctests_torch - - run: make doctests_gpytorch - - run: make doctests_botorch - - run: make doctests_markdown + - name: pip freeze (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: pip freeze + + - name: pip freeze (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: pip freeze + - name: doctests_minimal (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_minimal + + - name: doctests_minimal (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make doctests_minimal + - name: doctests_torch (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_torch + + - name: doctests_torch (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make doctests_torch + - name: doctests_gpytorch (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_gpytorch + + - name: doctests_gpytorch (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make doctests_gpytorch + - name: doctests_botorch (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_botorch + + - name: doctests_botorch (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make doctests_botorch + - name: doctests_markdown (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_markdown + + - name: doctests_markdown (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make doctests_markdown - name: Run umbridge doctests on Linux full sweeps when Docker is available shell: bash -l {0} run: | @@ -411,12 +492,26 @@ jobs: else echo "Skipping umbridge doctests because Docker is not available on this runner" fi - - name: Run MPMC doctests + - name: Run MPMC doctests (Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make doctests_mpmc + + - name: Run MPMC doctests (Windows) + if: runner.os == 'Windows' + shell: pwsh run: make doctests_mpmc # ----------------------------------------------------------- # Run unittests for Python source files # ----------------------------------------------------------- - - name: Run unittests (parallel) + - name: Run unittests (parallel, Unix) + if: runner.os != 'Windows' + shell: bash -el {0} + run: make unittests + + - name: Run unittests (parallel, Windows) + if: runner.os == 'Windows' + shell: pwsh run: make unittests # ----------------------------------------------------------- diff --git a/docs/ci-testing.md b/docs/ci-testing.md index f7a487cf9..1b1b13b1b 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -21,7 +21,7 @@ There is no nightly CI schedule. - Linux is the default feedback path and runs on every push. - macOS and Windows in `alltests.yml` are reserved for `develop`/`master` pushes, pull requests into those branches, branches whose name ends in `choi`, and manual runs. - `concurrency` cancels superseded runs in both workflows; in `alltests.yml`, `push` and `pull_request` use separate groups so a PR does not inherit cancelled sibling checks from a same-SHA push. -- `unittests.yml` passes `matrix.python-version` to `setup-miniconda` and asserts the running interpreter before testing, so its version labels are real. `alltests.yml` does **not**: it declares Python `3.13` but never passes it, so those jobs run whatever the Miniconda base ships. +- Both `unittests.yml` and `alltests.yml` pass `matrix.python-version` to `setup-miniconda` and assert the running interpreter before any test runs, so their version labels are real. Steps that touch Python use a profile-loading shell (`bash -el {0}` on Unix, `pwsh` on Windows); the default non-login shell silently falls back to the conda base interpreter, which is how these matrices previously went green without testing the versions they named. - Booktests are skipped on feature-branch pushes and run only in the full sweep. - `unittests.yml` is tiered: the `tests` job installs the full `test` extra (which needs Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), while `core-tests` installs the slim `test_core` extra so the published `requires-python` floor is exercised. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. - UMBridge doctests run only on Linux full sweeps with Docker available. From b57dce9ead0abc54071c5867554db7f84d8976c3 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Thu, 20 Aug 2026 10:35:26 +0800 Subject: [PATCH 03/10] Pre-release tests --- .github/workflows/unittests.yml | 48 +++++++++++++++++++++++++++++++++ docs/ci-testing.md | 4 ++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 529529dcd..4a2f03e3f 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -313,3 +313,51 @@ jobs: qmctoolscl fails to build on 3.8 and below, and on 3.6 the test_core \ pins (scikit-learn, pandas) do not resolve either. See the install log \ above and 'Minimum Python Version by Role' in CONTRIBUTING.md." + + # Early-warning job for the next Python. 3.15.0-rc.1 is published in + # actions/python-versions but NOT in conda-forge, so this uses setup-python + # rather than setup-miniconda like the jobs above. + # + # Non-blocking by design: at time of writing scipy (a core dependency) and + # scikit-learn publish no cp315 wheels, so the install is expected to fail + # until the scientific stack catches up. The job exists to tell us the day + # that changes -- when it goes green, promote 3.15 into the `tests` matrix. + prerelease-tests: + name: Core Unit Tests (Python ${{ matrix.python-version }}, pre-release) + runs-on: ubuntu-latest + # Never gate a merge on an unreleased interpreter. + continue-on-error: true + strategy: + fail-fast: false + matrix: + python-version: ['3.15.0-rc.1'] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Report interpreter + run: python -VV + + # Step-level continue-on-error is still needed: without it a failed + # install would skip the reporting step below. + - name: Install qmcpy and minimal test dependencies + id: install + continue-on-error: true + run: pip install -e ".[test_core]" + + - name: Run core unit tests + if: steps.install.outcome == 'success' + run: make unittests_core + + - name: Report pre-release ecosystem not ready + if: steps.install.outcome != 'success' + run: | + echo "::warning title=Python ${{ matrix.python-version }} not installable::\ + qmcpy could not be installed on Python ${{ matrix.python-version }}. \ + This is expected while the scientific stack lacks cp315 wheels (scipy \ + and scikit-learn in particular). See the install log above; when this \ + job passes, promote 3.15 into the tests matrix." diff --git a/docs/ci-testing.md b/docs/ci-testing.md index 1b1b13b1b..b882f1534 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -10,6 +10,7 @@ This page summarizes QMCPy's current GitHub Actions CI layout. | `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; branch name ending in `choi`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` |
  • Doctests
  • MPMC doctests and unit tests on all three OSes
  • `unittests`
  • Coverage upload
  • Booktests
  • Linux-only UMBridge doctests when Docker is available
| | `unittests.yml` (`tests` job) | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.10` to `3.14` |
  • Install test and optional extras
  • Run `unittests`
  • No MPMC stack installed, so MPMC unit tests skip
| | `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.6` to `3.9` |
  • Install `test_core` extra only
  • Run `unittests_core` (no booktests)
  • `3.9` must pass; `3.6`-`3.8` are install probes that warn instead of failing
| +| `unittests.yml` (`prerelease-tests` job) | same as above | `ubuntu`; Python `3.15.0-rc.1` |
  • Uses `actions/setup-python` with `allow-prereleases` (conda-forge has no 3.15)
  • Install `test_core`, run `unittests_core`
  • Non-blocking: expected to fail until `scipy` and `scikit-learn` ship cp315 wheels
| | `docs.yml` | `push` to `master` | `ubuntu`, Python `3.13` |
  • `uml`
  • `copydocs`
  • `mkdocs gh-deploy --force`
| | `pep8.yml` | `push` to `develop` or `master`; `workflow_dispatch` | `ubuntu`, Python `3.13` |
  • `check_pep8`
  • Open a badge-update pull request if badge assets change
| | `pypi-stats.yml` | Weekly schedule; `workflow_dispatch` | `ubuntu`, Python `3.13` |
  • Regenerate PyPI download statistics
  • Publish updated files
| @@ -23,7 +24,8 @@ There is no nightly CI schedule. - `concurrency` cancels superseded runs in both workflows; in `alltests.yml`, `push` and `pull_request` use separate groups so a PR does not inherit cancelled sibling checks from a same-SHA push. - Both `unittests.yml` and `alltests.yml` pass `matrix.python-version` to `setup-miniconda` and assert the running interpreter before any test runs, so their version labels are real. Steps that touch Python use a profile-loading shell (`bash -el {0}` on Unix, `pwsh` on Windows); the default non-login shell silently falls back to the conda base interpreter, which is how these matrices previously went green without testing the versions they named. - Booktests are skipped on feature-branch pushes and run only in the full sweep. -- `unittests.yml` is tiered: the `tests` job installs the full `test` extra (which needs Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), while `core-tests` installs the slim `test_core` extra so the published `requires-python` floor is exercised. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. +- `unittests.yml` is tiered: `tests` installs the full `test` extra (needing Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), `core-tests` installs the slim `test_core` extra so the published `requires-python` floor is exercised, and `prerelease-tests` looks ahead to the next interpreter. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. +- The pre-release tier is informational and never gates a merge. Promote a version out of it into the `tests` matrix once the job passes; `Programming Language :: Python :: 3.15` is deliberately **not** in `pyproject.toml` classifiers until then. - UMBridge doctests run only on Linux full sweeps with Docker available. - MPMC steps in `alltests.yml` are **not** OS-gated: they run on every OS the matrix selects. See [MPMC Coverage by OS](#mpmc-coverage-by-os). - `workflow_dispatch` means manually triggered workflow. From d90b1ce577a589aa249e2e9dec60a3813b3ea059 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Thu, 20 Aug 2026 16:11:49 +0800 Subject: [PATCH 04/10] Get more info --- .github/workflows/alltests.yml | 15 +++++++++++++-- .github/workflows/unittests.yml | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/alltests.yml b/.github/workflows/alltests.yml index 5c2cfa2b9..10497d535 100644 --- a/.github/workflows/alltests.yml +++ b/.github/workflows/alltests.yml @@ -71,6 +71,10 @@ jobs: python-version: ${{ matrix.python-version }} channels: conda-forge auto-activate-base: false + # Name the env explicitly: without this the active environment is + # ambiguous (base vs the auto-created `test`), which can leave pip + # and python pointing at different prefixes. + activate-environment: qmcpy-ci conda-remove-defaults: true # Without python-version above, matrix.python-version reached only the job @@ -80,13 +84,20 @@ jobs: if: runner.os != 'Windows' shell: bash -el {0} run: | - python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + echo "which python : $(which python)" + echo "which pip : $(which pip)" + conda info --envs || true + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" - name: Verify interpreter matches the matrix (Windows) if: runner.os == 'Windows' shell: pwsh run: | - python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + Get-Command python | Format-List + conda info --envs + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" # ----------------------------------------------------------- # Clean old coverage files diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 4a2f03e3f..d0ccc9dfc 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -47,6 +47,10 @@ jobs: python-version: ${{ matrix.python-version }} channels: conda-forge auto-activate-base: false + # Name the env explicitly: without this the active environment is + # ambiguous (base vs the auto-created `test`), which can leave pip + # and python pointing at different prefixes. + activate-environment: qmcpy-ci conda-remove-defaults: true # Guards the failure mode this matrix used to have: the job name claimed a @@ -57,13 +61,20 @@ jobs: if: runner.os != 'Windows' shell: bash -el {0} run: | - python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + echo "which python : $(which python)" + echo "which pip : $(which pip)" + conda info --envs || true + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" - name: Verify interpreter matches the matrix (Windows) if: runner.os == 'Windows' shell: pwsh run: | - python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got); print(sys.version)" + Get-Command python | Format-List + conda info --envs + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" # ----------------------------------------------------------- # Clean old coverage files From 590f95965225c9c280858195d707b0c7e041459b Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Thu, 20 Aug 2026 16:31:39 +0800 Subject: [PATCH 05/10] Security Fix --- .github/workflows/alltests.yml | 7 +++++++ .github/workflows/unittests.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/alltests.yml b/.github/workflows/alltests.yml index 10497d535..fbc6f8c5f 100644 --- a/.github/workflows/alltests.yml +++ b/.github/workflows/alltests.yml @@ -8,6 +8,13 @@ on: - master workflow_dispatch: +# CodeQL "Workflow does not contain permissions": restrict the GITHUB_TOKEN to +# the minimum. These jobs only read the repo -- checkout, conda/python setup, +# caching, and tests. Codecov uploads authenticate with CODECOV_TOKEN, not the +# GITHUB_TOKEN, so no write scope is needed. +permissions: + contents: read + concurrency: # Keep push and pull_request runs separate so same-SHA PR updates do not inherit cancelled push checks. group: alltests-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref_name }} diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index d0ccc9dfc..96d798ad0 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -11,6 +11,13 @@ on: - master workflow_dispatch: +# CodeQL "Workflow does not contain permissions": restrict the GITHUB_TOKEN to +# the minimum. These jobs only read the repo -- checkout, conda/python setup, +# caching, and tests. Codecov uploads authenticate with CODECOV_TOKEN, not the +# GITHUB_TOKEN, so no write scope is needed. +permissions: + contents: read + concurrency: group: unittests-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true From b6c22b98f50d80e41204e1762fbffe316e2fe5a4 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Thu, 20 Aug 2026 17:19:33 +0800 Subject: [PATCH 06/10] requires-python = ">= 3.9" in pyproject.toml --- .github/workflows/unittests.yml | 44 +++++++++------------------------ CONTRIBUTING.md | 10 +++----- README.md | 4 +-- docs/ci-testing.md | 4 +-- docs/mpmc-compatibility.md | 6 ++--- pyproject.toml | 11 ++++----- 6 files changed, 28 insertions(+), 51 deletions(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 96d798ad0..cfb6e73cc 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -272,28 +272,17 @@ jobs: shell: pwsh run: make unittests - # Interpreters below the `test` extra's 3.10 floor. `unittests_core` runs - # test/test_*.py against the slim `test_core` extra; modules needing an - # optional stack skip themselves via pytest.importorskip. - # `strict: false` versions are probes -- qmcpy's source is 3.6-clean but - # `qmctoolscl` fails to build below 3.9, so they warn instead of failing. - # This measures the real floor each run: if qmctoolscl starts building on - # 3.8, that job goes green and can be promoted to strict. + # Exercise the supported 3.9 floor without the `test` extra's Python 3.10+ + # dependencies. `unittests_core` runs test/test_*.py against the slim + # `test_core` extra; modules needing an optional stack skip themselves via + # pytest.importorskip. core-tests: name: Core Unit Tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - include: - - python-version: '3.6' - strict: false - - python-version: '3.7' - strict: false - - python-version: '3.8' - strict: false - - python-version: '3.9' - strict: true + python-version: ['3.9'] steps: - uses: actions/checkout@v4 @@ -306,32 +295,23 @@ jobs: activate-environment: qmcpy-core conda-remove-defaults: true - - name: Report interpreter + - name: Verify interpreter matches the matrix shell: bash -el {0} - run: python -VV + run: | + echo "which python : $(which python)" + echo "which pip : $(which pip)" + conda info --envs || true + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" - name: Install qmcpy and minimal test dependencies - id: install shell: bash -el {0} - continue-on-error: ${{ !matrix.strict }} run: pip install -e ".[test_core]" - name: Run core unit tests - if: steps.install.outcome == 'success' shell: bash -el {0} run: make unittests_core - - name: Report interpreter below the installable floor - if: steps.install.outcome != 'success' - shell: bash -el {0} - run: | - echo "::warning title=Python ${{ matrix.python-version }} not installable::\ - qmcpy could not be installed on Python ${{ matrix.python-version }}. \ - The qmcpy source itself is 3.6-clean; the blocker is a dependency -- \ - qmctoolscl fails to build on 3.8 and below, and on 3.6 the test_core \ - pins (scikit-learn, pandas) do not resolve either. See the install log \ - above and 'Minimum Python Version by Role' in CONTRIBUTING.md." - # Early-warning job for the next Python. 3.15.0-rc.1 is published in # actions/python-versions but NOT in conda-forge, so this uses setup-python # rather than setup-miniconda like the jobs above. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b87196d7..d0a4a1403 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,19 +79,17 @@ qmcpy-install-mpmc | Role | Install command | Binding dependency | Minimum Python | |---|---|---|---| -| Application user | `pip install qmcpy` | f-strings in the `qmcpy` source | 3.6 declared, 3.9 real\* | -| + torch / GP features | `pip install "qmcpy[torch,gpytorch,mpmc]"` | `torch >= 2.2.0` | 3.8 | +| Application user | `pip install qmcpy` | `qmctoolscl >= 1.2.1` | 3.9 | +| + torch / GP features | `pip install "qmcpy[torch,gpytorch,mpmc]"` | inherits the QMCPy floor | 3.9 | | + Bayesian optimization | `pip install "qmcpy[botorch]"` | `botorch >= 0.10.0` | 3.9 | | Course instructor (`class`) | `pip install -e ".[class]"` | `arviz >= 0.17`, `matplotlib >= 3.9.0`, `statsmodels >= 0.14.3` | 3.9 | | Test developer | `pip install -e ".[test]"` | `pytest >= 9.0.3`, `parsl >= 2026.01.05` | 3.10 | | Documentation developer | `pip install -e ".[docs]"` | inherits `test`; `pylint >= 4.0.5` | 3.10 | | Release / core developer | `pip install -e ".[dev]"` | inherits `docs` / `test` | 3.10 | -Using `qmcpy` needs 3.6+; contributing code, running tests, or building docs needs **3.10+**. We recommend 3.13 for development. +Using `qmcpy` needs Python **3.9+**; contributing code, running tests, or building docs needs **3.10+**. We recommend 3.13 for development. -\*`requires-python` is `>= 3.6` because the source uses f-strings and no later syntax, but the lowest interpreter that can complete `pip install qmcpy` today is **3.9**: `qmctoolscl` ships wheels only for CPython 3.12, and its sdist build fails on 3.8 and below, where setuptools rejects its package metadata. - -CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job runs `make unittests_core` (slim `test_core` extra, no notebook stack) on 3.6-3.9, where 3.9 must pass and 3.6-3.8 are probes that warn if installation fails. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system, with the interpreter pinned from the matrix and asserted before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. +CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job runs `make unittests_core` (slim `test_core` extra, no notebook stack) on Python 3.9. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system. Every conda matrix asserts the running interpreter before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. ## 📚 Using `qmcpy` In Courses (`class` Extra) diff --git a/README.md b/README.md index 717482e1d..5d31f0817 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The [QMCPy documentation](https://QMCSoftware.github.io/QMCSoftware/) contains a pip install qmcpy ``` -Requires Python >= 3.6 as declared, though 3.9 is the lowest version that installs today. Contributing code, running tests, or building the documentation requires Python >= 3.10 — see the [Minimum Python Version by Role](https://qmcsoftware.github.io/QMCSoftware/CONTRIBUTING/#minimum-python-version-by-role) table in the contributing guidelines for the full breakdown. +Requires Python >= 3.9. Contributing code, running tests, or building the documentation requires Python >= 3.10 — see the [Minimum Python Version by Role](https://qmcsoftware.github.io/QMCSoftware/CONTRIBUTING/#minimum-python-version-by-role) table in the contributing guidelines for the full breakdown. To install from source, please see the [contributing guidelines](https://qmcsoftware.github.io/QMCSoftware/CONTRIBUTING/). @@ -66,4 +66,4 @@ Want to contribute to QMCPy? Please see our [guidelines for contributors](https: This software would not be possible without the efforts of the [QMCPy community](https://qmcsoftware.github.io/QMCSoftware/community) including our steering council, collaborators, contributors, and sponsors. -QMCPy is distributed under an [Apache 2.0 license from the Illinois Institute of Technology](https://github.com/QMCSoftware/QMCSoftware/blob/master/LICENSE). \ No newline at end of file +QMCPy is distributed under an [Apache 2.0 license from the Illinois Institute of Technology](https://github.com/QMCSoftware/QMCSoftware/blob/master/LICENSE). diff --git a/docs/ci-testing.md b/docs/ci-testing.md index b882f1534..4649ae7fc 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -9,7 +9,7 @@ This page summarizes QMCPy's current GitHub Actions CI layout. | `alltests.yml` | Feature-branch `push` | `ubuntu`, Python `3.13` |
  • Non-Docker doctests
  • MPMC doctests and unit tests (Ubuntu only)
  • `unittests`
  • Coverage upload
| | `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; branch name ending in `choi`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` |
  • Doctests
  • MPMC doctests and unit tests on all three OSes
  • `unittests`
  • Coverage upload
  • Booktests
  • Linux-only UMBridge doctests when Docker is available
| | `unittests.yml` (`tests` job) | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.10` to `3.14` |
  • Install test and optional extras
  • Run `unittests`
  • No MPMC stack installed, so MPMC unit tests skip
| -| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.6` to `3.9` |
  • Install `test_core` extra only
  • Run `unittests_core` (no booktests)
  • `3.9` must pass; `3.6`-`3.8` are install probes that warn instead of failing
| +| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.9` |
  • Install `test_core` extra only
  • Run `unittests_core` (no booktests)
  • Blocking test of the declared Python floor
| | `unittests.yml` (`prerelease-tests` job) | same as above | `ubuntu`; Python `3.15.0-rc.1` |
  • Uses `actions/setup-python` with `allow-prereleases` (conda-forge has no 3.15)
  • Install `test_core`, run `unittests_core`
  • Non-blocking: expected to fail until `scipy` and `scikit-learn` ship cp315 wheels
| | `docs.yml` | `push` to `master` | `ubuntu`, Python `3.13` |
  • `uml`
  • `copydocs`
  • `mkdocs gh-deploy --force`
| | `pep8.yml` | `push` to `develop` or `master`; `workflow_dispatch` | `ubuntu`, Python `3.13` |
  • `check_pep8`
  • Open a badge-update pull request if badge assets change
| @@ -39,7 +39,7 @@ MPMC needs a platform-specific `pyg_lib` wheel that PyPI does not carry, install | `alltests.yml`, full sweep | `3.13` | Run | Run | Run | | `alltests.yml`, feature-branch `push` | `3.13` | Run | Not in matrix | Not in matrix | | `unittests.yml` (`tests`) | `3.10`-`3.14` | Skipped | Skipped | Skipped | -| `unittests.yml` (`core-tests`) | `3.6`-`3.9` | Skipped | Not in matrix | Not in matrix | +| `unittests.yml` (`core-tests`) | `3.9` | Skipped | Not in matrix | Not in matrix | "Run" covers both the MPMC doctests (`make doctests_mpmc`) and the MPMC unit tests in `test/test_dd_mpmc.py`. `unittests.yml` never calls `qmcpy-install-mpmc`, so those tests skip there via `pytest.importorskip("pyg_lib")` and its jobs pass without exercising MPMC — treat `alltests.yml` as the only source of MPMC signal. See [mpmc-compatibility.md](mpmc-compatibility.md) for the version-support policy behind this split. diff --git a/docs/mpmc-compatibility.md b/docs/mpmc-compatibility.md index 5d6b2a12c..ebe1939c9 100644 --- a/docs/mpmc-compatibility.md +++ b/docs/mpmc-compatibility.md @@ -7,7 +7,7 @@ - Treat MPMC as an optional feature, not part of the minimum QMCPy dependency set. - Prefer `pyg_lib` plus `torch-geometric`; do not require `torch-cluster` as a separate dependency. - For reproducible local work and future CI pinning, prefer a modern PyTorch line with matching `data.pyg.org` wheels installed by `qmcpy-install-mpmc`. -- `unittests.yml` runs the full suite on `3.10`-`3.14` plus a slim `core-tests` tier on `3.6`-`3.9` (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)); neither installs MPMC. +- `unittests.yml` runs the full suite on `3.10`-`3.14` plus a slim `core-tests` tier on `3.9` (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)); neither installs MPMC. ## Support Policy @@ -18,7 +18,7 @@ | `3.12` | Target | Supported | `torch >= 2.10`, `torch-geometric >= 2.6.1`, `pyg_lib >= 0.6.0` | Run MPMC doctests and unit tests | | `3.10` to `3.11` | Best effort | Not a release blocker for MPMC | May work with matching PyTorch / PyG wheels, but not required by current CI policy | Optional manual testing only | -Python `3.6` to `3.9` is covered only by the slim `core-tests` tier, which never installs MPMC's PyTorch Geometric stack (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)). +Python `3.9` is covered only by the slim `core-tests` tier, which never installs MPMC's PyTorch Geometric stack (see [Minimum Python Version by Role](CONTRIBUTING.md#minimum-python-version-by-role)). The distinction is intentional: @@ -30,7 +30,7 @@ The distinction is intentional: The current CI split is: - `alltests.yml`: the only workflow that installs the MPMC stack (`qmcpy-install-mpmc`) and runs `make doctests_mpmc` plus the MPMC unit tests, on Python `3.13`. The steps are not OS-gated: Ubuntu alone on feature-branch pushes, all three OSes on full sweeps. -- `unittests.yml`: `3.10`-`3.14` on all three OSes, plus a `core-tests` tier on Ubuntu for `3.6`-`3.9`. Neither calls `qmcpy-install-mpmc`, so `test/test_dd_mpmc.py` skips throughout via `pytest.importorskip("pyg_lib")`. This workflow gives **no** MPMC coverage. +- `unittests.yml`: `3.10`-`3.14` on all three OSes, plus a `core-tests` tier on Ubuntu for `3.9`. Neither calls `qmcpy-install-mpmc`, so `test/test_dd_mpmc.py` skips throughout via `pytest.importorskip("pyg_lib")`. This workflow gives **no** MPMC coverage. See [MPMC Coverage by OS](ci-testing.md#mpmc-coverage-by-os) for the per-operating-system breakdown. diff --git a/pyproject.toml b/pyproject.toml index df417488f..ac077b4d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,8 @@ classifiers= [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", - # 3.6-3.8 are omitted deliberately: `requires-python` still allows them - # (the source is 3.6-clean) but qmctoolscl does not build there, so - # advertising support would be false. See CONTRIBUTING.md. + # Keep these aligned with `requires-python` and the tested interpreter + # matrix. See CONTRIBUTING.md. "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -54,9 +53,9 @@ keywords=[ ] license = {file = "LICENSE"} dynamic = ["version"] -# Bare-install floor: the source uses f-strings (3.6+). Extras raise this, and -# qmctoolscl does not build below 3.9 -- see CONTRIBUTING.md for the full table. -requires-python = ">= 3.6" +# Bare-install floor: qmctoolscl does not build below 3.9. Extras may raise this; +# see CONTRIBUTING.md for the full table. +requires-python = ">= 3.9" dependencies = [ "numpy >= 1.17.0", "scipy >= 1.1.0", From 9959db7c9e66181b2ef585828a5e16fd8b9c036c Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Thu, 20 Aug 2026 22:04:59 +0800 Subject: [PATCH 07/10] Python 3.9 Compatibility --- .github/workflows/unittests.yml | 17 +++++++++++++---- CONTRIBUTING.md | 8 +++++--- docs/ci-testing.md | 4 ++-- pyproject.toml | 4 ++-- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index cfb6e73cc..ae692c903 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -273,9 +273,9 @@ jobs: run: make unittests # Exercise the supported 3.9 floor without the `test` extra's Python 3.10+ - # dependencies. `unittests_core` runs test/test_*.py against the slim - # `test_core` extra; modules needing an optional stack skip themselves via - # pytest.importorskip. + # dependencies. First verify the built wheel as a user would install it; + # then run test/test_*.py against the slim `test_core` extra. Modules needing + # an optional stack skip themselves via pytest.importorskip. core-tests: name: Core Unit Tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest @@ -304,9 +304,18 @@ jobs: python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" + - name: Build and test the user wheel + shell: bash -el {0} + run: | + python -m pip install build + python -m build --wheel + python -m pip install dist/*.whl + python -m pip check + python -c "import os,tempfile; os.chdir(tempfile.gettempdir()); import qmcpy; print(qmcpy.__file__)" + - name: Install qmcpy and minimal test dependencies shell: bash -el {0} - run: pip install -e ".[test_core]" + run: python -m pip install -e ".[test_core]" - name: Run core unit tests shell: bash -el {0} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0a4a1403..8a29a0c25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,9 +77,9 @@ qmcpy-install-mpmc `requires-python` covers a bare install; the optional dependency groups in `pyproject.toml` raise it. Each row shows the strictest floor among that role's pinned dependencies. -| Role | Install command | Binding dependency | Minimum Python | +| Role | Install command | Binding constraint | Minimum Python | |---|---|---|---| -| Application user | `pip install qmcpy` | `qmctoolscl >= 1.2.1` | 3.9 | +| Application user | `pip install qmcpy` | QMCPy support policy | 3.9 | | + torch / GP features | `pip install "qmcpy[torch,gpytorch,mpmc]"` | inherits the QMCPy floor | 3.9 | | + Bayesian optimization | `pip install "qmcpy[botorch]"` | `botorch >= 0.10.0` | 3.9 | | Course instructor (`class`) | `pip install -e ".[class]"` | `arviz >= 0.17`, `matplotlib >= 3.9.0`, `statsmodels >= 0.14.3` | 3.9 | @@ -89,7 +89,9 @@ qmcpy-install-mpmc Using `qmcpy` needs Python **3.9+**; contributing code, running tests, or building docs needs **3.10+**. We recommend 3.13 for development. -CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job runs `make unittests_core` (slim `test_core` extra, no notebook stack) on Python 3.9. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system. Every conda matrix asserts the running interpreter before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. +Python 3.9 is a deliberate QMCPy **support-policy floor**, not a claim about source syntax or `qmctoolscl`'s declared floor. It is the oldest interpreter whose current runtime stack QMCPy commits to support and test; earlier versions are outside that policy even if a particular toolchain can install them. + +CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job builds the QMCPy wheel on Python 3.9, installs it with no extras, checks its dependencies, and imports it from outside the source tree. It then runs `make unittests_core` with the slim `test_core` extra and no notebook stack. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system. Every conda matrix asserts the running interpreter before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. ## 📚 Using `qmcpy` In Courses (`class` Extra) diff --git a/docs/ci-testing.md b/docs/ci-testing.md index 4649ae7fc..ab81cec77 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -9,7 +9,7 @@ This page summarizes QMCPy's current GitHub Actions CI layout. | `alltests.yml` | Feature-branch `push` | `ubuntu`, Python `3.13` |
  • Non-Docker doctests
  • MPMC doctests and unit tests (Ubuntu only)
  • `unittests`
  • Coverage upload
| | `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; branch name ending in `choi`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` |
  • Doctests
  • MPMC doctests and unit tests on all three OSes
  • `unittests`
  • Coverage upload
  • Booktests
  • Linux-only UMBridge doctests when Docker is available
| | `unittests.yml` (`tests` job) | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.10` to `3.14` |
  • Install test and optional extras
  • Run `unittests`
  • No MPMC stack installed, so MPMC unit tests skip
| -| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.9` |
  • Install `test_core` extra only
  • Run `unittests_core` (no booktests)
  • Blocking test of the declared Python floor
| +| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.9` |
  • Build and install the no-extra user wheel, check dependencies, and import outside the source tree
  • Install `test_core`, then run `unittests_core` (no booktests)
  • Blocking test of the declared support-policy floor
| | `unittests.yml` (`prerelease-tests` job) | same as above | `ubuntu`; Python `3.15.0-rc.1` |
  • Uses `actions/setup-python` with `allow-prereleases` (conda-forge has no 3.15)
  • Install `test_core`, run `unittests_core`
  • Non-blocking: expected to fail until `scipy` and `scikit-learn` ship cp315 wheels
| | `docs.yml` | `push` to `master` | `ubuntu`, Python `3.13` |
  • `uml`
  • `copydocs`
  • `mkdocs gh-deploy --force`
| | `pep8.yml` | `push` to `develop` or `master`; `workflow_dispatch` | `ubuntu`, Python `3.13` |
  • `check_pep8`
  • Open a badge-update pull request if badge assets change
| @@ -24,7 +24,7 @@ There is no nightly CI schedule. - `concurrency` cancels superseded runs in both workflows; in `alltests.yml`, `push` and `pull_request` use separate groups so a PR does not inherit cancelled sibling checks from a same-SHA push. - Both `unittests.yml` and `alltests.yml` pass `matrix.python-version` to `setup-miniconda` and assert the running interpreter before any test runs, so their version labels are real. Steps that touch Python use a profile-loading shell (`bash -el {0}` on Unix, `pwsh` on Windows); the default non-login shell silently falls back to the conda base interpreter, which is how these matrices previously went green without testing the versions they named. - Booktests are skipped on feature-branch pushes and run only in the full sweep. -- `unittests.yml` is tiered: `tests` installs the full `test` extra (needing Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), `core-tests` installs the slim `test_core` extra so the published `requires-python` floor is exercised, and `prerelease-tests` looks ahead to the next interpreter. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. +- `unittests.yml` is tiered: `tests` installs the full `test` extra (needing Python `3.10`+ via `pytest >= 9.0.3` and `parsl >= 2026.01.05`), `core-tests` verifies the built no-extra wheel before installing the slim `test_core` extra, and `prerelease-tests` looks ahead to the next interpreter. Test modules self-skip through `pytest.importorskip` when an optional stack is missing. - The pre-release tier is informational and never gates a merge. Promote a version out of it into the `tests` matrix once the job passes; `Programming Language :: Python :: 3.15` is deliberately **not** in `pyproject.toml` classifiers until then. - UMBridge doctests run only on Linux full sweeps with Docker available. - MPMC steps in `alltests.yml` are **not** OS-gated: they run on every OS the matrix selects. See [MPMC Coverage by OS](#mpmc-coverage-by-os). diff --git a/pyproject.toml b/pyproject.toml index ac077b4d7..570ba971d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,8 +53,8 @@ keywords=[ ] license = {file = "LICENSE"} dynamic = ["version"] -# Bare-install floor: qmctoolscl does not build below 3.9. Extras may raise this; -# see CONTRIBUTING.md for the full table. +# Deliberate QMCPy support-policy floor, not a source-language or transitive +# dependency floor. CI verifies the built wheel on 3.9; see CONTRIBUTING.md. requires-python = ">= 3.9" dependencies = [ "numpy >= 1.17.0", From cef0d11284dd712390ef97c9494b3157b3cb4e14 Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Fri, 21 Aug 2026 14:02:20 +0800 Subject: [PATCH 08/10] Fix versions --- .github/workflows/unittests.yml | 50 ++++++++++++++++++++++++++++----- CONTRIBUTING.md | 7 +++-- docs/ci-testing.md | 4 +-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index ae692c903..1ef7bace0 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -276,12 +276,16 @@ jobs: # dependencies. First verify the built wheel as a user would install it; # then run test/test_*.py against the slim `test_core` extra. Modules needing # an optional stack skip themselves via pytest.importorskip. + # The 3.9 support claim is OS-independent, so prove it on every OS we ship + # for. qmctoolscl publishes exactly one wheel (cp312, win_amd64), so all three + # legs build it from its sdist -- which is precisely the risk being covered. core-tests: - name: Core Unit Tests (Python ${{ matrix.python-version }}) - runs-on: ubuntu-latest + name: Core Unit Tests on ${{ matrix.os }} (Python ${{ matrix.python-version }}) + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: + os: [ubuntu-latest, macos-latest, windows-latest] python-version: ['3.9'] steps: - uses: actions/checkout@v4 @@ -295,16 +299,25 @@ jobs: activate-environment: qmcpy-core conda-remove-defaults: true - - name: Verify interpreter matches the matrix + - name: Verify interpreter matches the matrix (Unix) + if: runner.os != 'Windows' shell: bash -el {0} run: | echo "which python : $(which python)" - echo "which pip : $(which pip)" conda info --envs || true python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" - - name: Build and test the user wheel + - name: Verify interpreter matches the matrix (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + conda info --envs + python -c "import sys; print('prefix:',sys.prefix); print(sys.version)" + python -c "import sys; got='.'.join(map(str,sys.version_info[:2])); want='${{ matrix.python-version }}'; assert got==want, 'matrix says %s but interpreter is %s'%(want,got)" + + - name: Build and test the user wheel (Unix) + if: runner.os != 'Windows' shell: bash -el {0} run: | python -m pip install build @@ -313,14 +326,37 @@ jobs: python -m pip check python -c "import os,tempfile; os.chdir(tempfile.gettempdir()); import qmcpy; print(qmcpy.__file__)" - - name: Install qmcpy and minimal test dependencies + - name: Build and test the user wheel (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + python -m pip install build + python -m build --wheel + $wheel = (Get-ChildItem dist/*.whl | Select-Object -First 1).FullName + python -m pip install $wheel + python -m pip check + python -c "import os,tempfile; os.chdir(tempfile.gettempdir()); import qmcpy; print(qmcpy.__file__)" + + - name: Install qmcpy and minimal test dependencies (Unix) + if: runner.os != 'Windows' shell: bash -el {0} run: python -m pip install -e ".[test_core]" - - name: Run core unit tests + - name: Install qmcpy and minimal test dependencies (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: python -m pip install -e ".[test_core]" + + - name: Run core unit tests (Unix) + if: runner.os != 'Windows' shell: bash -el {0} run: make unittests_core + - name: Run core unit tests (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: make unittests_core + # Early-warning job for the next Python. 3.15.0-rc.1 is published in # actions/python-versions but NOT in conda-forge, so this uses setup-python # rather than setup-miniconda like the jobs above. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a29a0c25..d3ee3a831 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,12 +75,13 @@ qmcpy-install-mpmc ### Minimum Python Version by Role -`requires-python` covers a bare install; the optional dependency groups in `pyproject.toml` raise it. Each row shows the strictest floor among that role's pinned dependencies. +`requires-python` covers a bare install; the optional dependency groups in `pyproject.toml` raise it. Each row shows the strictest floor among that role's pinned dependencies. Rows marked `+` add a capability to the Application-user install; unmarked rows are self-contained role profiles. | Role | Install command | Binding constraint | Minimum Python | |---|---|---|---| | Application user | `pip install qmcpy` | QMCPy support policy | 3.9 | -| + torch / GP features | `pip install "qmcpy[torch,gpytorch,mpmc]"` | inherits the QMCPy floor | 3.9 | +| + torch / GP features | `pip install "qmcpy[torch,gpytorch]"` | inherits the QMCPy floor | 3.9 | +| + MPMC | `pip install "qmcpy[mpmc]"`, then `qmcpy-install-mpmc` | `torch >= 2.10.0` | 3.10 | | + Bayesian optimization | `pip install "qmcpy[botorch]"` | `botorch >= 0.10.0` | 3.9 | | Course instructor (`class`) | `pip install -e ".[class]"` | `arviz >= 0.17`, `matplotlib >= 3.9.0`, `statsmodels >= 0.14.3` | 3.9 | | Test developer | `pip install -e ".[test]"` | `pytest >= 9.0.3`, `parsl >= 2026.01.05` | 3.10 | @@ -91,7 +92,7 @@ Using `qmcpy` needs Python **3.9+**; contributing code, running tests, or buildi Python 3.9 is a deliberate QMCPy **support-policy floor**, not a claim about source syntax or `qmctoolscl`'s declared floor. It is the oldest interpreter whose current runtime stack QMCPy commits to support and test; earlier versions are outside that policy even if a particular toolchain can install them. -CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job builds the QMCPy wheel on Python 3.9, installs it with no extras, checks its dependencies, and imports it from outside the source tree. It then runs `make unittests_core` with the slim `test_core` extra and no notebook stack. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system. Every conda matrix asserts the running interpreter before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. +CI measures the lower tier rather than assuming it: `unittests.yml`'s `core-tests` job builds the QMCPy wheel on Python 3.9 on Linux, macOS, and Windows, installs it with no extras, checks its dependencies, and imports it from outside the source tree. The 3.9 claim is OS-independent, and `qmctoolscl` ships only one wheel (cp312, `win_amd64`), so every leg builds it from its source distribution. It then runs `make unittests_core` with the slim `test_core` extra and no notebook stack. Its main `tests` job runs the full suite on 3.10-3.14, each version on one operating system. Every conda matrix asserts the running interpreter before any test runs. Test modules self-skip via `pytest.importorskip` when an optional stack (torch, gpytorch, PyG) is absent, so each interpreter runs what applies to it. ## 📚 Using `qmcpy` In Courses (`class` Extra) diff --git a/docs/ci-testing.md b/docs/ci-testing.md index ab81cec77..97956fdb0 100644 --- a/docs/ci-testing.md +++ b/docs/ci-testing.md @@ -9,7 +9,7 @@ This page summarizes QMCPy's current GitHub Actions CI layout. | `alltests.yml` | Feature-branch `push` | `ubuntu`, Python `3.13` |
  • Non-Docker doctests
  • MPMC doctests and unit tests (Ubuntu only)
  • `unittests`
  • Coverage upload
| | `alltests.yml` | `push` to `develop` or `master`; PR into `develop` or `master`; branch name ending in `choi`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.13` |
  • Doctests
  • MPMC doctests and unit tests on all three OSes
  • `unittests`
  • Coverage upload
  • Booktests
  • Linux-only UMBridge doctests when Docker is available
| | `unittests.yml` (`tests` job) | `push` to `develop` or `master`; PR into `develop` or `master`; `workflow_dispatch` | `ubuntu`, `macos`, `windows`; Python `3.10` to `3.14` |
  • Install test and optional extras
  • Run `unittests`
  • No MPMC stack installed, so MPMC unit tests skip
| -| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`; Python `3.9` |
  • Build and install the no-extra user wheel, check dependencies, and import outside the source tree
  • Install `test_core`, then run `unittests_core` (no booktests)
  • Blocking test of the declared support-policy floor
| +| `unittests.yml` (`core-tests` job) | same as above | `ubuntu`, `macos`, `windows`; Python `3.9` |
  • Build and install the no-extra user wheel, check dependencies, and import outside the source tree
  • Install `test_core`, then run `unittests_core` (no booktests)
  • Blocking test of the declared support-policy floor, on every supported OS
| | `unittests.yml` (`prerelease-tests` job) | same as above | `ubuntu`; Python `3.15.0-rc.1` |
  • Uses `actions/setup-python` with `allow-prereleases` (conda-forge has no 3.15)
  • Install `test_core`, run `unittests_core`
  • Non-blocking: expected to fail until `scipy` and `scikit-learn` ship cp315 wheels
| | `docs.yml` | `push` to `master` | `ubuntu`, Python `3.13` |
  • `uml`
  • `copydocs`
  • `mkdocs gh-deploy --force`
| | `pep8.yml` | `push` to `develop` or `master`; `workflow_dispatch` | `ubuntu`, Python `3.13` |
  • `check_pep8`
  • Open a badge-update pull request if badge assets change
| @@ -39,7 +39,7 @@ MPMC needs a platform-specific `pyg_lib` wheel that PyPI does not carry, install | `alltests.yml`, full sweep | `3.13` | Run | Run | Run | | `alltests.yml`, feature-branch `push` | `3.13` | Run | Not in matrix | Not in matrix | | `unittests.yml` (`tests`) | `3.10`-`3.14` | Skipped | Skipped | Skipped | -| `unittests.yml` (`core-tests`) | `3.9` | Skipped | Not in matrix | Not in matrix | +| `unittests.yml` (`core-tests`) | `3.9` | Skipped | Skipped | Skipped | "Run" covers both the MPMC doctests (`make doctests_mpmc`) and the MPMC unit tests in `test/test_dd_mpmc.py`. `unittests.yml` never calls `qmcpy-install-mpmc`, so those tests skip there via `pytest.importorskip("pyg_lib")` and its jobs pass without exercising MPMC — treat `alltests.yml` as the only source of MPMC signal. See [mpmc-compatibility.md](mpmc-compatibility.md) for the version-support policy behind this split. From 0507fa201b34e9284484fb4e6c9c60ea8f57954e Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Fri, 21 Aug 2026 14:19:57 +0800 Subject: [PATCH 09/10] Fix windows test failures --- qmcpy/discrete_distribution/digital_net_b2/digital_net_b2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qmcpy/discrete_distribution/digital_net_b2/digital_net_b2.py b/qmcpy/discrete_distribution/digital_net_b2/digital_net_b2.py index 0a463507f..89887d578 100644 --- a/qmcpy/discrete_distribution/digital_net_b2/digital_net_b2.py +++ b/qmcpy/discrete_distribution/digital_net_b2/digital_net_b2.py @@ -323,6 +323,8 @@ def __init__( repos = DataSource() if repos.exists(local_root + generating_matrices): datafile = repos.open(local_root + generating_matrices) + elif repos.exists(generating_matrices): + datafile = repos.open(generating_matrices) elif repos.exists( "https://raw.githubusercontent.com/QMCSoftware/LDData/refs/heads/main/dnet/" + generating_matrices @@ -354,8 +356,6 @@ def __init__( "https://raw.githubusercontent.com/QMCSoftware/" + generating_matrices ) - elif repos.exists(generating_matrices): - datafile = repos.open(generating_matrices) else: raise ParameterError("LDData path %s not found" % generating_matrices) contents = [line.rstrip("\n").strip() for line in datafile.readlines()] From 6bf54c0758875228e8fcf29aecf833ab96188a0a Mon Sep 17 00:00:00 2001 From: sou-cheng-choi Date: Fri, 21 Aug 2026 18:19:08 +0800 Subject: [PATCH 10/10] Installs the missing l3backend MiKTeX package --- .github/workflows/alltests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/alltests.yml b/.github/workflows/alltests.yml index fbc6f8c5f..ea8d95f42 100644 --- a/.github/workflows/alltests.yml +++ b/.github/workflows/alltests.yml @@ -426,7 +426,7 @@ jobs: Invoke-WithRetry -Description "Refresh MiKTeX package database" -Script { mpm --admin --update-db } - $packages = @('latexmk','dvipng','cm-super','lmodern','type1cm','tex-gyre') + $packages = @('latexmk','dvipng','cm-super','lmodern','type1cm','tex-gyre','l3backend') foreach ($pkg in $packages) { Invoke-WithRetry -Description "Install MiKTeX package $pkg" -Script { mpm --admin --install=$pkg @@ -437,6 +437,10 @@ jobs: Invoke-WithRetry -Description "Refresh MiKTeX filename database" -Script { initexmf --admin --update-fndb } + $L3BackendPath = kpsewhich l3backend-dvips.def + if ([string]::IsNullOrWhiteSpace($L3BackendPath)) { + throw "MiKTeX install did not provide l3backend-dvips.def" + } latex --version latexmk -v dvipng --version