Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
22debe2
refactor(cmake): ♻️ compile the core through a reusable object-librar…
robertodr Oct 23, 2026
93b9fa0
refactor(bindings): ♻️ move the mpi4py bridge out of bindings.cpp.in
robertodr Oct 23, 2026
66fba53
feat!: ✨ replace has_mpi with MPI_ABIS
robertodr Oct 23, 2026
a37afde
fix(ci): 🐛 read MPI_ABIS instead of has_mpi in resolve_cores.py
robertodr Oct 23, 2026
a53ddff
feat(python)!: ✨ load the MPI backend only for propagators given a co…
robertodr Oct 23, 2026
a6f9343
fix(nix): 🐛 put Open MPI on the dev shell's loader path for the mpi4p…
robertodr Oct 23, 2026
494fd20
feat(cmake)!: ✨ ship MPI-enabled libmonoprop variants behind an MPI c…
robertodr Oct 23, 2026
f90f1c1
feat(build): ✨ build MPI variants from MPI install prefixes for wheels
robertodr Oct 23, 2026
78b5266
chore(wheels): 🔧 audit MPI linkage and RPATHs of repaired wheels
robertodr Oct 24, 2026
f52dd22
feat(wheels): ✨ publish MPI-enabled wheels for MPICH and Open MPI
robertodr Oct 24, 2026
2cbffd5
fix(wheels): 🐛 expand possibly empty arrays safely for macOS bash 3.2
robertodr Oct 24, 2026
37f94ec
test(wheels): ✅ test release wheels against system, Intel and Homebre…
robertodr Oct 24, 2026
ef2b2e4
docs(wheels): 📝 document monoprop.MPI_ABIS and the MPI wheels workflow
robertodr Oct 24, 2026
87c6864
fix(bench): 🐛 use a communicator only when monoprop has an MPI backend
robertodr Oct 24, 2026
9f771be
fix(wheels): 🐛 prove the MPI backend loads in wheel tests and run the…
robertodr Oct 24, 2026
b16d998
fix(wheels): 🐛 repair macOS wheels and keep the Linux type stub
robertodr Oct 26, 2026
27ae2a2
refactor(cmake): ♻️ inspect mpi.h with try_compile and spell out the …
robertodr Oct 26, 2026
40cc758
docs(mpi): 📝 point users to their system MPI and cite the Cray guidance
robertodr Oct 26, 2026
43d6204
fix(wheels): 🐛 keep macOS MPI search paths and load floor-version MPI…
robertodr Oct 26, 2026
c0e4623
refactor(cmake): ♻️ take monoprop_WHEEL_BUILD straight from CIBUILDWHEEL
robertodr Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@ inputs:
restored cache carries a wheel built for another one.
required: true
mpi:
description: Install an MPI implementation ("on" or "off").
description: >-
Install an MPI implementation: "on" (Open MPI), "mpich", or "off".
required: false
default: "off"
build-deps:
description: >-
Install the packages needed to build monoprop (tools/packages/<manager>.txt). Jobs that only
test a built wheel skip them; Ubuntu 22.04 lacks some of them.
required: false
default: "true"

runs:
using: composite
Expand All @@ -35,12 +42,17 @@ runs:
shell: bash
env:
MPI: ${{ inputs.mpi }}
BUILD_DEPS: ${{ inputs.build-deps }}
run: |
set -euo pipefail

# macOS runners still ship bash 3.2, so no mapfile and no ${var,,}.
packages=()
add_list() {
if [[ ! -f "$1" ]]; then
echo "no package list $1 for mpi=$MPI on $RUNNER_OS" >&2
exit 2
fi
while IFS= read -r package; do
packages+=("$package")
done < <(sed -e 's/#.*//' -e 's/[[:space:]]*$//' "$1" | grep -v '^$' || true)
Expand All @@ -52,9 +64,14 @@ runs:
prefix=tools/packages/apt
fi

add_list "$prefix.txt"
if [[ "$(printf '%s' "$MPI" | tr '[:upper:]' '[:lower:]')" =~ ^(on|true|yes|1)$ ]]; then
if [[ "$BUILD_DEPS" == "true" ]]; then
add_list "$prefix.txt"
fi
mpi="$(printf '%s' "$MPI" | tr '[:upper:]' '[:lower:]')"
if [[ "$mpi" =~ ^(on|true|yes|1)$ ]]; then
add_list "$prefix-mpi.txt"
elif [[ "$mpi" == mpich ]]; then
add_list "$prefix-mpich.txt"
fi

# Derive the package from CXX, so a matrix lane needs no list of its own.
Expand All @@ -69,9 +86,18 @@ runs:
esac
fi

if [[ "$RUNNER_OS" == "macOS" ]]; then
brew install "${packages[@]}"
else
sudo apt-get update
sudo apt-get install -y "${packages[@]}"
if (( ${#packages[@]} > 0 )); then
if [[ "$RUNNER_OS" == "macOS" ]]; then
brew install "${packages[@]}"
else
sudo apt-get update
sudo apt-get install -y "${packages[@]}"
fi
fi

# Ubuntu 22.04 and older name MPICH's library libmpich.so.12, not the libmpi.so.12 the MPICH
# ABI requires; this is the system-wide fix mpi4py's installation guide gives.
libdir="/usr/lib/$(uname -m)-linux-gnu"
if [[ "$mpi" == mpich && -e "$libdir/libmpich.so.12" && ! -e "$libdir/libmpi.so.12" ]]; then
sudo ln -sr "$libdir/libmpich.so.12" "$libdir/libmpi.so.12"
fi
84 changes: 82 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ on:
- VERSION
- pyproject.toml
- packages/monoprop-bench-tools/pyproject.toml
- tools/cibw/**
- tools/packages/**
- cmake/custom/monopropMPI*.cmake
- .github/actions/setup/**

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -53,6 +57,15 @@ jobs:
uses: tj-actions/changed-files@v47.0.6
with:
files: packages/monoprop-bench-tools/pyproject.toml
- id: changed-wheel-support
if: ${{ github.event_name == 'pull_request' }}
uses: tj-actions/changed-files@v47.0.6
with:
files: |
tools/cibw/**
tools/packages/**
cmake/custom/monopropMPI*.cmake
.github/actions/setup/**
- id: check
env:
EVENT_NAME: ${{ github.event_name }}
Expand All @@ -62,6 +75,7 @@ jobs:
VERSION_CHANGED: ${{ steps.changed-version.outputs.any_changed }}
PYPROJECT_CHANGED: ${{ steps.changed-pyproject.outputs.any_changed }}
BENCH_TOOLS_PYPROJECT_CHANGED: ${{ steps.changed-bench-tools-pyproject.outputs.any_changed }}
WHEEL_SUPPORT_CHANGED: ${{ steps.changed-wheel-support.outputs.any_changed }}
run: |
if [[ "$EVENT_NAME" != "pull_request" ]]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
Expand All @@ -79,8 +93,14 @@ jobs:
version_changed = os.environ["VERSION_CHANGED"] == "true"
pyproject_changed = os.environ["PYPROJECT_CHANGED"] == "true"
bench_tools_changed = os.environ["BENCH_TOOLS_PYPROJECT_CHANGED"] == "true"

if deploy_workflow_changed or version_changed or bench_tools_changed:
wheel_support_changed = os.environ["WHEEL_SUPPORT_CHANGED"] == "true"

if (
deploy_workflow_changed
or version_changed
or bench_tools_changed
or wheel_support_changed
):
should_run = True
elif not pyproject_changed:
should_run = False
Expand Down Expand Up @@ -167,6 +187,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' || needs.check-pr-changes.outputs.should-run == 'true' }}
name: Build Python wheels for ${{ matrix.os }}
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 90
strategy:
matrix:
os: [ linux-intel, linux-arm, macos-arm ]
Expand Down Expand Up @@ -199,11 +220,69 @@ jobs:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: wheelhouse/*.whl

test-wheels:
Comment thread
robertodr marked this conversation as resolved.
name: Test wheel with ${{ matrix.mpi }} (${{ matrix.mpi4py }} mpi4py) on ${{ matrix.runner }}
needs: build-wheels
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
# Version floors: Ubuntu 22.04 ships Open MPI 4.1.2 and MPICH 4.0; impi-rt 2021.1.1 is the
# oldest Intel MPI on PyPI. Its Boost (1.74) is below monoprop's 1.85, so the C++
# consumer runs only on macOS here (and in test.yml); inspect-mpi still checks the headers.
- { runner: ubuntu-22.04, artifact: linux-intel, mpi: openmpi-4.1, setup-mpi: "on", build-deps: "false", mpi4py: source, packages: "", abi: openmpi, mpi-include: /usr/lib/x86_64-linux-gnu/openmpi/include, cpp: false }
- { runner: ubuntu-22.04, artifact: linux-intel, mpi: openmpi-4.1, setup-mpi: "on", build-deps: "false", mpi4py: wheel, packages: "", abi: "", mpi-include: "", cpp: false }
- { runner: ubuntu-22.04, artifact: linux-intel, mpi: mpich-4.0, setup-mpi: mpich, build-deps: "false", mpi4py: source, packages: "", abi: mpich, mpi-include: /usr/include/x86_64-linux-gnu/mpich, cpp: false }
- { runner: ubuntu-22.04, artifact: linux-intel, mpi: mpich-4.0, setup-mpi: mpich, build-deps: "false", mpi4py: wheel, packages: "", abi: "", mpi-include: "", cpp: false }
- { runner: ubuntu-22.04, artifact: linux-intel, mpi: intel-2021.1, setup-mpi: "off", build-deps: "false", mpi4py: wheel, packages: "impi-rt==2021.1.1", abi: "", mpi-include: "", cpp: false }
# Latest Intel MPI, and Homebrew Open MPI (the macOS rpath allowlist)
- { runner: ubuntu-26.04, artifact: linux-intel, mpi: intel-latest, setup-mpi: "off", build-deps: "false", mpi4py: wheel, packages: impi-rt, abi: "", mpi-include: "", cpp: false }
- { runner: macos-15, artifact: macos-arm, mpi: homebrew-openmpi, setup-mpi: "on", build-deps: "true", mpi4py: wheel, packages: "", abi: "", mpi-include: "", cpp: true }
env:
MONOPROP_TEST_VENV: ${{ github.workspace }}/.wheel-test-venv
PYTHON: python
steps:
- uses: actions/checkout@v7.0.1

# test-wheel-mpi.sh creates its venv from $PYTHON; pin the interpreter instead of relying on
# the runner image's python3 and its venv module.
- uses: actions/setup-python@v7
with:
python-version: "3.12"

- uses: actions/download-artifact@v8
with:
pattern: cibw-wheels-${{ matrix.artifact }}-*
path: dist
merge-multiple: true

- name: Set up the build environment
uses: ./.github/actions/setup
with:
python-version: "3.12"
cache-suffix: wheel-test-${{ matrix.mpi }}-${{ matrix.mpi4py }}
mpi: ${{ matrix.setup-mpi }}
build-deps: ${{ matrix.build-deps }}

- name: Check configure's MPI inspection on the system headers
if: matrix.mpi-include != ''
run: just inspect-mpi "${{ matrix.mpi-include }}" ${{ matrix.abi }}

- name: Test the wheel under MPI
run: just test-wheel dist ${{ matrix.mpi4py }} ${{ matrix.packages }}

- name: Build a C++ consumer against the installed wheel
if: matrix.cpp
run: just test-find-package build/find-package-smoke "$MONOPROP_TEST_VENV/bin/python"

upload_pypi:
name: Upload to PyPI
needs:
- build-sdist
- build-wheels
- test-wheels
if: github.event_name == 'release' && github.event.action == 'published'

runs-on: ubuntu-26.04
Expand Down Expand Up @@ -262,6 +341,7 @@ jobs:
needs:
- build-sdist
- build-wheels
- test-wheels
- build-bench-tools
if: github.event_name == 'release' && github.event.action == 'published'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scripts/resolve_cores.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def visible_physical_cores(cpus):


def main() -> str | None:
if not monoprop.has_mpi:
if not monoprop.MPI_ABIS:
return "monoprop was built without MPI; every rung shares this binary"

# PHYSICAL cores, and only the ones this process may run on. Physical, because that is what
Expand Down
3 changes: 3 additions & 0 deletions .lychee.postbuild.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ accept = [200, 403, 429]

# False positives:
# - localhost:3000: development server URL used as an example in the docs.
# - cpe.ext.hpe.com: HPE's documentation server sends an incomplete certificate chain, which
# browsers repair and lychee rejects as an unknown issuer.
exclude = [
"^http://localhost",
"^https://cpe\\.ext\\.hpe\\.com/",
]
16 changes: 11 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ set(
)
option(monoprop_ENABLE_MPI "Enable MPI parallelization" OFF)

# cibuildwheel exports CIBUILDWHEEL=1. Wheels must not carry build-machine library paths, which could
# shadow the MPI library mpi4py selects; source installs keep them to find Spack or conda libraries.
# Read on every configure, not cached: a build tree may be reused outside cibuildwheel. Used as a
# boolean, so unset or empty is false.
set(monoprop_WHEEL_BUILD "$ENV{CIBUILDWHEEL}")

Comment thread
robertodr marked this conversation as resolved.
option(monoprop_ENABLE_CXX_UNIT_TESTS "Enable C++ unit test suite" ON)

set(Python_FIND_VIRTUALENV FIRST)
Expand All @@ -65,9 +71,7 @@ find_package(

find_package(Threads REQUIRED)

if(monoprop_ENABLE_MPI)
find_package(MPI REQUIRED COMPONENTS CXX)
endif()
include(${PROJECT_SOURCE_DIR}/cmake/custom/monopropMPI.cmake)

include(${PROJECT_SOURCE_DIR}/cmake/compiler_flags/Sanitizers.cmake)
include(${PROJECT_SOURCE_DIR}/cmake/compiler_flags/CXXFlags.cmake)
Expand Down Expand Up @@ -95,6 +99,8 @@ message(STATUS " User-appended : ${EXTRA_CXXFLAGS}")
message(STATUS " Sanitizer profile : ${monoprop_SANITIZER}")

message(STATUS " MPI parallelization : ${monoprop_ENABLE_MPI}")
message(STATUS " MPI ABI families : ${monoprop_MPI_ABI_NAMES}")
message(STATUS " cibuildwheel build : ${monoprop_WHEEL_BUILD}")
message(STATUS " Max simulable modes : ${monoprop_MAX_NUM_MODES}")
message(STATUS " C++ unit tests : ${monoprop_ENABLE_CXX_UNIT_TESTS}")

Expand All @@ -103,8 +109,8 @@ include(GNUInstallDirs)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})

add_library(monoprop-objs OBJECT "")
add_library(monoprop SHARED $<TARGET_OBJECTS:monoprop-objs>)
# Its objects are attached in cpp/monoprop once every subdirectory has registered its sources.
add_library(monoprop SHARED)

# must run before add_subdirectory(cpp): CTest's enabled-ness does not propagate
# back up to a parent directory that has already been added as a subdirectory.
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,18 @@ with [Bencher](https://bencher.dev/) to catch performance regressions.
pip install monoprop # or: uv add monoprop
```

The prebuilt PyPI wheels are single-process (built **without** MPI). For multi-rank
runs, or to build the C++ library and executables, build from source (see below).
The PyPI wheels support MPI: install mpi4py with the `mpi` extra and pass a communicator to a
propagator. They use the MPI library already on your machine, from the MPICH family (MPICH, Intel
MPI, MVAPICH, Cray MPICH) or Open MPI, as provided by your cluster, package manager or own build:

```bash
pip install "monoprop[mpi]"
```

The [parallelism docs](https://docs.monoprop.algorithmiq.tech/features/parallelism) list the
minimum versions and how to get an MPI onto a machine that has none.

To build the C++ library and executables, build from source (see below).

## Quick example

Expand Down Expand Up @@ -135,8 +145,9 @@ select the MPI variant with `monoprop_ENABLE_MPI=ON` in the environment (or
`-C monoprop.mpi=ON`).
Standalone MPI-enabled C++ programs must initialize MPI before constructing a
propagator and finalize it only after all propagators have been destroyed. The
exported CMake target preserves the package's MPI setting independently of any
`MPI::MPI_CXX` target already present in the consuming project. QA coverage
exported `monoprop::monoprop` CMake target is always serial; MPI consumers request the `MPI`
component (`find_package(monoprop CONFIG REQUIRED COMPONENTS MPI)`) and link
`monoprop::monoprop_mpi`. QA coverage
merges separate serial and MPI-instrumented builds, including two-rank Python
and C++ MPI runs, so both compatibility paths contribute to the reports. Run
`just code-coverage` for the same combined report locally; it builds both
Expand Down
4 changes: 2 additions & 2 deletions benches/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ def pytest_sessionfinish() -> None:

@pytest.fixture(scope="session")
def bench_comm() -> Any:
"""Return the benchmark communicator (``MPI.COMM_WORLD`` or ``None``)."""
return None if MPI is None else MPI.COMM_WORLD
"""Return the benchmark communicator (``MPI.COMM_WORLD``, or ``None`` without an MPI backend)."""
return None if MPI is None or not monoprop.MPI_ABIS else MPI.COMM_WORLD


@pytest.fixture(scope="session")
Expand Down
24 changes: 24 additions & 0 deletions cmake/_mpi_abis.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 Algorithmiq
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""MPI ABI families this build of monoprop ships. Generated by CMake from cmake/_mpi_abis.py.in."""

from __future__ import annotations

ABIS: tuple[str, ...] = (@monoprop_MPI_ABIS_PY@)

# Version floors per MPI implementation, from cmake/custom/monopropMPIABI.cmake.
MIN_VERSIONS: dict[str, tuple[int, ...]] = {@monoprop_MPI_MIN_VERSIONS_PY@}

MODULE_FILES: dict[str, str] = {@monoprop_MPI_MODULE_FILES_PY@}
Loading
Loading