Spec-Zone.ru › NumPy 1.21

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

Обертывание функций 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–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/f2py/f2py.getting-started.html

Spec-Zone.ru

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