Tính toán Hafnian càng nhanh càng tốt


12

Thách thức là viết mã nhanh nhất có thể để tính toán Hafnian của ma trận .

Các Hafnian của một đối xứng 2n-by- 2nma trận Ađược định nghĩa là:

Ở đây S 2n đại diện cho tập hợp tất cả các hoán vị của các số nguyên từ 1đến 2n, đó là[1, 2n] .

Liên kết wikipedia cũng cung cấp một công thức tìm kiếm khác có thể được quan tâm (và thậm chí các phương thức nhanh hơn tồn tại nếu bạn tìm kiếm thêm trên web). Cùng một trang wiki nói về ma trận kề, nhưng mã của bạn cũng sẽ hoạt động cho các ma trận khác. Bạn có thể giả sử tất cả các giá trị sẽ là số nguyên nhưng không phải tất cả chúng đều dương.

Ngoài ra còn có một thuật toán nhanh hơn nhưng có vẻ khó hiểu. và Christian Sievers là người đầu tiên thực hiện nó (trong Haskell).

Trong câu hỏi này, ma trận đều vuông và đối xứng với kích thước chẵn.

Tham chiếu thực hiện (lưu ý điều này là sử dụng phương pháp chậm nhất có thể).

Dưới đây là một số ví dụ mã python từ ông 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

Nhiệm vụ

Bạn nên viết mã, được đưa ra 2nbởi một 2nma trận, xuất ra Hafnian của nó.

Vì tôi sẽ cần kiểm tra mã của bạn, sẽ rất hữu ích nếu bạn có thể đưa ra một cách đơn giản để tôi đưa ra một ma trận làm đầu vào cho mã của bạn, ví dụ bằng cách đọc từ tiêu chuẩn trong. Tôi sẽ kiểm tra mã của bạn trong các ma trận được chọn ngẫu nhiên với các yếu tố được chọn từ {-1, 0, 1}. Mục đích của việc thử nghiệm như thế này là để giảm khả năng Hafnian sẽ có giá trị rất lớn.

Lý tưởng nhất là mã của bạn sẽ có thể đọc chính xác như tôi có chúng trong các ví dụ trong câu hỏi này ngay từ tiêu chuẩn. Đó là [[1,-1],[-1,-1]]ví dụ đầu vào sẽ như thế nào . Nếu bạn muốn sử dụng định dạng đầu vào khác, vui lòng hỏi và tôi sẽ cố gắng hết sức để đáp ứng.

Điểm và quan hệ

Tôi sẽ kiểm tra mã của bạn trên các ma trận ngẫu nhiên có kích thước tăng dần và dừng lần đầu tiên mã của bạn mất hơn 1 phút trên máy tính của tôi. Các ma trận cho điểm sẽ phù hợp với tất cả các bài nộp để đảm bảo sự công bằng.

Nếu hai người có cùng số điểm thì người chiến thắng là người nhanh nhất với giá trị đó n. Nếu những cái đó trong vòng 1 giây với nhau thì đó là cái được đăng đầu tiên.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ và thư viện có sẵn nào bạn thích nhưng không có chức năng có sẵn để tính toán Hafnian. Nếu khả thi, sẽ rất tốt để có thể chạy mã của bạn, vì vậy vui lòng bao gồm một lời giải thích đầy đủ về cách chạy / biên dịch mã của bạn trong Linux nếu có thể.

Máy của tôi Thời gian sẽ được chạy trên máy 64 bit của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn với RAM 8GB, Bộ xử lý tám lõi AMD FX-8350 và Radeon HD 4250. Điều này cũng có nghĩa là tôi cần có thể chạy mã của bạn.

Gọi để trả lời bằng nhiều ngôn ngữ

Sẽ thật tuyệt khi nhận được câu trả lời bằng ngôn ngữ lập trình siêu nhanh yêu thích của bạn. Để bắt đầu mọi thứ, làm thế nào về fortran , nimrỉ sét ?

Bảng xếp hạng

  • 52 và đấm bằng C ++ . 30 giây.
  • 50 bởi NGN sử dụng C . 50 giây.
  • 46 bởi Christian Sievers sử dụng Haskell . 40 giây.
  • 40 và đấm bằng Python 2 + PyPy . 41 giây.
  • 34 bởi ngn sử dụng Python 3 + pypy . 29 giây.
  • 28 bởi Dennis bằng Python 3 . 35 giây. (Pypy chậm hơn)

Có giới hạn cho các giá trị tuyệt đối của các mục ma trận không? Chúng ta có thể trả về một xấp xỉ dấu phẩy động không? Chúng ta có phải sử dụng số nguyên chính xác tùy ý?
Dennis

@Dennis Trong thực tế tôi sẽ chỉ sử dụng -1,0,1 để kiểm tra (được chọn ngẫu nhiên). Tôi không muốn nó là một thách thức lớn. Thành thật mà nói tôi không biết liệu chúng ta có đạt được giới hạn của int 64 bit hay không trước khi mã quá chậm để chạy nhưng tôi đoán là chúng ta sẽ không. Hiện tại chúng tôi không ở đâu gần đó.

Nếu các mục được giới hạn ở -1,0,1 , điều đó nên được đề cập trong câu hỏi. Mã của chúng ta có phải làm việc ở tất cả các ma trận khác không?
Dennis

@Dennis Một phiên bản cũ được sử dụng để nói điều đó nhưng tôi phải viết về nó. Tôi thích nó hơn nếu mã không chuyên dùng cho các mục -1,0,1 nhưng tôi cho rằng tôi không thể dừng điều đó.

Bạn có nhiều trường hợp thử nghiệm? có lẽ cho n lớn hơn ?
dặm

Câu trả lời:


14

Haskell

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

Điều này thực hiện một biến thể của Thuật toán 2 của Andreas Bjorklund: Đếm các trận đấu hoàn hảo nhanh như Ryser .

Biên dịch bằng ghccác tùy chọn thời gian biên dịch -O3 -threadedvà sử dụng các tùy chọn thời gian chạy +RTS -Nđể song song hóa. Lấy đầu vào từ stdin.


2
Có thể lưu ý rằng parallelvectorphải được cài đặt?
H.PWiz

@ H.PWiz Không ai phàn nàn ở đây , nhưng chắc chắn, lưu ý rằng nó sẽ không bị tổn thương. Vâng, bây giờ bạn đã làm.
Christian Sievers

@ChristianSievers Tôi không nghĩ họ đang phàn nàn. OP có thể không quen thuộc với Haskell, vì vậy, nêu rõ những gì phải được cài đặt để có thể đặt mã thời gian là một ý tưởng tốt.
Dennis

@Dennis Tôi không có nghĩa là "bạn đã phàn nàn" nhưng "bạn đã lưu ý nó". Và tôi đã không nghĩ phàn nàn là một điều tiêu cực. OP giống như trong thử thách tôi liên kết, vì vậy sẽ không có vấn đề gì.
Christian Sievers

N = 40 trong 7,5 giây trên TIO ... Man, nhanh quá!
Dennis

6

Con trăn 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())))))

Hãy thử trực tuyến!


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];
}

Hãy thử trực tuyến! (13 giây cho n = 24)

Dựa trên việc triển khai Python nhanh hơn trong bài đăng khác của tôi. Chỉnh sửa dòng thứ hai thành #define S 3trên máy 8 lõi của bạn và biên dịch với g++ -pthread -march=native -O2 -ftree-vectorize.

Việc chia công việc thành một nửa, vì vậy giá trị của Snên là log2(#threads). Các loại có thể dễ dàng được thay đổi giữa int, long, float, và doublebằng cách thay đổi giá trị của #define TYPE.


Đây là câu trả lời hàng đầu cho đến nay. Mã của bạn không thực sự đọc trong đầu vào như được chỉ định vì nó không thể đối phó với khoảng trắng. Tôi phải làm ví dụtr -d \ < matrix52.txt > matrix52s.txt

@Lembik Xin lỗi, chỉ sử dụng nó để chống lại ma trận không có kích thước 24. Đã sửa lỗi ngay bây giờ để làm việc với các khoảng trắng.
dặm

4

Con trăn 3

Điều này tính haf (A) là một tổng số ghi nhớ (A [i] [j] * haf (A không có hàng và cols i và 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

C

Một nội dung khác của bài viết của Andreas Bjorklund , dễ hiểu hơn nhiều nếu bạn cũng xem mã Haskell của Christian Sievers . Đối với một vài cấp độ đầu tiên của đệ quy, nó phân phối các luồng vòng tròn trên các CPU có sẵn. Cấp độ cuối cùng của đệ quy, chiếm một nửa trong số các lệnh, được tối ưu hóa bằng tay.

Biên dịch với : gcc -O3 -pthread -march=native; cảm ơn @Dennis vì đã tăng tốc gấp 2 lần

n = 24 trong 24 giây trên 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;
}

Thuật toán:

Ma trận, đối xứng, được lưu trữ ở dạng tam giác phía dưới bên trái. Các chỉ số tam giác i,jtương ứng với chỉ số tuyến tính T(max(i,j))+min(i,j)trong đó Tmacro cho i*(i-1)/2. Các phần tử ma trận là đa thức bậc n. Một đa thức được biểu diễn dưới dạng một mảng các hệ số được sắp xếp từ số hạng không đổi ( p[0]) đến x n hệ số '( p[n]). Các giá trị ma trận -1,0,1 ban đầu được chuyển đổi thành đa thức const.

Chúng tôi thực hiện một bước đệ quy với hai đối số: nửa ma trận (tức là tam giác) acủa đa thức và một đa thức riêng biệt p(gọi là beta trong bài báo). Chúng tôi giảm mvấn đề kích thước (ban đầu m=2*n) theo cách đệ quy thành hai vấn đề về kích thước m-2và trả lại sự khác biệt của các hafnian của họ. Một trong số đó là sử dụng giống nhau amà không có hai hàng cuối cùng và rất giống nhau p. Một cách khác là sử dụng tam giác b[i][j] = a[i][j] + shmul(a[m-1][i],a[m-2][j]) + shmul(a[m-1][j],a[m-2][i])(trong đó shmulhoạt động nhân bội trên đa thức - nó giống như sản phẩm đa thức như bình thường, nhân với biến "x"; công suất cao hơn x ^ n bị bỏ qua) và đa thức riêng biệt q = p + shmul(p,a[m-1][m-2]). Khi đệ quy đạt kích thước-0 a, chúng ta trả về hệ số chính của p : p[n].

Hoạt động thay đổi và nhân lên được thực hiện trong chức năng f(x,y,z). Nó sửa đổi ztại chỗ. Nói một cách lỏng lẻo, nó làmz += shmul(x,y) . Đây có vẻ là phần quan trọng nhất về hiệu suất.

Sau khi đệ quy kết thúc, chúng ta cần sửa dấu của kết quả bằng cách nhân với (-1) n .


Bạn có thể hiển thị một ví dụ rõ ràng về đầu vào mà mã của bạn chấp nhận không? Nói cho một ma trận 2 by 2. Ngoài ra, bạn dường như đã đánh golf mã của bạn! (Đây là một thử thách mã nhanh nhất, không phải là thử thách chơi gôn.)

@Lembik Đối với bản ghi, như tôi đã nói trong trò chuyện, đầu vào có cùng định dạng với các ví dụ - json (thực tế, nó chỉ đọc các số và sử dụng n = sqrt (len (đầu vào)) / 2). Tôi thường viết mã ngắn, ngay cả khi chơi golf không phải là một yêu cầu.
ngn

Ma trận kích thước lớn nhất mà mã mới này sẽ hỗ trợ là gì?

1
-march=nativesẽ làm cho một sự khác biệt lớn ở đây. Ít nhất là trên TIO, nó gần như cắt giảm thời gian tường xuống một nửa.
Dennis

1
Ngoài ra, ít nhất là trên TIO, việc thực thi được tạo bởi gcc sẽ còn nhanh hơn nữa.
Dennis

3

Con trăn

Đây là khá nhiều cách triển khai tham chiếu của Thuật toán 2 từ bài báo đã đề cập . Những thay đổi chỉ là để chỉ giữ giá trị hiện tại của B , giảm các giá trị của β chỉ cập nhật g khi iX , và cắt ngắn đa thức nhân bởi chỉ tính giá trị lên đến mức độ 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

Hãy thử trực tuyến!

Đây là một phiên bản nhanh hơn với một số tối ưu hóa dễ dàng.

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)

Hãy thử trực tuyến!

Để thêm phần thú vị, đây là một triển khai tham khảo trong J.


Điều này khá chậm so với tất cả các cách hiểu danh sách và từ việc tính toán các giá trị tương đương trên đường chéo, do đó không cần phải đánh giá điểm này.
dặm

Khá tuyệt vời!

Rất đẹp! Tôi đã thử một điều tương tự với sympy, nó rất chậm và trong khi đúng với các ví dụ nhỏ, đã quay trở lại - sau một thời gian dài - một kết quả sai cho ma trận 24 * 24. Tôi không biết những gì đang xảy ra ở đó. - Theo mô tả ở trên Thuật toán 2, phép nhân đa thức đã có nghĩa là bị cắt cụt.
Christian Sievers

2
Trong pmul, sử dụng for j in range(n-i):và tránhif
Christian Sievers

1
@Lembik Nó tính toàn bộ ma trận; cho một yếu tố của khoảng hai thay thế j != kbằng j < k. Nó sao chép một hàm con trong trường hợp khác có thể tránh được khi chúng ta xử lý và xóa hai cái cuối thay vì hai hàng và cột đầu tiên. Và khi nó tính toán với x={1,2,4}và sau x={1,2,4,6}đó, nó lặp lại tính toán của nó i=5. Tôi đã thay thế cấu trúc của hai vòng lặp bên ngoài bằng vòng lặp đầu tiên ivà sau đó giả định đệ quy i in Xi not in X. - BTW, thật thú vị khi xem xét sự tăng trưởng của thời gian cần thiết so với các chương trình chậm khác.
Christian Sievers

1

Octave

Đây về cơ bản là một bản sao của mục nhập của Dennis , nhưng được tối ưu hóa cho Octave. Tối ưu hóa chính được thực hiện bằng cách sử dụng ma trận đầu vào đầy đủ (và chuyển vị của nó) và đệ quy chỉ sử dụng các chỉ số ma trận, thay vì tạo ma trận giảm.

Ưu điểm chính là giảm sao chép ma trận. Mặc dù Octave không có sự khác biệt giữa các con trỏ / tham chiếu và các giá trị và về mặt chức năng chỉ thực hiện theo từng giá trị, đó là một câu chuyện khác nhau đằng sau hậu trường. Ở đó, copy-on-write (bản sao lười biếng) được sử dụng. Điều đó có nghĩa là, đối với mã a=1;b=a;b=b+1, biến bchỉ được sao chép sang một vị trí mới ở câu lệnh cuối cùng, khi nó được thay đổi. Vì matinmatranspkhông bao giờ thay đổi, chúng sẽ không bao giờ được sao chép. Nhược điểm là hàm dành nhiều thời gian hơn để tính toán các chỉ số chính xác. Tôi có thể phải thử các biến thể khác nhau giữa các chỉ số số và logic để tối ưu hóa điều này.

Lưu ý quan trọng: ma trận đầu vào phải là int32 ! Lưu chức năng trong một tệp gọi là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

Kịch bản thử nghiệm ví dụ:

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

Tôi đã thử điều này bằng TIO và MATLAB (Tôi thực sự chưa bao giờ cài đặt Octave). Tôi tưởng tượng làm cho nó hoạt động là đơn giản như sudo apt-get install octave. Lệnh octavesẽ tải GUI Octave. Nếu nó phức tạp hơn thế này, tôi sẽ xóa câu trả lời này cho đến khi tôi cung cấp hướng dẫn cài đặt chi tiết hơn.


0

Gần đây Andreas Bjorklund, Brajesh Gupt và bản thân tôi đã xuất bản một thuật toán mới cho Hafnians của các ma trận phức tạp: https://arxiv.org/pdf/1805.12498.pdf . Đối với ma trận n \ lần n, nó có tỷ lệ như n ^ 3 2 ^ {n / 2}.

Nếu tôi hiểu chính xác thuật toán gốc của Andreas từ https://arxiv.org/pdf/1107.4466.pdf thì nó có tỷ lệ như n ^ 4 2 ^ {n / 2} hoặc n ^ 3 log (n) 2 ^ {n / 2} nếu bạn đã sử dụng biến đổi Fourier để thực hiện phép nhân đa thức.
Thuật toán của chúng tôi đặc biệt được xử lý cho các ma trận phức tạp, vì vậy nó sẽ không nhanh như các thuật toán được phát triển ở đây cho các ma trận {-1,0,1}. Tôi tự hỏi tuy nhiên nếu một người có thể sử dụng một số thủ thuật bạn đã sử dụng để cải thiện việc thực hiện của chúng tôi? Ngoài ra nếu mọi người quan tâm, tôi muốn xem cách triển khai của họ khi đưa ra số phức thay vì số nguyên. Cuối cùng, mọi bình luận, chỉ trích, cải tiến, lỗi, cải tiến đều được hoan nghênh trong kho của chúng tôi https://github.com/XanaduAI/hafnian/

Chúc mừng!


Chào mừng đến với trang web! Tuy nhiên, câu trả lời cho câu hỏi này phải chứa mã. Điều này sẽ tốt hơn để lại như một nhận xét, (Thật không may, bạn không có đại diện để thực hiện).
Đăng Rock Garf Hunter

Chào mừng đến với PPCG. Mặc dù câu trả lời của bạn có thể đưa ra nhận xét tốt, trang web này không dành cho QA. Đây là một trang web thách thức và trả lời cho một thách thức phải có mã và không phải là một lời giải thích về cái gì khác. Vui lòng cập nhật hoặc xóa (Nếu bạn không, mod sẽ)
Muhammad Salman

Chà, mã trên github, nhưng tôi đoán nó sẽ vô nghĩa khi chỉ sao chép-dán nó ở đây.
Nicolás Quesada

2
Nếu nó phù hợp với một câu trả lời, đặc biệt nếu bạn là một trong những tác giả, tôi không nghĩ có gì sai khi đăng một giải pháp cạnh tranh được quy kết đúng, đã được công bố ở nơi khác.
Dennis

@ NicolásQuesada Câu trả lời trên trang web này nên được khép kín nếu có thể, có nghĩa là chúng ta không nên đến một trang web khác để xem câu trả lời / mã của bạn.
mbomb007
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.