python -форранная интеграция: сравнение обратных вызовов между f2py и ctypes - PullRequest
2 голосов
/ 17 февраля 2020

Я нашел поучительный пример в: https://numpy.org/devdocs/f2py/python-usage.html#call -back-arguments . Здесь процедура fortran:

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__)
def f(i):
    return i * i
print(callback.foo(f))

Все отлично работает. Теперь я хотел бы повторить тест, используя ctypes. Я могу легко скомпилировать исходный код на Фортране: gfortran -shared callback.f -o callback.dll и загрузить библиотеку с помощью:

import ctypes as ct
import numpy as np
# import the dll
fortlib = ct.CDLL('callback.dll')

Вопросы:

  • Как я могу вызвать функцию foo в dll из ctypes, как я это делал с кодом, скомпилированным с помощью f2py?
  • Как я могу связать две переменные (указатель на функцию и указатель на реальную) требуется?

Заранее спасибо. Джанмарко

Платформа: Анаконда python 3.7.6, Mingw-64 на Windows 10

1 Ответ

2 голосов
/ 19 февраля 2020

Хороший стиль программирования диктует нам никогда не использовать односимвольные имена переменных. Современная реализация Fortran-2008 вашей подпрограммы на Fortran будет выглядеть примерно так:

module foo_mod

    use iso_c_binding, only: RK => c_double, IK => c_int32_t
    implicit none

    abstract interface
        function getFunVal_proc(inputInteger) result(funVal) bind(C)
            import :: RK, IK
            implicit none
            integer(IK), intent(in), value :: inputInteger
            real(RK) :: funVal
        end function getFunVal_proc
    end interface

contains

    subroutine getFoo(getFunValFromC,outputReal) bind(C,name="getFoo")
        !DEC$ ATTRIBUTES DLLEXPORT :: getFoo
        use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
        implicit none
        type(c_funptr), intent(in), value   :: getFunValFromC
        procedure(getFunVal_proc), pointer  :: getFunVal
        real(RK), intent(out)               :: outputReal
        integer(IK)                         :: indx

        ! associate the input C procedure pointer to a Fortran procedure pointer
        call c_f_procpointer(cptr=getFunValFromC, fptr=getFunVal)

        outputReal = 0._RK
        do indx = -5,5
            write(*,"(*(g0,:,' '))") "value of indx from inside Fortran: ", indx
            outputReal = outputReal + getFunVal(indx)
        end do

        write(*,"(*(g0,:,' '))") "value of outputReal from inside Fortran: ", outputReal

        ! nullify the Fortran pointer
        nullify(getFunVal)

    end subroutine getFoo

end module foo_mod

Это выглядит довольно многословно, но намного лучше, чем F77. В конце концов, мы живем в 21 веке. Затем вы должны скомпилировать этот код на Фортране через Intel ifort, например,

ifort /dll /threads /libs:static foo_mod.f90 /exe:foo.dll

Затем вы вызовете getFoo() из сгенерированной DLL foo.dll, как в следующем сценарии Python,

import ctypes as ct
import numpy as np

# This is the Python callback function to be passed to Fortran
def getSquare(inputInteger):
    print("value of indx received by getSquare() inside Python: ",inputInteger)
    return np.double(inputInteger**2)

# define ctypes wrapper function, with the proper result and argument types
getFunVal_proc =    ct.CFUNCTYPE( ct.c_double                  # callback (python) function result
                                , ct.c_int32                   # callback (python) function input integer argument
                                )
getSquare_pntr = getFunVal_proc(getSquare)

libpath = "foo.dll"
try:

    # open DLL
    foolib = ct.CDLL(libpath)

except Exception as e:

    import logging
    logger = logging.Logger("catch_all")
    logger.error(e, exc_info=True)

# define getFoo's interface from Fortran dll

foolib.getFoo.restype = None # return type of the Fortran subroutine/function
foolib.getFoo.argtypes =    [ getFunVal_proc            # procedure
                            , ct.POINTER(ct.c_double)   # real64 return value
                            , ]

outputReal = ct.c_double(0.)
foolib.getFoo   ( getSquare_pntr
                , ct.byref(outputReal)
                )
print("value of outputReal received in Python: ", np.double(outputReal))

Запуск этого скрипта даст что-то вроде следующего:

In [1]: run main.py
value of indx from inside Fortran:  -5
value of indx received by getSquare() inside Python:  -5
value of indx from inside Fortran:  -4
value of indx received by getSquare() inside Python:  -4
value of indx from inside Fortran:  -3
value of indx received by getSquare() inside Python:  -3
value of indx from inside Fortran:  -2
value of indx received by getSquare() inside Python:  -2
value of indx from inside Fortran:  -1
value of indx received by getSquare() inside Python:  -1
value of indx from inside Fortran:  0
value of indx received by getSquare() inside Python:  0
value of indx from inside Fortran:  1
value of indx received by getSquare() inside Python:  1
value of indx from inside Fortran:  2
value of indx received by getSquare() inside Python:  2
value of indx from inside Fortran:  3
value of indx received by getSquare() inside Python:  3
value of indx from inside Fortran:  4
value of indx received by getSquare() inside Python:  4
value of indx from inside Fortran:  5
value of indx received by getSquare() inside Python:  5
value of outputReal from inside Fortran:  110.0000000000000
value of outputReal received in Python:  110.0

Вышеупомянутый скрипт Python может снова выглядеть довольно многословным по сравнению с вашим кодом F2PY. Но это гораздо более профессионально, современно и соответствует стандартам как по стандартам Python, так и по стандартам Fortran, чем ваша реализация.

сноска: Intel ifort предоставляется бесплатно всем учащимся, преподавателям и открытым разработчики исходного кода на платформах Windows, Linux и Ma c. Это не значит, что гфортран это не хорошо. Но, на мой взгляд, использование g cc на Windows ОС в целом не лучше, чем нескончаемый кошмар (у меня нет связей с Intel, только пользователь).

...