How should Fortran variables be typed if I intend to pass them a boolean, NumPy array with f2py
? I have tried both integer*1
and logical*1
, but these both indicate that the array is copied.
For example, if I compile the file, foo.f95
, containing:
subroutine foo(x, n)
logical*1 x(n)
!f2py intent(in) x
!f2py intent(hide), depend(x) :: n=shape(x,0)
...
end subroutine
with f2py -c -m foo foo.f90 -DF2PY_REPORT_ON_ARRAY_COPY=1
and run something like:
import numpy as np
import foo
x = np.random.randn(100) < 0
foo.foo(x)
it prints
copied an array: size=100, elsize=1
I get the same result if I change logical*1
to integer*1
. What is the proper type for the boolean array in the Fortran file, so that the array is not copied?
Note that this is not a problem of memory contiguity since the arrays are 1D -- foo.foo(np.asfortranarray(x))
prints the same copy message.
From some experiments(*), it seems that Python/f2py treats np.int8
as compatible with logical*1
, while np.bool
or np.bool8
not for some reason. After inserting print *, "(fort) x = ", x
into foo.f90, we get:
>>> foo.foo( np.array( [ True, False, False ], dtype=np.bool ) )
copied an array: size=3, elsize=1
(fort) x = T F F
>>> foo.foo( np.array( [ False, True, False ], dtype=np.bool8 ) )
copied an array: size=3, elsize=1
(fort) x = F T F
>>> foo.foo( np.array( [ False, False, True ], dtype=np.int8 ) ) # no copy
(fort) x = F F T
Because True
and False
are simply mapped to 1 and 0, using an int8
array on the Python side may be convenient.
(*) Some experiments
Here, I changed the f2py intent comment to inout
to see whether we can modify the array from the Fortran side.
foo.f90:
subroutine foo(x, n)
use iso_c_binding
implicit none
integer n
logical*1 x( n )
! logical x( n )
! logical(c_bool) x( n )
!f2py intent(inout) x
!f2py intent(hide), depend(x) :: n=shape(x,0)
print *, "(fort) x = ", x
print *, "(fort) sizeof(x(1)) = ", sizeof(x(1))
print *, "(fort) resetting x(:) to true"
x(:) = .true.
end subroutine
test.py:
import numpy as np
import foo
for T in [ np.bool, np.bool8,
np.int, np.int8, np.int32, np.int64,
np.uint, np.uint8, np.uint32, np.uint64,
np.dtype('b'), np.dtype('int8'), np.dtype('int32') ]:
print( "-------------------------" )
print( "dtype =", T )
x = np.array( [ True, False, True ], dtype=T )
print( "input x =", x )
try:
foo.foo( x )
print( "output x =", x )
except:
print( "failed" )
Results with logical*1
:
-------------------------
dtype = <class 'bool'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'numpy.bool_'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'int'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int8'>
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = <class 'numpy.int32'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint8'>
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = <class 'numpy.uint32'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = int8
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = int8
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = int32
input x = [1 0 1]
failed
Results with logical
(default kind):
-------------------------
dtype = <class 'bool'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'numpy.bool_'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'int'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int8'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int32'>
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 4
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = <class 'numpy.int64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint8'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint32'>
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 4
(fort) resetting x(:) to true
output x = [1 1 1]
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = int8
input x = [1 0 1]
failed
-------------------------
dtype = int8
input x = [1 0 1]
failed
-------------------------
dtype = int32
input x = [1 0 1]
(fort) x = T F T
(fort) sizeof(x(1)) = 4
(fort) resetting x(:) to true
output x = [1 1 1]
Results with logical(c_bool)
(via iso_c_binding):
-------------------------
dtype = <class 'bool'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'numpy.bool_'>
input x = [ True False True]
failed
-------------------------
dtype = <class 'int'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int8'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.int32'>
input x = [1 0 1]
(fort) x = T F F
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [65793 0 1]
-------------------------
dtype = <class 'numpy.int64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint8'>
input x = [1 0 1]
failed
-------------------------
dtype = <class 'numpy.uint32'>
input x = [1 0 1]
(fort) x = T F F
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [65793 0 1]
-------------------------
dtype = <class 'numpy.uint64'>
input x = [1 0 1]
failed
-------------------------
dtype = int8
input x = [1 0 1]
failed
-------------------------
dtype = int8
input x = [1 0 1]
failed
-------------------------
dtype = int32
input x = [1 0 1]
(fort) x = T F F
(fort) sizeof(x(1)) = 1
(fort) resetting x(:) to true
output x = [65793 0 1]
For some reason, this last logical(c_bool)
does not work with the above usage... (f2py seems to consider logical(c_bool)
as 4 bytes, while gfortran treats it as 1 byte, so something is inconsistent...)