คำนวณแบบถาวรให้เร็วที่สุด


26

ความท้าทายคือการเขียนรหัสที่เร็วที่สุดที่เป็นไปได้สำหรับการคำนวณถาวรของเมทริกซ์

ถาวรของn-by- nmatrix A= ( ai,j) ถูกกำหนดเป็น

ป้อนคำอธิบายรูปภาพที่นี่

นี่แสดงให้เห็นถึงชุดของพีชคณิตทั้งหมดของS_n[1, n]

เป็นตัวอย่าง (จาก wiki):

ป้อนคำอธิบายรูปภาพที่นี่

ในเมทริกซ์คำถามนี้เป็นสี่เหลี่ยมจัตุรัสทั้งหมดและจะมีค่า-1และ1อยู่ในนั้นเท่านั้น

ตัวอย่าง

การป้อนข้อมูล:

[[ 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

งาน

คุณควรเขียนโค้ดที่ได้รับnจากnเมทริกซ์จะให้ผลลัพธ์เป็นแบบถาวร

เนื่องจากฉันจะต้องทดสอบโค้ดของคุณมันจะมีประโยชน์ถ้าคุณสามารถให้วิธีง่ายๆในการให้เมทริกซ์เป็นอินพุตของโค้ดของคุณตัวอย่างเช่นโดยการอ่านจากมาตรฐานใน

ได้รับการเตือนว่าถาวรสามารถมีขนาดใหญ่ (เมทริกซ์ 1s ทั้งหมดเป็นกรณีที่รุนแรง)

คะแนนและความสัมพันธ์

ฉันจะทดสอบโค้ดของคุณในเมทริกซ์ +1 ที่เพิ่มขนาดแบบสุ่มและหยุดในครั้งแรกที่รหัสของคุณใช้เวลามากกว่า 1 นาทีบนคอมพิวเตอร์ของฉัน เมทริกซ์การให้คะแนนจะสอดคล้องกันสำหรับการส่งทั้งหมดเพื่อความเป็นธรรม

หากคนสองคนได้คะแนนเท่ากันผู้ชนะคือคนที่เร็วที่สุดสำหรับค่าnนั้น หากสิ่งนั้นอยู่ภายใน 1 วินาทีของกันและกันมันก็จะเป็นโพสต์ก่อน

ภาษาและห้องสมุด

คุณสามารถใช้ภาษาและไลบรารีใด ๆ ที่คุณต้องการ แต่ไม่มีฟังก์ชั่นที่มีอยู่แล้วในการคำนวณแบบถาวร หากเป็นไปได้มันจะเป็นการดีที่จะสามารถรันโค้ดของคุณได้ดังนั้นโปรดระบุคำอธิบายโดยละเอียดเกี่ยวกับวิธีการรัน / คอมไพล์โค้ดของคุณใน Linux หากเป็นไปได้ "

การใช้งานอ้างอิง

มีคำถามคำถาม codegolfพร้อมรหัสจำนวนมากในภาษาต่างๆสำหรับการคำนวณแบบถาวรสำหรับเมทริกซ์ขนาดเล็ก MathematicaและMapleทั้งคู่มีการใช้งานอย่างถาวรหากคุณสามารถเข้าถึงได้

My Machineการจับเวลาจะทำงานบนเครื่อง 64 บิตของฉัน นี่คือการติดตั้ง Ubuntu มาตรฐานกับ 8GB RAM, AMD FX-8350 Eight-Core Processor และ Radeon HD 4250 นี่หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้

ข้อมูลระดับต่ำเกี่ยวกับเครื่องของฉัน

cat /proc/cpuinfo/|grep flags จะช่วยให้

ธง: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov ลูป pse36 sfpsffsfpfsffsfpsfpsfsfpsfsfsfpfxfxsffxfxfxfxfxfxfxfxfxfxqxfxfxfxfbfddt ที่ยอดเยี่ยมธง: fpu vme เดอ pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a palphs vbmc_fmstal_tws_tws_tws_twsfwfcfcfgwffgfgfgfgfgfgfgff

ฉันจะถามคำถามติดตามหลายภาษาที่เกี่ยวข้องอย่างใกล้ชิดที่ไม่ได้รับความเดือดร้อนจากปัญหา Int ขนาดใหญ่ดังนั้นผู้ที่ชื่นชอบScala , Nim , Julia , Rust , Bashสามารถแสดงภาษาของพวกเขาได้เช่นกัน

กระดานผู้นำ

  • n = 33 (45 วินาที. 64 วินาทีสำหรับ n = 34) Ton HospelในC ++พร้อม g ++ 5.4.0
  • n = 32 (32 วินาที) เดนนิสในCกับ gcc 5.4.0 โดยใช้ธง gcc ของ Ton Hospel
  • n = 31 (54 วินาที) Christian SieversในHaskell
  • n = 31 (60 วินาที) Primoในrpython
  • n = 30 (26 วินาที) ezrastในRust
  • n = 28 (49 วินาที) xnorกับPython + pypy 5.4.1
  • n = 22 (25 วินาที) ShebangกับPython + pypy 5.4.1

หมายเหตุ ในการฝึกซ้อมการกำหนดเวลาสำหรับเดนนิสและต้นทอนพระวรสารต่าง ๆ มากมายด้วยเหตุผลลึกลับ เช่นพวกเขาดูเหมือนจะเร็วขึ้นหลังจากที่ฉันโหลดเว็บเบราว์เซอร์! เวลาที่ยกมานั้นเร็วที่สุดในการทดสอบทั้งหมดที่ฉันทำ


5
ฉันอ่านประโยคแรกคิดว่า 'Lembik' เลื่อนลงมาอ๋อ - Lembik
orlp

@orlp :) มันนานมากแล้ว

1
@Lembik ฉันได้เพิ่มกรณีทดสอบขนาดใหญ่ ฉันจะรอคนที่จะยืนยันให้แน่ใจ
xnor

2
หนึ่งในคำตอบที่พิมพ์ผลโดยประมาณเนื่องจากมันใช้ความแม่นยำสองเท่าลอยเพื่อเก็บถาวร อนุญาตหรือไม่
เดนนิส

1
@ChristianSievers ฉันคิดว่าฉันอาจจะสามารถทำเวทมนตร์ด้วยสัญญาณบางอย่าง แต่มันก็ไม่ได้เลื่อนออกไป ...
Socratic Phoenix

คำตอบ:


13

gcc C ++ n ≈ 36 (57 วินาทีในระบบของฉัน)

ใช้สูตร Glynn ด้วยรหัสสีเทาสำหรับการอัปเดตหากจำนวนคอลัมน์ทั้งหมดเท่ากันมิฉะนั้นจะใช้วิธีของ Ryser เธรดและ vectorized ปรับให้เหมาะสมสำหรับ AVX ดังนั้นอย่าคาดหวังมากกับโปรเซสเซอร์รุ่นเก่า อย่ากังวลกับn>=35เมทริกซ์ที่มีเพียง +1 แม้ว่าระบบของคุณจะเร็วพอเนื่องจากตัวสะสม 128 บิตที่ลงนามแล้วจะล้น สำหรับแมทริกซ์แบบสุ่มคุณอาจไม่โดนกระแสเกิน สำหรับ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 ลูป pse36 sfpsffsfpfsffsfpsfpsfsfpsfsfsfpfxfxsffxfxfxfxfxfxfxfxfxfxqxfxfxfxfbfddt ที่ยอดเยี่ยมธง: fpu vme เดอ pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 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

ฉันยังคงทำการดีบักชุดทดสอบของฉันเพื่อเรียกใช้รหัสของคุณ แต่มันก็ดูเร็วมากขอบคุณ! ฉันสงสัยว่าขนาด int ที่ใหญ่ขึ้นอาจทำให้เกิดปัญหาเรื่องความเร็ว (ตามที่คุณแนะนำ) ฉันเห็นaccu.org/index.php/articles/1849ในกรณีที่มีความสนใจ

ฉันต้องแก้ไขรหัสของคุณเพื่อลบ quick_exit เนื่องจากมันทำให้ยากมากที่จะใช้ในชุดทดสอบ ทำไมคุณถึงใช้สูตรของ Ryser เมื่อ wiki ดูเหมือนว่าจะอ้างว่าอีกสูตรหนึ่งควรเร็วเป็นสองเท่า

@ 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 และคลี่วงใหญ่บางส่วน (เพื่อดูว่าฉันสามารถเพิ่มความเร็วในการขนานและคำนวณค่ามากกว่า 4 ค่าในครั้งเดียวโดยไม่ต้องเรียกpopcnt) หากประหยัดเวลาใด ๆ อุปสรรคใหญ่ถัดไปคือประเภทจำนวนเต็ม สำหรับเมทริกซ์ที่สร้างแบบสุ่มค่าแบบถาวรจะค่อนข้างเล็ก หากฉันสามารถหาวิธีที่ง่ายในการคำนวณขอบเขตก่อนทำการคำนวณจริงฉันอาจหุ้มทั้งหมดด้วยเงื่อนไขขนาดใหญ่
เดนนิส

@Dennis เกี่ยวกับการปลดลูปการเพิ่มประสิทธิภาพเล็ก ๆ ที่เป็นไปได้คือการทำให้แถวบนเป็น +1 ทั้งหมด
xnor

@ xnor ใช่ฉันลองแล้วในบางช่วงเวลา แต่จากนั้นย้อนกลับการเปลี่ยนแปลงเพื่อลองอย่างอื่น (ซึ่งไม่ได้ผลเลย ) คอขวดน่าจะเป็นคูณจำนวนเต็ม (ซึ่งช้า 64 บิตและจริงๆช้า 128) ซึ่งเป็นเหตุผลที่ผมหวังว่าจะช่วยให้ SSE บิต
เดนนิส

1
@ เดนนิสฉันเห็น เกี่ยวกับขอบเขตขอบเขตที่ไม่ชัดเจนอย่างหนึ่งคือในแง่ของมาตรฐานตัวดำเนินการ | ต่อ (M) | <= | M | ^ n ดูarxiv.org/pdf/1606.07474v1.pdf
xnor

5

Python 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

การใช้สูตรของ Ryser นั้นคล้ายคลึงกันมากโดยรวมเอา 0/1 ของสัมประสิทธิ์ทั้งหมดมากกว่า± 1-vectors จะใช้เวลาประมาณสองเท่าของสูตรกลีนน์เพราะจะเพิ่มมากกว่าทุก 2 ^ n +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

น่ากลัว คุณมี pypy เพื่อทดสอบด้วยหรือไม่

@ Lembik ไม่ฉันไม่ได้ติดตั้งมาก
xnor

ฉันจะใช้ pypy เมื่อทดสอบด้วย คุณเห็นวิธีการใช้สูตรรวดเร็วอื่น ๆ หรือไม่ ฉันคิดว่ามันสับสน

@ Lembik สูตรเร็วอื่น ๆ คืออะไร?
xnor

1
ตามการอ้างอิงบนเครื่องของฉันด้วยpypyสิ่งนี้สามารถคำนวณได้อย่างง่ายดายn=28ใน 44.6 วินาที ดูเหมือนว่าระบบของ Lembik จะค่อนข้างเทียบเคียงกับของฉันในความเร็วถ้าไม่เร็วขึ้นเล็กน้อย
Kade

4

Rust + extprim

Ryser ที่ใช้งานง่ายพร้อมโค้ดสีเทาใช้เวลาประมาณ65 90 วินาทีในการรัน n = 31 บนแล็ปท็อปของฉัน ฉันคิดว่าเครื่องของคุณจะไปถึงที่นั่นภายใน 60 ปี ฉันใช้ extprim 1.1.1 i128สำหรับ

ฉันไม่เคยใช้สนิมและไม่รู้ว่ากำลังทำอะไรอยู่ ไม่มีตัวเลือกคอมไพเลอร์อื่น ๆ นอกเหนือจากสิ่งที่cargo build --releaseไม่ ความเห็น / ข้อเสนอแนะ / การเพิ่มประสิทธิภาพได้รับการชื่นชม

การเรียกใช้นั้นเหมือนกับโปรแกรมของเดนนิส

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

คุณสามารถให้บรรทัดคำสั่ง copy และ pasteable สำหรับการติดตั้ง extprim และคอมไพล์โค้ดได้ไหม

เอาต์พุตดูเหมือนว่า "i128! (- 2)" โดยที่ -2 เป็นคำตอบที่ถูกต้อง นี่เป็นสิ่งที่คาดหวังและคุณสามารถเปลี่ยนเป็นเพียงเพื่อเอาท์พุทถาวรได้ไหม?

1
@Lembik: การส่งออกควรได้รับการแก้ไขในขณะนี้ ดูเหมือนว่าคุณจะเข้าใจการรวบรวม แต่ฉันโยนมันใน Git เพื่อให้คุณสามารถทำgit clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --releaseถ้าคุณต้องการให้แน่ใจว่ามีการตั้งค่าเดียวกันกับฉัน สินค้าจะจัดการการพึ่งพา Binary target/releaseไปใน
ezrast

น่าเสียดายที่นี่ให้คำตอบที่ผิดสำหรับ n = 29 bpaste.net/show/99d6e826d968

1
@ Lembik gah ขอโทษค่าระดับกลางล้นก่อนหน้านี้ฉันคิดว่า ได้รับการแก้ไขแล้วแม้ว่าโปรแกรมจะช้าลงมากในขณะนี้
ezrast

4

Haskell, 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 คุณสามารถดูขั้นตอนการปรับให้เหมาะสมจำนวนมากผ่านประวัติการแก้ไข น่าแปลกใจที่มันเป็นการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ รหัสจะขึ้นอยู่กับสูตรในส่วน "สูตร Balasubramanian-Bax / Franklin-Glynn" ในบทความ Wikipedia ที่การคำนวณถาวร

pคำนวณค่าถาวร มันถูกเรียกผ่านptซึ่งแปลงเมทริกซ์ด้วยวิธีที่ถูกต้องเสมอ แต่มีประโยชน์อย่างยิ่งสำหรับเมทริกซ์ที่เราได้รับที่นี่

รวบรวมกับ ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hsคอมไพล์ด้วยในการทำงานด้วย parallelisation ./<name> +RTS -Nให้มันรันไทม์พารามิเตอร์เช่นนี้ ข้อมูลมาจาก stdin พร้อมรายการที่คั่นด้วยเครื่องหมายจุลภาคซ้อนกันในวงเล็บเหมือน[[1,2],[3,4]]ในตัวอย่างที่ผ่านมา (ขึ้นบรรทัดใหม่ได้ทุกที่)


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)- ผลิตภัณฑ์ในรูปแบบ monadic โดยที่ 0 เป็นกรณีพิเศษ ดูเหมือนว่าจะเป็นประโยชน์มากกว่าบ่อยครั้ง
Angs

1
@ อันยิ่งใหญ่! ฉันเปลี่ยนมันให้อยู่ในรูปแบบที่ไม่ต้องการTransversable(ฉันเห็นว่าคุณไม่เปลี่ยนproducteatlier ก็ไม่มีข้อผิดพลาด ... ) สำหรับ ghc จาก Debian เสถียร มันใช้รูปแบบการป้อนข้อมูล แต่ดูเหมือนว่าใช้ได้: เราไม่ได้พึ่งพา แต่เพียงการปรับให้เหมาะสมเท่านั้น ทำให้เวลาน่าตื่นเต้นมากขึ้น: เมทริกซ์ 30x30 แบบสุ่มของฉันเร็วกว่า 29x29 เล็กน้อย แต่จากนั้น 31x31 ใช้เวลา 4x - INLINE นั้นดูเหมือนจะไม่ได้ผลสำหรับฉัน AFAIK ไม่สนใจฟังก์ชั่นวนซ้ำ
Christian Sievers

1
@ christian-sievers ใช่ฉันกำลังจะพูดอะไรบางอย่างเกี่ยวกับเรื่องนั้นproduct แต่ลืมไปแล้ว ดูเหมือนว่าความยาวจะมีศูนย์อยู่pด้วยดังนั้นสำหรับความยาวคี่เราควรใช้ผลิตภัณฑ์ปกติแทนการลัดวงจรเพื่อให้ได้ประโยชน์สูงสุดจากทั้งสองโลก
อ่างทอง

3

Mathematica, 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 วินาทีในระบบของฉัน สิ่งนี้ไม่ได้มีประสิทธิภาพเหมือนกับที่อื่นเพราะมันขึ้นอยู่กับความจริงที่ว่าถาวรสามารถพบได้เป็นค่าสัมประสิทธิ์ของผลิตภัณฑ์ของ polymomials จากแต่ละแถวของเมทริกซ์ ListConvolveที่มีประสิทธิภาพการคูณพหุนามจะดำเนินการโดยการสร้างรายการค่าสัมประสิทธิ์และมีประสิทธิภาพโดยใช้บิด สิ่งนี้ต้องใช้เวลาประมาณO (2 n n 2 ) ในการสันนิษฐานว่าใช้วิธีการแปลงรูปแบบ Fast Fourier หรือคล้ายกันซึ่งต้องใช้เวลาO ( n log n )


3

Python 2, n = 22 [อ้างอิง]

นี่คือการใช้งาน 'อ้างอิง' ที่ฉันแบ่งปันกับ Lembik เมื่อวานนี้มันพลาดไปn=23สองสามวินาทีบนเครื่องของเขาบนเครื่องของฉันมันใช้งานได้ในประมาณ 52 วินาที เพื่อให้บรรลุความเร็วเหล่านี้คุณต้องเรียกใช้ผ่าน PyPy

ฟังก์ชั่นแรกจะคำนวณค่าถาวรคล้ายกับวิธีคำนวณดีเทอร์มิแนนต์โดยไปที่แต่ละ submatrix จนกว่าคุณจะเหลือ 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

วิธี

ผมเคยใช้สูตร Balasubramanian-แบ็กซ์ / แฟรงคลิน-กลีนน์ที่มีความซับซ้อนรันไทม์ของO (2 n n) อย่างไรก็ตามแทนที่จะวนซ้ำδตามลำดับรหัสสีเทาฉันได้แทนที่การคูณเวกเตอร์แถวด้วยการดำเนินการ xor เดียว (การแมป (1, -1) →→ (0, 1) ผลรวมเวกเตอร์นั้นสามารถพบได้ในการดำเนินการครั้งเดียวโดยการลบ n จำนวนสองครั้ง


น่าเสียดายที่รหัสนี้ให้คำตอบที่ผิดสำหรับbpaste.net/show/8690251167e7

@Lembik อัปเดตแล้ว จากความอยากรู้คุณช่วยบอกผลลัพธ์ของรหัสต่อไปนี้ให้ฉันได้ไหม bpaste.net/show/76ec65e1b533
primo

มันให้ "True 18446744073709551615" ฉันได้เพิ่มผลลัพธ์สำหรับรหัสที่ดีของคุณตอนนี้เช่นกัน

@ Lembik ขอบคุณ ฉันแบ่งการคูณแล้วเพื่อไม่ให้เกิน 63 บิต ผลลัพธ์มีการระบุไว้ 8 กระทู้หรือไม่? 2 หรือ 4 สร้างความแตกต่างหรือไม่? หาก 30 เสร็จสิ้นใน 25 ดูเหมือนว่า 31 ควรต่ำกว่าหนึ่งนาที
โม่

-1

แร็กเก็ต 84 ไบต์

ฟังก์ชั่นที่เรียบง่ายต่อไปนี้ใช้ได้กับเมทริกซ์ขนาดเล็ก แต่แขวนบนเครื่องของฉันสำหรับเมทริกซ์ขนาดใหญ่

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

Ungolfed:

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