Skip to content

f2py Programming Style Requirements

f2py is a tool developed by Numpy, it is used to provide an interface between Python and Fortran. PROCESS uses f2py for its Fortran interface.

f2py can wrap most modern Fortran, that being said it is opinionated on the structure of modules, subroutines and function.

The following rules will be enforced on all Fortran code, whether currently wrapped or not, and any code not adhering to such rules will not pass code review.

Examples of rules may be given in the context of an example to aide understanding.

Derived types

f2py does not allow the use of derived types as subroutine/function arguments. For this reason, derived types should be avoided or necessary, and instead standard-type arguments passed as parameters.

Bad:

module foo
    implicit none

    type :: duck 
        integer :: id
        character(len=10) :: first_name
    end type duck

    subroutine say_hello(duck_instance)
        type(duck), intent(in) :: duck_instance

        write(*,*) 'Duck ', duck_instance%first_name, ' (with ID ', duck_instance%id, ') says quack!'
    end subroutine say_hello
end module foo

Good:

module foo
    implicit none

    subroutine say_hello(duck_first_name, duck_id)
        character(len=10), intent(in) :: duck_first_name
        integer, intent(in) :: duck_id

        write(*,*) 'Duck ', duck_instance%first_name, ' (with ID ', duck_instance%id, ') says hello!'
    end subroutine say_hello
end module foo

Parameters as dimensions of an array

f2py does not allow dimensions of an array to be parameters without issuing an f2py directive.

Bad:

module example
    ! module variables
    integer, parameter :: number = 50

    subroutine example_routine()
        integer, dimension(number, number) :: matrix

        ...
    end subroutine example_routine

end module example

Good:

module example
    ! module variables
    integer, parameter :: number = 50

    subroutine example_routine()
        !f2py integer, intent(aux) :: number
        integer, dimension(number, number) :: matrix

        ...
    end subroutine example_routine

end module example

with !f2py integer, intent(aux) :: number being the f2py directive.

Function type decleration

Fortran function method signatures can be defined as follows:

Bad:

real function example(p1, p2) result(p3)
    ! arguments
    real, intent(in) :: p1, p2

which states that p1 and p2 are real parameters of the example function and p3 is the real result of example. This declaration is valid in Fortran, but will be rejected by f2py; f2py cannot interpret the type of p3. Instead the following patterns should be used:

Good:

function example(p1, p2) result(p3)
    ! arguments
    real, intent(in) :: p1, p2

    ! return
    real :: p3

Interfaces

Interfaces declaring functions are used throughout PROCESS to allow a function to be an argument of another routine (e.g. for integration). f2py requires that function declaration within an interface follows the above rules:

Bad:

interface
    function fun(rho)
        real(dp), intent(in) :: rho
        real(dp) :: fint
    end function fun
end interface

Where fint is implicitly intent(out). f2py requires an explicit results is provided:

Good:

interface
    function fun(rho) result(fint)
        real(dp), intent(in) :: rho
        real(dp) :: fint
    end function fun
end interface

Also note that when using this interface as an argument, the standard Fortran way would be real(dp), external :: fun. However, when a results is given, this is the same as providing the real(dp) decleration (see above); only external :: fun is required.

Private/public attributes

f2py cannot see private routines or variables. This means the use of private must be carefully used otherwise f2py will raise errors thinking it cannot see variables. This does, however, mean that the private keyword can be used to 'hide' error-causing variables from f2py - such as derived types that are not used as routine parameters.

Developers should also be carefully not to accidentally implicitly-private a module by using the public keyword on a variable or routine; every other variable/module variable will become implicitly private.

Generally, we advise against the use of private and public keywords unless in very select circumstances.

Intrinsic double precision

The Fortran-recommended way to handle real precision is to use real64 from the intrinsic iso_fortran_env:

Bad:

! source/fortran/foo.f90
module foo
    use, intrinsic :: iso_fortran_env, only: dp=>real64

    implicit none

    contains 

    subroutine bar(x, y)
        real(dp), intent(in) :: x
        real(dp), intent(out) :: y

        y = x*2
    end subroutine bar
end module foo

When f2py wraps this module, it will wrap each subroutine seperately and the dp pointer will not be available within this wrapper. Because of this, an error will be raised declaring dp to be undefined.

The solution is to preprocess wrapped files first, and replace dp with 8. We use an ifndef preprocessor directive to indicate not to include the use ... line in the wrapping process.

Good:

! source/fortran/foo.f90
module foo
#ifndef dp
    use, intrinsic :: iso_fortran_env, only: dp=>real64
#endif

    implicit none

    contains 

    subroutine bar(x, y)
        real(dp), intent(in) :: x
        real(dp), intent(out) :: y

        y = x*2
    end subroutine bar
end module foo

This then creates an intermediate source file which does not have undefined pointers:

! build/foo.f90
! intermediate source file
module foo
    implicit none

    contains 

    subroutine bar(x, y)
        real(8), intent(in) :: x
        real(8), intent(out) :: y

        y = x*2
    end subroutine bar
end module foo

Assumed-shape arrays

f2py needs to have a definite size for each dimension of an array to be passed back through the Python-Fortran interface, ie intent(out). This means assumed-shape and assumed-size arrays will not work when declared as a return value.

Bad:

module foo
    use, intrinsic :: iso_fortran_env, only: dp=>real64

    implicit none

    contains 

    subroutine bar(x, y)
        real(dp), dimension(:,:), intent(in) :: x
        real(dp), dimension(:,:), intent(out) :: y

        ! routine logic
    end subroutine bar
end module foo

For example, if both arrays have dimensions of sizes n and i, respectively.

Good:

module foo
    use, intrinsic :: iso_fortran_env, only: dp=>real64

    implicit none

    contains 

    subroutine bar(x, y, n, i)
        integer, intent(in) :: n, i
        real(dp), dimension(:,:), intent(in) :: x
        real(dp), dimension(n,i), intent(out) :: y

        ! routine logic
    end subroutine bar
end module foo

Notice that the input array, x, does not need to be changed.

Strings

Length

Firstly, a string being set via the Python-Fortran interface must be of the exact length specified, f2py will reject anything under or over. For this reason, we provide a utility function process.utilities.f2py_string_patch.string_to_f2py_compatible, to be used as follows:

! foo.py

from process.utilities.f2py_string_patch import string_to_f2py_compatible


def bar(string: str) -> None:
    fortran.foomod.barvar = string_to_f2py_compatible(fortran.foomod.barvar, string)

where fortran.foomod.barvar will be set as the string string, padded with whitespace if too short or truncated if too long.

Remember to also use the fortran trim function to remove padding when using any strings inside of Fortran code.

Arrays

Character arrays in Fortran, declared like character(len=10), dimension(m), intent(out) :: foo, are not liked by f2py. Instead, the slightly outdated character*10, dimension(m), intent(out) :: foo syntax is needed. This error will manifest itself as the character array (foo) only being of size 1 regardless of the value of m.