Spec-Zone.ru › NumPy 1.20

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

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

  • Создание так называемого файла сигнатур, содержащего описания обёрток для функций Fortran или C, также называемых сигнатурами функций. В случае процедур Fortran, F2PY может создать начальный файл сигнатур, просканировав исходный код Fortran и вычленив всю необходимую информацию для создания обёртки функций.
  • По желанию, созданные F2PY файлы сигнатур могут быть отредактированы для оптимизации обёртки функций, чтобы сделать их «умнее» и более «pythonic».
  • 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 может сгенерировать более Pythonic (более явные, удобные в использовании и менее подверженные ошибкам) обёртки для функций 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–2021 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.20/f2py/f2py.getting-started.html

Spec-Zone.ru

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