Як реалізувати ефективну функцію індексації для двох інтегралів частинок <ij | kl>?


11

Це проста проблема перерахування симетрії. Я даю тут повний досвід, але знання квантової хімії не потрібні.

Інтеграл двох частинок є: І має такі 4 симетрії: мене є функція, яка обчислює інтеграл і зберігає їх у 1D масиві , індексується так:я J | до л = г | * я ( х ) г | * J ( х ' ) г | до ( х ) г | л ( х ' )ij|klя J | до л = J я | л до = до л | я J = л до | J я

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 k jψi(x)ikjl

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

Примітка: функції, які я реалізував, насправді приймають чотири числа , , , у так званій нотації "хімії" , тобто аргументи та замінені, але це не важливо.J K L ( я J | до л ) = я K | J л J Kijkl(ij|kl)=ik|jljk


Поза темою, але чи я єдина людина, яка здивована нотацією ? Вбий, вбий вогнем! d3x
n00b

1
d3x - це просто поширений ярлик для де . Замість того, щоб виписати все це прямо, набагато простіше просто використовувати і все зрозуміло. dx1dx2dx3x=(x1,x2,x3)d3x
Ondřej Čertík

Я чітко бачу, що це: оманливе, огидне і потребує знищення. Там немає у , так чому змінна інтегрування? Чому б не просто ? Мені страшно просто дивитись на це, і тепер мені потрібно лягти. xx=(x1,x2,x3)dx
n00b

@ n00b, я вважаю, що є кращим, оскільки він також визначає розмірність інтеграла (дуже важливо, оскільки інтеграл дає різні результати в 1D, 2D та 3D). d3x
Ondřej Čertík

Відповіді:


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

де друга петля є, тому що ми не можемо вкласти цикл повністю всередині циклу без if / пропустити, щоб перевірити трикутні показники.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

Поєднання обох цих даних дає повний набір, тому поєднання обох циклів дає нам повний набір індексів.

Суттєвою проблемою є те, що ці закономірності важко обчислити для довільних i, j, k, l. Тому я б запропонував карту, яка дає індекс, заданий i, j, k, l. Відверто кажучи, якщо ви це взагалі робите, ви можете також скористатися підходом "+ + фільтр", оскільки вам потрібно це зробити лише один раз для заданої . Плюсом вищевказаного методу є те, що у вас є принаймні передбачувана структура циклу.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)

У fortran нам просто доведеться запустити цикл і зберегти значення. Ми можемо використовувати простий індекс для зберігання комбінації i, j, k, l як єдине значення ( ) і зберігати ці значення в масиві, індекс якого такий же, як індекс вище. Потім ми можемо повторити цей масив і отримати значення i, j, k, l зі значень. Щоб отримати idx для довільних i, j, k, l, знадобиться зворотна карта та фільтр для обробки симетрії, хоча, можливо, ми могли б побудувати функцію з наведеної структури. Функцією генерації масиву idx у фортран буде: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

Привіт Філ, велике спасибі за відповідь! Я перевірив це, і є дві проблеми. Наприклад idx_all (1, 2, 3, 4, 4) == idx_all (1, 2, 4, 3, 4) = 76. Але <12 | 34> / = <12 | 43>. Він дорівнює лише в тому випадку, якщо орбіталі справжні. Тож ваше рішення, схоже, стосується 8 симетрій (див. Мій приклад Fortran вище для більш простої версії, ijkl2intindex ()). Друга проблема полягає в тому, що показники не є послідовними, результати я вставив тут: gist.github.com/2703756 . Ось правильні результати з моєї програми ijkl2intindex2 () вище: gist.github.com/2703767 .
Ondřej Čertík

1
@ OndřejČertík: Ви хочете, щоб знак пов’язаний? мати idxpair повернути знак, якщо ви переключили замовлення.
Смерть

OndřejČertík: Зараз я бачу різницю. Як вказує @Deathbreath, ви можете заперечувати індекс, але це не буде таким чистим для загального циклу. Я подумаю і оновлю його.
Філ Н

Насправді, заперечення індексу не спрацює повністю, оскільки idxpair отримає значення неправильним.
Філ Н

@PhilH: Не заперечуйте індекс . Просто додайте знак змінної повернення в idxpair. Тоді відповідь - .
<ij|kl>=<ji|kl>=<ij|lk>=<ji|lk>
ijkl[idxpair(indexij,indexkl,,)]signijsignkl
Смерть

3

Ось ідея використання простої кривої заповнення простору, модифікованої для повернення одного ключа для випадків симетрії (усі фрагменти коду знаходяться в пітоні).

# 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), ви повинні отримати гідну продуктивність.
  • Ви можете обчислити ключ за допомогою плавців, а потім перетворити ключ у ціле число, щоб використовувати його як індекс, це дозволить компілятору використовувати одиниці з плаваючою комою (наприклад, 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)

Ви мали на увазі "якщо n - потужність 2", а не кратна 2?
Арон Ахмадія

так, дякую Арону. Я написав цю відповідь перед тим, як піти на вечерю, і Халк писав.
fcruz

Розумний! Однак, чи не максимальний індекс n ^ 4 (або n ^ 4-1, якщо ви почнете з 0)? Проблема полягає в тому, що для базового розміру, який я хочу зробити, він не впишеться в пам'ять. З послідовним індексом розмір масиву дорівнює n ^ 2 * (n ^ 2 + 3) / 4. Гм, це все одно лише 1/4 повного розміру. Тому, можливо, я не повинен турбуватися про коефіцієнт 4 у споживанні пам'яті. Але все-таки має бути якийсь спосіб кодування правильного послідовного індексу, використовуючи лише ці 4 симетрії (краще, ніж моє некрасиве рішення в моєму дописі, де мені потрібно зробити подвійні петлі).
Ondřej Čertík

так, це правильно! Я не знаю, як елегантно вирішити (без сортування та перенумерації) індексу, але провідним терміном використання пам'яті є O (N ^ 4). Коефіцієнт 4 повинен мало змінити пам’ять для великої Н.
fcruz

0

Це не просто узагальнення заданої індексації симетричної матриці? Рішення там є зміщеним (i, j) = i * (i + 1) / 2 + j, чи не так? Чи не можете ви подвоїти це питання та проіндексувати подвійно симетричний 4D масив? Реалізація, що вимагає розгалуження, видається непотрібною.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.