Paul Wintz
Paul Wintz

Reputation: 2743

Why does a Fortran function compiled with f2py return zero when I change the name of the result variable?

When I call the following function, it returns 1 as expected:

integer function my_func() result(myresult)
    myresult = 1
end function my_func

But when I modify the name of the return value to start with the letter "r" then the function returns 0.

integer function my_func() result(rresult)
    rresult = 1
end function my_func

What is causing this? My first thought was that it related to implicit typing, but the function is within a module that specifies implicit none.

Here is the complete module

module my_mod
implicit none

contains

integer function my_func() result(myresult)
    myresult = 1
end function my_func

end module my_mod

I am using Fortran 90 and compiling with gfortran.

EDIT

Here is a complete program to demonstrate the problem

Makefile:

.PHONY: pytest clean

CYTHON_LIB = fortran_mods.cpython-37m-x86_64-linux-gnu.so
FFLAGS += -fdefault-real-8

pytest: $(CYTHON_LIB)
        ./tests.py

$(CYTHON_LIB): my_mod.F90
        f2py -c -m fortran_mods my_mod.F90 --f90flags="$(FFLAGS)"

clean:
        rm *.so

my_mod.F90:

module my_mod
implicit none

contains

!********************************************************

integer function my_func_without_r() result(myresult)
    myresult = 1
end function

integer function my_func_with_r() result(rresult)
    rresult = 1
end function

end module my_mod

tests.py

#!/usr/bin/env python3
import fortran_mods
from fortran_mods import *

print("with r:", my_mod.my_func_with_r())
print("without r:", my_mod.my_func_without_r())

The output when make pytest is run and FFLAGS += -fdefault-real-8 is included in the Makefile is

with r: 0.0
without r: 1

and otherwise is

with r: 1.0
without r: 1

Upvotes: 3

Views: 787

Answers (1)

jbdv
jbdv

Reputation: 1263

The problem here is definitely with how F2PY wraps the Fortran functions, not the Fortran code itself.

To gain a bit more insight into how F2PY wraps a function (especially if things don't work out as expected), it always helps to split the process into parts (see The smart way). So first create a signature file allowing you to see how F2PY interpreted your code. For your particular example, run:

f2py -m fortran_mods -h my_mod.pyf my_mod.F90

This will produce a signature file my_mod.pyf looking something like this:

python module fortran_mods ! in 
    interface  ! in :fortran_mods
        module my_mod ! in :fortran_mods:my_mod.F90
            function my_func_without_r() result (myresult) ! in :fortran_mods:my_mod.F90:my_mod
                integer :: myresult
            end function my_func_without_r
            function my_func_with_r() result (rresult) ! in :fortran_mods:my_mod.F90:my_mod
                real :: rresult
            end function my_func_with_r
        end module my_mod
    end interface 
end python module fortran_mods

Clearly F2PY has misidentified my_func_with_r's result variable rresult as real. You could simply replace the real :: rresult with the intended integer :: rresult in my_mod.pyf, take the next/second step of F2PY wrapping, and compile using the corrected signature file:

f2py -c my_mod.pyf my_mod.F90

Your python script should now give the expected output.

This approach of modifying the signature file may not be desired if you have many functions to wrap. What may be causing difficulty for F2PY is that your function definitions use result variables, without type definitions for them appearing in the body of the function (i.e. a F2PY problem, not a Fortran problem). If you change your function definitions to e.g.:

function my_func_with_r() result(rresult)
    integer :: rresult
    rresult = 1
end function

or

integer function my_func_with_r()
    my_func_with_r = 1
end function

you can do the F2PY wrapping in one step as you did originally, and should still get the correct output.

Finally, I'll add another vote to the concerns raised in the comments: using -fdefault-real-8 when wrapping functions with F2PY is asking for trouble. To make a Fortran procedure callable from Python, F2PY creates:

a Python C/API extension module (called as wrapper module) that implements a Python extension function (written in C, called as wrapper function) which in turn calls the given Fortran procedure.

This whole process is based on how F2PY interprets data types from your Fortran source code - if source code is compiled with compiler flags that change the declared data types, things are bound to end up broken (directly similar to the real/integer mismatch your original question is about). The data types for your variables and functions should therefore be explicitly set in the Fortran code itself, see here and here about Fortran's kind parameter in general, and here related to F2PY in particular.

Upvotes: 6

Related Questions