F2Py dengan array bentuk yang dapat dialokasikan dan diasumsikan

18

Saya ingin menggunakan f2pyFortran modern. Secara khusus saya mencoba untuk mendapatkan contoh dasar berikut ini untuk bekerja. Ini adalah contoh berguna terkecil yang bisa saya hasilkan.

! 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

Catatan yang ndisimpulkan dari bentuk parameter input x. Catatan yang ydialokasikan dan dialokasikan di dalam tubuh subrutin.

Ketika saya kompilasi ini dengan f2py

f2py -c alloc_test.f90 -m alloc

Dan kemudian jalankan dengan Python

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

Saya mendapatkan kesalahan berikut

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

Jadi saya pergi dan membuat dan mengedit pyffile secara manual

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

Asli

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

Diubah

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

Sekarang ini berjalan tetapi nilai-nilai output zselalu 0. Beberapa pencetakan debug mengungkapkan bahwa nmemiliki nilai 0dalam subrutin f. Saya berasumsi bahwa saya kehilangan beberapa f2pysihir header untuk mengelola situasi ini dengan benar.

Lebih umum apa cara terbaik untuk menghubungkan subrutin di atas ke Python? Saya lebih suka tidak perlu memodifikasi subrutin itu sendiri.

MRocklin
sumber
Matt, apakah Anda terbiasa dengan panduan praktik terbaik Ondrej Certik, khususnya, bagian Interfacing dengan Python ? Kami telah membahas masalah antarmuka serupa untuk PyClaw dan belum menyelesaikannya pada saat ini juga :)
Aron Ahmadia

Jawaban:

23

Saya tidak super akrab dengan internal f2py, tapi saya sangat akrab dengan membungkus Fortran. F2py hanya mengotomatiskan beberapa atau semua hal di bawah ini.

  1. Pertama-tama Anda perlu mengekspor ke C menggunakan modul iso_c_binding, seperti dijelaskan misalnya di sini:

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

    Penafian: Saya adalah penulis utama halaman fortran90.org. Ini adalah satu-satunya platform dan kompiler cara independen untuk memanggil Fortran dari C. Ini adalah F2003, jadi hari ini tidak ada alasan untuk menggunakan cara lain.

  2. Anda hanya dapat mengekspor / memanggil array dengan panjang penuh yang ditentukan (bentuk eksplisit), yaitu:

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

    tetapi tidak menganggap bentuk:

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

    Itu karena bahasa C tidak mendukung array seperti itu sendiri. Ada pembicaraan untuk memasukkan dukungan seperti itu dalam F2008 atau yang lebih baru (saya tidak yakin), dan cara kerjanya adalah melalui beberapa struktur data C pendukung, karena Anda perlu membawa informasi bentuk tentang array.

    Di Fortran, Anda sebaiknya menggunakan bentuk asumsi, hanya dalam kasus khusus Anda harus menggunakan bentuk eksplisit, seperti dijelaskan di sini:

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

    Itu berarti, bahwa Anda perlu menulis pembungkus sederhana di sekitar subrutin bentuk asumsi Anda, yang akan membungkus berbagai hal menjadi array bentuk eksplisit, per tautan pertama saya di atas.

  3. Setelah Anda memiliki tanda tangan C, panggil saja dari Python dengan cara apa pun yang Anda suka, saya menggunakan Cython, tetapi Anda dapat menggunakan ctype, atau C / API dengan tangan.

  4. The deallocate(y)pernyataan tidak diperlukan, Fortran deallocates otomatis.

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

  5. real*8tidak boleh digunakan, melainkan real(dp):

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

  6. Pernyataan y(:) = 1.0ini menetapkan 1.0 dalam presisi tunggal, sehingga sisa digit akan acak! Ini adalah perangkap umum:

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

    Anda harus menggunakan y(:) = 1.0_dp.

  7. Alih-alih menulis y(:) = 1.0_dp, Anda bisa menulis y = 1, itu saja. Anda dapat menetapkan bilangan bulat ke angka floating point tanpa kehilangan keakuratan, dan Anda tidak perlu meletakkan redundan (:)di sana. Jauh lebih sederhana.

  8. Dari pada

    y = 1
    z = x + y
    

    gunakan saja

    z = x + 1

    dan jangan repot-repot dengan yarray sama sekali.

  9. Anda tidak memerlukan pernyataan "kembali" di akhir subrutin.

  10. Terakhir, Anda mungkin harus menggunakan modul, dan cukup letakkan implicit nonedi tingkat modul dan Anda tidak perlu mengulanginya di setiap subrutin.

    Kalau tidak, itu terlihat bagus bagiku. Berikut adalah kode mengikuti saran 1-10 di atas ::

    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
    

    Ini menunjukkan subrutin yang disederhanakan serta bungkus C.

    Sejauh f2py, mungkin mencoba menulis bungkus ini untuk Anda dan gagal. Saya juga tidak yakin apakah itu menggunakan iso_c_bindingmodul. Jadi untuk semua alasan ini, saya lebih suka membungkus barang dengan tangan. Maka jelaslah apa yang terjadi.

Ondřej Čertík
sumber
Sejauh yang saya tahu, f2py tidak bergantung pada ikatan ISO C (target utamanya adalah Fortran 77 dan kode Fortran 90).
Aron Ahmadia
Saya tahu saya agak bodoh, ytetapi saya ingin membuat sesuatu dialokasikan (kode saya sebenarnya memiliki alokasi non-sepele). Saya tidak tahu tentang banyak poin lainnya. Sepertinya saya harus melihat ke dalam semacam panduan praktik terbaik Fortran90 .... Terima kasih atas jawaban yang menyeluruh!
MRocklin
Perhatikan bahwa menggunakan kompiler Fortran hari ini, Anda membungkus F77 dengan cara yang persis sama --- dengan menulis pembungkus iso_c_binding sederhana dan memanggil subrutin legacy f77 dari itu.
Ondřej Čertík
6

Yang harus Anda lakukan adalah sebagai berikut:

!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

Meskipun ukuran array x dan z sekarang dilewatkan sebagai argumen eksplisit, f2py membuat argumen n opsional. Berikut ini adalah dokumentasi fungsi yang muncul ke 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)

Mengimpor dan memanggilnya dari python:

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

memberikan hasil sebagai berikut:

[ 2.  2.  2.  2.  2.]
Prometheous
sumber
Apakah ada cara untuk menggunakan beberapa ekspresi non-sepele sebagai ukuran? Misalnya, saya lulus ndan ingin mendapatkan array ukuran 2 ** n. Sejauh ini saya harus melewati 2 ** n sebagai argumen terpisah.
Alleo