Mamy XXI wiek, kompilator gcc i tak każdy kod tłumaczy na C, nieważne w czym piszemy: Fortran, Python.... Czyli numeryczna biblioteka, powiedzmy relatywistyczne całki Fermiego-Diraca powinna być gotowa do natychmiastowego użytku we Fortranie. Do takiego wniosku niespodziewanie dotarłem po 10 dniach głowienia się, jak napisać interface umożliwiający użytkowanie powyższej biblioteki w FORTRANIE. Jeżeli kogoś interesuje TYLKO samo rozwiązanie, to jest na końcu poniższych wywodów.

Plan postanowiłem natychmiastowo wcielić w życie. Niestety, bardzo naiwny pomysł, aby literalnie użyć funkcji z C w kodzie Fortrana 90

f = ffermi(-0.5d0, 0.4d0, 0.5d0)

nie pozwolił skompilować kodu. Wyskoczył błąd kompilacji:
Error: Function ‘ffermi’ at (1) has no IMPLICIT type;
Prosty trick oszukuje kompilator:
real*8, external :: ffermi
ale tym razem błąd generuje linker: /usr/bin/ld: test_fermi_dirac.f90:(.text+0x441e): undefined reference to `ffermi_'
Z dawnych czasów pamiętałem jeszcze, że Fortran przy linkowaniu dodaje podkreślnik _ na końcu nazwy funkcji/procedury, dlatego funkcji z C nie da się bezpośrednio użyć. Należy zmienić jej nazwę, w tym wypadku na ffermi_ . Rozwiązanie idiotyczne, na szczęście gfortran posiada modyfikator -fno-underscoring , który wyłącza powyższe zachowanie:
gfortran -fno-underscoring test_fermi_dirac.f90 -o test
No to teraz wystarczy dać namiary na bibliotekę zawierającą skompilowaną funkcję ffermi ... nie tak szybko. Żadne z poniższych komend nie produkują pliku wykonywalnego:
gfortran -fno-underscoring test_fermi_dirac.f90 -o test libfermidirac.a
gfortran -fno-underscoring test_fermi_dirac.f90 -o test -lfermidirac

Ani linkowanie statyczne, ani dynamiczne nie działa. Funkcja nie jest rozpoznana.

Ponieważ wszystkie inne sposoby zawiodły, przyszedł czas na przeczytanie instrukcji. Niestety, FORTRAN ma długą historię, wiele wersji, a tutoriali o różnym stopniu aktualności jest pełno. Po wypróbowaniu kilku udało się ostatecznie skompilować kod, z użyciem zarówno statycznego jak i dynamicznego linkowania, dodając interface postaci:

INTERFACE
REAL(C_DOUBLE) FUNCTION ofermi(k, eta, theta) BIND(C, name='Ffermi')
USE ISO_C_BINDING
IMPLICIT NONE
REAL(C_DOUBLE) :: k, eta, theta
END FUNCTION ofermi
END INTERFACE

Wszystko byłoby pięknie, gdyby nie to, że tak opakowana funkcja w najlepszym wypadku zwracała losowo wyglądające liczby, w najgorszym NaN-y lub Infinity. W rzeczywistości liczby te nie były wcale losowe. Konkretnie jedna z nich przykuła moją uwagę 0.69314718055994518. Jeżeli ktoś nie rozpoznaje jej na pierwszy rzut oka, to polecam pakiet do rozpoznawania stałych w Mathematice mojego autorstwa


Import["https://raw.githubusercontent.com/VA00/SymbolicRegressionPackage/master/SymbolicRegression.m"]
RecognizeConstant[0.69314718055994518]



Ten trop okazał się niestety ślepy, aczkolwiek dał pewną wskazówkę, jako że powyższa liczba to ffermi(0.0,0.0,0.0) . W jakiś sposób argumenty funkcji były albo równe zeru, albo bardzo bliskie. Biorąc pod uwagę, że pomylenie typu zmiennej, powiedzmy float vs double, może mieć katastrofalne skutki, ale nie tego gatunku, doszedłem do konkluzji, że pomylony jest adres zmiennej (wskaźnik do niej) z jej wartością. I to był strzał w dziesiątkę. Prawidłowy interface musi dodatkowo poinformować, że biblioteka oczekuje WARTOŚCI argumentów, a nie ich położenia w pamięci. Tymczasem domyślne zachowanie jest odwrotne, i musi być zmienione słowem kluczowym VALUE

: INTERFACE
REAL(C_DOUBLE) FUNCTION ffermi(k, eta, theta) BIND(C, name='Ffermi')
USE ISO_C_BINDING
IMPLICIT NONE
REAL(C_DOUBLE), VALUE :: k, eta, theta
END FUNCTION ffermi
END INTERFACE

Wniosek jest następujący: faktycznie, w Fortranie żadne dodatkowe zabiegi nie są potrzebne, aby użyć skompilowanej funkcji bibliotecznej. Należy natomiast bardzo dokładnie wyspecyfikować nie tylko typy argumentów, ale także to, czy są to ich wartości czy wskaźniki do nich. Domyślnie są to wskaźniki.