Tính toán vĩnh viễn càng nhanh càng tốt


26

Thách thức là viết mã nhanh nhất có thể để tính toán vĩnh viễn của ma trận .

Vĩnh viễn của một n-by- nmatrix A= ( ai,j) được định nghĩa là

nhập mô tả hình ảnh ở đây

Ở đây S_nđại diện cho tập hợp tất cả các hoán vị của [1, n].

Ví dụ (từ wiki):

nhập mô tả hình ảnh ở đây

Trong câu hỏi này, ma trận đều là hình vuông và sẽ chỉ có các giá trị -11trong đó.

Ví dụ

Đầu vào:

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

Dài hạn:

-4

Đầu vào:

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

Dài hạn:

0

Đầu vào:

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

Dài hạn:

192

Đầu vào:

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

Dài hạn:

1021509632

Nhiệm vụ

Bạn nên viết mã đó, đưa ra một nbằng nma trận, kết quả đầu ra lâu dài của nó.

Vì tôi sẽ cần kiểm tra mã của bạn, sẽ rất hữu ích nếu bạn có thể đưa ra một cách đơn giản để tôi đưa ra một ma trận làm đầu vào cho mã của bạn, ví dụ bằng cách đọc từ tiêu chuẩn.

Được cảnh báo rằng vĩnh viễn có thể lớn (ma trận tất cả 1 giây là trường hợp cực đoan).

Điểm và quan hệ

Tôi sẽ kiểm tra mã của bạn trên các ma trận ngẫu nhiên + -1 có kích thước tăng dần và dừng lần đầu tiên mã của bạn mất hơn 1 phút trên máy tính của tôi. Các ma trận cho điểm sẽ phù hợp với tất cả các bài nộp để đảm bảo sự công bằng.

Nếu hai người có cùng số điểm thì người chiến thắng là người nhanh nhất với giá trị đó n. Nếu những cái đó trong vòng 1 giây với nhau thì đó là cái được đăng đầu tiên.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ và thư viện có sẵn nào bạn thích nhưng không có chức năng có sẵn để tính toán vĩnh viễn. Nếu khả thi, sẽ rất tốt để có thể chạy mã của bạn, vì vậy vui lòng bao gồm một lời giải thích đầy đủ về cách chạy / biên dịch mã của bạn trong Linux nếu có thể.

Tham khảo thực hiện

Đã có một câu hỏi câu hỏi về codegolf với rất nhiều mã trong các ngôn ngữ khác nhau để tính toán vĩnh viễn cho các ma trận nhỏ. MathematicaMaple cũng có các triển khai vĩnh viễn nếu bạn có thể truy cập chúng.

Máy của tôi Thời gian sẽ được chạy trên máy 64 bit của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn với RAM 8GB, Bộ xử lý tám lõi AMD FX-8350 và Radeon HD 4250. Điều này cũng có nghĩa là tôi cần có khả năng chạy mã của bạn.

Thông tin cấp thấp về máy của tôi

cat /proc/cpuinfo/|grep flags cho

cờ f16c lahf_lm

Tôi sẽ hỏi một câu hỏi tiếp theo đa ngôn ngữ có liên quan chặt chẽ mà không gặp phải vấn đề Int lớn để những người yêu thích Scala , Nim , Julia , Rust , Bash cũng có thể thể hiện ngôn ngữ của họ.

Ban lãnh đạo

  • n = 33 (45 giây. 64 giây cho n = 34). TonMedel trong C ++ với g ++ 5.4.0.
  • n = 32 (32 giây). Dennis trong C với gcc 5.4.0 bằng cờ gcc của TonMedel.
  • n = 31 (54 giây). Christian SieversHaskell
  • n = 31 (60 giây). primo trong rpython
  • n = 30 (26 giây). ezrastRust
  • n = 28 (49 giây). xnor với Python + pypy 5.4.1
  • n = 22 (25 giây). Shebang với Python + pypy 5.4.1

Lưu ý . Trong thực tế, thời gian cho Dennis và TonMedel rất khác nhau vì những lý do bí ẩn. Ví dụ, chúng dường như nhanh hơn sau khi tôi tải trình duyệt web! Thời gian được trích dẫn là nhanh nhất trong tất cả các bài kiểm tra tôi đã thực hiện.


5
Tôi đọc câu đầu tiên, nghĩ 'Lembik', cuộn xuống, vâng - Lembik.
orlp

@orlp :) Lâu lắm rồi.

1
@Lembik Tôi đã thêm một trường hợp thử nghiệm lớn. Tôi sẽ đợi ai đó xác nhận để chắc chắn.
xnor

2
Một trong những câu trả lời in một kết quả gần đúng, vì nó sử dụng các phao chính xác kép để lưu trữ vĩnh viễn. Điều đó có được phép không?
Dennis

1
@ChristianSievers Tôi nghĩ rằng tôi có thể làm được một số phép thuật với các dấu hiệu, nhưng nó đã không thành công ...
Socratic Phoenix

Câu trả lời:


13

gcc C ++ n ≈ 36 (57 giây trên hệ thống của tôi)

Sử dụng công thức Glynn với mã Gray để cập nhật nếu tất cả các tổng số cột đều, nếu không thì sử dụng phương thức của Ryser. Có ren và véc tơ. Tối ưu hóa cho AVX, vì vậy đừng kỳ vọng nhiều vào bộ xử lý cũ. Đừng bận tâm với n>=35ma trận chỉ có +1 ngay cả khi hệ thống của bạn đủ nhanh vì bộ tích lũy 128 bit đã ký sẽ tràn. Đối với ma trận ngẫu nhiên, bạn có thể sẽ không bị tràn. Đối với n>=37các bội số nội bộ sẽ bắt đầu tràn cho một 1/-1ma trận tất cả . Vì vậy, chỉ sử dụng chương trình này cho n<=36.

Chỉ cần cung cấp cho các phần tử ma trận trên STDIN cách nhau bởi bất kỳ loại khoảng trắng nào

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

cờ 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 Arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold

Tôi vẫn đang gỡ lỗi khai thác thử nghiệm của tôi để chạy mã của bạn nhưng nó trông rất nhanh, cảm ơn bạn! Tôi đã tự hỏi nếu kích thước int lớn hơn có thể gây ra vấn đề tốc độ (như bạn đề xuất). Tôi thấy accu.org/index.php/articles/1849 trong trường hợp nó không được quan tâm.

Tôi đã phải sửa đổi mã của bạn để xóa quick_exit vì những mã này rất khó sử dụng trong khai thác thử nghiệm. Không có hứng thú, tại sao bạn lại sử dụng công thức của Ryser khi wiki dường như yêu cầu một công cụ khác phải nhanh gấp đôi?

@Lembik Tôi đã chuyển sang công thức của Ryser vì với cái khác tôi cần thu nhỏ lại 2 << (n-1)ở cuối, điều đó có nghĩa là bộ tích lũy int128 của tôi đã vượt xa trước thời điểm đó.
TonMedel

1
@Lembik Có :-)
TonMedel

7

C99, n ≈ 33 (35 giây)

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

Đầu vào hiện tại hơi cồng kềnh; nó được lấy với các hàng làm đối số dòng lệnh, trong đó mỗi mục được biểu thị bằng dấu của nó, nghĩa là + chỉ ra 1- chỉ ra -1 .

Chạy thử nghiệm

$ 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

Bạn có bất cứ ý tưởng để cải thiện?
xnor

@xnor Một vài. Tôi muốn thử nhân bội với SSE và bỏ kiểm soát một phần vòng lặp lớn (để xem liệu tôi có thể tăng tốc độ song song hóa và tính toán hơn 4 giá trị cùng một lúc mà không cần gọi không popcnt). Nếu điều đó tiết kiệm bất cứ lúc nào, trở ngại lớn tiếp theo là loại số nguyên. Đối với ma trận được tạo ngẫu nhiên, vĩnh viễn là tương đối nhỏ. Nếu tôi có thể tìm thấy một cách dễ dàng để tính toán một ràng buộc trước khi thực hiện tính toán thực tế, tôi có thể gói toàn bộ mọi thứ trong một điều kiện lớn.
Dennis

@Dennis Về việc hủy đăng ký vòng lặp, một tối ưu hóa nhỏ có thể là làm cho hàng trên cùng là tất cả +1.
xnor

@xnor Yeah, tôi đã cố gắng mà tại một số điểm, nhưng sau đó quay trở sự thay đổi để thử cái gì khác (mà không làm việc ra ở tất cả ). Nút cổ chai dường như là phép nhân số nguyên (chậm cho 64 bit và thực sự chậm cho 128), đó là lý do tại sao tôi hy vọng SSE sẽ giúp một chút.
Dennis

1
@Dennis tôi hiểu rồi. Về giới hạn, một ràng buộc không rõ ràng là về mặt chỉ tiêu toán tử | Per (M) | <= | M | ^ n. Xem arxiv.org/pdf/1606.07474v1.pdf
xnor

5

Con trăn 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

Sử dụng công thức Glynn với mã Gray để cập nhật. Chạy đến n=23một phút trên máy của tôi. Một người chắc chắn có thể thực hiện tốt hơn việc này bằng ngôn ngữ nhanh hơn và với cấu trúc dữ liệu tốt hơn. Điều này không sử dụng ma trận có giá trị ± 1.

Việc triển khai công thức Ryser rất giống nhau, tổng hợp trên tất cả các vectơ hệ số 0/1 thay vì các vectơ ± 1. Nó mất khoảng gấp đôi thời gian so với công thức của Glynn vì thêm vào tất cả 2 ^ n vectơ như vậy, trong khi một nửa của Glynn sử dụng tính đối xứng cho chỉ những người bắt đầu bằng +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

Tuyệt vời. Bạn đã có pypy để kiểm tra quá?

@Lembik Không, tôi không cài đặt nhiều.
xnor

Tôi sẽ sử dụng pypy khi tôi kiểm tra nó quá. Bạn có thể thấy làm thế nào để thực hiện các công thức nhanh khác? Tôi thấy khó hiểu.

@Lembik Công thức nhanh nào khác?
xnor

1
Để tham khảo, trên máy của tôi với pypyđiều này đã có thể dễ dàng tính toán n=28trong 44,6 giây. Hệ thống của Lembik dường như khá tương đương với tốc độ của tôi nếu không nhanh hơn một chút.
Kade

4

Rust + extprim

Ryser đơn giản với triển khai mã Gray này mất khoảng 65 90 giây để chạy n = 31 trên máy tính xách tay của tôi. Tôi tưởng tượng máy của bạn sẽ đến đó trong khoảng dưới 60s. Tôi đang sử dụng extprim 1.1.1 cho i128.

Tôi chưa bao giờ sử dụng Rust và không biết tôi đang làm gì. Không có tùy chọn trình biên dịch nào khác hơn bất cứ điều gì cargo build --release. Nhận xét / đề xuất / tối ưu hóa được đánh giá cao.

Yêu cầu giống hệt với chương trình của 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);
}

Bạn có thể cung cấp các dòng lệnh sao chép và có thể dán để cài đặt extprim và biên dịch mã không?

Đầu ra trông giống như "i128! (- 2)" trong đó -2 là câu trả lời đúng. Điều này có được mong đợi không và bạn có thể thay đổi nó thành đầu ra vĩnh viễn không?

1
@Lembik: Đầu ra nên được sửa ngay bây giờ. Có vẻ như bạn đã tìm ra phần tổng hợp, nhưng tôi đã ném nó vào Git để bạn có thể làm git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --releasenếu bạn muốn chắc chắn có cùng thiết lập như tôi. Hàng hóa sẽ xử lý các phụ thuộc. Nhị phân đi vào target/release.
ezrast

Thật không may, điều này đưa ra câu trả lời sai cho n = 29. bpaste.net/show/99d6e826d968

1
@Lembik gah, xin lỗi, giá trị trung gian đã tràn ra sớm hơn tôi nghĩ. Nó đã được sửa, mặc dù chương trình chậm hơn rất nhiều.
ezrast

4

Haskell, n = 31 (54 giây)

Với rất nhiều đóng góp vô giá của @Angs: sử dụng Vector, sử dụng các sản phẩm ngắn mạch, nhìn vào n lẻ.

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

Những nỗ lực đầu tiên của tôi về sự song song trong Haskell. Bạn có thể thấy rất nhiều bước tối ưu hóa thông qua lịch sử sửa đổi. Thật đáng ngạc nhiên, đó chủ yếu là những thay đổi rất nhỏ. Mã này dựa trên công thức trong phần "Công thức Balasubramanian-Bax / Franklin-Glynn" trong bài viết trên Wikipedia về tính toán vĩnh viễn .

ptính toán vĩnh viễn. Nó được gọi thông qua ptđó biến đổi ma trận theo cách luôn hợp lệ, nhưng đặc biệt hữu ích cho các ma trận mà chúng ta nhận được ở đây.

Biên dịch với ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs. Để chạy song song, cung cấp cho nó các tham số thời gian chạy như thế này : ./<name> +RTS -N. Đầu vào là từ stdin với các danh sách được phân tách bằng dấu phẩy được lồng trong các dấu ngoặc như [[1,2],[3,4]]trong ví dụ trước (dòng mới được phép ở mọi nơi).


1
Tôi đã có thể cải thiện tốc độ 20-25% bằng cách cắm vào Data.Vector. Những thay đổi không bao gồm thay đổi loại chức năng: 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 Cảm ơn rất nhiều! Tôi thực sự không cảm thấy muốn nhìn vào các kiểu dữ liệu phù hợp hơn. Thật đáng ngạc nhiên khi những thứ nhỏ bé phải thay đổi (cũng phải sử dụng V.product). Điều đó chỉ cho tôi ~ 10%. Thay đổi mã để các vectơ chỉ chứa Ints. Điều đó không sao vì chúng chỉ được thêm vào, những con số lớn đến từ phép nhân. Sau đó, nó là ~ 20%. Tôi đã thử thay đổi tương tự với mã cũ, nhưng tại thời điểm đó, nó đã làm chậm nó. Tôi đã thử lại vì nó cho phép sử dụng các vectơ không có hộp , điều này giúp ích rất nhiều!
Christian Sievers

1
@ christian-sievers glab Tôi có thể giúp đỡ. Đây là một tối ưu hóa dựa trên may mắn thú vị khác mà tôi đã tìm thấy: x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)- sản phẩm là một nếp gấp đơn trong đó 0 là trường hợp đặc biệt. Có vẻ là có lợi thường xuyên hơn không.
Angs

1
@Angs Tuyệt vời! Tôi đã thay đổi nó thành một hình thức không cần thiết Transversable(tôi thấy việc bạn không thay đổi productăn uống là không có lỗi ...) cho ghc từ ví dụ Debian ổn định. Đó là sử dụng hình thức đầu vào, nhưng điều đó có vẻ tốt: chúng tôi không dựa vào nó, chỉ tối ưu hóa cho nó. Làm cho thời gian thú vị hơn nhiều: ma trận 30x30 ngẫu nhiên của tôi nhanh hơn một chút so với 29x29, nhưng sau đó 31x31 mất gấp 4 lần thời gian. - INLINE đó dường như không làm việc cho tôi. AFAIK nó bị bỏ qua cho các chức năng đệ quy.
Christian Sievers

1
@ christian-sievers Vâng, tôi định nói gì đó về điều đó product nhưng quên mất. Dường như chỉ có độ dài thậm chí có số không p, vì vậy, đối với độ dài lẻ, chúng ta nên sử dụng sản phẩm thông thường thay vì ngắn mạch để có được kết quả tốt nhất của cả hai thế giới.
Angs

3

Toán học, 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}]]]

Sử dụng Timinglệnh, ma trận 20x20 cần khoảng 48 giây trên hệ thống của tôi. Điều này không chính xác như hiệu quả khác vì nó phụ thuộc vào thực tế là vĩnh viễn có thể được tìm thấy như là hệ số của sản phẩm đa hình từ mỗi hàng của ma trận. Phép nhân đa thức hiệu quả được thực hiện bằng cách tạo danh sách hệ số và thực hiện tích chập bằng cách sử dụng ListConvolve. Điều này đòi hỏi khoảng thời gian O (2 n n 2 ) giả sử tích chập được thực hiện bằng cách sử dụng biến đổi Fourier nhanh hoặc tương tự đòi hỏi thời gian O ( n log n ).


3

Python 2, n = 22 [Tham khảo]

Đây là triển khai 'tham khảo' mà tôi đã chia sẻ với Lembik ngày hôm qua, nó bỏ lỡ việc thực hiện n=23 vài giây trên máy của anh ấy, trên máy của tôi, nó thực hiện trong khoảng 52 giây. Để đạt được những tốc độ này, bạn cần chạy nó thông qua PyPy.

Hàm đầu tiên tính toán vĩnh viễn tương tự như cách tính định thức, bằng cách đi qua từng hàm con cho đến khi bạn còn lại một 2x2 mà bạn có thể áp dụng quy tắc cơ bản. Nó cực kỳ chậm .

Hàm thứ hai là hàm thực hiện hàm Ryser (phương trình thứ hai được liệt kê trong Wikipedia). Tập hợp Svề cơ bản là tập hợp các số {1,...,n}(biến s_listtrong mã).

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()

Tôi nghĩ bạn nên viết lại mô tả "tương tự như cách tính định thức". Nó không giống như các phương pháp cho yếu tố quyết định là chậm cho permanents, nhưng một trong những phương pháp chậm đối với yếu tố quyết định hoạt động tương tự (và như chậm) cho permanents.
Christian Sievers

1
@ChristianSievers Điểm hay, tôi đã thay đổi nó.
Kade

2

RPython 5.4.1, n ≈ 32 (37 giây)

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

Để biên dịch, tải xuống nguồn PyPy gần đây nhất và thực hiện các thao tác sau:

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

Kết quả thực thi sẽ được đặt tên matrix-permanent-choặc tương tự trong thư mục làm việc hiện tại.

Kể từ PyPy 5.0, các nguyên thủy luồng của RPython ít nguyên thủy hơn trước đây. Các luồng mới được sinh ra đòi hỏi GIL, ít nhiều vô dụng đối với các tính toán song song. Tôi đã sử dụng forkthay thế, vì vậy nó có thể không hoạt động như mong đợi trên Windows, mặc dù tôi chưa kiểm tra lỗi biên dịch ( unresolved external symbol _fork).

Việc thực thi chấp nhận tối đa hai tham số dòng lệnh. Đầu tiên là số lượng chủ đề, tham số tùy chọn thứ hai là n. Nếu nó được cung cấp, một ma trận ngẫu nhiên sẽ được tạo ra, nếu không nó sẽ được đọc từ stdin. Mỗi hàng phải được phân tách dòng mới (không có dòng mới) và mỗi không gian giá trị được phân tách. Đầu vào ví dụ thứ ba sẽ được đưa ra là:

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

Sử dụng mẫu

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

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

phương pháp

Tôi đã sử dụng công thức Balasubramanian-Bax / Franklin-Glynn , với độ phức tạp thời gian chạy là O (2 n n) . Tuy nhiên, thay vì lặp lại những δ để mã màu xám, tôi đã thay thế nhân vector hàng với một thao tác đơn xor (lập bản đồ (1, -1) → (0, 1)). Tổng vectơ có thể được tìm thấy tương tự trong một hoạt động, bằng cách lấy n trừ hai lần số đếm.


Thật không may, mã đưa ra câu trả lời sai cho bpaste.net/show/8690251167e7

@Lembik cập nhật. Vì tò mò, bạn có thể cho tôi biết kết quả của đoạn mã sau không? bpaste.net/show/76ec65e1b533
primo

Nó cho "True 18446744073709551615" Tôi đã thêm kết quả cho mã rất tốt của bạn bây giờ.

@Lembik cảm ơn. Tôi đã phân chia phép nhân để không tràn 63 bit. Là kết quả được liệt kê thực hiện với 8 chủ đề? Liệu 2 hoặc 4 có làm nên sự khác biệt? Nếu 30 kết thúc sau 25, có vẻ như 31 nên dưới một phút.
primo

-1

Vợt 84 byte

Thực hiện theo chức năng đơn giản cho các ma trận nhỏ hơn nhưng treo trên máy của tôi cho các ma trận lớn hơn:

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

Ung dung:

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

Mã có thể dễ dàng được sửa đổi cho số lượng hàng và cột không bằng nhau.

Kiểm tra:

(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]])

Đầu ra:

-4
192

Như tôi đã đề cập ở trên, nó bị treo khi thử nghiệm như sau:

(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
Câu trả lời này có tốt hơn trong phiên bản codegolf hơn là phiên bản tốc độ của câu hỏi này không?
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.