Примеры F2PY#

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

Примечание

Лучшее место для поиска примеров — это Трекер проблем NumPy, или тестовые случаи для f2py. Некоторые дополнительные варианты использования находятся в Сокращение шаблонного кода и использование шаблонов.

Пошаговое руководство по F2PY: базовый модуль расширения#

Создание источника для базового модуля расширения#

Рассмотрим следующую подпрограмму, содержащуюся в файле с именем add.f

C
      SUBROUTINE ZADD(A,B,C,N)
C
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J)+B(J)
 20   CONTINUE
      END

Эта процедура просто складывает элементы в двух смежных массивах и помещает результат в третий. Память для всех трёх массивов должна быть предоставлена вызывающей процедурой. Очень простой интерфейс к этой процедуре может быть автоматически сгенерирован с помощью f2py:

python -m numpy.f2py -m add add.f

Эта команда создаст модуль расширения с именем addmodule.c в текущем каталоге. Этот модуль расширения теперь можно скомпилировать и использовать из Python, как и любой другой модуль расширения.

Создание скомпилированного модуля расширения#

Вы также можете заставить f2py скомпилировать add.f вместе с созданным модулем расширения, оставляя только файл библиотеки общего пользования, который можно импортировать из Python:

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

Эта команда создаёт модуль расширения Python, совместимый с вашей платформой. Этот модуль затем можно импортировать в Python. Он будет содержать метод для каждой подпрограммы в add. Строка документации каждого метода содержит информацию о том, как можно вызвать метод модуля:

>>> import add
>>> print(add.zadd.__doc__)
zadd(a,b,c,n)

Wrapper for ``zadd``.

Parameters
----------
a : input rank-1 array('D') with bounds (*)
b : input rank-1 array('D') with bounds (*)
c : input rank-1 array('D') with bounds (*)
n : input int

Улучшение базового интерфейса#

Интерфейс по умолчанию является очень буквальным переводом кода Fortran на Python. Аргументы массивов Fortran преобразуются в массивы NumPy, а целочисленный аргумент должен быть сопоставлен с C целое число. Интерфейс попытается преобразовать все аргументы к требуемым типам (и формам) и выдаст ошибку, если это не удастся. Однако, поскольку f2py ничего не знает о семантике аргументов (так что C является выходным и n должны действительно соответствовать размерам массива), возможно злоупотребление этой функцией, что может привести к аварийному завершению Python. Например:

>>> add.zadd([1, 2, 3], [1, 2], [3, 4], 1000)

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

Для улучшения интерфейса, f2py поддерживает директивы. Это достигается путем создания файла сигнатуры. Обычно лучше начинать с интерфейсов, которые f2py производит в этом файле, что соответствует поведению по умолчанию. Чтобы получить f2py для генерации файла интерфейса используйте -h опция:

python -m numpy.f2py -h add.pyf -m add add.f

Эта команда создаёт add.pyf файл в текущем каталоге. Раздел этого файла, соответствующий zadd равен:

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(*) :: a
   double complex dimension(*) :: b
   double complex dimension(*) :: c
   integer :: n
end subroutine zadd

Размещая директивы intent и проверяя код, интерфейс можно значительно очистить, чтобы метод Python-модуля был и проще в использовании, и более устойчив к некорректным входным данным.

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(n) :: a
   double complex dimension(n) :: b
   double complex intent(out),dimension(n) :: c
   integer intent(hide),depend(a) :: n=len(a)
end subroutine zadd

Директива intent, intent(out) используется, чтобы сообщить f2py, что c является выходной переменной и должен быть создан интерфейсом перед передачей в нижележащий код. Директива intent(hide) указывает f2py не позволять пользователю указывать переменную, n, но вместо этого получить его из размера a. Зависит( a ) директива необходима, чтобы сообщить f2py, что значение n зависит от ввода a (чтобы он не пытался создать переменную n, пока переменная a не создана).

После изменения add.pyf, новый файл модуля Python может быть сгенерирован компиляцией обоих add.f и add.pyf:

python -m numpy.f2py -c add.pyf add.f

Строка документации нового интерфейса:

>>> import add
>>> print(add.zadd.__doc__)
c = zadd(a,b)

Wrapper for ``zadd``.

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

Returns
-------
c : rank-1 array('D') with bounds (n)

Теперь функцию можно вызывать гораздо более надёжным способом:

>>> add.zadd([1, 2, 3], [4, 5, 6])
array([5.+0.j, 7.+0.j, 9.+0.j])

Обратите внимание на автоматическое преобразование в правильный формат, которое произошло.

Вставка директив в исходный код Fortran#

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

Примечание

Для проектов, где код на Fortran активно разрабатывается, это может быть предпочтительным.

Таким образом, если исходный код изменен для включения:

C
      SUBROUTINE ZADD(A,B,C,N)
C
CF2PY INTENT(OUT) :: C
CF2PY INTENT(HIDE) :: N
CF2PY DOUBLE COMPLEX :: A(N)
CF2PY DOUBLE COMPLEX :: B(N)
CF2PY DOUBLE COMPLEX :: C(N)
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J) + B(J)
 20   CONTINUE
      END

Затем можно скомпилировать модуль расширения, используя:

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

Итоговая сигнатура для функции add.zadd точно такая же, как была создана ранее. Если исходный код содержал A(N) вместо A(*) и так далее с B и C, тогда почти такой же интерфейс можно получить, поместив INTENT(OUT) :: C строка комментария в исходном коде. Единственное отличие в том, что N будет необязательным вводом, который по умолчанию будет равен длине A.

Пример фильтрации#

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

C
      SUBROUTINE DFILTER2D(A,B,M,N)
C
      DOUBLE PRECISION A(M,N)
      DOUBLE PRECISION B(M,N)
      INTEGER N, M
CF2PY INTENT(OUT) :: B
CF2PY INTENT(HIDE) :: N
CF2PY INTENT(HIDE) :: M
      DO 20 I = 2,M-1
         DO 40 J = 2,N-1
            B(I,J) = A(I,J) +
     &           (A(I-1,J)+A(I+1,J) +
     &           A(I,J-1)+A(I,J+1) )*0.5D0 +
     &           (A(I-1,J-1) + A(I-1,J+1) +
     &           A(I+1,J-1) + A(I+1,J+1))*0.25D0
 40      CONTINUE
 20   CONTINUE
      END

Этот код может быть скомпилирован и связан в модуль расширения с именем filter с помощью:

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

Это создаст модуль расширения в текущем каталоге с методом под названием dfilter2d который возвращает отфильтрованную версию ввода.

depends пример ключевого слова#

Рассмотрим следующий код, сохраненный в файле myroutine.f90:

subroutine s(n, m, c, x)
	implicit none
  	integer, intent(in) :: n, m
  	real(kind=8), intent(out), dimension(n,m) :: x
  	real(kind=8), intent(in) :: c(:)

	x = 0.0d0
	x(1, 1) = c(1)

end subroutine s

Обёртывание этого с помощью python -m numpy.f2py -c myroutine.f90 -m myroutine, мы можем сделать следующее в Python:

>>> import numpy as np
>>> import myroutine
>>> x = myroutine.s(2, 3, np.array([5, 6, 7]))
>>> x
array([[5., 0., 0.],
   [0., 0., 0.]])

Теперь вместо непосредственной генерации модуля расширения мы сначала создадим файл сигнатуры для этой подпрограммы. Это распространённый шаблон для многоэтапной генерации модулей расширения. В данном случае после выполнения

python -m numpy.f2py myroutine.f90 -m myroutine -h myroutine.pyf

генерируется следующий файл сигнатуры:

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out),depend(m,n) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

Теперь, если мы запустим python -m numpy.f2py -c myroutine.pyf myroutine.f90 мы видим ошибку; обратите внимание, что файл сигнатуры включал depend(m,n) оператор для x что не является необходимым. Действительно, изменив файл выше, чтобы он читался

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

и выполняется f2py -c myroutine.pyf myroutine.f90 дает правильные результаты.

Подробнее#