Spec-Zone.ru › NumPy 2.0

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

Spec-Zone.ru

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