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має значення 0в підпрограмі f. Я припускаю, що мені не вистачає якоїсь f2pyмагії заголовків, щоб правильно керувати цією ситуацією.

Більш загально, який найкращий спосіб зв’язати вищевказану підпрограму в Python? Я настійно вважаю за краще не змінювати саму підпрограму.


Метт, ти знайомий з посібником з найкращих практик Ондрея Чертика, зокрема з розділом « Взаємозв’язок із Python» ? Ми обговорювали подібну проблему взаємодії з PyClaw і поки не вирішили її. :)
Арон Ахмадія

Відповіді:


23

Я не дуже добре знайомий з інтерфейсами f2py, але мені дуже добре знайомий з обгортанням Fortran. F2py просто автоматизує деякі або всі речі нижче.

  1. Спочатку потрібно експортувати до C за допомогою модуля iso_c_binding, як описано, наприклад, тут:

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

    Відмова: Я є основним автором сторінок fortran90.org. Це єдиний незалежний від платформи та компілятора спосіб виклику Fortran від C. Це F2003, тому в наші дні немає жодного приводу використовувати інший спосіб.

  2. Ви можете експортувати / викликувати масиви лише із вказаною повною довжиною (явна форма), тобто:

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

    але не приймати форму:

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

    Це тому, що мова С не підтримує такі масиви. Існує розмова про те, щоб включити таку підтримку або в F2008, або пізніше (я не впевнений), і спосіб, як це буде працювати, полягає в деяких підтримуючих структурах даних C, оскільки вам потрібно нести інформацію про масив.

    У Fortran слід в основному використовувати припущену форму, лише в особливих випадках слід використовувати явну форму, як описано тут:

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

    Це означає, що вам потрібно написати просту обгортку навколо підпрограми набути форми, яка буде обертати речі в явні масиви фігури за моїм першим посиланням вище.

  3. Коли у вас є підпис C, просто зателефонуйте йому з Python будь-яким способом, який вам подобається, я використовую Cython, але ви можете використовувати ctype або C / API вручну.

  4. The 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).
Арон Ахмадія

Я знав, що я трохи глум, yале хотів, щоб щось було виділено (мій фактичний код має нетривіальні виділення). Я навіть не знав про багато інших моментів. Схоже, я повинен заглянути в якийсь посібник з найкращих практик Fortran90 .... Дякую за ретельну відповідь!
MRocklin

Зауважте, що, використовуючи сьогоднішні компілятори Fortran, ви обмотаєте F77 точно так само, написавши просту оболонку iso_c_binding і викликаючи з неї застарілу підпрограму f77.
Ondřej Čertík

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і хочу отримати масив розміру 2 ** n. Поки що я маю передавати також 2 ** n як окремий аргумент.
Аллео
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.