Использование привязок F2PY в Python#
На этой странице вы можете найти полное описание и несколько примеров общих шаблонов использования F2PY с Python и различными типами аргументов. Для дополнительных примеров и случаев использования см. Примеры F2PY.
Объекты типа Fortran#
Все обертки для подпрограмм Fortran/C, общих блоков или данных модуля Fortran 90, сгенерированные F2PY, предоставляются Python как fortran объекты типа. Обертки процедур являются вызываемыми fortran объекты типа, в то время как обёртки для данных Fortran
имеют атрибуты, ссылающиеся на объекты данных.
Все fortran объекты типа имеют атрибут _cpointer который содержит
PyCapsule ссылаясь на C указатель соответствующей функции или переменной Fortran/C на уровне C. Такие PyCapsule объекты могут использоваться как аргументы обратного вызова для функций, сгенерированных F2PY, чтобы обойти слой Python C/API для вызова функций Python из Fortran или C. Это может быть полезно, когда вычислительные аспекты таких функций реализованы на C или Fortran и обёрнуты с помощью F2PY (или любого другого инструмента, способного предоставить PyCapsule
содержащий функцию).
Рассмотрим файл Fortran 77 `ftype.f:
C FILE: FTYPE.F
SUBROUTINE FOO(N)
INTEGER N
Cf2py integer optional,intent(in) :: n = 13
REAL A,X
COMMON /DATA/ A,X(3)
C PRINT*, "IN FOO: N=",N," A=",A," X=[",X(1),X(2),X(3),"]"
END
C END OF FTYPE.F
и оболочка, построенная с использованием f2py -c ftype.f -m ftype.
В Python вы можете наблюдать типы foo и data, и как получить доступ к отдельным объектам обернутого кода Fortran.
>>> import ftype
>>> print(ftype.__doc__)
This module 'ftype' is auto-generated with f2py (version:2).
Functions:
foo(n=13)
COMMON blocks:
/data/ a,x(3)
.
>>> type(ftype.foo), type(ftype.data)
(, )
>>> ftype.foo()
IN FOO: N= 13 A= 0. X=[ 0. 0. 0.]
>>> ftype.data.a = 3
>>> ftype.data.x = [1,2,3]
>>> ftype.foo()
IN FOO: N= 13 A= 3. X=[ 1. 2. 3.]
>>> ftype.data.x[1] = 45
>>> ftype.foo(24)
IN FOO: N= 24 A= 3. X=[ 1. 45. 3.]
>>> ftype.data.x
array([ 1., 45., 3.], dtype=float32)
Скалярные аргументы#
В общем случае, скалярный аргумент для функции-обёртки, сгенерированной F2PY, может быть обычным скаляром Python (целое число, число с плавающей точкой, комплексное число), а также произвольным последовательным объектом (список, кортеж, массив, строка) скаляров. В последнем случае первый элемент последовательного объекта передаётся в подпрограмму Fortran как скалярный аргумент.
Примечание
Когда требуется приведение типов и возможна потеря информации из-за сужения, например, при приведении float к integer или complex к float, F2PY не вызвать исключение.
Для приведения типа из комплексного в вещественный используется только вещественная часть комплексного числа.
intent(inout)скалярные аргументы считаются объектами массива для того, чтобы иметь in situ изменения вступят в силу. Рекомендуется использовать массивы с правильным типом, но также работают и другие типы. Подробнее об атрибуте intent.
Рассмотрим следующий код Fortran 77:
C FILE: SCALAR.F
SUBROUTINE FOO(A,B)
REAL*8 A, B
Cf2py intent(in) a
Cf2py intent(inout) b
PRINT*, " A=",A," B=",B
PRINT*, "INCREMENT A AND B"
A = A + 1D0
B = B + 1D0
PRINT*, "NEW A=",A," B=",B
END
C END OF FILE SCALAR.F
и оберните его с помощью f2py -c -m scalar scalar.f.
В Python:
>>> import scalar
>>> print(scalar.foo.__doc__)
foo(a,b)
Wrapper for ``foo``.
Parameters
----------
a : input float
b : in/output rank-0 array(float,'d')
>>> scalar.foo(2, 3)
A= 2. B= 3.
INCREMENT A AND B
NEW A= 3. B= 4.
>>> import numpy
>>> a = numpy.array(2) # these are integer rank-0 arrays
>>> b = numpy.array(3)
>>> scalar.foo(a, b)
A= 2. B= 3.
INCREMENT A AND B
NEW A= 3. B= 4.
>>> print(a, b) # note that only b is changed in situ
2 4
Строковые аргументы#
Сгенерированные F2PY функции-обёртки принимают почти любой объект Python в качестве строкового
аргумента, поскольку str применяется для нестроковых объектов. Исключениями являются массивы NumPy, которые должны иметь код типа 'S1' или 'b' (соответствует устаревшему 'c' или '1' typecodes, соответственно) при использовании в качестве строковых
аргументов. См. Скаляры для получения дополнительной информации об этих кодах типов.
Строка может иметь произвольную длину при использовании в качестве строкового аргумента для функции-обёртки, сгенерированной F2PY. Если длина больше ожидаемой, строка молча обрезается. Если длина меньше ожидаемой, выделяется дополнительная память и заполняется \0.
Поскольку строки Python неизменяемы, intent(inout) аргумент ожидает
массивную версию строки, чтобы иметь in situ изменения вступят в силу.
Рассмотрим следующий код Fortran 77:
C FILE: STRING.F
SUBROUTINE FOO(A,B,C,D)
CHARACTER*5 A, B
CHARACTER*(*) C,D
Cf2py intent(in) a,c
Cf2py intent(inout) b,d
PRINT*, "A=",A
PRINT*, "B=",B
PRINT*, "C=",C
PRINT*, "D=",D
PRINT*, "CHANGE A,B,C,D"
A(1:1) = 'A'
B(1:1) = 'B'
C(1:1) = 'C'
D(1:1) = 'D'
PRINT*, "A=",A
PRINT*, "B=",B
PRINT*, "C=",C
PRINT*, "D=",D
END
C END OF FILE STRING.F
и оберните его с помощью f2py -c -m mystring string.f.
Сессия Python:
>>> import mystring
>>> print(mystring.foo.__doc__)
foo(a,b,c,d)
Wrapper for ``foo``.
Parameters
----------
a : input string(len=5)
b : in/output rank-0 array(string(len=5),'c')
c : input string(len=-1)
d : in/output rank-0 array(string(len=-1),'c')
>>> from numpy import array
>>> a = array(b'123\0\0')
>>> b = array(b'123\0\0')
>>> c = array(b'123')
>>> d = array(b'123')
>>> mystring.foo(a, b, c, d)
A=123
B=123
C=123
D=123
CHANGE A,B,C,D
A=A23
B=B23
C=C23
D=D23
>>> a[()], b[()], c[()], d[()]
(b'123', b'B23', b'123', b'D2')
Аргументы массива#
В общем случае, аргументы массивов для функций-обёрток, сгенерированных F2PY, принимают произвольные последовательности, которые могут быть преобразованы в объекты массивов NumPy. Есть два заметных исключения:
intent(inout)аргументы массива всегда должны быть proper-contiguous и имеют совместимыйdtype, иначе вызывается исключение.intent(inplace)аргументы массива будут изменены in situ если аргумент имеет другой тип, чем ожидалось (см.intent(inplace)атрибут для получения дополнительной информации).
В общем случае, если массив NumPy proper-contiguous и имеет правильный тип, то он напрямую передается в обернутую функцию Fortran/C. В противном случае создается поэлементная копия входного массива, и копия, будучи правильно непрерывной и с правильным типом, используется как аргумент массива.
Обычно не нужно беспокоиться о том, как массивы хранятся в памяти и предполагают ли обёрнутые функции (будь то функции Fortran или C) тот или иной порядок хранения. F2PY автоматически обеспечивает, чтобы обёрнутые функции получали аргументы с правильным порядком хранения; базовый алгоритм разработан для создания копий массивов только при абсолютной необходимости. Однако при работе с очень большими многомерными входными массивами, размер которых близок к размеру физической памяти вашего компьютера, необходимо следить за использованием аргументов с правильной смежностью (contiguity) и правильным типом.
Для преобразования входных массивов в порядок хранения по столбцам перед передачей их в подпрограммы Fortran используйте функцию numpy.asfortranarray.
Рассмотрим следующий код Fortran 77:
C FILE: ARRAY.F
SUBROUTINE FOO(A,N,M)
C
C INCREMENT THE FIRST ROW AND DECREMENT THE FIRST COLUMN OF A
C
INTEGER N,M,I,J
REAL*8 A(N,M)
Cf2py intent(in,out,copy) a
Cf2py integer intent(hide),depend(a) :: n=shape(a,0), m=shape(a,1)
DO J=1,M
A(1,J) = A(1,J) + 1D0
ENDDO
DO I=1,N
A(I,1) = A(I,1) - 1D0
ENDDO
END
C END OF FILE ARRAY.F
и оберните его с помощью f2py -c -m arr array.f -DF2PY_REPORT_ON_ARRAY_COPY=1.
В Python:
>>> import arr
>>> from numpy import asfortranarray
>>> print(arr.foo.__doc__)
a = foo(a,[overwrite_a])
Wrapper for ``foo``.
Parameters
----------
a : input rank-2 array('d') with bounds (n,m)
Other Parameters
----------------
overwrite_a : input int, optional
Default: 0
Returns
-------
a : rank-2 array('d') with bounds (n,m)
>>> a = arr.foo([[1, 2, 3],
... [4, 5, 6]])
created an array from object
>>> print(a)
[[ 1. 3. 4.]
[ 3. 5. 6.]]
>>> a.flags.c_contiguous
False
>>> a.flags.f_contiguous
True
# even if a is proper-contiguous and has proper type,
# a copy is made forced by intent(copy) attribute
# to preserve its original contents
>>> b = arr.foo(a)
copied an array: size=6, elsize=8
>>> print(a)
[[ 1. 3. 4.]
[ 3. 5. 6.]]
>>> print(b)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> b = arr.foo(a, overwrite_a = 1) # a is passed directly to Fortran
... # routine and its contents is discarded
...
>>> print(a)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> print(b)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> a is b # a and b are actually the same objects
True
>>> print(arr.foo([1, 2, 3])) # different rank arrays are allowed
created an array from object
[ 1. 1. 2.]
>>> print(arr.foo([[[1], [2], [3]]]))
created an array from object
[[[ 1.]
[ 1.]
[ 2.]]]
>>>
>>> # Creating arrays with column major data storage order:
...
>>> s = asfortranarray([[1, 2, 3], [4, 5, 6]])
>>> s.flags.f_contiguous
True
>>> print(s)
[[1 2 3]
[4 5 6]]
>>> print(arr.foo(s))
>>> s2 = asfortranarray(s)
>>> s2 is s # an array with column major storage order
# is returned immediately
True
>>> # Note that arr.foo returns a column major data storage order array:
...
>>> s3 = ascontiguousarray(s)
>>> s3.flags.f_contiguous
False
>>> s3.flags.c_contiguous
True
>>> s3 = arr.foo(s3)
copied an array: size=6, elsize=8
>>> s3.flags.f_contiguous
True
>>> s3.flags.c_contiguous
False
Аргументы обратного вызова#
F2PY поддерживает вызов функций Python из кодов Fortran или C.
Рассмотрим следующий код Fortran 77:
C FILE: CALLBACK.F
SUBROUTINE FOO(FUN,R)
EXTERNAL FUN
INTEGER I
REAL*8 R, FUN
Cf2py intent(out) r
R = 0D0
DO I=-5,5
R = R + FUN(I)
ENDDO
END
C END OF FILE CALLBACK.F
и оберните его с помощью f2py -c -m callback callback.f.
В Python:
>>> import callback
>>> print(callback.foo.__doc__)
r = foo(fun,[fun_extra_args])
Wrapper for ``foo``.
Parameters
----------
fun : call-back function
Other Parameters
----------------
fun_extra_args : input tuple, optional
Default: ()
Returns
-------
r : float
Notes
-----
Call-back functions::
def fun(i): return r
Required arguments:
i : input int
Return objects:
r : float
>>> def f(i): return i*i
...
>>> print(callback.foo(f))
110.0
>>> print(callback.foo(lambda i:1))
11.0
В приведенном выше примере F2PY смог точно угадать сигнатуру функции обратного вызова. Однако иногда F2PY не может установить соответствующую сигнатуру; в этих случаях сигнатура функции обратного вызова должна быть явно определена в файле сигнатуры.
Для облегчения этого, файлы сигнатур могут содержать специальные модули (имена
этих модулей содержат специальный __user__ подстрока), которые определяют различные сигнатуры для функций обратного вызова. Аргументы обратного вызова в сигнатурах подпрограмм имеют external атрибут (см. также intent(callback)
атрибут). Чтобы связать аргумент обратного вызова с его сигнатурой в __user__ блок модуля, use оператор может быть использован, как показано ниже. Та же сигнатура для аргумента callback может быть указана в разных сигнатурах подпрограмм.
Мы используем тот же код Fortran 77, что и в предыдущем примере, но теперь
будем притворяться, что F2PY не смог правильно угадать сигнатуры
аргументов обратного вызова. Сначала создаем начальный файл сигнатур callback2.pyf используя F2PY:
f2py -m callback2 -h callback2.pyf callback.f
Затем измените его следующим образом
! -*- f90 -*-
python module __user__routines
interface
function fun(i) result (r)
integer :: i
real*8 :: r
end function fun
end interface
end python module __user__routines
python module callback2
interface
subroutine foo(f,r)
use __user__routines, f=>fun
external f
real*8 intent(out) :: r
end subroutine foo
end interface
end python module callback2
Наконец, мы собираем модуль расширения, используя
f2py -c callback2.pyf callback.f.
Пример сеанса Python для этого фрагмента будет идентичен предыдущему примеру, за исключением того, что имена аргументов будут отличаться.
Иногда пакет Fortran может требовать, чтобы пользователи предоставляли подпрограммы, которые пакет будет использовать. F2PY может создать интерфейс к таким подпрограммам, чтобы функции Python могли вызываться из Fortran.
Рассмотрим следующую подпрограмму Fortran 77, которая принимает массив в качестве входных данных
и применяет функцию func к его элементам.
subroutine calculate(x,n)
cf2py intent(callback) func
external func
c The following lines define the signature of func for F2PY:
cf2py real*8 y
cf2py y = func(y)
c
cf2py intent(in,out,copy) x
integer n,i
real*8 x(n), func
do i=1,n
x(i) = func(x(i))
end do
end
Код на Fortran ожидает, что функция func был определен внешне.
Чтобы использовать функцию Python для func, он должен иметь атрибут
intent(callback) и он должен быть указан перед external оператор.
Наконец, создайте модуль расширения с помощью f2py -c -m foo calculate.f
В Python:
>>> import foo
>>> foo.calculate(range(5), lambda x: x*x)
array([ 0., 1., 4., 9., 16.])
>>> import math
>>> foo.calculate(range(5), math.exp)
array([ 1. , 2.71828183, 7.3890561, 20.08553692, 54.59815003])
Функция включена в качестве аргумента в вызове Python-функции подпрограммы Fortran, хотя она была не в списке аргументов подпрограммы Fortran. Ключевое слово "external" относится к C-функции, сгенерированной f2py, а не к самой Python-функции. Python-функция по сути передаётся в C-функцию.
Функция обратного вызова также может быть явно установлена в модуле. Тогда нет необходимости передавать функцию в списке аргументов в функцию Fortran. Это может быть желательно, если функция Fortran, вызывающая функцию обратного вызова Python, сама вызывается другой функцией Fortran.
Рассмотрим следующую подпрограмму Fortran 77:
subroutine f1()
print *, "in f1, calling f2 twice.."
call f2()
call f2()
return
end
subroutine f2()
cf2py intent(callback, hide) fpy
external fpy
print *, "in f2, calling f2py.."
call fpy()
return
end
и оберните его с помощью f2py -c -m pfromf extcallback.f.
В Python:
>>> import pfromf
>>> pfromf.f2()
Traceback (most recent call last):
File "" , line 1, in
pfromf.error: Callback fpy not defined (as an argument or module pfromf attribute).
>>> def f(): print("python f")
...
>>> pfromf.fpy = f
>>> pfromf.f2()
in f2, calling f2py..
python f
>>> pfromf.f1()
in f1, calling f2 twice..
in f2, calling f2py..
python f
in f2, calling f2py..
python f
>>>
Примечание
При использовании модифицированного кода Fortran через callstatement или других директив, обернутая функция Python должна вызываться как обратный вызов, иначе будет использоваться только голая процедура Fortran. Для получения дополнительных сведений см.
numpy/numpy#26681
Разрешение аргументов для функций обратного вызова#
Интерфейсы, сгенерированные F2PY, очень гибки в отношении аргументов обратного вызова. Для каждого аргумента обратного вызова предусмотрен дополнительный необязательный аргумент вводится F2PY. Этот аргумент можно использовать
для передачи дополнительных аргументов пользовательским функциям обратного вызова.
Если сгенерированная F2PY функция-обертка ожидает следующий аргумент обратного вызова:
def fun(a_1,...,a_n):
...
return x_1,...,x_k
но следующая функция Python
def gun(b_1,...,b_m):
...
return y_1,...,y_l
предоставлен пользователем, и, кроме того,
fun_extra_args = (e_1,...,e_p)
используется, то применяются следующие правила, когда функция Fortran или C
оценивает аргумент обратного вызова gun:
Если
p == 0затемgun(a_1, ..., a_q)вызывается, здесьq = min(m, n).Если
n + p <= mзатемgun(a_1, ..., a_n, e_1, ..., e_p)вызывается.Если
p <= m < n + pзатемgun(a_1, ..., a_q, e_1, ..., e_p)вызывается, и здесьq=m-p.Если
p > mзатемgun(e_1, ..., e_m)вызывается.Если
n + pменьше, чем количество обязательных аргументов дляgunтогда возникает исключение.
Если функция gun может возвращать любое количество объектов в виде кортежа; затем применяются следующие правила:
Если
k < l, затемy_{k + 1}, ..., y_lигнорируются.Если
k > l, тогда толькоx_1, ..., x_lустановлены.
Общие блоки#
F2PY генерирует обертки для common блоки, определенные в блоке сигнатуры подпрограммы.
Общие блоки видны всем кодам Fortran, связанным с текущим модулем расширения, но не другим модулям расширения (это ограничение связано со способом импорта общих библиотек в Python). В Python обёртки F2PY для
common блоки являются fortran объектах типа, которые имеют (динамические) атрибуты,
связанные с членами данных общих блоков. При доступе эти
атрибуты возвращаются как объекты массивов NumPy (многомерные массивы являются
Fortran-смежными), которые напрямую ссылаются на члены данных в общих блоках. Члены
данных могут быть изменены прямым присваиванием или изменениями на месте в
соответствующих объектах массивов.
Рассмотрим следующий код Fortran 77:
C FILE: COMMON.F
SUBROUTINE FOO
INTEGER I,X
REAL A
COMMON /DATA/ I,X(4),A(2,3)
PRINT*, "I=",I
PRINT*, "X=[",X,"]"
PRINT*, "A=["
PRINT*, "[",A(1,1),",",A(1,2),",",A(1,3),"]"
PRINT*, "[",A(2,1),",",A(2,2),",",A(2,3),"]"
PRINT*, "]"
END
C END OF COMMON.F
и оберните его с помощью f2py -c -m common common.f.
В Python:
>>> import common
>>> print(common.data.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
>>> common.data.i = 5
>>> common.data.x[1] = 2
>>> common.data.a = [[1,2,3],[4,5,6]]
>>> common.foo()
>>> common.foo()
I= 5
X=[ 0 2 0 0 ]
A=[
[ 1.00000000 , 2.00000000 , 3.00000000 ]
[ 4.00000000 , 5.00000000 , 6.00000000 ]
]
>>> common.data.a[1] = 45
>>> common.foo()
I= 5
X=[ 0 2 0 0 ]
A=[
[ 1.00000000 , 2.00000000 , 3.00000000 ]
[ 45.0000000 , 45.0000000 , 45.0000000 ]
]
>>> common.data.a # a is Fortran-contiguous
array([[ 1., 2., 3.],
[ 45., 45., 45.]], dtype=float32)
>>> common.data.a.flags.f_contiguous
True
Данные модуля Fortran 90#
Интерфейс F2PY к данным модулей Fortran 90 аналогичен обработке общих блоков Fortran 77.
Рассмотрим следующий код Fortran 90:
module mod
integer i
integer :: x(4)
real, dimension(2,3) :: a
real, allocatable, dimension(:,:) :: b
contains
subroutine foo
integer k
print*, "i=",i
print*, "x=[",x,"]"
print*, "a=["
print*, "[",a(1,1),",",a(1,2),",",a(1,3),"]"
print*, "[",a(2,1),",",a(2,2),",",a(2,3),"]"
print*, "]"
print*, "Setting a(1,2)=a(1,2)+3"
a(1,2) = a(1,2)+3
end subroutine foo
end module mod
и оберните его с помощью f2py -c -m moddata moddata.f90.
В Python:
>>> import moddata
>>> print(moddata.mod.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
b : 'f'-array(-1,-1), not allocated
foo()
Wrapper for ``foo``.
>>> moddata.mod.i = 5
>>> moddata.mod.x[:2] = [1,2]
>>> moddata.mod.a = [[1,2,3],[4,5,6]]
>>> moddata.mod.foo()
i= 5
x=[ 1 2 0 0 ]
a=[
[ 1.000000 , 2.000000 , 3.000000 ]
[ 4.000000 , 5.000000 , 6.000000 ]
]
Setting a(1,2)=a(1,2)+3
>>> moddata.mod.a # a is Fortran-contiguous
array([[ 1., 5., 3.],
[ 4., 5., 6.]], dtype=float32)
>>> moddata.mod.a.flags.f_contiguous
True
Выделяемые массивы#
F2PY имеет базовую поддержку для выделяемых массивов модуля Fortran 90.
Рассмотрим следующий код Fortran 90:
module mod
real, allocatable, dimension(:,:) :: b
contains
subroutine foo
integer k
if (allocated(b)) then
print*, "b=["
do k = 1,size(b,1)
print*, b(k,1:size(b,2))
enddo
print*, "]"
else
print*, "b is not allocated"
endif
end subroutine foo
end module mod
и оберните его с помощью f2py -c -m allocarr allocarr.f90.
В Python:
>>> import allocarr
>>> print(allocarr.mod.__doc__)
b : 'f'-array(-1,-1), not allocated
foo()
Wrapper for ``foo``.
>>> allocarr.mod.foo()
b is not allocated
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6]] # allocate/initialize b
>>> allocarr.mod.foo()
b=[
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
]
>>> allocarr.mod.b # b is Fortran-contiguous
array([[ 1., 2., 3.],
[ 4., 5., 6.]], dtype=float32)
>>> allocarr.mod.b.flags.f_contiguous
True
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # reallocate/initialize b
>>> allocarr.mod.foo()
b=[
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
7.000000 8.000000 9.000000
]
>>> allocarr.mod.b = None # deallocate array
>>> allocarr.mod.foo()
b is not allocated