Cómo trabajar con punteros de función en Fortran en programas científicos

11

Aquí hay un uso típico de los punteros de función en C. Me gustaría hacer algo similar en Fortran. Tengo algunas ideas, pero me gustaría saber si hay alguna forma canónica de hacerlo.

Los punteros de función y los contextos pasados ​​por el usuario se almacenan y luego se llaman más tarde.

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

La función del usuario se vuelve a llamar utilizando su contexto en varios momentos posteriores.

En PETSc, también hacen un uso intensivo de cadenas -> tablas de puntero de función. Todo es un complemento, por lo que el usuario puede registrar sus propias implementaciones y son de primera clase.

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

Esto registra la rutina de creación en un "FList", luego PCSetFromOptions () ofrece la posibilidad de elegir este método frente a cualquiera de las otras opciones. Si el sistema admite carga dinámica, puede omitir la dependencia en tiempo de compilación del símbolo PCCreate_GAMG y simplemente pasar NULL, entonces el símbolo se buscará en la biblioteca compartida en tiempo de ejecución.

Tenga en cuenta que este paso más allá de una "fábrica", es una inversión del dispositivo de control similar a lo que Martin Fowler llama "localizador de servicio".

Nota: esto surgió en mi correspondencia privada con Jed Brown, donde me hizo esta pregunta. Decidí externalizarlo y ver qué respuestas pueden dar las personas.

Ondřej Čertík
fuente

Respuestas:

5

No es necesario usar la transferencia para emular void *en un código Fortran moderno. En su lugar, solo use el módulo intrínseco ISO_C_BINDING , que es compatible con todos los compiladores de Fortran. Este módulo facilita la interfaz entre Fortran y C, con algunas advertencias muy pequeñas. Se pueden usar las funciones C_LOCy C_FUNLOCpara obtener punteros C a los datos y procedimientos de Fortran, respectivamente.

Con respecto al ejemplo PETSC anterior, supongo que el contexto suele ser un puntero a una estructura definida por el usuario, que es equivalente a un tipo de datos derivado en Fortran. Eso no debería ser un problema con el uso C_LOC. El mango opaco de TSIFunction también es muy sencillo de manejar: simplemente use el tipo de datos ISO_C_BINDING c_ptr, que es equivalente a void *en C. Una biblioteca escrita en Fortran puede usarla c_ptrsi necesita evitar la estricta verificación de tipos de Fortran moderna.

Brian
fuente
Brian, sí, mientras tanto, Jed y yo hemos descubierto bastantes soluciones para la devolución de llamada, ver aquí: fortran90.org/src/best-practices.html#type-casting-in-callbacks , el tipo (c_ptr) es la sección número V.
Ondřej Čertík
9

Supongo que hay mucho de lo que es lenguaje específico de PETSc en su pregunta (con lo que no estoy familiarizado), por lo que puede haber una arruga aquí que no entiendo del todo, pero tal vez esto aún sea útil para ayudarlo empezado.

Básicamente, debe definir la interfaz para el procedimiento, y luego puede pasar un puntero a una función que sigue a esta interfaz. El siguiente código muestra un ejemplo. Primero, hay un módulo que define la interfaz y muestra un ejemplo rápido de un fragmento de código que ejecutaría la rutina proporcionada por el usuario que sigue esa interfaz. El siguiente es un programa que muestra cómo el usuario usaría este módulo y definiría la función a ejecutar.

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
fuente
Gracias por la respuesta. El ejemplo PETSc anterior también almacena el puntero de función en alguna estructura de datos interna, pero creo que es bastante trivial guardarlo PROCEDURE(function_template), POINTER :: funcinternamente.
Ondřej Čertík
Tenga en cuenta que el puntero es un objeto opaco en lugar de la dirección de ese código, por lo que AFAIK no puede haber interoperabilidad con C. En PETSc, tenemos que mantener tablas de punteros de función para envoltorios en C para estas cosas.
Matt Knepley
El ejemplo PETSc almacena tanto el puntero de la función como el contexto (datos privados del usuario que se devuelven cuando se llama a la función). El contexto es realmente crucial, de lo contrario el usuario termina haciendo cosas horribles como hacer referencia a los globales. Como no hay equivalente para void*, el usuario termina teniendo que escribir interfaces para las funciones de la biblioteca. Si implementa la biblioteca en C, esto es suficiente, pero si implementa en Fortran, debe asegurarse de que el compilador nunca vea la INTERFAZ "ficticia" de la biblioteca al mismo tiempo que la INTERFAZ del usuario.
Jed Brown
2
El equivalente de void*en Fortran es un transfermétodo. Vea aquí un ejemplo de uso. Los otros 3 enfoques además del transfermétodo son "matrices de trabajo", "tipo derivado específico en lugar de void *" y usan variables de módulo locales para el módulo.
Ondřej Čertík
Es una lástima que el usuario tenga que manipular transferun tipo sin sentido ( character (len=1), allocatable) solo para llamar a la función.
Jed Brown