Python 2 używa pypy i pp: n = 15 w 3 minuty
Również zwykła brutalna siła. Ciekawe, że mam prawie taką samą prędkość jak kuroi neko z C ++. Mój kod może dotrzeć n = 12
w ciągu około 5 minut. I uruchamiam go tylko na jednym wirtualnym rdzeniu.
edycja: Zmniejsz przestrzeń wyszukiwania o współczynnik n
Zauważyłem, że cyklicznie wektor A*
z A
produkuje te same numery jak prawdopodobieństw (te same numery) jako oryginalnego wektora A
kiedy iteracyjnego B
. Np Wektor (1, 1, 0, 1, 0, 0)
ma takie same prawdopodobieństwa jak każdy z wektorów (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
i (0, 1, 1, 0, 1, 0)
przy wyborze losowym B
. Dlatego nie muszę iterować każdego z tych 6 wektorów, ale tylko około 1 i zastąpić count[i] += 1
je count[i] += cycle_number
.
Zmniejsza to złożoność od Theta(n) = 6^n
do Theta(n) = 6^n / n
. Dlatego n = 13
jest to około 13 razy szybsze niż moja poprzednia wersja. Oblicza się n = 13
w ciągu około 2 minut i 20 sekund. Bo n = 14
wciąż jest trochę za wolno. Zajmuje to około 13 minut.
edycja 2: Programowanie wielordzeniowe
Niezbyt zadowolony z następnej poprawy. Postanowiłem także spróbować uruchomić mój program na wielu rdzeniach. Na moich rdzeniach 2 + 2 mogę teraz obliczyć n = 14
w około 7 minut. Tylko współczynnik poprawy 2.
Kod jest dostępny w tym repozytorium github: Link . Wielordzeniowe programowanie sprawia, że jest trochę brzydkie.
edycja 3: Zmniejszenie przestrzeni wyszukiwania A
wektorów i B
wektorów
Zauważyłem tę samą lustrzaną symetrię wektorów, A
co kuroi neko. Nadal nie jestem pewien, dlaczego to działa (i czy działa dla każdego n
).
Zmniejszenie przestrzeni wyszukiwania B
wektorów jest nieco sprytniejsze. Generację wektorów ( itertools.product
) zastąpiłem własną funkcją. Zasadniczo zaczynam od pustej listy i umieszczam ją na stosie. Dopóki stos nie będzie pusty, usuwam listę, jeśli nie ma takiej samej długości jak n
, generuję 3 inne listy (dodając -1, 0, 1) i wypychając je na stos. Jeśli lista ma taką samą długość n
, mogę ocenić sumy.
Teraz, gdy sam generuję wektory, mogę je filtrować w zależności od tego, czy mogę osiągnąć sumę = 0, czy nie. Np. Jeśli mój wektor A
jest (1, 1, 1, 0, 0)
i mój wektor B
wygląda (1, 1, ?, ?, ?)
, wiem, że nie mogę wypełnić ?
wartości, więc to A*B = 0
. Więc nie muszę powtarzać tych wszystkich 6 wektorów B
formularza (1, 1, ?, ?, ?)
.
Możemy to poprawić, jeśli zignorujemy wartości dla 1. Jak zauważono w pytaniu, wartościami i = 1
są sekwencja A081671 . Istnieje wiele sposobów ich obliczania. Wybieram proste nawrotom: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Ponieważ możemy obliczyć i = 1
w zasadzie w krótkim czasie, możemy filtrować więcej wektorów B
. Np A = (0, 1, 0, 1, 1)
a B = (1, -1, ?, ?, ?)
. Możemy zignorować wektory, gdzie pierwszy ? = 1
, ponieważ A * cycled(B) > 0
, dla wszystkich tych wektorów. Mam nadzieję, że możesz śledzić. To chyba nie najlepszy przykład.
Dzięki temu mogę obliczyć n = 15
w 6 minut.
edycja 4:
Szybko wprowadzono świetny pomysł kuroi neko, który mówi, że B
i -B
daje te same wyniki. Przyspieszenie x2. Wdrożenie to tylko szybki hack. n = 15
za 3 minuty.
Kod:
Aby uzyskać pełny kod, odwiedź Github . Poniższy kod stanowi jedynie przedstawienie głównych funkcji. Pominąłem importowanie, programowanie wielordzeniowe, drukowanie wyników, ...
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
Stosowanie:
Musisz zainstalować pypy (dla Python 2 !!!). Równoległy moduł python nie jest portowany dla Pythona 3. Następnie musisz zainstalować równoległy moduł python pp-1.6.4.zip . Wyodrębnij go cd
do folderu i zadzwoń pypy setup.py install
.
Następnie możesz wywołać mój program za pomocą
pypy you-do-the-math.py 15
Automatycznie określi liczbę procesorów. Po zakończeniu programu mogą pojawić się komunikaty o błędach, po prostu je zignoruj. n = 16
powinno być możliwe na twoim komputerze.
Wynik:
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%
Uwagi i pomysły:
- Mam procesor i7-4600m z 2 rdzeniami i 4 wątkami. Nie ma znaczenia, jeśli użyję 2 lub 4 wątków. Zużycie procesora wynosi 50% przy 2 wątkach i 100% przy 4 wątkach, ale nadal zajmuje to tyle samo czasu. Nie wiem dlaczego. Sprawdziłem, że każdy wątek ma tylko połowę ilości danych, gdy są 4 wątki, sprawdziłem wyniki ...
- Używam wielu list. Python nie jest dość wydajny w przechowywaniu, muszę skopiować wiele list, ... Więc pomyślałem o użyciu liczby całkowitej. Mógłbym użyć bitów 00 (dla 0) i 11 (dla 1) w wektorze A, a bitów 10 (dla -1), 00 (dla 0) i 01 (dla 1) w wektorze B. Dla produktu z A i B, musiałbym tylko obliczyć
A & B
i policzyć 01 i 10 bloków. Cyklowanie można wykonać, przesuwając wektor i używając masek ... Właściwie to wszystko zaimplementowałem, można to znaleźć w niektórych moich starszych wersjach na Githubie. Okazało się jednak, że jest wolniejszy niż w przypadku list. Chyba pypy naprawdę optymalizuje operacje na listach.