numpy 如何使用ctypes运行Fortran脚本?

ffscu2ro  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(150)

首先,我没有用fortran编写代码的经验。我试图用python ctypes运行fortran代码。我使用命令gfortran -shared -g -o test.so test.f90将我的 test.f90 文件(代码如下)转换为 * test.so *。
在阅读了“基本数据类型”一章中通过ctypes从Python调用的C函数返回不正确的值和表之后,https://docs.python.org/3/library/ctypes.html#module-ctypes我对在我的Python代码中传递正确的数据类型有了一个线索,比如ctypes.c_double(123)real(kind=c_double)。我收到了一个Type Error: wrong type。我不知道如何将正确的数据类型传递给real(kind=c_double),dimension(*),它对我来说似乎是一个数组。在Numpy array to ctypes with FORTRAN orderinghttps://numpy.org/doc/stable/reference/generated/numpy.ndarray.ctypes.html上,很好地解释了numpy提供了ctype转换。所以我尝试了一些随机数组来传递np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double))的值。现在我收到另一个类型错误:TypeError: item 2 in _argtypes_ has no from_param method
我也在下面分享了我的python代码。我不知道我需要在我的函数中传递哪些数据类型。我将非常感谢你的解释。

function sticksum( anzd, w, b, a, weight, val, maxTchan, sthresh) result(spek) bind(c, name="sticksum")
    use, intrinsic :: iso_c_binding

    real(kind=c_double) :: anzd
    real(kind=c_double) :: b,a
    real(kind=c_double),dimension(*) :: w,weight
    real(kind=c_double),dimension(*) :: val
    integer(kind=c_int) :: maxTchan
    real(kind=c_double) :: sthresh
    real(kind=c_double) :: spek

    integer :: i, t, j,anz
    real    :: stick, maxw
    
    
    anz = anzd
    spek = 123
    i=1
    t = w(i) * b + a + 1.5
    if(t >= 1) THEN
        spek = anz
        stick = 0. + val(t)
        maxw = weight(i)*stick
        do i=2,anz
            t = w(i) * b + a + 1.5
            if(t > maxTchan) exit
            stick = val(t)
            maxw = max(weight(i)*stick,maxw)
            if( (w(i)*w(i)-w(i-1)*w(i-1)) > 0.5) THEN
                spek = spek + maxw
                maxw = 0
            end if
        end do
    end if
end function sticksum
from ctypes import *
import numpy as np 
so_file = "./test.so"
my_functions = CDLL(so_file)

print(type(my_functions))

my_functions.sticksum_4.argtypes = [c_double,np.ndarray.ctypes,c_double,c_double,np.ndarray.ctypes,np.ndarray.ctypes,c_int, c_double]
my_functions.restype = c_double

anzd = c_double(123) 
w = np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double))
b=c_double(123) 
a=c_double(123) 
weight=np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double))
val=np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double))
maxTchan=c_int(123)
sthresh=c_double(123)

sum = my_functions.sticksum_4(anzd,w,b,a,weight,val,maxTchan,sthresh)
eit6fx6z

eit6fx6z1#

我想先声明 Fortran 不是我的技能之一-我接触过它~5次(SO 的其他问题)。
我发现了一堆问题:

  • Fortran
    *参数通过引用传递给函数/子例程([GNU.GCC]: Argument passing conventions).这花了我一个小时的时间来弄清楚.在我修复了所有的休息,我得到 * 访问冲突 * 甚至打印一个这样的参数.我在调查期间添加了虚拟函数.无论如何,最简单的版本是指定 value 作为按值传递的值(反之则是从调用代码传递指针)
  • 由于我在 Win 上,我必须将 dllexport 属性添加到每个要由 .dll 导出的函数(我也可以通过将 /EXPORT 传递给每个函数的链接器来完成)。我还有另一个挫折,因为当检查函数导出时,我主要使用的工具(检查[SO]: Discover missing module using command-line ("DLL load failed" error) (@CristiFati's answer))不会显示它:
    • 依赖关系 *:

    • 依赖关系步行者 *:

  • 第一个参数是维度,不需要是双精度
  • 其他(次要)问题(如重组,添加模块的东西,...)
  • Python
  • 参数类型与 Fortran 定义中的参数类型不匹配,从而产生(如问题中的 URL 所述)Undefined Behavior。我说明了传递 NumPy 数组的两种方法(未注解的一种对我来说似乎更好一点)。

请注意,在使用多维数组时,FortranC 中的轴顺序不同(例如,对于 2D,前者是column major
设法让示例工作(我不知道 sticksum 的所有操作都应该做什么)。

  • dll00.f90
module dll00

use iso_c_binding
implicit none

contains

#if 0
function dummy(i0, d0) result(res) bind(C, name="dummy")
    !dec$ attributes dllexport :: dummy
    integer(kind=c_int), intent(in), value :: i0
    real(kind=c_double), intent(in), value :: d0
    integer(kind=c_int) :: res

    res = 1618
    print *, "xxx", res
    print *, "yyy", i0
    print *, "zzz", d0
end function dummy
#endif

function sticksum(anz, w, b, a, weight, value, maxTchan, sthresh) result(spek) bind(C, name="sticksum")
    !dec$ attributes dllexport :: sticksum
    integer(kind=c_int), intent(in), value :: anz, maxTchan
    real(kind=c_double), intent(in), value :: b, a, sthresh
    real(kind=c_double), intent(in), dimension(*) :: w, weight, value
    real(kind=c_double) :: spek

    integer :: i, t, j
    real :: stick, maxw

    spek = 123
    i = 1
    t = w(i) * b + a + 1.5
    if (t >= 1) then
        spek = anz
        stick = 0. + value(t)
        maxw = weight(i) * stick
        do i = 2, anz
            t = w(i) * b + a + 1.5
            if (t > maxTchan) exit
            stick = value(t)
            maxw = max(weight(i) * stick, maxw)
            if ((w(i) * w(i) - w(i - 1) * w(i - 1)) > 0.5) then
                spek = spek + maxw
                maxw = 0
            end if
        end do
    end if
end function sticksum

end module dll00
  • code00.py
#!/usr/bin/env python

import ctypes as cts
import sys

import numpy as np

DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

ELEMENT_TYPE = cts.c_double

DblPtr = cts.POINTER(cts.c_double)

def np_mat_type(dim, element_type=ELEMENT_TYPE):
    return np.ctypeslib.ndpointer(dtype=element_type, shape=(dim,), flags="F_CONTIGUOUS")

def main(*argv):
    dim = 5
    dll = cts.CDLL(DLL_NAME)
    sticksum = dll.sticksum
    sticksum.argtypes = (cts.c_int, np_mat_type(dim), cts.c_double, cts.c_double, np_mat_type(dim), np_mat_type(dim), cts.c_int, cts.c_double)
    #sticksum.argtypes = (cts.c_int, DblPtr, cts.c_double, cts.c_double, DblPtr, DblPtr, cts.c_int, cts.c_double)  # @TODO - cfati: alternative
    sticksum.restype = cts.c_double

    if 0:
        dummy = dll.dummy
        dummy.argtypes = (cts.c_int, cts.c_double)
        dummy.restype = cts.c_int
        print(dummy(cts.c_int(3141593), cts.c_double(2.718282)))

    w = np.arange(dim, dtype=ELEMENT_TYPE)
    a = 1
    b = 2
    weight = np.arange(1, dim + 1, dtype=ELEMENT_TYPE)
    val = np.arange(2, dim + 2, dtype=ELEMENT_TYPE)
    #print(w, weight, val)

    mtc = 3
    stresh = 6

    res = sticksum(dim, w, b, a, weight, val, mtc, stresh)
    #res = sticksum(dim, np.asfortranarray(w, dtype=ELEMENT_TYPE).ctypes.data_as(DblPtr), b, a, np.asfortranarray(weight, dtype=ELEMENT_TYPE).ctypes.data_as(DblPtr), np.asfortranarray(val, dtype=ELEMENT_TYPE).ctypes.data_as(DblPtr), mtc, stresh)  # @TODO - cfati: alternative
    print("\n{:s} returned: {:.3f}".format(sticksum.__name__, res))

if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q075937942]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul

[prompt]>
[prompt]> dir /b
code00.py
dll00.f90

[prompt]>
[prompt]> "f:\Install\pc032\Intel\OneAPI\Version\compiler\2021.3.0\windows\bin\intel64\ifort.exe" /nologo /fpp /c dll00.f90

[prompt]>
[prompt]> link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"f:\Install\pc032\Intel\OneAPI\Version\compiler\2021.3.0\windows\compiler\lib\intel64_win" dll00.obj
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.dll
dll00.exp
dll00.f90
dll00.lib
dll00.mod
dll00.obj

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32

sticksum returned: 5.000

Done.

读起来可能也很有趣:

相关问题