Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .github/workflows/real-libraries-portability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ jobs:
run: |
source examples/fortran/bspline/build_all.sh
python -m pytest -q examples/fortran/bspline/tests
- name: Run PRIMA example
run: |
source examples/fortran/prima/build_all.sh
python -m pytest -q examples/fortran/prima/tests
- name: Run Pythonic BLAS API example
run: |
source examples/fortran/pythonic_blas/build.sh
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ release tags add a leading `v` to the package version.

## Unreleased

- `--export-symbols` and `build_fortran_extension(export_symbols=...)` accept
module-qualified Fortran procedure identities. Generated contracts and
source builds publish only that reviewed function surface while retaining
callback, type, and other declaration dependencies required by its
signatures.
- The PRIMA example links five derivative-free solvers against one statically
compiled `libprimaf` archive through a generated semantic contract and runs
in the real-library portability matrix. Its guide includes a reproducible
build, source-checked build and test examples, and an optional SciPy COBYLA
parity check.

- Forwardable Fortran optional arguments use a linear number of contained
procedures and converge on one native call site instead of enumerating
presence combinations. Descriptor categories that cannot be forwarded,
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ provides task-oriented recipes for reshaping the API.

## Proven on real libraries

PRIK builds and numerically tests seven maintained libraries, not just generated
PRIK builds and numerically tests eight maintained libraries, not just generated
wrappers.

| Project | Native language and PRIK input | Validated surface |
Expand All @@ -180,10 +180,11 @@ wrappers.
| [FFTPACK](https://pynumlab.github.io/prik/user/examples/fortran/fftpack-wrapper/) | Fortran source/interfaces | 31 Fourier, cosine, and sine transform procedures |
| [MINPACK](https://pynumlab.github.io/prik/user/examples/fortran/minpack-wrapper/) | Fortran source/interfaces | 22 nonlinear and least-squares procedures, including callbacks |
| [BSPLINE-FORTRAN](https://pynumlab.github.io/prik/user/examples/fortran/bspline-wrapper/) | Fortran source/interfaces | 15 interpolation routines and modern Fortran classes |
| [PRIMA](https://pynumlab.github.io/prik/user/examples/fortran/prima-wrapper/) | Fortran source and generated contract; link static archive | 5 derivative-free solvers with Python callbacks |
| [libm](https://pynumlab.github.io/prik/user/examples/c/libm-wrapper/) | C declarations from `<math.h>`; link compiled platform libm | 60 target-generated ISO C99 math functions |
| [TA-Lib](https://pynumlab.github.io/prik/user/examples/c/ta-lib-wrapper/) | C declarations from `ta_libc.h`; link compiled `libta-lib` | All 322 double and float-input indicators over NumPy arrays, checked against TA-Lib's reference results |

The **Real Libraries Portability** workflow runs all seven on Linux x86-64,
The **Real Libraries Portability** workflow runs all eight on Linux x86-64,
Linux ARM64, macOS Intel, and macOS ARM64 with Python 3.12. See the [Examples
Gallery](https://pynumlab.github.io/prik/user/examples/#tested-platforms) for the
compiler matrix; each project guide also records its own tested platforms.
Expand Down
4 changes: 2 additions & 2 deletions docs/developer/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ reparse source — and say why in the test name or a comment beside it. A
modified-contract test stays separate because it asserts a deliberately
different public API.

The real-library suite covers five Fortran projects—BLAS, LAPACK, FFTPACK,
MINPACK, and BSPLINE-FORTRAN—plus the C libm and TA-Lib projects.
The real-library suite covers six Fortran projects—BLAS, LAPACK, FFTPACK,
MINPACK, BSPLINE-FORTRAN, and PRIMA—plus the C libm and TA-Lib projects.

## Test And Fixture Placement

Expand Down
6 changes: 3 additions & 3 deletions docs/developer/workflows/quality-assurance.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ Minimize an actionable fuzz failure and retain it as a focused regression.

Native changes need focused codegen evidence and relevant end-to-end coverage.
Ordinary local runs exclude `real_library`. Maintained coverage includes the
five Fortran examples—BLAS, LAPACK, FFTPACK, MINPACK, and BSPLINE-FORTRAN—and
six Fortran examples—BLAS, LAPACK, FFTPACK, MINPACK, BSPLINE-FORTRAN, and PRIMA—and
the C libm and TA-Lib examples across the hosted portability matrix. Each has
its own example workflow; leave LAPACK wrapper tests to GitHub Actions
unless explicitly requested. See [Pull request checks](ci.md) for hosted
its own build and tests in that workflow; leave LAPACK wrapper tests to GitHub
Actions unless explicitly requested. See [Pull request checks](ci.md) for hosted
coverage, compiler, real-library, benchmark, and documentation evidence.
7 changes: 4 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,13 @@ its `.pyi` contract. It needs no installation.

## Proven on real libraries

The maintained example suite covers five Fortran libraries—
The maintained example suite covers six Fortran libraries—
[BLAS](user/examples/fortran/blas-wrapper.md),
[LAPACK](user/examples/fortran/lapack-wrapper.md),
[FFTPACK](user/examples/fortran/fftpack-wrapper.md),
[MINPACK](user/examples/fortran/minpack-wrapper.md), and
[BSPLINE-FORTRAN](user/examples/fortran/bspline-wrapper.md)—and two C
[MINPACK](user/examples/fortran/minpack-wrapper.md),
[BSPLINE-FORTRAN](user/examples/fortran/bspline-wrapper.md), and
[PRIMA](user/examples/fortran/prima-wrapper.md)—and two C
libraries: [libm](user/examples/c/libm-wrapper.md) and
[TA-Lib](user/examples/c/ta-lib-wrapper.md). Each project has a complete build
and numerical validation workflow, including its tested platforms and
Expand Down
275 changes: 275 additions & 0 deletions docs/user/examples/fortran/prima-wrapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
---
title: Build and Validate PRIMA with PRIK
audience: users, advanced users
prerequisites: arrays, callbacks, packaging
related: ../../guide/callbacks.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.

### What this example shows

- 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.

You should already be comfortable with NumPy arrays, Python callables, and
building a local Fortran extension.

---

## 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 |

The source snapshot lives under `examples/fortran/prima/native/`; the build
does not download PRIMA.

## 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 |

---

## 1. Prepare the repository and toolchain

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
```

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/).

---

## 2. Build the PRIK wrapper

The build script compiles PRIMA into `libprimaf.a`, selects five public
procedures for the generated `.pyi` contract, and links the wrapper against
that archive:

<!-- prik-doc-source: examples/fortran/prima/build_prik.sh -->
```bash
export EXAMPLE_WORKSPACE="$PWD"
export PRIMA_BUILD_ROOT="$(mktemp -d)"

mkdir -p "$PRIMA_BUILD_ROOT/prik/generated"

cmake \
-S "$EXAMPLE_WORKSPACE/examples/fortran/prima" \
-B "$PRIMA_BUILD_ROOT/native" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_Fortran_COMPILER="$(command -v gfortran)"
cmake --build "$PRIMA_BUILD_ROOT/native" --target primaf --parallel 2

PRIMA_SOURCES=()
while IFS= read -r source; do
PRIMA_SOURCES+=("$EXAMPLE_WORKSPACE/examples/fortran/prima/native/$source")
done < "$EXAMPLE_WORKSPACE/examples/fortran/prima/sources.txt"

python3 -m prik generate --pyi \
"${PRIMA_SOURCES[@]}" \
--export-symbols "$EXAMPLE_WORKSPACE/examples/fortran/prima/export_symbols.txt" \
--out "$PRIMA_BUILD_ROOT/contract" \
--compiler "$(command -v gfortran)" \
-I "$EXAMPLE_WORKSPACE/examples/fortran/prima/native/common" \
-D PRIMA_REAL_PRECISION=64 \
-D PRIMA_INTEGER_KIND=0

cd "$PRIMA_BUILD_ROOT/prik"
python3 -m prik "$PRIMA_BUILD_ROOT/contract/__init__.pyi" \
--out prik_prima \
--out-dir "$PRIMA_BUILD_ROOT/prik/generated" \
--compiler "$(command -v gfortran)" \
--native-link-item "archive:$PRIMA_BUILD_ROOT/native/libprimaf.a" \
--native-linker-language fortran \
-I "$PRIMA_BUILD_ROOT/native/mod" \
--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:

```bash
source examples/fortran/prima/build_all.sh
```

It builds the extension, exports its directory on `PYTHONPATH`, and records
the temporary build directory in `PRIMA_BUILD_ROOT` for this shell.

---

## 3. Use the generated Python API

The public Python API has exactly these entries:

| 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:

```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

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)
```

The callback writes the objective value into `result`; the solver updates `x`
in place. The assertion checks the result against the known minimum.

---

## 4. Run the complete test suite

After the build finishes, run:

```bash
python3 -m pytest -q examples/fortran/prima/tests
```

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.

---

## 5. See how results are validated

For the quadratic in section 3, the known minimizer `(1, -2)` is the primary
numerical check. The COBYLA test below also confirms that its optional
progress callback receives the expected argument shapes. The test file's
`_objective` helper evaluates `(x[0] - 1)^2 + (x[1] + 2)^2`:

<!-- prik-doc-source: examples/fortran/prima/tests/test_solvers.py::test_cobyla_runs_with_every_optional_callback_dummy_present -->
```python
def test_cobyla_runs_with_every_optional_callback_dummy_present(prima):
x = np.asfortranarray(np.array([3.0, 0.0], dtype=np.float64))
observed = []

def objective_and_constraints(values, f, constraints):
_objective(values, f)

def progress(values, f, nf, tr, cstrv, nlconstr, terminate):
observed.append((f, nf, tr, cstrv, nlconstr.shape, terminate.shape))

prima.cobyla_mod.cobyla(
objective_and_constraints,
np.int32(0),
x,
maxfun=np.int32(100),
callback_fcn=progress,
)

np.testing.assert_allclose(x, np.array([1.0, -2.0]), atol=2.0e-3, rtol=0.0)
assert observed
assert observed[-1][4:] == ((0,), ())
```

SciPy 1.18's
[COBYLA implementation](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html)
also comes from PRIMA, so the optional SciPy test is a cross-interface parity
check rather than an independent algorithmic oracle. Both results are also
checked against the known minimizer `(1, -2)`.

---

## 6. Run focused examples

After building the extension, run one solver test or the optional SciPy
comparison:

```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
```

The checked-in test file is a starting point for your own cases: add a
`test_*` function there, or a `test_*.py` file beside it. The shared `prima`
fixture imports the built extension. Change the objective, initial `x`, and
expected result, then run your new test with the same pytest command.

- 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)

---

## 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`.
- 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/)
match [libprima/prima commit `1d76fb88aeffb427cd17ed1e9d0d3b34f414913f`](https://github.com/libprima/prima/tree/1d76fb88aeffb427cd17ed1e9d0d3b34f414913f).
They retain the upstream [BSD 3-Clause license](../../../../examples/fortran/prima/LICENCE.txt).
Loading
Loading