Использование связывающих модулей 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-смежные или просто смежные, когда данные хранятся строчно, т.е. индексация данных, как хранящихся в памяти, начинается с наибольшего измерения.
Для одномерных массивов эти понятия совпадают.
Например, двумерный массив 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-подпрограмму, принимающую массив и применяющую функцию 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-подпрограмму:
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-смежными), которые напрямую связаны с членами данных общих блоков. Члены данных могут изменяться прямым присваиванием или путём изменений in situ соответствующих объектов массивов.
Рассмотрим следующий код 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)
foo - Function signature:
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 - Function signature:
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–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.19/f2py/python-usage.html