Berechnen Sie die bleibende Karte so schnell wie möglich


26

Die Herausforderung besteht darin, den schnellstmöglichen Code für die Berechnung der Permanenz einer Matrix zu schreiben .

Die Permanenz einer n-by- nMatrix A= ( ai,j) ist definiert als

Bildbeschreibung hier eingeben

Hier wird S_ndie Menge aller Permutationen von dargestellt [1, n].

Als Beispiel (aus dem Wiki):

Bildbeschreibung hier eingeben

In dieser Frage sind Matrizen alle quadratisch und haben nur die Werte -1und 1in ihnen.

Beispiele

Eingang:

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

Permanent:

-4

Eingang:

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

Permanent:

0

Eingang:

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

Permanent:

192

Eingang:

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

Permanent:

1021509632

Die Aufgabe

Sie sollten Code schreiben, der bei einer ndurch nMatrix seine bleibende Zahl ausgibt.

Da ich Ihren Code testen muss, wäre es hilfreich, wenn Sie mir eine einfache Möglichkeit geben könnten, eine Matrix als Eingabe für Ihren Code zu verwenden, z. B. durch Einlesen von standard in.

Seien Sie gewarnt, dass die bleibende Zahl groß sein kann (die All-1s-Matrix ist der Extremfall).

Partituren und Krawatten

Ich werde Ihren Code auf zufälligen + -1 Matrizen mit zunehmender Größe testen und stoppen, wenn Ihr Code zum ersten Mal länger als 1 Minute auf meinem Computer dauert. Die Bewertungsmatrizen werden für alle Einreichungen konsistent sein, um die Fairness zu gewährleisten.

Wenn zwei Personen die gleiche Punktzahl erzielen, ist der Gewinner derjenige, der für diesen Wert von am schnellsten ist n. Wenn diese innerhalb einer Sekunde voneinander entfernt sind, ist dies die zuerst veröffentlichte.

Sprachen und Bibliotheken

Sie können jede verfügbare Sprache und Bibliothek verwenden, die Sie möchten, aber keine bereits vorhandene Funktion, um die permanente zu berechnen. Wo immer möglich, wäre es gut, wenn Sie Ihren Code ausführen könnten. Fügen Sie daher bitte eine vollständige Erklärung dazu bei, wie Sie Ihren Code unter Linux ausführen / kompilieren, wenn dies überhaupt möglich ist. "

Referenzimplementierungen

Es gibt bereits eine Codegolf-Frage mit viel Code in verschiedenen Sprachen zur Berechnung der Permanenz für kleine Matrizen. Mathematica und Maple haben auch permanente Implementierungen, wenn Sie auf diese zugreifen können.

Mein Computer Die Timings werden auf meinem 64-Bit-Computer ausgeführt. Dies ist eine Ubuntu-Standardinstallation mit 8 GB RAM, AMD FX-8350 Eight-Core-Prozessor und Radeon HD 4250. Dies bedeutet auch, dass ich in der Lage sein muss, Ihren Code auszuführen.

Niedrige Informationen zu meiner Maschine

cat /proc/cpuinfo/|grep flags gibt

Fahnen: FPU vme de pse tsc msr PAE mce CX8 APIC September mtrr PGE mca cmov pat PSE36 CLFLUSH MMX fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm CONSTANT_TSC rep_good NOPlat NONSTOP_TSC extd_apicid aperfmperf pni pclmulqdq überwachen SSSE3 fma CX16 sse4_1 sse4_2 popcnt aes XSAVE AVX f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch

Ich werde eine eng verwandte mehrsprachige Folgefrage stellen, die nicht unter dem großen Int-Problem leidet, damit Liebhaber von Scala , Nim , Julia , Rust und Bash auch ihre Sprachen zur Schau stellen können.

Bestenliste

  • n = 33 (45 Sekunden. 64 Sekunden für n = 34). Ton Hospel in C ++ mit g ++ 5.4.0.
  • n = 32 (32 Sekunden). Dennis in C mit gcc 5.4.0 unter Verwendung der gcc-Flags von Ton Hospel.
  • n = 31 (54 Sekunden). Christian Sievers in Haskell
  • n = 31 (60 Sekunden). primo in rpython
  • n = 30 (26 Sekunden). Ezrast in Rust
  • n = 28 (49 Sekunden). xnor mit Python + pypy 5.4.1
  • n = 22 (25 Sekunden). Shebang mit Python + Pypy 5.4.1

Hinweis . In der Praxis variieren die Timings für Dennis und Ton Hospel aus mysteriösen Gründen stark. Zum Beispiel scheinen sie schneller zu sein, nachdem ich einen Webbrowser geladen habe! Die angegebenen Zeiten sind die schnellsten in allen Tests, die ich gemacht habe.


5
Ich las den ersten Satz, dachte 'Lembik', scrollte runter, yep - Lembik.
Orlp

@orlp :) Es ist lange her.

1
@Lembik Ich habe einen großen Testfall hinzugefügt. Ich werde auf jemanden warten, der es bestätigt, um sicherzugehen.
Xnor

2
Eine der Antworten gibt ein ungefähres Ergebnis aus, da es Gleitkommazahlen mit doppelter Genauigkeit verwendet, um die bleibende Karte zu speichern. Ist das erlaubt
Dennis

1
@ChristianSievers Ich dachte, ich könnte etwas Magie mit Zeichen machen, aber es hat nicht
Socratic Phoenix

Antworten:


13

gcc C ++ n ≈ 36 (57 Sekunden auf meinem System)

Verwendet die Glynn-Formel mit einem Gray-Code für Aktualisierungen, wenn alle Spaltensummen gerade sind, andernfalls wird die Ryser-Methode verwendet. Threaded und vektorisiert. Optimiert für AVX, erwarten Sie also nicht viel von älteren Prozessoren. Sorgen Sie nicht n>=35für eine Matrix mit nur +1, auch wenn Ihr System schnell genug ist, da der signierte 128-Bit-Akku überläuft. Bei Zufallsmatrizen werden Sie wahrscheinlich nicht auf den Überlauf stoßen. Für n>=37die internen Multiplikatoren beginnt ein Überlauf für alle 1/-1Matrizen. Verwenden Sie dieses Programm also nur für n<=36.

Geben Sie die Matrixelemente in STDIN einfach durch Leerzeichen getrennt an

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

Fahnen: FPU vme de pse tsc msr PAE mce CX8 APIC September mtrr PGE mca cmov pat PSE36 CLFLUSH MMX fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm CONSTANT_TSC rep_good NOPlat NONSTOP_TSC extd_apicid aperfmperf pni pclmulqdq überwachen SSSE3 fma CX16 sse4_1 sse4_2 popcnt aes XSAVE AVX F16C lahf_lm cmp_legacy SVM extapic cr8_legacy abm SSE4A misalignsse 3DNowPrefetch osvw ibs xop skinit wdt lwp FMA4 t SKE 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

Ich debugge immer noch mein Testgeschirr, um Ihren Code auszuführen, aber es sieht sehr schnell aus, danke! Ich habe mich gefragt, ob die größere int-Größe möglicherweise ein Geschwindigkeitsproblem verursacht (wie Sie vorgeschlagen haben). Ich habe accu.org/index.php/articles/1849 gesehen, falls es von Interesse ist.

Ich musste Ihren Code ändern, um den quick_exit zu entfernen, da dies die Verwendung in einem Test-Harness sehr erschwerte. Warum verwenden Sie aus Interesse die Ryser-Formel, wenn das Wiki zu behaupten scheint, dass das andere doppelt so schnell sein sollte?

@Lembik Ich habe zu Rysers Formel gewechselt, da ich bei der anderen Formel 2 << (n-1)am Ende eine Reduzierung vornehmen muss, was bedeutet, dass mein int128-Akku weit vor diesem Zeitpunkt übergelaufen ist.
Ton Hospel

1
@ Lembik Ja :-)
Ton Hospel

7

C99, n ≈ 33 (35 Sekunden)

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

Die Eingabe ist derzeit etwas umständlich; Es werden Zeilen als Befehlszeilenargumente verwendet, wobei jeder Eintrag durch sein Vorzeichen dargestellt wird, dh + zeigt eine 1 an und - zeigt eine -1 an .

Testlauf

$ 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

Haben Sie Verbesserungsvorschläge?
XNOR

@xnor Ein paar. Ich möchte eine gepackte Multiplikation mit SSE versuchen und die große Schleife teilweise ausrollen (um zu sehen, ob ich die Parallelisierung beschleunigen und mehr als 4 Werte gleichzeitig berechnen kann, ohne aufzurufen popcnt). Wenn das Zeit spart, ist die nächste große Hürde der Integer-Typ. Für zufällig erzeugte Matrizen ist die bleibende Zahl vergleichsweise klein. Wenn ich einen einfachen Weg finden kann, um eine Grenze zu berechnen, bevor ich die eigentliche Berechnung durchführe, könnte ich das Ganze in eine große Bedingung einwickeln.
Dennis

@Dennis Beim Abrollen der Schleife besteht eine kleine mögliche Optimierung darin, die oberste Reihe auf alle +1 zu setzen.
Xnor

@xnor Ja, ich versuchte , dass an einem gewissen Punkt, aber dann die Änderung rückgängig gemacht , etwas anderes zu versuchen (die nicht funktionierten überhaupt ). Der Engpass scheint die ganzzahlige Multiplikation zu sein (die für 64 Bit langsam und für 128 Bit wirklich langsam ist), weshalb ich hoffe, dass SSE ein bisschen hilft.
Dennis

1
@ Tennis Ich verstehe. Bei Grenzen handelt es sich bei einer nicht offensichtlichen Grenze um die Operatornorm | Per (M) | <= | M | ^ n. Siehe 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

Verwendet die Glynn-Formel mit einem Gray-Code für Aktualisierungen. Läuft n=23in einer Minute auf meinem Computer. Dies kann man sicherlich besser in einer schnelleren Sprache und mit besseren Datenstrukturen umsetzen. Dies bedeutet nicht, dass die Matrix einen Wert von ± 1 hat.

Eine Ryser-Formelimplementierung ist sehr ähnlich und summiert über alle 0/1-Vektoren von Koeffizienten anstatt über ± 1-Vektoren. Es dauert ungefähr doppelt so lange wie Glynns Formel, da über alle 2 ^ n solche Vektoren addiert werden, wohingegen Glynns Hälften die Symmetrie nur für diejenigen verwenden, die mit beginnen +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

Genial. Hast du auch etwas zu testen?

@Lembik Nein, ich habe nicht viel installiert.
Donnerstag,

Ich werde pypy verwenden, wenn ich es auch teste. Können Sie sehen, wie die andere schnelle Formel implementiert wird? Ich finde es verwirrend.

@Lembik Was ist die andere schnelle Formel?
Xnor

1
Als Referenz konnte auf meiner Maschine damit in 44,6 Sekunden pypyproblemlos gerechnet werden n=28. Lembiks System scheint ziemlich schnell zu sein, wenn nicht sogar ein bisschen schneller.
Kade

4

Rust + extprim

Dieser einfache Ryser mit Gray-Code-Implementierung benötigt ungefähr 65 bis 90 Sekunden, um n = 31 auf meinem Laptop auszuführen. Ich kann mir vorstellen, dass Ihre Maschine in weit unter 60 Jahren dort ankommt. Ich benutze extprim 1.1.1 für i128.

Ich habe Rust noch nie benutzt und weiß nicht, was ich tue. Keine anderen Compileroptionen als die, die dies tun cargo build --release. Kommentare / Vorschläge / Optimierungen sind willkommen.

Der Aufruf ist identisch mit Dennis 'Programm.

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

Könnten Sie bitte copy und pasteable Kommandozeilen für die Installation von extprim und das Kompilieren des Codes angeben.

Die Ausgabe sieht aus wie "i128! (- 2)", wobei -2 die richtige Antwort ist. Wird dies erwartet und könnten Sie es bitte ändern, um nur die permanente auszugeben?

1
@Lembik: Ausgabe sollte jetzt behoben sein. Sieht so aus, als hätten Sie die Kompilierung herausgefunden, aber ich habe sie in Git geworfen, damit Sie sie ausführen können, git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --releasewenn Sie sicher sein möchten, dass Sie dasselbe Setup wie ich haben. Die Fracht übernimmt die Abhängigkeiten. Binär geht rein target/release.
Ezrast

Leider gibt dies die falsche Antwort für n = 29. bpaste.net/show/99d6e826d968

1
@Lembik gah, sorry, Zwischenwerte sind früher übergelaufen als ich dachte. Es ist behoben, obwohl das Programm jetzt viel langsamer ist.
Esrast

4

Haskell, n = 31 (54 s)

Mit vielen unschätzbaren Beiträgen von @Angs: Verwenden Sie Vector, verwenden Sie Kurzschlussprodukte, schauen Sie sich ungerade n an.

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

Meine ersten Parallelitätsversuche in Haskell. Sie können viele Optimierungsschritte im Revisionsverlauf sehen. Erstaunlicherweise waren es meist sehr kleine Änderungen. Der Code basiert auf der Formel im Abschnitt "Balasubramanian-Bax / Franklin-Glynn-Formel" im Wikipedia-Artikel zur Berechnung der bleibenden Karte .

pberechnet die bleibende. Es heißt über ptdas die Matrix in einer Weise transformiert wird, die immer gültig ist, aber besonders nützlich für die Matrizen, die wir hier erhalten.

Kompilieren mit ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs. So führen Sie mit Parallelisierung, geben Parameter Laufzeit wie folgt aus : ./<name> +RTS -N. Die Eingabe erfolgt wie [[1,2],[3,4]]im letzten Beispiel von stdin mit durch Kommas getrennten Listen in Klammern (Zeilenumbrüche sind überall zulässig).


1
Durch das Einstecken konnte ich eine Geschwindigkeitsverbesserung von 20-25% erzielen Data.Vector. Die Änderungen vorbehalten Funktionstypen geändert: 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 Vielen Dank! Ich hatte nicht wirklich Lust, besser geeignete Datentypen zu untersuchen. Es ist erstaunlich, wie wenig sich ändern muss (musste auch genutzt werden V.product). Das gab mir nur ~ 10%. Der Code wurde so geändert, dass die Vektoren nur Ints enthalten . Das ist in Ordnung, weil sie nur addiert werden, die großen Zahlen stammen aus der Multiplikation. Dann waren es ~ 20%. Ich hatte die gleiche Änderung mit dem alten Code versucht, aber zu diesem Zeitpunkt verlangsamte es es. Ich habe es noch einmal versucht, weil es erlaubt, Vektoren ohne Box zu verwenden , was sehr geholfen hat!
Christian Sievers

1
@ christian-sievers glab könnte mir weiterhelfen. Hier ist eine weitere spaßige, auf Glück basierende Optimierung, die ich gefunden habe: x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)- Produkt als monadische Falte, wobei 0 ein Sonderfall ist. Scheint oft nützlich zu sein.
Angs

1
@Angs Großartig! Ich habe das in eine Form geändert, die nicht benötigt wird Transversable(ich sehe, dass productes kein Fehler war, den Esser nicht zu ändern ...) für ghc von zB Debian Stable. Es wird die Form der Eingabe verwendet, aber das scheint in Ordnung zu sein: Wir verlassen uns nicht darauf, sondern optimieren nur dafür. Das Timing ist viel aufregender: Meine zufällige 30x30-Matrix ist etwas schneller als 29x29, aber 31x31 dauert dann 4x. - Dass INLINE bei mir nicht funktioniert. AFAIK wird für rekursive Funktionen ignoriert.
Christian Sievers

1
@ christian-sievers Ja, ich wollte gerade etwas dazu sagen, habe es product aber vergessen. Es scheint, als hätten nur gerade Längen Nullen. pFür ungerade Längen sollten wir das reguläre Produkt anstelle des Kurzschlusses verwenden, um das Beste aus beiden Welten zu erhalten.
Angs

3

Mathematica, Nr. 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}]]]

Mit dem TimingBefehl benötigt eine 20x20-Matrix auf meinem System ungefähr 48 Sekunden. Dies ist nicht genau so effizient wie das andere, da es auf der Tatsache beruht, dass die bleibende Karte als der Koeffizient des Produkts von Polymomen aus jeder Reihe der Matrix gefunden werden kann. Eine effiziente Polynommultiplikation wird durchgeführt, indem die Koeffizientenlisten erstellt und eine Faltung unter Verwendung von durchgeführt werden ListConvolve. Dies erfordert ungefähr 0 (2 n n 2 ) Zeit, vorausgesetzt, die Faltung wird unter Verwendung einer Fast Fourier-Transformation oder ähnlichem durchgeführt, was 0 ( n log n ) Zeit erfordert .


3

Python 2, n = 22 [Referenz]

Dies ist die 'Referenz'-Implementierung, die ich gestern mit Lembik geteilt habe n=23 auf seiner Maschine um einige Sekunden , auf meiner Maschine dauert es ungefähr 52 Sekunden. Um diese Geschwindigkeiten zu erreichen, müssen Sie dies über PyPy ausführen.

Die erste Funktion berechnet die bleibende Zahl ähnlich wie die Determinante berechnet werden könnte, indem Sie jede Untermatrix durchgehen, bis Sie ein 2x2 übrig haben, auf das Sie die Grundregel anwenden können. Es ist unglaublich langsam .

Die zweite Funktion implementiert die Ryser-Funktion (die zweite in Wikipedia aufgeführte Gleichung). Die Menge Sist im Wesentlichen die Potenz der Zahlen {1,...,n}(variabel s_listim Code).

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

Ich denke, Sie sollten die Beschreibung "ähnlich wie die Determinante berechnet würde" umformulieren. Es ist nicht so, dass die Methode für Determinanten für bleibende Karten langsam ist, aber eine langsame Methode für Determinanten funktioniert ähnlich (und ebenso langsam) für bleibende Karten.
Christian Sievers

1
@ ChristianSievers Guter Punkt, ich habe es geändert.
Kade

2

RPython 5.4.1, n ≈ 32 (37 Sekunden)

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

Laden Sie zum Kompilieren die neueste PyPy-Quelle herunter und führen Sie Folgendes aus:

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

Die resultierende ausführbare Datei wird benannt matrix-permanent-c im aktuellen Arbeitsverzeichnis oder ähnelt diesen.

Ab PyPy 5.0 sind RPythons Threading-Primitive viel weniger primitiv als früher. Neu erzeugte Threads erfordern die GIL, die für parallele Berechnungen praktisch unbrauchbar ist. Ich habe forkstattdessen verwendet, so dass es unter Windows möglicherweise nicht wie erwartet funktioniert, obwohl ich das Kompilieren ( unresolved external symbol _fork) nicht erfolgreich getestet habe .

Die ausführbare Datei akzeptiert bis zu zwei Befehlszeilenparameter. Der erste ist die Anzahl der Threads, der zweite optionale Parameter ist n. Wenn es bereitgestellt wird, wird eine zufällige Matrix generiert, andernfalls wird es von stdin gelesen. Jede Zeile muss durch Zeilenumbrüche (ohne abschließende Zeilenumbrüche) getrennt sein und jeder Wert muss durch Leerzeichen getrennt sein. Die dritte Beispieleingabe würde wie folgt lauten:

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

Beispielnutzung

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

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

Methode

Ich habe die Balasubramanian-Bax / Franklin-Glynn-Formel mit einer Laufzeitkomplexität von O (2 n n) verwendet . Anstatt jedoch das δ in grauer Codereihenfolge zu iterieren , habe ich stattdessen die Vektorzeilenmultiplikation durch eine einzelne xor-Operation ersetzt (Abbildung (1, -1) → (0, 1)). Die Vektorsumme kann ebenfalls in einer einzigen Operation gefunden werden, indem n minus das Doppelte der Popcount-Zahl genommen wird.


Leider gibt der Code die falsche Antwort für bpaste.net/show/8690251167e7

@Lembik aktualisiert. Können Sie mir aus Neugier das Ergebnis des folgenden Codes mitteilen? bpaste.net/show/76ec65e1b533
primo

Es gibt "True 18446744073709551615" Ich habe die Ergebnisse für Ihre sehr schön, jetzt auch Code hinzugefügt.

@ Lembik danke. Ich hatte die Multiplikation bereits aufgeteilt, um 63-Bit nicht zu überlaufen. Wurde das aufgelistete Ergebnis mit 8 Threads aufgenommen? Machen 2 oder 4 einen Unterschied? Wenn 30 in 25 endet, sollte 31 weniger als eine Minute dauern.
Primo

-1

Schläger 84 Bytes

Die folgende einfache Funktion funktioniert für kleinere Matrizen, hängt aber für größere Matrizen an meiner Maschine:

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

Der Code kann leicht für eine ungleiche Anzahl von Zeilen und Spalten geändert werden.

Testen:

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

Ausgabe:

-4
192

Wie oben erwähnt, hängt es beim Testen von Folgendem ab:

(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
Ist diese Antwort in der Codegolf-Version besser als in der Speed-Version dieser Frage?
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.