คำนวณ Hafnian โดยเร็วที่สุด


12

ความท้าทายคือการเขียนรหัสที่เร็วที่สุดที่เป็นไปได้สำหรับการคำนวณHafnian ของเมทริกซ์

Hafnian ของเมทริกซ์สมมาตร - 2nโดย - 2nเมทริกซ์Aหมายถึง:

นี่ S 2nหมายถึงชุดของพีชคณิตทั้งหมดของจำนวนเต็มจาก1การที่เป็น2n[1, 2n]

ลิงก์วิกิพีเดียยังมีสูตรการค้นหาที่แตกต่างกันซึ่งอาจเป็นที่สนใจ หน้า wiki เดียวกันพูดถึงการฝึกหัด adjacency แต่รหัสของคุณควรใช้กับเมทริกซ์อื่นเช่นกัน คุณสามารถสันนิษฐานได้ว่าค่าทั้งหมดจะเป็นจำนวนเต็ม แต่ไม่ใช่ว่าเป็นค่าบวกทั้งหมด

นอกจากนี้ยังมีอัลกอริทึมที่เร็วขึ้น แต่ดูเหมือนจะเข้าใจยาก และ Christian Sievers เป็นคนแรกที่นำไปใช้ (ใน Haskell)

ในเมทริกซ์คำถามนี้ทุกสแควร์และสมมาตรมีขนาดเท่ากัน

การใช้งานอ้างอิง (โปรดทราบว่านี่ใช้วิธีที่ช้าที่สุด)

นี่คือตัวอย่างโค้ดไพ ธ อนจาก Mr. 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} จุดประสงค์ของการทดสอบเช่นนี้คือเพื่อลดโอกาสที่ Hafnian จะมีค่ามาก

นึกคิดรหัสของคุณจะสามารถอ่านในเมทริกซ์ตรงที่ฉันมีพวกเขาในตัวอย่างในคำถามนี้ตรงจากมาตรฐานมานั่นคือการป้อนข้อมูลจะมีลักษณะ[[1,-1],[-1,-1]]เช่น หากคุณต้องการใช้รูปแบบการป้อนข้อมูลอื่นโปรดถามและฉันจะพยายามอย่างดีที่สุดเพื่อรองรับ

คะแนนและความสัมพันธ์

ฉันจะทดสอบโค้ดของคุณในเมทริกซ์แบบสุ่มที่เพิ่มขนาดและหยุดในครั้งแรกที่รหัสของคุณใช้เวลามากกว่า 1 นาทีบนคอมพิวเตอร์ของฉัน เมทริกซ์การให้คะแนนจะสอดคล้องกันสำหรับการส่งทั้งหมดเพื่อความเป็นธรรม

หากคนสองคนได้คะแนนเท่ากันผู้ชนะคือคนที่เร็วที่สุดสำหรับค่าnนั้น หากสิ่งนั้นอยู่ภายใน 1 วินาทีของกันและกันมันก็จะเป็นโพสต์ก่อน

ภาษาและห้องสมุด

คุณสามารถใช้ภาษาและไลบรารีใด ๆ ที่คุณต้องการ แต่ไม่มีฟังก์ชั่นที่มีอยู่แล้วในการคำนวณ Hafnian หากเป็นไปได้มันจะเป็นการดีที่จะสามารถรันโค้ดของคุณได้ดังนั้นโปรดระบุคำอธิบายโดยละเอียดเกี่ยวกับวิธีการรัน / คอมไพล์โค้ดของคุณใน Linux หากเป็นไปได้ "

My Machineการจับเวลาจะทำงานบนเครื่อง 64 บิตของฉัน นี่คือการติดตั้ง Ubuntu มาตรฐานกับ 8GB RAM, AMD FX-8350 Eight-Core Processor และ Radeon HD 4250 นี่หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้

สอบถามคำตอบได้หลายภาษา

มันจะเป็นการดีหากได้รับคำตอบในภาษาการเขียนโปรแกรมที่รวดเร็วเป็นพิเศษที่คุณชื่นชอบ ในการเริ่มต้นสิ่งปิดวิธีการเกี่ยวกับFortran , nimและสนิม ?

ลีดเดอร์บอร์ด

  • 52โดยไมล์โดยใช้ภาษา C ++ 30 วินาที.
  • 50โดย NGN โดยใช้C 50 วินาที
  • 46คริสเตียน Sievers ใช้Haskell 40 วินาที
  • 40โดยใช้ไมล์งูหลาม 2 + pypy 41 วินาที
  • 34โดย NGN ใช้งูหลาม 3 + pypy 29 วินาที
  • 28โดยเดนนิสใช้งูหลาม 3 35 วินาที (Pypy ช้ากว่า)

มีข้อ จำกัด สำหรับค่าสัมบูรณ์ของรายการเมทริกซ์หรือไม่? เราสามารถคืนค่าประมาณค่าทศนิยมได้หรือไม่? เราต้องใช้จำนวนเต็มความแม่นยำโดยพลการหรือไม่?
Dennis

@Dennis ในทางปฏิบัติฉันจะใช้ -1,0,1 ในการทดสอบเท่านั้น (เลือกโดยการสุ่ม) ฉันไม่ต้องการให้มันเป็นความท้าทายที่ยิ่งใหญ่ ในความซื่อสัตย์ทั้งหมดฉันไม่รู้ว่าเราจะใช้ขีด จำกัด ของ 64 บิต int ก่อนที่รหัสจะทำงานช้าเกินไป แต่ฉันเดาว่าเราจะไม่ทำ ขณะนี้เราไม่มีที่ไหนใกล้

หากรายการถูก จำกัด ที่-1,0,1ควรจะกล่าวถึงในคำถาม รหัสของเราต้องทำงานกับเมทริกซ์อื่นหรือไม่?
Dennis

@Dennis รุ่นเก่าเคยบอกว่า แต่ฉันต้องเขียนทับมัน ฉันต้องการมันถ้ารหัสไม่ได้เฉพาะสำหรับรายการ -1,0,1 แต่ฉันคิดว่าฉันไม่สามารถหยุดที่

คุณมีกรณีทดสอบเพิ่มเติมหรือไม่ บางทีอาจจะมีขนาดใหญ่n ?
ไมล์

คำตอบ:


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

การดำเนินการนี้การเปลี่ยนแปลงของอัลกอริทึมที่ 2 ของแอนเดรีBjörklund: นับเพอร์เฟจ้อเป็น Fast เป็น Ryser

รวบรวมโดยใช้ghcกับตัวเลือกเวลารวบรวม-O3 -threadedและใช้ตัวเลือกเวลาทำงาน+RTS -Nสำหรับการขนาน รับอินพุตจาก stdin


2
อาจทราบparallelและvectorต้องติดตั้ง?
H.PWiz

@ H.PWiz ไม่มีใครบ่นที่นี่แต่แน่นอนว่ามันจะไม่เจ็บ ตอนนี้คุณทำ
Christian Sievers

@ChristianSievers ฉันไม่คิดว่าพวกเขาจะบ่น OP อาจไม่คุ้นเคยกับ Haskell ดังนั้นโปรดระบุสิ่งที่ต้องติดตั้งอย่างชัดเจนเพื่อให้ได้รหัสเวลาเป็นความคิดที่ดี
Dennis

@Dennis ฉันไม่ได้หมายความว่า "คุณบ่น" แต่ "คุณได้บันทึกไว้" และฉันไม่คิดว่าจะบ่นว่าเป็นสิ่งที่ไม่ดี OP เหมือนกันกับความท้าทายที่ฉันเชื่อมโยงดังนั้นจึงไม่มีปัญหา
Christian Sievers

N = 40 ใน 7.5 วินาทีสำหรับ TIO ... ผู้ชายนี่เร็วมาก!
Dennis

6

Python 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-core 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

Python 3

สิ่งนี้จะคำนวณ haf (A) เป็นผลรวมที่บันทึกไว้ (A [i] [j] * haf (A ที่ไม่มีแถวและ cols 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

Impl อีกประการหนึ่งของกระดาษ Andreas Björklundของซึ่งเป็นเรื่องง่ายที่จะเข้าใจถ้าคุณยังมองคริสเตียน Sievers รหัส สำหรับการเรียกซ้ำสองสามครั้งแรกมันจะกระจายเธรดแบบวนรอบผ่านซีพียูที่มีอยู่ ระดับสุดท้ายของการเรียกซ้ำซึ่งคิดเป็นครึ่งหนึ่งของการเรียกใช้นั้นได้รับการปรับปรุงด้วยมือ

รวบรวมกับ: gcc -O3 -pthread -march=native; ขอบคุณ @Dennis สำหรับการเร่งความเร็ว 2x

n = 24 ใน 24s บน 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และคืนความแตกต่างของ hafnians หนึ่งในนั้นคือการใช้เดียวกันโดยไม่ต้องสองแถวสุดท้ายและเดียวกันมาก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เป็นงานที่กะคูณใน polynomials - มันก็เหมือนสินค้าพหุนามตามปกติคูณเพิ่มเติมโดยตัวแปร "x"; อำนาจสูงกว่า x ^ n จะถูกละเว้น) q = p + shmul(p,a[m-1][m-2])และพหุนามแยกต่างหาก เมื่อเรียกซ้ำฮิตขนาด-0 a, เรากลับค่าสัมประสิทธิ์ที่สำคัญของ p[n]P:

f(x,y,z)การดำเนินการเปลี่ยนแปลงและคูณถูกนำมาใช้ในการทำงาน มันปรับเปลี่ยนzในสถานที่ z += shmul(x,y)พูดอย่างที่มันไม่ สิ่งนี้ดูเหมือนจะเป็นส่วนที่สำคัญที่สุดในการปฏิบัติงาน

หลังจากการเรียกซ้ำได้เสร็จสิ้นการที่เราต้องแก้ไขสัญญาณของผลโดยคูณด้วย (-1) n


คุณสามารถแสดงตัวอย่างที่ชัดเจนของการป้อนรหัสของคุณยอมรับได้หรือไม่? พูดเมทริกซ์ขนาด 2 คูณ 2 นอกจากนี้คุณดูเหมือนจะมีรหัสกอล์ฟของคุณ! (นี่เป็นรหัสที่เร็วที่สุดไม่ใช่ความท้าทายสำหรับเล่นกอล์ฟ)

@ Lembik สำหรับบันทึกอย่างที่ฉันพูดในการแชทอินพุตอยู่ในรูปแบบเดียวกับตัวอย่าง - json (อันที่จริงมันอ่านตัวเลขเท่านั้นและใช้ n = sqrt (len (อินพุต)) / 2) ฉันมักจะเขียนรหัสสั้น ๆ ถึงแม้ว่าการเล่นกอล์ฟจะไม่ใช่ข้อกำหนดก็ตาม
ngn

อะไรคือเมทริกซ์ขนาดใหญ่ที่สุดที่โค้ดใหม่นี้ควรสนับสนุน?

1
-march=nativeจะสร้างความแตกต่างใหญ่ที่นี่ อย่างน้อยใน TIO ก็เกือบจะลดกำแพงเวลาครึ่งแล้ว
Dennis

1
นอกจากนี้อย่างน้อยใน TIO ไฟล์ปฏิบัติการที่สร้างโดย gcc จะยิ่งเร็วขึ้น
Dennis

3

หลาม

นี่คือสวยมากตรงไปตรงมาดำเนินการอ้างอิงของอัลกอริทึมที่ 2 จากที่กล่าวถึงกระดาษ การเปลี่ยนแปลงแค่เพียงเก็บค่าปัจจุบันของBลดลงค่าของβโดยเฉพาะการอัปเดตกรัมเมื่อฉันXและตัดทอนพหุนามคูณโดยเฉพาะการคำนวณค่าขึ้นไปการศึกษาระดับปริญญา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)

ลองออนไลน์!

เพื่อความสนุกสนานเพิ่มนี่คือการอ้างอิงการดำเนินงานในเจ


นี่ค่อนข้างช้าจากความเข้าใจในรายการทั้งหมดและจากการคำนวณค่าที่เทียบเท่าในแนวทแยงมุมดังนั้นจึงไม่จำเป็นต้องทำการเปรียบเทียบ
ไมล์

น่ากลัวมาก!

ดีมาก! ฉันลองสิ่งที่คล้ายกันกับ sympy ซึ่งช้าอย่างน่าตกใจและในขณะที่ถูกต้องสำหรับตัวอย่างเล็ก ๆ กลับมา - หลังจากเวลานาน - ผลที่ผิดสำหรับเมทริกซ์ 24 * 24 ฉันไม่รู้ว่าเกิดอะไรขึ้นที่นั่น - ตามคำอธิบายด้านบนอัลกอริทึม 2 การคูณพหุนามที่มีความหมายแล้วจะถูกตัดทอน
Christian Sievers

2
ในการpmulใช้for j in range(n-i):และหลีกเลี่ยงif
Christian Sievers

1
@ Lembik มันคำนวณเมทริกซ์ทั้งหมด; สำหรับปัจจัยที่เกี่ยวกับสองแทนที่ด้วยj != k j < kมันจะคัดลอก submatrix ในกรณีอื่นซึ่งสามารถหลีกเลี่ยงได้เมื่อเราจัดการและลบสองอันสุดท้ายแทนที่จะเป็นสองแถวและคอลัมน์แรก และเมื่อมันคำนวณด้วยx={1,2,4}และต่อมาด้วยแล้วมันซ้ำการคำนวณขึ้นอยู่กับx={1,2,4,6} i=5ฉันแทนที่โครงสร้างของสองวงด้านนอกครั้งแรกกับการวนลูปบนiแล้วซ้ำสมมติและi in X i not in X- BTW มันอาจน่าสนใจที่จะดูการเติบโตของเวลาที่ต้องการเมื่อเทียบกับโปรแกรมที่ช้ากว่าอื่น ๆ
Christian Sievers

1

เสียงคู่แปด

นี่เป็นเพียงสำเนาของรายการเดนนิสแต่ได้รับการปรับให้เหมาะกับอ็อกเทฟ การปรับให้เหมาะสมหลักทำได้โดยใช้เมทริกซ์อินพุตแบบเต็ม (และการแปลงข้อมูล) และการเรียกซ้ำโดยใช้ดัชนีเมทริกซ์เท่านั้นแทนที่จะสร้างเมทริกซ์ที่ลดลง

ข้อดีหลักคือลดการคัดลอกเมทริกซ์ ในขณะที่ 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 GUI หากมีความซับซ้อนมากกว่านี้ฉันจะลบคำตอบนี้จนกว่าฉันจะให้คำแนะนำการติดตั้งโดยละเอียดเพิ่มเติม


0

เมื่อเร็ว ๆ นี้แอนเดรี Bjorklund, Brajesh Gupt และตัวเองตีพิมพ์เป็นขั้นตอนวิธีการใหม่สำหรับการฝึกอบรมของ Hafnians ซับซ้อน: https://arxiv.org/pdf/1805.12498.pdf สำหรับเมทริกซ์ n \ times n จะปรับขนาดเช่น n ^ 3 2 ^ {n / 2}

ถ้าฉันเข้าใจอัลกอริทึมดั้งเดิมของ Andreas อย่างถูกต้องจากhttps://arxiv.org/pdf/1107.4466.pdfมันมีขนาดเหมือน n ^ 4 2 ^ {n / 2} หรือ n ^ 3 บันทึก (n) 2 ^ {n / 2} ถ้าคุณใช้ฟูริเยร์แปลงเพื่อทำพหุนาม
อัลกอริธึมของเราได้รับการออกแบบเฉพาะสำหรับเมทริกซ์เชิงซ้อนดังนั้นมันจะไม่เร็วเท่าที่พัฒนาขึ้นสำหรับเมทริกซ์ {-1,0,1} ฉันสงสัยว่าถ้าใครสามารถใช้เทคนิคบางอย่างที่คุณใช้เพื่อปรับปรุงการใช้งานของเรา นอกจากนี้หากผู้คนสนใจฉันต้องการที่จะเห็นว่าการดำเนินงานของพวกเขาทำอย่างไรเมื่อได้รับจำนวนเชิงซ้อนแทนจำนวนเต็ม ในที่สุดความคิดเห็นคำวิจารณ์การปรับปรุงข้อบกพร่องการปรับปรุงจะได้รับการต้อนรับในพื้นที่เก็บข้อมูลของเราhttps://github.com/XanaduAI/hafnian/

ไชโย!


ยินดีต้อนรับสู่เว็บไซต์! อย่างไรก็ตามคำตอบสำหรับคำถามนี้ควรมีรหัส นี่คงจะดีกว่าเป็นความคิดเห็น (ซึ่งน่าเสียดายที่คุณไม่มีตัวแทนที่จะทำ)
Ad Hoc Garf Hunter

ยินดีต้อนรับสู่ PPCG ในขณะที่คำตอบของคุณอาจแสดงความคิดเห็นได้ดีไซต์นี้ไม่ใช่สำหรับ QA นี่คือไซต์ท้าทายและคำตอบของการท้าทายต้องมีรหัสและไม่ใช่คำอธิบายอย่างอื่น โปรดอัปเดตหรือลบ (หากคุณไม่ต้องการ mods)
Muhammad Salman

รหัสอยู่บน github แต่ฉันเดาว่ามันไร้สาระแค่คัดลอกวางที่นี่
Nicolás Quesada

2
หากมันเหมาะสมกับคำตอบโดยเฉพาะอย่างยิ่งถ้าคุณเป็นหนึ่งในผู้เขียนฉันไม่คิดว่าจะมีอะไรผิดปกติกับการโพสต์วิธีแก้ปัญหาการแข่งขันที่ได้รับการบันทึกไว้อย่างถูกต้องซึ่งตีพิมพ์ที่อื่น
Dennis

@ NicolásQuesadaคำตอบในเว็บไซต์นี้ควรอยู่ในตัวเองถ้าเป็นไปได้หมายความว่าเราไม่ควรไปที่เว็บไซต์อื่นเพื่อดูคำตอบ / รหัสของคุณ
mbomb007
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.