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 = 12w 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 Aprodukuje te same numery jak prawdopodobieństw (te same numery) jako oryginalnego wektora Akiedy 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] += 1je count[i] += cycle_number.
Zmniejsza to złożoność od Theta(n) = 6^ndo Theta(n) = 6^n / n. Dlatego n = 13jest to około 13 razy szybsze niż moja poprzednia wersja. Oblicza się n = 13w ciągu około 2 minut i 20 sekund. Bo n = 14wciąż 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 = 14w 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 Awektorów i Bwektorów
Zauważyłem tę samą lustrzaną symetrię wektorów, Aco kuroi neko. Nadal nie jestem pewien, dlaczego to działa (i czy działa dla każdego n).
Zmniejszenie przestrzeni wyszukiwania Bwektoró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 Ajest (1, 1, 1, 0, 0)i mój wektor Bwyglą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 Bformularza (1, 1, ?, ?, ?).
Możemy to poprawić, jeśli zignorujemy wartości dla 1. Jak zauważono w pytaniu, wartościami i = 1są 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 = 1w 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 = 15w 6 minut.
edycja 4:
Szybko wprowadzono świetny pomysł kuroi neko, który mówi, że Bi -Bdaje te same wyniki. Przyspieszenie x2. Wdrożenie to tylko szybki hack. n = 15za 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 cddo 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 = 16powinno 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 & Bi 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.