Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

f2py: Specifying real precision in fortran when interfacing with python?

I am playing around with f2py. I'm a bit confused about numpy intrinsic types vs. fortran 90 types. It seems like I can only use single precision reals in fortran 90, when interacting with python. Let me illustrate with an example:

Say I have this fortran 90 module, test.f90, to be compiled with f2py and imported in python:

module test
implicit none

integer, parameter :: sp = selected_real_kind(6,37) ! single precision
integer, parameter :: dp = selected_real_kind(15,307) ! double precision

real(sp) :: r_sp = 1.0
real(dp) :: r_dp = 1.0_dp
end module

and I compile like this:

f2py -c -m test test.f90

Then, in python:

>>> import test
>>> test.test.r_sp
array(1.0, dtype=float32)
>>> test.test.r_dp
array(1.0)

IOW, it seems like f2py doesn't accept double precision. This becomes even more problematic when passing input to a fortran 90 subroutine from python. Say I extend my module to:

module test

implicit none
integer, parameter :: sp = selected_real_kind(6,37) ! single precision
integer, parameter :: dp = selected_real_kind(15,307) ! double precision

real(sp) :: r_sp = 1.0
real(dp) :: r_dp = 1.0_dp

contains 

subroutine input_sp(val)
  real(sp), intent(in) :: val
  real(sp) :: x
  x = val
  write(*,*) x
end subroutine

subroutine input_dp(val)
  real(dp), intent(in) :: val
  real(dp) :: x
  x = val
  write(*,*) x
end subroutine
end module

f2py -c -m test test.f90

python

>>> import test
>>> test.test.input_sp(array(1.0,dtype=float32))
1.0000000    
>>> test.test.input_sp(array(1.0,dtype=float64))
1.0000000    
>>> test.test.input_dp(array(1.0,dtype=float32))
-1.15948430791165406E+155
>>> test.test.input_dp(array(1.0,dtype=float64))

-1.15948430791165406E+155

So, it seems like any input variable to be sent from python must be declared single precision. Is this a known issue with f2py?

Also, as a follow up question: Converting from sp to dp works, in the following sense:

subroutine input_sp_to_dp(val)
  real(sp), intent(in) :: val(2)
  real(dp) :: x(2)
  x = val
  write(*,*) x
end subroutine

But I wonder if this is compiler specific at all? Can I expect the above subroutine to do the right thing with any compiler on any architecture? When testing, I used gfortran fro all the above examples.

like image 469
arne Avatar asked Sep 21 '12 02:09

arne


1 Answers

In your first example, I don't know why you say it seems like f2py doesn't accept double precision, when test.test.r_dp is double precision. A numpy array that shows a value with a decimal point and no explicit dtype is a double precision array.

The second example shows a limitation in F2PY's handling of type definitions with kind=<kind>. See the FAQ: https://numpy.org/doc/stable/f2py/advanced.html#dealing-with-kind-specifiers

To see what is happening, run f2py test.f90 -m test. I get this:

Reading fortran codes...
    Reading file 'test.f90' (format:free)
Post-processing...
    Block: test
            Block: test
                Block: input_sp
                Block: input_dp
Post-processing (stage 2)...
    Block: test
        Block: unknown_interface
            Block: test
                Block: input_sp
                Block: input_dp
Building modules...
    Building module "test"...
        Constructing F90 module support for "test"...
          Variables: r_dp sp r_sp dp
            Constructing wrapper function "test.input_sp"...
getctype: "real(kind=sp)" is mapped to C "float" (to override define dict(real = dict(sp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
getctype: "real(kind=sp)" is mapped to C "float" (to override define dict(real = dict(sp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
getctype: "real(kind=sp)" is mapped to C "float" (to override define dict(real = dict(sp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
              input_sp(val)
            Constructing wrapper function "test.input_dp"...
getctype: "real(kind=dp)" is mapped to C "float" (to override define dict(real = dict(dp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
getctype: "real(kind=dp)" is mapped to C "float" (to override define dict(real = dict(dp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
getctype: "real(kind=dp)" is mapped to C "float" (to override define dict(real = dict(dp="<C typespec>")) in /Users/warren/tmp/.f2py_f2cmap file).
              input_dp(val)
    Wrote C/API module "test" to file "./testmodule.c"
    Fortran 90 wrappers are saved to "./test-f2pywrappers2.f90"

Note that it is mapping both "real(kind=sp)" and "real(kind=dp)" to C "float", which is single precision.

There are several ways to fix this.

Method 1

Change the type declarations to "real(kind=4)" and "real(kind=8)" (or "real4" and "real8"), respectively.

Of course, this defeats the purpose of using selected_real_kind, and for some compilers, 4 and 8 are not the correct KIND values for single and double precision. In this case, with gfortran, sp is 4 and dp is 8, so it works.

Method 2

Tell f2py how to handle those declarations. This is explained in the f2py FAQ, and it is the approach suggested in the "getctype: ..." messages in the output of f2py shown above.

In this case, you would create a file called .f2py_f2cmap (in the directory where you are running f2py) that contains the line

dict(real=dict(sp='float', dp='double'))

Then f2py will do the right thing with those real(sp) and real(dp) declarations.

Method 3

It also works to rearrange your code a bit:

module types

implicit none
integer, parameter :: sp = selected_real_kind(6,37) ! single precision
integer, parameter :: dp = selected_real_kind(15,307) ! double precision

real(sp) :: r_sp = 1.0
real(dp) :: r_dp = 1.0_dp

end module


module input

contains 

subroutine input_sp(val)
  use types
  real(sp), intent(in) :: val
  real(sp) :: x
  x = val
  write(*,*) x
end subroutine

subroutine input_dp(val)
  use types
  real(dp), intent(in) :: val
  real(dp) :: x
  x = val
  write(*,*) dp, val, x
end subroutine

end module

See Subroutine argument not passed correctly from Python to Fortran for a similar suggestion.

like image 92
Warren Weckesser Avatar answered Sep 22 '22 20:09

Warren Weckesser