Как работать с указателями на функции в Фортране в научных программах

11

Вот типичное использование указателей на функции в C. Я хотел бы сделать нечто подобное в Fortran. У меня есть некоторые идеи, но я хотел бы знать, есть ли какой-то канонический способ сделать это.

Указатели и контексты функций, переданные пользователем, сохраняются, а затем вызываются позже.

typedef PetscErrorCode (*TSIFunction)(TS,PetscReal,Vec,Vec,Vec,void*);
PetscErrorCode TSSetIFunction(TS ts,Vec res,TSIFunction f,void *ctx);

Функция пользователя вызывается обратно с использованием их контекста в разное время.

В PETSc также интенсивно используются таблицы указателей на строку -> функция. Все это плагин, так что пользователь может зарегистрировать свои собственные реализации, и они первоклассные.

#define PCGAMG "gamg"
  PCRegisterDynamic(PCGAMG         ,path,"PCCreate_GAMG",PCCreate_GAMG);

Это регистрирует процедуру создания в «FList», затем PCSetFromOptions () предлагает возможность выбрать этот метод в сравнении с любым другим выбором. Если система поддерживает динамическую загрузку, вы можете пропустить зависимость времени компиляции от символа PCCreate_GAMG и просто передать NULL, тогда этот символ будет найден в общей библиотеке во время выполнения.

Обратите внимание, что это один шаг за пределы «фабрики», это инверсия управляющего устройства, похожая на то, что Мартин Фаулер называет «локатором услуг».

Примечание: это произошло в моей личной переписке с Джедом Брауном, где он задал мне этот вопрос. Я решил передать его на аутсорсинг и посмотреть, какие ответы могут дать люди.

Ондржей Чертик
источник

Ответы:

5

Нет необходимости использовать трансфер для эмуляции void *в современном коде Фортрана. Вместо этого просто используйте встроенный модуль ISO_C_BINDING , который поддерживается всеми основными компиляторами Fortran. Этот модуль очень упрощает взаимодействие между Fortran и C, с некоторыми незначительными оговорками. Можно использовать C_LOCи C_FUNLOCфункцию , чтобы получить указатели C для данных и процедур Fortran, соответственно.

Что касается приведенного выше примера PETSC, я предполагаю, что контекст, как правило, является указателем на пользовательскую структуру, которая эквивалентна производному типу данных в Fortran. Это не должно быть проблемой, чтобы иметь дело с использованием C_LOC. С непрозрачным дескриптором TSIFunction также очень просто иметь дело: просто используйте тип данных ISO_C_BINDING c_ptr, который эквивалентен типу void *C. Библиотека, написанная на Fortran, может использовать ее, c_ptrесли ей нужно обойти строгую проверку типов в современном Fortran.

Брайан
источник
Брайан, да, тем временем мы с Джедом нашли довольно много решений для обратного вызова, см. Здесь: fortran90.org/src/best-practices.html#type-casting-in-callbacks , тип (c_ptr) это секция номер V.
Ондржей Чертик
9

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

По сути, вы должны определить интерфейс для процедуры, а затем вы можете передать указатель на функцию, которая следует за этим интерфейсом. Следующий код показывает пример. Во-первых, есть модуль, который определяет интерфейс и показывает быстрый пример фрагмента кода, который будет выполнять подпрограмму, предоставленную пользователем, который следует этому интерфейсу. Далее идет программа, которая показывает, как пользователь будет использовать этот модуль и определить функцию, которая будет выполняться.

MODULE xmod

  ABSTRACT INTERFACE
  FUNCTION function_template(n,x) RESULT(y)
      INTEGER, INTENT(in) :: n
      REAL, INTENT(in) :: x(n)
      REAL :: y
  END FUNCTION function_template
  END INTERFACE

CONTAINS

  SUBROUTINE execute_function(n,x,func,y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    PROCEDURE(function_template), POINTER :: func
    REAL, INTENT(out) :: y
    y = func(n,x)
  END SUBROUTINE execute_function

END MODULE xmod


PROGRAM xprog

  USE xmod

  REAL :: x(4), y
  PROCEDURE(function_template), POINTER :: func

  x = [1.0, 2.0, 3.0, 4.0]
  func => summation

  CALL execute_function(4,x,func,y)

  PRINT*, y  ! should give 10.0

CONTAINS

  FUNCTION summation(n,x) RESULT(y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    REAL :: y
    y = SUM(x)
  END FUNCTION summation

END PROGRAM xprog
Barron
источник
Спасибо за ответ. Приведенный выше пример PETSc также хранит указатель функции в некоторой внутренней структуре данных, но я думаю, что это довольно тривиально сохранить PROCEDURE(function_template), POINTER :: funcвнутри.
Ондржей Чертик
Обратите внимание, что указатель является непрозрачным объектом, а не адресом этого кода, поэтому в AFAIK не может быть совместимости с C. В PETSc для этих вещей мы должны поддерживать таблицы указателей функций для оболочек C.
Мэтт Кнепли
В примере PETSc хранятся как указатель функции, так и контекст (личные данные пользователя, которые передаются обратно при вызове функции). Контекст действительно важен, в противном случае пользователь в конечном итоге делает ужасные вещи, такие как ссылки на глобальные переменные. Так как нет эквивалента void*, пользователю приходится писать интерфейсы для библиотечных функций. Если вы реализуете библиотеку в C, этого достаточно, но если вы внедряете в Fortran, вы должны убедиться, что компилятор никогда не увидит «фиктивный» ИНТЕРФЕЙС библиотеки одновременно с ИНТЕРФЕЙСОМ пользователя.
Джед Браун
2
Эквивалентом void*в Фортране является transferметод. Смотрите здесь для примера использования. Другие 3 подхода, помимо transferметода, представляют собой «рабочие массивы», «конкретный производный тип вместо void *» и используют локальные для модуля переменные модуля.
Ондржей Чертик
Обидно, что пользователь вынужден прибегать transferк бессмысленному типу ( character (len=1), allocatable) только для вызова функции.
Джед Браун