Hi there.
I am trying to learn how to interface with Fortran from Python using ‘iso_c_binding’. To test interoperability, I wrote a simple Fortran module to calculate the sum and mean of a 1D array, which is given below:
module module_calculate_stats
use iso_fortran_env
use iso_c_binding
implicit none
contains
subroutine calculate_sum(array, array_sum) bind(C)
real(kind=c_float), intent(in), dimension(:)::array
real(kind=c_float), intent(out)::array_sum
array_sum=sum(array)
end subroutine calculate_sum
subroutine calculate_mean(array, array_mean) bind(C)
real(kind=c_float), intent(in), dimension(:)::array
real(kind=c_float), intent(out)::array_mean
array_mean=sum(array)/size(array)
end subroutine calculate_mean
end module module_calculate_stats
I have successfully called the module subroutines from another Fortran program. The module was compiled into a shared library using the following command:
Now, onto the Python program that calls the Fortran module:
import ctypes as ct
import numpy as np
if __name__=='__main__':
fort_lib=ct.CDLL('./module_calculate_stats.so')
fort_lib.calculate_sum.argtypes=[
ct.POINTER(ct.c_float),]
fort_lib.calculate_sum.restype=ct.c_float
total=ct.c_float(-1)
array=np.array(
[1, 2, 3, 4, 5, 6], dtype=ct.c_float, order='F')
array_ptr=array.ctypes.data_as(ct.POINTER(ct.c_float))
fort_lib.calculate_sum(array_ptr, ct.byref(total))
print(total)
When I run the Python program, I either get a segmentation fault (core dumped) or Internal Error: Invalid type in descriptor.
What am I doing wrong and how do I fix it?
module module_calculate_stats
use iso_fortran_env
use iso_c_binding
implicit none
contains
subroutine calculate_sum(array, array_sum, n) bind(C)
integer(c_int), value :: n
real(kind=c_float), intent(in), dimension(n)::array
real(kind=c_float), intent(out)::array_sum
array_sum=sum(array)
end subroutine calculate_sum
subroutine calculate_mean(array, array_mean, n) bind(C)
integer(c_int), value :: n
real(kind=c_float), intent(in), dimension(n)::array
real(kind=c_float), intent(out)::array_mean
array_mean=sum(array)/n
end subroutine calculate_mean
end module module_calculate_stats
And the Python part:
import ctypes as ct
import numpy as np
from numpy.ctypeslib import ndpointer
fort_lib=ct.CDLL('./module_calculate_stats.so')
fort_lib.calculate_sum.argtypes=[
ndpointer(dtype=ct.c_float),
ct.POINTER(ct.c_float),
ct.c_int]
fort_lib.calculate_sum.restype=None
def my_mean(arr):
arr = np.ascontiguousarray(arr, dtype=ct.c_float)
mean = ct.c_float(0.0)
n = arr.size
fort_lib.calculate_sum(arr, mean, n)
return mean.value
print(my_mean([1,2,3,4,5]))
The actual call to the Fortran library should always be hided inside a python function, as you have to check that all the arguments are at least contiguous etc.
Thank you for your help. It did help me in fixing the issue.
Is there any reason you used the value argument for the n variable, couldn’t it have been intent(in) like the array variable?
Another question I have with your answer is, do I have to specify the array size to the Fortran module? As I have seen in many examples, you can just use : for dynamically allocated arrays.
And in my Fortran program that calls the subroutines, I used a dynamic array and did not pass the size to the subroutine.
For consistency, n should preferably have intent(in).
The attribute value is a matter of choice, but requires consistent handling because fortran passes arguments by reference and C by value:
if you add value, you can pass the argument “normally” in python.
if you do not add value, then you need to pass by reference in python.
integer(c_int) function intproduct(a, b) result(res) bind(c)
!! Product of two integers directly in C types
integer(c_int), intent(in) :: a, b
res = a*b
end function
integer(c_int) function intproduct_byvalue(a, b) result(res) bind(c)
!! Product of two integers directly in C types
integer(c_int), value, intent(in) :: a, b
res = a*b
end function
Yes it could. No real reason, just a stylistic preference.
Strictly speaking when you pass a scalar variable with the value attribute its value may be passed directly on a register of the CPU, while if you use an intent(in) you pass the address of the variable on a register of the CPU, so you have to dereference that address. But the difference is negligible, so it is just a stylistic preference.
As I’m using procedure that should appear as a C procedure I stick to the C way of writing and way of passing arguments.
If I were calling Fortran from Fortran I would have used a variable with intent(in) as this is the way one writes in Fortran.
Of course there is a difference in Fortran between a variable with the value attribute and one with the intent(in) attribute.