Использование привязок 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) (<class 'fortran'>, <class 'fortran'>) >>> 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 не генерирует исключение.
- При преобразовании типа complex в real используется только вещественная часть комплексного числа.
-
Скалярные аргументы
intent(inout)предполагаются объектами массива, чтобы изменения «в месте» были эффективными. Рекомендуется использовать массивы с соответствующим типом, но также работают и другие типы. Дополнительную информацию об атрибуте 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' коды типов) при использовании в качестве строковых аргументов. Дополнительную информацию о кодах типов см. в разделе Скаляры.
Строка может иметь произвольную длину при использовании в качестве строкового аргумента для функции-оболочки, сгенерированной F2PY. Если длина больше ожидаемой, строка обрезается без выдачи сообщения об ошибке. Если длина меньше ожидаемой, выделяется дополнительная память, которая заполняется \0.
Поскольку строки Python неизменяемы, аргумент intent(inout) ожидает массивоподобную версию строки, чтобы изменения «в месте» были эффективными.
Рассмотрим следующий код 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)должны всегда быть собственно непрерывными и иметь совместимыйdtype, в противном случае возникает исключение. -
Массивоподобные аргументы
intent(inplace)будут изменены «в месте», если аргумент имеет тип, отличный от ожидаемого (см. атрибутintent(inplace)атрибута для получения дополнительной информации).
В общем случае, если NumPy-массив собственно непрерывный и имеет правильный тип, он непосредственно передается в обернутую Fortran/C-функцию. В противном случае создается поэлементная копия входного массива, и эта копия, будучи собственно непрерывной и с правильным типом, используется в качестве массивоподобного аргумента.
Обычно не нужно беспокоиться о том, как массивы хранятся в памяти и предполагают ли обернутые функции (будь то Fortran или C-функции) тот или иной порядок хранения. F2PY автоматически гарантирует, что обернутые функции получают аргументы с правильным порядком хранения; лежащий в основе алгоритм разработан таким образом, чтобы создавать копии массивов только в том случае, если это абсолютно необходимо. Однако, при работе с очень большими многомерными входными массивами размером, близким к размеру физической памяти вашего компьютера, следует позаботиться об использовании собственно непрерывных аргументов и аргументов правильного типа.
Для преобразования входных массивов в порядок хранения по столбцам перед передачей их 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, как показано ниже. Та же сигнатура для аргумента обратного вызова может быть указана в разных сигнатурах процедур.
Мы используем тот же код 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 "<stdin>", line 1, in <module>
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
>>>
Решение аргументов функций обратного вызова
Интерфейсы, сгенерированные F2PY, очень гибкие по отношению к аргументам обратного вызова. Для каждого аргумента обратного вызова F2PY вводит дополнительный необязательный аргумент <name>_extra_args. Этот аргумент может быть использован для передачи дополнительных аргументов пользователю, предоставляющему функции обратного вызова.
Если сгенерированная 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)
используется, то при вычислении аргумента обратного вызова gun Fortran или C функции применяются следующие правила:
- Если
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
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/f2py/python-usage.html