Bilgisayar: matematik yapıyorsun


13

Bu zorluk kısmen bir algoritma zorluğu, biraz matematik içerir ve kısmen de en hızlı kod zorluğudur.

Bazı pozitif tamsayı için n, bir homojen rastgele dize düşünün 1s ve 0uzunluğu s nve diyoruz A. Şimdi de uzunlukta bir ikinci homojen seçilen rasgele dize düşünün ndeğerlerini almış -1, 0,ya da 1ve diyoruz B_pre. Şimdi izin Bolmak B_pre+ B_pre. Bu B_prekendi kendine birleştirilir.

Şimdi iç ürünü incelemek Ave B[j,...,j+n-1]ve diyoruz Z_jgelen ve dizini 1.

Görev

Çıktı n+1kesirlerin bir listesi olmalıdır . iÇıkış th süreli olmaktadır tam olasılık olduğu tüm birinci ibakımından Z_jile j <= ieşit 0.

Puan

nKodunuzun makinede 10 dakikadan daha kısa sürede doğru çıkışı verdiği en büyük kod.

Kravat Kırıcı

İki cevap aynı puana sahipse, gönderilen cevap ilk kazanır.

Birinin sınırsız puan almak için bir yöntem bulması (çok çok) olası bir durumda, böyle bir çözümün ilk geçerli kanıtı kabul edilecektir.

İpucu

Bu sorunu matematiksel olarak çözmeye çalışmayın, çok zor. Bana göre en iyi yol, lisenin temel olasılık tanımlarına geri dönüp olasılıkların kapsamlı bir numaralandırması için kod almanın akıllı yollarını bulmaktır.

Diller ve kütüphaneler

Serbestçe kullanılabilen bir derleyici / tercüman / vb. Olan herhangi bir dili kullanabilirsiniz. Linux ve Linux için serbestçe bulunan tüm kütüphaneler için.

Benim makine zamanlamaları benim makinede işletilecek. Bu, AMD FX-8350 Sekiz Çekirdekli İşlemciye standart bir ubuntu yüklemesidir. Bu ayrıca kodunuzu çalıştırabilmem gerektiği anlamına gelir. Sonuç olarak, yalnızca kolayca bulunabilen ücretsiz yazılımları kullanın ve lütfen kodunuzu nasıl derleyeceğiniz ve çalıştıracağınızla ilgili tüm talimatları ekleyin.


Bazı test çıktıları. Her biri için sadece ilk çıktıyı düşünün n. İşte o zaman i=1. İçin nolması gerektiği 1 13 ila.

 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

Ayrıca için genel bir formül bulabilirsiniz i=1at http://oeis.org/A081671 .

Büyük şerit (dile göre bölünmüş)

  • n = 15. Jakube tarafından 1dk49s'da Python + paralel python + pypy
  • n = 17. Keith Randall tarafından 3 dakika 37 saniyede C ++
  • n = 16. kuroi neko tarafından 2dk38s içinde C ++

1
@Knerd Nasıl hayır diyebilirim. Ben linux kodunuzu çalıştırmak için nasıl çalışacağız ama çok takdir herhangi bir yardım.

Tamam, yorumları sildiğim için üzgünüm.
Okumadığınız

Diğer soru yine, geçerli bir giriş çıktısı örneğiniz olabilir mi?
Knerd

Grafik kartınız nedir? GPU için bir iş gibi görünüyor.
Michael M.

1
@Knerd Bunun yerine soruya bir olasılık tablosu ekledim. Umarım faydalıdır.

Yanıtlar:


5

C ++, n = 18, 8 iş parçacığında 9 dakikada

(Makinenizde 10 dakikadan daha kısa bir süre içinde gerçekleşip gerçekleşmediğini bana bildirin.)

B dizisinde birkaç simetri formundan faydalanıyorum. Bunlar döngüsel (bir konuma kaydır), tersine çevirme (öğelerin sırasını tersine çevir) ve işarettir (her öğenin negatifini alır). Öncelikle denememiz gereken Bs listesini ve ağırlıklarını hesaplıyorum. Daha sonra her B, A'nın tüm 2 ^ n değerleri için hızlı bir rutin (bitcount talimatları kullanarak) ile çalıştırılır.

İşte n == 18 sonucu:

> 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

Aşağıdaki programı şununla derleyin: 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;
}

İyi, bu benim kendi hayvan canavar üzerinde daha fazla çalışma beni dağıtır ...

Bunun için teşekkürler. Mevcut kazanan girişe sahipsiniz. -pthreadTekrar hatırlamamız gerek . Ben olsun n=17benim makinede.

Hata! Ödülün tamamını almış olmalısın. Üzgünüm son tarihi kaçırdım.

@Lembik: sorun değil.
Keith Randall

6

Pypy ve pp: n = 15 kullanarak 3 dakikada python 2

Ayrıca basit bir kaba kuvvet. İlginçti, neredeyse C ++ ile kuroi neko ile aynı hızı elde ediyorum. Kodum n = 12yaklaşık 5 dakika içinde ulaşabilir. Ve sadece bir sanal çekirdek üzerinde çalıştırıyorum.

edit: Arama alanını bir faktör kadar azaltır n

Döngüsüz bir vektör vektörünün A*, tekrarladığımda Aorijinal vektörle aynı sayıları (aynı sayılar) ürettiğini fark ettim . EG vektör vektörlerinin her biriyle aynı olasılıkları vardır , , , ve rasgele seçme . Bu nedenle bu 6 vektörlerin her yineleme zorunda, ama sadece yaklaşık 1 ve değiştirmeyin ile .AB(1, 1, 0, 1, 0, 0)(1, 0, 1, 0, 0, 1)(0, 1, 0, 0, 1, 1)(1, 0, 0, 1, 1, 0)(0, 0, 1, 1, 0, 1)(0, 1, 1, 0, 1, 0)Bcount[i] += 1count[i] += cycle_number

Bu karmaşıklığı azaltır Theta(n) = 6^niçin Theta(n) = 6^n / n. Bu nedenle n = 13önceki sürümümden yaklaşık 13 kat daha hızlı. n = 13Yaklaşık 2 dakika 20 saniye içinde hesaplar . Çünkü n = 14hala biraz fazla yavaş. Yaklaşık 13 dakika sürer.

değiştir 2: Çok çekirdekli programlama

Bir sonraki gelişmeden gerçekten memnun değil. Programımı birden çok çekirdek üzerinde yürütmeye de karar verdim. 2 + 2 çekirdeklerimde şimdi n = 14yaklaşık 7 dakika içinde hesaplayabilirim . Sadece 2 gelişme faktörü.

Kod bu github deposunda kullanılabilir: Link . Çok çekirdekli programlama biraz çirkin.

edit 3: AVektörler ve Bvektörler için arama alanını azaltma

AKuroi neko'nun yaptığı gibi vektörler için aynı ayna simetrisini fark ettim . Hala neden işe yaradığından emin değilim (ve her biri için işe yarayıp yaramadığını n).

BVektörler için arama alanının azaltılması biraz daha zekidir. Vektörlerin ( itertools.product) neslini kendi fonksiyonuyla değiştirdim. Temel olarak, boş bir listeyle başlıyorum ve bir yığına koyuyorum. Yığın boş olana kadar, bir listeyi kaldırırım, eğer uzunluğu ile aynı değilse, nbaşka 3 liste (-1, 0, 1 ekleyerek) oluşturur ve bunları yığının üzerine iterim. Bir liste ile aynı uzunlukta nolan toplamları değerlendirebilirim.

Şimdi vektörleri kendim ürettiğime göre, toplam = 0'a ulaşıp ulaşamayacağımıza bağlı olarak bunları filtreleyebilirim. Benim vektör eğer Ör Aolduğunu (1, 1, 1, 0, 0)ve benim vektör Bgörünüyor (1, 1, ?, ?, ?), biliyorum, ben doldurmak olamaz ?böylece, değerlerle A*B = 0. Bu yüzden B, formun bu 6 vektörünü yinelemek zorunda değilim (1, 1, ?, ?, ?).

Biz değerleri için, söz konusu belirtildiği gibi 1. değerlerini göz ardı edersek biz bu konuda artırabilir i = 1dizisi olan A081671 . Bunları hesaplamanın birçok yolu vardır. Basit tekrarını seç: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n. i = 1Temelde hiçbir zaman hesaplayamadığımız için daha fazla vektörü filtreleyebiliriz B. Örneğin A = (0, 1, 0, 1, 1)ve B = (1, -1, ?, ?, ?). İlkleri vektörleri görmezden gelebiliriz ? = 1, çünkü A * cycled(B) > 0tüm bu vektörler için. Umarım takip edebilirsin. Muhtemelen en iyi örnek değil.

Bununla n = 156 dakika içinde hesaplayabilirim .

düzenleme 4:

Çabuk, söylüyor kuroi neko harika bir fikir, hayata Bve -Baynı sonuçlar üretir. Hızlanma x2. Ancak uygulama sadece hızlı bir saldırıdır. n = 153 dakika içinde.

Kod:

Kodun tamamı için Github'u ziyaret edin . Aşağıdaki kod sadece ana özelliklerin bir gösterimidir. İthalatları, çok çekirdekli programlamayı, sonuçları yazdırmayı, ...

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

Kullanımı:

Pypy yüklemeniz gerekiyor (Python 2 için !!!). Paralel python modülü Python 3 için taşınmaz . Sonra paralel python modülünü pp-1.6.4.zip kurmanız gerekir . cdKlasöre ayıklayın ve arayın pypy setup.py install.

Sonra programımı

pypy you-do-the-math.py 15

Otomatik olarak cpu sayısını belirler. Programı bitirdikten sonra bazı hata mesajları olabilir, sadece dikkate almayın. n = 16makinenizde mümkün olmalıdır.

Çıktı:

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%

Notlar ve fikirler:

  • 2 çekirdekli ve 4 yivli i7-4600m işlemcim var. 2 veya 4 iş parçacığı kullanırsam önemli değil. İşlemci kullanımı 2 iş parçacığı ile% 50 ve 4 iş parçacığı ile% 100'dür, ancak yine de aynı miktarda zaman alır. Neden bilmiyorum. 4 iş parçacığı olduğunda, her iş parçacığı sadece veri yarıya kadar olduğunu kontrol ettim, sonuçları kontrol, ...
  • Çok fazla liste kullanıyorum. Python depolamakta oldukça verimli değil, birçok listeyi kopyalamak zorundayım, ... Bunun yerine bir tamsayı kullanmayı düşündüm. A vektöründe 00 (0 için) ve 11 (1 için) bitlerini ve B vektöründe 10 (-1 için), 00 (0 için) ve 01 (1 için) bitlerini kullanabilirim. Ürün için A ve B, sadece A & B01 ve 10 blokları hesaplamak ve saymak zorunda kalacaktım . Bisiklete binme vektörü değiştirerek ve maskeleri kullanarak yapılabilir, ... Aslında tüm bunları uyguladım, Github'daki bazı eski taahhütlerimde bulabilirsiniz. Ancak, listelerden daha yavaş olduğu ortaya çıktı. Sanırım pypy liste işlemlerini gerçekten optimize ediyor.

Bilgisayarımda n = 12 koşusu 7:25, C ++ ıvır zıvır parçam yaklaşık 1:23 alır, bu da yaklaşık 5 kat daha hızlı yapar. Sadece iki gerçek çekirdek ile CPU'm, tek iş parçacıklı bir uygulamaya kıyasla 2,5 faktör gibi bir şey kazanacak, bu yüzden gerçek 8 çekirdekli bir CPU 3 kat daha hızlı bir şey çalıştırmalı ve bu temel tek çekirdekli hız iyileştirmesi yaşlanmam i3-2100. Üstelik büyüyen bir hesaplama süresi ile başa çıkmak için tüm bu C ++ çemberlerinden geçip geçmemek çabaya değer olup olmadığı tartışmalıdır.

Codegolf.stackexchange.com/questions/41021/… hissi alıyorum ... De Bruijn dizisi yararlı olur mu?
kennytm

multithreading hakkında, her bir ipliği bir tane üzerinde kilitleyerek 2 + 2 çekirdeğinizden biraz daha fazlasını sıkıştırabilirsiniz. X2 kazancı, sistemde bir kibrit çöpü her hareket ettirildiğinde zamanlayıcının iş parçacıklarınızın etrafında kaymasından kaynaklanır. Çekirdek kilitleme ile muhtemelen x2,5 kazanç elde edersiniz. Python'un işlemci benzeşimi ayarlamasına izin verip vermediği konusunda hiçbir fikrim yok.

Teşekkürler, içine bakacağım. Ama ben çok iş parçacıklığındaki bir acemiyim.
Jakube

nbviewer.ipython.org/gist/minrk/5500077 paralellik için farklı bir araç kullansa da bundan biraz bahseder.

5

yünlü kabadayı - C ++ - çok yavaş

Peki daha iyi bir programcı C ++ uygulaması aldı, ben bunun için çıkmak çağırıyorum.

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

Yürütülebilir dosyayı oluşturma

Uyarılar olmadan derleyen ve sorunsuz bir şekilde çalışan bağımsız bir C ++ 11 kaynağıdır:

  • Win7 ve MSVC2013
  • Win7 ve MinGW - g ++ 4.7
  • Ubuntu & g ++ 4.8 (2 CPU tahsis edilmiş bir VirtualBox VM'de)

G ++ ile derlerseniz , şunu kullanın: g ++ -O3 -pthread -std = c ++ 11
unutmak -pthreadhoş ve samimi bir çekirdek dökümü üretecektir.

optimizasyonları

  1. Son Z terimi birinciye eşittir (her iki durumda da Bpre x A), bu nedenle son iki sonuç her zaman eşittir, bu da son Z değerini hesaplamaktan vazgeçer.
    Kazanç ihmal edilebilir, ancak kodlamanın hiçbir maliyeti yoktur, bu yüzden de kullanabilirsiniz.

  2. Jakube'un keşfettiği gibi, belirli bir A vektörünün tüm döngüsel değerleri aynı olasılıkları üretir.
    Bunları tek bir A örneği ile hesaplayabilir ve sonucu olası dönüş sayısıyla çarpabilirsiniz. Dönme grupları kolayca ihmal edilebilir bir sürede önceden hesaplanabilir, bu nedenle bu büyük bir net hız kazancıdır.
    Bir n uzunluk vektörünün permütasyon sayısı n-1 olduğundan, karmaşıklık o (6 n ) 'den o (6 n / (n-1)) ' e düşer , temel olarak aynı hesaplama süresi için bir adım ileri gider.

  3. Simetrik desen çiftleri de aynı olasılıkları ortaya çıkarmaktadır. Örneğin, 100101 ve 101001.
    Bunun matematiksel bir kanıtı yok, ancak sezgisel olarak tüm olası B desenleriyle sunulduğunda, her simetrik A değeri, aynı küresel sonuç için karşılık gelen simetrik B değeri ile kıvrılır.
    Bu, A grubu sayısının yaklaşık% 30 azalması için biraz daha A vektörün yeniden gruplandırılmasına izin verir.

  4. YANLIŞ Yarı gizemli bir nedenden ötürü, sadece bir veya iki bitli tüm desenler aynı sonucu verir. Bu, pek çok farklı grubu temsil etmez, ancak yine de neredeyse ücretsiz olarak birleştirilebilir.

  5. B ve -B vektörleri (tüm bileşenlerin -1 ile çarpımı ile B) aynı olasılıkları üretir.
    (örneğin [1, 0, -1, 1] ve [-1, 0, 1, -1]).
    Boş vektör hariç (tüm bileşenler 0'a eşittir), B ve -B bir çift ayrı vektör oluşturur.
    Bu, her bir çiftten sadece birini göz önünde bulundurarak ve katkısını 2 ile çarparak B değerlerinin sayısını yarıya indirmeye izin verir ve her bir olasılığa bilinen bir null B katkısını sadece bir kez ekler.

Nasıl çalışır

B değerlerinin sayısı çok büyüktür (3 n ), bu yüzden bunları önceden hesaplamak, hesaplamayı yavaşlatacak ve sonunda kullanılabilir RAM'i tüketecek uygunsuz miktarda bellek gerektirir.
Ne yazık ki, optimize edilmiş B değerlerinin yarım setini numaralandırmanın basit bir yolunu bulamadım, bu yüzden özel bir jeneratörü kodlamaya başvurdum.

Güçlü B jeneratörünün kodlaması çok eğlenceliydi, ancak verim mekanizmalarını destekleyen diller onu çok daha zarif bir şekilde programlamaya izin verecekti.
Özetle yaptığı şey, bir Bpre vektörünün "iskeletini" 1'lerin gerçek -1 veya +1 değerlerini temsil ettiği bir ikili vektör olarak düşünmektir.
Tüm bu + 1 / -1 potansiyel değerleri arasında ilki +1 olarak sabitlenir (böylece olası B / -B vektöründen birini seçerek) ve kalan tüm olası + 1 / -1 kombinasyonları numaralandırılır.
Son olarak, basit bir kalibrasyon sistemi her bir iş parçacığının yaklaşık aynı boyutta bir değer aralığını işlemesini sağlar.

Bir değerler, eşitlenebilir parçalar halinde yeniden gruplandırma için büyük ölçüde filtrelenir.
Bu, kaba kuvvetin tüm olası değerleri incelediği bir hesaplama öncesi aşamada yapılır.
Bu bölüm ihmal edilebilir bir O (2 n ) yürütme süresine sahiptir ve optimize edilmesi gerekmez (kod zaten olduğu gibi okunamıyor!).

İç ürünü değerlendirmek için (sadece sıfıra karşı test edilmesi gerekir), B'nin -1 ve 1 bileşenleri ikili vektörler halinde yeniden gruplandırılır.
Sıfır olmayan A değerlerine karşılık gelen B değerleri arasında eşit sayıda + 1s ve -1s varsa iç ürün null olur.
Bu, basit maskeleme ve bit sayma işlemleri ile hesaplanabilir, bunun yardımıyla std::bitsetçirkin iç talimatlara başvurmak zorunda kalmadan makul derecede verimli bit sayımı kodu üretecektir.

İş, zorla CPU benzeşimi ile çekirdekler arasında eşit olarak bölünür (her biraz yardımcı olur, ya da öyle diyorlar).

Örnek sonuç

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

performanslar

Multithreading bu konuda mükemmel bir şekilde çalışmalıdır, ancak sadece "gerçek" çekirdekler hesaplama hızına tam olarak katkıda bulunacaktır. CPU'mun 4 CPU için sadece 2 çekirdeği var ve tek iş parçacıklı bir sürümden elde edilen kazanç yaklaşık 3.5.

Derleyiciler

Çok iş parçacıklığındaki ilk sorun, GNU derleyicilerinin Microsoft'tan daha kötü performans gösterdiğine inanmamı sağladı.

Daha kapsamlı bir testten sonra, g ++ bir gün daha kazanıyor ve yaklaşık% 30 daha hızlı kod üretiyor (diğer iki hesaplama ağır projede fark ettiğim oran).

Özellikle, std::bitsetkütüphane g ++ 4.8 tarafından ayrılmış bit sayımı talimatları ile uygulanırken, MSVC 2013 yalnızca geleneksel bit kaydırmalarının döngülerini kullanır.

Beklendiği gibi, 32 veya 64 bit olarak derlemek fark etmez.

Diğer ayrıntılandırmalar

Tüm azaltma işlemlerinden sonra aynı olasılıkları üreten birkaç A grubunu fark ettim, ancak onları yeniden gruplandırmaya izin verecek bir model tanımlayamadım.

İşte n = 11 için fark ettiğim çiftler:

  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

Bence son iki olasılık hep aynı olmalı. Çünkü n + 1 iç ürün aslında birincisi ile aynıdır.

Demek istediğim, ilk n iç ürünün sıfır olması ve yalnızca ilk n + 1 olması durumunda sıfır olmasıydı. En son iç ürün, daha önce yaptığınız gibi yeni bir bilgi sağlamaz. Dolayısıyla, n sıfır ürün veren dizelerin sayısı, n + 1 sıfır ürün veren dizeyle tam olarak aynıdır.

İlgi alanı dışında tam olarak ne hesaplıyordunuz?

Güncelleme için teşekkürler ama "0 2160009216 2176782336" satırını anlamıyorum. Bu durumda tam olarak neyi sayıyorsunuz? İlk iç ürünün sıfır olma olasılığı bundan daha küçüktür.

Bunu nasıl derleyeceğiniz ve çalıştıracağınız konusunda tavsiye verebilir misiniz? G ++ -Wall -std = c ++ 11 kuroineko.cpp -o kuroineko ve ./kuroineko 12'yi denedim ama verirterminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.