代码之家  ›  专栏  ›  技术社区  ›  Debanjan Basu

使用python-ctypes将fortran与python接口

  •  12
  • Debanjan Basu  · 技术社区  · 12 年前

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

    我一直在寻找一种方法,将fortran代码用于我在python中的博士工作——随后使用matplotlib实时可视化计算。

    THIS POST help(这告诉fortran代码可以使用ctypes模块在python中使用/调用,并且fortran函数绑定了备用名称,这在逻辑上对我来说很有意义,尽管我不知道它是如何工作的。但我们 明智地选择我们的战斗!)。
    然后 this SO post 还处理从python调用fortran函数。

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

    我有所有的部分来制作一个最小的工作示例 another answer has already done 。但我想看看输出机制和涉及实际浮点的数学运算。这是我做的测试用例。

    测试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选项来编译

    为了确保我的两个功能 prnt sin_2 在那里的什么地方,我查过了 nm :

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

    到现在为止,一直都还不错。我的功能 prnt公司 二氧化硅 已映射到 prnt_ sin_2_ 在图书馆里。

    从python解释器调用fortran函数

    这就是所有这些变得有点潮湿的地方。使用 the table in python-ctypes documentation ,我输入了以下内容-

    >>> 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 最后。这是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帖子的大量链接是为了帮助其他像我现在这样问同样问题的人。

    3 回复  |  直到 7 年前
        1
  •  12
  •   Eryk Sun    12 年前

    在Fortran中,参数是通过引用传递的。Fortran字符数组不是以null结尾的;长度通过值作为隐式传递 long int 论点此外,Python的 float 类型是 double ,所以您可能想要使用Fortran real(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
    

    记得设置ctypes argtypes 对于功能,以及 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
    
        2
  •  5
  •   John Warner    12 年前

    我使用 f2py 通常情况下,这很简单。如果您的代码符合Fortran 90声明(例如。 double precision, intent(in) :: myvar ),f2py将自动将代码包装到C中,并编译成一个可通过Python直接调用的共享对象。您可以像导入任何其他python模块一样导入f2py创建的模块。C包装器是透明的,可以处理Python和Fortran之间的类型接口,您根本不必处理C_types。我一定会推荐这条路线。

        3
  •  3
  •   user35915    6 年前

    我只想指出,人们应该始终使用 iso_c_binding 模块和 bind 使用接口Python和Fortran时的属性 ctypes 。这样您就可以确保您的变量类型匹配。编译器在构建共享库时可以随意更改名称,因此在切换编译器供应商时,甚至只是使用不同版本的编译器并尝试调用时,可能会得到未定义的引用 t.sin_2_ 如示例所示。另请参阅 here.

    所以你应该这样做:

    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
    

    然后

    from ctypes import *
    
    test = CDLL('./test.so')
    
    test.c_sin_2.restype = c_float
    
    print(test.c_sin_2(c_float(3)))
    

    不过,我不太确定为什么我还要设置 restype 也许有更丰富经验的人可以告诉我们。

    Here 你可以找到一些额外的信息。