Рассчитайте гафнианский как можно быстрее


12

Задача состоит в том, чтобы написать максимально быстрый код для вычисления матрицы Хафниана .

Hafnian симметричной 2nматрицы с размерностью 2nматрицы Aопределяются следующим образом:

Здесь S 2n представляет множество всех перестановок целых чисел от 1до 2n, то есть [1, 2n].

Ссылка на википедию также дает другую формулу, которая может быть интересна (и даже более быстрые методы существуют, если вы загляните дальше в сеть). На той же вики-странице говорится о матрицах смежности, но ваш код должен работать и для других матриц. Можно предположить, что все значения будут целыми числами, но не все они будут положительными.

Существует также более быстрый алгоритм, но его трудно понять. и Кристиан Сиверс был первым, кто реализовал это (в Хаскеле).

В этом вопросе все матрицы квадратные и симметричные с четной размерностью.

Ссылочная реализация (обратите внимание, что это самый медленный из возможных методов).

Вот пример кода Python от мистера Xcoder.

from itertools import permutations
from math import factorial

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

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

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

print(hafnian(M))
-13

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

print(hafnian(M))
13

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

print(hafnian(M))
83

Задание

Вы должны написать код, который с 2nпомощью 2nматрицы выводит свой гафнианский.

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

В идеале ваш код сможет читать в матрицах точно так же, как я это делал в примерах в этом вопросе, прямо из стандартного. Это означает, что ввод будет выглядеть, [[1,-1],[-1,-1]]например. Если вы хотите использовать другой формат ввода, пожалуйста, спросите, и я сделаю все возможное, чтобы приспособиться.

Счета и связи

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

Если два человека получают одинаковый результат, то победителем считается тот, кто быстрее всех получит это значение n. Если они находятся в пределах 1 секунды друг от друга, то это тот, который размещен первым.

Языки и библиотеки

Вы можете использовать любой доступный язык и библиотеки, которые вам нравятся, но без ранее существовавшей функции для вычисления гафнианского. Там, где это возможно, было бы хорошо иметь возможность запускать ваш код, поэтому, пожалуйста, включите полное объяснение того, как запускать / компилировать ваш код в Linux, если это возможно.

Моя машина Время будет работать на моей 64-битной машине. Это стандартная установка Ubuntu с 8 ГБ ОЗУ, восьмиъядерным процессором AMD FX-8350 и Radeon HD 4250. Это также означает, что мне нужно иметь возможность запускать ваш код.

Звоните для ответов на нескольких языках

Было бы здорово получить ответы на своем любимом супер-быстром языке программирования. Для начала, как насчет Фортрана , Нима и Ржавчины ?

Leaderboard

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

Есть ли предел для абсолютных значений элементов матрицы? Можем ли мы вернуть приближение с плавающей запятой? Нужно ли использовать целые числа произвольной точности?
Денис

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

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

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

У вас есть больше тестов? возможно для большего п ?
миль

Ответы:


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 Андреаса Бьёрклунда: Подсчет идеальных совпадений так же быстро, как и Райзера .

Компиляция с использованием ghcпараметров времени компиляции -O3 -threadedи использования параметров времени выполнения +RTS -Nдля распараллеливания. Принимает ввод от стандартного ввода.


2
Может быть, обратите внимание, что parallelи vectorдолжны быть установлены?
H.PWiz

@ H.PWiz Никто не жаловался здесь , но уверен, отметив , что она не повредит. Ну, теперь ты сделал.
Кристиан Сиверс

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

@ Денис Я имел в виду не «ты жаловался», а «ты это заметил». И я не думал о том, чтобы жаловаться как негативная вещь. ОП такой же, как в задании, с которым я связан, поэтому проблем быть не должно.
Кристиан Сиверс

N = 40 за 7,5 секунд на TIO ... Чувак, это быстро!
Деннис

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

Попробуйте онлайн! (13 с для n = 24)

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

Делит работу пополам, поэтому стоимость Sдолжна быть log2(#threads). Типы могут быть легко изменены между int, long, floatи doubleпутем изменения значения #define TYPE.


Пока это главный ответ. Ваш код фактически не читается во входных данных, как указано, поскольку он не может справиться с пробелами. Я должен был сделать, например,tr -d \ < matrix52.txt > matrix52s.txt

@Lembik Извините, он использовался только для матрицы без пробелов размера 24. Теперь исправлена ​​работа с пробелами.
миль

4

Python 3

Это вычисляет haf (A) как запомненную сумму (A [i] [j] * haf (A без строк и столбцов i и j)).

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

3

С

Еще один след из статьи Андреаса Бьёрклунда , которую намного легче понять, если вы посмотрите на код Хаскелла Кристиана Сиверса . Для первых нескольких уровней рекурсии она распределяет циклические потоки по доступным процессорам. Последний уровень рекурсии, на который приходится половина вызовов, оптимизируется вручную.

Компиляция с: gcc -O3 -pthread -march=native; спасибо @Dennis за ускорение в 2 раза

n = 24 в 24 с на TIO

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

Алгоритм:

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

Мы выполняем рекурсивный шаг с двумя аргументами: полуматрица (то есть треугольник) aполиномов и отдельный полином p(в статье упоминается как бета). Мы уменьшаем mпроблему размера (изначально m=2*n) рекурсивно до двух проблем размера m-2и возвращаем разность их 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операция многократного смещения для многочленов - это как полиномиальное произведение, как обычно, дополнительно умноженное на переменную "x"; степени, превышающие x ^ n, игнорируются) и отдельный многочлен q = p + shmul(p,a[m-1][m-2]). Когда рекурсия поражает размер-0 a, мы возвращаем главный коэффициент р: p[n].

Операция сдвига и умножения реализована в функции f(x,y,z). Он модифицирует zна месте. Грубо говоря, это так z += shmul(x,y). Это, кажется, самая важная часть производительности.

После завершения рекурсии нам нужно исправить знак результата, умножив его на (-1) n .


Не могли бы вы показать явный пример ввода, который принимает ваш код? Скажем для матрицы 2 на 2. Кроме того, вы, кажется, играли в гольф свой код! (Это задача с быстрым кодом, а не

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

Какую матрицу наибольшего размера должен поддерживать этот новый код?

1
-march=nativeбудет иметь большое значение здесь. По крайней мере, на TIO, это почти вдвое сокращает время на стене.
Деннис

1
Кроме того, по крайней мере на TIO исполняемый файл, созданный gcc, будет еще быстрее.
Деннис

3

питон

Это довольно прямая, эталонная реализация алгоритма 2 из упомянутой статьи . Единственными изменениями были только сохранение текущего значения B , отбрасывание значений β путем обновления только g, когда iX , и усеченное полиномиальное умножение только путем вычисления значений до степени n .

from itertools import chain,combinations

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

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

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

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

Попробуйте онлайн!

Вот более быстрая версия с некоторыми простыми оптимизациями.

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

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

Попробуйте онлайн!

Для дополнительного удовольствия, вот справочная реализация в J.


Это довольно медленно для всех представлений списка и для вычисления эквивалентных значений по диагонали, поэтому нет необходимости сравнивать это.
миль

Довольно круто!

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

2
В pmul, использовать for j in range(n-i):и избегатьif
Кристиан Сиверс

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

1

октава

Это в основном копия записи Денниса , но оптимизированная для Octave. Основная оптимизация выполняется путем использования полной входной матрицы (и ее транспонирования) и рекурсии с использованием только индексов матриц, а не путем создания сокращенных матриц.

Основным преимуществом является уменьшенное копирование матриц. Хотя Octave не имеет различий между указателями / ссылками и значениями и функционально выполняет только передачу по значению, за кулисами это другая история. Там используется копирование при записи (ленивая копия). Это означает, что для кода a=1;b=a;b=b+1переменная bкопируется в новое место только в последнем операторе, когда она изменяется. Поскольку matinи matranspникогда не изменяются, они никогда не будут скопированы. Недостатком является то, что функция тратит больше времени на вычисление правильных показателей. Возможно, мне придется попробовать разные варианты между числовыми и логическими показателями, чтобы оптимизировать это.

Важное примечание: матрица ввода должна быть int32! Сохраните функцию в файле с именемhaf.m

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

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

Пример тестового скрипта:

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

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

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


0

Недавно Андреас Бьорклунд, Брайжеш Гупт и я опубликовали новый алгоритм для гафнианов сложных матриц: https://arxiv.org/pdf/1805.12498.pdf . Для матрицы n \ times n она масштабируется как n ^ 3 2 ^ {n / 2}.

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

Ура!


Добро пожаловать на сайт! Однако ответы на этот вопрос должны содержать код. Это лучше оставить в качестве комментария (К сожалению, у вас нет представителя, чтобы сделать).
Специальный охотник за

Добро пожаловать в PPCG. Хотя ваш ответ может дать хороший комментарий, сайт не для QA. Это сайт с запросом, и для ответа на вызов должен быть указан код, а не объяснение чего-либо другого. Пожалуйста, обновите или удалите (если вы этого не сделаете, моды будут)
Мухаммед Салман

Ну, код на github, но я думаю, это просто бессмысленно просто скопировать и вставить его здесь.
Николас Кесада

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

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