Skip to content

F2py fortran example #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 11, 2023
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: awvwgk/setup-fortran@main
id: setup-fortran
with:
compiler: gcc
version: 11
- uses: wntrblm/nox@2022.11.21
- run: nox

Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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


Expand Down
44 changes: 44 additions & 0 deletions projects/pi-fortran/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.17.2...3.26)

project(pi
VERSION 1.0.1
DESCRIPTION "pi estimator"
LANGUAGES C Fortran
)

find_package(Python REQUIRED COMPONENTS Interpreter Development.Module NumPy)

# F2PY headers
execute_process(
COMMAND "${Python_EXECUTABLE}"
-c "import numpy.f2py; print(numpy.f2py.get_include())"
OUTPUT_VARIABLE F2PY_INCLUDE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)

set(f2py_module_name "pi")
set(fortran_src_file "${CMAKE_SOURCE_DIR}/pi/_pi.f")
set(f2py_module_c "${f2py_module_name}module.c")

add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
COMMAND ${PYTHON_EXECUTABLE} -m "numpy.f2py"
"${fortran_src_file}"
"${CMAKE_SOURCE_DIR}/pi/pi.pyf" # Must include custom .pyf file
-m "pi"
--lower # Important
DEPENDS pi/_pi.f # Fortran source
)

python_add_library(${CMAKE_PROJECT_NAME} MODULE
"${f2py_module_name}module.c"
"${F2PY_INCLUDE_DIR}/fortranobject.c"
"${fortran_src_file}" WITH_SOABI)

target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
${F2PY_INCLUDE_DIR}
)

target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Python::NumPy)

install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION pi)
25 changes: 25 additions & 0 deletions projects/pi-fortran/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Pi

This is an example project demonstrating the use of scikit-build for distributing a standalone FORTRAN library, *pi*;
a CMake package for that library; and a Python wrapper implemented in f2py.

The example assume some familiarity with CMake and f2py, only really going into detail on the scikit-build parts.

To install the package run in the project directory

```bash
pip install .
```

To run the Python tests, first install the package, then in the project directory run

```bash
pytest
```

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

A few surprises:
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).
2. The module will require numpy to work.
3. Between failed builds, it is best to clear out the `_skbuild` folder.
17 changes: 17 additions & 0 deletions projects/pi-fortran/pi/_pi.f
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
subroutine estimate_pi(pi_estimated, num_trials)
C Estimate pi using Monte Carlo
integer num_trials, num_in_circle
real*8 pi_estimated, x, y
Cf2py intent(in) num_trials
Cf2py intent(out) pi_estimated

num_in_circle = 0d0
do i = 1, num_trials
call random_number(x)
call random_number(y)
if (x**2 + y**2 < 1.0d0) then
num_in_circle = num_in_circle + 1
endif
end do
pi_estimated = (4d0 * num_in_circle) / num_trials
end subroutine estimate_pi
10 changes: 10 additions & 0 deletions projects/pi-fortran/pi/pi.pyf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
! -*- f90 -*-
! Note: the context of this file is case sensitive.
pymodule pi
interface
subroutine estimate_pi(pi_estimated,num_trials) ! in pi/_pi.f
real*8 intent(out) :: pi_estimated
integer intent(in) :: num_trials
end subroutine estimate_pi
end interface
end pymodule pi
16 changes: 16 additions & 0 deletions projects/pi-fortran/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[build-system]
requires = [
"scikit-build-core",
"numpy>=1.21",
]
build-backend = "scikit_build_core.build"

[project]
name = "pi-fortran"
version = "1.0.1"
requires-python = ">=3.7"
dependencies = ["numpy>=1.21"]

[tool.scikit-build]
ninja.minimum-version = "1.10"
cmake.minimum-version = "3.17.2"
9 changes: 9 additions & 0 deletions projects/pi-fortran/tests/test_pi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import math

import pytest
from pi import pi


def test_estimate_pi():
pi_est = pi.estimate_pi(1e8)
assert pi_est == pytest.approx(math.pi, rel=0.001)