Come implementare una funzione di indicizzazione efficiente per due integrali di particelle <ij | kl>?


11

Questo è un semplice problema di enumerazione della simmetria. Fornisco qui il quadro completo, ma non è necessaria alcuna conoscenza della chimica quantistica.

L'integrale di due particelle è: i j | k l = ψ * i ( x ) ψ * j ( x ' ) ψ k ( x ) ψ l ( x ' )ij|kl E ha i seguenti 4 simmetrie: i j | k l = j i | l k = k l | i j = l k | j i Ho una funzione che calcola l'integrale e li memorizza in un array 1D, indicizzati come segue:

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))

dove la funzione ijkl2intindex2restituisce un indice univoco, tenendo conto delle simmetrie di cui sopra. L'unico requisito è che se si esegue il ciclo su tutte le combinazioni di i, j, k, l (da 1 a n ciascuna), riempirà l' int2array consecutivamente e assegnerà lo stesso indice a tutte le combinazioni ijkl correlate da quanto sopra 4 simmetrie.

La mia attuale implementazione in Fortran è qui . È molto lento. Qualcuno sa come farlo in modo efficace? (In qualsiasi lingua.)

ψi(x)ikjl

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

ijkl(ij|kl)=ik|jljk


d3x

1
d3xdx1dx2dx3x=(x1,x2,x3)d3x

xx=(x1,x2,x3)dx

d3x

Risposte:


5

[Modifica: la quarta volta è il fascino, finalmente qualcosa di sensato]

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

La combinazione di entrambi fornisce il set completo, quindi mettere insieme entrambi i loop ci dà il set completo di indici.

n

In python possiamo scrivere il seguente iteratore per darci i valori idx e i, j, k, l per ogni diverso scenario:

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

E poi passaci sopra così:

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

Ciao Phil, grazie mille per la risposta! L'ho provato e ci sono due problemi. Ad esempio idx_all (1, 2, 3, 4, 4) == idx_all (1, 2, 4, 3, 4) = 76. Ma <12 | 34> / = <12 | 43>. È uguale solo se gli orbitali sono reali. Quindi la tua soluzione sembra essere nel caso di 8 simmetrie (vedi il mio esempio Fortran sopra per una versione più semplice, ijkl2intindex ()). Il secondo problema è che gli indici non sono consecutivi, ho incollato i risultati qui: gist.github.com/2703756 . Ecco i risultati corretti dalla mia routine ijkl2intindex2 () sopra: gist.github.com/2703767 .
Ondřej Čertík,

1
@ OndřejČertík: vuoi associare un cartello? fai in modo che idxpair restituisca un segno se hai cambiato ordine.
Deathbreath

OndřejČertík: Adesso vedo la differenza. Come sottolinea @Deathbreath, puoi annullare l'indice, ma non sarà così pulito per l'intero ciclo. Ci penserò e lo aggiornerò.
Phil H,

In realtà, negare l'indice non funzionerà completamente poiché l'idxpair sbaglierà il valore.
Phil H,

<ij|kl>=<ji|kl>=<ij|lk>=<ji|lk>
ijkl[idxpair(indexij,indexkl,,)]signijsignkl

3

Ecco l'idea di utilizzare una semplice curva di riempimento dello spazio modificata per restituire la stessa chiave per i casi di simmetria (tutti i frammenti di codice sono in pitone).

# 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)) 

Appunti:

  • L'esempio è python ma se si incorporano le funzioni nel codice fortran e si srotola il ciclo interno per (i, j, k, l), si dovrebbero ottenere prestazioni decenti.
  • È possibile calcolare la chiave utilizzando float e quindi convertire la chiave in numero intero da utilizzare come indice, ciò consentirebbe al compilatore di utilizzare le unità in virgola mobile (ad es. AVX è disponibile).
  • Se N è una potenza di 2, le moltiplicazioni sarebbero solo spostamenti di bit.
  • Il trattamento per le simmetrie non è efficiente in memoria (cioè non produce un'indicizzazione continua) e utilizza circa 1/4 delle voci dell'array dell'indice totale.

Ecco un esempio di test per 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

Uscita per 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

Se di interesse, la funzione inversa di 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)

Intendevi "se n è una potenza di 2" anziché multiplo di 2?
Aron Ahmadia,

si grazie Aron. Ho scritto questa risposta poco prima di andare a cena e Hulk stava scrivendo.
fcruz,

Intelligente! Tuttavia, non è l'indice massimo n ^ 4 (o n ^ 4-1 se si parte da 0)? Il problema è che per le dimensioni di base che voglio essere in grado di fare, non si adatta alla memoria. Con indice consecutivo, la dimensione dell'array è n ^ 2 * (n ^ 2 + 3) / 4. Hm, comunque è solo circa 1/4 della dimensione intera. Quindi forse non dovrei preoccuparmi del fattore 4 nel consumo di memoria. Tuttavia, ci deve essere un modo per codificare l'indice consecutivo corretto usando solo queste 4 simmetrie (meglio della mia brutta soluzione nel mio post, dove ho bisogno di fare doppi loop).
Ondřej Čertík,

sì, è vero! Non so come risolvere elegantemente (senza ordinare e rinumerare) l'indice ma il termine principale nell'uso della memoria è O (N ^ 4). Il fattore 4 dovrebbe fare una piccola differenza nella memoria per il grande N.
fcruz,

0

Non è solo la generalizzazione del problema di indicizzazione della matrice simmetrica compatta? La soluzione è offset (i, j) = i * (i + 1) / 2 + j, non è vero? Non puoi raddoppiare su questo e indicizzare un array 4D doppiamente simmetrico? L'implementazione che richiede la ramificazione sembra non necessaria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.