10

经验:
fortran 约 3 个月
python - 中级:在此之前从未在 python 中使用过 ctypes 模块

我正在寻找一种将 fortran 代码用于我在 python 中的博士工作的方法 - 随后使用计算进行动态可视化,以使用 matplotlib 进行可视化。

这篇文章有帮助(这表明可以使用 ctypes 模块在 python 中使用/调用 fortran 代码 - 并且鉴于 fortran 函数具有绑定到它们的备用名称 - 这在逻辑上对我来说很有意义,尽管我不知道这是如何工作的细节。但我们确实明智地选择了我们的战斗!)。
然后这个 SO 帖子也处理从 python 调用 fortran 函数。

下一个合乎逻辑的步骤是查找python 模块 ctypes 的文档。这讨论了如何在 API 级别使用 python 访问共享库。

我有所有的东西来制作一个最小的工作示例,另一个答案已经完成了。但我想看看输出机制和涉及真实浮点数的数学运算。这是我制作的测试用例。

测试.f90

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

编辑:出于某种原因,我的工作计算机需要 -fPIC 选项来编译

为了确保我的两个功能prntsin_2在某个地方,我检查了nm

$ nm test.so | tail -3  
0000067f T prnt_
0000065c T sin_2_
         U sinf@@GLIBC_2.0

到现在为止还挺好。我的函数prntsin_2已映射到库中prnt_sin_2_库中。

从 python 解释器调用 fortran 函数

这就是所有这些都变得有点湿透的地方。使用python-ctypes 文档中的表格,我输入了以下内容 -

>>> 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

我想在每个输出末尾打印的 1 是 python 让我知道布尔输出的方式t.prnt_.true..
我有点担心t.prnt_当我切换到正确的字符串数据类型时情况会变得更糟。文字打印没问题,只有e最后有 a 。那是EOL字符吗?

然后是t.sin_2_功能。我决定用它来计算 sin(4.56)**2。这是怎么回事 -

>>> 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)

我在这里哪里出错了?我试图解释我是如何解决这个问题的,这样如果我在某处犯了明显的错误就会很明显。
指向其他 SO 帖子的大量链接是为了帮助其他提出与我现在一样的问题的人。

4

3 回答 3

11

在 Fortran 中,参数是通过引用传递的。Fortran 字符数组不是以 null 结尾的;long int长度作为隐式参数按值传递。此外,Python 的float类型是 a double,因此您可能希望使用 Fortranreal(8)以保持一致性。

测试.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

请记住在适当的情况下argtypes为函数设置 ctypes 。restype在这种情况下sin_2,接受一个浮点指针并返回一个浮点数。

ctypes 示例:

>>> 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
于 2013-04-08T13:58:33.990 回答
5

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.

于 2013-04-10T18:37:09.917 回答
3

I would just like to point out that one should always use the iso_c_binding module and the bind attribute when interfacing Python and Fortran using ctypes. That way you can be sure that your variable types match. And the compiler is free to mangle names however it likes when building the shared library so you might get undefined references when switching compiler vendor or even just using a different version of your compiler and trying to call t.sin_2_ as per the example. See also here.

So you should do something like:

module test
  use iso_c_binding
  implicit none

contains

  function sin_2(r) bind(c, name='c_sin_2')
    real(c_float) :: sin_2
    real(c_float), intent(in), value :: r

    sin_2 = sin(r)**2
  end function sin_2
end module test

And then

from ctypes import *

test = CDLL('./test.so')

test.c_sin_2.restype = c_float

print(test.c_sin_2(c_float(3)))

I'm not quite sure, though, why I still have to set the restype, maybe someone with more experience can tell us that.

Here you can find some additional info.

于 2018-11-29T14:58:52.157 回答