Calcola l'hafniano il più rapidamente possibile


12

La sfida è scrivere il codice più veloce possibile per calcolare l' hafniano di una matrice .

L'hafniano di una matrice simmetrica 2n-by- è definito come:2nA

Qui S 2n rappresenta l'insieme di tutte le permutazioni degli interi da 1a 2n, cioè [1, 2n].

Il link di Wikipedia fornisce anche una formula dall'aspetto diverso che potrebbe essere di interesse (e esistono metodi ancora più veloci se si guarda oltre sul web). La stessa pagina wiki parla delle matrici di adiacenza ma il tuo codice dovrebbe funzionare anche per altre matrici. Puoi presumere che i valori saranno tutti numeri interi ma non che siano tutti positivi.

C'è anche un algoritmo più veloce ma sembra difficile da capire. e Christian Sievers fu il primo ad implementarlo (in Haskell).

In questa domanda le matrici sono tutte quadrate e simmetriche con dimensioni pari.

Implementazione di riferimento (notare che sta usando il metodo più lento possibile).

Ecco alcuni esempi di codice Python di 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

L'obiettivo

Dovresti scrivere un codice che, dato 2nda una 2nmatrice, genera il suo Hafnian.

Poiché avrò bisogno di testare il tuo codice, sarebbe utile se tu potessi darmi un modo semplice per dare una matrice come input al tuo codice, ad esempio leggendo dallo standard in. Proverò il tuo codice in matrici scelte casualmente con elementi selezionato da {-1, 0, 1}. Lo scopo di testare in questo modo è ridurre le possibilità che l'Hafnian abbia un valore molto grande.

Idealmente il tuo codice sarà in grado di leggere le matrici esattamente come le ho negli esempi in questa domanda direttamente dallo standard in. Questo è l'input che sembrerebbe [[1,-1],[-1,-1]]per esempio. Se si desidera utilizzare un altro formato di input, si prega di chiedere e farò del mio meglio per accogliere.

Punteggi e pareggi

Metterò alla prova il tuo codice su matrici casuali di dimensioni crescenti e mi fermerò la prima volta che il tuo codice impiegherà più di 1 minuto sul mio computer. Le matrici di punteggio saranno coerenti per tutti gli invii al fine di garantire l'equità.

Se due persone ottengono lo stesso punteggio, il vincitore è quello che è il più veloce per quel valore di n. Se quelli sono entro 1 secondo l'uno dall'altro, è quello pubblicato per primo.

Lingue e biblioteche

È possibile utilizzare qualsiasi lingua e libreria disponibili, ma nessuna funzione preesistente per calcolare l'hafniano. Laddove possibile, sarebbe bene poter eseguire il tuo codice, quindi per favore includi una spiegazione completa su come eseguire / compilare il tuo codice in Linux, se possibile ».

La mia macchina I tempi verranno eseguiti sulla mia macchina a 64 bit. Questa è un'installazione Ubuntu standard con 8 GB di RAM, processore AMD FX-8350 Eight-Core e Radeon HD 4250. Ciò significa anche che devo essere in grado di eseguire il codice.

Chiama per le risposte in più lingue

Sarebbe bello avere risposte nel tuo linguaggio di programmazione super veloce preferito. Per iniziare, che ne dici di fortran , nim e rust ?

Classifica

  • 52 per miglia usando C ++ . 30 secondi.
  • 50 da ngn utilizzando C . 50 secondi.
  • 46 di Christian Sievers utilizzando Haskell . 40 secondi.
  • 40 per miglia usando Python 2 + pypy . 41 secondi.
  • 34 di ngn utilizzando Python 3 + pypy . 29 secondi.
  • 28 di Dennis utilizzando Python 3 . 35 secondi. (Pypy è più lento)

Esiste un limite per i valori assoluti delle voci della matrice? Possiamo restituire un'approssimazione in virgola mobile? Dobbiamo usare numeri interi di precisione arbitraria?
Dennis

@Dennis In pratica userò solo -1,0,1 per il test (scelto a caso). Non voglio che sia una grande sfida int. In tutta onestà, non so se raggiungeremo i limiti di 64 bit prima che il codice diventi troppo lento per essere eseguito, ma suppongo che non lo faremo. Attualmente non siamo vicini a questo.

Se le voci sono limitate a -1,0,1 , questo dovrebbe essere menzionato sulla domanda. Il nostro codice deve funzionare per tutte le altre matrici?
Dennis

@Dennis Una vecchia versione lo diceva, ma devo averlo scritto sopra. Lo preferirei se il codice non fosse specializzato per -1,0,1 voci ma suppongo di non poterlo fermare.

Hai più casi di test? forse per n più grande ?
miglia

Risposte:


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

Questo implementa una variante di Algorithm 2 di Andreas Björklund: Conteggio di abbinamenti perfetti veloce come Ryser .

Compilare utilizzando ghccon le opzioni di compilazione -O3 -threadede utilizzare le opzioni di runtime +RTS -Nper la parallelizzazione. Riceve input dallo stdin.


2
Forse notarlo parallele vectordeve essere installato?
H.Piz,

@ H.PWiz Nessuno si è lamentato qui , ma certo, notando che non farà male. Bene, ora l'hai fatto.
Christian Sievers,

@ChristianSievers Non credo si stiano lamentando. L'OP potrebbe non avere familiarità con Haskell, quindi affermare esplicitamente cosa deve essere installato per essere in grado di programmare il codice è una buona idea.
Dennis

@Dennis Non intendevo "ti sei lamentato" ma "l'hai notato". E non pensavo di lamentarmi come una cosa negativa. L'OP è lo stesso della sfida a cui mi sono collegato, quindi non dovrebbero esserci problemi.
Christian Sievers,

N = 40 in 7,5 secondi su TIO ... Amico, questo è veloce!
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())))))

Provalo online!


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

Provalo online! (13s per n = 24)

Sulla base del più veloce di Python implementazione nel mio altro post. Modifica la seconda riga su #define S 3sulla tua macchina a 8 core e compila con g++ -pthread -march=native -O2 -ftree-vectorize.

Dividi il lavoro a metà, quindi il valore di Sdovrebbe essere log2(#threads). I tipi possono essere facilmente modificati tra int, long, float, e doublemodificando il valore di #define TYPE.


Questa è la risposta principale finora. Il codice in realtà non legge nell'input come specificato in quanto non può far fronte agli spazi. Ho dovuto fare ad esempiotr -d \ < matrix52.txt > matrix52s.txt

@Lembik Siamo spiacenti, l'ho usato solo contro la matrice spaziale di dimensione 24. Risolto il problema ora con gli spazi.
miglia

4

Python 3

Questo calcola haf (A) come somma memorizzata (A [i] [j] * haf (A senza righe e lettere i e 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

Un altro impl del documento di Andreas Björklund , che è molto più facile da capire se si guarda anche al codice Haskell di Christian Sievers . Per i primi livelli della ricorsione, distribuisce thread round-robin su CPU disponibili. L'ultimo livello della ricorsione, che rappresenta la metà delle invocazioni, è ottimizzato a mano.

Compilare con: gcc -O3 -pthread -march=native; grazie @Dennis per un 2x speed-up

n = 24 in 24s su 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;
}

Algoritmo:

La matrice, che è simmetrica, è memorizzata in forma triangolare in basso a sinistra. Gli indici triangolari i,jcorrispondono all'indice lineare T(max(i,j))+min(i,j)dove Tè una macro per i*(i-1)/2. Gli elementi Matrix sono polinomi di grado n. Un polinomio è rappresentato come una matrice di coefficienti ordinati dal termine costante ( p[0]) al coefficiente di x n ( p[n]). I valori iniziali della matrice -1,0,1 vengono prima convertiti in polinomi const.

Eseguiamo un passaggio ricorsivo con due argomenti: la mezza matrice (ovvero il triangolo) adei polinomi e un polinomio separato p(indicato come beta nel documento). Riduciamo il mproblema di dimensioni (inizialmente m=2*n) ricorsivamente a due problemi di dimensioni m-2e restituiamo la differenza dei loro hafniani. Uno di questi è usare lo stesso asenza le sue ultime due righe, e lo stesso p. Un altro è usare il triangolo b[i][j] = a[i][j] + shmul(a[m-1][i],a[m-2][j]) + shmul(a[m-1][j],a[m-2][i])(dove si shmultrova l'operazione di moltiplicazione di spostamento sui polinomi - è come un prodotto polinomiale come al solito, inoltre moltiplicato per la variabile "x"; i poteri superiori a x ^ n vengono ignorati) e il polinomio separato q = p + shmul(p,a[m-1][m-2]). Quando ricorsione colpisce una dimensione-0 a, torniamo il maggiore coefficiente di p: p[n].

L'operazione di spostamento e moltiplicazione è implementata in funzione f(x,y,z). Modifica zsul posto. Parlando liberamente, lo fa z += shmul(x,y). Questa sembra essere la parte più critica per le prestazioni.

Dopo che la ricorsione è terminata, dobbiamo correggere il segno del risultato moltiplicando per (-1) n .


Potresti mostrare un esempio esplicito dell'input accettato dal tuo codice? Dire per una matrice 2 per 2. Inoltre, sembra che tu abbia giocato a golf il tuo codice! (Questa è una sfida con il codice più veloce, non una sfida per il golf.)

@Lembik Per la cronaca, come ho detto in chat, l'input è nello stesso formato degli esempi - json (in realtà, legge solo i numeri e usa n = sqrt (len (input)) / 2). Di solito scrivo codice breve, anche quando il golf non è un requisito.
ngn

Qual è la matrice di dimensioni maggiori che dovrebbe supportare questo nuovo codice?

1
-march=nativefarà una grande differenza qui. Almeno su TIO, quasi dimezza il tempo di parete.
Dennis

1
Inoltre, almeno su TIO, l'eseguibile prodotto da gcc sarà ancora più veloce.
Dennis,

3

Pitone

Si tratta praticamente di un'implementazione di riferimento diretta dell'Algorithm 2 del documento citato . Le uniche modifiche dovevano mantenere solo il valore corrente di B , lasciando cadere i valori di β soltanto aggiornando g quando iX , e troncato polinomio moltiplicazione calcolando solo i valori fino al grado 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

Provalo online!

Ecco una versione più veloce con alcune delle facili ottimizzazioni.

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)

Provalo online!

Per ulteriore divertimento, ecco un'implementazione di riferimento in J.


Questo è piuttosto lento da tutte le comprensioni dell'elenco e dal calcolo di valori equivalenti attraverso la diagonale, quindi non è necessario confrontarlo.
miglia

Abbastanza bello!

Molto bella! Ho provato una cosa simile con sympy che è stata sorprendentemente lenta e, pur essendo corretta per i piccoli esempi, ha restituito - dopo molto tempo - un risultato sbagliato per la matrice 24 * 24. Non ho idea di cosa stia succedendo lì. - Dalla descrizione sopra l'algoritmo 2, la moltiplicazione polinomiale lì è già destinata a essere troncata.
Christian Sievers,

2
In pmul, usa for j in range(n-i):ed evitaif
Christian Sievers il

1
@Lembik Calcola l'intera matrice; per un fattore di circa due sostituire j != kcon j < k. Copia una matrice secondaria nell'altro caso, che può essere evitata quando gestiamo ed eliminiamo le ultime due anziché le prime due righe e colonne. E quando calcola con x={1,2,4}e successivamente con x={1,2,4,6}poi ripete i suoi calcoli fino a i=5. Ho sostituito la struttura dei due anelli esterni con il primo loop acceso ie poi assumendo ricorsivamente i in Xe i not in X. - A proposito, potrebbe essere interessante osservare la crescita del tempo necessario rispetto agli altri programmi più lenti.
Christian Sievers,

1

Ottava

Questa è sostanzialmente una copia della voce di Dennis , ma ottimizzata per Octave. L'ottimizzazione principale viene eseguita utilizzando la matrice di input completa (e la sua trasposizione) e la ricorsione utilizzando solo indici di matrice, anziché creare matrici ridotte.

Il vantaggio principale è la copia ridotta delle matrici. Mentre Octave non ha una differenza tra puntatori / riferimenti e valori e funzionalmente funziona solo in base al valore, è una storia diversa dietro le quinte. Qui viene utilizzato il comando copia su scrittura (copia lazy). Ciò significa che, per il codice a=1;b=a;b=b+1, la variabile bviene copiata in una nuova posizione nell'ultima istruzione, quando viene modificata. Poiché matine matranspnon vengono mai modificati, non verranno mai copiati. Lo svantaggio è che la funzione passa più tempo a calcolare gli indici corretti. Potrei dover provare diverse variazioni tra indici numerici e logici per ottimizzare questo.

Nota importante: matrice di input dovrebbe essere int32! Salva la funzione in un file chiamatohaf.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

Esempio di script di test:

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

Ho provato questo usando TIO e MATLAB (in realtà non ho mai installato Octave). Immagino che farlo funzionare sia semplice sudo apt-get install octave. Il comando octavecaricherà la GUI di Octave. Se è più complicato di questo, eliminerò questa risposta finché non avrò fornito istruzioni di installazione più dettagliate.


0

Recentemente Andreas Bjorklund, Brajesh Gupt ed io abbiamo pubblicato un nuovo algoritmo per gli hafniani di matrici complesse: https://arxiv.org/pdf/1805.12498.pdf . Per una matrice n \ times n si ridimensiona come n ^ 3 2 ^ {n / 2}.

Se capisco correttamente l'algoritmo originale di Andreas da https://arxiv.org/pdf/1107.4466.pdf, si ridimensiona come n ^ 4 2 ^ {n / 2} o n ^ 3 log (n) 2 ^ {n / 2} se hai usato trasformazioni di Fourier per eseguire moltiplicazioni polinomiali.
Il nostro algoritmo è specificamente progettato per matrici complesse, quindi non sarà veloce come quelli sviluppati qui per matrici {-1,0,1}. Mi chiedo comunque se si possono usare alcuni dei trucchi che hai usato per migliorare la nostra implementazione? Inoltre, se le persone sono interessate, vorrei vedere come fanno le loro implementazioni quando vengono dati numeri complessi anziché numeri interi. Infine, eventuali commenti, critiche, miglioramenti, bug, miglioramenti sono i benvenuti nel nostro repository https://github.com/XanaduAI/hafnian/

Saluti!


Benvenuti nel sito! Tuttavia, le risposte a questa domanda dovrebbero contenere codice. Questo sarebbe meglio lasciare un commento, (che sfortunatamente non hai il rappresentante da fare).
Ad Hoc Garf Hunter

Benvenuti in PPCG. Mentre la tua risposta potrebbe fare un bel commento, il sito non è per il controllo qualità. Questo è un sito di sfida e la risposta a una sfida deve avere un codice e non una spiegazione di qualcos'altro. Si prega di aggiornare o eliminare (se non lo farai, le mod lo faranno)
Muhammad Salman

Bene, il codice è su github, ma suppongo che abbia senso semplicemente copiarlo e incollarlo qui.
Nicolás Quesada,

2
Se si adatta a una risposta, specialmente se sei uno degli autori, non credo che ci sia qualcosa di sbagliato nel pubblicare una soluzione competitiva, correttamente attribuita, che era stata pubblicata altrove.
Dennis,

@ NicolásQuesada Le risposte su questo sito dovrebbero essere autosufficienti, se possibile, il che significa che non dovremmo andare su un altro sito per visualizzare la tua risposta / codice.
mbomb007,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.