F2Py con matrices de formas asignables y asumidas

18

Me gustaría usar f2pycon Fortran moderno. En particular, estoy tratando de que el siguiente ejemplo básico funcione. Este es el ejemplo útil más pequeño que pude generar.

! 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

Tenga en cuenta que nse infiere de la forma del parámetro de entrada x. Tenga en cuenta que yestá asignado y desasignado dentro del cuerpo de la subrutina.

Cuando compilo esto con f2py

f2py -c alloc_test.f90 -m alloc

Y luego correr en Python

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

Obtuve el siguiente error

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

Así que voy y creo y edito el pyfarchivo manualmente

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

Original

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

Modificado

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

Ahora se ejecuta pero los valores de la salida zson siempre 0. Algunas impresiones de depuración revelan que ntiene el valor 0dentro de la subrutina f. Supongo que me falta algo de f2pymagia de encabezado para manejar esta situación correctamente.

En general, ¿cuál es la mejor manera de vincular la subrutina anterior a Python? Preferiría no tener que modificar la subrutina misma.

MRocklin
fuente
Matt, ¿estás familiarizado con la guía de mejores prácticas de Ondrej Certik, específicamente, la sección Interfaz con Python ? Hemos estado discutiendo un problema de interfaz similar para PyClaw y tampoco lo hemos resuelto en este momento :)
Aron Ahmadia

Respuestas:

23

No estoy muy familiarizado con las partes internas f2py, pero estoy muy familiarizado con envolver Fortran. F2py solo automatiza algunas o todas las cosas a continuación.

  1. Primero debe exportar a C utilizando el módulo iso_c_binding, como se describe, por ejemplo, aquí:

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

    Descargo de responsabilidad: soy el autor principal de las páginas fortran90.org. Esta es la única forma independiente de plataforma y compilador de llamar a Fortran desde C. Este es F2003, por lo que en estos días no hay razón para usarlo de otra manera.

  2. Solo puede exportar / llamar matrices con longitud completa especificada (forma explícita), es decir:

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

    pero no asumir forma:

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

    Esto se debe a que el lenguaje C no admite tales matrices en sí. Se habla de incluir dicho soporte en F2008 o posterior (no estoy seguro), y la forma en que funcionaría es a través de algunas estructuras de datos C de soporte, ya que necesita llevar información de forma sobre la matriz.

    En Fortran, debe usar principalmente la forma de asumir, solo en casos especiales debe usar la forma explícita, como se describe aquí:

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

    Eso significa que debe escribir un contenedor simple alrededor de su subrutina de forma de suposición, que envolverá las cosas en matrices de formas explícitas, según mi primer enlace anterior.

  3. Una vez que tenga una firma C, simplemente llámela desde Python de la manera que desee, uso Cython, pero puede usar ctype o C / API a mano.

  4. La deallocate(y)declaración no es necesaria, Fortran desasigna automáticamente.

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

  5. real*8no debe usarse, sino más bien real(dp):

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

  6. La declaración y(:) = 1.0asigna 1.0 en precisión simple, por lo que el resto de los dígitos serán aleatorios. Esta es una trampa común:

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

    Necesitas usar y(:) = 1.0_dp.

  7. En lugar de escribir y(:) = 1.0_dp, puedes escribir y = 1, eso es todo. Puede asignar un número entero a un número de coma flotante sin perder precisión, y no necesita colocar el redundante (:)allí. Mucho mas simple.

  8. En lugar de

    y = 1
    z = x + y
    

    Solo usa

    z = x + 1

    y no te molestes con la ymatriz en absoluto.

  9. No necesita la declaración "return" al final de la subrutina.

  10. Finalmente, probablemente deberías estar usando módulos, y simplemente colocar implicit noneel nivel del módulo y no necesitas repetirlo en cada subrutina.

    De lo contrario, me parece bien. Aquí está el código que sigue las sugerencias 1-10 anteriores ::

    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
    

    Muestra la subrutina simplificada, así como una envoltura en C.

    En cuanto a f2py, probablemente intente escribir este contenedor para usted y falla. Tampoco estoy seguro de si está utilizando el iso_c_bindingmódulo. Entonces, por todas estas razones, prefiero envolver las cosas a mano. Entonces está exactamente claro lo que está sucediendo.

Ondřej Čertík
fuente
Hasta donde sé, f2py no se basa en enlaces ISO C (su objetivo principal es el código Fortran 77 y Fortran 90).
Aron Ahmadia
Sabía que estaba siendo un poco tonto, ypero quería hacer que algo fuera asignado (mi código real tiene asignaciones no triviales). Sin embargo, no conocía muchos de los otros puntos. Parece que debería buscar algún tipo de guía de mejores prácticas de Fortran90 ... ¡Gracias por la respuesta completa!
MRocklin
Tenga en cuenta que con los compiladores actuales de Fortran, ajusta F77 exactamente de la misma manera --- escribiendo un contenedor iso_c_binding simple y llame a la subrutina f77 heredada desde él.
Ondřej Čertík
6

Todo lo que tienes que hacer es lo siguiente:

!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

Aunque el tamaño de la matriz xy z ahora se pasa como un argumento explícito, f2py hace que el argumento n sea opcional. A continuación se muestra la cadena de documentación de la función tal como aparece en 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)

Importando y llamándolo desde python:

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

da el siguiente resultado:

[ 2.  2.  2.  2.  2.]
Prometedor
fuente
¿Hay alguna manera de usar alguna expresión no trivial como tamaño? Por ejemplo, paso ny quiero obtener una variedad de tamaños 2 ** n. Hasta ahora tengo que pasar también 2 ** n como argumento separado.
Alleo