Wie implementiere ich eine effiziente Indexierungsfunktion für zwei Partikelintegrale <ij | kl>?

11

Dies ist ein einfaches Problem der Symmetrieaufzählung. Ich gebe hier den vollständigen Hintergrund an, aber es sind keine Kenntnisse der Quantenchemie erforderlich.

Die beiden Teilchen integral ist: i j | k l = ψ * i ( x ) ψ * j ( x ' ) ψ k ( x ) ψ L ( x ' )ij|kl Und es folgende 4 Symmetrien hat: i j | k l = j i | l k = k l | i j = l k | j i I haben eine Funktiondass das Integral und speichert sie in einem 1DArray berechnet, wie folgt indexiert:

ij|kl=ψi(x)ψj(x)ψk(x)ψl(x)|xx|d3xd3x
ij|kl=ji|lk=kl|ij=lk|ji
int2
int2(ijkl2intindex2(i, j, k, l))

Dabei gibt die Funktion ijkl2intindex2einen eindeutigen Index zurück, wobei die obigen Symmetrien berücksichtigt werden. Die einzige Voraussetzung ist, dass, wenn Sie alle Kombinationen von i, j, k, l (von jeweils 1 bis n) int2durchlaufen , das Array nacheinander gefüllt wird und allen ijkl-Kombinationen, die sich auf die oben genannten beziehen, der gleiche Index zugewiesen wird 4 Symmetrien.

Meine aktuelle Implementierung in Fortran ist hier . Es ist sehr langsam. Weiß jemand, wie man das effektiv macht? (In jeder Sprache.)

ψi(x)ikjl

ij|kl=ji|lk=kj|il=il|kj=
=kl|ij=lk|ji=il|kj=kj|il

ijkl(ij|kl)=ik|jljk

Ondřej Čertík
quelle
d3x
1
d3xdx1dx2dx3x=(x1,x2,x3)d3x
xx=(x1,x2,x3)dx
d3x

Antworten:

5

[Edit: 4. Mal ist der Reiz, endlich etwas Vernünftiges]

nn2(n2+3)t(t(n))+t(t(n1))t(a)at(a)=a(a+1)/2

ijtid(i,j)tid(k,l)tid(a,b)a,b

def ascendings(n):
    idx = 0
    for i in range(1,n+1):
        for j in range(1,i+1):
            for k in range(1,i):
                for l in range(1,k+1):
                    idx = idx + 1
                    print(i,j,k,l)
            k=i
            for l in range(1,j+1):
                idx = idx + 1
                print(i,j,k,l)
    return idx

llk

t(t(n1))

def mixcendings(n):
    idx = 0
    for j in range(2,n+1):
        for i in range(1,j):
            for k in range(1,j):
                for l in range(1,k):
                    print(i,j,k,l)
                    idx = idx + 1
            k=j
            for l in range(1,i+1):
                print(i,j,k,l)
                idx = idx + 1
    return idx

Die Kombination dieser beiden ergibt den vollständigen Satz. Wenn Sie also beide Schleifen zusammenfügen, erhalten Sie den vollständigen Satz von Indizes.

n

In Python können wir den folgenden Iterator schreiben, um uns die Werte für idx und i, j, k, l für jedes Szenario zu geben:

def iterate_quad(n):
    idx = 0
    for i in range(1,n+1):
        for j in range(1,i+1):
            for k in range(1,i):
                for l in range(1,k+1):
                    idx = idx + 1
                    yield (idx,i,j,k,l)
                    #print(i,j,k,l)
            k=i
            for l in range(1,j+1):
                idx = idx + 1
                yield (idx,i,j,k,l)

    for i in range(2,n+1):
        for j in range(1,i):
            for k in range(1,i):
                for l in range(1,k):
                    idx = idx + 1
                    yield (idx,i,j,k,l)
            k=i
            for l in range(1,j+1):
                idx = idx + 1
                yield (idx,i,j,k,l)

in3+jn2+kn+l

integer function squareindex(i,j,k,l,n)
    integer,intent(in)::i,j,k,l,n
    squareindex = (((i-1)*n + (j-1))*n + (k-1))*n + l
end function

integer function generate_order_array(n,arr)
    integer,intent(in)::n,arr(*)
    integer::total,idx,i,j,k,l
    total = n**2 * (n**2 + 3)
    reshape(arr,total)
    idx = 0
    do i=1,n
      do j=1,i
        do k=1,i-1
          do l=1,k
            idx = idx+1
            arr(idx) = squareindex(i,j,k,l,n)
          end do
        end do
        k=i
        do l=1,j
          idx = idx+1
          arr(idx) = squareindex(i,j,k,l,n)
        end do
      end do
    end do

    do i=2,n
      do j=1,i-1
        do k=1,i-1
          do l=1,j
            idx = idx+1
            arr(idx) = squareindex(i,j,k,l,n)
          end do
        end do
        k=i
        do l=1,j
          idx = idx+1
          arr(idx) = squareindex(i,j,k,l,n)
        end do
      end do
    end do

    generate_order_array = idx
  end function

Und dann Schleife darüber:

maxidx = generate_order_array(n,arr)
do idx=1,maxidx
  i = idx/(n**3) + 1
  t_idx = idx - (i-1)*n**3
  j = t_idx/(n**2) + 1
  t_idx = t_idx - (j-1)*n**2
  k = t_idx/n + 1
  t_idx = t_idx - (k-1)*n
  l = t_idx

  ! now have i,j,k,l, so do stuff
  ! ...
end do
Phil H.
quelle
Hallo Phil, vielen Dank für die Antwort! Ich habe es getestet und es gibt zwei Probleme. Zum Beispiel idx_all (1, 2, 3, 4, 4) == idx_all (1, 2, 4, 3, 4) = 76. Aber <12 | 34> / = <12 | 43>. Es ist nur dann gleich, wenn die Orbitale real sind. Ihre Lösung scheint also für den Fall von 8 Symmetrien zu sein (siehe mein Fortran-Beispiel oben für eine einfachere Version, den ijkl2intindex ()). Das zweite Problem ist, dass die Indizes nicht aufeinander folgen . Ich habe die Ergebnisse hier eingefügt : gist.github.com/2703756 . Hier sind die korrekten Ergebnisse meiner Routine ijkl2intindex2 () oben: gist.github.com/2703767 .
Ondřej Čertík
1
@ OndřejČertík: Sie möchten ein Zeichen zugeordnet? Lassen Sie idxpair ein Zeichen zurückgeben, wenn Sie die Bestellung gewechselt haben.
Todesatem
OndřejČertík: Ich sehe jetzt den Unterschied. Wie @Deathbreath hervorhebt, können Sie den Index negieren, aber das ist für die gesamte Schleife nicht so sauber. Ich werde nachdenken und es aktualisieren.
Phil H
Das Negieren des Index funktioniert nicht vollständig, da das idxpair den falschen Wert erhält.
Phil H
<ij|kl>=<ji|kl>=<ij|lk>=<ji|lk>
ijkl[idxpair(indexij,indexkl,,)]signijsignkl
3

Hier ist eine Idee der Verwendung einer einfachen Raumfüllungskurve, die so modifiziert wurde, dass sie denselben Schlüssel für die Symmetriefälle zurückgibt (alle Codefragmente sind in Python).

# Simple space-filling curve
def forge_key(i, j, k, l, n): 
  return i + j*n + k*n**2 + l*n**3

# Considers the possible symmetries of a key
def forge_key_symmetry(i, j, k, l, n): 
  return min(forge_key(i, j, k, l, n), 
             forge_key(j, i, l, k, n), 
             forge_key(k, l, i, j, n), 
             forge_key(l, k, j, i, n)) 

Anmerkungen:

  • Das Beispiel ist Python, aber wenn Sie die Funktionen in Ihren fortran-Code einbinden und Ihre innere Schleife für (i, j, k, l) abrollen, sollten Sie eine anständige Leistung erzielen.
  • Sie könnten den Schlüssel mit Gleitkommazahlen berechnen und ihn dann in eine Ganzzahl konvertieren, um ihn als Index zu verwenden. Dies würde es dem Compiler ermöglichen, die Gleitkommaeinheiten zu verwenden (z. B. ist AVX verfügbar).
  • Wenn N eine Potenz von 2 ist, wären die Multiplikationen nur Bitverschiebungen.
  • Die Behandlung der Symmetrien ist im Speicher nicht effizient (dh sie erzeugt keine kontinuierliche Indizierung) und verwendet ungefähr 1/4 der gesamten Indexarray-Einträge.

Hier ist ein Testbeispiel für n = 2:

for i in range(n):
  for j in range(n):
    for k in range(n):
      for l in range(n):
        key = forge_key_symmetry(i, j, k, l, n)
        print i, j, k , l, key

Ausgabe für n = 2:

i j k l key
0 0 0 0 0
0 0 0 1 1
0 0 1 0 1
0 0 1 1 3
0 1 0 0 1
0 1 0 1 5
0 1 1 0 6
0 1 1 1 7
1 0 0 0 1
1 0 0 1 6
1 0 1 0 5
1 0 1 1 7
1 1 0 0 3
1 1 0 1 7
1 1 1 0 7
1 1 1 1 15

Wenn von Interesse, ist die Umkehrfunktion von forge_key:

# Inverse of forge_key
def split_key(key, n): 
  d = key / n**3
  c = (key - d*n**3) / n**2
  b = (key - c*n**2 - d*n**3) / n 
  a = (key - b*n - c*n**2 - d*n**3)
  return (a, b, c, d)
fcruz
quelle
Meinten Sie "wenn n eine Potenz von 2 ist" anstelle eines Vielfachen von 2?
Aron Ahmadia
Ja, danke Aron. Ich schrieb diese Antwort kurz vor dem Abendessen und Hulk schrieb.
Fcruz
Klug! Ist der maximale Index jedoch nicht n ^ 4 (oder n ^ 4-1, wenn Sie bei 0 beginnen)? Das Problem ist, dass die Basisgröße, die ich ausführen möchte, nicht in den Speicher passt. Bei aufeinanderfolgendem Index beträgt die Größe des Arrays n ^ 2 * (n ^ 2 + 3) / 4. Hm, das ist sowieso nur etwa 1/4 der vollen Größe. Vielleicht sollte ich mir also keine Sorgen um den Faktor 4 beim Speicherverbrauch machen. Trotzdem muss es eine Möglichkeit geben, den korrekten fortlaufenden Index nur mit diesen 4 Symmetrien zu codieren (besser als meine hässliche Lösung in meinem Beitrag, in der ich Doppelschleifen ausführen muss).
Ondřej Čertík
Ja, das ist richtig! Ich weiß nicht, wie ich den Index elegant lösen soll (ohne ihn zu sortieren und neu zu nummerieren), aber der führende Begriff in der Speichernutzung ist O (N ^ 4). Der Faktor 4 sollte einen kleinen Unterschied im Gedächtnis für große N. machen
fcruz
0

Ist dies nicht nur die Verallgemeinerung des Problems der gepackten symmetrischen Matrixindizierung? Die Lösung dort ist Offset (i, j) = i * (i + 1) / 2 + j, nicht wahr? Können Sie dies nicht verdoppeln und ein doppelt symmetrisches 4D-Array indizieren? Die Implementierung, die eine Verzweigung erfordert, erscheint unnötig.

Jeff
quelle