कंप्यूटर: आप गणित करते हैं


13

यह चुनौती आंशिक रूप से एक एल्गोरिदम चुनौती है, जिसमें कुछ गणित शामिल है और आंशिक रूप से केवल सबसे तेज़ कोड चुनौती है।

कुछ सकारात्मक पूर्णांक के लिए n, लंबाई के 1एस और 0एस के समान रूप से यादृच्छिक स्ट्रिंग पर विचार nकरें और इसे कॉल करें A। अब लंबाई के दूसरे समान रूप से चुने गए यादृच्छिक स्ट्रिंग पर भी विचार करें , nजिनके मूल्य हैं -1, 0,या 1इसे कॉल करें B_pre। आइए अब Bहो B_pre+ B_pre। जो अपने आप में B_preसम्‍मिलित है।

अब की आंतरिक उत्पाद पर विचार Aऔर B[j,...,j+n-1]और इसे कहते Z_jऔर सूचकांक से 1

कार्य

आउटपुट n+1अंशों की एक सूची होनी चाहिए । iउत्पादन में वें अवधि होना चाहिए सटीक संभावना है कि सभी पहले की iशर्तों Z_jके साथ j <= iबराबर 0

स्कोर

सबसे बड़ा nजिसके लिए आपका कोड मेरी मशीन पर 10 मिनट के भीतर सही आउटपुट देता है।

टाई ब्रेकर

यदि दो उत्तरों में समान स्कोर है, तो पहले ने पहली जीत दर्ज की।

बहुत (बहुत) संभावना नहीं है कि किसी को असीमित स्कोर प्राप्त करने के लिए एक विधि मिलती है, इस तरह के समाधान का पहला वैध प्रमाण स्वीकार किया जाएगा।

संकेत

गणितीय रूप से इस समस्या को हल करने की कोशिश न करें, यह बहुत कठिन है। मेरे विचार में सबसे अच्छा तरीका उच्च विद्यालय से प्रायिकता की बुनियादी परिभाषाओं पर वापस जाना है और संभावनाओं की एक विस्तृत गणना करने के लिए कोड प्राप्त करने के लिए स्मार्ट तरीके खोजना है।

भाषाएं और पुस्तकालय

आप किसी भी भाषा का उपयोग कर सकते हैं जिसमें एक स्वतंत्र रूप से उपलब्ध संकलक / दुभाषिया / आदि है। लिनक्स और किसी भी पुस्तकालय के लिए जो लिनक्स के लिए भी स्वतंत्र रूप से उपलब्ध हैं।

मेरी मशीन टाइमिंग मेरी मशीन पर चलाई जाएगी। यह एक मानक ubuntu एक AMD FX-8350 आठ-कोर प्रोसेसर पर स्थापित है। इसका मतलब यह भी है कि मुझे आपका कोड चलाने में सक्षम होना चाहिए। परिणामस्वरूप, केवल आसानी से उपलब्ध मुफ्त सॉफ़्टवेयर का उपयोग करें और कृपया अपने कोड को संकलित करने और चलाने के लिए पूर्ण निर्देश शामिल करें।


कुछ परीक्षण आउटपुट। प्रत्येक के लिए सिर्फ पहला आउटपुट पर विचार करें n। वह तो कब का है i=1। के लिए n1 से 13 से उन्हें होना चाहिए।

 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

आप http://oeis.org/A081671i=1 पर भी एक सामान्य सूत्र पा सकते हैं ।

लीडरबोर्ड (भाषा द्वारा विभाजित)

  • n = 15. जेक्यूब द्वारा 1min49s में पायथन + समानांतर अजगर + पिपी
  • n = 17. कीथ रान्डेल द्वारा 3min37s में C ++
  • n = 16. C ++ 2min38s में kuroi neko द्वारा

1
@Knerd मैं कैसे नहीं कह सकता। मैं अपने कोड को लिनक्स में चलाने का तरीका जानने की कोशिश करूंगा लेकिन किसी भी मदद की बहुत सराहना की।

ठीक है, टिप्पणियों को हटाने के लिए खेद है। उन सभी के लिए जो पढ़ा नहीं था, यह था अगर F # या C # की अनुमति है :)
Knerd

दूसरा प्रश्न फिर से, क्या आपके पास वैध इनपुट आउटपुट का उदाहरण है?
Knerd

आपका ग्राफिक कार्ड क्या है? एक GPU के लिए एक नौकरी की तरह लग रहा है।
माइकल एम।

1
@Knerd मैंने इसके बजाय प्रश्नों की संभावनाओं की तालिका जोड़ी। मुझे आशा है कि यह उपयोगी है।

जवाबों:


5

8 थ्रेड्स पर 9 मिनट में C ++, n = 18

(मुझे बताएं कि क्या यह आपकी मशीन पर 10 मिनट से कम समय में बनाता है।)

मैं बी सरणी में समरूपता के कई रूपों का लाभ उठाता हूं। वे चक्रीय हैं (एक स्थिति से बदलाव), उलट (तत्वों के क्रम को उल्टा), और हस्ताक्षर (प्रत्येक तत्व के नकारात्मक को लें)। पहले मैं Bs की सूची की गणना करता हूं, जिसे हमें प्रयास करने और उनके वजन की आवश्यकता होती है। फिर प्रत्येक B को A के सभी 2 ^ n मानों के लिए एक तेज़ दिनचर्या (बिटकॉइन निर्देशों का उपयोग करके) के माध्यम से चलाया जाता है।

यहाँ 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

नीचे दिए गए कार्यक्रम को संकलित करें 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;
}

अच्छा है, जो मुझे अपने पालतू राक्षस पर आगे काम करने की अनुमति देता है ...

इसके लिए धन्यवाद। आपके पास वर्तमान विजेता प्रविष्टि है। हमें -pthreadफिर से याद करना होगा। मैं n=17अपनी मशीन पर मिलता हूं।

उफ़ .. आपको तो पूरा साथ मिलना चाहिए था। क्षमा करें, मैं समय सीमा से चूक गया।

@Lembik: कोई बात नहीं।
कीथ रान्डेल

6

अजगर और पीपी का उपयोग करके पायथन 2: 3 मिनट में n = 15

इसके अलावा सिर्फ एक सरल जानवर बल। यह देखने के लिए दिलचस्प है, कि मुझे लगभग वही गति मिलती है, जो कुरो नेको को सी ++ के साथ मिलती है। मेरा कोड n = 12लगभग 5 मिनट में पहुंच सकता है । और मैं इसे केवल एक वर्चुअल कोर पर चलाता हूं।

संपादित करें: के एक कारक द्वारा खोज स्थान को कम करें n

मैंने देखा, कि जब मैं पुनरावृति करता हूं, तो एक चक्रीय वेक्टर मूल वेक्टर के रूप में संभाव्यता (समान संख्या) के समान संख्याओं A*का Aउत्पादन करता है । उदाहरण के लिए वेक्टर ही संभावनाओं वैक्टर में से प्रत्येक के रूप में है , , , और जब एक यादृच्छिक चुनने । इसलिए मुझे इन 6 वैक्टरों में से प्रत्येक पर पुनरावृत्ति नहीं करनी है, लेकिन केवल 1 के बारे में है और इसके साथ बदलें ।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

इससे जटिलता कम हो Theta(n) = 6^nजाती है Theta(n) = 6^n / n। इसलिए n = 13यह मेरे पिछले संस्करण के रूप में उपवास के बारे में 13 गुना है। यह n = 13लगभग 2 मिनट 20 सेकंड में गणना करता है। के लिए n = 14यह अभी भी थोड़ा धीमा है। इसमें लगभग 13 मिनट लगते हैं।

संपादन 2: मल्टी-कोर-प्रोग्रामिंग

अगले सुधार से वास्तव में खुश नहीं हैं। मैंने कई कोर पर अपने कार्यक्रम को निष्पादित करने का प्रयास करने का भी फैसला किया। मेरे 2 + 2 कोर पर अब मैं n = 14लगभग 7 मिनट में गणना कर सकता हूं । केवल 2 सुधार का एक कारक।

इस github रेपो में लिंक उपलब्ध है: लिंक । मल्टी-कोर प्रोग्रामिंग बनाता है थोड़ा बदसूरत है।

संपादन 3: Aवैक्टर और Bवैक्टर के लिए खोज स्थान कम करना

मैंने वैक्टर के लिए उसी दर्पण-समरूपता को देखा Aजैसा कि कुरोइ नेको ने किया था। अभी भी यकीन नहीं है, यह क्यों काम करता है (और अगर यह प्रत्येक के लिए काम करता है n)।

Bवैक्टर के लिए खोज स्थान को कम करना थोड़ा चालाक है। मैंने itertools.productअपने स्वयं के फ़ंक्शन के साथ वैक्टर ( ) की पीढ़ी को बदल दिया । मूल रूप से, मैं एक खाली सूची से शुरू करता हूं और इसे स्टैक पर रखता हूं। जब तक स्टैक खाली नहीं होता है, मैं एक सूची को हटा देता हूं, अगर यह उसी के समान नहीं है n, तो मैं 3 अन्य सूचियां (-1, 0, 1 जोड़कर) उत्पन्न करता हूं और उन्हें स्टैक पर धकेलता हूं। मेरी सूची में लंबाई समान है n, मैं रकम का मूल्यांकन कर सकता हूं।

अब जब मैं स्वयं वैक्टर तैयार करता हूं, तो मैं उन्हें इस आधार पर फ़िल्टर कर सकता हूं कि मैं योग = 0 तक पहुंच सकता हूं या नहीं। जैसे अगर मेरा वेक्टर Aहै (1, 1, 1, 0, 0), और मेरा वेक्टर Bदिखता है (1, 1, ?, ?, ?), मुझे पता है, कि मैं ?मूल्यों के साथ नहीं भर सकता , ताकि A*B = 0। इसलिए मुझे Bफॉर्म के उन सभी 6 वैक्टरों पर ध्यान नहीं देना है (1, 1, ?, ?, ?)

हम इस पर सुधार कर सकते हैं, यदि हम मानों को नजरअंदाज करते हैं। 1. जैसा कि सवाल में कहा गया है, के लिए मान A081671i = 1 क्रम के हैं । उन लोगों की गणना करने के कई तरीके हैं। मैं सरल पुनरावृत्ति चुनता हूं :। चूंकि हम मूल रूप से समय में गणना कर सकते हैं , इसलिए हम अधिक वैक्टर को फ़िल्टर कर सकते हैं । जैसे और । हम इन सभी वैक्टरों के लिए वैक्टर, जहां पहले , को अनदेखा कर सकते हैं । मुझे आशा है कि आप अनुसरण कर सकते हैं। यह शायद सबसे अच्छा उदाहरण नहीं है।a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / ni = 1BA = (0, 1, 0, 1, 1)B = (1, -1, ?, ?, ?)? = 1A * cycled(B) > 0

इसके साथ मैं n = 156 मिनट में गणना कर सकता हूं ।

4 संपादित करें:

त्वरित रूप से कार्यान्वित की गई कुरोइ नेको के महान विचार, जो कहता है, Bऔर -Bवही परिणाम उत्पन्न करता है। स्पीडअप x2। कार्यान्वयन केवल एक त्वरित हैक है, हालांकि। n = 153 मिनट में।

कोड:

पूरा कोड देखने के लिए Github । निम्नलिखित कोड केवल मुख्य विशेषताओं का प्रतिनिधित्व है। मैंने आयातों को छोड़ दिया, मल्टीकोर प्रोग्रामिंग, परिणाम छपाई, ...

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

उपयोग:

आपको pypy (Python 2 !!! के लिए) इंस्टॉल करना होगा। समानांतर अजगर मॉड्यूल को पाइथन 3 के लिए पोर्ट नहीं किया गया है। फिर आपको समानांतर अजगर मॉड्यूल pp-1.6.4.zip को स्थापित करना होगा । इसे cdफोल्डर में निकालें और कॉल करें pypy setup.py install

तब आप मेरे कार्यक्रम को कॉल कर सकते हैं

pypy you-do-the-math.py 15

यह स्वचालित रूप से सीपीयू की संख्या निर्धारित करेगा। कार्यक्रम खत्म करने के बाद कुछ त्रुटि संदेश हो सकते हैं, बस उन्हें अनदेखा करें। n = 16आपकी मशीन पर संभव होना चाहिए।

आउटपुट:

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%

नोट्स और विचार:

  • मेरे पास 2 कोर और 4 थ्रेड्स के साथ एक i7-4600m प्रोसेसर है। इससे कोई फर्क नहीं पड़ता कि मैं 2 या 4 धागे का उपयोग करता हूं। सीपीयू-उपयोग 2 थ्रेड्स के साथ 50% और 4 थ्रेड्स के साथ 100% है, लेकिन इसमें अभी भी उतना ही समय लगता है। मुझे पता नहीं क्यों। मैंने जाँच की, कि प्रत्येक थ्रेड में केवल आधा डेटा होता है, जब 4 थ्रेड्स होते हैं, परिणाम की जाँच की, ...
  • मैं बहुत सारी सूचियों का उपयोग करता हूं। पायथन भंडारण में बहुत कुशल नहीं है, मुझे बहुत सारी सूचियों को कॉपी करना है, ... इसलिए मैंने इसके बजाय पूर्णांक का उपयोग करने के बारे में सोचा। मैं वेक्टर ए में बिट्स 00 (0 के लिए) और 11 (1 के लिए) और बिट्स 10 (-1 के लिए), 00 (0 के लिए) और 01 (1 के लिए) वेक्टर बी में उत्पाद के लिए उपयोग कर सकता हूं। ए और बी में, मुझे केवल A & B01 और 10 ब्लॉकों की गणना और गणना करनी होगी । साइकिल को वेक्टर को स्थानांतरित करने और मास्क का उपयोग करने के साथ किया जा सकता है, ... मैंने वास्तव में यह सब लागू किया है, आप इसे जीथब पर मेरे कुछ पुराने कमिट्स में पा सकते हैं। लेकिन यह सूचियों की तुलना में धीमा हो गया। मुझे लगता है, pypy वास्तव में सूची संचालन का अनुकूलन करता है।

मेरे पीसी पर n = 12 रन में 7:25 लगते हैं जबकि मेरे C ++ के टुकड़े में लगभग 1:23 लगते हैं, जो इसे लगभग 5 गुना तेज बनाता है। केवल दो सच्चे कोर के साथ, मेरा CPU मोनो-थ्रेडेड एप्लिकेशन की तुलना में 2.5 फैक्टर जैसा कुछ हासिल करेगा, इसलिए एक सच्चे 8 कोर सीपीयू को 3 बार तेजी से चलाना चाहिए, और यह मूल मोनो-कोर गति सुधार के साथ गिनती नहीं है मेरी उम्र बढ़ने i3-2100। हालांकि इन सभी C ++ हुप्स के माध्यम से जाने के लिए एक तेजी से बढ़ते संगणना समय से निपटने का प्रयास लायक है, हालांकि यह बहस का मुद्दा है।

मुझे codegolf.stackexchange.com/questions/41021/… की अनुभूति हो रही है ... क्या डे ब्रूजन अनुक्रम उपयोगी होगा?
kennytm

मल्टीथ्रेडिंग के बारे में, आप प्रत्येक थ्रेड को एक पर लॉक करके अपने 2 + 2 कोर के थोड़ा और अधिक निचोड़ सकते हैं। हर बार जब आपके माचिस की तीलियों को सिस्टम में ले जाया जाता है, तो आपके थ्रेड के आसपास शिफ्टिंग होने के कारण X2 का लाभ होता है। कोर लॉकिंग के साथ, आपको संभवतः x2.5 लाभ मिलेगा। कोई विचार नहीं है अगर अजगर प्रोसेसर आत्मीयता सेट करने की अनुमति देता है, हालांकि।

धन्यवाद, मैं इस पर गौर करूंगा। लेकिन मैं मल्टीथ्रेडिंग में एक बहुत नौसिखिया हूं।
जकूबे

nbviewer.ipython.org/gist/minrk/5500077 में इसका कुछ उल्लेख है, यद्यपि समानता के लिए एक अलग उपकरण का उपयोग किया जाता है।

5

ऊनी धमकाना - C ++ - बहुत धीमा

खैर जब से एक बेहतर प्रोग्रामर ने C ++ कार्यान्वयन पर लिया, मैं इसके लिए क्विट कह रहा हूं।

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

निष्पादन योग्य निर्माण

यह एक स्टैंडअलोन सी ++ 11 स्रोत है जो चेतावनी के बिना संकलित करता है और आसानी से चलता है:

  • Win7 और MSVC2013
  • Win7 & MinGW - g ++ 4.7
  • उबंटू और जी ++ 4.8 (वर्चुअलबॉक्स वीएम में 2 सीपीयू आवंटित)

यदि आप g ++ के साथ संकलन करते हैं, तो उपयोग करें: g ++ -O3 -pthread -std = c ++ 11 के
बारे में भूलकर -pthreadएक अच्छा और मैत्रीपूर्ण कोर डंप का उत्पादन होगा।

अनुकूलन

  1. अंतिम Z शब्द पहले (Bpre x A दोनों मामलों में) के बराबर है, इसलिए अंतिम दो परिणाम हमेशा बराबर होते हैं, जो अंतिम Z मान की गणना करते हैं।
    लाभ उपेक्षित है, लेकिन इसे कोड करने से कुछ भी खर्च नहीं होता है इसलिए आप इसका उपयोग कर सकते हैं।

  2. जैसा कि जैकुब ने खोजा था, एक ए वेक्टर के सभी चक्रीय मूल्य समान संभावनाएं पैदा करते हैं।
    आप ए के एक उदाहरण से इनकी गणना कर सकते हैं और इसके संभावित घुमावों की संख्या से परिणाम को गुणा कर सकते हैं। रोटेशन समूहों को आसानी से एक उपेक्षित मात्रा में पूर्व-गणना की जा सकती है, इसलिए यह एक शुद्ध शुद्ध गति है।
    चूंकि n लंबाई वेक्टर के क्रमपरिवर्तन की संख्या n-1 है, इसलिए जटिलता o (6 n ) से o (6 n / (n-1)) तक गिरती है , मूल रूप से समान गणना समय के लिए एक कदम आगे जा रही है।

  3. यह प्रतीत होता है कि सममित पैटर्न के जोड़े भी समान संभावनाएं उत्पन्न करते हैं। उदाहरण के लिए, 100101 और 101001।
    मेरे पास इसका कोई गणितीय प्रमाण नहीं है, लेकिन सहज रूप से जब सभी संभव बी पैटर्न के साथ प्रस्तुत किया जाता है, तो प्रत्येक सममित ए मूल्य को उसी वैश्विक परिणाम के लिए संबंधित सममित बी मान के साथ सजाया जाएगा।
    यह कुछ और A वैक्टरों को पुन: समूहित करने की अनुमति देता है, A समूहों की संख्या में लगभग 30% की कमी के लिए।

  4. गलत , कुछ अर्ध-रहस्यमय कारणों से, केवल एक या दो बिट सेट के साथ सभी पैटर्न एक ही परिणाम उत्पन्न करते हैं। यह कई अलग-अलग समूहों का प्रतिनिधित्व नहीं करता है, लेकिन फिर भी उन्हें लगभग किसी भी कीमत पर विलय नहीं किया जा सकता है।

  5. वैक्टर B और -B (B सभी घटकों को -1 से गुणा करता है) समान संभावनाएँ उत्पन्न करता है।
    (उदाहरण के लिए [1, 0, -1, 1] और [-1, 0, 1, -1])।
    अशक्त वेक्टर को छोड़कर (सभी घटक 0 के बराबर), B और -B अलग-अलग वैक्टर की एक जोड़ी बनाते हैं ।
    यह प्रत्येक जोड़े में से केवल एक पर विचार करके और अपने योगदान को 2 से गुणा करके बी मान की संख्या को आधे में कटौती करने की अनुमति देता है, नल बी के ज्ञात वैश्विक योगदान को प्रत्येक संभावना में केवल एक बार जोड़ते हैं।

यह काम किस प्रकार करता है

बी मानों की संख्या बहुत बड़ी है (3 एन ), इसलिए पूर्व-कंप्यूटिंग के लिए उन्हें अनिश्चित मात्रा में स्मृति की आवश्यकता होगी, जो गणना को धीमा कर देगा और अंततः उपलब्ध रैम को समाप्त कर देगा।
दुर्भाग्य से, मुझे अनुकूलित बी मानों के आधे सेट की गणना करने का एक सरल तरीका नहीं मिला, इसलिए मैंने एक समर्पित जनरेटर कोडिंग का सहारा लिया।

शक्तिशाली बी जनरेटर कोड के लिए बहुत मजेदार था, हालांकि उपज तंत्र का समर्थन करने वाली भाषाओं ने इसे बहुत अधिक सुरुचिपूर्ण तरीके से प्रोग्राम करने की अनुमति दी होगी।
संक्षेप में यह क्या करता है कि बायर वेक्टर के "कंकाल" को एक द्विआधारी वेक्टर के रूप में माना जाता है जहां 1s वास्तविक -1 या +1 मानों का प्रतिनिधित्व करते हैं।
इन सभी + 1 / -1 संभावित मूल्यों के बीच, पहले वाले को +1 के लिए तय किया गया है (इस प्रकार संभव बी / बी-वेक्टर का चयन करके), और शेष सभी संभावित 1 / -1 संयोजनों की गणना की जाती है।
अंत में, एक साधारण अंशांकन प्रणाली सुनिश्चित करती है कि प्रत्येक श्रमिक धागा लगभग समान आकार के मूल्यों की एक श्रृंखला को संसाधित करेगा।

एक मान को भारी रूप से कंफर्टेबल चंक्स में फिर से इकट्ठा करने के लिए फ़िल्टर किया जाता है।
यह एक पूर्व-कंप्यूटिंग चरण में किया जाता है जो जानवर-बल सभी संभावित मूल्यों की जांच करता है।
इस भाग में एक उपेक्षित O (2 n ) निष्पादन समय है और इसे अनुकूलित करने की आवश्यकता नहीं है (कोड पहले से ही अपठनीय होने के कारण पर्याप्त है!)।

आंतरिक उत्पाद का मूल्यांकन करने के लिए (जिसमें केवल शून्य के खिलाफ परीक्षण किया जाना चाहिए), बी के -1 और 1 घटकों को बाइनरी वैक्टर में फिर से वर्गीकृत किया गया है।
आंतरिक उत्पाद शून्य है (और केवल अगर) तो शून्य मानों के अनुरूप बी मानों के बीच + 1s और -1 s की समान संख्या है।
यह सरल मास्किंग और बिट काउंट संचालन के साथ गणना की जा सकती है, इसके द्वारा std::bitsetबदसूरत आंतरिक निर्देशों का सहारा लिए बिना यथोचित कुशल बिट काउंट कोड का उत्पादन किया जा सकता है ।

काम को कोर के बीच समान रूप से विभाजित किया गया है, मजबूर सीपीयू आत्मीयता के साथ (हर छोटी सी मदद करता है, या इसलिए वे कहते हैं)।

उदाहरण परिणाम

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

प्रदर्शन

मल्टीथ्रेडिंग को इस पर पूरी तरह से काम करना चाहिए, हालांकि केवल "सच" कोर पूरी तरह से गणना की गति में योगदान देगा। मेरे सीपीयू में 4 सीपीयू के लिए केवल 2 कोर हैं, और एकल-थ्रेडेड संस्करण पर लाभ "केवल" लगभग 3.5 है।

संकलनकर्ता

मल्टीथ्रेडिंग के साथ एक प्रारंभिक समस्या ने मुझे विश्वास दिलाया कि GNU संकलक Microsoft से भी बदतर प्रदर्शन कर रहे थे।

अधिक गहन परीक्षण के बाद, यह प्रतीत होता है कि जी ++ दिन फिर से जीतता है, लगभग 30% तेज कोड का उत्पादन करता है (उसी अनुपात में मैंने दो अन्य संगणना-भारी परियोजनाओं पर ध्यान दिया)।

विशेष रूप से, std::bitsetलाइब्रेरी को जी ++ 4.8 द्वारा समर्पित बिट काउंट निर्देशों के साथ लागू किया जाता है, जबकि MSVC 2013 पारंपरिक बिट शिफ्ट्स के केवल लूप का उपयोग करता है।

जैसा कि कोई उम्मीद कर सकता था, 32 या 64 बिट्स में संकलन से कोई फर्क नहीं पड़ता।

आगे शोधन

मैंने कुछ ए समूहों को कम करने के संचालन के बाद समान संभावनाओं का निर्माण करने पर ध्यान दिया, लेकिन मैं एक पैटर्न की पहचान नहीं कर सका जो उन्हें फिर से संगठित करने की अनुमति देगा।

यहाँ जोड़े हैं जिन्हें मैंने 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

मुझे लगता है कि अंतिम दो संभावनाएं हमेशा समान होनी चाहिए। ऐसा इसलिए है क्योंकि n + 1th आंतरिक उत्पाद वास्तव में पहले वाले के समान है।

मेरा मतलब यह था कि पहले n आंतरिक उत्पाद शून्य हैं यदि और केवल अगर पहले n + 1 हैं। बहुत अंतिम आंतरिक उत्पाद कोई नई जानकारी प्रदान नहीं करता है जैसा कि आपने पहले ही किया है। अतः n शून्य उत्पाद देने वाले तार की संख्या ठीक उसी प्रकार है जैसे कि n + 1 शून्य उत्पाद देते हैं।

रुचि से बाहर, क्या आप वास्तव में इसके बजाय कंप्यूटिंग कर रहे थे?

अपडेट के लिए धन्यवाद, लेकिन मैं "0 2160009216 2176782336" लाइन नहीं समझता। वास्तव में आप इस मामले में क्या गिना रहे हैं? पहला आंतरिक उत्पाद शून्य होने की संभावना उससे बहुत छोटी है।

क्या आप इसे संकलित करने और चलाने के बारे में कुछ सलाह दे सकते हैं? मैंने g ++ -Wall -std = c ++ 11 kuroineko.cpp -o kuroineko और /kuroineko 12 की कोशिश की, लेकिन यह देता हैterminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.