| title | PRIK — Python Runtime Interop Kit |
|---|---|
| description | PRIK generates native Python bindings from Fortran projects, producing importable extensions and editable .pyi contracts for Pythonic APIs. |
| audience | users |
| prerequisites | none |
| related | user/getting-started/index.md, user/getting-started/installation.md, user/performance.md, developer/architecture.md |
| status | maintained |
| publication | reviewed |
PRIK (Python Runtime Interop Kit) generates native Python
bindings for Fortran, with editable .pyi contracts for shaping
Pythonic APIs.
Project status: Alpha. Core Fortran wrapper workflows are
implemented and tested across supported compilers, but public APIs may still
change before 1.0.
PRIK starts with Fortran-to-Python. Its semantic contract model is designed to support more native languages over time.
Install the package in a virtual environment:
python3 -m pip install prikCreate scale.f90:
real(8) function scale(value, factor) result(output)
real(8), intent(in) :: value
real(8), intent(in) :: factor
output = value * factor
end function scaleBuild an importable extension:
python3 -m prik scale.f90Call the generated Python API:
import numpy as np
import scale
result = scale.scale(np.float64(3.0), np.float64(2.5))
print(result) # 7.5No manual binding code is required. PRIK derives the native wrapper and a readable Python signature from the Fortran source.
For a richer API, PRIK lets you reshape the generated Python surface without changing the native implementation. Switch tabs to compare the default and edited versions.
Create points.f90:
module points
implicit none
type :: point
real(8) :: x = 0.0d0
real(8) :: y = 0.0d0
end type point
contains
subroutine move(item, dx, dy)
type(point), intent(inout) :: item
real(8), intent(in) :: dx, dy
item%x = item%x + dx
item%y = item%y + dy
end subroutine move
real(8) function norm_squared(item) result(value)
type(point), intent(in) :: item
value = item%x * item%x + item%y * item%y
end function norm_squared
end module pointsBuild:
python3 -m prik points.f90 --out geometryimport numpy as np
import geometry.points as points
item = points.point(x=np.float64(3.0), y=np.float64(4.0))
points.move(item, np.float64(1.0), np.float64(-2.0))
print(item.x, item.y) # 4.0 2.0
print(points.norm_squared(item)) # 20.0The generated points.pyi is:
from prik.contracts import Addr, Arg, Float64, native_call
class point:
x: Float64 = 0.0
y: Float64 = 0.0
def __init__(self, *, x: Float64 = 0.0, y: Float64 = 0.0) -> None: ...
@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2))])
def move(item: point, dx: Float64, dy: Float64) -> None: ...
def norm_squared(item: point) -> Float64: ...Generate it:
python3 -m prik generate --pyi points.f90 --out contractsThe edited points.pyi is:
from prik.contracts import Addr, Arg, Float64, Pass, bind, native_call
class point:
x: Float64 = 0.0
y: Float64 = 0.0
def __init__(self, *, x: Float64 = 0.0, y: Float64 = 0.0) -> None: ...
@bind("move")
@native_call([Pass(), Addr(Arg(0)), Addr(Arg(1))])
def translate(self, dx: Float64, dy: Float64) -> None: ...
@bind("norm_squared")
@native_call([Pass()])
def norm_squared(self) -> Float64: ...@bind("move") keeps the original native target while the declaration's
placement and name define the Python-facing API. Pass() supplies the
receiver (self) to the native call; Addr(Arg(...)) passes the remaining
arguments by address as required by the native calling convention.
Build from the contract:
python3 -m prik contracts/__init__.pyi \
--native-fortran-sources points.f90 \
--out geometryThe native Fortran is unchanged, but the Python surface is now:
import numpy as np
import geometry.points as points
item = points.point(x=np.float64(3.0), y=np.float64(4.0))
item.translate(np.float64(1.0), np.float64(-2.0))
print(item.x, item.y) # 4.0 2.0
print(item.norm_squared()) # 20.0Same Fortran source, but a more natural Python API: module procedures become methods.
- Natural Python APIs: Fortran modules become namespaces and derived types become classes.
- Editable contracts: generated
.pyifiles let you rename, hide, flatten, or reorganize the public API. - Explicit native behavior: NumPy dtypes, array layouts, ownership, and lifetimes are checked at the boundary.
- Clear limits: unsupported contracts fail before wrapper generation with actionable diagnostics.
The maintained examples wrap and numerically validate BLAS, LAPACK, FFTPACK, and MINPACK. The reproducible performance comparison measures PRIK and NumPy's f2py against the same Fortran kernels.
The published benchmark compares both tools on the same Fortran sources and the same machine. The charts show the current published snapshot. Results are specific to its machine and toolchain, which are documented with the full results.
Runtime-call performance — values above 1.0× favor PRIK.
The chart shows f2py time ÷ PRIK time: values above 1.0× favor PRIK and
values below 1.0× favor f2py.
Clean end-to-end build time — lower times are better.
See the benchmark machine, full results, and methodology →
Ready to wrap your Fortran project?
Install PRIK →{ .prik-primary-cta } Read Getting Started →{ .prik-primary-cta }
Working on PRIK itself?
Read Developer Documentation →{ .prik-primary-cta }
