지속 물을 가능한 빨리 계산하십시오


26

문제는 매트릭스영속성 을 계산할 수있는 가장 빠른 코드를 작성하는 것 입니다.

상설 n-by- n매트릭스 A(= ai,j)으로 정의

여기에 이미지 설명을 입력하십시오

여기에 S_n의 모든 순열 세트가 표시 [1, n]됩니다.

예를 들어 (위키에서) :

여기에 이미지 설명을 입력하십시오

이 질문에 행렬은 모두 정사각형 만 값이됩니다 -11그들입니다.

입력:

[[ 1 -1 -1  1]
 [-1 -1 -1  1]
 [-1  1 -1  1]
 [ 1 -1 -1  1]]

퍼머넌트:

-4

입력:

[[-1 -1 -1 -1]
 [-1  1 -1 -1]
 [ 1 -1 -1 -1]
 [ 1 -1  1 -1]]

퍼머넌트:

0

입력:

[[ 1 -1  1 -1 -1 -1 -1 -1]
 [-1 -1  1  1 -1  1  1 -1]
 [ 1 -1 -1 -1 -1  1  1  1]
 [-1 -1 -1  1 -1  1  1  1]
 [ 1 -1 -1  1  1  1  1 -1]
 [-1  1 -1  1 -1  1  1 -1]
 [ 1 -1  1 -1  1 -1  1 -1]
 [-1 -1  1 -1  1  1  1  1]]

퍼머넌트:

192

입력:

[[1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1],
 [1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1],
 [-1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1],
 [-1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1],
 [-1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1],
 [1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1],
 [1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1],
 [1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
 [1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1],
 [-1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1],
 [-1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1],
 [1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
 [-1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1],
 [1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1],
 [1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
 [1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
 [-1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1],
 [1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1],
 [1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1],
 [-1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1]]

퍼머넌트:

1021509632

작업

nby n매트릭스가 주어지면 영구적으로 출력 되는 코드를 작성해야합니다 .

코드를 테스트해야하므로 표준 입력에서 읽는 것과 같이 코드를 입력 할 수있는 간단한 방법을 제공 할 수 있다면 도움이 될 것입니다.

지속 물이 클 수 있음에 유의하십시오 (1의 행렬은 모두 극단적 인 경우입니다).

점수와 관계

크기가 커지는 임의의 +1 행렬에서 코드를 테스트하고 컴퓨터에서 코드가 처음 1 분 이상 걸리면 중지합니다. 공정성을 보장하기 위해 모든 제출물에 대해 점수 매트릭스가 일관됩니다.

두 사람이 같은 점수를 얻는다면 그 값이 가장 빠른 승자가 n됩니다. 그것들이 서로 1 초 이내에 있다면 그것은 먼저 게시 된 것입니다.

언어와 라이브러리

사용 가능한 언어와 라이브러리를 사용할 수 있지만 기존 기능을 사용하여 영구 물을 계산할 수는 없습니다. 가능하다면 코드를 실행하는 것이 좋을 것이므로 가능한 경우 리눅스에서 코드를 실행 / 컴파일하는 방법에 대한 자세한 설명을 포함하십시오. '

참조 구현

작은 행렬에 대한 영속성을 계산하기 위해 다른 언어로 된 많은 코드가있는 코드 골프 질문 문제 가 이미 있습니다 . MathematicaMaple 은 모두 액세스 할 수 있으면 영구적으로 구현됩니다.

내 컴퓨터 타이밍이 64 비트 컴퓨터에서 실행됩니다. 이것은 8GB RAM, AMD FX-8350 8 코어 프로세서 및 Radeon HD 4250이 포함 된 표준 우분투 설치입니다. 또한 코드를 실행할 수 있어야합니다.

내 컴퓨터에 대한 저수준 정보

cat /proc/cpuinfo/|grep flags 준다

플래그 : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpexgb rdtscp lm constant_tsc rep_good nopl nonssemps pqds pqds pqds pqds pqsd f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs xop skinit wdt lwp fma4 tce nodeid_msr tbm topoext perfctr_core perfctr_nb cpb hw_pstate vmmcall bmirvssap_abps_bpsbcast tlock_bpsbcast tlock_bbpsbcast tlock_bmpsbcast tlock_bbpsbcast tlock_bbpsbcast tlock_bcast

나는 큰 Int 문제로 고통받지 않는 밀접한 관련 후속 다국어 질문을 할 것이므로 Scala , Nim , Julia , Rust , Bash를 좋아하는 사람들 도 그들의 언어를 과시 할 수 있습니다.

리더 보드

  • n = 33 (45 초. n = 34의 경우 64 초). g ++ 5.4.0 을 사용하는 C ++의 Ton Hospel
  • n = 32 (32 초). Ton Hospel의 gcc 플래그를 사용하는 gcc 5.4.0의 C 에서 Dennis .
  • n = 31 (54 초). 기독교 승인 Sievers 에서 하스켈
  • n = 31 (60 초). 프리모 에서 rpython
  • n = 30 (26 초). ezrast 에서
  • n = 28 (49 초). XNOR파이썬 +는 5.4.1을 pypy
  • n = 22 (25 초). 오두막파이썬 +는 5.4.1을 pypy

참고 . 실제로 Dennis와 Ton Hospel의시기는 신비한 이유로 매우 다양합니다. 예를 들어 웹 브라우저를로드 한 후에는 더 빠른 것 같습니다! 인용 된 시간은 내가 한 모든 테스트에서 가장 빠릅니다.


5
나는 'Lembik'이라고 생각한 첫 번째 문장을 읽었습니다.
orlp

@orlp :) 오랜만입니다.

1
@Lembik 나는 큰 테스트 사례를 추가했습니다. 누군가가 확인하기를 기다릴 것입니다.
xnor

2
정답을 저장하기 위해 배정도 부동 소수점을 사용하기 때문에 답 중 하나는 대략적인 결과를 인쇄합니다. 허용 되나요?
Dennis

1
@ChristianSievers 나는 간판을 가지고 마술을 할 수있을 거라 생각했지만 튀어 나오지 않았다 ...
Socratic Phoenix

답변:


13

gcc C ++ n ≈ 36 (시스템에서 57 초)

모든 열 합계가 짝수이면 업데이트를 위해 Glynn 수식을 회색 코드와 함께 사용하고, 그렇지 않으면 Ryser의 방법을 사용합니다. 스레드 및 벡터화. AVX에 최적화되어 있으므로 구형 프로세서에서는 그다지 기대하지 마십시오. n>=35부호있는 128 비트 누산기가 오버플로되므로 시스템이 충분히 빠르더라도 +1 만있는 행렬은 신경 쓰지 마십시오 . 랜덤 매트릭스의 경우 오버플로에 영향을 미치지 않을 것입니다. 들어 n>=37내부 승수 모두를위한 오버 플로우 시작합니다 1/-1매트릭스. 따라서이 프로그램 만 사용하십시오 n<=36.

STDIN의 행렬 요소를 공백으로 분리하십시오.

permanent
1 2
3 4
^D

permanent.cpp:

/*
  Compile using something like:
    g++ -Wall -O3 -march=native -fstrict-aliasing -std=c++11 -pthread -s permanent.cpp -o permanent
*/

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstdint>
#include <climits>
#include <array>
#include <vector>
#include <thread>
#include <future>
#include <ctgmath>
#include <immintrin.h>

using namespace std;

bool const DEBUG = false;
int const CACHE = 64;

using Index  = int_fast32_t;
Index glynn;
// Number of elements in our vectors
Index const POW   = 3;
Index const ELEMS = 1 << POW;
// Over how many floats we distribute each row
Index const WIDTH = 9;
// Number of bits in the fraction part of a floating point number
int const FLOAT_MANTISSA = 23;
// Type to use for the first add/multiply phase
using Sum  = float;
using SumN = __restrict__ Sum __attribute__((vector_size(ELEMS*sizeof(Sum))));
// Type to convert to between the first and second phase
using ProdN = __restrict__ int32_t __attribute__((vector_size(ELEMS*sizeof(int32_t))));
// Type to use for the third and last multiply phase.
// Also used for the final accumulator
using Value = __int128;
using UValue = unsigned __int128;

// Wrap Value so C++ doesn't really see it and we can put it in vectors etc.
// Needed since C++ doesn't fully support __int128
struct Number {
    Number& operator+=(Number const& right) {
        value += right.value;
        return *this;
    }
    // Output the value
    void print(ostream& os, bool dbl = false) const;
    friend ostream& operator<<(ostream& os, Number const& number) {
        number.print(os);
        return os;
    }

    Value value;
};

using ms = chrono::milliseconds;

auto nr_threads = thread::hardware_concurrency();
vector<Sum> input;

// Allocate cache aligned datastructures
template<typename T>
T* alloc(size_t n) {
    T* mem = static_cast<T*>(aligned_alloc(CACHE, sizeof(T) * n));
    if (mem == nullptr) throw(bad_alloc());
    return mem;
}

// Work assigned to thread k of nr_threads threads
Number permanent_part(Index n, Index k, SumN** more) {
    uint64_t loops = (UINT64_C(1) << n) / nr_threads;
    if (glynn) loops /= 2;
    Index l = loops < ELEMS ? loops : ELEMS;
    loops /= l;
    auto from = loops * k;
    auto to   = loops * (k+1);

    if (DEBUG) cout << "From=" << from << "\n";
    uint64_t old_gray = from ^ from/2;
    uint64_t bit = 1;
    bool bits = (to-from) & 1;

    Index nn = (n+WIDTH-1)/WIDTH;
    Index ww = nn * WIDTH;
    auto column = alloc<SumN>(ww);
    for (Index i=0; i<n; ++i)
        for (Index j=0; j<ELEMS; ++j) column[i][j] = 0;
    for (Index i=n; i<ww; ++i)
        for (Index j=0; j<ELEMS; ++j) column[i][j] = 1;
    Index b;
    if (glynn) {
        b = n > POW+1 ? n - POW - 1: 0;
        auto c = n-1-b;
        for (Index k=0; k<l; k++) {
            Index gray = k ^ k/2;
            for (Index j=0; j< c; ++j)
                if (gray & 1 << j)
                    for (Index i=0; i<n; ++i)
                        column[i][k] -= input[(b+j)*n+i];
                else
                    for (Index i=0; i<n; ++i)
                        column[i][k] += input[(b+j)*n+i];
        }
        for (Index i=0; i<n; ++i)
            for (Index k=0; k<l; k++)
                column[i][k] += input[n*(n-1)+i];

        for (Index k=1; k<l; k+=2)
            column[0][k] = -column[0][k];

        for (Index i=0; i<b; ++i, bit <<= 1) {
            if (old_gray & bit) {
                bits = bits ^ 1;
                for (Index j=0; j<ww; ++j)
                    column[j] -= more[i][j];
            } else {
                for (Index j=0; j<ww; ++j)
                    column[j] += more[i][j];
            }
        }

        for (Index i=0; i<n; ++i)
            for (Index k=0; k<l; k++)
                column[i][k] /= 2;
    } else {
        b = n > POW ? n - POW : 0;
        auto c = n-b;
        for (Index k=0; k<l; k++) {
            Index gray = k ^ k/2;
            for (Index j=0; j<c; ++j)
                if (gray & 1 << j)
                    for (Index i=0; i<n; ++i)
                        column[i][k] -= input[(b+j)*n+i];
        }

        for (Index k=1; k<l; k+=2)
            column[0][k] = -column[0][k];

        for (Index i=0; i<b; ++i, bit <<= 1) {
            if (old_gray & bit) {
                bits = bits ^ 1;
                for (Index j=0; j<ww; ++j)
                    column[j] -= more[i][j];
            }
        }
    }

    if (DEBUG) {
        for (Index i=0; i<ww; ++i) {
            cout << "Column[" << i << "]=";
            for (Index j=0; j<ELEMS; ++j) cout << " " << column[i][j];
            cout << "\n";
        }
    }

    --more;
    old_gray = (from ^ from/2) | UINT64_C(1) << b;
    Value total = 0;
    SumN accu[WIDTH];
    for (auto p=from; p<to; ++p) {
        uint64_t new_gray = p ^ p/2;
        uint64_t bit = old_gray ^ new_gray;
        Index i = __builtin_ffsl(bit);
        auto diff = more[i];
        auto c = column;
        if (new_gray > old_gray) {
            // Phase 1 add/multiply.
            // Uses floats until just before loss of precision
            for (Index i=0; i<WIDTH; ++i) accu[i] = *c++ -= *diff++;

            for (Index j=1; j < nn; ++j)
                for (Index i=0; i<WIDTH; ++i) accu[i] *= *c++ -= *diff++;
        } else {
            // Phase 1 add/multiply.
            // Uses floats until just before loss of precision
            for (Index i=0; i<WIDTH; ++i) accu[i] = *c++ += *diff++;

            for (Index j=1; j < nn; ++j)
                for (Index i=0; i<WIDTH; ++i) accu[i] *= *c++ += *diff++;
        }

        if (DEBUG) {
            cout << "p=" << p << "\n";
            for (Index i=0; i<ww; ++i) {
                cout << "Column[" << i << "]=";
                for (Index j=0; j<ELEMS; ++j) cout << " " << column[i][j];
                cout << "\n";
            }
        }

        // Convert floats to int32_t
        ProdN prod32[WIDTH] __attribute__((aligned (32)));
        for (Index i=0; i<WIDTH; ++i)
            // Unfortunately gcc doesn't recognize the static_cast<int32_t>
            // as a vector pattern, so force it with an intrinsic
#ifdef __AVX__
            //prod32[i] = static_cast<ProdN>(accu[i]);
            reinterpret_cast<__m256i&>(prod32[i]) = _mm256_cvttps_epi32(accu[i]);
#else   // __AVX__
            for (Index j=0; j<ELEMS; ++j)
                prod32[i][j] = static_cast<int32_t>(accu[i][j]);
#endif  // __AVX__

        // Phase 2 multiply. Uses int64_t until just before overflow
        int64_t prod64[3][ELEMS];
        for (Index i=0; i<3; ++i) {
            for (Index j=0; j<ELEMS; ++j)
                prod64[i][j] = static_cast<int64_t>(prod32[i][j]) * prod32[i+3][j] * prod32[i+6][j];
        }
        // Phase 3 multiply. Collect into __int128. For large matrices this will
        // actually overflow but that's ok as long as all 128 low bits are
        // correct. Terms will cancel and the final sum can fit into 128 bits
        // (This will start to fail at n=35 for the all 1 matrix)
        // Strictly speaking this needs the -fwrapv gcc option
        for (Index j=0; j<ELEMS; ++j) {
            auto value = static_cast<Value>(prod64[0][j]) * prod64[1][j] * prod64[2][j];
            if (DEBUG) cout << "value[" << j << "]=" << static_cast<double>(value) << "\n";
            total += value;
        }
        total = -total;

        old_gray = new_gray;
    }

    return bits ? Number{-total} : Number{total};
}

// Prepare datastructures, Assign work to threads
Number permanent(Index n) {
    Index nn = (n+WIDTH-1)/WIDTH;
    Index ww = nn*WIDTH;

    Index rows  = n > (POW+glynn) ? n-POW-glynn : 0;
    auto data = alloc<SumN>(ww*(rows+1));
    auto pointers = alloc<SumN *>(rows+1);
    auto more = &pointers[0];
    for (Index i=0; i<rows; ++i)
        more[i] = &data[ww*i];
    more[rows] = &data[ww*rows];
    for (Index j=0; j<ww; ++j)
        for (Index i=0; i<ELEMS; ++i)
            more[rows][j][i] = 0;

    Index loops = n >= POW+glynn ? ELEMS : 1 << (n-glynn);
    auto a = &input[0];
    for (Index r=0; r<rows; ++r) {
        for (Index j=0; j<n; ++j) {
            for (Index i=0; i<loops; ++i)
                more[r][j][i] = j == 0 && i %2 ? -*a : *a;
            for (Index i=loops; i<ELEMS; ++i)
                more[r][j][i] = 0;
            ++a;
        }
        for (Index j=n; j<ww; ++j)
            for (Index i=0; i<ELEMS; ++i)
                more[r][j][i] = 0;
    }

    if (DEBUG)
        for (Index r=0; r<=rows; ++r)
            for (Index j=0; j<ww; ++j) {
                cout << "more[" << r << "][" << j << "]=";
                for (Index i=0; i<ELEMS; ++i)
                    cout << " " << more[r][j][i];
                cout << "\n";
            }

    // Send work to threads...
    vector<future<Number>> results;
    for (auto i=1U; i < nr_threads; ++i)
        results.emplace_back(async(DEBUG ? launch::deferred: launch::async, permanent_part, n, i, more));
    // And collect results
    auto r = permanent_part(n, 0, more);
    for (auto& result: results)
        r += result.get();

    free(data);
    free(pointers);

    // For glynn we should double the result, but we will only do this during
    // the final print. This allows n=34 for an all 1 matrix to work
    // if (glynn) r *= 2;
    return r;
}

// Print 128 bit number
void Number::print(ostream& os, bool dbl) const {
    const UValue BILLION = 1000000000;

    UValue val;
    if (value < 0) {
        os << "-";
        val = -value;
    } else
        val = value;
    if (dbl) val *= 2;

    uint32_t output[5];
    for (int i=0; i<5; ++i) {
        output[i] = val % BILLION;
        val /= BILLION;
    }
    bool print = false;
    for (int i=4; i>=0; --i) {
        if (print) {
            os << setfill('0') << setw(9) << output[i];
        } else if (output[i] || i == 0) {
            print = true;
            os << output[i];
        }
    }
}

// Read matrix, check for sanity
void my_main() {
    Sum a;
    while (cin >> a)
        input.push_back(a);

    size_t n = sqrt(input.size());
    if (input.size() != n*n)
        throw(logic_error("Read " + to_string(input.size()) +
                          " elements which does not make a square matrix"));

    vector<double> columns_pos(n, 0);
    vector<double> columns_neg(n, 0);
    Sum *p = &input[0];
    for (size_t i=0; i<n; ++i)
        for (size_t j=0; j<n; ++j, ++p) {
            if (*p >= 0) columns_pos[j] += *p;
            else         columns_neg[j] -= *p;
        }
    std::array<double,WIDTH> prod;
    prod.fill(1);

    int32_t odd = 0;
    for (size_t j=0; j<n; ++j) {
        prod[j%WIDTH] *= max(columns_pos[j], columns_neg[j]);
        auto sum = static_cast<int32_t>(columns_pos[j] - columns_neg[j]);
        odd |= sum;
    }
    glynn = (odd & 1) ^ 1;
    for (Index i=0; i<WIDTH; ++i)
        // A float has an implicit 1. in front of the fraction so it can
        // represent 1 bit more than the mantissa size. And 1 << (mantissa+1)
        // itself is in fact representable
        if (prod[i] && log2(prod[i]) > FLOAT_MANTISSA+1)
            throw(range_error("Values in matrix are too large. A subproduct reaches " + to_string(prod[i]) + " which doesn't fit in a float without loss of precision"));

    for (Index i=0; i<3; ++i) {
        auto prod3 = prod[i] * prod[i+3] * prod[i+6];
        if (log2(prod3) >= CHAR_BIT*sizeof(int64_t)-1)
            throw(range_error("Values in matrix are too large. A subproduct reaches " + to_string(prod3) + " which doesn't fit in an int64"));
    }

    nr_threads = pow(2, ceil(log2(static_cast<float>(nr_threads))));
    uint64_t loops = UINT64_C(1) << n;
    if (glynn) loops /= 2;
    if (nr_threads * ELEMS > loops)
        nr_threads = max(loops / ELEMS, UINT64_C(1));
    // if (DEBUG) nr_threads = 1;

    cout << n << " x " << n << " matrix, method " << (glynn ? "Glynn" : "Ryser") << ", " << nr_threads << " threads" << endl;

    // Go for the actual calculation
    auto start = chrono::steady_clock::now();
    auto perm = permanent(n);
    auto end = chrono::steady_clock::now();
    auto elapsed = chrono::duration_cast<ms>(end-start).count();

    cout << "Permanent=";
    perm.print(cout, glynn);
    cout << " (" << elapsed / 1000. << " s)" << endl;
}

// Wrapper to print any exceptions
int main() {
    try {
        my_main();
    } catch(exception& e) {
        cerr << "Error: " << e.what() << endl;
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

플래그 : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonssempcs pqd psqqd F16C lahf_lm cmp_legacy SVM extapic cr8_legacy ABM SSE4A misalignsse 3dnowprefetch osvw IBS XOP skinit WDT LWP fma4 TCE nodeid_msr TBM topoext perfctr_core perfctr_nb CPB hw_pstate vmmcall BMI1 아라 NPT lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold

코드를 실행하기 위해 테스트 하네스를 디버깅하고 있지만 매우 빠릅니다. 감사합니다! int 크기가 클수록 속도 문제가 발생할 수 있는지 궁금합니다. accu.org/index.php/articles/1849 가 관심있는 경우를 보았습니다 .

테스트 하네스에서 사용하기가 매우 어려워서 quick_exit를 제거하도록 코드를 수정해야했습니다. 흥미롭게도 위키가 다른 위키가 두 배 빠르다고 주장 할 때 Ryser의 공식을 사용하는 이유는 무엇입니까?

@Lembik 나는 Ryser의 공식으로 전환했습니다. 다른 하나와 함께 축소해야하기 2 << (n-1)때문에 int128 누산기가 그 지점보다 훨씬 전에 오버플로되었음을 의미합니다.
Ton Hospel

1
@Lembik 예 :-)
Ton Hospel

7

C99, n ≈ 33 (35 초)

#include <stdint.h>
#include <stdio.h>

#define CHUNK_SIZE 12
#define NUM_THREADS 8

#define popcnt __builtin_popcountll
#define BILLION (1000 * 1000 * 1000)
#define UPDATE_ROW_PPROD() \
    update_row_pprod(row_pprod, row, rows, row_sums, mask, mask_popcnt)

typedef __int128 int128_t;

static inline int64_t update_row_pprod
(
    int64_t* row_pprod, int64_t row, int64_t* rows,
    int64_t* row_sums, int64_t mask, int64_t mask_popcnt
)
{
    int64_t temp = 2 * popcnt(rows[row] & mask) - mask_popcnt;

    row_pprod[0] *= temp;
    temp -= 1;
    row_pprod[1] *= temp;
    temp -= row_sums[row];
    row_pprod[2] *= temp;
    temp += 1;
    row_pprod[3] *= temp;

    return row + 1;
}

int main(int argc, char* argv[])
{
    int64_t size = argc - 1, rows[argc - 1];
    int64_t row_sums[argc - 1];
    int128_t permanent = 0, sign = size & 1 ? -1 : 1;

    if (argc == 2)
    {
        printf("%d\n", argv[1][0] == '-' ? -1 : 1);
        return 0;
    }

    for (int64_t row = 0; row < size; row++)
    {
        char positive = argv[row + 1][0] == '+' ? '-' : '+';

        sign *= ',' - positive;
        rows[row] = row_sums[row] = 0;

        for (char* p = &argv[row + 1][1]; *p; p++)
        {
            rows[row] <<= 1;
            rows[row] |= *p == positive;
            row_sums[row] += *p == positive;
        }

        row_sums[row] = 2 * row_sums[row] - size;
    }

    #pragma omp parallel for reduction(+:permanent) num_threads(NUM_THREADS)
    for (int64_t mask = 1; mask < 1LL << (size - 1); mask += 2)
    {
        int64_t mask_popcnt = popcnt(mask);
        int64_t row = 0;
        int128_t row_prod = 1 - 2 * (mask_popcnt & 1);
        int128_t row_prod_high = -row_prod;
        int128_t row_prod_inv = row_prod;
        int128_t row_prod_inv_high = -row_prod;

        for (int64_t chunk = 0; chunk < size / CHUNK_SIZE; chunk++)
        {
            int64_t row_pprod[4] = {1, 1, 1, 1};

            for (int64_t i = 0; i < CHUNK_SIZE; i++)
                row = UPDATE_ROW_PPROD();

            row_prod *= row_pprod[0], row_prod_high *= row_pprod[1];
            row_prod_inv *= row_pprod[3], row_prod_inv_high *= row_pprod[2];
        }

        int64_t row_pprod[4] = {1, 1, 1, 1};

        while (row < size)
            row = UPDATE_ROW_PPROD();

        row_prod *= row_pprod[0], row_prod_high *= row_pprod[1];
        row_prod_inv *= row_pprod[3], row_prod_inv_high *= row_pprod[2];
        permanent += row_prod + row_prod_high + row_prod_inv + row_prod_inv_high;
    }

    permanent *= sign;

    if (permanent < 0)
        printf("-"), permanent *= -1;

    int32_t output[5], print = 0;

    output[0] = permanent % BILLION, permanent /= BILLION;
    output[1] = permanent % BILLION, permanent /= BILLION;
    output[2] = permanent % BILLION, permanent /= BILLION;
    output[3] = permanent % BILLION, permanent /= BILLION;
    output[4] = permanent % BILLION;

    if (output[4])
        printf("%u", output[4]), print = 1;
    if (print)
        printf("%09u", output[3]);
    else if (output[3])
        printf("%u", output[3]), print = 1;
    if (print)
        printf("%09u", output[2]);
    else if (output[2])
        printf("%u", output[2]), print = 1;
    if (print)
        printf("%09u", output[1]);
    else if (output[1])
        printf("%u", output[1]), print = 1;
    if (print)
        printf("%09u\n", output[0]);
    else
        printf("%u\n", output[0]);
}

입력은 현재 약간 성가시다. 행을 명령 행 인수로 사용합니다. 여기서 각 항목은 부호로 표시됩니다. 즉, +1 을 나타내고 --1을 나타냅니다 .

시운전

$ gcc -Wall -std=c99 -march=native -Ofast -fopenmp -fwrapv -o permanent permanent.c
$ ./permanent +--+ ---+ -+-+ +--+
-4
$ ./permanent ---- -+-- +--- +-+-
0
$ ./permanent +-+----- --++-++- +----+++ ---+-+++ +--++++- -+-+-++- +-+-+-+- --+-++++
192
$ ./permanent +-+--+++----++++-++- +-+++++-+--+++--+++- --+++----+-+++---+-- ---++-++++++------+- -+++-+++---+-+-+++++ +-++--+-++++-++-+--- +--+---+-++++---+++- +--+-++-+++-+-+++-++ +-----+++-----++-++- --+-+-++-+-++++++-++ -------+----++++---- ++---++--+-++-++++++ -++-----++++-----+-+ ++---+-+----+-++-+-+ +++++---+++-+-+++-++ +--+----+--++-+----- -+++-++--+++--++--++ ++--++-++-+++-++-+-+ +++---+--++---+----+ -+++-------++-++-+--
1021509632
$ time ./permanent +++++++++++++++++++++++++++++++{,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,}     # 31
8222838654177922817725562880000000

real    0m8.365s
user    1m6.504s
sys     0m0.000s
$ time ./permanent ++++++++++++++++++++++++++++++++{,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,}   # 32
263130836933693530167218012160000000

real    0m17.013s
user    2m15.226s
sys     0m0.001s
$ time ./permanent +++++++++++++++++++++++++++++++++{,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,} # 33
8683317618811886495518194401280000000

real    0m34.592s
user    4m35.354s
sys     0m0.001s

개선에 대한 아이디어가 있습니까?
xnor

@ xnor 몇 가지. SSE를 사용하여 팩형 곱셈을 시도하고 큰 루프를 부분적으로 풀어보고 싶습니다 popcnt. 그것이 시간을 절약한다면, 다음 큰 장애물은 정수 타입입니다. 무작위로 생성 된 행렬의 경우, 영구적은 비교적 작습니다. 실제 계산을 수행하기 전에 바운드를 계산하는 쉬운 방법을 찾을 수 있다면 모든 것을 큰 조건부로 감쌀 수 있습니다.
Dennis

@Dennis 루프를 풀 때 가장 작은 행은 맨 위 행을 모두 +1로 만드는 것입니다.
xnor

그래 @xnor, 나는 어떤 점에서 그 시도,하지만 (작동하지 않았다 뭔가 다른 시도의 변화를 되돌아 전혀를 ). 병목 현상은 정수 곱셈 (64 비트에서는 느리고 128 비트에서는 느림) 인 것처럼 보이 므로 SSE가 조금 도움이되기를 바랍니다.
Dennis

1
@Dennis 알겠습니다. 경계에 대해, 명백하지 않은 하나의 경계는 연산자 규범 | Per (M) | <= | M | ^ n입니다.
xnor

5

파이썬 2, n ≈ 28

from operator import mul

def fast_glynn_perm(M):
    row_comb = [sum(c) for c in zip(*M)]
    n=len(M)

    total = 0
    old_grey = 0 
    sign = +1

    binary_power_dict = {2**i:i for i in range(n)}
    num_loops = 2**(n-1)

    for bin_index in xrange(1, num_loops + 1):  
        total += sign * reduce(mul, row_comb)

        new_grey = bin_index^(bin_index/2)
        grey_diff = old_grey ^ new_grey
        grey_diff_index = binary_power_dict[grey_diff]

        new_vector = M[grey_diff_index]
        direction = 2 * cmp(old_grey,new_grey)      

        for i in range(n):
            row_comb[i] += new_vector[i] * direction

        sign = -sign
        old_grey = new_grey

    return total/num_loops

Glynn 공식회색 코드 와 함께 사용업데이트를 위해 합니다. n=23내 컴퓨터에서 1 분 안에 실행됩니다 . 더 빠른 언어와 더 나은 데이터 구조로 이것을 더 잘 구현할 수 있습니다. 이것은 행렬이 ± 1 값이라는 것을 사용하지 않습니다.

Ryser 공식 구현은 매우 유사하여 ± 1 벡터가 아닌 모든 0/1 벡터 계수로 합산됩니다. Glynn의 공식보다 약 2 배의 시간이 걸리는 반면, Glynn의 절반은로 시작하는 것에 만 대칭을 사용 +1합니다.

from operator import mul

def fast_ryser_perm(M):
    n=len(M)
    row_comb = [0] * n

    total = 0
    old_grey = 0 
    sign = +1

    binary_power_dict = {2**i:i for i in range(n)}
    num_loops = 2**n

    for bin_index in range(1, num_loops) + [0]: 
        total += sign * reduce(mul, row_comb)

        new_grey = bin_index^(bin_index/2)
        grey_diff = old_grey ^ new_grey
        grey_diff_index = binary_power_dict[grey_diff]

        new_vector = M[grey_diff_index]
        direction = cmp(old_grey, new_grey)

        for i in range(n):
            row_comb[i] += new_vector[i] * direction

        sign = -sign
        old_grey = new_grey

    return total * (-1)**n

대단해 파이도 테스트 했습니까?

@Lembik 아니요, 많이 설치되지 않았습니다.
xnor

나는 그것을 테스트 할 때 pypy를 사용할 것이다. 다른 빠른 공식을 구현하는 방법을 알 수 있습니까? 혼란 스럽습니다.

@Lembik 다른 빠른 공식은 무엇입니까?
xnor

1
참고로, 내 컴퓨터 에서 44.6 초 만 pypy에 쉽게 계산할 수있었습니다 n=28. Lembik의 시스템은 조금 더 빠르지 않으면 속도를내는 것과 상당히 비슷해 보입니다.
Kade

4

녹 + extprim

그레이 코드를 구현 한이 간단한 Ryser는 랩톱에서 n = 31을 실행 하는 데 약 65 90 초가 걸립니다 . 나는 당신의 기계가 60 대 미만으로 도착할 것이라고 생각합니다. 에 extprim 1.1.1을 사용하고 i128있습니다.

나는 Rust를 사용한 적이 없으며 내가 무엇을하고 있는지 전혀 모른다. 다른 것 이외의 컴파일러 옵션 cargo build --release은 없습니다. 의견 / 제안 / 최적화에 감사드립니다.

호출은 Dennis의 프로그램과 동일합니다.

use std::env;
use std::thread;
use std::sync::Arc;
use std::sync::mpsc;

extern crate extprim;
use extprim::i128::i128;

static THREADS : i64 = 8; // keep this a power of 2.

fn main() {
  // Read command line args for the matrix, specified like
  // "++- --- -+-" for [[1, 1, -1], [-1, -1, -1], [-1, 1, -1]].
  let mut args = env::args();
  args.next();

  let mat : Arc<Vec<Vec<i64>>> = Arc::new(args.map( |ss|
    ss.trim().bytes().map( |cc| if cc == b'+' {1} else {-1}).collect()
  ).collect());

  // Figure how many iterations each thread has to do.
  let size = 2i64.pow(mat.len() as u32);
  let slice_size = size / THREADS; // Assumes divisibility.

  let mut accumulator : i128;
  if slice_size >= 4 { // permanent() requires 4 divides slice_size
    let (tx, rx) = mpsc::channel();

    // Launch threads.
    for ii in 0..THREADS {
      let mat = mat.clone();
      let tx = tx.clone();
      thread::spawn(move ||
        tx.send(permanent(&mat, ii * slice_size, (ii+1) * slice_size))
      );
    }

    // Accumulate results.
    accumulator = extprim::i128::ZERO;
    for _ in 0..THREADS {
      accumulator += rx.recv().unwrap();
    }
  }
  else { // Small matrix, don't bother threading.
    accumulator = permanent(&mat, 0, size);
  }
  println!("{}", accumulator);
}

fn permanent(mat: &Vec<Vec<i64>>, start: i64, end: i64) -> i128 {
  let size = mat.len();
  let sentinel = std::i64::MAX / size as i64;

  let mut bits : Vec<bool> = Vec::with_capacity(size);
  let mut sums : Vec<i64> = Vec::with_capacity(size);

  // Initialize gray code bits.
  let gray_number = start ^ (start / 2);

  for row in 0..size {
    bits.push((gray_number >> row) % 2 == 1);
    sums.push(0);
  }

  // Initialize column sums
  for row in 0..size {
    if bits[row] {
      for column in 0..size {
        sums[column] += mat[row][column];
      }
    }
  }

  // Do first two iterations with initial sums
  let mut total = product(&sums, sentinel);
  for column in 0..size {
    sums[column] += mat[0][column];
  }
  bits[0] = true;

  total -= product(&sums, sentinel);

  // Do rest of iterations updating gray code bits incrementally
  let mut gray_bit : usize;
  let mut idx = start + 2;
  while idx < end {
    gray_bit = idx.trailing_zeros() as usize;

    if bits[gray_bit] {
      for column in 0..size {
        sums[column] -= mat[gray_bit][column];
      }
      bits[gray_bit] = false;
    }
    else {
      for column in 0..size {
        sums[column] += mat[gray_bit][column];
      }
      bits[gray_bit] = true;
    }

    total += product(&sums, sentinel);

    if bits[0] {
      for column in 0..size {
        sums[column] -= mat[0][column];
      }
      bits[0] = false;
    }
    else {
      for column in 0..size {
        sums[column] += mat[0][column];
      }
      bits[0] = true;
    }

    total -= product(&sums, sentinel);
    idx += 2;
  }
  return if size % 2 == 0 {total} else {-total};
}

#[inline]
fn product(sums : &Vec<i64>, sentinel : i64) -> i128 {
  let mut ret : Option<i128> = None;
  let mut tally = sums[0];
  for ii in 1..sums.len() {
    if tally.abs() >= sentinel {
      ret = Some(ret.map_or(i128::new(tally), |n| n * i128::new(tally)));
      tally = sums[ii];
    }
    else {
      tally *= sums[ii];
    }
  }
  if ret.is_none() {
    return i128::new(tally);
  }
  return ret.unwrap() * i128::new(tally);
}

extprim 설치 및 코드 컴파일을 위해 복사 및 붙여 넣기 가능한 명령 줄을 제공 할 수 있습니까?

출력은 "i128! (-2)"와 같습니다. 여기서 -2는 정답입니다. 이것이 예상되는 것이며 영구적 인 출력을 출력하기 위해 변경할 수 있습니까?

1
@Lembik : 출력이 수정되었습니다. 컴파일을 알아 낸 것처럼 보이지만 Git에서 던 졌으므로 git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --release나와 같은 설정을 원한다면 할 수 있습니다. 화물은 의존성을 처리합니다. 이진이 들어갑니다 target/release.
ezrast 2016 년

불행히도 이것은 n = 29에 대한 오답을 제공합니다. bpaste.net/show/99d6e826d968

1
@Lembik gah, 죄송합니다. 중간 값이 생각보다 일찍 넘쳤습니다. 프로그램이 지금 훨씬 느리지 만 수정되었습니다.
ezrast

4

하스켈, n = 31 (54s)

@Angs의 귀중한 공헌은 use Vector, 단락 제품 사용 , 홀수 n을 참조하십시오.

import Control.Parallel.Strategies
import qualified Data.Vector.Unboxed as V
import Data.Int

type Row = V.Vector Int8

x :: Row -> [Row] -> Integer -> Int -> Integer
x p (v:vs) m c = let c' = c - 1
                     r = if c>0 then parTuple2 rseq rseq else r0
                     (a,b) = ( x p                  vs m    c' ,
                               x (V.zipWith(-) p v) vs (-m) c' )
                             `using` r
                 in a+b
x p _      m _ = prod m p

prod :: Integer -> Row -> Integer
prod m p = if 0 `V.elem` p then 0 
                           else V.foldl' (\a b->a*fromIntegral b) m p

p, pt :: [Row] -> Integer
p (v:vs) = x (foldl (V.zipWith (+)) v vs) (map (V.map (2*)) vs) 1 11
           `div` 2^(length vs)
p [] = 1 -- handle 0x0 matrices too  :-)

pt (v:vs) | even (length vs) = p ((V.map (2*) v) : vs ) `div` 2
pt mat                       = p mat

main = getContents >>= print . pt . map V.fromList . read

Haskell에서 병렬 처리를 처음 시도했습니다. 개정 내역을 통해 많은 최적화 단계를 볼 수 있습니다. 놀랍게도, 그것은 대부분 매우 작은 변화였습니다. 이 코드는 Wikipedia 기사의 "Balasubramanian-Bax / Franklin-Glynn formula"섹션의 공식을 기반으로 하며 영구적 인 계산에 관한 것 입니다.

p지속 물을 계산합니다. pt항상 유효한 방식으로 행렬을 변환하는 via 를 호출 하지만 특히 우리가 얻는 행렬에 유용합니다.

로 컴파일하십시오 ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs. 병렬화로 실행하려면 다음과 같은 런타임 매개 변수를 제공하십시오 ./<name> +RTS -N. [[1,2],[3,4]]마지막 예에서 와 같이 괄호 안에 쉼표로 구분 된 중첩 목록이있는 stdin에서 입력 한 것입니다 (모든 줄에 허용되는 줄 바꿈).


1
에 연결하면 20-25 %의 속도 향상을 얻을 수있었습니다 Data.Vector. 변경 기능 유형을 변경 제외 : import qualified Data.Vector as V, x (V.zipWith(-) p v) vs (-m) c' ), p (v:vs) = x (foldl (V.zipWith (+)) v vs) (map (V.map (2*)) vs) 1 11,main = getContents >>= print . p . map V.fromList . read
Angs

1
@Angs 감사합니다! 더 적합한 데이터 유형을 조사하고 싶지는 않았습니다. 사소한 것들이 어떻게 바뀌어야하는지 놀랍습니다 V.product. 그것은 단지 ~ 10 %를 주었다. 벡터에 Ints 만 포함되도록 코드를 변경했습니다 . 그것들은 단지 더해지기 때문에 괜찮습니다. 큰 숫자는 곱셈에서 나옵니다. 그런 다음 ~ 20 %였습니다. 이전 코드와 동일한 변경을 시도했지만 당시 속도가 느려졌습니다. 박스가없는 벡터 를 사용할 수 있기 때문에 다시 시도했는데 많은 도움이되었습니다!
Christian Sievers

1
@ christian-sievers glab 나는 도움이 될 수 있습니다. 여기에 내가 찾은 또 다른 재미있는 운 기반 최적화 x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)가 있습니다. 더 자주 유익하지 않은 것 같습니다.
Angs

1
@Angs 위대하다! 나는 데비안 안정과 같은 ghc에 대해 필요하지 않은 형태로 변경했습니다 Transversable(나는 당신의 product먹는 사람을 바꾸지 않는 것이 실수가 아닙니다 ...). 입력 형식을 사용하고 있지만 괜찮아 보입니다. 우리는 입력에 의존하지 않고 최적화하기만합니다. 타이밍을 훨씬 더 흥미롭게 만듭니다. 임의의 30x30 매트릭스는 29x29보다 약간 빠르지 만 31x31은 4x 시간이 걸립니다. -인라인이 저에게는 효과가없는 것 같습니다. AFAIK 재귀 함수에서는 무시됩니다.
Christian Sievers

1
@ christian-sievers 그래, 나는 그것에 대해 말하려고 product 했지만 잊었다. 길이조차도 0 인 것처럼 보이 p므로 홀수 길이의 경우 두 세계를 모두 활용하기 위해 단락 대신 일반 제품을 사용해야합니다.
Angs

3

수학, n ≈ 20

p[m_] := Last[Fold[Take[ListConvolve[##, {1, -1}, 0], 2^Length[m]]&,
  Table[If[IntegerQ[Log2[k]], m[[j, Log2[k] + 1]], 0], {j, n}, {k, 0, 2^Length[m] - 1}]]]

Timing명령을 사용하면 시스템에서 20x20 매트릭스에 약 48 초가 필요합니다. 이것은 매트릭스의 각 행에서 polymomial의 곱의 계수로 영구적을 찾을 수 있다는 사실에 의존하기 때문에 다른 것만 큼 정확하지는 않습니다. 계수 목록을 생성하고를 사용하여 컨볼 루션을 수행함으로써 효율적인 다항식 곱셈이 수행됩니다 ListConvolve. 이것은 회선이 고속 푸리에 변환 또는 O ( n log n ) 시간 을 필요로하는 유사한 것을 사용하여 수행되는 것으로 가정하면 약 O (2 n n 2 ) 시간이 필요하다 .


3

파이썬 2, n = 22 [참조]

이것은 어제 Lembik과 공유 한 '참조'구현 n=23입니다. 컴퓨터에서 몇 초 정도 걸리고 내 컴퓨터에서는 약 52 초 안에 수행하지 않습니다. 이러한 속도를 달성하려면 PyPy를 통해이를 실행해야합니다.

첫 번째 함수는 기본 규칙을 적용 할 수있는 2x2가 남을 때까지 각 하위 행렬을 검토하여 행렬식을 계산할 수있는 방법과 유사한 영구성을 계산합니다. 그것은이다 매우 느린 .

두 번째 함수는 Ryser 함수 (Wikipedia에 나열된 두 번째 방정식)를 구현하는 함수입니다. 세트 S는 기본적으로 숫자의 파워 세트 입니다 {1,...,n}( s_list코드에서 변수 ).

from random import *
from time import time
from itertools import*

def perm(a): # naive method, recurses over submatrices, slow 
    if len(a) == 1:
        return a[0][0]
    elif len(a) == 2:
        return a[0][0]*a[1][1]+a[1][0]*a[0][1]
    else:
        tsum = 0
        for i in xrange(len(a)):
            transposed = [zip(*a)[j] for j in xrange(len(a)) if j != i]
            tsum += a[0][i] * perm(zip(*transposed)[1:])
        return tsum

def perm_ryser(a): # Ryser's formula, using matrix entries
    maxn = len(a)
    n_list = range(1,maxn+1)
    s_list = chain.from_iterable(combinations(n_list,i) for i in range(maxn+1))
    total = 0
    for st in s_list:
        stotal = (-1)**len(st)
        for i in xrange(maxn):
            stotal *= sum(a[i][j-1] for j in st)
        total += stotal
    return total*((-1)**maxn)


def genmatrix(d):
    mat = []
    for x in xrange(d):
        row = []
        for y in xrange(d):
            row.append([-1,1][randrange(0,2)])
        mat.append(row)
    return mat

def main():
    for i in xrange(1,24):
        k = genmatrix(i)
        print 'Matrix: (%dx%d)'%(i,i)
        print '\n'.join('['+', '.join(`j`.rjust(2) for j in a)+']' for a in k)
        print 'Permanent:',
        t = time()
        p = perm_ryser(k)
        print p,'(took',time()-t,'seconds)'

if __name__ == '__main__':
    main()

"결정자를 계산하는 방법과 비슷한"설명을 바꿔야한다고 생각합니다. 결정자에 대한 방법이 영원의 속도가 느린 것과 같지는 않지만 결정자에 대한 하나의 느린 방법은 지속 물에 대해 비슷하고 느리게 작동합니다.
Christian Sievers

1
@ChristianSievers 좋은 지적, 나는 그것을 변경했습니다.
Kade

2

RPython 5.4.1, n ≈ 32 (37 초)

from rpython.rlib.rtime import time
from rpython.rlib.rarithmetic import r_int, r_uint
from rpython.rlib.rrandom import Random
from rpython.rlib.rposix import pipe, close, read, write, fork, waitpid
from rpython.rlib.rbigint import rbigint

from math import log, ceil
from struct import pack

bitsize = len(pack('l', 1)) * 8 - 1

bitcounts = bytearray([0])
for i in range(16):
  b = bytearray([j+1 for j in bitcounts])
  bitcounts += b


def bitcount(n):
  bits = 0
  while n:
    bits += bitcounts[n & 65535]
    n >>= 16
  return bits


def main(argv):
  if len(argv) < 2:
    write(2, 'Usage: %s NUM_THREADS [N]'%argv[0])
    return 1
  threads = int(argv[1])

  if len(argv) > 2:
    n = int(argv[2])
    rnd = Random(r_uint(time()*1000))
    m = []
    for i in range(n):
      row = []
      for j in range(n):
        row.append(1 - r_int(rnd.genrand32() & 2))
      m.append(row)
  else:
    m = []
    strm = ""
    while True:
      buf = read(0, 4096)
      if len(buf) == 0:
        break
      strm += buf
    rows = strm.split("\n")
    for row in rows:
      r = []
      for val in row.split(' '):
        r.append(int(val))
      m.append(r)
    n = len(m)

  a = []
  for row in m:
    val = 0
    for v in row:
      val = (val << 1) | -(v >> 1)
    a.append(val)

  batches = int(ceil(n * log(n) / (bitsize * log(2))))

  pids = []
  handles = []
  total = rbigint.fromint(0)
  for i in range(threads):
    r, w = pipe()
    pid = fork()
    if pid:
      close(w)
      pids.append(pid)
      handles.append(r)
    else:
      close(r)
      total = run(n, a, i, threads, batches)
      write(w, total.str())
      close(w)
      return 0

  for pid in pids:
    waitpid(pid, 0)

  for handle in handles:
    strval = read(handle, 256)
    total = total.add(rbigint.fromdecimalstr(strval))
    close(handle)

  print total.rshift(n-1).str()

  return 0


def run(n, a, mynum, threads, batches):
  start = (1 << n-1) * mynum / threads
  end = (1 << n-1) * (mynum+1) / threads

  dtotal = rbigint.fromint(0)
  for delta in range(start, end):
    pdelta = rbigint.fromint(1 - ((bitcount(delta) & 1) << 1))
    for i in range(batches):
      pbatch = 1
      for j in range(i, n, batches):
        pbatch *= n - (bitcount(delta ^ a[j]) << 1)
      pdelta = pdelta.int_mul(pbatch)
    dtotal = dtotal.add(pdelta)

  return dtotal


def target(*args):
  return main

컴파일하기, 최신 PyPy 소스를 다운로드 하고 다음을 실행하십시오.

pypy /path/to/pypy-src/rpython/bin/rpython matrix-permanent.py

결과 실행 파일은 matrix-permanent-c현재 작업 디렉토리에서 이름이 비슷하거나 비슷합니다.

PyPy 5.0부터 RPython의 스레딩 프리미티브는 예전보다 훨씬 덜 프리미티브합니다. 새로 생성 된 스레드에는 GIL이 필요하며 이는 병렬 계산에 거의 쓸모가 없습니다. fork대신 사용 했기 때문에 Windows에서 예상대로 작동하지 않을 수 있습니다. 했지만 테스트 하지 않았지만unresolved external symbol _fork ).

실행 파일은 최대 두 개의 명령 줄 매개 변수를 허용합니다. 첫 번째는 스레드 수이고 두 번째 선택적 매개 변수는n 입니다. 제공되는 경우 임의 행렬이 생성되고, 그렇지 않으면 stdin에서 읽습니다. 각 행은 개행 문자로 구분되고 (후행 개행 문자없이) 각 값 공간이 구분되어야합니다. 세 번째 입력 예는 다음과 같습니다.

1 -1 1 -1 -1 1 1 1 -1 -1 -1 -1 1 1 1 1 -1 1 1 -1
1 -1 1 1 1 1 1 -1 1 -1 -1 1 1 1 -1 -1 1 1 1 -1
-1 -1 1 1 1 -1 -1 -1 -1 1 -1 1 1 1 -1 -1 -1 1 -1 -1
-1 -1 -1 1 1 -1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 1 -1
-1 1 1 1 -1 1 1 1 -1 -1 -1 1 -1 1 -1 1 1 1 1 1
1 -1 1 1 -1 -1 1 -1 1 1 1 1 -1 1 1 -1 1 -1 -1 -1
1 -1 -1 1 -1 -1 -1 1 -1 1 1 1 1 -1 -1 -1 1 1 1 -1
1 -1 -1 1 -1 1 1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1
1 -1 -1 -1 -1 -1 1 1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1
-1 -1 1 -1 1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 1 1
-1 -1 -1 -1 -1 -1 -1 1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1
1 1 -1 -1 -1 1 1 -1 -1 1 -1 1 1 -1 1 1 1 1 1 1
-1 1 1 -1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1 -1 1 -1 1
1 1 -1 -1 -1 1 -1 1 -1 -1 -1 -1 1 -1 1 1 -1 1 -1 1
1 1 1 1 1 -1 -1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1
1 -1 -1 1 -1 -1 -1 -1 1 -1 -1 1 1 -1 1 -1 -1 -1 -1 -1
-1 1 1 1 -1 1 1 -1 -1 1 1 1 -1 -1 1 1 -1 -1 1 1
1 1 -1 -1 1 1 -1 1 1 -1 1 1 1 -1 1 1 -1 1 -1 1
1 1 1 -1 -1 -1 1 -1 -1 1 1 -1 -1 -1 1 -1 -1 -1 -1 1
-1 1 1 1 -1 -1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1 1 -1 -1

샘플 사용법

$ time ./matrix-permanent-c 8 30
8395059644858368

real    0m8.582s
user    1m8.656s
sys     0m0.000s

방법

I는 사용한 Balasubramanian에-백스 / 프랭클린 글린 수식 의 실행 복잡도, O (2 N , N) . 그러나 δ 를 회색 코드 순서 로 반복하는 대신 벡터 행 곱셈을 단일 xor 연산으로 대체했습니다 (매핑 (1, -1) → (0, 1)). 벡터 수는 팝 카운트의 n에서 2를 빼서 단일 연산에서 찾을 수 있습니다.


불행히도 코드는 bpaste.net/show/8690251167e7에 대한 잘못된 답변을 제공합니다

@Lembik이 업데이트되었습니다. 호기심으로 다음 코드의 결과를 알려주시겠습니까? bpaste.net/show/76ec65e1b533
primo

그것은 "진정한 18446744073709551615"를 제공합니다. 나는 지금 당신의 코드에 아주 좋은 결과를 추가했습니다.

@Lembik 감사합니다. 63 비트를 오버플로하지 않도록 곱셈을 이미 나누었습니다. 8 개의 스레드로 결과가 표시 되었습니까? 2 또는 4가 차이를 줍니까? 25에서 30이 끝나면 31이 1 분 미만인 것 같습니다.
primo

-1

라켓 84 바이트

다음과 같은 간단한 기능은 작은 행렬에서는 작동하지만 더 큰 행렬에서는 내 컴퓨터에 매달려 있습니다.

(for/sum((p(permutations(range(length l)))))(for/product((k l)(c p))(list-ref k c)))

언 골프 드 :

(define (f ll) 
  (for/sum ((p (permutations (range (length ll))))) 
    (for/product ((l ll)(c p)) 
      (list-ref l c))))

동일하지 않은 수의 행과 열에 맞게 코드를 쉽게 수정할 수 있습니다.

테스트 :

(f '[[ 1 -1 -1  1]
     [-1 -1 -1  1]
     [-1  1 -1  1]
     [ 1 -1 -1  1]])

(f '[[ 1 -1  1 -1 -1 -1 -1 -1]
 [-1 -1  1  1 -1  1  1 -1]
 [ 1 -1 -1 -1 -1  1  1  1]
 [-1 -1 -1  1 -1  1  1  1]
 [ 1 -1 -1  1  1  1  1 -1]
 [-1  1 -1  1 -1  1  1 -1]
 [ 1 -1  1 -1  1 -1  1 -1]
 [-1 -1  1 -1  1  1  1  1]])

산출:

-4
192

위에서 언급했듯이 다음과 같은 테스트가 중단됩니다.

(f '[[1 -1 1 -1 -1 1 1 1 -1 -1 -1 -1 1 1 1 1 -1 1 1 -1]
 [1 -1 1 1 1 1 1 -1 1 -1 -1 1 1 1 -1 -1 1 1 1 -1]
 [-1 -1 1 1 1 -1 -1 -1 -1 1 -1 1 1 1 -1 -1 -1 1 -1 -1]
 [-1 -1 -1 1 1 -1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 1 -1]
 [-1 1 1 1 -1 1 1 1 -1 -1 -1 1 -1 1 -1 1 1 1 1 1]
 [1 -1 1 1 -1 -1 1 -1 1 1 1 1 -1 1 1 -1 1 -1 -1 -1]
 [1 -1 -1 1 -1 -1 -1 1 -1 1 1 1 1 -1 -1 -1 1 1 1 -1]
 [1 -1 -1 1 -1 1 1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1]
 [1 -1 -1 -1 -1 -1 1 1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1]
 [-1 -1 1 -1 1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 1 1]
 [-1 -1 -1 -1 -1 -1 -1 1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1]
 [1 1 -1 -1 -1 1 1 -1 -1 1 -1 1 1 -1 1 1 1 1 1 1]
 [-1 1 1 -1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1 -1 1 -1 1]
 [1 1 -1 -1 -1 1 -1 1 -1 -1 -1 -1 1 -1 1 1 -1 1 -1 1]
 [1 1 1 1 1 -1 -1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1]
 [1 -1 -1 1 -1 -1 -1 -1 1 -1 -1 1 1 -1 1 -1 -1 -1 -1 -1]
 [-1 1 1 1 -1 1 1 -1 -1 1 1 1 -1 -1 1 1 -1 -1 1 1]
 [1 1 -1 -1 1 1 -1 1 1 -1 1 1 1 -1 1 1 -1 1 -1 1]
 [1 1 1 -1 -1 -1 1 -1 -1 1 1 -1 -1 -1 1 -1 -1 -1 -1 1]
 [-1 1 1 1 -1 -1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1 1 -1 -1]])

3
이 질문의 속도 버전보다는 codegolf 버전 에서이 답변이 더 낫습니까?
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.