Обчисліть Хафняна якомога швидше


12

Завдання полягає в тому, щоб написати найшвидший код, можливий для обчислення Хафніана матриці .

Hafnian симетричною 2nматриці з розмірністю 2nматриці Aвизначаються наступним чином:

Тут S 2n являє собою сукупність усіх перестановок цілих чисел від 1до 2n, тобто [1, 2n].

Посилання на вікіпедію також дає іншу формулу, яка може представляти інтерес (і ще швидші методи існують, якщо ви заглянете далі в Інтернет). Ця ж сторінка вікі розповідає про матриці суміжності, але ваш код повинен працювати і для інших матриць. Ви можете припустити, що всі значення будуть цілими числами, але не те, що вони всі позитивні.

Існує також більш швидкий алгоритм, але він, здається, важко зрозуміти. а Крістіан Сіверс першим здійснив це (у Хаскеллі).

У цьому питанні матриці всі квадратні та симетричні з рівними розмірами.

Реалізація довідки (зверніть увагу, що це використовується найповільніший можливий метод).

Ось приклад коду python від містера Xcoder.

from itertools import permutations
from math import factorial

def hafnian(matrix):
    my_sum = 0
    n = len(matrix) // 2
    for sigma in permutations(range(n*2)):
        prod = 1
        for j in range(n):
            prod *= matrix[sigma[2*j]][sigma[2*j+1]]
        my_sum += prod
    return my_sum / (factorial(n) * 2 ** n)

print(hafnian([[-1, 1, 1, -1, 0, 0, 1, -1], [1, 0, 1, 0, -1, 0, -1, -1], [1, 1, -1, 1, -1, -1, 0, -1], [-1, 0, 1, -1, -1, 1, -1, 0], [0, -1, -1, -1, -1, 0, 0, -1], [0, 0, -1, 1, 0, 0, 1, 1], [1, -1, 0, -1, 0, 1, 1, 0], [-1, -1, -1, 0, -1, 1, 0, 1]]))
4

M = [[1, 1, 0, 0, 0, 0, 0, 1, 0, 0], [1, 1, -1, 0, -1, 1, 1, 1, 0, -1], [0, -1, -1, -1, 0, -1, -1, 0, -1, 1], [0, 0, -1, 1, -1, 1, -1, 0, 1, -1], [0, -1, 0, -1, -1, -1, -1, 1, -1, 1], [0, 1, -1, 1, -1, 1, -1, -1, 1, -1], [0, 1, -1, -1, -1, -1, 1, 0, 0, 0], [1, 1, 0, 0, 1, -1, 0, 1, 1, -1], [0, 0, -1, 1, -1, 1, 0, 1, 1, 1], [0, -1, 1, -1, 1, -1, 0, -1, 1, 1]]

print(hafnian(M))
-13

M = [[-1, 0, -1, -1, 0, -1, 0, 1, -1, 0, 0, 0], [0, 0, 0, 0, 0, -1, 0, 1, -1, -1, -1, -1], [-1, 0, 0, 1, 0, 0, 0, 1, -1, 1, -1, 0], [-1, 0, 1, -1, 1, -1, -1, -1, 0, -1, -1, -1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, -1, 0], [-1, -1, 0, -1, 0, 0, 1, 1, 1, 1, 1, 0], [0, 0, 0, -1, 0, 1, 1, -1, -1, 0, 1, 0], [1, 1, 1, -1, 0, 1, -1, 1, -1, -1, -1, -1], [-1, -1, -1, 0, 0, 1, -1, -1, -1, 1, -1, 0], [0, -1, 1, -1, 1, 1, 0, -1, 1, -1, 1, 1], [0, -1, -1, -1, -1, 1, 1, -1, -1, 1, 0, -1], [0, -1, 0, -1, 0, 0, 0, -1, 0, 1, -1, 1]]

print(hafnian(M))
13

M = [[-1, 1, 0, 1, 0, -1, 0, 0, -1, 1, -1, 1, 0, -1], [1, -1, 1, -1, 1, 1, -1, 0, -1, 1, 1, 0, 0, -1], [0, 1, 1, 1, -1, 1, -1, -1, 0, 0, -1, 0, -1, -1], [1, -1, 1, -1, 1, 0, 1, 1, -1, -1, 0, 0, 1, 1], [0, 1, -1, 1, 0, 1, 0, 1, -1, -1, 1, 1, 0, -1], [-1, 1, 1, 0, 1, 1, -1, 0, 1, -1, -1, -1, 1, -1], [0, -1, -1, 1, 0, -1, -1, -1, 0, 1, -1, 0, 1, -1], [0, 0, -1, 1, 1, 0, -1, 0, 0, -1, 0, 0, 0, 1], [-1, -1, 0, -1, -1, 1, 0, 0, 1, 1, 0, 1, -1, 0], [1, 1, 0, -1, -1, -1, 1, -1, 1, 1, 1, 0, 1, 0], [-1, 1, -1, 0, 1, -1, -1, 0, 0, 1, -1, 0, -1, 0], [1, 0, 0, 0, 1, -1, 0, 0, 1, 0, 0, 1, 1, 1], [0, 0, -1, 1, 0, 1, 1, 0, -1, 1, -1, 1, 1, -1], [-1, -1, -1, 1, -1, -1, -1, 1, 0, 0, 0, 1, -1, -1]]

print(hafnian(M))
83

Завдання

Ви повинні написати код , який, дається з 2nдопомогою 2nматриці, виводить її Hafnian.

Оскільки мені знадобиться перевірити ваш код, було б корисно, якби ви могли дати простий спосіб мені надати матрицю як вхід до вашого коду, наприклад, читаючи зі стандарту дюйма. Я перевірю ваш код у випадково вибраних матрицях з елементами вибрано з {-1, 0, 1}. Мета такого тестування - зменшити ймовірність того, що Гафній буде дуже великою цінністю.

В ідеалі ваш код зможе читати в матрицях саме так, як я їх маю у прикладах у цьому питанні прямо від стандартних дюймів. Це буде вхідний вигляд, [[1,-1],[-1,-1]]наприклад. Якщо ви хочете використовувати інший формат введення, будь ласка, запитайте, і я зроблю все можливе, щоб розмістити його.

Оцінки та зв’язки

Я перевіряю ваш код на випадкових матрицях збільшення розміру і зупиняюсь, коли ваш код займе більше ніж 1 хвилину на моєму комп’ютері. Матриці оцінювання будуть послідовними для всіх робіт, щоб забезпечити справедливість.

Якщо двоє людей отримують однаковий бал, тоді виграє той, хто найшвидший за цю величину n. Якщо вони знаходяться в межах 1 секунди один від одного, то це перша, розміщена першою.

Мови та бібліотеки

Ви можете використовувати будь-яку доступну мову та бібліотеки, які вам подобаються, але жодної попередньої функції для обчислення Хафніана. Там, де це можливо, було б добре запустити свій код, тому, будь-ласка, включіть повне пояснення, як запустити / скомпілювати свій код в Linux, якщо це можливо. "

Моя машина Часи синхронізуються на моєму 64-бітному апараті. Це стандартна установка ubuntu з 8 ГБ оперативної пам’яті, AMD FX-8350 восьмиядерний процесор та Radeon HD 4250. Це також означає, що мені потрібно мати можливість запускати ваш код.

Зателефонуйте на відповіді іншими мовами

Було б чудово отримати відповіді на вашій улюбленій мові програмування супер швидкої. Для того, щоб почати речі, як про FORtran , ЧПМ і іржі ?

Таблиця лідерів

  • 52 миль за допомогою C ++ . 30 секунд.
  • 50 з використанням СПП C . 50 секунд.
  • 46 Крістіан Сіверс за допомогою Haskell . 40 секунд.
  • 40 миль за допомогою Python 2 + pypy . 41 секунда.
  • 34 на ngn, використовуючи Python 3 + pypy . 29 секунд.
  • 28 Деніс за допомогою Python 3 . 35 секунд. (Піпі повільніше)

Чи існує обмеження абсолютних значень матричних записів? Чи можемо ми повернути наближення плаваючої точки? Чи потрібно використовувати довільні точні цілі числа?
Денніс

@Dennis На практиці я використовую лише -1,0,1 для тестування (вибрано навмання). Я не хочу, щоб це було великим завданням. Чесно кажучи, я не знаю, чи вдаримо ми до меж 64 бітових точок, перш ніж код стане занадто повільним, але я гадаю, що ми цього не зробимо. В даний час ми ніде не близькі до цього.

Якщо записи обмежені -1,0,1 , це слід зазначити у питанні. Чи повинен наш код взагалі працювати для інших матриць?
Денніс

@Dennis Старий варіант говорив про це, але я, мабуть, написав це. Я б вважав за краще, якби код не був спеціалізованим для -1,0,1 записів, але, мабуть, я не можу цього зупинити.

Чи є у вас більше тестових випадків? можливо для більших n ?
миль

Відповіді:


14

Хаскелл

import Control.Parallel.Strategies
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector as VB

type Poly = V.Vector Int

type Matrix = VB.Vector ( VB.Vector Poly )

constpoly :: Int -> Int -> Poly
constpoly n c = V.generate (n+1) (\i -> if i==0 then c else 0)

add :: Poly -> Poly -> Poly
add = V.zipWith (+)

shiftmult :: Poly -> Poly -> Poly
shiftmult a b = V.generate (V.length a) 
                           (\i -> sum [ a!j * b!(i-1-j) | j<-[0..i-1] ])
  where (!) = V.unsafeIndex

x :: Matrix -> Int -> Int -> Int -> Poly -> Int
x  _    0  _ m p = m * V.last p
x mat n c m p =
  let mat' = VB.generate (2*n-2) $ \i ->
             VB.generate i       $ \j ->
                 shiftmult (mat!(2*n-1)!i) (mat!(2*n-2)!j) `add`
                 shiftmult (mat!(2*n-1)!j) (mat!(2*n-2)!i) `add`
                 (mat!i!j)
      p' = p `add` shiftmult (mat!(2*n-1)!(2*n-2)) p
      (!) = VB.unsafeIndex
      r = if c>0 then parTuple2 rseq rseq else r0
      (a,b) = (x mat (n-1) (c-1) m p, x mat' (n-1) (c-1) (-m) p')
              `using` r
  in a+b

haf :: [[Int]] -> Int
haf m = let n=length m `div` 2
        in x (VB.fromList $ map (VB.fromList . map (constpoly n)) m) 
             n  5  ((-1)^n)  (constpoly n 1) 

main = getContents >>= print . haf . read

Це реалізує варіацію алгоритму 2 Андреаса Бьерклунда: Підрахунок ідеальних відповідностей так швидко, як і Райзер .

Скомпілюйте, використовуючи ghcпараметри часу компіляції -O3 -threadedта використовуйте параметри часу виконання +RTS -Nдля паралелізації. Бере вхід з stdin.


2
Можливо, зауважте, що parallelі vectorпотрібно встановити?
H.PWiz

@ H.PWiz Ніхто не скаржився тут , але впевнений, зазначивши , що вона не зашкодить. Ну, тепер ви зробили.
Крістіан Сіверс

@ChristianSievers Я не думаю, що вони скаржаться. ОП може бути не знайомий з Haskell, тому чітко вказуючи, що потрібно встановити, щоб мати змогу ввести код - хороша ідея.
Денніс

@Dennis Я не мав на увазі "ти скаржився", але "ти це зазначив". І я не думав скаржитися як негатив. ОП - це те саме, що і у виклику, з яким я пов'язаний, тому проблем не повинно бути.
Крістіан Сіверс

N = 40 за 7,5 секунд на TIO ... Людина, це швидко!
Денніс

6

Пітон 3

from functools import lru_cache

@lru_cache(maxsize = None)
def haf(matrix):
	n = len(matrix)
	if n == 2: return matrix[0][1]
	h = 0
	for j in range(1, n):
		if matrix[0][j] == 0: continue
		copy = list(matrix)
		del copy[:j+1:j]
		copy = list(zip(*copy))
		del copy[:j+1:j]
		h += matrix[0][j] * haf(tuple(copy))
	return h

print(haf(tuple(map(tuple, eval(open(0).read())))))

Спробуйте в Інтернеті!


6

C ++ (gcc)

#define T(x) ((x)*((x)-1)/2)
#define S 1
#define J (1<<S)
#define TYPE int

#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>

using namespace std;

struct H {
    int s, w, t;
    TYPE *b, *g;
};

void *solve(void *a);
void hafnian(TYPE *b, int s, TYPE *g, int w, int t);

int n, m, ti = 0;
TYPE r[J] = {0};
pthread_t pool[J];

int main(void) {
    vector<int> a;
    string s;
    getline(cin, s);

    for (int i = 0; i < s.size(); i++)
        if (s[i] == '0' || s[i] == '1')
            a.push_back((s[i-1] == '-' ? -1 : 1)*(s[i] - '0'));

    for (n = 1; 4*n*n < a.size(); n++);
    m = n+1;

    TYPE z[T(2*n)*m] = {0}, g[m] = {0};

    for (int j = 1; j < 2*n; j++)
        for (int k = 0; k < j; k++)
            z[(T(j)+k)*m] = a[j*2*n+k];
    g[0] = 1;

    hafnian(z, 2*n, g, 1, -1);

    TYPE h = 0;
    for (int t = 0; t < ti; t++) {
        pthread_join(pool[t], NULL);
        h += r[t];
    }

    cout << h << endl;

    return 0;
}

void *solve(void *a) {
    H *p = reinterpret_cast<H*>(a);
    hafnian(p->b, p->s, p->g, p->w, p->t);
    delete[] p->b;
    delete[] p->g;
    delete p;
    return NULL;
}

void hafnian(TYPE *b, int s, TYPE *g, int w, int t) {
    if (t == -1 && (n < S || s/2 == n-S)) {
        H *p = new H;
        TYPE *c = new TYPE[T(s)*m], *e = new TYPE[m];
        copy(b, b+T(s)*m, c);
        copy(g, g+m, e);
        p->b = c;
        p->s = s;
        p->g = e;
        p->w = w;
        p->t = ti;
        pthread_create(pool+ti, NULL, solve, p);
        ti++;
    }
    else if (s > 0) {
        TYPE c[T(s-2)*m], e[m];
        copy(b, b+T(s-2)*m, c);
        hafnian(c, s-2, g, -w, t);
        copy(g, g+m, e);

        for (int u = 0; u < n; u++) {
            TYPE *d = e+u+1,
                  p = g[u], *x = b+(T(s)-1)*m;
            for (int v = 0; v < n-u; v++)
                d[v] += p*x[v];
        }

        for (int j = 1; j < s-2; j++)
            for (int k = 0; k < j; k++)
                for (int u = 0; u < n; u++) {
                    TYPE *d = c+(T(j)+k)*m+u+1,
                          p = b[(T(s-2)+j)*m+u], *x = b+(T(s-1)+k)*m,
                          q = b[(T(s-2)+k)*m+u], *y = b+(T(s-1)+j)*m;
                    for (int v = 0; v < n-u; v++)
                        d[v] += p*x[v] + q*y[v];
                }

        hafnian(c, s-2, e, w, t);
    }
    else
        r[t] += w*g[n];
}

Спробуйте в Інтернеті! (13s для n = 24)

На основі більш швидкої реалізації Python в іншому моєму дописі. Відредагуйте другий рядок #define S 3на своїй 8-ядерній машині та компілюйте з g++ -pthread -march=native -O2 -ftree-vectorize.

Розбиває роботу навпіл, тому значення Sмає бути log2(#threads). Типи можуть бути легко змінені між int, long, floatі doubleшляхом зміни значення #define TYPE.


Це провідна відповідь поки що. Ваш код насправді не читається на вході, як зазначено, оскільки він не справляється з пробілами. Мені довелося це зробити, наприкладtr -d \ < matrix52.txt > matrix52s.txt

@Lembik Вибачте, його використовували лише проти безпросторової матриці розміром 24. Виправлено зараз для роботи з пробілами.
миль

4

Пітон 3

Це обчислює haf (A) як запам'ятовувану суму (A [i] [j] * haf (A без рядків і знаків i і j)).

#!/usr/bin/env python3
import json,sys
a=json.loads(sys.stdin.read())
n=len(a)//2
b={0:1}
def haf(x):
 if x not in b:
  i=0
  while not x&(1<<i):i+=1
  x1=x&~(1<<i)
  b[x]=sum(a[i][j]*haf(x1&~(1<<j))for j in range(2*n)if x1&(1<<j)and a[i][j])
 return b[x]
print(haf((1<<2*n)-1))

3

С

Ще одна прикмета статті Андреаса Бьорклунда , яку набагато простіше зрозуміти, якщо ви також подивитеся на код Хескелла Крістіана Сіверса . Для перших кількох рівнів рекурсії він розподіляє круглолистові потоки за наявними процесорами. Останній рівень рекурсії, на який припадає половина викликів, оптимізований вручну.

Компіляція з: gcc -O3 -pthread -march=native; дякую @Dennis за 2-кратну швидкість

n = 24 за 24 секунди на TIO

#define _GNU_SOURCE
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<unistd.h>
#include<pthread.h>
#define W while
#define R return
#define S static
#define U (1<<31)
#define T(i)((i)*((i)-1)/2)
typedef int I;typedef long L;typedef char C;typedef void V;
I n,ncpu,icpu;
S V f(I*x,I*y,I*z){I i=n,*z1=z+n;W(i){I s=0,*x2=x,*y2=y+--i;W(y2>=y)s+=*x2++**y2--;*z1--+=s;}}
typedef struct{I m;V*a;V*p;pthread_barrier_t*bar;I r;}A;S V*(h1)(V*);
I h(I m,I a[][n+1],I*p){
 m-=2;I i,j,k=0,u=T(m),v=u+m,b[u][n+1],q[n+1];
 if(!m){I*x=a[v+m],*y=p+n-1,s=0;W(y>=p)s-=*x++**y--;R s;}
 memcpy(b,a,sizeof(b));memcpy(q,p,sizeof(q));f(a[v+m],p,q);
 for(i=1;i<m;i++)for(j=0;j<i;j++){f(a[u+i],a[v+j],b[k]);f(a[u+j],a[v+i],b[k]);k++;}
 if(2*n-m>8)R h(m,a,p)-h(m,b,q);
 pthread_barrier_t bar;pthread_barrier_init(&bar,0,2);pthread_t th;
 cpu_set_t cpus;CPU_ZERO(&cpus);CPU_SET(icpu++%ncpu,&cpus);
 pthread_attr_t attr;pthread_attr_init(&attr);
 pthread_attr_setaffinity_np(&attr,sizeof(cpu_set_t),&cpus);
 A arg={m,a,p,&bar};pthread_create(&th,&attr,h1,&arg);
 I r=h(m,b,q);pthread_barrier_wait(&bar);pthread_join(th,0);pthread_barrier_destroy(&bar);
 R arg.r-r;
}
S V*h1(V*x0){A*x=(A*)x0;x->r=h(x->m,x->a,x->p);pthread_barrier_wait(x->bar);R 0;}
I main(){
 ncpu=sysconf(_SC_NPROCESSORS_ONLN);
 S C s[200000];I i=0,j=0,k,l=0;W((k=read(0,s+l,sizeof(s)-l))>0)l+=k;
 n=1;W(s[i]!=']')n+=s[i++]==',';n/=2;
 I a[T(2*n)][n+1];memset(a,0,sizeof(a));k=0;
 for(i=0;i<2*n;i++)for(j=0;j<2*n;j++){
  W(s[k]!='-'&&(s[k]<'0'||s[k]>'9'))k++;
  I v=0,m=s[k]=='-';k+=m;W(k<l&&('0'<=s[k]&&s[k]<='9'))v=10*v+s[k++]-'0';
  if(i>j)*a[T(i)+j]=v*(1-2*m);
 }
 I p[n+1];memset(p,0,sizeof(p));*p=1;
 printf("%d\n",(1-2*(n&1))*h(2*n,a,p));
 R 0;
}

Алгоритм:

Матриця, яка є симетричною, зберігається в нижній лівій трикутній формі. Індекси трикутника i,jвідповідають лінійному індексу, T(max(i,j))+min(i,j)де Tмакрос i*(i-1)/2. Матричні елементи - це многочлени ступеня n. Поліном представлений у вигляді масиву коефіцієнтів, упорядкованих від постійного члена ( p[0]) до коефіцієнта x n '( p[n]). Початкові значення матриці -1,0,1 спочатку перетворюються на поліноми const.

Ми виконуємо рекурсивний крок з двома аргументами: напівматрицею (тобто трикутником) aмногочленів та окремим многочленом p(згаданим у статті бета). Ми зменшуємо mзадачу m=2*nрозміру (спочатку ) рекурсивно до двох задач розміру m-2і повертаємо різницю їхніх хафній. Один з них - використовувати те саме, що aне має останніх двох рядів, і те саме p. Інша полягає у використанні трикутника b[i][j] = a[i][j] + shmul(a[m-1][i],a[m-2][j]) + shmul(a[m-1][j],a[m-2][i])(де shmulоперація зсув-множення на поліномах - це як добуток полінома, як звичайно, додатково помножений на змінну "х"; потужності вище x ^ n ігноруються), і окремий многочлен q = p + shmul(p,a[m-1][m-2]). Коли рекурсія вражає розмір-0 a, ми повертаємо головний коефіцієнт р: p[n].

Операція зсуву та множення реалізована у функції f(x,y,z). Це змінює zна місці. Помірно кажучи, це робить z += shmul(x,y). Це здається найбільш критичною для продуктивності.

Після закінчення рекурсії нам потрібно зафіксувати знак результату шляхом множення на (-1) n .


Чи можете ви показати явний приклад вводу, який приймає ваш код? Скажіть для матриці 2 на 2. Крім того, ви, схоже, переграли свій код! (Це виклик з найшвидшим кодом, а не виклик для гольфу.)

@Lembik Для запису, як я вже говорив у чаті, вхід у тому ж форматі, що і приклади - json (насправді він читає лише числа і використовує n = sqrt (len (input)) / 2). Я зазвичай пишу короткий код, навіть коли гольф не є вимогою.
ngn

Яку матрицю найбільшого розміру повинен підтримувати цей новий код?

1
-march=nativeтут буде велика зміна. Принаймні, на TIO, це майже скорочує час стіни навпіл.
Денніс

1
Крім того, принаймні на TIO, виконуваний програмою gcc буде ще швидшим.
Денніс

3

Пітон

Це в значній мірі пряма, референтна реалізація Алгоритму 2 із згаданої роботи . Зміни полягали лише в тому, щоб зберегти поточне значення B , скинувши значення β лише оновленням g, коли iX , і усічене множення поліномів, лише обчисливши значення до ступеня n .

from itertools import chain,combinations

def powerset(s):
    return chain.from_iterable(combinations(s, k) for k in range(len(s)+1))

def padd(a, b):
    return [a[i]+b[i] for i in range(len(a))]

def pmul(a, b):
    n = len(a)
    c = [0]*n
    for i in range(n):
        for j in range(n):
            if i+j < n:
                c[i+j] += a[i]*b[j]
    return c

def hafnian(m):
    n = len(m) / 2
    z = [[[c]+[0]*n for c in r] for r in m]
    h = 0
    for x in powerset(range(1, n+1)):
        b = z
        g = [1] + [0]*n
        for i in range(1, n+1):
            if i in x:
                g = pmul(g, [1] + b[0][1][:n])
                b = [[padd(b[j+2][k+2], [0] + padd(pmul(b[0][j+2], b[1][k+2]), pmul(b[0][k+2], b[1][j+2]))[:n]) if j != k else 0 for k in range(2*n-2*i)] for j in range(2*n-2*i)]
            else:
                b = [r[2:] for r in b[2:]]
        h += (-1)**(n - len(x)) * g[n]
    return h

Спробуйте в Інтернеті!

Ось більш швидка версія з деякими простими оптимізаціями.

def hafnian(m):
  n = len(m)/2
  z = [[0]*(n+1) for _ in range(n*(2*n-1))]
  for j in range(1, 2*n):
    for k in range(j):
      z[j*(j-1)/2+k][0] = m[j][k]
  return solve(z, 2*n, 1, [1] + [0]*n, n)

def solve(b, s, w, g, n):
  if s == 0:
    return w*g[n]
  c = [b[(j+1)*(j+2)/2+k+2][:] for j in range(1, s-2) for k in range(j)]
  h = solve(c, s-2, -w, g, n)
  e = g[:]
  for u in range(n):
    for v in range(n-u):
      e[u+v+1] += g[u]*b[0][v]
  for j in range(1, s-2):
    for k in range(j):
      for u in range(n):
        for v in range(n-u):
          c[j*(j-1)/2+k][u+v+1] += b[(j+1)*(j+2)/2][u]*b[(k+1)*(k+2)/2+1][v] + b[(k+1)*(k+2)/2][u]*b[(j+1)*(j+2)/2+1][v]
  return h + solve(c, s-2, w, e, n)

Спробуйте в Інтернеті!

Для додаткової забави, ось довідкова реалізація в Дж.


Це досить повільно з усіх розумінь списку та від обчислення еквівалентних значень по діагоналі, тому немає необхідності в цьому оцінювати.
миль

Досить дивовижно!

Дуже хороша! Я спробував подібну річ із симпатією, яка була приголомшливо повільною, і, будучи правильним для невеликих прикладів, повернула - через тривалий час - неправильний результат для матриці 24 * 24. Я поняття не маю, що там відбувається. - Відповідно до алгоритму 2, поліноміальне множення там уже передбачається усіченим.
Крістіан Сіверс

2
В pmul, використовувати for j in range(n-i):і уникнутиif
Christian Сіверс

1
@Lembik Він обчислює всю матрицю; для коефіцієнта близько двох замінити j != kна j < k. Він копіює підматрицю в іншому випадку, якої можна уникнути, коли ми обробляємо та видаляємо останні два замість перших двох рядків та стовпців. І коли він обчислює, x={1,2,4}а пізніше x={1,2,4,6}потім повторює свої обчислення аж до i=5. Я замінив структуру двох зовнішніх петель спочатку циклічно, iа потім рекурсивно припускаючи i in Xі i not in X. - BTW, Можливо, буде цікаво подивитися на час, необхідний у порівнянні з іншими повільнішими програмами.
Крістіан Сіверс

1

Октава

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

Основна перевага - скорочення копіювання матриць. Хоча Octave не має різниці між покажчиками / посиланнями та значеннями і функціонально лише передає значення, це вже інша історія. Там використовується копіювання на запис (лінива копія). Це означає, що для коду a=1;b=a;b=b+1змінна bкопіюється на нове місце лише в останньому операторі, коли вона змінюється. Оскільки matinі matranspніколи не змінюються, вони ніколи не будуть скопійовані. Недоліком є ​​те, що функція витрачає більше часу на обчислення правильних показників. Можливо, мені доведеться спробувати різні варіації між числовими та логічними показниками, щоб оптимізувати це.

Важлива примітка: матриця введення повинна бути int32! Збережіть функцію у файлі під назвоюhaf.m

function h=haf(matin,indices,matransp,transp)

    if nargin-4
        indices=int32(1:length(matin));
        matransp=matin';
        transp=false;
    end
    if(transp)
        matrix=matransp;
    else
        matrix=matin;
    end
    ind1=indices(1);
    n=length(indices);
    if n==2
        h=matrix(ind1,indices(2));
        return
    end
    h=0*matrix(1); 
    for j=1:(n-1)
        indj=indices(j+1);
        k=matrix(ind1,indj);
        if logical(k)
            indicestemp=true(n,1);
            indicestemp(1:j:j+1)=false;
            h=h+k.*haf(matin,indices(indicestemp),matransp,~transp);
        end
    end
end

Приклад тестового сценарію:

matrix = int32([0 0 1 -1 1 0 -1 -1 -1 0 -1 1 0 1 1 0 0 1 0 0 1 0 1 1;0 0 1 0 0 -1 -1 -1 -1 0 1 1 1 1 0 -1 -1 0 0 1 1 -1 0 0;-1 -1 0 1 0 1 -1 1 -1 1 0 0 1 -1 0 0 0 -1 0 -1 1 0 0 0;1 0 -1 0 1 1 0 1 1 0 0 0 1 0 0 0 1 -1 -1 -1 -1 1 0 -1;-1 0 0 -1 0 0 1 -1 0 1 -1 -1 -1 1 1 0 1 1 1 0 -1 1 -1 -1;0 1 -1 -1 0 0 1 -1 -1 -1 0 -1 1 0 0 0 -1 0 0 1 0 0 0 -1;1 1 1 0 -1 -1 0 -1 -1 0 1 1 -1 0 1 -1 0 0 1 -1 0 0 0 -1;1 1 -1 -1 1 1 1 0 0 1 0 1 0 0 0 0 1 0 1 0 -1 1 0 0;1 1 1 -1 0 1 1 0 0 -1 1 -1 1 1 1 0 -1 -1 -1 -1 0 1 1 -1;0 0 -1 0 -1 1 0 -1 1 0 1 0 0 0 0 0 1 -1 0 0 0 1 -1 -1;1 -1 0 0 1 0 -1 0 -1 -1 0 0 1 0 0 -1 0 -1 -1 -1 -1 -1 1 -1;-1 -1 0 0 1 1 -1 -1 1 0 0 0 -1 0 0 -1 0 -1 -1 0 1 -1 0 0;0 -1 -1 -1 1 -1 1 0 -1 0 -1 1 0 1 -1 -1 1 -1 1 0 1 -1 1 -1;-1 -1 1 0 -1 0 0 0 -1 0 0 0 -1 0 0 -1 1 -1 -1 0 1 0 -1 -1;-1 0 0 0 -1 0 -1 0 -1 0 0 0 1 0 0 1 1 1 1 -1 -1 0 -1 -1;0 1 0 0 0 0 1 0 0 0 1 1 1 1 -1 0 0 1 -1 -1 -1 0 -1 -1;0 1 0 -1 -1 1 0 -1 1 -1 0 0 -1 -1 -1 0 0 -1 1 0 0 -1 -1 1;-1 0 1 1 -1 0 0 0 1 1 1 1 1 1 -1 -1 1 0 1 1 -1 -1 -1 1;0 0 0 1 -1 0 -1 -1 1 0 1 1 -1 1 -1 1 -1 -1 0 1 1 0 0 -1;0 -1 1 1 0 -1 1 0 1 0 1 0 0 0 1 1 0 -1 -1 0 0 0 1 0;-1 -1 -1 1 1 0 0 1 0 0 1 -1 -1 -1 1 1 0 1 -1 0 0 0 0 0;0 1 0 -1 -1 0 0 -1 -1 -1 1 1 1 0 0 0 1 1 0 0 0 0 1 0;-1 0 0 0 1 0 0 0 -1 1 -1 0 -1 1 1 1 1 1 0 -1 0 -1 0 1;-1 0 0 1 1 1 1 0 1 1 1 0 1 1 1 1 -1 -1 1 0 0 0 -1 0])

tic
i=1;
while(toc<60)
    tic
    haf(matrix(1:i,1:i));
    i=i+1;
end

Я спробував це, використовуючи TIO і MATLAB (я фактично ніколи не встановлював Octave). Я уявляю, як зробити це так само просто sudo apt-get install octave. Команда octaveзавантажить графічний інтерфейс Octave. Якщо це складніше, ніж це, я видалю цю відповідь, поки не надаю більш детальні інструкції щодо встановлення.


0

Нещодавно Andreas Bjorklund, Brajesh Gupt і я опублікували новий алгоритм для Hafnians складних матриць: https://arxiv.org/pdf/1805.12498.pdf . Для матриці n \ times n вона масштабується як n ^ 3 2 ^ {n / 2}.

Якщо я правильно розумію оригінальний алгоритм Андреаса з https://arxiv.org/pdf/1107.4466.pdf, він масштабується як n ^ 4 2 ^ {n / 2} або n ^ 3 log (n) 2 ^ {n / 2} якщо ви використовували перетворення Фур'є для поліноміального множення.
Наш алгоритм спеціально розроблений для складних матриць, тому він не буде настільки швидким, як ті, розроблені тут для {-1,0,1} матриць. Цікаво, однак, чи можна скористатись трьома хитрощами, які ви використали для вдосконалення нашого впровадження? Також, якщо люди зацікавлені, я хотів би побачити, як їх реалізовують, коли дають складні числа замість цілих чисел. Нарешті, будь-які коментарі, критики, удосконалення, помилки, покращення вітаються у нашому сховищі https://github.com/XanaduAI/hafnian/

Ура!


Ласкаво просимо на сайт! Однак відповіді на це питання повинні містити код. Це краще залишити як коментар (Що, на жаль, у вас немає повторення).
Ad Hoc Garf Hunter

Ласкаво просимо до PPCG. Хоча ваша відповідь може зробити гарний коментар, сайт не для забезпечення якості. Це сайт для викликів, і відповідь на виклик повинен мати код, а не пояснення чогось іншого. Будь ласка, оновіть або видаліть (якщо цього не зробите, модники зроблять)
Мухаммад Салман

Ну, код є на github, але я думаю, що це нісенітниця просто копіювати та вставляти його сюди.
Nicolás Quesada

2
Якщо це відповідає у відповідь, особливо якщо ви один з авторів, я не думаю, що немає нічого поганого в опублікуванні належним чином віднесеного конкурентного рішення, опублікованого в інших місцях.
Денніс

@ NicolásQuesada Відповіді на цьому веб-сайті, якщо це можливо, повинні бути самостійними, тобто ми не повинні переходити на інший сайт, щоб переглянути вашу відповідь / код.
mbomb007
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.