Experience:
fortran for about 3 months
python - intermediate : never used the ctypes module in python before this
I was looking for a way to use the fortran code for my doctoral work in python - subsequently using the computation for visualizations on-the-fly for visualizations using matplotlib.
THIS POST helped (this tells that fortran code can be used/invoked in python using the ctypes module - and given that the fortran functions have alternate names bound to them - this much makes sense to me logically although i do not know how this works in detail. But we DO choose our battles wisely!).
Then this SO post deals with calling fortran functions from python as well.
The next logical step was to look up the documentation for the python module ctypes. This talks about how the shared library can be accessed using python at an API level.
I had all the pieces to make a minimal working example, which another answer has already done. But i wanted to look at the output mechanism and mathematical operations involving real floats. Here is the test case i made.
function prnt(s)
character(80):: s
logical :: prnt
print*, s
prnt = .true.
end function prnt
function sin_2(r)
real:: r,sin_2
sin_2 = sin(r)**2
end function sin_2
$gfortran -shared -g -o test.so test.f90
EDIT: for some reason my work computer needs the -fPIC option to compile
To make sure my two functions prnt
and sin_2
are in there somewhere, i checked with nm
:
$ nm test.so | tail -3
0000067f T prnt_
0000065c T sin_2_
U sinf@@GLIBC_2.0
So far so good. My functions prnt
and sin_2
have been mapped onto prnt_
and sin_2_
inside the library.
this is where all of this gets a bit soggy. Using the table in python-ctypes documentation, I typed in the following -
>>> from ctypes import byref, cdll, c_float,c_char_p
>>> t = cdll.LoadLibrary('./test.so')
>>> c = c_char_p("Mary had a little lamb")
>>> t.prnt_('Mary had a little lamb')
Mary had a little lambe
1
>>> t.prnt_("Mary had a little lamb")
Mary had a little lambe
1
>>> t.prnt_(c)
Mary had a little lambe[� .prnt_(c)
1
I suppose that the 1 printed at the end of each output is python's way of letting me know that the boolean output to t.prnt_
is .true.
.
I am a bit worried about how the situation gets worse with t.prnt_
when i switch to the proper datatype for the string. The literals print okay, only with a e
at the end. Is that the EOL character?
Then there is the t.sin_2_
function. I have decided to use it to compute sin(4.56)**2. here is how that went -
>>> f = c_float(4.56)
>>> t.sin_2_(4.56)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <type 'exceptions.TypeError'>: Don't know how to convert parameter 1
>>> t.sin_2_(f)
Segmentation fault (core dumped)
Where did i go wrong here? I tried to explain how I approached the problem so that it would be apparent if I made an obvious gaffe somewhere.
The multitude of links to other SO posts is to help other people who ask the same question as I do now.
I use f2py pretty frequently, its quite simple. If your code complies to Fortran 90 declarations (e.g. double precision, intent(in) :: myvar
), f2py will wrap your code automatically to C and is compiled into a shared object that is directly callable through Python. You can import your f2py created module as you would any other python module. The C wrapper is transparent and handles the type interfacing between Python and Fortran, you don't have to mess with c_types at all. I would definitely recommend this route.
In Fortran, arguments are passed by reference. Fortran character arrays aren't null terminated; the length is passed by value as an implicit long int
argument. Also, Python's float
type is a double
, so you may want to use Fortran real(8)
for consistency.
test.f90:
function prnt(s) ! byref(s), byval(length) [long int, implicit]
character(len=*):: s ! variable length input
logical :: prnt
write(*, "(A)") s ! formatted, to remove initial space
prnt = .true.
end function prnt
function sin_2(r) ! byref(r)
real:: r, sin_2 ! float; use real(8) for double
sin_2 = sin(r)**2
end function sin_2
Remember to set ctypes argtypes
for functions, and restype
where appropriate. In this case sin_2
takes a float pointer and returns a float.
ctypes example:
>>> from ctypes import *
>>> test = CDLL('./test.so')
>>> test.prnt_.argtypes = [c_char_p, c_long]
>>> test.sin_2_.argtypes = [POINTER(c_float)]
>>> test.sin_2_.restype = c_float
>>> s = 'Mary had a little lamb'
>>> test.prnt_(s, len(s))
Mary had a little lamb
1
>>> x = c_float(4.56)
>>> test.sin_2_(byref(x))
0.9769567847251892
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With