diff --git a/CHANGELOG.md b/CHANGELOG.md index 9751f5495..752ea5246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ release tags add a leading `v` to the package version. ## Unreleased -- The README and the documentation homepage now open with a short terminal - demo that builds a Fortran module and calls it from Python, followed by links - to Colab, installation, and the real-library examples. +- Improve the Open MPI `mpi_f08` tutorial +- Improve the PRIMA example guide +- The README and the documentation homepage now open with a short terminal demo. - The repository root holds fewer files. Contributor notes moved to `.github/CONTRIBUTING.md`, the pre-push hook to `tools/githooks/` (activate it with `git config core.hooksPath tools/githooks`), and the documentation theme diff --git a/docs/user/examples/fortran/prima-wrapper.md b/docs/user/examples/fortran/prima-wrapper.md index 30af113a0..5526dbda8 100644 --- a/docs/user/examples/fortran/prima-wrapper.md +++ b/docs/user/examples/fortran/prima-wrapper.md @@ -2,91 +2,88 @@ title: Build and Validate PRIMA with PRIK audience: users, advanced users prerequisites: arrays, callbacks, packaging -related: ../../guide/callbacks.md, ../../reference/cli-commands.md +related: ../../guide/callbacks.md, ../../guide/optional-arguments.md, ../../reference/cli-commands.md status: maintained publication: reviewed --- # Build and Validate PRIMA with PRIK -This example builds the checked-in [libPRIMA](https://github.com/libprima/prima) -Fortran sources once and wraps five derivative-free solvers as one Python -extension. Its numerical tests exercise Python callbacks and check known -solutions. +This example turns [PRIMA](https://github.com/libprima/prima), the modern +Fortran implementation of Powell's derivative-free optimization solvers, into +one Python extension. It compiles PRIMA once into a static library, asks PRIK +to generate bindings only for the five solvers, and links those bindings +against that library. Your objective function is an ordinary Python callable. -### What this example shows +### What you get -- Select five `module::procedure` entrypoints while retaining the callback - declarations their signatures need. -- Link a PRIK wrapper to a prebuilt static Fortran archive without compiling - the native sources twice. -- Call the solvers from Python, including optional callbacks and optional - arguments inside callback interfaces. +One extension, `prik_prima`, with exactly five solvers: -You should already be comfortable with NumPy arrays, Python callables, and -building a local Fortran extension. +| Module | Solver | Problem type | +| --- | --- | --- | +| `uobyqa_mod` | `uobyqa` | Unconstrained | +| `newuoa_mod` | `newuoa` | Unconstrained | +| `bobyqa_mod` | `bobyqa` | Bound constraints | +| `lincoa_mod` | `lincoa` | Linear constraints | +| `cobyla_mod` | `cobyla` | Nonlinear constraints | + +Each solver takes your objective as a Python callback and updates a NumPy `x` +in place. Every optional PRIMA argument stays optional in Python, including a +progress callback that can stop the solver early. --- -## Versions used +## Quick start -| Component | Version / source | -| --- | --- | -| PRIK | current repository checkout | -| PRIMA | [libprima/prima commit `1d76fb88`](https://github.com/libprima/prima/tree/1d76fb88aeffb427cd17ed1e9d0d3b34f414913f) | -| Python | 3.12 in the dedicated CI job | -| NumPy | 2.5.1 | -| SciPy (optional comparison) | 1.18.0 in CI | -| Native compilers | GNU Fortran 13 + GCC 13 in CI; compatible local compilers work | - -The source snapshot lives under `examples/fortran/prima/native/`; the build -does not download PRIMA. +From a PRIK checkout with PRIK installed, and with CMake and GNU Fortran on +`PATH` (see [Set up a clean environment](#set-up-a-clean-environment)): -## Tested platforms +```bash +source examples/fortran/prima/build_all.sh +python3 -m pytest -q examples/fortran/prima/tests +``` -The Real Libraries Portability workflow builds and runs the numerical suite -with Python 3.12 on: +The first command builds the extension and puts it on `PYTHONPATH` for this +shell; use `source`, not `bash`, so that setting survives. The second runs the +example's tests. -| Operating system | Architectures | Native toolchain | -| --- | --- | --- | -| Linux | x86-64, ARM64 | GNU Fortran 13 + GCC 13 | -| macOS | Intel, ARM64 | GNU Fortran 13 + GNU GCC 13 | +After this, you can `import prik_prima` in the same shell and call the solvers +as shown in [Use the generated API](#use-the-generated-api). --- -## 1. Prepare the repository and toolchain +## Key files -Clone PRIK, create a virtual environment, and install the Python tools used by -the dedicated CI job: - -```bash -git clone https://github.com/PyNumLab/prik.git -cd prik -python3 -m venv .venv -. .venv/bin/activate -python3 -m pip install --upgrade pip -python3 -m pip install -e ".[qa]" "numpy==2.5.1" -``` +Everything lives under +[`examples/fortran/prima/`](../../../../examples/fortran/prima/): -Install CMake and GNU Fortran separately. On Ubuntu: +| File | What it does | +| --- | --- | +| [`export_symbols.txt`](../../../../examples/fortran/prima/export_symbols.txt) | Names the five `module::procedure` solvers PRIK exposes. | +| [`sources.txt`](../../../../examples/fortran/prima/sources.txt) | Lists the 55 PRIMA sources, used by both CMake and PRIK. | +| [`CMakeLists.txt`](../../../../examples/fortran/prima/CMakeLists.txt) | Compiles those sources into the static library `libprimaf.a`. | +| [`build_prik.sh`](../../../../examples/fortran/prima/build_prik.sh) | Runs the three build steps below. | +| [`build_all.sh`](../../../../examples/fortran/prima/build_all.sh) | Runs `build_prik.sh` and adds the extension to `PYTHONPATH`. | +| [`tests/test_solvers.py`](../../../../examples/fortran/prima/tests/test_solvers.py) | Checks every solver and the callbacks. | +| [`native/`](../../../../examples/fortran/prima/native/) | The PRIMA source snapshot; the build downloads nothing. | -```bash -sudo apt-get update -sudo apt-get install --yes cmake gcc gfortran -gfortran --version -``` +--- -All remaining commands run from the repository root in this shell with the -virtual environment active. The runnable project lives under -[`examples/fortran/prima/`](../../../../examples/fortran/prima/). +## How the build works ---- +PRIMA is compiled once. PRIK reads the same sources to learn the solvers' +interfaces, but it generates bindings only for the five names in +`export_symbols.txt` and links them to the library CMake already built. Use +this pattern for any Fortran library built as a static archive when Python +needs only a few of its entry points: -## 2. Build the PRIK wrapper +```text +55 PRIMA sources ─┬─ cmake ──────────────────────> libprimaf.a ─┐ + │ │ + └─ prik generate --pyi ──> contract/ ─ prik ──┴─> prik_prima +``` -The build script compiles PRIMA into `libprimaf.a`, selects five public -procedures for the generated `.pyi` contract, and links the wrapper against -that archive: +`build_prik.sh` runs those three steps: ```bash @@ -127,107 +124,165 @@ python3 -m prik "$PRIMA_BUILD_ROOT/contract/__init__.pyi" \ --jobs 2 ``` -CMake compiles the 55 native sources once. PRIK analyzes those same sources -with matching real-precision and integer-kind settings, then links its -generated wrapper to the archive. - -For normal use, source the convenience entrypoint: +| Step | Command | Result | +| --- | --- | --- | +| 1. Compile PRIMA | `cmake` | `libprimaf.a` and its Fortran module files | +| 2. Generate the contract | `prik generate --pyi` with `--export-symbols` | One `.pyi` file per solver module, plus the callback interfaces they use | +| 3. Build the extension | `prik` with `--native-link-item archive:…` | `prik_prima`, linked to `libprimaf.a` without recompiling PRIMA | -```bash -source examples/fortran/prima/build_all.sh -``` +Steps 1 and 2 use the same `PRIMA_REAL_PRECISION=64` and +`PRIMA_INTEGER_KIND=0`, so the contract describes the library that was +actually compiled. Everything is written to the temporary `PRIMA_BUILD_ROOT` +directory, not to the repository. -It builds the extension, exports its directory on `PYTHONPATH`, and records -the temporary build directory in `PRIMA_BUILD_ROOT` for this shell. +The generated contracts live in `$PRIMA_BUILD_ROOT/contract/`, one `.pyi` +file per solver module. Open them to see each solver's exact Python signature, +including every optional argument. --- -## 3. Use the generated Python API +## Use the generated API -The public Python API has exactly these entries: +Every solver follows the same pattern: the objective receives the current point +and writes its value into `result`; the solver updates `x` in place. Pass `x` +as a Fortran-ordered `float64` array and integer options as `np.int32`. -| Module | Solver | -| --- | --- | -| `bobyqa_mod` | `bobyqa` | -| `cobyla_mod` | `cobyla` | -| `lincoa_mod` | `lincoa` | -| `newuoa_mod` | `newuoa` | -| `uobyqa_mod` | `uobyqa` | - -For example, UOBYQA minimizes a two-variable quadratic whose known minimum is -at `(1, -2)`. After building the extension, run this in Python: +UOBYQA minimizes a quadratic whose minimum is at `(1, -2)`: ```python import numpy as np import prik_prima -x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64)) - def objective(values, result): result[...] = (values[0] - 1.0) ** 2 + (values[1] + 2.0) ** 2 +x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64)) prik_prima.uobyqa_mod.uobyqa(objective, x, maxfun=np.int32(100)) -np.testing.assert_allclose(x, [1.0, -2.0], atol=2e-3, rtol=0) -print(x) +print(x) # [ 1. -2.] ``` -The callback writes the objective value into `result`; the solver updates `x` -in place. The assertion checks the result against the known minimum. +`newuoa`, `bobyqa`, and `lincoa` take the same objective. `cobyla` handles +nonlinear constraints: its callback also fills a `constraints` array, each +entry meaning `constraint <= 0`, and its second argument `m_nlcon` is the +number of constraints. Requiring `x[1] >= -1` moves the minimum to `(1, -1)`: ---- +```python +def objective_and_constraints(values, result, constraints): + objective(values, result) + constraints[0] = -1.0 - values[1] # x[1] >= -1, written as -1 - x[1] <= 0 -## 4. Run the complete test suite +x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64)) +prik_prima.cobyla_mod.cobyla(objective_and_constraints, np.int32(1), x, maxfun=np.int32(200)) +print(x.round(3)) # [ 1. -1.] +``` -After the build finishes, run: +**Progress callback.** Pass `callback_fcn` to follow each iteration. Setting +`terminate[...] = True` stops the solver early; here UOBYQA stops after 8 +evaluations instead of 22: -```bash -python3 -m pytest -q examples/fortran/prima/tests +```python +def progress(values, f, nf, tr, cstrv, nlconstr, terminate): + print(f"evaluation {nf}: f = {f:.3g}") + if f < 1e-6: + terminate[...] = True + +x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64)) +prik_prima.uobyqa_mod.uobyqa(objective, x, maxfun=np.int32(100), callback_fcn=progress) ``` -The suite checks a numerical result for each of the five exposed solvers, -exact API selection, and callback behavior when optional arguments are -present or omitted. It is not an exhaustive solver-option or constraint -suite. The [test file](../../../../examples/fortran/prima/tests/test_solvers.py) -shows each solver case and checks COBYLA's optional progress callback. +Arguments that a solver does not use arrive as `None`; for example, `cstrv` and +`nlconstr` are `None` in UOBYQA's callback. + +**Final value and evaluation count.** `f`, `nf`, and `info` are optional +Fortran outputs. Pass rank-zero arrays to receive them; omitting them keeps them +absent to PRIMA: + +```python +f = np.zeros((), dtype=np.float64) +nf = np.zeros((), dtype=np.int32) +x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64)) +prik_prima.uobyqa_mod.uobyqa(objective, x, f=f, nf=nf, maxfun=np.int32(100)) +print(float(f), int(nf)) +``` --- -## 5. Run focused examples +## Run the tests -After building the extension, run one solver test or the optional SciPy -comparison: +```bash +python3 -m pytest -q examples/fortran/prima/tests +``` + +The suite checks each solver against the known minimum, that the extension +exposes exactly the five solvers, and the progress callback with optional +arguments present or omitted. It is not an exhaustive solver-option or +constraint suite. + +To compare COBYLA with SciPy's, which also comes from PRIMA: ```bash -python3 -m pytest -q examples/fortran/prima/tests/test_solvers.py::test_lincoa_minimizes_a_quadratic python3 -m pip install "scipy==1.18.0" python3 -m pytest -q examples/fortran/prima/tests/test_solvers.py::test_cobyla_agrees_with_scipy_on_a_quadratic ``` -SciPy's COBYLA also uses PRIMA, so this is a cross-interface comparison; the -known minimizer remains the independent numerical check. To test your own -problem, add a case beside the checked-in tests and run it with pytest. - -- Solver and callback examples → - [`test_solvers.py`](../../../../examples/fortran/prima/tests/test_solvers.py) -- Reviewed API selection → - [`export_symbols.txt`](../../../../examples/fortran/prima/export_symbols.txt) -- Copyable build script → - [`build_prik.sh`](../../../../examples/fortran/prima/build_prik.sh) -- Project instructions → - [`examples/fortran/prima/README.md`](../../../../examples/fortran/prima/README.md) +To test your own problem, add a case beside the checked-in tests. --- +## Set up a clean environment + +Clone PRIK, create a virtual environment, and install the Python tools used by +the dedicated CI job: + +```bash +git clone https://github.com/PyNumLab/prik.git +cd prik +python3 -m venv .venv +. .venv/bin/activate +python3 -m pip install --upgrade pip +python3 -m pip install -e ".[qa]" "numpy==2.5.1" +``` + +Install CMake and GNU Fortran separately. On Ubuntu: + +```bash +sudo apt-get update +sudo apt-get install --yes cmake gcc gfortran +gfortran --version +``` + +Run the example's commands from the repository root with the virtual +environment active. + +## Versions used + +| Component | Version / source | +| --- | --- | +| PRIK | current repository checkout | +| PRIMA | [libprima/prima commit `1d76fb88`](https://github.com/libprima/prima/tree/1d76fb88aeffb427cd17ed1e9d0d3b34f414913f) | +| Python | 3.12 in the dedicated CI job | +| NumPy | 2.5.1 | +| SciPy (optional comparison) | 1.18.0 in CI | +| Native compilers | GNU Fortran 13 + GCC 13 in CI; compatible local compilers work | + +## Tested platforms + +The Real Libraries Portability workflow builds and runs the numerical suite +with Python 3.12 on: + +| Operating system | Architectures | Native toolchain | +| --- | --- | --- | +| Linux | x86-64, ARM64 | GNU Fortran 13 + GCC 13 | +| macOS | Intel, ARM64 | GNU Fortran 13 + GNU GCC 13 | + ## Troubleshooting - Confirm that `cmake` and `gfortran` are available on `PATH`. -- Use `source examples/fortran/prima/build_all.sh`; executing it in a child - shell does not preserve the exported `PYTHONPATH`. +- Use `source examples/fortran/prima/build_all.sh`; running it with `bash` + starts a child shell, so the exported `PYTHONPATH` is lost. - SciPy is optional. The COBYLA comparison skips if it is not installed. - Run one failing solver test with `-vv -s` to see its output. ---- - ## Source provenance The files under [`examples/fortran/prima/native/`](../../../../examples/fortran/prima/native/) diff --git a/docs/user/tutorials/openmpi-f08.md b/docs/user/tutorials/openmpi-f08.md index 4b61c6a00..ebf81ed84 100644 --- a/docs/user/tutorials/openmpi-f08.md +++ b/docs/user/tutorials/openmpi-f08.md @@ -59,15 +59,33 @@ if rank == 0: You build two APIs: the **wrapped API** (`prik_openmpi_f08`), generated by PRIK, and the **Python API** (`prik_mpi.py`), a few lines of Python on top of -it. In the measured setup, both had lower median times than mpi4py for small +it. The Python API is deliberately minimal: it sends only `MPI_INT` buffers and +reports no receive status, to show the pattern rather than all of mpi4py. In +the measured setup, both had lower median times than mpi4py for small `Allreduce` calls; see [Compare call times](#compare-call-times) for the other operations and the test environment. +## How this works + +1. **Select** the 18 `mpi_f08` names the program needs, out of hundreds + ([step 1](#1-choose-what-to-wrap)). +2. **Generate, then edit, a `.pyi` contract.** PRIK writes the Fortran + signatures as they are; the edits make them Pythonic: `ierror` becomes an + exception, `count` comes from the NumPy buffer, and output arguments become + return values ([steps 2-4](#2-generate-the-contract)). +3. **Add a thin Python layer**, `prik_mpi.py`, that gives the familiar + `MPI.COMM_WORLD` and `comm.Send(...)` style ([step 5](#5-add-the-python-api)). + ## What you need - Linux or macOS, Python 3.10 or later, NumPy, and PRIK; see [Installation](../getting-started/installation.md). - GNU Fortran and GCC of the same version; CI uses version 13. +- **Open MPI's source and build trees**, configured with the `mpi_f08` + bindings. PRIK reads the `mpi_f08` Fortran sources, which an installed Open + MPI does not ship. If you do not have them, follow + [Build Open MPI from source](#build-open-mpi-from-source) first; it also + builds a matching mpi4py for the comparison. - The tutorial's files, from the PRIK repository: [`mpi_exports.txt`](https://github.com/PyNumLab/prik/blob/main/tests/fortran/assumed_types/end_to_end/fixtures/contracts/openmpi/mpi_exports.txt), [`mpi_f08.pyi`](https://github.com/PyNumLab/prik/blob/main/tests/fortran/assumed_types/end_to_end/fixtures/contracts/openmpi/mpi_f08.pyi), @@ -88,44 +106,14 @@ for file in \ done ``` -## Install Open MPI and mpi4py - -Build [Open MPI 5.0.11](https://www.open-mpi.org/software/ompi/v5.0/) from -source and keep its source and build trees, which PRIK reads. From your -working directory: +Point two variables at Open MPI's trees, and put that Open MPI's `mpifort` and +`mpirun` on `PATH`: ```bash -OMPI_ROOT="$HOME/openmpi-5.0.11" -TUTORIAL_DIR="$PWD" -mkdir -p "$OMPI_ROOT/source" "$OMPI_ROOT/build" "$OMPI_ROOT/toolchain" -ln -sf "$(command -v gfortran-13)" "$OMPI_ROOT/toolchain/gfortran" -ln -sf "$(command -v gcc-13)" "$OMPI_ROOT/toolchain/gcc" -export PATH="$OMPI_ROOT/toolchain:$PATH" -curl -fsSLO https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.11.tar.bz2 -tar -xjf openmpi-5.0.11.tar.bz2 -C "$OMPI_ROOT/source" --strip-components=1 -cd "$OMPI_ROOT/build" -../source/configure --prefix="$OMPI_ROOT/install" --enable-mpi-fortran=usempif08 CC=gcc FC=gfortran -make -j2 && make install -cd "$TUTORIAL_DIR" -export PATH="$OMPI_ROOT/install/bin:$PATH" -export LD_LIBRARY_PATH="$OMPI_ROOT/install/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" -export DYLD_LIBRARY_PATH="$OMPI_ROOT/install/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" -OMPI_SRC="$OMPI_ROOT/source" -OMPI_BUILD="$OMPI_ROOT/build" +OMPI_SRC="$HOME/openmpi-5.0.11/source" +OMPI_BUILD="$HOME/openmpi-5.0.11/build" ``` -Build mpi4py against the same Open MPI; both checks should report 5.0.11: - -```bash -MPI4PY_BUILD_MPICC="$OMPI_ROOT/install/bin/mpicc" \ - python3 -m pip install --no-cache-dir --no-binary=mpi4py mpi4py==4.1.2 -mpirun --version -python3 -c 'from mpi4py import MPI; print(MPI.Get_library_version())' -``` - -For Open MPI 4.1.8, which CI also tests, change `5.0.11` to `4.1.8` and `v5.0` -to `v4.1`. - ## 1. Choose what to wrap `mpi_exports.txt` selects the names the program needs from `mpi_f08`'s @@ -199,6 +187,35 @@ Replace the published module with the edited one you downloaded: cp mpi_f08.pyi contract/mpi_f08.pyi ``` +The downloaded `mpi_f08.pyi` is the generated contract after these edits. +Compare `allreduce` with the generated version in step 2: + +```python +@raises(status="ierror", success=0) +@bind("MPI_Allreduce") +@native_call([Arg(0), Arg(1), Int32(Arg(1).size), Arg(2), Arg(3), Arg(4), Hidden("ierror", Int32)]) +def allreduce( + sendbuf: Annotated[AnyNative[Flat], ReadOnly], + recvbuf: AnyNative[Flat], + datatype: Mpi_Datatype, + op: Mpi_Op, + comm: Mpi_Comm, +) -> None: ... +``` + +Each function still calls the Fortran routine it names; the edits change only +its Python signature: + +| Edit | Effect in Python | +| --- | --- | +| `@bind("MPI_Send")` on `def send` | The Python name differs from the Fortran name. | +| `Int32(Arg(0).size)` | The count comes from the buffer, so the caller does not pass it. | +| `Return("rank", 0)` | The output argument becomes the return value. | +| `Hidden("ierror", Int32)` with `@raises(status="ierror", success=0)` | A nonzero error code raises an exception. | + +
+The complete edited mpi_f08.pyi + ```python from prik.contracts import Annotated, AnyNative, Arg, Flat, Hidden, Int32, ReadOnly, Return, bind, native_call, raises @@ -319,15 +336,7 @@ __all__ = [ ] ``` -Each function still calls the Fortran routine it names; the edits change only -its Python signature: - -| Edit | Effect in Python | -| --- | --- | -| `@bind("MPI_Send")` on `def send` | The Python name differs from the Fortran name. | -| `Int32(Arg(0).size)` | The count comes from the buffer, so the caller does not pass it. | -| `Return("rank", 0)` | The output argument becomes the return value. | -| `Hidden("ierror", Int32)` with `@raises(status="ierror", success=0)` | A nonzero error code raises an exception. | +
## 4. Build the wrapped API @@ -346,8 +355,33 @@ generated code is compiled; the extension links to the installed Open MPI. ## 5. Add the Python API -`prik_mpi.py` gives the wrapped API mpi4py's `Comm` object. It is kept small: -one datatype, `MPI_INT`, and no receive status. +`prik_mpi.py` imports the wrapped API as `_mpi` and gives it mpi4py's `Comm` +object. Each method forwards to one wrapped function, adding the datatype and +the communicator handle: + +```python +class Comm: + """A communicator, with the methods mpi4py spells for it.""" + + def __init__(self, handle): + self.handle = handle + + def Get_rank(self): + return _mpi.comm_rank(self.handle) + + def Allreduce(self, sendbuf, recvbuf, op=SUM): + _mpi.allreduce(sendbuf, recvbuf, _INT, op, self.handle) + + +COMM_WORLD = Comm(_mpi.mpi_comm_world) + +# Like mpi4py, MPI starts when this module is imported and stops at exit. +_mpi.init() +atexit.register(_mpi.finalize) +``` + +
+The complete prik_mpi.py ```python @@ -411,6 +445,8 @@ _mpi.init() atexit.register(_mpi.finalize) ``` +
+ ## 6. Run it ```bash @@ -455,3 +491,41 @@ median of five runs was: `Get_rank` does almost nothing, so its time is the overhead of a call, which PRIK can still reduce. + +## Build Open MPI from source + +Do this once if you do not already have Open MPI's source and build trees. +It builds [Open MPI 5.0.11](https://www.open-mpi.org/software/ompi/v5.0/) and +keeps both trees, which PRIK reads. From your working directory: + +```bash +OMPI_ROOT="$HOME/openmpi-5.0.11" +TUTORIAL_DIR="$PWD" +mkdir -p "$OMPI_ROOT/source" "$OMPI_ROOT/build" "$OMPI_ROOT/toolchain" +ln -sf "$(command -v gfortran-13)" "$OMPI_ROOT/toolchain/gfortran" +ln -sf "$(command -v gcc-13)" "$OMPI_ROOT/toolchain/gcc" +export PATH="$OMPI_ROOT/toolchain:$PATH" +curl -fsSLO https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.11.tar.bz2 +tar -xjf openmpi-5.0.11.tar.bz2 -C "$OMPI_ROOT/source" --strip-components=1 +cd "$OMPI_ROOT/build" +../source/configure --prefix="$OMPI_ROOT/install" --enable-mpi-fortran=usempif08 CC=gcc FC=gfortran +make -j2 && make install +cd "$TUTORIAL_DIR" +export PATH="$OMPI_ROOT/install/bin:$PATH" +export LD_LIBRARY_PATH="$OMPI_ROOT/install/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +export DYLD_LIBRARY_PATH="$OMPI_ROOT/install/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" +OMPI_SRC="$OMPI_ROOT/source" +OMPI_BUILD="$OMPI_ROOT/build" +``` + +Build mpi4py against the same Open MPI; both checks should report 5.0.11: + +```bash +MPI4PY_BUILD_MPICC="$OMPI_ROOT/install/bin/mpicc" \ + python3 -m pip install --no-cache-dir --no-binary=mpi4py mpi4py==4.1.2 +mpirun --version +python3 -c 'from mpi4py import MPI; print(MPI.Get_library_version())' +``` + +For Open MPI 4.1.8, which CI also tests, change `5.0.11` to `4.1.8` and `v5.0` +to `v4.1`.