Jak pracować ze wskaźnikami funkcji w Fortranie w programach naukowych

11

Oto typowe użycie wskaźników funkcji w C. Chciałbym zrobić coś podobnego w Fortranie. Mam kilka pomysłów, ale chciałbym wiedzieć, czy istnieje jakiś kanoniczny sposób.

Wskaźniki funkcji i konteksty przekazane przez użytkownika są przechowywane, a następnie wywoływane później.

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

Funkcja użytkownika jest wywoływana za pomocą kontekstu w późniejszym czasie.

W PETSc często wykorzystują tabele wskaźników string -> function. Wszystko jest wtyczką, więc użytkownik może zarejestrować własne implementacje i są one najwyższej klasy.

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

To rejestruje procedurę tworzenia w „FList”, a następnie PCSetFromOptions () oferuje możliwość wyboru tej metody w porównaniu z innymi opcjami. Jeśli system obsługuje ładowanie dynamiczne, możesz pominąć zależność czasu kompilacji od symbolu PCCreate_GAMG i po prostu przekazać NULL, wówczas symbol zostanie wyszukany w udostępnionej bibliotece w czasie wykonywania.

Zauważ, że ten krok poza „fabryką” jest odwróceniem urządzenia sterującego podobnego do tego, co Martin Fowler nazywa „lokalizatorem usług”.

Uwaga: pojawiło się to w mojej prywatnej korespondencji z Jedem Brownem, gdzie zadał mi to pytanie. Postanowiłem to zlecić na zewnątrz i zobaczyć, jakie odpowiedzi ludzie mogą wymyślić.

Ondřej Čertík
źródło

Odpowiedzi:

5

Nie ma potrzeby używania transferu do emulacji void *w nowoczesnym kodzie Fortran. Zamiast tego wystarczy użyć wewnętrznego modułu ISO_C_BINDING , który jest obsługiwany przez wszystkie główne kompilatory Fortran. Ten moduł bardzo ułatwia interfejs między Fortranem i C, z kilkoma bardzo drobnymi zastrzeżeniami. Można użyć funkcji C_LOCi, C_FUNLOCaby uzyskać wskaźniki C odpowiednio do danych i procedur Fortrana.

W odniesieniu do powyższego przykładu PETSC zakładam, że kontekst jest zwykle wskaźnikiem do struktury zdefiniowanej przez użytkownika, która jest równoważna pochodnemu typowi danych w Fortranie. Nie powinno to stanowić problemu przy używaniu C_LOC. Z nieprzezroczystym uchwytem TSIFction można również bardzo łatwo sobie poradzić: wystarczy użyć typu danych ISO_C_BINDING c_ptr, który jest równoważny z literą void *C. Biblioteka napisana w języku Fortran może użyć, c_ptrjeśli musi obejść ścisłe sprawdzanie typu nowoczesnego Fortrana.

Brian
źródło
Brian, tak, w międzyczasie wymyśliliśmy z Jedem sporo rozwiązań dotyczących oddzwaniania, patrz tutaj: fortran90.org/src/best-practices.html#type-casting-in-callbacks , typ (c_ptr) to numer sekcji V.
Ondřej Čertík
9

Wydaje mi się, że w twoim pytaniu jest wiele języka specyficznego dla PETSc (z którym nie jestem zaznajomiony), więc może pojawić się zmarszczka, której nie do końca rozumiem, ale może nadal będzie to przydatne Rozpoczęty.

Zasadniczo musisz zdefiniować interfejs dla procedury, a następnie możesz przekazać wskaźnik do funkcji następującej po tym interfejsie. Poniższy kod pokazuje przykład. Po pierwsze, istnieje moduł, który definiuje interfejs i pokazuje szybki przykład fragmentu kodu, który wykonałby procedurę udostępnioną przez użytkownika korzystającego z tego interfejsu. Dalej jest program, który pokazuje, w jaki sposób użytkownik użyłby tego modułu i zdefiniował funkcję do wykonania.

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
źródło
Dziękuję za odpowiedź. Powyższy przykład PETSc przechowuje również wskaźnik funkcji w jakiejś wewnętrznej strukturze danych, ale myślę, że zapisywanie PROCEDURE(function_template), POINTER :: funcwewnętrzne jest dość trywialne .
Ondřej Čertík,
Zauważ, że wskaźnik jest nieprzezroczystym obiektem, a nie adresem tego kodu, więc AFAIK nie może być interoperacyjny z C. W PETSc musimy utrzymywać tabele wskaźników funkcji dla opakowań C dla tych rzeczy.
Matt Knepley,
Przykład PETSc przechowuje zarówno wskaźnik funkcji, jak i kontekst (prywatne dane użytkownika, które są przekazywane z powrotem, gdy funkcja jest wywoływana). Kontekst jest naprawdę kluczowy, w przeciwnym razie użytkownik kończy okropne rzeczy, takie jak odwoływanie się do globali. Ponieważ nie ma odpowiednika void*, użytkownik musi sam napisać interfejsy dla funkcji bibliotecznych. Jeśli zaimplementujesz bibliotekę w C, to wystarczy, ale jeśli zaimplementujesz w Fortran, musisz upewnić się, że kompilator nigdy nie zobaczy „fałszywego” INTERFEJSU biblioteki w tym samym czasie, co INTERFEJS użytkownika.
Jed Brown,
2
Odpowiednikiem void*w Fortran jest transfermetoda. Zobacz tutaj przykład użycia. Pozostałe 3 podejścia oprócz transfermetody to „tablice robocze”, „specyficzny typ pochodny zamiast void *” i użycie zmiennych modułu lokalnych dla modułu.
Ondřej Čertík,
Szkoda, że ​​użytkownik musi zmarnować transferjakiś nonsensowny typ ( character (len=1), allocatable) tylko po to, by wywołać tę funkcję.
Jed Brown,