Come lavorare con i puntatori a funzione in Fortran nei programmi scientifici


11

Ecco un uso tipico dei puntatori a funzione in C. Vorrei fare qualcosa di simile in Fortran. Ho alcune idee, ma vorrei sapere se esiste un modo canonico per farlo.

I puntatori e contesti di funzione passati dall'utente vengono memorizzati, quindi chiamati in seguito.

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

La funzione dell'utente viene richiamata utilizzando il loro contesto in vari momenti successivi.

In PETSc, usano anche pesantemente le tabelle dei puntatori di funzione string ->. Tutto è un plugin, quindi l'utente può registrare le proprie implementazioni e sono di prima classe.

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

Ciò registra la routine di creazione in un "FList", quindi PCSetFromOptions () offre la possibilità di scegliere questo metodo rispetto a qualsiasi altra scelta. Se il sistema supporta il caricamento dinamico, è possibile saltare la dipendenza in fase di compilazione dal simbolo PCCreate_GAMG e passare semplicemente NULL, quindi il simbolo verrà cercato nella libreria condivisa in fase di esecuzione.

Si noti che questo passo oltre una "fabbrica", è un'inversione del dispositivo di controllo simile a quello che Martin Fowler chiama "localizzatore di servizio".

Nota: questo è emerso nella mia corrispondenza privata con Jed Brown, dove mi ha fatto questa domanda. Ho deciso di esternalizzarlo e vedere quali risposte le persone possono trovare.

Risposte:


5

Non è necessario utilizzare il trasferimento per emulare void *in un moderno codice Fortran. Invece, basta usare il modulo intrinseco ISO_C_BINDING , che è supportato da tutti i compilatori Fortran tradizionali. Questo modulo semplifica l'interfaccia tra Fortran e C, con alcuni avvertimenti molto minori. Si possono usare le funzioni C_LOCe C_FUNLOCper ottenere i puntatori C ai dati e alle procedure Fortran, rispettivamente.

Per quanto riguarda l'esempio PETSC sopra, presumo che il contesto sia tipicamente un puntatore a una struttura definita dall'utente, che è equivalente a un tipo di dati derivati ​​in Fortran. Questo non dovrebbe essere un problema da gestire C_LOC. L'handle TSIFunction opaco è anche molto semplice da gestire: basta usare il tipo di dati ISO_C_BINDING c_ptr, che è equivalente a void *in C. Una libreria scritta in Fortran può usare c_ptrse deve aggirare il rigoroso controllo del tipo di Fortran moderno.


Brian, sì, nel frattempo, Jed e io abbiamo capito alcune soluzioni al callback, vedi qui: fortran90.org/src/best-practices.html#type-casting-in-callbacks , il tipo (c_ptr) è la sezione numero V.
Ondřej Čertík,

9

C'è molto di ciò che immagino sia un linguaggio specifico di PETSc nella tua domanda (con il quale non ho familiarità), quindi potrebbe esserci una ruga qui che non capisco bene, ma forse questo sarà ancora utile per farti iniziato.

Fondamentalmente, è necessario definire l'interfaccia per la procedura, quindi è possibile passare un puntatore a una funzione che segue questa interfaccia. Il codice seguente mostra un esempio. Innanzitutto, esiste un modulo che definisce l'interfaccia e mostra un rapido esempio di un pezzo di codice che eseguirà la routine fornita dall'utente che segue tale interfaccia. Il prossimo è un programma che mostra come l'utente userebbe questo modulo e definirà la funzione da eseguire.

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

Grazie per la risposta. L'esempio PETSc sopra memorizza anche il puntatore a funzione in una struttura di dati interna, ma penso che sia abbastanza banale salvare PROCEDURE(function_template), POINTER :: funcinternamente.
Ondřej Čertík,

Si noti che il puntatore è un oggetto opaco anziché l'indirizzo di quel codice, quindi AFAIK non può esserci interoperabilità con C. In PETSc, per queste cose dobbiamo mantenere tabelle di puntatori a funzione per wrapper C.
Matt Knepley,

L'esempio PETSc memorizza sia il puntatore della funzione che il contesto (dati utente privati ​​che vengono restituiti quando viene chiamata la funzione). Il contesto è davvero cruciale, altrimenti l'utente finisce per fare cose orribili come fare riferimento a globali. Poiché non esiste un equivalente void*, l'utente finisce per dover scrivere le interfacce per le funzioni di libreria. Se si implementa la libreria in C, questo è sufficiente, ma se si implementa in Fortran, è necessario assicurarsi che il compilatore non veda mai INTERFACCIA "fittizia" della libreria contemporaneamente all'interfaccia utente.
Jed Brown l'

2
L'equivalente di void*in Fortran è un transfermetodo. Vedi qui per un esempio di utilizzo. Gli altri 3 approcci oltre al transfermetodo sono "array di lavoro", "tipo derivato specifico anziché void *" e utilizzano le variabili del modulo locali al modulo.
Ondřej Čertík,

È un peccato che l'utente debba andare in giro con transferun tipo senza senso ( character (len=1), allocatable) solo per chiamare la funzione.
Jed Brown,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.