Python 2 utilisant pypy et pp: n = 15 en 3 minutes
Aussi juste une simple force brute. Il est intéressant de voir que j'obtiens presque la même vitesse que kuroi neko avec C ++. Mon code peut atteindre n = 12
environ 5 minutes. Et je ne l'exécute que sur un seul cœur virtuel.
modifier: réduire l'espace de recherche d'un facteur de n
J'ai remarqué qu'un vecteur cyclé A*
de A
produit les mêmes nombres que les probabilités (mêmes nombres) que le vecteur d'origine A
lorsque je répète B
. Par exemple, le vecteur(1, 1, 0, 1, 0, 0)
a les mêmes probabilités que chacun des vecteurs (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
et au (0, 1, 1, 0, 1, 0)
moment de choisir un échantillon aléatoire B
. Par conséquent, je n'ai pas à répéter sur chacun de ces 6 vecteurs, mais seulement environ 1 et à remplacer count[i] += 1
par count[i] += cycle_number
.
Cela réduit la complexité de Theta(n) = 6^n
à Theta(n) = 6^n / n
. Par conséquent, n = 13
c'est environ 13 fois plus rapide que ma version précédente. Il calcule n = 13
en environ 2 minutes 20 secondes. Car n = 14
c'est encore un peu trop lent. Cela prend environ 13 minutes.
edit 2: Programmation multicœur
Pas vraiment satisfait de la prochaine amélioration. J'ai décidé d'essayer également d'exécuter mon programme sur plusieurs cœurs. Sur mes 2 + 2 cœurs, je peux maintenant calculer n = 14
en environ 7 minutes. Seul un facteur d'amélioration de 2.
Le code est disponible dans ce dépôt github: Lien . La programmation multicœur rend un peu moche.
edit 3: Réduction de l'espace de recherche pour les A
vecteurs et les B
vecteurs
J'ai remarqué la même symétrie miroir pour les vecteurs A
que kuroi neko. Je ne sais toujours pas pourquoi cela fonctionne (et si cela fonctionne pour chacun n
).
La réduction de l'espace de recherche de B
vecteurs est un peu plus intelligente. J'ai remplacé la génération des vecteurs ( itertools.product
), par une fonction propre. Fondamentalement, je commence avec une liste vide et la mets sur une pile. Jusqu'à ce que la pile soit vide, je supprime une liste, si elle n'a pas la même longueur quen
, je génère 3 autres listes (en ajoutant -1, 0, 1) et en les poussant sur la pile. Je une liste a la même longueur que n
, je peux évaluer les sommes.
Maintenant que je génère moi-même les vecteurs, je peux les filtrer selon que je peux atteindre la somme = 0 ou non. Par exemple, si mon vecteur A
est (1, 1, 1, 0, 0)
, et mon vecteur B
semble (1, 1, ?, ?, ?)
, je sais, que je ne peux pas remplir les ?
valeurs, de sorte que A*B = 0
. Je n'ai donc pas à parcourir tous les 6 vecteurs B
de la forme (1, 1, ?, ?, ?)
.
Nous pouvons améliorer cela si nous ignorons les valeurs de 1. Comme indiqué dans la question, car les valeurs de i = 1
sont la séquence A081671 . Il existe de nombreuses façons de les calculer. Je choisis la récurrence simple: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Comme nous pouvons calculer i = 1
en un rien de temps, nous pouvons filtrer plus de vecteurs pour B
. Par exemple A = (0, 1, 0, 1, 1)
et B = (1, -1, ?, ?, ?)
. Nous pouvons ignorer les vecteurs, où le premier ? = 1
, parce que A * cycled(B) > 0
, pour tous ces vecteurs. J'espère que vous pourrez suivre. Ce n'est probablement pas le meilleur exemple.
Avec cela, je peux calculer n = 15
en 6 minutes.
modifier 4:
La grande idée de kuroi neko mise en œuvre rapidement, qui dit cela B
et -B
produit les mêmes résultats. Accélération x2. La mise en œuvre n'est qu'un rapide hack, cependant. n = 15
en 3 minutes.
Code:
Pour le code complet, visitez Github . Le code suivant n'est qu'une représentation des principales fonctionnalités. J'ai laissé de côté les importations, la programmation multicœur, l'impression des résultats, ...
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
Usage:
Vous devez installer pypy (pour Python 2 !!!). Le module python parallèle n'est pas porté pour Python 3. Ensuite, vous devez installer le module python parallèle pp-1.6.4.zip . Extrayez-le cd
dans le dossier et appelez pypy setup.py install
.
Ensuite, vous pouvez appeler mon programme avec
pypy you-do-the-math.py 15
Il déterminera automatiquement le nombre de processeurs. Il peut y avoir des messages d'erreur après avoir terminé le programme, ignorez-les. n = 16
devrait être possible sur votre machine.
Production:
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%
Notes et idées:
- J'ai un processeur i7-4600m avec 2 cœurs et 4 threads. Peu importe si j'utilise 2 ou 4 fils. L'utilisation du processeur est de 50% avec 2 threads et 100% avec 4 threads, mais cela prend toujours le même temps. Je ne sais pas pourquoi. J'ai vérifié que chaque thread n'a que la moitié de la quantité de données, lorsqu'il y a 4 threads, j'ai vérifié les résultats, ...
- J'utilise beaucoup de listes. Python n'est pas assez efficace pour stocker, je dois copier beaucoup de listes, ... J'ai donc pensé à utiliser un entier à la place. Je pourrais utiliser les bits 00 (pour 0) et 11 (pour 1) dans le vecteur A, et les bits 10 (pour -1), 00 (pour 0) et 01 (pour 1) dans le vecteur B. Pour le produit de A et B, je n'aurais qu'à calculer
A & B
et compter les blocs 01 et 10. Le cyclisme peut être fait en déplaçant le vecteur et en utilisant des masques, ... J'ai en fait implémenté tout cela, vous pouvez le trouver dans certaines de mes anciennes validations sur Github. Mais il s'est avéré être plus lent qu'avec les listes. Je suppose que pypy optimise vraiment les opérations de liste.