Spec-Zone.ru › NumPy 1.21

Использование 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API