Spec-Zone.ru › NumPy 1.19

Три способа обертки - начало работы

Обертывание функций 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 будет использован для иллюстрации; сохраните его как fib1.f:

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(a,[n])

Wrapper for ``fib``.

Parameters
----------
a : input rank-1 array('d') with bounds (n)

Other Parameters
----------------
n : input int, optional
    Default: len(a)

>>> 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)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    fib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10
    >>>
    

    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__)
a = fib(n)

Wrapper for ``fib``.

Parameters
----------
n : input int

Returns
-------
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__)
a = fib(n)

Wrapper for ``fib``.

Parameters
----------
n : input int

Returns
-------
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.19/f2py/f2py.getting-started.html

Spec-Zone.ru

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