Примеры использования F2PY
Ниже приведены некоторые примеры использования F2PY. Этот список не является исчерпывающим, но может служить отправной точкой при упаковке собственного кода.
Примечание
Лучшее место для поиска примеров — трекер задач NumPy на сайте GitHub, или тесты для f2py. Некоторые другие примеры использования находятся в Раздел по сокращению boilerplate и шаблонам.
Руководство по 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(out) используется для указания f2py, что c является переменной вывода и должна быть создана интерфейсом перед передачей в основной код. Директива intent(hide) указывает f2py не разрешать пользователю задавать переменную n, а вместо этого брать её из размера a. Директива depend( 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 получается правильный результат.
Дополнительная информация
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/f2py/f2py-examples.html