Skip to content

Commit 52f172a

Browse files
frank1010111henryiiipre-commit-ci[bot]
authored
F2py fortran example (#32)
* 🎉 new fortran module * 🐛 fix f2py build error with numpy headers not being found by cmake * add pi-fortran project to nox test list * Use CMakeLists suggested by numpy, which does not depend on find_package(F2PY) * ci: skip Windows for Fortran * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * 🐛 fix f2py underscore problem * add fortran compiler setup to CI workflow * chore: use scikit-build-core instead Signed-off-by: Henry Schreiner <[email protected]> * Delete setup.py --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2c70c57 commit 52f172a

File tree

8 files changed

+127
-1
lines changed

8 files changed

+127
-1
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ jobs:
2626

2727
steps:
2828
- uses: actions/checkout@v3
29+
- uses: awvwgk/setup-fortran@main
30+
id: setup-fortran
31+
with:
32+
compiler: gcc
33+
version: 11
2934
- uses: wntrblm/[email protected]
3035
- run: nox
3136

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
hello_list = ["hello-pure", "hello-cpp", "hello-pybind11", "hello-cython"]
66
if not sys.platform.startswith("win"):
7-
hello_list.append("hello-cmake-package")
7+
hello_list.extend(["hello-cmake-package", "pi-fortran"])
88
long_hello_list = [*hello_list, "pen2-cython", "core-c-hello", "core-pybind11-hello"]
99

1010

projects/pi-fortran/CMakeLists.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
cmake_minimum_required(VERSION 3.17.2...3.26)
2+
3+
project(pi
4+
VERSION 1.0.1
5+
DESCRIPTION "pi estimator"
6+
LANGUAGES C Fortran
7+
)
8+
9+
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module NumPy)
10+
11+
# F2PY headers
12+
execute_process(
13+
COMMAND "${Python_EXECUTABLE}"
14+
-c "import numpy.f2py; print(numpy.f2py.get_include())"
15+
OUTPUT_VARIABLE F2PY_INCLUDE_DIR
16+
OUTPUT_STRIP_TRAILING_WHITESPACE
17+
)
18+
19+
set(f2py_module_name "pi")
20+
set(fortran_src_file "${CMAKE_SOURCE_DIR}/pi/_pi.f")
21+
set(f2py_module_c "${f2py_module_name}module.c")
22+
23+
add_custom_command(
24+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
25+
COMMAND ${PYTHON_EXECUTABLE} -m "numpy.f2py"
26+
"${fortran_src_file}"
27+
"${CMAKE_SOURCE_DIR}/pi/pi.pyf" # Must include custom .pyf file
28+
-m "pi"
29+
--lower # Important
30+
DEPENDS pi/_pi.f # Fortran source
31+
)
32+
33+
python_add_library(${CMAKE_PROJECT_NAME} MODULE
34+
"${f2py_module_name}module.c"
35+
"${F2PY_INCLUDE_DIR}/fortranobject.c"
36+
"${fortran_src_file}" WITH_SOABI)
37+
38+
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
39+
${F2PY_INCLUDE_DIR}
40+
)
41+
42+
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Python::NumPy)
43+
44+
install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION pi)

projects/pi-fortran/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Pi
2+
3+
This is an example project demonstrating the use of scikit-build for distributing a standalone FORTRAN library, *pi*;
4+
a CMake package for that library; and a Python wrapper implemented in f2py.
5+
6+
The example assume some familiarity with CMake and f2py, only really going into detail on the scikit-build parts.
7+
8+
To install the package run in the project directory
9+
10+
```bash
11+
pip install .
12+
```
13+
14+
To run the Python tests, first install the package, then in the project directory run
15+
16+
```bash
17+
pytest
18+
```
19+
20+
This is slightly modified from the example in the [numpy docs](https://numpy.org/devdocs/f2py/buildtools/skbuild.html), but we are using Monte Carlo to estimate the value of $\pi$.
21+
22+
A few surprises:
23+
1. The dreaded underscore problem has a way of cropping up. One solution is explicitly writing out the interface in a [signature (`.pyf`) file](https://numpy.org/devdocs/f2py/signature-file.html).
24+
2. The module will require numpy to work.
25+
3. Between failed builds, it is best to clear out the `_skbuild` folder.

projects/pi-fortran/pi/_pi.f

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
subroutine estimate_pi(pi_estimated, num_trials)
2+
C Estimate pi using Monte Carlo
3+
integer num_trials, num_in_circle
4+
real*8 pi_estimated, x, y
5+
Cf2py intent(in) num_trials
6+
Cf2py intent(out) pi_estimated
7+
8+
num_in_circle = 0d0
9+
do i = 1, num_trials
10+
call random_number(x)
11+
call random_number(y)
12+
if (x**2 + y**2 < 1.0d0) then
13+
num_in_circle = num_in_circle + 1
14+
endif
15+
end do
16+
pi_estimated = (4d0 * num_in_circle) / num_trials
17+
end subroutine estimate_pi

projects/pi-fortran/pi/pi.pyf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
! -*- f90 -*-
2+
! Note: the context of this file is case sensitive.
3+
pymodule pi
4+
interface
5+
subroutine estimate_pi(pi_estimated,num_trials) ! in pi/_pi.f
6+
real*8 intent(out) :: pi_estimated
7+
integer intent(in) :: num_trials
8+
end subroutine estimate_pi
9+
end interface
10+
end pymodule pi

projects/pi-fortran/pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[build-system]
2+
requires = [
3+
"scikit-build-core",
4+
"numpy>=1.21",
5+
]
6+
build-backend = "scikit_build_core.build"
7+
8+
[project]
9+
name = "pi-fortran"
10+
version = "1.0.1"
11+
requires-python = ">=3.7"
12+
dependencies = ["numpy>=1.21"]
13+
14+
[tool.scikit-build]
15+
ninja.minimum-version = "1.10"
16+
cmake.minimum-version = "3.17.2"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import math
2+
3+
import pytest
4+
from pi import pi
5+
6+
7+
def test_estimate_pi():
8+
pi_est = pi.estimate_pi(1e8)
9+
assert pi_est == pytest.approx(math.pi, rel=0.001)

0 commit comments

Comments
 (0)