Build and Validate MINPACK with PRIK¶
This example turns fortran-lang/minpack, the modern Fortran MINPACK, into one Python extension with all 22 public procedures. MINPACK's solvers call back into Python for each residual, so you write your problem as an ordinary Python function. The suite checks every procedure against exact solutions and direct linear-algebra identities.
What you get¶
One extension, prik_reference_minpack, whose Fortran module minpack_module
holds all 22 procedures:
| Family | Procedures |
|---|---|
| Hybrid nonlinear solvers (root finding) | hybrd, hybrd1, hybrj, hybrj1 |
| Levenberg-Marquardt solvers (least squares) | lmder, lmder1, lmdif, lmdif1, lmstr, lmstr1 |
| Diagnostics and finite differences | chkder, enorm, fdjac1, fdjac2 |
| Factorization and update helpers | dogleg, lmpar, qform, qrfac, qrsolv, r1mpyq, r1updt, rwupdt |
Quick start¶
From a PRIK checkout with PRIK installed and GNU Fortran on PATH (see
Set up a clean environment):
source examples/fortran/minpack/build_all.sh
python3 -m pytest -q examples/fortran/minpack/tests
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
tests.
After this, the solvers import in the same shell:
from prik_reference_minpack import minpack_module as minpack
Use the generated API shows complete calls.
Key files¶
Everything lives under examples/fortran/minpack/:
| File | What it does |
|---|---|
native/minpack.f90 |
The MINPACK source: one file holding the public module and its implementation. |
build_prik.sh |
Builds the extension with one PRIK command. |
build_all.sh |
Runs build_prik.sh and adds the extension to PYTHONPATH. |
routine_inventory.py |
The list of the 22 procedures, grouped by family. |
tests/test_solvers.py |
The root-finding and least-squares solvers, with Python callbacks. |
tests/test_diagnostics.py |
The diagnostics and finite-difference helpers. |
tests/test_linear_algebra.py |
The factorization and update helpers. |
tests/test_scipy_comparison.py |
Compares the eight solvers SciPy exposes with SciPy's MINPACK-based solvers. |
tests/test_routine_coverage.py |
Checks that the inventory, the generated exports, and the tests stay in sync. |
How the build works¶
MINPACK keeps its public declarations and implementations in one source file, so one PRIK command generates the wrapper and compiles MINPACK into the same extension. There is no separate native library:
native/minpack.f90 ──prik──> wrapper + MINPACK, compiled together ──> prik_reference_minpack
build_prik.sh runs that command:
export EXAMPLE_WORKSPACE="$PWD"
export MINPACK_BUILD_ROOT="$(mktemp -d)"
mkdir -p "$MINPACK_BUILD_ROOT/prik/generated"
cd "$MINPACK_BUILD_ROOT/prik"
python3 -m prik "$EXAMPLE_WORKSPACE/examples/fortran/minpack/native/minpack.f90" \
--out prik_reference_minpack \
--out-dir "$MINPACK_BUILD_ROOT/prik/generated" \
--compiler "$(command -v gfortran)" \
--jobs 8 \
--wrapper-fortran-flags="-O0 -g0" \
--wrapper-c-flags="-O0 -g0"
The example uses -O0 so the tests focus on correct results rather than
optimization-dependent ones. Everything is written to the temporary
MINPACK_BUILD_ROOT directory, not to the repository.
Use the generated API¶
MINPACK routines keep their documented Fortran argument order, including work
arrays and their lengths. The callback receives the problem sizes, the current
point, and an output array it fills with the residuals. Each solver returns
MINPACK's info status and updates x in place.
Root finding. hybrd1 finds where the circle x² + y² = 4 meets the curve
y = x³:
import numpy as np
from prik_reference_minpack import minpack_module as minpack
def equations(n, x, fvec, iflag):
fvec[0] = x[0] ** 2 + x[1] ** 2 - 4.0
fvec[1] = x[1] - x[0] ** 3
x = np.array([1.0, 1.0])
fvec = np.empty(2)
info = minpack.hybrd1(equations, np.int32(2), x, fvec, np.float64(1e-10), np.empty(19), np.int32(19))
print(info, x.round(6)) # 1 [1.174222 1.619013]
The work array needs at least n(3n + 13)/2 entries, 19 for two unknowns.
info == 1 means MINPACK estimates the relative error in x is within the
tolerance.
Least squares. lmdif1 fits y = a·exp(b·t) to five points:
t = np.array([0.0, 1.0, 2.0, 3.0, 4.0])
y = 2.0 * np.exp(-0.5 * t)
def residuals(m, n, p, fvec, iflag):
fvec[:] = p[0] * np.exp(p[1] * t) - y
p = np.array([1.0, 0.0])
fvec = np.empty(5)
info = minpack.lmdif1(residuals, np.int32(5), np.int32(2), p, fvec, np.float64(1e-10),
np.empty(2, dtype=np.int32), np.empty(25), np.int32(25))
print(info, p.round(6)) # 2 [ 2. -0.5]
Here m = 5 residuals fit n = 2 parameters; the work array needs at least
m·n + 5n + m entries, 25 in this case. lmdif1 estimates the Jacobian by
finite differences; lmder1 and hybrj1 take a callback that also supplies
it.
How results are validated¶
Each procedure is called with representative data and checked against a known
solution or a direct linear-algebra result. The runnable hybrd1 test solves
x - [1, -2] = 0; its minpack fixture supplies minpack_module:
def test_hybrd1(minpack):
target = np.array([1.0, -2.0], dtype=np.float64)
callback_calls = 0
def residual(_count, x, fvec, _iflag):
nonlocal callback_calls
callback_calls += 1
fvec[:] = x - target
x = np.array([4.0, 4.0], dtype=np.float64)
fvec = np.empty(2, dtype=np.float64)
info = minpack.hybrd1(
residual,
np.int32(2),
x,
fvec,
np.float64(1.0e-12),
np.empty(19, dtype=np.float64),
np.int32(19),
)
assert info == np.int32(1)
assert callback_calls > 0
np.testing.assert_allclose(x, target, atol=1.0e-10)
np.testing.assert_allclose(fvec, 0.0, atol=1.0e-10)
The complete suite applies the same pattern to the other root-finding and least-squares solvers, and checks the helpers with algebraic invariants. It also verifies callback counts, caller-array writebacks, and Fortran-order matrices.
SciPy cross-check. SciPy's root(method="hybr") and
least_squares(method="lm") are built on MINPACK, so the eight solvers they
cover (hybrd, hybrd1, hybrj, hybrj1, lmdif, lmdif1, lmder,
lmder1) are also compared with SciPy on the two nonlinear problems from
Use the generated API. Each case first checks PRIK's
answer independently, then that PRIK and SciPy agree. The other 14 procedures
have no public SciPy counterpart. The comparison skips if SciPy is not
installed:
python3 -m pip install "scipy==1.18.0"
python3 -m pytest -q examples/fortran/minpack/tests/test_scipy_comparison.py
Run the tests¶
Run the complete suite, one family, or one routine:
python3 -m pytest -q examples/fortran/minpack/tests
python3 -m pytest -q examples/fortran/minpack/tests/test_solvers.py
python3 -m pytest -q \
examples/fortran/minpack/tests/test_solvers.py::test_hybrd1
Set up a clean environment¶
Clone PRIK, create a virtual environment, and install the Python tools used by the dedicated CI job:
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 GNU Fortran separately. On Ubuntu:
sudo apt-get update
sudo apt-get install --yes 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 |
| MINPACK | fortran-lang/minpack commit c0b5aea |
| Python | 3.12 in the dedicated CI job |
| NumPy | 2.5.1 |
| Fortran compiler | GNU Fortran 13 in CI; a compatible gfortran works locally |
Tested platforms¶
The Real Libraries Portability workflow builds and runs the complete 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
gfortranis available onPATH. - Use
source examples/fortran/minpack/build_all.sh; running it withbashstarts a child shell, so the exportedPYTHONPATHis lost. AttributeErroron a routine such ashybrd1: import it fromprik_reference_minpack.minpack_module, not from the extension's top level.- Start with one helper or solver test and add
-vv -swhen diagnosing a callback or generated-wrapper failure.
Source provenance¶
examples/fortran/minpack/native/minpack.f90
matches the upstream src/minpack.f90 at
fortran-lang/minpack commit c0b5aea9fcd2b83865af921a7a7e881904f8d3c2.
See the upstream repository, its API documentation, and its license before redistributing the bundled native source.