Palina
Palina

Reputation: 49

Calling Fortran shared library in Python with ctypes

I'm trying to use a Fortran module in Python. I have several arrays in Python -- both numeric and string. I defined the arrays in Fortran and Python, but I think that I used the wrong type for the numeric arrays. I get an error that converting the first argument (a numeric array) failed. What types should I use?

Error:

Traceback (most recent call last):
  File "py_try.py", line 66, in <module>
    writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat,xlon,date_char,num_met,num_lev,kx,dd_strvar,station_strvar,synop,string4, bogus, iseq_num, iunit)
ctypes.ArgumentError: argument 1: <type 'exceptions.TypeError'>: Don't know how to convert parameter 1

Fortran:

subroutine write_obs(p, z, t, td, spd, wdir, xlon, kx, slp, ter, xlat,  date_char, dd, station, num_met, num_lev, synop, string4, bogus, iseq_num, iunit) bind(C, name='write_obs')
use iso_c_binding
implicit none
integer(c_int) k, kx, num_met, num_lev, iseq_num, iunit, ierr
real(kind=c_float) p(kx), slp(kx), z(kx), t(kx), td(kx)
real(kind=c_float) spd(kx), ter(kx), xlat(kx), xlon(kx), wdir(kx)
character(len=1,kind=c_char) date_char
character(len=1,kind=c_char) synop, string4
character(len=1,kind=c_char), intent(in) :: dd(kx)
character(len=1,kind=c_char), intent(in) :: station(kx)
logical(c_bool) bogus
character(len=84,kind=c_char) rpt_format
character(len=22,kind=c_char) meas_format
character(len=14,kind=c_char) end_format

rpt_format = ' ( 2f20.5 , 2a40 , '&
&' 2a40 , 1f20.5 , 5i10 , 3L10 , '&
&' 2i10 , a20 , 13( f13.5 , i7 ) )'

meas_format = ' ( 10( f13.5 , i7 ) ) '
end_format = ' ( 3 ( i7 ) )'

do 100 k=1 , kx

write ( UNIT = iunit , iostat = ierr , FMT = rpt_format ) &
& xlat(k), xlon(k), dd(k), station(k), &
& synop , string4, ter(k), num_met, 0, 0, iseq_num, 0, &
& .true., bogus, .false., &
& -888888, -888888, date_char, slp(k), 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0

write ( UNIT = iunit , iostat = ierr , FMT = meas_format ) &
& p(k), 0, z(k), 0, t(k), 0, td(k), 0, &
& spd(k), 0, wdir(k), 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0


write ( UNIT = iunit , iostat = ierr, FMT = meas_format ) &
& -777777., 0, -777777., 0, float(num_lev), 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0

write ( UNIT = iunit, iostat = ierr, FMT = end_format ) &
& num_lev, 0, 0

if (ierr .NE. 0 ) then
    print '(A,I5,A)','Troubles writing a sounding.Error #', ierr
    stop 'writing_error'
endif
100 continue
return
end subroutine write_obs

Python:

import numpy as np
import ctypes
from ctypes import c_int, c_char
writelittler=ctypes.CDLL("/writelittler.so")
p = np.asarray([ 982.6, 999.7 ], dtype="float64")
p_strvar=ctypes.c_void_p(p.ctypes.data)

# [other numerical arrays omitted]

bogus = 0
kx=2
iseq_num = 0
iunit=2
date_char = '      20160128060000'
dt=np.dtype('a40')
dd = np.asarray([ '1111111111111111111111111111111111111111', '6666666666666666666666666666666666666666', '9999999999999999999999999999999999999999'  ], dtype=dt)
station = np.asarray([ 'V111111111111111111111111111111111111111','M111111111111111111111111111111111111111' ], dtype=dt)

dd_strvar=ctypes.c_void_p(dd.ctypes.data)
station_strvar=ctypes.c_void_p(station.ctypes.data)
num_met=6
num_lev=1
synop='FM-12 SYNOP                             '
string4='                                        '

writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat, xlon,date_char,num_met,num_lev,kx,dd_strvar, station_strvar,synop,string4, bogus, iseq_num, iunit)

Upvotes: 2

Views: 722

Answers (1)

Alexander Vogt
Alexander Vogt

Reputation: 18098

You did not specify the argument types of the Fortran subroutine in Python. Furthermore, you do not declare/initialize the variables you pass to Fortran either. Therefore, Python does not know what to do with it. That is what it is telling you in the error message.

As Fortran uses call by reference by default, the argtypes are:

writelittler.write_obs.argtypes = [ POINTER(c_float), # p
                                    POINTER(c_float), # z
                                    POINTER(c_float), # t
                                    POINTER(c_float), # td
                                    POINTER(c_float), # spd
                                    POINTER(c_float), # wdir
                                    POINTER(c_int),   # kx
                                    POINTER(c_float), # slp
                                    POINTER(c_float), # ter
                                    POINTER(c_float), # xlat
                                    POINTER(c_char),  # date_char
                                    POINTER(c_char),  # dd
                                    POINTER(c_char),  # station
                                    POINTER(c_int),   # num_met
                                    POINTER(c_int),   # num_lev
                                    POINTER(c_char),  # synop
                                    POINTER(c_int),   # string4
                                    POINTER(c_bool),  # bogus
                                    POINTER(c_bool),  # string4
                                    POINTER(c_int),   # iseq_num
                                    POINTER(c_int)    # iunit
                                  ]

[This can probably be simplified...]

Additionally, Fortran subroutines do not have a return value:

writelittler.write_obs.restype = None

Furthermore, you need to adjust the function call itself (in Python):

writelittler.write_obs( p.ctypes.data_as(POINTER(c_float)), # Numpy data type
                        ctypes.byref(z), # ctypes data type
                        ctypes.byref(t),
                        ctypes.byref(td),
                        ctypes.byref(spd),
                        ctypes.byref(wdir),
                        ctypes.byref(slp),
                        ctypes.byref(ter),
                        ctypes.byref(xlat), 
                        ctypes.byref(xlon),
                        ctypes.byref(date_char),
                        ctypes.byref(num_met),
                        ctypes.byref(num_lev),
                        ctypes.byref(kx),
                        ctypes.byref(dd_strvar), 
                        ctypes.byref(station_strvar),
                        ctypes.byref(synop),
                        ctypes.byref(string4), 
                        ctypes.byref(bogus), 
                        ctypes.byref(iseq_num), 
                        ctypes.byref(iunit) )

[This is not validated...]

Now that Python knows what to expect, you still need to declare the missing variables. As your code is know, it will result in the error:

Traceback (most recent call last):
  File "test.py", line 51, in <module>
    writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat, 
xlon,date_char,num_met,num_lev,kx,dd_strvar,
station_strvar,synop,string4, bogus, iseq_num, iunit)
NameError: name 'z' is not defined

Upvotes: 2

Related Questions