Calcule o Hafnian o mais rápido possível

12

O desafio é escrever o código mais rápido possível para calcular o Hafnian de uma matriz .

O Hafnian de um simétrica 2n-by- 2nmatriz Aé definida como:

Aqui S 2n representa o conjunto de todas as permutações dos números inteiros de 1a 2n, isto é [1, 2n].

O link da wikipedia também fornece uma fórmula de aparência diferente que pode ser interessante (e existem métodos ainda mais rápidos se você procurar mais na web). A mesma página da wiki fala sobre matrizes de adjacência, mas seu código também deve funcionar para outras matrizes. Você pode assumir que todos os valores serão inteiros, mas não que todos sejam positivos.

Também existe um algoritmo mais rápido, mas parece difícil de entender. e Christian Sievers foi o primeiro a implementá-lo (em Haskell).

Nesta questão, as matrizes são todas quadradas e simétricas, com dimensão uniforme.

Implementação de referência (observe que este está usando o método mais lento possível).

Aqui está um exemplo de código python do Sr. 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

A tarefa

Você deve escrever código que, dada uma 2npor 2nmatriz, gera a Hafnian.

Como precisarei testar seu código, seria útil se você desse uma maneira simples de fornecer uma matriz como entrada para seu código, por exemplo, lendo a partir do padrão em. Testarei seu código em matrizes escolhidas aleatoriamente com elementos selecionado de {-1, 0, 1}. O objetivo de testes como esse é reduzir a chance de o Hafnian ser um valor muito grande.

Idealmente, seu código será capaz de ler as matrizes exatamente como as tenho nos exemplos desta pergunta, diretamente do padrão. Essa é a entrada que seria semelhante, [[1,-1],[-1,-1]]por exemplo. Se você quiser usar outro formato de entrada, pergunte e farei o possível para acomodar.

Pontuações e laços

Testarei seu código em matrizes aleatórias de tamanho crescente e pararei na primeira vez em que o código demorar mais de 1 minuto no meu computador. As matrizes de pontuação serão consistentes para todos os envios, a fim de garantir justiça.

Se duas pessoas obtiverem a mesma pontuação, o vencedor será o mais rápido para esse valor de n. Se estes estiverem a 1 segundo um do outro, será o primeiro publicado.

Línguas e bibliotecas

Você pode usar qualquer idioma e bibliotecas disponíveis que desejar, mas nenhuma função preexistente para calcular o Hafnian. Sempre que possível, seria bom poder executar seu código; portanto, inclua uma explicação completa de como executar / compilar seu código no Linux, se possível.

Minha máquina Os tempos serão executados na minha máquina de 64 bits. Esta é uma instalação padrão do ubuntu com 8GB de RAM, processador de oito núcleos AMD FX-8350 e Radeon HD 4250. Isso também significa que eu preciso executar seu código.

Solicite respostas em mais idiomas

Seria ótimo obter respostas em sua linguagem de programação super rápida favorita. Para começar, que tal fortran , nim e ferrugem ?

Entre os melhores

  • 52 por milhas usando C ++ . 30 segundos.
  • 50 por NGN usando C . 50 segundos.
  • 46 por Christian Sievers usando Haskell . 40 segundos.
  • 40 por milhas usando Python 2 + pypy . 41 segundos.
  • 34 por ngn usando Python 3 + pypy . 29 segundos.
  • 28 por Dennis usando Python 3 . 35 segundos. (Pypy é mais lento)

fonte
Existe um limite para os valores absolutos das entradas da matriz? Podemos retornar uma aproximação de ponto flutuante? Temos que usar números inteiros de precisão arbitrários?
Dennis
@Dennis Na prática, utilizarei apenas -1,0,1 para testar (escolhido aleatoriamente). Não quero que seja um grande desafio. Com toda a honestidade, não sei se atingiremos os limites de 64 bits antes que o código fique muito lento para ser executado, mas meu palpite é que não. Atualmente não estamos nem perto disso.
Se as entradas estiverem limitadas a -1,0,1 , isso deve ser mencionado na pergunta. Nosso código precisa funcionar para outras matrizes?
Dennis
@ Dennis Uma versão antiga costumava dizer isso, mas devo ter escrito sobre ela. Eu preferiria que o código não fosse especializado para -1,0,1 entradas, mas suponho que não posso parar com isso.
Você tem mais casos de teste? talvez para n maior ?
miles

Respostas:

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

Isso implementa uma variação do algoritmo 2 de Andreas Björklund: Contando combinações perfeitas tão rápido quanto Ryser .

Compile usando ghcopções de tempo de compilação -O3 -threadede use opções de tempo de execução +RTS -Npara paralelização. Recebe entrada de stdin.

Peneiradores cristãos
fonte
2
Talvez observe isso parallele vectordeve ser instalado?
H.PWiz
@ H.PWiz Ninguém reclamou aqui , mas com certeza, observando que não vai doer. Bem, agora você fez.
Christian Sievers 03/03
@ChristianSievers Acho que não estão reclamando. O OP pode não estar familiarizado com Haskell, portanto, declarar explicitamente o que deve ser instalado para que o código possa cronometrar é uma boa idéia.
217 Dennis
@ Dennis Eu não quis dizer "você se queixou", mas "você notou". E não pensei em reclamar como algo negativo. O OP é o mesmo do desafio ao qual vinculei, portanto não deve haver problema.
Christian Sievers 03/03
N = 40 em 7,5 segundos no TIO ... Cara, isso é rápido!
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())))))

Experimente online!

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

Experimente online! (13s para n = 24)

Baseado na implementação mais rápida do Python no meu outro post. Edite a segunda linha #define S 3em sua máquina de 8 núcleos e compile com g++ -pthread -march=native -O2 -ftree-vectorize.

O trabalho divide pela metade, então o valor de Sdeveria ser log2(#threads). Os tipos podem ser facilmente alterados entre int, long, float, e doublemodificando o valor de #define TYPE.

milhas
fonte
Esta é a resposta principal até agora. Na verdade, seu código não lê a entrada conforme especificado, pois não pode lidar com espaços. Eu tinha que fazer, por exemplotr -d \ < matrix52.txt > matrix52s.txt
@ Lembik Desculpe, só o usei contra a matriz sem espaço do tamanho 24. Corrigido agora para trabalhar com espaços.
milhas
4

Python 3

Isso calcula haf (A) como uma soma memorizada (A [i] [j] * haf (A sem linhas e colunas iej)).

#!/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))
ngn
fonte
3

C

Outro impl do artigo de Andreas Björklund , que é muito mais fácil de entender se você também olhar o código Haskell de Christian Sievers . Nos primeiros níveis de recursão, ele distribui threads de rodízio nas CPUs disponíveis. O último nível da recursão, que responde por metade das invocações, é otimizado manualmente.

Compilar com: gcc -O3 -pthread -march=native; obrigado @Dennis por uma aceleração de 2x

n = 24 em 24s no 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:

A matriz, que é simétrica, é armazenada na forma triangular inferior esquerda. Os índices de triângulo i,jcorrespondem ao índice linear para o T(max(i,j))+min(i,j)qual Té uma macro i*(i-1)/2. Os elementos da matriz são polinômios de grau n. Um polinômio é representado como uma matriz de coeficientes ordenados do termo constante ( p[0]) até o coeficiente de x n ( p[n]). Os valores iniciais da matriz -1,0,1 são primeiro convertidos em polinômios const.

Realizamos uma etapa recursiva com dois argumentos: a meia matriz (ou seja, triângulo) ade polinômios e um polinômio separado p(referido como beta no artigo). Reduzimos o mproblema de tamanho (inicialmente m=2*n) recursivamente para dois problemas de tamanho m-2e retornamos a diferença de seus hafnianos. Um deles é usar o mesmo asem as duas últimas linhas e o mesmo p. Outra é usar o triângulo b[i][j] = a[i][j] + shmul(a[m-1][i],a[m-2][j]) + shmul(a[m-1][j],a[m-2][i])(onde shmulé a operação de multiplicação por deslocamento em polinômios - é como um produto polinomial como de costume, adicionalmente multiplicado pela variável "x"; potências maiores que x ^ n são ignoradas) e o polinômio separado q = p + shmul(p,a[m-1][m-2]). Quando recursão atinge um tamanho 0 a, voltamos a maior coeficiente de p: p[n].

A operação de troca e multiplicação é implementada em função f(x,y,z). Ele modifica zno local. Vagamente falando, é verdade z += shmul(x,y). Essa parece ser a parte mais crítica para o desempenho.

Após o término da recursão, precisamos corrigir o sinal do resultado multiplicando por (-1) n .

ngn
fonte
Você poderia mostrar um exemplo explícito da entrada que seu código aceita? Diga para uma matriz 2 por 2. Além disso, você parece ter jogado seu código! (Este é um desafio que mais código, não um desafio de golfe.)
@Lembik Para o registro, como eu disse no chat, a entrada está no mesmo formato dos exemplos - json (na verdade, ele apenas lê os números e usa n = sqrt (len (input)) / 2). Geralmente escrevo códigos curtos, mesmo quando o golfe não é um requisito.
NGN
Qual é a maior matriz de tamanho que esse novo código deve suportar?
1
-march=nativefará uma grande diferença aqui. Pelo menos no TIO, quase reduz o tempo da parede pela metade.
Dennis
1
Além disso, pelo menos no TIO, o executável produzido pelo gcc será ainda mais rápido.
Dennis
3

Pitão

Essa é uma implementação de referência direta e direta do algoritmo 2 do documento mencionado . As únicas alterações foram manter apenas o valor atual de B , descartando os valores de β atualizando apenas g quando iX e multiplicando polinômios truncados, calculando apenas os valores até o grau 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

Experimente online!

Aqui está uma versão mais rápida, com algumas das otimizações fáceis.

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)

Experimente online!

Para mais diversão, aqui está uma implementação de referência em J.

milhas
fonte
Isso é muito lento em todas as compreensões da lista e na computação de valores equivalentes na diagonal, portanto, não há necessidade de comparar isso.
miles
Pretty awesome!
Muito agradável! Tentei uma coisa semelhante com o sympy, que era chocantemente lento e, embora estivesse correto nos pequenos exemplos, retornei - depois de muito tempo - um resultado errado para a matriz 24 * 24. Não tenho ideia do que está acontecendo lá. - Pela descrição acima do algoritmo 2, a multiplicação polinomial já deve ser truncada.
Christian Sievers
2
Em pmul, use for j in range(n-i):e evite oif
Christian Sievers
1
@Lembik Ele calcula toda a matriz; por um fator de cerca de dois, substitua j != kpor j < k. Ele copia uma submatriz no caso else, o que pode ser evitado quando manipulamos e excluímos as duas últimas, em vez das duas primeiras linhas e colunas. E quando computa com x={1,2,4}e depois com x={1,2,4,6}, repete seus cálculos até i=5. Substituí a estrutura dos dois loops externos com o primeiro loop ie, em seguida, assumindo recursivamente i in Xe i not in X. - BTW, pode ser interessante observar o crescimento do tempo necessário em comparação com outros programas mais lentos.
Christian Sievers
1

Oitava

Esta é basicamente uma cópia da entrada de Dennis , mas otimizada para o Octave. A otimização principal é feita usando a matriz de entrada completa (e sua transposição) e a recursão usando apenas índices da matriz, em vez de criar matrizes reduzidas.

A principal vantagem é a cópia reduzida de matrizes. Embora o Octave não tenha uma diferença entre ponteiros / referências e valores e, funcionalmente, apenas passe por valor, é uma história diferente nos bastidores. Lá, a cópia na gravação (cópia lenta) é usada. Isso significa que, para o código a=1;b=a;b=b+1, a variável bé copiada apenas para um novo local na última instrução, quando é alterada. Desde matine matranspnunca são alterados, eles nunca serão copiados. A desvantagem é que a função gasta mais tempo calculando os índices corretos. Talvez eu precise tentar diferentes variações entre índices numéricos e lógicos para otimizar isso.

Nota importante: a matriz de entrada deve ser int32! Salve a função em um arquivo chamadohaf.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

Exemplo de script de teste:

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

Eu tentei isso usando o TIO e o MATLAB (na verdade, nunca instalei o Octave). Eu imagino fazê-lo funcionar é tão simples quanto sudo apt-get install octave. O comando octavecarregará a GUI do Octave. Se for mais complicado que isso, excluirei esta resposta até fornecer instruções de instalação mais detalhadas.

Sanchises
fonte
0

Recentemente, Andreas Bjorklund, Brajesh Gupt e eu publicamos um novo algoritmo para hafianos de matrizes complexas: https://arxiv.org/pdf/1805.12498.pdf . Para uma matriz n \ times n, ela é dimensionada como n ^ 3 2 ^ {n / 2}.

Se eu entendi corretamente o algoritmo original de Andreas em https://arxiv.org/pdf/1107.4466.pdf, ele é escalado como n ^ 4 2 ^ {n / 2} ou n ^ 3 log (n) 2 ^ {n / 2} se você usou transformadas de Fourier para fazer multiplicações polinomiais.
Nosso algoritmo é especificamente adaptado para matrizes complexas, portanto não será tão rápido quanto o desenvolvido aqui para matrizes {-1,0,1}. Gostaria de saber, no entanto, se alguém pode usar alguns dos truques que você usou para melhorar nossa implementação? Além disso, se as pessoas estiverem interessadas, eu gostaria de ver como é a implementação delas quando recebem números complexos em vez de números inteiros. Finalmente, quaisquer comentários, críticas, melhorias, bugs, melhorias são bem-vindos em nosso repositório https://github.com/XanaduAI/hafnian/

Felicidades!

Nicolás Quesada
fonte
Bem vindo ao site! No entanto, as respostas a esta pergunta devem conter código. É melhor deixar isso como um comentário (que infelizmente você não tem o representante a fazer).
Post Rock Garf Hunter
Bem-vindo ao PPCG. Embora sua resposta possa fazer um bom comentário, o site não é para controle de qualidade. Este é um site de desafios e a resposta a um desafio deve ter código e não uma explicação de outra coisa. Atualização gentilmente ou delete (Se você não vai, mods vai)
Muhammad Salman
Bem, o código está no github, mas acho que não faz sentido copiar e colar aqui.
Nicolás Quesada
2
Se isso se encaixa em uma resposta, especialmente se você é um dos autores, não acho que haja algo errado em postar uma solução competitiva atribuída corretamente que foi publicada em outro lugar.
Dennis
@ NicolásQuesada As respostas neste site devem ser independentes, se possível, o que significa que não precisamos ir a outro site para ver sua resposta / código.
mbomb007