Fortran中通过ctypes的python回调

2024-04-20 12:09:58 发布

您现在位置:Python中文网/ 问答频道 /正文

考虑这个C-可互操作的Fortran子例程,它是从Python调用的,以Python回调函数作为输入参数,然后调用它

module FortranFunc_mod

    ! C-interoperable interface for the python callback
    abstract interface
        function getSquare_proc( x ) result(xSquared) bind(C)
            use, intrinsic :: iso_c_binding, only: c_double
            real(c_double), intent(in)              :: x
            real(c_double)                          :: xSquared
        end function getSquare_proc
    end interface

contains

    subroutine fortranFunc( getSquareFromPython ) bind(C, name="fortranFunc")
        !DEC$ ATTRIBUTES DLLEXPORT :: fortranFunc
        use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer, c_double
        implicit none
        type(c_funptr), intent(in)          :: getSquareFromPython
        procedure(getSquare_proc), pointer  :: getSquare
        real(c_double)                      :: x = 2._c_double, xSquared

        ! associate the input C procedure pointer to a Fortran procedure pointer
        call c_f_procpointer(cptr=getSquareFromPython, fptr=getSquare)
        xSquared = getSquare(x)
        write(*,*) "xSquared = ", xSquared
    end subroutine fortranFunc

end module FortranFunc_mod

Python函数可以如下所示:

import numpy as np
import ctypes as ct

# import dll and define result type
ff = ct.CDLL('FortranFunc_mod')
ff.fortranFunc.restype = None

# define and decorate Python callback with propoer ctypes
@ct.CFUNCTYPE( ct.c_double, ct.c_double ) # result type, argument type
def getSquareFromPython(x): return np.double(x**2)

# call Fortran function
ff.fortranFunc( getSquareFromPython )

但是,使用ifort编译此代码(已成功完成),然后运行Python代码会导致以下错误

        ---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-3-2b798bfb58b5> in <module>
     11
     12 # call Fortran function
---> 13 ff.fortranFunc( getSquareFromPython )

OSError: exception: access violation reading 0xFFFFFFFFFFFFFFFF

在这个简单的例子中,我遗漏了什么?在Fortran和python代码之间是否需要额外的C-wrapper来定义回调原型?如果您还可以提供与C等价的代码来调用Python函数,那也会有所帮助。你知道吗


Tags: 函数代码typefunctioncallendmoduledouble
1条回答
网友
1楼 · 发布于 2024-04-20 12:09:58

示例的主要问题是ff.fortranFunc只指定了它的返回类型,而没有指定它的参数类型。Fortran子例程fortranFunc有一个输入参数type(c_funptr),这也应该反映在Python方面。你知道吗

具体如何实现一个解决方案,取决于您是只想在Python中进行更改,还是愿意在Fortran源代码中进行更改。我将概述这两种解决方案:

仅在Python中进行更改

下面是Python测试例程的更新版本(我称之为test.py),具体更改如下:

  • 指定ff.fortranFunc.argtypes
  • arg_type被指定为指向c_double的指针—标量参数在C中的传递方式
  • 回调函数getSquareFromPython也被修改以反映这个x[0]

(有关最后两点的详细信息,请参见ctypes documentation-2.7版本可能会更清楚地解释这一点)

import ctypes as ct

# callback function ctypes specification
return_type = ct.c_double
arg_type = ct.POINTER(ct.c_double)
func_spec = ct.CFUNCTYPE(return_type, arg_type)

# import dll and define result AND argument type
ff = ct.CDLL('FortranFunc_mod')
ff.fortranFunc.restype = None
ff.fortranFunc.argtypes = [ct.POINTER(func_spec),]

# decorate Python callback
@func_spec
def getSquareFromPython(x):
    return x[0]**2

# call Fortran function
ff.fortranFunc( getSquareFromPython )

在Python和Fortran中进行更改

如果希望更接近原始的Python实现,也可以通过对test.py进行以下更改来实现:

  • 更改fortranFunc的参数类型:arg_type = ct.c_double
  • 更改getSquareFromPython的返回值:x**2

但是,由于回调函数现在需要一个c_double作为输入参数(而不是指向该参数的指针),因此您必须更改Fortran抽象接口以反映这一点,方法是将value属性添加到伪参数x

abstract interface
    function getSquare_proc( x ) result(xSquared) bind(C)
        use, intrinsic :: iso_c_binding, only: c_double
        real(c_double), intent(in), value       :: x
        real(c_double)                          :: xSquared
    end function getSquare_proc
end interface

编译并运行

编译并运行任何一个修改过的代码版本,在Windows上使用ifort得到以下结果(适当更改compile命令和库名称后,它也可以在Linux和OS X上使用gfortran):

> ifort /DLL FortranFunc_mod.f90 /o FortranFunc_mod.dll
...
> python test.py
 xSquared =    4.00000000000000

注意两种情况之间的区别

这两个实现之间的差异通过查看getSquareFromPython的参数x的动态类型可以清楚地反映出来(它还解释了两个备选方案所需的符号更改)。对于第一个备选方案,您可以将左侧的语句添加到getSquareFromPython,以获得右侧显示的结果:

print(type(x).__name__)                 :  LP_c_double
print(type(x.contents).__name__)        :  c_double
print(type(x.contents.value).__name__)  :  float
print(type(x[0]).__name__)              :  float
print(x.contents.value == x[0])         :  True

而对于第二种选择:

print(type(x).__name__)                 :  float

相关问题 更多 >