Ich möchte f2py
mit modernen Fortran verwenden. Insbesondere versuche ich, das folgende grundlegende Beispiel zum Laufen zu bringen. Dies ist das kleinste nützliche Beispiel, das ich generieren konnte.
! alloc_test.f90
subroutine f(x, z)
implicit none
! Argument Declarations !
real*8, intent(in) :: x(:)
real*8, intent(out) :: z(:)
! Variable Declarations !
real*8, allocatable :: y(:)
integer :: n
! Variable Initializations !
n = size(x)
allocate(y(n))
! Statements !
y(:) = 1.0
z = x + y
deallocate(y)
return
end subroutine f
Beachten Sie, dass dies n
aus der Form des Eingabeparameters abgeleitet wird x
. Beachten Sie, dass dies y
im Hauptteil der Unterroutine zugewiesen und freigegeben wird.
Wenn ich das mit kompiliere f2py
f2py -c alloc_test.f90 -m alloc
Und dann in Python ausführen
from alloc import f
from numpy import ones
x = ones(5)
print f(x)
Ich erhalte den folgenden Fehler
ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but got (-1,)
Also erstelle und bearbeite ich die pyf
Datei manuell
f2py -h alloc_test.pyf -m alloc alloc_test.f90
Original
python module alloc ! in
interface ! in :alloc
subroutine f(x,z) ! in :alloc:alloc_test.f90
real*8 dimension(:),intent(in) :: x
real*8 dimension(:),intent(out) :: z
end subroutine f
end interface
end python module alloc
Geändert
python module alloc ! in
interface ! in :alloc
subroutine f(x,z,n) ! in :alloc:alloc_test.f90
integer, intent(in) :: n
real*8 dimension(n),intent(in) :: x
real*8 dimension(n),intent(out) :: z
end subroutine f
end interface
end python module alloc
Jetzt läuft es aber die Werte der Ausgabe z
sind immer 0
. Bei einigen Debug-Druckvorgängen wird angezeigt, dass n
der Wert 0
innerhalb der Unterroutine liegt f
. Ich gehe davon aus, dass mir etwas Kopfzauber fehlt f2py
, um diese Situation richtig zu handhaben.
Allgemeiner gesagt, was ist der beste Weg, um die obige Subroutine mit Python zu verknüpfen? Ich würde es nachdrücklich vorziehen, das Unterprogramm selbst nicht ändern zu müssen.
Antworten:
Ich bin nicht besonders vertraut mit f2py-Interna, aber ich bin sehr vertraut damit, Fortran zu verpacken. F2py automatisiert nur einige oder alle der folgenden Dinge.
Sie müssen zuerst mit dem Modul iso_c_binding nach C exportieren, wie hier beschrieben:
http://fortran90.org/src/best-practices.html#interfacing-with-c
Haftungsausschluss: Ich bin der Hauptautor der fortran90.org-Seiten. Dies ist die einzige plattform- und compilerunabhängige Methode, um Fortran von C aus aufzurufen. Dies ist F2003. Heutzutage gibt es keinen Grund, eine andere Methode zu verwenden.
Sie können nur Arrays mit der angegebenen vollständigen Länge (explizite Form) exportieren / aufrufen, dh:
aber keine Form annehmen:
Dies liegt daran, dass die C-Sprache solche Arrays selbst nicht unterstützt. Es ist die Rede, eine solche Unterstützung in F2008 oder höher aufzunehmen (ich bin nicht sicher), und die Art und Weise, wie dies funktionieren würde, ist durch einige unterstützende C-Datenstrukturen, da Sie Forminformationen über das Array tragen müssen.
In Fortran sollten Sie hauptsächlich die Form annehmen, nur in besonderen Fällen sollten Sie die explizite Form verwenden, wie hier beschrieben:
http://fortran90.org/src/best-practices.html#arrays
Das bedeutet, dass Sie einen einfachen Wrapper um Ihre Subroutine "Form annehmen" schreiben müssen, der die Dinge in explizite Form-Arrays einschließt, wie in meinem ersten Link oben angegeben.
Sobald Sie eine C-Signatur haben, rufen Sie sie einfach in Python auf, wie Sie möchten. Ich verwende Cython, aber Sie können ctype oder C / API von Hand verwenden.
Die
deallocate(y)
Anweisung wird nicht benötigt, Fortran wird automatisch freigegeben.http://fortran90.org/src/best-practices.html#allocatable-arrays
real*8
sollte nicht verwendet werden, sondernreal(dp)
:http://fortran90.org/src/best-practices.html#floating-point-numbers
Die Anweisung
y(:) = 1.0
weist 1,0 mit einfacher Genauigkeit zu, der Rest der Ziffern ist also zufällig! Dies ist eine häufige Falle:http://fortran90.org/src/gotchas.html#floating-point-numbers
Sie müssen verwenden
y(:) = 1.0_dp
.Anstatt zu schreiben
y(:) = 1.0_dp
, können Sie einfach schreibeny = 1
, das war's. Sie können einer Gleitkommazahl eine Ganzzahl zuweisen, ohne an Genauigkeit zu verlieren, und Sie müssen die Redundanz nicht(:)
dort einfügen . Viel einfacherAnstatt
benutz einfach
und kümmere dich überhaupt nicht um das
y
Array.Sie brauchen die "return" -Anweisung am Ende des Unterprogramms nicht.
Schließlich sollten Sie wahrscheinlich Module verwenden und nur
implicit none
die Modulebene aufrufen, und Sie müssen sie nicht in jeder Unterroutine wiederholen.Ansonsten sieht es für mich gut aus. Hier ist der Code, der den obigen Vorschlägen 1-10 folgt:
Es zeigt das vereinfachte Unterprogramm sowie einen C-Wrapper.
In Bezug auf f2py wird wahrscheinlich versucht, diesen Wrapper für Sie zu schreiben, und dies schlägt fehl. Ich bin mir auch nicht sicher, ob es das
iso_c_binding
Modul verwendet. Aus all diesen Gründen bevorzuge ich es, Dinge von Hand zu wickeln. Dann ist genau klar, was passiert.quelle
y
aber ich wollte, dass etwas zugewiesen wurde (mein aktueller Code weist nicht-triviale Zuweisungen auf). Über viele der anderen Punkte wusste ich allerdings nichts. Es sieht so aus, als sollte ich mir eine Art Fortran90-Leitfaden für bewährte Methoden ansehen. Vielen Dank für die gründliche Antwort!Sie müssen lediglich Folgendes tun:
Obwohl die Größe von Array x und z jetzt als explizites Argument übergeben wird, macht f2py das Argument n optional. Im Folgenden sehen Sie die Dokumentzeichenfolge der Funktion, wie sie für Python angezeigt wird:
Importieren und Aufrufen von Python:
gibt die folgende Ausgabe aus:
quelle
n
und möchte ein Array von Größe erhalten2 ** n
. Bisher muss ich auch 2 ** n als separates Argument übergeben.