Komputer: Anda melakukan perhitungan

13

Tantangan ini sebagian merupakan tantangan algoritma, melibatkan beberapa matematika dan sebagian hanya merupakan tantangan kode tercepat.

Untuk bilangan bulat positif n, pertimbangkan string acak 1s dan 0s yang panjangnya seragam ndan sebut saja A. Sekarang juga pertimbangkan string acak panjang nyang dipilih secara seragam dan kedua yang nilainya adalah -1, 0,atau 1dan menyebutnya B_pre. Sekarang mari Bmenjadi B_pre+ B_pre. Itu B_predisatukan dengan dirinya sendiri.

Sekarang perhatikan produk dalam Adan B[j,...,j+n-1]dan menyebutnya Z_jdan indeks dari 1.

Tugas

Outputnya harus berupa daftar n+1fraksi. The iIstilah th di output harus tepat probabilitas bahwa semua yang pertama iistilah Z_jdengan j <= isama 0.

Skor

Yang terbesar di nmana kode Anda memberikan hasil yang benar dalam waktu kurang dari 10 menit pada mesin saya.

Tie Breaker

Jika dua jawaban memiliki skor yang sama, jawaban yang pertama menang.

Dalam peristiwa (sangat sangat) tidak mungkin bahwa seseorang menemukan metode untuk mendapatkan skor tanpa batas, bukti valid pertama dari solusi semacam itu akan diterima.

Petunjuk

Jangan mencoba menyelesaikan masalah ini secara matematis, itu terlalu sulit. Cara terbaik dalam pandangan saya adalah kembali ke definisi dasar probabilitas dari sekolah menengah dan menemukan cara cerdas untuk mendapatkan kode untuk melakukan enumerasi lengkap tentang kemungkinan.

Bahasa dan perpustakaan

Anda dapat menggunakan bahasa apa pun yang memiliki kompiler / interpreter / dll yang tersedia secara bebas. untuk Linux dan semua perpustakaan yang juga tersedia secara bebas untuk Linux.

Mesin saya Pengaturan waktu akan dijalankan di mesin saya. Ini adalah instalasi ubuntu standar pada Prosesor Delapan Core AMD FX-8350. Ini juga berarti saya harus dapat menjalankan kode Anda. Sebagai akibatnya, hanya gunakan perangkat lunak gratis yang mudah tersedia dan harap sertakan instruksi lengkap cara menyusun dan menjalankan kode Anda.


Beberapa hasil tes. Pertimbangkan output pertama untuk masing-masing n. Saat itulah i=1. Untuk n1-13 seharusnya.

 1: 4/6
 2: 18/36
 3: 88/216
 4: 454/1296
 5: 2424/7776
 6: 13236/46656
 7: 73392/279936
 8: 411462/1679616
 9: 2325976/10077696
10: 13233628/60466176
11: 75682512/362797056
12: 434662684/2176782336
13: 2505229744/13060694016

Anda juga dapat menemukan formula umum untuk i=1di http://oeis.org/A081671 .

Papan (dibagi berdasarkan bahasa)

  • n = 15. Python + python paralel + pypy dalam 1min49detik oleh Jakube
  • n = 17. C ++ dalam 3min37s oleh Keith Randall
  • n = 16. C ++ dalam 2min38s oleh kuroi neko
Martin Ender
sumber
1
@Knerd Bagaimana saya bisa bilang tidak. Saya akan mencoba mencari cara menjalankan kode Anda di linux tetapi bantuan sangat dihargai.
Oke, maaf karena menghapus komentar. Untuk semua yang tidak membaca, itu jika F # atau C # diizinkan :)
Knerd
Pertanyaan lain lagi, apakah Anda mungkin memiliki contoh output input yang valid?
Knerd
Apa kartu grafis Anda? Sepertinya pekerjaan untuk GPU.
Michael M.
1
@Knerd saya menambahkan tabel probabilitas untuk pertanyaan itu. Saya harap ini membantu.

Jawaban:

5

C ++, n = 18 dalam 9 menit pada 8 utas

(Beri tahu saya jika itu membuatnya kurang dari 10 menit di komputer Anda.)

Saya mengambil keuntungan dari beberapa bentuk simetri dalam array B. Itu adalah siklik (bergeser dengan satu posisi), pembalikan (membalik urutan elemen), dan tanda (ambil negatif dari setiap elemen). Pertama saya menghitung daftar B yang perlu kita coba dan beratnya. Kemudian setiap B dijalankan melalui rutin cepat (menggunakan instruksi bitcount) untuk semua nilai 2 ^ n dari A.

Inilah hasil untuk n == 18:

> time ./a.out 18
 1: 16547996212044 / 101559956668416
 2:  3120508430672 / 101559956668416
 3:   620923097438 / 101559956668416
 4:   129930911672 / 101559956668416
 5:    28197139994 / 101559956668416
 6:     6609438092 / 101559956668416
 7:     1873841888 / 101559956668416
 8:      813806426 / 101559956668416
 9:      569051084 / 101559956668416
10:      510821156 / 101559956668416
11:      496652384 / 101559956668416
12:      493092812 / 101559956668416
13:      492186008 / 101559956668416
14:      491947940 / 101559956668416
15:      491889008 / 101559956668416
16:      449710584 / 101559956668416
17:      418254922 / 101559956668416
18:      409373626 / 101559956668416

real    8m55.854s
user    67m58.336s
sys 0m5.607s

Kompilasi program di bawah ini dengan g++ --std=c++11 -O3 -mpopcnt dot.cc

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>

using namespace std;

typedef long long word;

word n;

void inner(word bpos, word bneg, word w, word *cnt) {
    word maxi = n-1;
    for(word a = (1<<n)-1; a >= 0; a--) {
        word m = a;
        for(word i = maxi; i >= 0; i--, m <<= 1) {
            if(__builtin_popcount(m&bpos) != __builtin_popcount(m&bneg))
                break;
            cnt[i]+=w;
        }
    }
}

word pow(word n, word e) {
    word r = 1;
    for(word i = 0; i < e; i++) r *= n;
    return r;
}

typedef struct {
    word b;
    word weight;
} Bentry;

mutex block;
Bentry *bqueue;
word bhead;
word btail;
word done = -1;

word maxb;

// compute -1*b
word bneg(word b) {
    word w = 1;
    for(word i = 0; i < n; i++, w *= 3) {
        word d = b / w % 3;
        if(d == 1)
            b += w;
        if(d == 2)
            b -= w;
    }
    return b;
}

// rotate b one position
word brot(word b) {
    b *= 3;
    b += b / maxb;
    b %= maxb;
    return b;
}

// reverse b
word brev(word b) {
    word r = 0;
    for(word i = 0; i < n; i++) {
        r *= 3;
        r += b % 3;
        b /= 3;
    }
    return r;
}

// individual thread's work routine
void work(word *cnt) {
    while(true) {
        // get a queue entry to work on
        block.lock();
        if(btail == done) {
            block.unlock();
            return;
        }
        if(bhead == btail) {
            block.unlock();
            this_thread::sleep_for(chrono::microseconds(10));
            continue;
        }
        word i = btail++;
        block.unlock();

        // thread now owns bqueue[i], work on it
        word b = bqueue[i].b;
        word w = 1;
        word bpos = 0;
        word bneg = 0;
        for(word j = 0; j < n; j++, b /= 3) {
            word d = b % 3;
            if(d == 1)
                bpos |= 1 << j;
            if(d == 2)
                bneg |= 1 << j;
        }
        bpos |= bpos << n;
        bneg |= bneg << n;
        inner(bpos, bneg, bqueue[i].weight, cnt);
    }
}

int main(int argc, char *argv[]) {
    n = atoi(argv[1]);

    // allocate work queue
    maxb = pow(3, n);
    bqueue = (Bentry*)(malloc(maxb*sizeof(Bentry)));

    // start worker threads
    word procs = thread::hardware_concurrency();
    vector<thread> threads;
    vector<word*> counts;
    for(word p = 0; p < procs; p++) {
        word *cnt = (word*)calloc(64+n*sizeof(word), 1);
        threads.push_back(thread(work, cnt));
        counts.push_back(cnt);
    }

    // figure out which Bs we actually want to test, and with which weights
    bool *bmark = (bool*)calloc(maxb, 1);
    for(word i = 0; i < maxb; i++) {
        if(bmark[i]) continue;
        word b = i;
        word w = 0;
        for(word j = 0; j < 2; j++) {
            for(word k = 0; k < 2; k++) {
                for(word l = 0; l < n; l++) {
                    if(!bmark[b]) {
                        bmark[b] = true;
                        w++;
                    }
                    b = brot(b);
                }
                b = bneg(b);
            }
            b = brev(b);
        }
        bqueue[bhead].b = i;
        bqueue[bhead].weight = w;
        block.lock();
        bhead++;
        block.unlock();
    }
    block.lock();
    done = bhead;
    block.unlock();

    // add up results from threads
    word *cnt = (word*)calloc(n,sizeof(word));
    for(word p = 0; p < procs; p++) {
        threads[p].join();
        for(int i = 0; i < n; i++) cnt[i] += counts[p][i];
    }
    for(word i = 0; i < n; i++)
        printf("%2lld: %14lld / %14lld\n", i+1, cnt[n-1-i], maxb<<n);
    return 0;
}
Keith Randall
sumber
Bagus, itu mengeluarkan saya untuk bekerja lebih jauh pada monster peliharaan saya sendiri ...
Terima kasih untuk ini. Anda memiliki entri pemenang saat ini. Kita harus mengingatnya -pthreadlagi. Saya sampai n=17di mesin saya.
Ups .. Anda harus mendapatkan hadiah penuh. Maaf saya melewatkan tenggat waktu.
@Lembik: tidak ada masalah.
Keith Randall
6

Python 2 menggunakan pypy dan pp: n = 15 dalam 3 menit

Juga hanya kekuatan kasar yang sederhana. Menarik untuk dilihat, bahwa saya hampir mendapatkan kecepatan yang sama dengan kuroi neko dengan C ++. Kode saya dapat mencapai n = 12sekitar 5 menit. Dan saya hanya menjalankannya pada satu inti virtual.

sunting: Mengurangi ruang pencarian dengan faktor n

Saya melihat, bahwa vektor bersepeda A*dari Amenghasilkan angka yang sama seperti probabilitas (nomor yang sama) sebagai vektor asli Aketika saya iterate lebih B. Vektor misalnya The (1, 1, 0, 1, 0, 0)memiliki probabilitas yang sama karena masing-masing dari vektor (1, 0, 1, 0, 0, 1), (0, 1, 0, 0, 1, 1), (1, 0, 0, 1, 1, 0), (0, 0, 1, 1, 0, 1)dan (0, 1, 1, 0, 1, 0)ketika memilih secara acak B. Karena itu saya tidak perlu mengulangi masing-masing dari 6 vektor ini, tetapi hanya sekitar 1 dan ganti count[i] += 1dengan count[i] += cycle_number.

Ini mengurangi kompleksitas dari Theta(n) = 6^nmenjadi Theta(n) = 6^n / n. Karenanya untuk n = 13ini sekitar 13 kali lebih cepat dari versi saya sebelumnya. Itu menghitung n = 13dalam sekitar 2 menit 20 detik. Untuk n = 14itu masih agak terlalu lambat. Dibutuhkan sekitar 13 menit.

sunting 2: Pemrograman multi-inti

Tidak terlalu senang dengan peningkatan selanjutnya. Saya memutuskan untuk juga mencoba menjalankan program saya pada banyak core. Pada 2 + 2 core saya, saya sekarang dapat menghitung n = 14dalam sekitar 7 menit. Hanya faktor 2 perbaikan.

Kode ini tersedia di repo github ini: Tautan . Pemrograman multi-core sedikit jelek.

sunting 3: Mengurangi ruang pencarian untuk Avektor dan Bvektor

Saya memperhatikan simetri cermin yang sama untuk vektor Aseperti yang kuroi neko lakukan. Masih tidak yakin, mengapa ini berhasil (dan jika berhasil untuk masing-masing n).

Pengurangan ruang pencarian untuk Bvektor sedikit lebih pintar. Saya mengganti generasi vektor ( itertools.product), dengan fungsi sendiri. Pada dasarnya, saya mulai dengan daftar kosong dan meletakkannya di tumpukan. Sampai tumpukan kosong, saya menghapus daftar, jika tidak memiliki panjang yang sama dengan n, saya menghasilkan 3 daftar lainnya (dengan menambahkan -1, 0, 1) dan mendorongnya ke tumpukan. Daftar saya memiliki panjang yang sama dengan n, saya bisa mengevaluasi jumlahnya.

Sekarang saya menghasilkan vektor sendiri, saya bisa memfilternya tergantung pada apakah saya dapat mencapai jumlah = 0 atau tidak. Misalnya jika vektor saya Aadalah (1, 1, 1, 0, 0), dan vektor saya Bterlihat (1, 1, ?, ?, ?), saya tahu, bahwa saya tidak dapat mengisi ?dengan nilai, jadi itu A*B = 0. Jadi saya tidak perlu mengulangi semua 6 vektor Bformulir itu (1, 1, ?, ?, ?).

Kita dapat memperbaiki ini, jika kita mengabaikan nilai-nilai untuk 1. Seperti disebutkan dalam pertanyaan, untuk nilai-nilai untuk i = 1adalah urutan A081671 . Ada banyak cara untuk menghitungnya. Saya memilih kekambuhan sederhana: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n. Karena i = 1pada dasarnya kami dapat menghitung dalam waktu singkat, kami dapat memfilter lebih banyak vektor B. Misalnya A = (0, 1, 0, 1, 1)dan B = (1, -1, ?, ?, ?). Kita dapat mengabaikan vektor, di mana yang pertama ? = 1, karena A * cycled(B) > 0, untuk semua vektor ini. Saya harap Anda bisa mengikuti. Itu mungkin bukan contoh terbaik.

Dengan ini saya bisa menghitung n = 15dalam 6 menit.

edit 4:

Cepat menerapkan ide hebat kuroi neko, yang mengatakan, itu Bdan -Bmenghasilkan hasil yang sama. Speedup x2. Implementasinya hanya hack cepat. n = 15dalam 3 menit.

Kode:

Untuk kode lengkap kunjungi Github . Kode berikut hanya merupakan representasi dari fitur-fitur utama. Saya meninggalkan impor, pemrograman multicore, mencetak hasilnya, ...

count = [0] * n
count[0] = oeis_A081671(n)

#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
    if A not in visited:
        # generate all vectors, which have the same probability
        # mirrored and cycled vectors
        same_probability_set = set()
        for i in range(n):
            tmp = [A[(i+j) % n] for j in range(n)]
            same_probability_set.add(tuple(tmp))
            same_probability_set.add(tuple(tmp[::-1]))
        visited.update(same_probability_set)
        todo[A] = len(same_probability_set)

# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
    ones = [sum(A[i:]) for i in range(n)] + [0]
    # + [0], so that later ones[n] doesn't throw a exception
    stack.append(([0] * n, 0, 0, 0, False))

    while stack:
        B, index, sum1, sum2, used_negative = stack.pop()
        if index < n:
            # fill vector B[index] in all possible ways,
            # so that it's still possible to reach 0.
            if used_negative:
                for v in (-1, 0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, True))
            else:
                for v in (0, 1):
                    sum1_new = sum1 + v * A[index]
                    sum2_new = sum2 + v * A[index - 1 if index else n - 1]
                    if abs(sum1_new) <= ones[index+1]:
                        if abs(sum2_new) <= ones[index] - A[n-1]:
                            C = B[:]
                            C[index] = v
                            stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
        else:
            # B is complete, calculate the sums
            count[1] += cycled_count  # we know that the sum = 0 for i = 1
            for i in range(2, n):
                sum_prod = 0
                for j in range(n-i):
                    sum_prod += A[j] * B[i+j]
                for j in range(i):
                    sum_prod += A[n-i+j] * B[j]
                if sum_prod:
                    break
                else:
                    if used_negative:
                        count[i] += 2*cycled_count
                    else:
                        count[i] += cycled_count

Pemakaian:

Anda harus menginstal pypy (untuk Python 2 !!!). Modul python paralel tidak di-porting untuk Python 3. Kemudian Anda harus menginstal modul python paralel pp-1.6.4.zip . Ekstrak itu, cdke dalam folder dan panggil pypy setup.py install.

Maka Anda dapat memanggil program saya dengan

pypy you-do-the-math.py 15

Secara otomatis akan menentukan jumlah cpu. Mungkin ada beberapa pesan kesalahan setelah menyelesaikan program, abaikan saja. n = 16harus dimungkinkan pada mesin Anda.

Keluaran:

Calculation for n = 15 took 2:50 minutes

 1  83940771168 / 470184984576  17.85%
 2  17379109692 / 470184984576   3.70%
 3   3805906050 / 470184984576   0.81%
 4    887959110 / 470184984576   0.19%
 5    223260870 / 470184984576   0.05%
 6     67664580 / 470184984576   0.01%
 7     30019950 / 470184984576   0.01%
 8     20720730 / 470184984576   0.00%
 9     18352740 / 470184984576   0.00%
10     17730480 / 470184984576   0.00%
11     17566920 / 470184984576   0.00%
12     17521470 / 470184984576   0.00%
13     17510280 / 470184984576   0.00%
14     17507100 / 470184984576   0.00%
15     17506680 / 470184984576   0.00%

Catatan dan ide:

  • Saya memiliki prosesor i7-4600m dengan 2 core dan 4 thread. Tidak masalah Jika saya menggunakan 2 atau 4 utas. Penggunaan cpu adalah 50% dengan 2 utas dan 100% dengan 4 utas, tetapi masih membutuhkan waktu yang sama. Saya tidak tahu kenapa. Saya memeriksa, bahwa setiap utas hanya memiliki setengah jumlah data, ketika ada 4 utas, memeriksa hasilnya, ...
  • Saya menggunakan banyak daftar. Python tidak cukup efisien dalam menyimpan, saya harus menyalin banyak daftar, ... Jadi saya malah berpikir untuk menggunakan integer. Saya dapat menggunakan bit 00 (untuk 0) dan 11 (untuk 1) dalam vektor A, dan bit 10 (untuk -1), 00 (untuk 0) dan 01 (untuk 1) dalam vektor B. Untuk produk A dan B, saya hanya perlu menghitung A & Bdan menghitung 01 dan 10 blok. Bersepeda dapat dilakukan dengan menggeser vektor dan menggunakan topeng, ... Saya benar-benar menerapkan semua ini, Anda dapat menemukannya di beberapa komitmen lama saya di Github. Tapi ternyata, lebih lambat dibandingkan dengan daftar. Saya kira, pypy benar-benar mengoptimalkan operasi daftar.
Jakube
sumber
Pada PC saya, n = 12 run memakan waktu 7:25 sedangkan sampah C ++ saya memakan waktu sekitar 1:23, yang membuatnya sekitar 5 kali lebih cepat. Dengan hanya dua core sejati, CPU saya akan mendapatkan sesuatu seperti faktor 2,5 dibandingkan dengan aplikasi mono-threaded, jadi CPU 8 core benar harus menjalankan sesuatu seperti 3 kali lebih cepat, dan itu tidak termasuk dengan peningkatan kecepatan mono-core dasar lebih dari i3-2100 penuaan saya. Apakah melalui semua simpanan C ++ ini untuk mengatasi waktu komputasi yang tumbuh secara eksponensial layak dilakukan masih bisa diperdebatkan.
Saya mendapatkan perasaan codegolf.stackexchange.com/questions/41021/ ... ... Apakah urutan de Bruijn bermanfaat?
kennytm
tentang multithreading, Anda bisa memeras lebih banyak 2 + 2 core Anda dengan mengunci setiap utas pada satu. Gain x2 adalah karena scheduler bergeser di sekitar utas Anda setiap kali batang korek api dipindahkan dalam sistem. Dengan penguncian inti, Anda mungkin akan mendapatkan keuntungan x2.5. Tidak tahu apakah Python memungkinkan untuk mengatur afinitas prosesor.
Terima kasih, saya akan memeriksanya. Tapi saya cukup banyak pemula di multithreading.
Jakube
nbviewer.ipython.org/gist/minrk/5500077 telah menyebutkan hal ini, meskipun menggunakan alat yang berbeda untuk paralelisme.
5

pengganggu wol - C ++ - terlalu lambat

Yah karena programmer yang lebih baik mengambil implementasi C ++, saya memanggil berhenti untuk yang satu ini.

#include <cstdlib>
#include <cmath>
#include <vector>
#include <bitset>
#include <future>
#include <iostream>
#include <iomanip>

using namespace std;

/*
6^^n events will be generated, so the absolute max
that can be counted by a b bits integer is
E(b*log(2)/log(6)), i.e. n=24 for a 64 bits counter

To enumerate 3 possible values of a size n vector we need
E(n*log(3)/log(2))+1 bits, i.e. 39 bits
*/
typedef unsigned long long Counter; // counts up to 6^^24

typedef unsigned long long Benumerator; // 39 bits
typedef unsigned long      Aenumerator; // 24 bits

#define log2_over_log6 0.3869

#define A_LENGTH ((size_t)(8*sizeof(Counter)*log2_over_log6))
#define B_LENGTH (2*A_LENGTH)

typedef bitset<B_LENGTH> vectorB;

typedef vector<Counter> OccurenceCounters;

// -----------------------------------------------------------------
// multithreading junk for CPUs detection and allocation
// -----------------------------------------------------------------
int number_of_CPUs(void)
{
    int res = thread::hardware_concurrency();
    return res == 0 ? 8 : res;
}

#ifdef __linux__
#include <sched.h>
void lock_on_CPU(int cpu)
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(cpu, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);
}
#elif defined (_WIN32)
#include <Windows.h>
#define lock_on_CPU(cpu) SetThreadAffinityMask(GetCurrentThread(), 1 << cpu)
#else
// #warning is not really standard, so this might still cause compiler errors on some platforms. Sorry about that.
#warning "Thread processor affinity settings not supported. Performances might be improved by providing a suitable alternative for your platform"
#define lock_on_CPU(cpu)
#endif

// -----------------------------------------------------------------
// B values generator
// -----------------------------------------------------------------
struct Bvalue {
    vectorB p1;
    vectorB m1;
};

struct Bgenerator {
    int n;                 // A length
    Aenumerator stop;      // computation limit
    Aenumerator zeroes;    // current zeroes pattern
    Aenumerator plusminus; // current +1/-1 pattern
    Aenumerator pm_limit;  // upper bound of +1/-1 pattern

    Bgenerator(int n, Aenumerator start=0, Aenumerator stop=0) : n(n), stop(stop)
    {
        // initialize generator so that first call to next() will generate first value
        zeroes    = start - 1;
        plusminus = -1;
        pm_limit  = 0;
    }

    // compute current B value
    Bvalue value(void)
    {
        Bvalue res;
        Aenumerator pm = plusminus;
        Aenumerator position = 1;
        int i_pm = 0;
        for (int i = 0; i != n; i++)
        {
            if (zeroes & position)
            {
                if (i_pm == 0)  res.p1 |= position; // first non-zero value fixed to +1
                else         
                {
                    if (pm & 1) res.m1 |= position; // next non-zero values
                    else        res.p1 |= position;
                    pm >>= 1;
                }
                i_pm++;
            }
            position <<= 1;
        }
        res.p1 |= (res.p1 << n); // concatenate 2 Bpre instances
        res.m1 |= (res.m1 << n);
        return res;
    }

    // next value
    bool next(void)
    {
        if (++plusminus == pm_limit)
        {
            if (++zeroes == stop) return false;
            plusminus = 0;
            pm_limit = (1 << vectorB(zeroes).count()) >> 1;
        }
        return true;
    }

    // calibration: produces ranges that will yield the approximate same number of B values
    vector<Aenumerator> calibrate(int segments)
    {
        // setup generator for the whole B range
        zeroes = 0;
        stop = 1 << n;
        plusminus = -1;
        pm_limit = 0;

        // divide range into (nearly) equal chunks
        Aenumerator chunk_size = ((Aenumerator)pow (3,n)-1) / 2 / segments;

        // generate bounds for zeroes values
        vector<Aenumerator> res(segments + 1);
        int bound = 0;
        res[bound] = 1;
        Aenumerator count = 0;
        while (next()) if (++count % chunk_size == 0) res[++bound] = zeroes;
        res[bound] = stop;
        return res;
    }
};

// -----------------------------------------------------------------
// equiprobable A values merging
// -----------------------------------------------------------------
static char A_weight[1 << A_LENGTH];
struct Agroup {
    vectorB value;
    int     count;
    Agroup(Aenumerator a = 0, int length = 0) : value(a), count(length) {}
};
static vector<Agroup> A_groups;

Aenumerator reverse(Aenumerator n) // this works on N-1 bits for a N bits word
{
    Aenumerator res = 0;
    if (n != 0) // must have at least one bit set for the rest to work
    {
        // construct left-padded reverse value
        for (int i = 0; i != 8 * sizeof(n)-1; i++)
        {
            res |= (n & 1);
            res <<= 1;
            n >>= 1;
        }

        // shift right to elimitate trailing zeroes
        while (!(res & 1)) res >>= 1;
    }
    return res;
}

void generate_A_groups(int n)
{
    static bitset<1 << A_LENGTH> lookup(0);
    Aenumerator limit_A = (Aenumerator)pow(2, n);
    Aenumerator overflow = 1 << n;
    for (char & w : A_weight) w = 0;

    // gather rotation cycles
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        Aenumerator rotated = a;
        int cycle_length = 0;
        for (int i = 0; i != n; i++)
        {
            // check for new cycles
            if (!lookup[rotated])
            {
                cycle_length++;
                lookup[rotated] = 1;
            }

            // rotate current value
            rotated <<= 1;
            if (rotated & overflow) rotated |= 1;
            rotated &= (overflow - 1);
        }

        // store new cycle
        if (cycle_length > 0) A_weight[a] = cycle_length;
    }

    // merge symetric groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        // skip already grouped values
        if (A_weight[a] == 0) continue;

        // regroup a symetric pair
        Aenumerator r = reverse(a);
        if (r != a)
        {
            A_weight[a] += A_weight[r];
            A_weight[r] = 0;
        }  
    }

    // generate groups
    for (Aenumerator a = 0; a != limit_A; a++)
    {
        if (A_weight[a] != 0) A_groups.push_back(Agroup(a, A_weight[a]));
    }
}

// -----------------------------------------------------------------
// worker thread
// -----------------------------------------------------------------
OccurenceCounters solve(int n, int index, Aenumerator Bstart, Aenumerator Bstop)
{
    OccurenceCounters consecutive_zero_Z(n, 0);  // counts occurences of the first i terms of Z being 0

    // lock on assigned CPU
    lock_on_CPU(index);

    // enumerate B vectors
    Bgenerator Bgen(n, Bstart, Bstop);
    while (Bgen.next())
    {
        // get next B value
        Bvalue B = Bgen.value();

        // enumerate A vector groups
        for (const auto & group : A_groups)
        {
            // count consecutive occurences of inner product equal to zero
            vectorB sliding_A(group.value);
            for (int i = 0; i != n; i++)
            {
                if ((sliding_A & B.p1).count() != (sliding_A & B.m1).count()) break;
                consecutive_zero_Z[i] += group.count;
                sliding_A <<= 1;
            }
        }
    }
    return consecutive_zero_Z;
}

// -----------------------------------------------------------------
// main
// -----------------------------------------------------------------
#define die(msg) { cout << msg << endl; exit (-1); }

int main(int argc, char * argv[])
{
    int n = argc == 2 ? atoi(argv[1]) : 16; // arbitray value for debugging
    if (n < 1 || n > 24) die("vectors of lenght between 1 and 24 is all I can (try to) compute, guv");

    auto begin = time(NULL);

    // one worker thread per CPU
    int num_workers = number_of_CPUs();

    // regroup equiprobable A values
    generate_A_groups(n);

    // compute B generation ranges for proper load balancing
    vector<Aenumerator> ranges = Bgenerator(n).calibrate(num_workers);

    // set workers to work
    vector<future<OccurenceCounters>> workers(num_workers);
    for (int i = 0; i != num_workers; i++)
    {
        workers[i] = async(
            launch::async, // without this parameter, C++ will decide whether execution shall be sequential or asynchronous (isn't C++ fun?).
            solve, n, i, ranges[i], ranges[i+1]); 
    }

    // collect results
    OccurenceCounters result(n + 1, 0);
    for (auto& worker : workers)
    {
        OccurenceCounters partial = worker.get();
        for (size_t i = 0; i != partial.size(); i++) result[i] += partial[i]*2; // each result counts for a symetric B pair
    }
    for (Counter & res : result) res += (Counter)1 << n; // add null B vector contribution
    result[n] = result[n - 1];                           // the last two probabilities are equal by construction

    auto duration = time(NULL) - begin;

    // output
    cout << "done in " << duration / 60 << ":" << setw(2) << setfill('0') << duration % 60 << setfill(' ')
        << " by " << num_workers << " worker thread" << ((num_workers > 1) ? "s" : "") << endl;
    Counter events = (Counter)pow(6, n);
    int width = (int)log10(events) + 2;
    cout.precision(5);
    for (int i = 0; i <= n; i++) cout << setw(2) << i << setw(width) << result[i] << " / " << events << " " << fixed << (float)result[i] / events << endl;

    return 0;
}

Membangun executable

Ini adalah sumber C ++ 11 mandiri yang mengkompilasi tanpa peringatan dan berjalan dengan lancar:

  • Win7 & MSVC2013
  • Win7 & MinGW - g ++ 4.7
  • Ubuntu & g ++ 4.8 (dalam VM VirtualBox dengan 2 CPU dialokasikan)

Jika Anda mengkompilasi dengan g ++, gunakan: g ++ -O3 -pthread -std = c ++ 11
melupakan -pthreadakan menghasilkan dump inti yang bagus dan ramah.

Optimalisasi

  1. Istilah Z terakhir sama dengan yang pertama (Bpre x A dalam kedua kasus), sehingga dua hasil terakhir selalu sama, yang mengeluarkan komputasi nilai Z terakhir.
    Keuntungannya bisa diabaikan, tetapi mengkodekannya tidak ada biaya sehingga Anda sebaiknya menggunakannya.

  2. Seperti yang Jakube temukan, semua nilai siklik dari vektor A yang diberikan menghasilkan probabilitas yang sama.
    Anda dapat menghitung ini dengan satu instance dari A dan mengalikan hasilnya dengan jumlah rotasi yang dimungkinkan. Grup rotasi dapat dengan mudah dihitung sebelumnya dalam waktu yang dapat diabaikan, jadi ini adalah perolehan kecepatan bersih yang besar.
    Karena jumlah permutasi dari vektor panjang n adalah n-1, kompleksitas turun dari o (6 n ) ke o (6 n / (n-1)), pada dasarnya melangkah lebih jauh untuk waktu komputasi yang sama.

  3. Tampaknya pasangan pola simetris juga menghasilkan probabilitas yang sama. Sebagai contoh, 100101 dan 101001.
    Saya tidak punya bukti matematis tentang itu, tetapi secara intuitif ketika disajikan dengan semua pola B yang mungkin, setiap nilai A simetris akan berbelit-belit dengan nilai B simetrik yang sesuai untuk hasil global yang sama.
    Ini memungkinkan untuk mengelompokkan lagi beberapa vektor A, untuk pengurangan sekitar 30% dari jumlah grup A.

  4. SALAH Untuk beberapa alasan semi-misterius, semua pola dengan hanya satu atau dua bit yang diatur menghasilkan hasil yang sama. Ini tidak mewakili banyak kelompok yang berbeda, tetapi masih dapat digabung tanpa biaya.

  5. Vektor B dan -B (B dengan semua komponen dikalikan -1) menghasilkan probabilitas yang sama.
    (misalnya [1, 0, -1, 1] dan [-1, 0, 1, -1]).
    Kecuali untuk vektor nol (semua komponen sama dengan 0), B dan -B membentuk sepasang vektor yang berbeda .
    Ini memungkinkan untuk memotong jumlah nilai B menjadi dua dengan mempertimbangkan hanya satu dari setiap pasangan dan mengalikan kontribusinya dengan 2, menambahkan kontribusi global yang diketahui dari null B untuk setiap probabilitas hanya sekali.

Bagaimana itu bekerja

Jumlah nilai B sangat besar (3 n ), jadi pra-komputasi mereka akan membutuhkan jumlah memori yang tidak senonoh, yang akan memperlambat perhitungan dan akhirnya menghabiskan RAM yang tersedia.
Sayangnya, saya tidak dapat menemukan cara sederhana untuk menghitung setengah set nilai B yang dioptimalkan, jadi saya menggunakan pengkodean generator khusus.

Generator B yang perkasa sangat menyenangkan untuk dikodekan, meskipun bahasa yang mendukung mekanisme hasil akan memungkinkan untuk memprogramnya dengan cara yang jauh lebih elegan.
Singkatnya adalah mempertimbangkan "kerangka" dari vektor Bpre sebagai vektor biner di mana 1s mewakili nilai -1 atau +1 aktual.
Di antara semua nilai potensial + 1 / -1 ini, yang pertama ditetapkan ke +1 (dengan demikian memilih salah satu dari vektor B / -B yang mungkin), dan semua kemungkinan kombinasi + 1 / -1 yang tersisa disebutkan.
Terakhir, sistem kalibrasi sederhana memastikan setiap utas pekerja akan memproses berbagai nilai dengan ukuran yang kira-kira sama.

Nilai-nilai A sangat difilter untuk pengelompokan ulang dalam potongan yang dapat disinkronkan.
Ini dilakukan dalam fase pra-komputasi yang memaksa memeriksa semua nilai yang mungkin.
Bagian ini memiliki waktu eksekusi O (2 n ) yang dapat diabaikan dan tidak perlu dioptimalkan (kode yang sudah cukup terbaca seperti itu!).

Untuk mengevaluasi produk dalam (yang hanya perlu diuji terhadap nol), komponen -1 dan 1 B dikelompokkan kembali menjadi vektor biner.
Produk dalam adalah nol jika (dan hanya jika) ada jumlah yang sama dengan +1 dan -1s di antara nilai B yang sesuai dengan nilai A yang tidak nol.
Ini dapat dihitung dengan masking sederhana dan operasi penghitungan bit, dibantu dengan std::bitsetitu akan menghasilkan kode hitung bit yang cukup efisien tanpa harus menggunakan instruksi intrinsik yang jelek.

Pekerjaan ini dibagi rata di antara core, dengan afinitas CPU yang dipaksakan (setiap sedikit membantu, atau begitulah kata mereka).

Contoh hasil

C:\Dev\PHP\_StackOverflow\C++\VectorCrunch>release\VectorCrunch.exe 16
done in 8:19 by 4 worker threads
 0  487610895942 / 2821109907456 0.17284
 1   97652126058 / 2821109907456 0.03461
 2   20659337010 / 2821109907456 0.00732
 3    4631534490 / 2821109907456 0.00164
 4    1099762394 / 2821109907456 0.00039
 5     302001914 / 2821109907456 0.00011
 6     115084858 / 2821109907456 0.00004
 7      70235786 / 2821109907456 0.00002
 8      59121706 / 2821109907456 0.00002
 9      56384426 / 2821109907456 0.00002
10      55686922 / 2821109907456 0.00002
11      55508202 / 2821109907456 0.00002
12      55461994 / 2821109907456 0.00002
13      55451146 / 2821109907456 0.00002
14      55449098 / 2821109907456 0.00002
15      55449002 / 2821109907456 0.00002
16      55449002 / 2821109907456 0.00002

Pertunjukan

Multithreading harus bekerja dengan sempurna pada ini, meskipun hanya core "benar" yang akan berkontribusi penuh pada kecepatan komputasi. CPU saya hanya memiliki 2 core untuk 4 CPU, dan keuntungan dari versi single-threaded adalah "hanya" sekitar 3,5.

Kompiler

Masalah awal dengan multithreading membuat saya percaya bahwa kompiler GNU berkinerja lebih buruk daripada Microsoft.

Setelah pengujian yang lebih menyeluruh, tampaknya g ++ menang sekali lagi, menghasilkan kode kira-kira 30% lebih cepat (rasio yang sama saya perhatikan pada dua proyek berat-komputasi lainnya).

Khususnya, std::bitsetperpustakaan diimplementasikan dengan instruksi jumlah bit khusus oleh g ++ 4.8, sedangkan MSVC 2013 hanya menggunakan loop dari pergeseran bit konvensional.

Seperti yang bisa diharapkan, kompilasi dalam 32 atau 64 bit tidak membuat perbedaan.

Penyempurnaan lebih lanjut

Saya perhatikan beberapa kelompok A yang menghasilkan probabilitas yang sama setelah semua operasi pengurangan, tetapi saya tidak bisa mengidentifikasi pola yang memungkinkan untuk mengelompokkan kembali mereka.

Berikut adalah pasangan yang saya perhatikan untuk n = 11:

  10001011 and 10001101
 100101011 and 100110101
 100101111 and 100111101
 100110111 and 100111011
 101001011 and 101001101
 101011011 and 101101011
 101100111 and 110100111
1010110111 and 1010111011
1011011111 and 1011111011
1011101111 and 1011110111

sumber
Saya pikir dua probabilitas terakhir harus selalu sama. Ini karena produk dalam n + 1 sebenarnya sama dengan yang pertama.
Yang saya maksudkan adalah bahwa produk dalam n pertama adalah nol jika dan hanya jika n pertama + 1 adalah. Produk dalam terakhir tidak memberikan informasi baru seperti yang telah Anda lakukan sebelumnya. Jadi jumlah string yang memberikan n nol produk sama persis dengan jumlah yang memberi n +1 produk.
Karena ketertarikan, tepatnya apa yang Anda hitung?
Terima kasih atas pembaruannya tetapi saya tidak mengerti baris "0 2160009216 2176782336". Apa tepatnya yang Anda hitung dalam kasus ini? Probabilitas bahwa produk dalam pertama adalah nol jauh lebih kecil dari itu.
Bisakah Anda memberikan beberapa saran tentang cara kompilasi dan menjalankan ini? Saya mencoba g ++ -Wall -std = c ++ 11 kuroineko.cpp -o kuroineko dan ./kuroineko 12 tetapi hasilnyaterminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)