具有可分配和假定形状数组的F2Py


18

我想f2py与现代Fortran 一起使用。特别是,我试图使以下基本示例正常工作。这是我可以生成的最小的有用示例。

! alloc_test.f90
subroutine f(x, z)
  implicit none

! Argument Declarations !
  real*8, intent(in) ::  x(:)
  real*8, intent(out) :: z(:)

! Variable Declarations !
  real*8, allocatable :: y(:)
  integer :: n

! Variable Initializations !
  n = size(x)
  allocate(y(n))

! Statements !
  y(:) = 1.0
  z = x + y

  deallocate(y)
  return
end subroutine f

注意,这n是根据输入参数的形状推断的x。请注意,y已在子例程的主体内进行分配和释放。

当我用 f2py

f2py -c alloc_test.f90 -m alloc

然后在Python中运行

from alloc import f
from numpy import ones
x = ones(5)
print f(x)

我收到以下错误

ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but got (-1,)

所以我去pyf手动创建和编辑文件

f2py -h alloc_test.pyf -m alloc alloc_test.f90

原版的

python module alloc ! in 
    interface  ! in :alloc
        subroutine f(x,z) ! in :alloc:alloc_test.f90
            real*8 dimension(:),intent(in) :: x
            real*8 dimension(:),intent(out) :: z
        end subroutine f
    end interface 
end python module alloc

改性

python module alloc ! in 
    interface  ! in :alloc
        subroutine f(x,z,n) ! in :alloc:alloc_test.f90
            integer, intent(in) :: n
            real*8 dimension(n),intent(in) :: x
            real*8 dimension(n),intent(out) :: z
        end subroutine f
    end interface 
end python module alloc

现在它可以运行,但输出的值z始终为0。一些调试打印显示在子例程中n具有该值。我假设我缺少一些标题魔术来正确处理这种情况。 0ff2py

一般来说,将上述子例程链接到Python的最佳方法是什么?我强烈希望不必修改子例程本身。


Matt,您是否熟悉Ondrej Certik的最佳做法指南,特别是与Python接口部分?我们一直在讨论PyClaw的类似接口问题,目前也尚未解决:)
Aron Ahmadia 2013年

Answers:


23

我对f2py内部结构不是很熟悉,但是对包装Fortran非常熟悉。F2py只是自动执行下面的部分或全部操作。

  1. 首先,您需要使用iso_c_binding模块导出到C,如此处所述:

    http://fortran90.org/src/best-practices.html#interface-with-c

    免责声明:我是fortran90.org页面的主要作者。这是从C调用Fortran的唯一与平台和编译器无关的方式。这是F2003,因此,如今没有理由使用任何其他方式。

  2. 您只能导出/调用具有指定完整长度(显式形状)的数组,即:

    integer(c_int), intent(in) :: N
    real(c_double), intent(out) :: mesh(N)

    但不采用以下形状:

    real(c_double), intent(out) :: mesh(:)

    这是因为C语言本身不支持此类数组。有人谈论在F2008或更高版本中包含这种支持(我不确定),它的工作方式是通过一些支持C的数据结构,因为您需要携带有关阵列的形状信息。

    在Fortran中,应主要使用假定形状,仅在特殊情况下,应使用显式形状,如下所述:

    http://fortran90.org/src/best-practices.html#arrays

    这意味着,您需要在您的假定形状子例程周围编写一个简单的包装程序,该包装程序将根据上面的第一个链接将内容包装到显式的形状数组中。

  3. 一旦有了C签名,就可以以任何喜欢的方式从Python调用它,我使用Cython,但是您可以手动使用ctype或C / API。

  4. deallocate(y)不需要该语句,Fortran会自动取消分配。

    http://fortran90.org/src/best-practices.html#allocatable-arrays

  5. real*8不应该使用,而是real(dp)

    http://fortran90.org/src/best-practices.html#floating-point-numbers

  6. 该语句y(:) = 1.0以单精度分配1.0,因此其余数字将是随机的!这是一个常见的陷阱:

    http://fortran90.org/src/gotchas.html#floating-point-numbers

    您需要使用y(:) = 1.0_dp

  7. 无需编写y(:) = 1.0_dp,您只需编写即可y = 1。您可以将整数分配给浮点数而不会失去准确性,并且不需要在其中放置冗余(:)。简单得多。

  8. 代替

    y = 1
    z = x + y

    只是使用

    z = x + 1

    而且根本不用理会y数组。

  9. 子例程末尾不需要“ return”语句。

  10. 最后,您可能应该使用模块,并将其放在implicit none模块级别,而无需在每个子例程中重复进行。

    否则对我来说看起来不错。以下是上面建议1-10的代码:

    module test
    use iso_c_binding, only: c_double, c_int
    implicit none
    integer, parameter :: dp=kind(0.d0)
    
    contains
    
    subroutine f(x, z)
    real(dp), intent(in) ::  x(:)
    real(dp), intent(out) :: z(:)
    z = x + 1
    end subroutine
    
    subroutine c_f(n, x, z) bind(c)
    integer(c_int), intent(in) :: n
    real(c_double), intent(in) ::  x(n)
    real(c_double), intent(out) :: z(n)
    call f(x, z)
    end subroutine
    
    end module

    它显示了简化的子例程以及C包装程序。

    就f2py而言,它可能会尝试为您编写此包装器并失败。我也不确定是否正在使用该iso_c_binding模块。因此,出于所有这些原因,我更喜欢用手包裹东西。然后很清楚发生了什么。


据我所知,f2py不依赖ISO C绑定(其主要目标是Fortran 77和Fortran 90代码)。
Aron Ahmadia 2013年

我知道我有点傻,y但是我想分配一些东西(我的实际代码分配不平凡)。不过,我对其他许多方面一无所知。看来我应该去研究某种Fortran90最佳实践指南。...感谢您提供详尽的答案!
MRocklin

请注意,使用当今的Fortran编译器,您可以以完全相同的方式包装F77,方法是编写一个简单的iso_c_binding包装器,并从中调用旧的f77子例程。
昂德里杰·塞蒂克

6

您所要做的只是以下几点:

!alloc_test.f90
subroutine f(x, z, n)
  implicit none

! Argument Declarations !
  integer :: n
  real*8, intent(in) ::  x(n)
  real*8, intent(out) :: z(n)

! Variable Declarations !
  real*8, allocatable :: y(:)

! Variable Initializations !
  allocate(y(n))

! Statements !
  y(:) = 1.0
  z = x + y

  deallocate(y)
  return
end subroutine f

尽管现在将数组x和z的大小作为显式参数传递,但f2py使参数n为可选。以下是在python中显示的函数的文档字符串:

Type:       fortran
String Form:<fortran object>
Docstring:
f - Function signature:
  z = f(x,[n])
Required arguments:
  x : input rank-1 array('d') with bounds (n)
Optional arguments:
  n := len(x) input int
Return objects:
  z : rank-1 array('d') with bounds (n)

从python导入并调用它:

from alloc import f
from numpy import ones
x = ones(5)
print f(x)

给出以下输出:

[ 2.  2.  2.  2.  2.]

有没有办法使用一些非平凡的表达式作为大小?例如,我通过n并想要获得一个size数组2 ** n。到目前为止,我还必须将2 ** n作为单独的参数传递。
Alleo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.