Примеры 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 дает правильные результаты.