Zugriffsreihenfolge mehrdimensionaler Arrays beim Aufrufen von Fortran-Subroutinen aus Python (ctypeslib und f2py)

Einführung

In Python möchten Sie möglicherweise eine komplizierte Verarbeitung durchführen, indem Sie eine for-Anweisung für ein mehrdimensionales Array aktivieren (Daten werden häufig durch ein mehrdimensionales Array in der Atmosphäre und im Ozeanfeld dargestellt). In diesem Fall ist die Ausführungszeit jedoch häufig lang Sie könnten in Schwierigkeiten sein. Es gibt verschiedene Beschleunigungsmethoden, um dies zu lösen. Eine Lösung besteht jedoch darin, eine Unterroutine in Fortran zu schreiben, die häufig in der Flüssigkeitsberechnung verwendet wird, und sie von Python aus aufzurufen. Es gibt zwei Möglichkeiten, eine Fortran-Subroutine aus Python aufzurufen: Verwenden von numpy.ctypeslib und Verwenden von f2py. Die Zugriffsreihenfolge des mehrdimensionalen Arrays unterscheidet sich zwischen den beiden, aber ich konnte die auf Japanisch zusammengefasste Seite nicht finden, also habe ich einen Artikel geschrieben.

Die Ausführungsumgebung ist wie folgt.

Unterschiede im Umgang mit mehrdimensionalen Arrays zwischen Python und Fortran

Es ist eine grundlegende Geschichte. Wenn Sie sie kennen, können Sie diesen Abschnitt überspringen.

Obwohl die Programmiersprache mehrdimensionale Arrays verarbeitet, sind sie im Speicher nebeneinander in eindimensionaler Richtung angeordnet. Zu diesem Zeitpunkt hängt es von der Sprache ab, wie es gespeichert wird. Im folgenden Code wird der Wert 0 in der Reihenfolge in die Elemente des Arrays geschrieben, in der sie gespeichert werden.

loop.py


import numpy as np

nk = 2
nj = 3
ni = 4
array = np.empty((nk, nj, ni), dtype=np.float32, order="C")
for k in range(nk):
    for j in range(nj):
        for i in range(ni):
            array[k, j, i] = 0    

loop.f90


integer(4), parameter :: nk = 2, nj = 3, ni = 4
real(4), dimension(ni, nj, nk) :: array

do k = 1, nk
    do j = 1, nj
        do i = 1, ni
            array(i, j, k) = 0
        end do
    end do
end do

Der Unterschied besteht darin, dass in Python (um genau zu sein, im Fall von "order =" C "im numpy-Array) die Reihenfolge" [k, j, i] "und in Fortran" (i, j, k) "lautet. (Wird manchmal als Zeilenpriorität bzw. Spaltenpriorität bezeichnet.) Ob auf sie in der Reihenfolge zugegriffen wird, in der sie im Speicher gespeichert sind, oder auf diskrete Weise, wirkt sich auf die Ausführungszeit aus, insbesondere wenn die Größe des Arrays groß ist.

Bei Verwendung von numpy.ctypeslib

Schreiben wir als Beispiel einen Code, der ein zweidimensionales Array von numpy an Fortran übergibt und ein Doppel davon zurückgibt (dasselbe gilt für drei oder mehr Dimensionen). Die Details des Codes werden hier nicht erläutert. Wenn Sie also beispielsweise Verknüpfung mit Fortran, C-Sprache kennen möchten % E7% B7% A8 / Fortran,% 20C% E8% A8% 80% E8% AA% 9E% 20% E3% 81% A8% E3% 81% AE% E9% 80% A3% E6% 90% BA. Siehe andere Artikel wie HTML).

doubling1.py


from ctypes import POINTER, c_int32, c_void_p
import numpy as np

libDoubling = np.ctypeslib.load_library("doubling1.so", ".")
libDoubling.doubling.argtypes = [
    np.ctypeslib.ndpointer(dtype=np.float32),
    np.ctypeslib.ndpointer(dtype=np.float32),
    POINTER(c_int32),
    POINTER(c_int32),
]
libDoubling.doubling.restype = c_void_p

nj = 2
ni = 3

vin = np.empty((nj, ni), dtype=np.float32, order="C")
vin[0, 0] = 1
vin[0, 1] = 2
vin[0, 2] = 3
vin[1, 0] = 4
vin[1, 1] = 5
vin[1, 2] = 6

vout = np.empty((nj, ni), dtype=np.float32, order="C")

_nj = POINTER((c_int32))(c_int32(nj))
_ni = POINTER((c_int32))(c_int32(ni))

libDoubling.doubling(vin, vout, _ni, _nj)

print(vin)
# [[1. 2. 3.]
#  [4. 5. 6.]]
print(vout)
# [[ 2.  4.  6.]
#  [ 8. 10. 12.]]

doubling1.f90


! gfortran -Wall -cpp -fPIC -shared doubling1.f90 -o doubling1.so
subroutine doubling(vin, vout, ni, nj) bind(C)
    use, intrinsic :: ISO_C_BINDING
    implicit none
    integer(c_int32_t), intent(in) :: ni, nj
    real(c_float), dimension(ni, nj), intent(in) :: vin
    real(c_float), dimension(ni, nj), intent(out) :: vout
    integer(c_int32_t) :: i, j

    do j = 1, nj
        do i = 1, ni
            vout(i, j) = vin(i, j) * 2
        end do
    end do
end subroutine doubling

Ein Zeiger wird an das Argument der Fortran-Subroutine (Shared Library) übergeben. In Python und Fortran sind die Indizes das Gegenteil von "[j, i]" und "(i, j)", aber es sollte einfach sein, wenn Sie die Reihenfolge berücksichtigen, in der die Daten im Speicher gespeichert werden. ..

Bei Verwendung von f2py

Verwenden Sie als Nächstes f2py und schreiben Sie denselben Code wie zuvor. Ich werde auch nicht auf Details eingehen, aber ich denke, Sie können auf Artikel wie Umgang mit Numpy-Arrays mit f2py verweisen.

doubling2.f90


! f2py -c --fcompiler=gfortran -m doubling2 doubling2.f90
subroutine doubling2(vin, vout, ni, nj)
    implicit none
    integer(4), intent(in) :: ni, nj
    real(4), dimension(nj, ni), intent(in) :: vin
    real(4), dimension(nj, ni), intent(out) :: vout
    integer(4) :: i, j

    do j = 1, nj
        do i = 1, ni
            vout(j, i) = vin(j, i) * 2
        end do
    end do
end subroutine doubling2

doubling2.py


import numpy as np
import doubling2

nj = 2
ni = 3
vin = np.empty((nj, ni), dtype=np.float32, order="C")
vin[0, 0] = 1
vin[0, 1] = 2
vin[0, 2] = 3
vin[1, 0] = 4
vin[1, 1] = 5
vin[1, 2] = 6

vout = np.empty((nj, ni), dtype=np.float32, order="C")

vout = doubling2.doubling2(vin, ni, nj)
print(vin)
# [[1. 2. 3.]
#  [4. 5. 6.]]
print(vout)
# [[ 2.  4.  6.]
#  [ 8. 10. 12.]]

Wenn Sie f2py verwenden, können Sie in Python einfach "[j, i]" als "(j, i)" in Fortran verwenden. Es hat den Vorteil, dass der Code gleich aussieht, aber die do-Anweisung in doubling2.f90 hat den Nachteil, dass die Zugriffsreihenfolge des Arrays nicht der Datenspeicherreihenfolge im Speicher entspricht.

Wie kann ich das lösen? Mit anderen Worten, ich möchte auf den Code auf der Fortran-Seite in derselben Reihenfolge wie doubling1.f90 zugreifen.

doubling3.f90


! f2py -c --fcompiler=gfortran -m doubling3 doubling3.f90
subroutine doubling3(vin, vout, ni, nj)
    implicit none
    integer(4), intent(in) :: ni, nj
    real(4), dimension(ni, nj), intent(in) :: vin
    real(4), dimension(ni, nj), intent(out) :: vout
    integer(4) :: i, j

    do j = 1, nj
        do i = 1, ni
            vout(i, j) = vin(i, j) * 2
        end do
    end do
end subroutine doubling3

Sie könnten denken, dass Sie die Speicherreihenfolge im Speicher umkehren können, indem Sie das Argument "order" des numpy-Arrays auf "F" anstelle des Standardarguments "C" setzen. Dies löst dieses Problem jedoch nicht. Das heißt, selbst wenn Sie order =" C " inorder = "F"in doubling2.py ändern, lautet der entsprechende Fortran-Code immer noch doubling2.f90, und die Verwendung von doubling3.f90 führt zu einem Fehler. ist.

Die Lösung besteht darin, das Array mithilfe des Attributs T zu transponieren, wie in f2py - Array-Neuordnung verhindern - Stapelüberlauf beschrieben. Das ist.

doubling3.py


import numpy as np
import doubling3

nj = 2
ni = 3
vin = np.empty((nj, ni), dtype=np.float32, order="C")
vin[0, 0] = 1
vin[0, 1] = 2
vin[0, 2] = 3
vin[1, 0] = 4
vin[1, 1] = 5
vin[1, 2] = 6

_vout = np.empty((nj, ni), dtype=np.float32, order="C")

_vout = doubling3.doubling3(vin.T, ni, nj)
vout = _vout.T
print(vin)
# [[1. 2. 3.]
#  [4. 5. 6.]]
print(vout)
# [[ 2.  4.  6.]
#  [ 8. 10. 12.]]

Wenn Sie beispielsweise GPV mithilfe der netCDF4-Bibliothek lesen möchten, diese effizient mit einer Fortran-Subroutine verarbeiten und dann mit matplotlib + cartopy zeichnen möchten, wird diese Methode empfohlen. Es ist mühsam, umzuziehen, um "Osetsusuke" zu vermeiden, aber selbst wenn es enthalten ist, ist der Umfang der Beschreibung im Vergleich zur Verwendung von numpy.ctypeslib gering, und ich denke, dass f2py einfacher zu verwenden ist.

Recommended Posts

Zugriffsreihenfolge mehrdimensionaler Arrays beim Aufrufen von Fortran-Subroutinen aus Python (ctypeslib und f2py)
Vorsichtsmaßnahmen und Fehlerbehandlung beim Aufrufen der .NET-DLL aus Python mit Pythonnet
Ein Memorandum zum Aufrufen von Python aus Common Lisp
Angeben des Bereichs von Ruby- und Python-Arrays
[Python] Vorsichtsmaßnahmen beim Zuweisen von Werten zu mehrdimensionalen Arrays
E / A-bezogene Zusammenfassung von Python und Fortan
So erreichen Sie den Zugriff durch Attribut und Beibehaltung der Einfügereihenfolge in Python dict