F2Py con array di forme allocabili e ipotizzate


18

Mi piacerebbe usare f2pycon il moderno Fortran. In particolare, sto cercando di far funzionare il seguente esempio di base. Questo è il più piccolo esempio utile che potrei generare.

! 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

Si noti che nè dedotto dalla forma del parametro di input x. Si noti che yè allocato e deallocato all'interno del corpo della subroutine.

Quando lo compilo con f2py

f2py -c alloc_test.f90 -m alloc

E poi esegui in Python

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

Ottengo il seguente errore

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

Quindi vado a creare e modificare il pyffile manualmente

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

Originale

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

Modificata

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

Ora funziona ma i valori dell'output zsono sempre 0. Alcune stampe di debug rivelano che nha il valore 0all'interno della subroutine f. Presumo che mi manchi un po 'di f2pymagia dell'intestazione per gestire correttamente questa situazione.

Più in generale, qual è il modo migliore per collegare la subroutine sopra in Python? Preferirei fortemente non dover modificare la subroutine stessa.


Matt, conosci la guida alle migliori pratiche di Ondrej Certik, in particolare la sezione Interfaccia con Python ? Abbiamo discusso di un problema di interfaccia simile per PyClaw e non l'abbiamo ancora risolto a questo punto :)
Aron Ahmadia

Risposte:


23

Non conosco molto bene gli interni di f2py, ma conosco molto bene il wrapping di Fortran. F2py automatizza solo alcune o tutte le cose di seguito.

  1. Per prima cosa devi esportare in C usando il modulo iso_c_binding, come descritto ad esempio qui:

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

    Disclaimer: sono l'autore principale delle pagine fortran90.org. Questo è l'unico modo indipendente dalla piattaforma e dal compilatore di chiamare Fortran da C. Questo è F2003, quindi in questi giorni non c'è motivo di usare nessun altro modo.

  2. È possibile esportare / chiamare array solo con la lunghezza specificata (forma esplicita), ovvero:

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

    ma non assumere forma:

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

    Questo perché il linguaggio C non supporta tali array. Si parla di includere tale supporto in F2008 o versioni successive (non sono sicuro) e il modo in cui funzionerebbe è attraverso alcune strutture di dati C di supporto, poiché è necessario trasportare informazioni sulla forma dell'array.

    In Fortran, dovresti usare principalmente la forma di assunzione, solo in casi speciali dovresti usare la forma esplicita, come descritto qui:

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

    Ciò significa che è necessario scrivere un semplice wrapper attorno alla subroutine di forma presunta, che avvolgerà le cose in matrici di forme esplicite, secondo il mio primo link sopra.

  3. Una volta che hai una firma C, chiamala semplicemente da Python nel modo che preferisci, io uso Cython, ma puoi usare ctype o C / API a mano.

  4. L' deallocate(y)istruzione non è necessaria, Fortran si sposta automaticamente.

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

  5. real*8non dovrebbe essere usato, ma piuttosto real(dp):

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

  6. L'istruzione y(:) = 1.0assegna 1.0 con una sola precisione, quindi il resto delle cifre sarà casuale! Questa è una trappola comune:

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

    È necessario utilizzare y(:) = 1.0_dp.

  7. Invece di scrivere y(:) = 1.0_dp, puoi semplicemente scrivere y = 1, tutto qui. È possibile assegnare un numero intero a un numero in virgola mobile senza perdere la precisione e non è necessario inserire il ridondante (:). Molto più semplice

  8. Invece di

    y = 1
    z = x + y

    basta usare

    z = x + 1

    e non preoccuparti affatto ydell'array.

  9. Non è necessaria l'istruzione "return" alla fine della subroutine.

  10. Infine, dovresti probabilmente usare i moduli e metterti implicit nonea livello di modulo e non devi ripeterlo in ogni subroutine.

    Altrimenti mi sta bene. Ecco il codice che segue i suggerimenti 1-10 sopra ::

    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

    Mostra la subroutine semplificata e un wrapper C.

    Per quanto riguarda f2py, probabilmente prova a scrivere questo wrapper per te e fallisce. Inoltre, non sono sicuro che stia utilizzando il iso_c_bindingmodulo. Quindi, per tutti questi motivi, preferisco avvolgere le cose a mano. Quindi è esattamente chiaro cosa sta succedendo.


Per quanto ne so, f2py non si basa sui binding ISO C (il suo obiettivo principale è il codice Fortran 77 e Fortran 90).
Aron Ahmadia,

Sapevo di essere un po 'stupido, yma volevo fare qualcosa che fosse allocato (il mio codice attuale ha allocazioni non banali). Non sapevo di molti altri punti però. Sembra che dovrei andare a dare un'occhiata a una sorta di guida alle migliori pratiche di Fortran90 .... Grazie per la risposta completa!
MRocklin,

Si noti che utilizzando i compilatori Fortran di oggi, si avvolge F77 esattamente nello stesso modo --- scrivendo un semplice wrapper iso_c_binding e richiamando da esso la subroutine legacy f77.
Ondřej Čertík,

6

Tutto quello che devi fare è il seguente:

!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

Sebbene la dimensione dell'array xe z sia ora passata come argomento esplicito, f2py rende l'argomento n facoltativo. Di seguito è riportato il docstring della funzione come appare a 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)

Importazione e chiamata da Python:

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

fornisce il seguente output:

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

C'è un modo per usare alcune espressioni non banali come dimensioni? Ad esempio, passo ne voglio ottenere una matrice di dimensioni 2 ** n. Finora devo passare anche 2 ** n come argomento separato.
Alleo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.