Spec-Zone.ru › NumPy 1.18

Три способа обертывания — начало работы

Обертывание функций Fortran или C для Python с помощью F2PY состоит из следующих шагов:

  • Создание так называемого файла сигнатур, содержащего описания оберток для функций Fortran или C, также называемых сигнатурами функций. В случае Fortran-процедур F2PY может создать начальный файл сигнатур, сканируя исходные коды Fortran и захватывая всю необходимую информацию для создания оберточных функций.
  • По желанию, созданные F2PY файлы сигнатур могут быть отредактированы для оптимизации оберточных функций, чтобы сделать их «умнее» и более «питоническими».
  • F2PY считывает файл сигнатур и записывает модуль Python C/API, содержащий связи Fortran/C/Python.
  • F2PY компилирует все исходные файлы и строит модуль расширения, содержащий обертки. При построении модулей расширения F2PY использует numpy_distutils, который поддерживает ряд компиляторов Fortran 77/90/95, включая Gnu, Intel, Sun Fortre, SGI MIPSpro, Absoft, NAG, Compaq и т. д.

В зависимости от конкретной ситуации, эти шаги могут быть выполнены либо одной командой, либо поэтапно, некоторые шаги могут быть пропущены или объединены с другими.

Ниже я опишу три типичных подхода к использованию F2PY. Для иллюстрации будет использоваться следующий пример кода Fortran 77:

C FILE: FIB1.F
      SUBROUTINE FIB(A,N)
C
C     CALCULATE FIRST N FIBONACCI NUMBERS
C
      INTEGER N
      REAL*8 A(N)
      DO I=1,N
         IF (I.EQ.1) THEN
            A(I) = 0.0D0
         ELSEIF (I.EQ.2) THEN
            A(I) = 1.0D0
         ELSE 
            A(I) = A(I-1) + A(I-2)
         ENDIF
      ENDDO
      END
C END FILE FIB1.F

Быстрый способ

Самый быстрый способ обернуть Fortran-подпрограмму FIB для Python — это запустить

python -m numpy.f2py -c fib1.f -m fib1

Эта команда создает (см. -c флаг, выполните python -m numpy.f2py без аргументов, чтобы увидеть объяснение опций командной строки) модуль расширения fib1.so (см. -m флаг) в текущий каталог. Теперь в Python Fortran-подпрограмма FIB доступна через fib1.fib.

>>> import numpy
>>> import fib1
>>> print fib1.fib.__doc__
fib - Function signature:
  fib(a,[n])
Required arguments:
  a : input rank-1 array('d') with bounds (n)
Optional arguments:
  n := len(a) input int

>>> a = numpy.zeros(8,'d')
>>> fib1.fib(a)
>>> print a
[  0.   1.   1.   2.   3.   5.   8.  13.]

Примечание

  • Обратите внимание, что F2PY определил, что второй аргумент n является размерностью первого массива-аргумента a. Поскольку по умолчанию все аргументы являются только входными, F2PY делает вывод, что n может быть необязательным со значением по умолчанию len(a).
  • Можно использовать разные значения для необязательного n:

    >>> a1 = numpy.zeros(8,'d')
    >>> fib1.fib(a1,6)
    >>> print a1
    [ 0.  1.  1.  2.  3.  5.  0.  0.]
    

    но возникает исключение, когда это несовместимо с входным массивом a:

    >>> fib1.fib(a,10)
    fib:n=10
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    fib.error: (len(a)>=n) failed for 1st keyword n
    >>>
    

    Это демонстрирует одну из полезных функций F2PY, заключающуюся в том, что F2PY выполняет базовые проверки совместимости между связанными аргументами, чтобы избежать непредвиденных сбоев.

  • При использовании NumPy-массива, который является Fortran-непрерывным и имеет тип данных, соответствующий предполагаемому типу Fortran, как входного аргумента массива, его указатель C непосредственно передается в Fortran.

    В противном случае F2PY создает непрерывную копию (с соответствующим типом данных) входного массива и передает указатель C копии в Fortran-подпрограмму. В результате любые возможные изменения в (копии) входного массива не влияют на исходный аргумент, как показано ниже:

    >>> a = numpy.ones(8,'i')
    >>> fib1.fib(a)
    >>> print a
    [1 1 1 1 1 1 1 1]
    

    Очевидно, это не ожидаемое поведение. Тот факт, что в приведенном выше примере все работало с dtype=float , считается случайным.

    F2PY предоставляет атрибут intent(inplace), который позволит изменить атрибуты входного массива таким образом, чтобы любые изменения, внесенные Fortran-процедурой, также были эффективны для входного аргумента. Например, если указать intent(inplace) a (см. ниже, как это сделать), то приведенный выше пример будет выглядеть так:

    >>> a = numpy.ones(8,'i')
    >>> fib1.fib(a)
    >>> print a
    [  0.   1.   1.   2.   3.   5.   8.  13.]
    

    Однако рекомендованный способ вернуть изменения, внесенные Fortran-подпрограммой, в Python, — это использовать атрибут intent(out) . Это более эффективный и чистый способ.

  • Использование fib1.fib в Python очень похоже на использование FIB в Fortran. Однако использование выходных аргументов in situ в Python является плохим стилем, поскольку в Python нет механизма безопасности по отношению к неверным типам аргументов. При использовании Fortran или C компиляторы естественным образом обнаруживают любые несовпадения типов во время компиляции, но в Python типы необходимо проверять во время выполнения. Таким образом, использование выходных аргументов in situ в Python может привести к трудно обнаруживаемым ошибкам, помимо того, что код будет менее читаемым, когда реализованы все необходимые проверки типов.

Хотя демонстрируемый способ обертывания Fortran-процедур для Python очень простой, у него есть несколько недостатков (см. комментарии выше). Эти недостатки обусловлены тем, что F2PY не может определить, каково фактическое намерение того или иного аргумента, является ли он входным или выходным аргументом, или чем-то другим. Таким образом, F2PY по умолчанию консервативно предполагает, что все аргументы являются входными аргументами.

Однако существуют способы (см. ниже), как «научить» F2PY истинным намерениям аргументов функции; и тогда F2PY сможет генерировать более питонические (более явные, более простые в использовании и менее подверженные ошибкам) обертки для Fortran-функций.

Умный способ

Давайте применим шаги по обертыванию Fortran-функций для Python по одному.

  • Сначала мы создадим файл сигнатур из fib1.f, выполнив

    python -m numpy.f2py fib1.f -m fib2 -h fib1.pyf
    

    Файл сигнатур сохраняется в fib1.pyf (см. -h флаг), и его содержимое показано ниже.

    !    -*- f90 -*-
    python module fib2 ! in 
        interface  ! in :fib2
            subroutine fib(a,n) ! in :fib2:fib1.f
                real*8 dimension(n) :: a
                integer optional,check(len(a)>=n),depend(a) :: n=len(a)
            end subroutine fib
        end interface 
    end python module fib2
    
    ! This file was auto-generated with f2py (version:2.28.198-1366).
    ! See http://cens.ioc.ee/projects/f2py2e/
    
  • Далее, мы «научим» F2PY, что аргумент n является входным аргументом (используйте атрибут intent(in) ), а результат, т. е. содержимое a после вызова Fortran-функции FIB, должен быть возвращен в Python (используйте атрибут intent(out) ). Кроме того, массив a должен быть создан динамически, используя размер, заданный входным аргументом n (используйте атрибут depend(n) для указания зависимостей).

    Содержимое измененной версии fib1.pyf (сохраненной как fib2.pyf ):

    !    -*- f90 -*-
    python module fib2 
        interface
            subroutine fib(a,n)
                real*8 dimension(n),intent(out),depend(n) :: a
                integer intent(in) :: n
            end subroutine fib
        end interface 
    end python module fib2
    
  • И, наконец, мы создаем модуль расширения, выполнив

    python -m numpy.f2py -c fib2.pyf fib1.f
    

В Python:

>>> import fib2
>>> print fib2.fib.__doc__
fib - Function signature:
  a = fib(n)
Required arguments:
  n : input int
Return objects:
  a : rank-1 array('d') with bounds (n)

>>> print fib2.fib(8)
[  0.   1.   1.   2.   3.   5.   8.  13.]

Примечание

  • Очевидно, что сигнатура fib2.fib теперь более точно соответствует намерениям Fortran-подпрограммы FIB: при заданном числе n , fib2.fib возвращает первые n числа Фибоначчи в виде NumPy-массива. Кроме того, новая сигнатура Python fib2.fib исключает любые неожиданности, с которыми мы столкнулись с fib1.fib.
  • Обратите внимание, что по умолчанию использование одиночного intent(out) также подразумевает intent(hide) . Аргумент, для которого указан атрибут intent(hide) , не будет отображаться в списке аргументов оберточной функции.

Быстрый и умный способ

«Умный» способ обертывания Fortran-функций, как описано выше, подходит для обертывания (например, сторонних) Fortran-кодов, для которых изменения в их исходных кодах нежелательны или даже невозможны.

Однако, если редактирование Fortran-кодов приемлемо, то в большинстве случаев можно пропустить генерацию промежуточного файла сигнатур. А именно, специфичные для F2PY атрибуты можно вставить непосредственно в исходные коды Fortran, используя так называемую директиву F2PY. Директива F2PY определяет специальные комментарии (начинающиеся, например, с Cf2py ), которые игнорируются Fortran-компиляторами, но F2PY интерпретирует их как обычные строки.

Вот показана измененная версия примера Fortran-кода, сохраненная как fib3.f:

C FILE: FIB3.F
      SUBROUTINE FIB(A,N)
C
C     CALCULATE FIRST N FIBONACCI NUMBERS
C
      INTEGER N
      REAL*8 A(N)
Cf2py intent(in) n
Cf2py intent(out) a
Cf2py depend(n) a
      DO I=1,N
         IF (I.EQ.1) THEN
            A(I) = 0.0D0
         ELSEIF (I.EQ.2) THEN
            A(I) = 1.0D0
         ELSE 
            A(I) = A(I-1) + A(I-2)
         ENDIF
      ENDDO
      END
C END FILE FIB3.F

Теперь построение модуля расширения можно выполнить одной командой:

python -m numpy.f2py -c -m fib3 fib3.f

Обратите внимание, что результирующая обертка для FIB такая же «умная», как и в предыдущем случае:

>>> import fib3
>>> print fib3.fib.__doc__
fib - Function signature:
  a = fib(n)
Required arguments:
  n : input int
Return objects:
  a : rank-1 array('d') with bounds (n)

>>> print fib3.fib(8)
[  0.   1.   1.   2.   3.   5.   8.  13.]

© 2005–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.18/f2py/getting-started.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API