2つの粒子積分<ij | kl>に効率的なインデックス関数を実装する方法は?


11

これは単純な対称列挙問題です。ここで完全な背景を説明しますが、量子化学の知識は必要ありません。

2つの粒子の積分は次のとおりです。 そして、次の4つの対称性があります。 積分を計算して、次のようにインデックス付けされた1D配列に格納する関数があります。ij|kl

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

ijkl2intindex2上記の対称性を考慮して、関数は一意のインデックスを返します。唯一の要件は、i、j、k、lのすべての組み合わせ(それぞれ1からnまで)をループする場合、int2配列を連続して埋め、上記に関連するすべてのijklの組み合わせに同じインデックスを割り当てることです4つの対称性。

Fortranでの私の現在の実装はこちらです。とても遅いです。誰もこれを効果的に行う方法を知っていますか?(任意の言語で。)

ヒント:軌道が実数である場合、上記の対称性に加えて、およびを交換できるため、合計8つの対称性が得られます: そして、インデックス付けのための非常に高速な関数を実装できますこちらの実装を参照してください。軌道が実数でない場合の効率的な索引付けスキームを見つけたいと思います。ψi(x)ikjl

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

注:私は実際には4つの数字受け入れる実装という機能、、、いわゆる"化学"表記の、すなわちとの引数を入れ替え、しかし、これは重要ではありません。ijkl(ij|kl)=ik|jljk


トピック外ですが、記法 app然としているのは私だけですか?殺して、火で殺せ!d3x
n00b

1
d3xは、一般的に使用されるショートカットです。。すべてを明示的に記述する代わりに、を使用する方がはるかに簡単で、すべてが明確です。dx1dx2dx3x=(x1,x2,x3)d3x
オンドレジ・セティク

誤解を招く、恐ろしい、破壊が必要なものが何であるかをはっきりと見ることができます。全く存在しないでなぜそれは積分変数でありますか?なぜだけではないのですか?私はそれを見ているだけで気分が悪くなり、今は横になる必要があります。xx=(x1,x2,x3)dx
n00b

@ n00b、は積分の次元も指定するため、好ましいと思います(積分は1D、2D、3Dで異なる結果を与えるため、非常に重要です)。d3x
オンドレジ・セティク

回答:


5

[編集:4回目の魅力、ついに賢明なもの]

私はこれに逆戻りしました:私はフィルターベースのアプローチを示す別の答えから始め、それを使用して一連の値のすべての有効な組み合わせを生成し、オンライン整数シーケンスデータベースでシーケンスを調べました。組み合わせの数は、これはありそうにありません(なぜ3ですか?)。それはまた、あるの三角形の数であり、。これを入手したので、その理由を知る必要があります。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

ここで、2番目のループは、三角形のインデックスをチェックするif / skipなしでループをループ内に完全にネストできないためです。llk

2番目の項は、最初のペアが昇順で、2番目のペアが降順です(上で処理されるため、no ==に注意してください)。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

これらの両方を組み合わせることで完全なセットが得られるため、両方のループをまとめると、インデックスの完全なセットが得られます。

重要な問題は、これらのパターンを任意のi、j、k、lに対して計算するのが難しいことです。したがって、i、j、k、lが与えられたインデックスを生成するマップを提案します。率直に言って、これを行う場合は、指定された一度だけ行う必要があるため、generate + filterアプローチを使用することもできます。上記の方法のプラス面は、少なくとも予測可能なループ構造があることです。n

Pythonでは、次のイテレータを記述して、さまざまなシナリオごとにidxおよびi、j、k、lの値を提供できます。

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

そして、このようにループします:

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

こんにちはフィル、答えてくれてありがとう!私はそれをテストしましたが、2つの問題があります。たとえば、idx_all(1、2、3、4、4)== idx_all(1、2、4、3、4)=76。ただし、<12 | 34> / = <12 | 43>。軌道が実数である場合にのみ等しくなります。したがって、あなたの解決策は8つの対称性の場合です(より簡単なバージョン、ijkl2intindex()については上記のFortranの例を参照してください)。2番目の問題は、インデックスが連続していないことです。ここで結果を貼り付けました:gist.github.com/2703756 :ここでは上記の私のijkl2intindex2()ルーチンから正しい結果ですgist.github.com/2703767は
オンドレジ・セティク

1
@OndřejČertík:サインを関連付けたいですか?順序を切り替えた場合、idxpairがサインを返すようにします。
デスブレス

OndřejČertík:違いがわかりました。@Deathbreathが指摘しているように、インデックスを無効にすることはできますが、ループ全体としてはそれほどきれいではありません。考えて更新します。
フィルH

実際、idxpairが値を誤って取得するため、インデックスの無効化は完全には機能しません。
フィルH

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

3

これは、対称の場合に同じキーを返すように修正された単純な空間充填曲線を使用するアイデアです(すべてのコードスニペットは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)) 

ノート:

  • 例はpythonですが、関数をfortranコードにインライン化し、(i、j、k、l)の内部ループを展開すると、まともなパフォーマンスが得られるはずです。
  • floatを使用してキーを計算し、インデックスとして使用するためにキーを整数に変換できます。これにより、コンパイラは浮動小数点ユニットを使用できるようになります(AVXが使用可能など)。
  • Nが2のべき乗である場合、乗算は単なるビットシフトになります。
  • 対称性の処理はメモリ内で効率的ではなく(つまり、連続したインデックス作成を行わない)、インデックス配列エントリ全体の約1/4を使用します。

以下は、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

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

興味深い場合、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)

2の倍数ではなく、「nが2のべき乗の場合」という意味ですか?
アロンアフマディア

はい、アロンに感謝します。私は夕食に行く直前にこの答えを書き、ハルクは書いていました。
フクルス

賢い!ただし、最大インデックスはn ^ 4(または0から開始する場合はn ^ 4-1)ではありませんか?問題は、できるようにしたい基本サイズでは、メモリに収まらないことです。連続したインデックスでは、配列のサイズはn ^ 2 *(n ^ 2 + 3)/ 4です。Hm、それはとにかくフルサイズの約1/4にすぎません。したがって、メモリ消費の4倍を心配する必要はありません。それでも、これらの4つの対称性のみを使用して正しい連続インデックスをエンコードする方法が必要です(私の投稿の二重ループを行う必要があるい解決策よりも優れています)。
オンドレジ・セティク

ええ、そうです!インデックスをエレガントに解決する方法はわかりません(並べ替えや番号の付け直しを行わない)が、メモリ使用量の主要な用語はO(N ^ 4)です。4の係数により、大きなNのメモリにわずかな違いが生じるはずです
。– fcruz

0

これは、パックされた対称行列のインデックス付け問題の一般化だけではありませんか?そこにある解決策は、offset(i、j)= i *(i + 1)/ 2 + jです。これを二重にして、二重対称4D配列のインデックスを作成することはできませんか?分岐を必要とする実装は不要のようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.