Comment travailler avec des pointeurs de fonction dans Fortran dans les programmes scientifiques

11

Voici une utilisation typique des pointeurs de fonction en C. Je voudrais faire quelque chose de similaire dans Fortran. J'ai quelques idées, mais j'aimerais savoir s'il existe un moyen canonique de le faire.

Les pointeurs de fonction et les contextes transmis par l'utilisateur sont stockés, puis appelés ultérieurement.

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

La fonction de l'utilisateur est rappelée à l'aide de son contexte à différents moments ultérieurs.

Dans PETSc, ils font également un usage intensif des tables de pointeurs chaîne -> fonction. Tout est un plugin, donc l'utilisateur peut enregistrer ses propres implémentations et ils sont de première classe.

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

Cela enregistre la routine de création dans un "FList", puis PCSetFromOptions () offre la possibilité de choisir cette méthode par rapport à tout autre choix. Si le système prend en charge le chargement dynamique, vous pouvez ignorer la dépendance au moment de la compilation sur le symbole PCCreate_GAMG et simplement passer NULL, puis le symbole sera recherché dans la bibliothèque partagée au moment de l'exécution.

Notez que cette étape au-delà d'une "usine", c'est une inversion du dispositif de contrôle similaire à ce que Martin Fowler appelle "localisateur de service".

Remarque: cela est venu dans ma correspondance privée avec Jed Brown, où il m'a posé cette question. J'ai décidé de l'externaliser et de voir quelles réponses les gens peuvent trouver.

Ondřej Čertík
la source

Réponses:

5

Il n'est pas nécessaire d'utiliser le transfert pour émuler void *dans un code Fortran moderne. À la place, utilisez simplement le module intrinsèque ISO_C_BINDING , qui est pris en charge par tous les compilateurs Fortran traditionnels. Ce module permet une interface très simple entre Fortran et C, avec quelques mises en garde très mineures. On peut utiliser les fonctions C_LOCet C_FUNLOCpour obtenir des pointeurs C vers les données et procédures Fortran, respectivement.

En ce qui concerne l'exemple PETSC ci-dessus, je suppose que le contexte est généralement un pointeur vers une structure définie par l'utilisateur, ce qui équivaut à un type de données dérivé dans Fortran. Cela ne devrait pas être un problème pour gérer l'utilisation C_LOC. La poignée TSIFunction opaque est également très simple à manipuler: utilisez simplement le type de données ISO_C_BINDING c_ptr, qui est équivalent à void *C. Une bibliothèque écrite en Fortran peut utiliser c_ptrsi elle a besoin de contourner la vérification de type stricte de Fortran moderne.

Brian
la source
Brian, oui, en attendant, Jed et moi avons trouvé pas mal de solutions au rappel, voir ici: fortran90.org/src/best-practices.html#type-casting-in-callbacks , le type (c_ptr) est le numéro de section V.
Ondřej Čertík
9

Il y a beaucoup de ce que je suppose être un langage spécifique à PETSc dans votre question (avec laquelle je ne suis pas familier), donc il pourrait y avoir une ride ici, je ne comprends pas très bien, mais peut-être que cela sera toujours utile pour vous commencé.

Fondamentalement, vous devez définir l'interface de la procédure, puis vous pouvez passer un pointeur vers une fonction qui suit cette interface. Le code suivant montre un exemple. Tout d'abord, il existe un module qui définit l'interface et montre un exemple rapide d'un morceau de code qui exécuterait la routine fournie par l'utilisateur qui suit cette interface. Vient ensuite un programme qui montre comment l'utilisateur utiliserait ce module et définirait la fonction à exécuter.

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
la source
Merci d'avoir répondu. L'exemple PETSc ci-dessus stocke également le pointeur de fonction dans une structure de données interne, mais je pense qu'il est assez trivial d'enregistrer en PROCEDURE(function_template), POINTER :: funcinterne.
Ondřej Čertík
Notez que le pointeur est un objet opaque plutôt que l'adresse de ce code, donc AFAIK il ne peut pas y avoir d'interopérabilité avec C. Dans PETSc, nous devons maintenir des tables de pointeurs de fonction pour les wrappers C pour ces choses.
Matt Knepley
L'exemple PETSc stocke à la fois le pointeur de fonction et le contexte (données d'utilisateur privé qui sont renvoyées lorsque la fonction est appelée). Le contexte est vraiment crucial, sinon l'utilisateur finit par faire des choses horribles comme référencer des globaux. Puisqu'il n'y a pas d'équivalent à void*, l'utilisateur finit par devoir écrire lui-même des interfaces pour les fonctions de bibliothèque. Si vous implémentez la bibliothèque en C, cela suffit, mais si vous implémentez dans Fortran, vous devez vous assurer que le compilateur ne voit jamais l'INTERFACE "factice" de la bibliothèque en même temps que l'INTERFACE de l'utilisateur.
Jed Brown
2
L'équivalent de void*Fortran est une transferméthode. Voir ici pour un exemple d'utilisation. Les 3 autres approches en plus de la transferméthode sont des "tableaux de travail", "type dérivé spécifique au lieu de void *" et utilisent des variables de module locales au module.
Ondřej Čertík
C'est dommage que l'utilisateur doive se moquer transferd'un type absurde ( character (len=1), allocatable) juste pour appeler la fonction.
Jed Brown