Использование F2PY связывающих в Python
Все обертки для Fortran/C процедур, общих блоков или данных модуля Fortran 90, сгенерированные F2PY, представлены в Python как объекты типа fortran. Обёртки процедур являются вызываемыми объектами типа fortran, в то время как обёртки для данных Fortran имеют атрибуты, относящиеся к объектам данных.
Все объекты типа fortran имеют атрибут _cpointer, который содержит CObject, ссылающийся на C-указатель соответствующей Fortran/C функции или переменной на уровне C. Такие CObject могут использоваться в качестве аргумента обратного вызова сгенерированных F2PY функций для обхода уровня Python C/API вызова Python функций из Fortran или C, когда вычислительная часть таких функций реализована на C или Fortran и обернута с помощью F2PY (или любого другого инструмента, способного предоставить CObject функции).
Рассмотрим файл 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)
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:
>>> 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) скалярные аргументы предполагаются массивами объектов, чтобы изменения in situ были эффективными. Рекомендуется использовать массивы с соответствующим типом, но также работают и другие типы.
Рассмотрим следующий код 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, которые должны иметь код типа 'c' или '1' при использовании в качестве строковых аргументов.
Строка может иметь произвольную длину при использовании в качестве строкового аргумента для сгенерированной 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) массивные аргументы, которые всегда должны быть правильно-смежными и иметь правильный тип, иначе возникает исключение. Другим исключением являются intent(inplace) массивные аргументы, атрибуты которых будут изменены in situ, если аргумент имеет тип, отличный от ожидаемого (см. атрибут intent(inplace) для получения дополнительной информации).
В общем случае, если массив NumPy является правильно-смежным и имеет правильный тип, то он напрямую передаётся в обернутую Fortran/C функцию. В противном случае, выполняется поэлементная копия входного массива, и копия, являющаяся правильно-смежной и с правильным типом, используется в качестве массивного аргумента.
Существуют два типа правильно-смежных массивов NumPy:
- Массивы Fortran-смежные, когда данные хранятся по столбцам, т.е. индексация данных, хранящихся в памяти, начинается с наименьшего измерения;
- Массивы C-смежные или просто смежные, когда данные хранятся по строкам, т.е. индексация данных, хранящихся в памяти, начинается с наивысшего измерения.
Для одномерных массивов эти понятия совпадают.
Например, массив 2x2 A является Fortran-смежным, если его элементы хранятся в памяти в следующем порядке:
A[0,0] A[1,0] A[0,1] A[1,1]
и C-смежным, если порядок такой:
A[0,0] A[0,1] A[1,0] A[1,1]
Чтобы проверить, является ли массив C-смежным, используйте атрибут .flags.c_contiguous массивов NumPy. Чтобы проверить на Fortran-смежность, используйте атрибут .flags.f_contiguous.
Обычно нет необходимости беспокоиться о том, как массивы хранятся в памяти и о том, предполагают ли обернутые функции (будь то Fortran или C функции) тот или иной порядок хранения. F2PY автоматически гарантирует, что обернутые функции получают аргументы с правильным порядком хранения; соответствующий алгоритм предназначен для создания копий массивов только в случае крайней необходимости. Однако, при работе с очень большими многомерными входными массивами с размерами, близкими к размеру физической памяти вашего компьютера, необходимо быть внимательными, чтобы всегда использовать правильно-смежные и правильного типа аргументы.
Чтобы преобразовать входные массивы в порядок хранения по столбцам перед передачей их Fortran процедурам, используйте функцию numpy.asfortranarray(<array>).
Рассмотрим следующий код 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
Ожидается, что функция 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 подпрограммы. “Внешний” относится к 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)
используется, то при вызове 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
© 2005–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/f2py/python-usage.html