Wie man mit Funktionszeigern in Fortran in wissenschaftlichen Programmen arbeitet


11

Hier ist eine typische Verwendung von Funktionszeigern in C. Ich möchte etwas Ähnliches in Fortran tun. Ich habe einige Ideen, aber ich würde gerne wissen, ob es einen kanonischen Weg gibt, dies zu tun.

Die vom Benutzer übergebenen Funktionszeiger und Kontexte werden gespeichert und später aufgerufen.

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

Die Funktion des Benutzers wird zu verschiedenen späteren Zeitpunkten unter Verwendung seines Kontexts zurückgerufen.

In PETSc verwenden sie auch stark String -> Funktionszeigertabellen. Alles ist ein Plugin, so dass der Benutzer seine eigenen Implementierungen registrieren kann und sie erstklassig sind.

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

Dadurch wird die Erstellungsroutine in einer "FList" registriert, und PCSetFromOptions () bietet die Möglichkeit, diese Methode im Vergleich zu anderen Optionen auszuwählen. Wenn das System das dynamische Laden unterstützt, können Sie die Abhängigkeit zur Kompilierungszeit vom PCCreate_GAMG-Symbol überspringen und einfach NULL übergeben. Anschließend wird das Symbol zur Laufzeit in der gemeinsam genutzten Bibliothek nachgeschlagen.

Beachten Sie, dass dies ein Schritt über eine "Fabrik" hinaus eine Umkehrung des Steuergeräts ist, ähnlich dem, was Martin Fowler "Service Locator" nennt.

Hinweis: Dies kam in meiner privaten Korrespondenz mit Jed Brown zum Ausdruck, in der er mir diese Frage stellte. Ich beschloss, es auszulagern und zu sehen, welche Antworten die Leute finden können.

Antworten:


5

Es ist nicht erforderlich, die Übertragung zum Emulieren void *in einem modernen Fortran-Code zu verwenden. Verwenden Sie stattdessen einfach das intrinsische Modul ISO_C_BINDING , das von allen gängigen Fortran-Compilern unterstützt wird. Dieses Modul macht es sehr einfach, eine Schnittstelle zwischen Fortran und C herzustellen, mit einigen sehr kleinen Einschränkungen. Mit den Funktionen C_LOCund C_FUNLOCkönnen C-Zeiger auf Fortran-Daten bzw. -Prozeduren abgerufen werden.

In Bezug auf das obige PETSC-Beispiel gehe ich davon aus, dass der Kontext normalerweise ein Zeiger auf eine benutzerdefinierte Struktur ist, die einem abgeleiteten Datentyp in Fortran entspricht. Das sollte kein Problem sein C_LOC. Das undurchsichtige TSIFunction-Handle ist ebenfalls sehr einfach zu handhaben: Verwenden Sie einfach den Datentyp ISO_C_BINDING c_ptr, der void *in C entspricht . Eine in Fortran geschriebene Bibliothek kann verwendet werden, c_ptrwenn die strenge Typprüfung des modernen Fortran umgangen werden muss.


Brian, ja, in der Zwischenzeit haben Jed und ich einige Lösungen für den Rückruf gefunden, siehe hier: fortran90.org/src/best-practices.html#type-casting-in-callbacks , der Typ (c_ptr) ist die Abschnittsnummer V.
Ondřej Čertík

9

Ich vermute, dass Ihre Frage eine PETSc-spezifische Sprache enthält (mit der ich nicht vertraut bin), daher kann es hier zu Falten kommen, die ich nicht ganz verstehe, aber vielleicht ist dies dennoch hilfreich, um Sie zu erreichen gestartet.

Grundsätzlich müssen Sie die Schnittstelle für die Prozedur definieren und dann einen Zeiger auf eine Funktion übergeben, die dieser Schnittstelle folgt. Der folgende Code zeigt ein Beispiel. Erstens gibt es ein Modul, das die Schnittstelle definiert und ein kurzes Beispiel für einen Codeabschnitt zeigt, der die vom Benutzer bereitgestellte Routine ausführt, die dieser Schnittstelle folgt. Als nächstes folgt ein Programm, das zeigt, wie der Benutzer dieses Modul verwenden und die auszuführende Funktion definieren würde.

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

Danke für die Antwort. Das obige PETSc-Beispiel speichert den Funktionszeiger auch in einer internen Datenstruktur, aber ich denke, dass es ziemlich trivial ist, PROCEDURE(function_template), POINTER :: funcintern zu speichern .
Ondřej Čertík

Beachten Sie, dass der Zeiger eher ein undurchsichtiges Objekt als die Adresse dieses Codes ist, sodass AFAIK keine Interoperabilität mit C aufweisen kann. In PETSc müssen wir für diese Dinge Tabellen mit Funktionszeigern für C-Wrapper verwalten.
Matt Knepley

Das PETSc-Beispiel speichert sowohl den Funktionszeiger als auch den Kontext (private Benutzerdaten, die beim Aufruf der Funktion zurückgegeben werden). Der Kontext ist wirklich entscheidend, sonst macht der Benutzer schreckliche Dinge wie das Referenzieren von Globals. Da es kein Äquivalent zu gibt void*, muss der Benutzer selbst Schnittstellen für Bibliotheksfunktionen schreiben. Wenn Sie die Bibliothek in C implementieren, reicht dies aus. Wenn Sie jedoch in Fortran implementieren, müssen Sie sicherstellen, dass der Compiler die "Dummy" -SCHNITTSTELLE der Bibliothek niemals gleichzeitig mit der SCHNITTSTELLE des Benutzers sieht.
Jed Brown

2
Das Äquivalent von void*in Fortran ist eine transferMethode. Sehen Sie hier für ein Beispiel der Nutzung. Die anderen 3 Ansätze neben der transferMethode sind "Arbeitsarrays", "spezifischer abgeleiteter Typ anstelle von void *" und verwenden modullokale Modulvariablen.
Ondřej Čertík

Es ist eine Schande, dass der Benutzer mit transfereinem Unsinnstyp ( character (len=1), allocatable) herumspielen muss, um die Funktion aufzurufen.
Jed Brown
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.