python-fortran集成:f2py和ctypes的回调比较
python-fortran integration: callback comparison between f2py and ctypes
我在 https://numpy.org/devdocs/f2py/python-usage.html#call-back-arguments 中找到了一个有启发性的示例。
这里是 fortran 例程:
C FILE: CALLBACK.F
SUBROUTINE FOO(FUN,R)
EXTERNAL FUN
INTEGER I
REAL*8 R, FUN
Cf2py intent(out) r
R = 0D0
DO I=-5,5
R = R + FUN(I)
ENDDO
END
C END OF FILE CALLBACK.F
这可以用命令 f2py -c -m callback callback.f 编译
并使用 python 代码调用:
import callback
print(callback.foo.__doc__)
def f(i):
return i * i
print(callback.foo(f))
一切正常。现在,我想使用 ctypes 重复测试。
我可以使用以下命令轻松编译 Fortran 源代码:gfortran -shared callback.f -o callback.dll
我可以加载库:
import ctypes as ct
import numpy as np
# import the dll
fortlib = ct.CDLL('callback.dll')
问题:
- 我怎样才能像我一样调用来自ctypes的dll中的foo函数
用f2py编译的代码做了什么?
- 我如何连接两者
需要变量(指向函数的指针和指向实数的指针)?
提前致谢。詹马可
平台:Anaconda python 3.7.6,Mingw-64 Windows 10
良好的编程风格要求我们永远不要使用单字符变量名。 Fortran 子例程的现代 Fortran-2008 实现类似于以下内容:
module foo_mod
use iso_c_binding, only: RK => c_double, IK => c_int32_t
implicit none
abstract interface
function getFunVal_proc(inputInteger) result(funVal) bind(C)
import :: RK, IK
implicit none
integer(IK), intent(in), value :: inputInteger
real(RK) :: funVal
end function getFunVal_proc
end interface
contains
subroutine getFoo(getFunValFromC,outputReal) bind(C,name="getFoo")
!DEC$ ATTRIBUTES DLLEXPORT :: getFoo
use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
implicit none
type(c_funptr), intent(in), value :: getFunValFromC
procedure(getFunVal_proc), pointer :: getFunVal
real(RK), intent(out) :: outputReal
integer(IK) :: indx
! associate the input C procedure pointer to a Fortran procedure pointer
call c_f_procpointer(cptr=getFunValFromC, fptr=getFunVal)
outputReal = 0._RK
do indx = -5,5
write(*,"(*(g0,:,' '))") "value of indx from inside Fortran: ", indx
outputReal = outputReal + getFunVal(indx)
end do
write(*,"(*(g0,:,' '))") "value of outputReal from inside Fortran: ", outputReal
! nullify the Fortran pointer
nullify(getFunVal)
end subroutine getFoo
end module foo_mod
这看起来有点冗长,但比 F77 好得多。毕竟,我们生活在 21 世纪。然后你会通过 Intel ifort 编译这个 Fortran 代码,例如,
ifort /dll /threads /libs:static foo_mod.f90 /exe:foo.dll
然后,您将从生成的 DLL foo.dll
中调用 getFoo()
,如以下 Python 脚本,
import ctypes as ct
import numpy as np
# This is the Python callback function to be passed to Fortran
def getSquare(inputInteger):
print("value of indx received by getSquare() inside Python: ",inputInteger)
return np.double(inputInteger**2)
# define ctypes wrapper function, with the proper result and argument types
getFunVal_proc = ct.CFUNCTYPE( ct.c_double # callback (python) function result
, ct.c_int32 # callback (python) function input integer argument
)
getSquare_pntr = getFunVal_proc(getSquare)
libpath = "foo.dll"
try:
# open DLL
foolib = ct.CDLL(libpath)
except Exception as e:
import logging
logger = logging.Logger("catch_all")
logger.error(e, exc_info=True)
# define getFoo's interface from Fortran dll
foolib.getFoo.restype = None # return type of the Fortran subroutine/function
foolib.getFoo.argtypes = [ getFunVal_proc # procedure
, ct.POINTER(ct.c_double) # real64 return value
, ]
outputReal = ct.c_double(0.)
foolib.getFoo ( getSquare_pntr
, ct.byref(outputReal)
)
print("value of outputReal received in Python: ", np.double(outputReal))
运行 这个脚本会产生如下内容,
In [1]: run main.py
value of indx from inside Fortran: -5
value of indx received by getSquare() inside Python: -5
value of indx from inside Fortran: -4
value of indx received by getSquare() inside Python: -4
value of indx from inside Fortran: -3
value of indx received by getSquare() inside Python: -3
value of indx from inside Fortran: -2
value of indx received by getSquare() inside Python: -2
value of indx from inside Fortran: -1
value of indx received by getSquare() inside Python: -1
value of indx from inside Fortran: 0
value of indx received by getSquare() inside Python: 0
value of indx from inside Fortran: 1
value of indx received by getSquare() inside Python: 1
value of indx from inside Fortran: 2
value of indx received by getSquare() inside Python: 2
value of indx from inside Fortran: 3
value of indx received by getSquare() inside Python: 3
value of indx from inside Fortran: 4
value of indx received by getSquare() inside Python: 4
value of indx from inside Fortran: 5
value of indx received by getSquare() inside Python: 5
value of outputReal from inside Fortran: 110.0000000000000
value of outputReal received in Python: 110.0
与您的 F2PY 代码相比,上面的 Python 脚本可能再次显得相当冗长。但它比您的实施更专业、更现代、更符合标准,符合 Python 和 Fortran 标准。
脚注:Intel ifort 对 Windows、Linux 和 Mac 平台上的所有学生、教师和开源开发人员免费提供。这并不意味着 gfortran 不好。但在我看来,在 Windows OS 上使用 gcc 通常不比永无止境的噩梦好(我与英特尔没有任何关系,只是一个用户)。
我在 https://numpy.org/devdocs/f2py/python-usage.html#call-back-arguments 中找到了一个有启发性的示例。 这里是 fortran 例程:
C FILE: CALLBACK.F
SUBROUTINE FOO(FUN,R)
EXTERNAL FUN
INTEGER I
REAL*8 R, FUN
Cf2py intent(out) r
R = 0D0
DO I=-5,5
R = R + FUN(I)
ENDDO
END
C END OF FILE CALLBACK.F
这可以用命令 f2py -c -m callback callback.f 编译 并使用 python 代码调用:
import callback
print(callback.foo.__doc__)
def f(i):
return i * i
print(callback.foo(f))
一切正常。现在,我想使用 ctypes 重复测试。 我可以使用以下命令轻松编译 Fortran 源代码:gfortran -shared callback.f -o callback.dll 我可以加载库:
import ctypes as ct
import numpy as np
# import the dll
fortlib = ct.CDLL('callback.dll')
问题:
- 我怎样才能像我一样调用来自ctypes的dll中的foo函数 用f2py编译的代码做了什么?
- 我如何连接两者 需要变量(指向函数的指针和指向实数的指针)?
提前致谢。詹马可
平台:Anaconda python 3.7.6,Mingw-64 Windows 10
良好的编程风格要求我们永远不要使用单字符变量名。 Fortran 子例程的现代 Fortran-2008 实现类似于以下内容:
module foo_mod
use iso_c_binding, only: RK => c_double, IK => c_int32_t
implicit none
abstract interface
function getFunVal_proc(inputInteger) result(funVal) bind(C)
import :: RK, IK
implicit none
integer(IK), intent(in), value :: inputInteger
real(RK) :: funVal
end function getFunVal_proc
end interface
contains
subroutine getFoo(getFunValFromC,outputReal) bind(C,name="getFoo")
!DEC$ ATTRIBUTES DLLEXPORT :: getFoo
use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
implicit none
type(c_funptr), intent(in), value :: getFunValFromC
procedure(getFunVal_proc), pointer :: getFunVal
real(RK), intent(out) :: outputReal
integer(IK) :: indx
! associate the input C procedure pointer to a Fortran procedure pointer
call c_f_procpointer(cptr=getFunValFromC, fptr=getFunVal)
outputReal = 0._RK
do indx = -5,5
write(*,"(*(g0,:,' '))") "value of indx from inside Fortran: ", indx
outputReal = outputReal + getFunVal(indx)
end do
write(*,"(*(g0,:,' '))") "value of outputReal from inside Fortran: ", outputReal
! nullify the Fortran pointer
nullify(getFunVal)
end subroutine getFoo
end module foo_mod
这看起来有点冗长,但比 F77 好得多。毕竟,我们生活在 21 世纪。然后你会通过 Intel ifort 编译这个 Fortran 代码,例如,
ifort /dll /threads /libs:static foo_mod.f90 /exe:foo.dll
然后,您将从生成的 DLL foo.dll
中调用 getFoo()
,如以下 Python 脚本,
import ctypes as ct
import numpy as np
# This is the Python callback function to be passed to Fortran
def getSquare(inputInteger):
print("value of indx received by getSquare() inside Python: ",inputInteger)
return np.double(inputInteger**2)
# define ctypes wrapper function, with the proper result and argument types
getFunVal_proc = ct.CFUNCTYPE( ct.c_double # callback (python) function result
, ct.c_int32 # callback (python) function input integer argument
)
getSquare_pntr = getFunVal_proc(getSquare)
libpath = "foo.dll"
try:
# open DLL
foolib = ct.CDLL(libpath)
except Exception as e:
import logging
logger = logging.Logger("catch_all")
logger.error(e, exc_info=True)
# define getFoo's interface from Fortran dll
foolib.getFoo.restype = None # return type of the Fortran subroutine/function
foolib.getFoo.argtypes = [ getFunVal_proc # procedure
, ct.POINTER(ct.c_double) # real64 return value
, ]
outputReal = ct.c_double(0.)
foolib.getFoo ( getSquare_pntr
, ct.byref(outputReal)
)
print("value of outputReal received in Python: ", np.double(outputReal))
运行 这个脚本会产生如下内容,
In [1]: run main.py
value of indx from inside Fortran: -5
value of indx received by getSquare() inside Python: -5
value of indx from inside Fortran: -4
value of indx received by getSquare() inside Python: -4
value of indx from inside Fortran: -3
value of indx received by getSquare() inside Python: -3
value of indx from inside Fortran: -2
value of indx received by getSquare() inside Python: -2
value of indx from inside Fortran: -1
value of indx received by getSquare() inside Python: -1
value of indx from inside Fortran: 0
value of indx received by getSquare() inside Python: 0
value of indx from inside Fortran: 1
value of indx received by getSquare() inside Python: 1
value of indx from inside Fortran: 2
value of indx received by getSquare() inside Python: 2
value of indx from inside Fortran: 3
value of indx received by getSquare() inside Python: 3
value of indx from inside Fortran: 4
value of indx received by getSquare() inside Python: 4
value of indx from inside Fortran: 5
value of indx received by getSquare() inside Python: 5
value of outputReal from inside Fortran: 110.0000000000000
value of outputReal received in Python: 110.0
与您的 F2PY 代码相比,上面的 Python 脚本可能再次显得相当冗长。但它比您的实施更专业、更现代、更符合标准,符合 Python 和 Fortran 标准。
脚注:Intel ifort 对 Windows、Linux 和 Mac 平台上的所有学生、教师和开源开发人员免费提供。这并不意味着 gfortran 不好。但在我看来,在 Windows OS 上使用 gcc 通常不比永无止境的噩梦好(我与英特尔没有任何关系,只是一个用户)。