Cara bekerja dengan pointer fungsi di Fortran dalam program ilmiah

11

Berikut ini adalah penggunaan khas pointer fungsi di C. Saya ingin melakukan sesuatu yang serupa di Fortran. Saya punya beberapa ide, tetapi saya ingin tahu apakah ada cara kanonik untuk melakukannya.

Pointer fungsi dan konteks yang dilewatkan oleh pengguna disimpan, lalu dipanggil nanti.

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

Fungsi pengguna dipanggil kembali menggunakan konteks mereka di berbagai waktu kemudian.

Dalam PETSc, mereka juga menggunakan string -> tabel pointer fungsi. Semuanya adalah plugin, sehingga pengguna dapat mendaftarkan implementasi mereka sendiri dan mereka adalah kelas satu.

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

Ini mendaftarkan kreasi rutin dalam "FList", lalu PCSetFromOptions () menawarkan kemampuan untuk memilih metode ini dibandingkan pilihan lainnya. Jika sistem mendukung pemuatan dinamis, Anda dapat melewati dependensi waktu kompilasi pada simbol PCCreate_GAMG dan hanya melewati NULL, maka simbol tersebut akan dicari di perpustakaan bersama saat run time.

Perhatikan bahwa ini satu langkah di luar "pabrik", itu adalah inversi perangkat kontrol yang mirip dengan apa yang Martin Fowler sebut "pelacak layanan".

Catatan: ini muncul dalam korespondensi pribadi saya dengan Jed Brown, di mana ia mengajukan pertanyaan ini kepada saya. Saya memutuskan untuk melakukan outsourcing dan melihat jawaban apa yang bisa diberikan orang.

Ondřej Čertík
sumber

Jawaban:

5

Tidak perlu menggunakan transfer untuk ditiru void *dalam kode Fortran modern. Sebagai gantinya, cukup gunakan modul intrinsik ISO_C_BINDING , yang didukung oleh semua kompiler Fortran utama. Modul ini membuatnya sangat mudah untuk antarmuka antara Fortran dan C, dengan beberapa peringatan yang sangat kecil. Satu dapat menggunakan C_LOCdan C_FUNLOCfungsi untuk mendapatkan pointer C ke Fortran data dan prosedur, masing-masing.

Berkenaan dengan contoh PETSC di atas, saya menganggap konteksnya biasanya pointer ke struktur yang ditentukan pengguna, yang setara dengan tipe data yang diturunkan di Fortran. Itu seharusnya tidak menjadi masalah untuk digunakan C_LOC. Pegangan TSIFunction yang buram juga sangat mudah untuk ditangani: cukup gunakan tipe data ISO_C_BINDING c_ptr, yang setara dengan void *dalam C. Perpustakaan yang ditulis dalam Fortran dapat digunakan c_ptrjika perlu menangani pemeriksaan ketat Fortran yang modern.

Brian
sumber
Brian, ya, sementara itu, Jed dan saya telah menemukan beberapa solusi untuk callback, lihat di sini: fortran90.org/src/best-practices.html#type-casting-in-callbacks , tipe (c_ptr) adalah nomor bagian V.
Ondřej Čertík
9

Ada banyak hal yang saya duga adalah bahasa khusus PETSc dalam pertanyaan Anda (yang saya tidak kenal), jadi mungkin ada kerutan di sini yang saya tidak mengerti, tapi mungkin ini masih berguna untuk membuat Anda mulai.

Pada dasarnya, Anda harus mendefinisikan antarmuka untuk prosedur, dan kemudian Anda bisa meneruskan pointer ke fungsi yang mengikuti antarmuka ini. Kode berikut menunjukkan contoh. Pertama, ada modul yang mendefinisikan antarmuka dan menunjukkan contoh cepat dari sepotong kode yang akan menjalankan rutin yang disediakan oleh pengguna yang mengikuti antarmuka itu. Berikutnya adalah program yang menunjukkan bagaimana pengguna akan menggunakan modul ini dan menentukan fungsi yang akan dieksekusi.

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
sumber
Terima kasih atas jawabannya. Contoh PETSc di atas juga menyimpan fungsi pointer di beberapa struktur data internal, tapi saya pikir itu cukup sepele untuk menyimpan secara PROCEDURE(function_template), POINTER :: funcinternal.
Ondřej Čertík
Perhatikan bahwa pointer adalah objek buram daripada alamat kode itu, jadi AFAIK tidak dapat interoperabilitas dengan C. Dalam PETSc, kita harus memelihara tabel pointer fungsi untuk pembungkus C untuk hal-hal ini.
Matt Knepley
Contoh PETSc menyimpan pointer fungsi dan konteks (data pengguna pribadi yang dikembalikan ketika fungsi dipanggil). Konteksnya sangat penting, jika tidak, pengguna akhirnya melakukan hal-hal mengerikan seperti mereferensikan global. Karena tidak ada yang setara dengan void*, pengguna akhirnya harus menulis antarmuka untuk fungsi perpustakaan sendiri. Jika Anda menerapkan pustaka di C, ini sudah cukup, tetapi jika Anda menerapkan di Fortran, Anda harus memastikan bahwa kompiler tidak pernah melihat ANTARMUKA "dummy" pustaka pada saat yang sama dengan INTERFACE pengguna.
Jed Brown
2
Setara dengan void*di Fortran adalah transfermetode. Lihat di sini untuk contoh penggunaan. 3 pendekatan lain selain transfermetode ini adalah "work array", "tipe turunan spesifik, bukan void *" dan menggunakan variabel modul lokal ke modul.
Ondřej Čertík
Sayang sekali pengguna harus muck sekitar dengan transferjenis omong kosong ( character (len=1), allocatable) hanya untuk memanggil fungsi.
Jed Brown