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 = 12
sekitar 5 menit. Dan saya hanya menjalankannya pada satu inti virtual.
sunting: Mengurangi ruang pencarian dengan faktor n
Saya melihat, bahwa vektor bersepeda A*
dari A
menghasilkan angka yang sama seperti probabilitas (nomor yang sama) sebagai vektor asli A
ketika 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] += 1
dengan count[i] += cycle_number
.
Ini mengurangi kompleksitas dari Theta(n) = 6^n
menjadi Theta(n) = 6^n / n
. Karenanya untuk n = 13
ini sekitar 13 kali lebih cepat dari versi saya sebelumnya. Itu menghitung n = 13
dalam sekitar 2 menit 20 detik. Untuk n = 14
itu 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 = 14
dalam 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 A
vektor dan B
vektor
Saya memperhatikan simetri cermin yang sama untuk vektor A
seperti yang kuroi neko lakukan. Masih tidak yakin, mengapa ini berhasil (dan jika berhasil untuk masing-masing n
).
Pengurangan ruang pencarian untuk B
vektor 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 A
adalah (1, 1, 1, 0, 0)
, dan vektor saya B
terlihat (1, 1, ?, ?, ?)
, saya tahu, bahwa saya tidak dapat mengisi ?
dengan nilai, jadi itu A*B = 0
. Jadi saya tidak perlu mengulangi semua 6 vektor B
formulir itu (1, 1, ?, ?, ?)
.
Kita dapat memperbaiki ini, jika kita mengabaikan nilai-nilai untuk 1. Seperti disebutkan dalam pertanyaan, untuk nilai-nilai untuk i = 1
adalah 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 = 1
pada 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 = 15
dalam 6 menit.
edit 4:
Cepat menerapkan ide hebat kuroi neko, yang mengatakan, itu B
dan -B
menghasilkan hasil yang sama. Speedup x2. Implementasinya hanya hack cepat. n = 15
dalam 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, cd
ke 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 = 16
harus 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 & B
dan 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.