Python 2 usando pypy e pp: n = 15 em 3 minutos
Também apenas uma força bruta simples. Interessante ver que quase chego à mesma velocidade que o kuroi neko com C ++. Meu código pode alcançarn = 12
em cerca de 5 minutos. E eu apenas o executo em um núcleo virtual.
editar: reduz o espaço de pesquisa por um fator de n
I notado, que um vector ciclado A*
de A
produz os mesmos números como probabilidades (mesmos números), o vector inicial A
quando iterar B
. Por exemplo, o vector (1, 1, 0, 1, 0, 0)
tem as mesmas probabilidades de cada um dos vectores (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
e (0, 1, 1, 0, 1, 0)
quando se escolhe uma forma aleatória B
. Portanto, não tenho que iterar sobre cada um desses 6 vetores, mas apenas cerca de 1 e substituir count[i] += 1
por count[i] += cycle_number
.
Isso reduz a complexidade de Theta(n) = 6^n
para Theta(n) = 6^n / n
. Portanto n = 13
, é cerca de 13 vezes mais rápido que a minha versão anterior. Calcula n = 13
em cerca de 2 minutos e 20 segundos. Pois n = 14
ainda é um pouco lento demais. Demora cerca de 13 minutos.
editar 2: Programação multi-core
Não estou muito feliz com a próxima melhoria. Decidi também tentar executar meu programa em vários núcleos. Nos meus núcleos 2 + 2, agora posso calcular n = 14
em cerca de 7 minutos. Apenas um fator de 2 melhorias.
O código está disponível neste repositório do github: Link . A programação de múltiplos núcleos é um pouco feia.
edit 3: Reduzindo o espaço de pesquisa para A
vetores e B
vetores
Notei a mesma simetria de espelho para os vetores A
que kuroi neko. Ainda não tenho certeza, por que isso funciona (e se funciona para cada um n
).
A redução do espaço de pesquisa de B
vetores é um pouco mais inteligente. Substituí a geração dos vetores ( itertools.product
) por uma função própria. Basicamente, começo com uma lista vazia e a coloco em uma pilha. Até que a pilha esteja vazia, removo uma lista, se não tiver o mesmo comprimento que n
, gerei outras 3 listas (anexando -1, 0, 1) e empurrando-as para a pilha. Se uma lista tiver o mesmo comprimento n
, posso avaliar as somas.
Agora que eu mesmo gero os vetores, posso filtrá-los, dependendo se consigo atingir a soma = 0 ou não. Por exemplo, se meu vetor A
é (1, 1, 1, 0, 0)
, e meu vetor B
parece (1, 1, ?, ?, ?)
, eu sei, que não posso preencher os ?
valores com, para que A*B = 0
. Portanto, não tenho que repetir todos esses 6 vetores B
do formulário (1, 1, ?, ?, ?)
.
Podemos melhorar isso, se ignorarmos os valores de 1. Conforme observado na pergunta, os valores de i = 1
são a sequência A081671 . Existem muitas maneiras de calcular isso. Eu escolho a recorrência simples: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Como podemos calcular i = 1
basicamente em pouco tempo, podemos filtrar mais vetores para B
. Por exemplo, A = (0, 1, 0, 1, 1)
e B = (1, -1, ?, ?, ?)
. Podemos ignorar vetores, onde o primeiro ? = 1
, porque o A * cycled(B) > 0
, para todos esses vetores. Espero que você possa acompanhar. Provavelmente não é o melhor exemplo.
Com isso eu posso calcular n = 15
em 6 minutos.
editar 4:
Implementou rapidamente a grande ideia de kuroi neko, que diz isso B
e -B
produz os mesmos resultados. Aceleração x2. A implementação é apenas um hack rápido, no entanto. n = 15
em 3 minutos.
Código:
Para o código completo, visite Github . O código a seguir é apenas uma representação dos principais recursos. Deixei de fora as importações, a programação multicore, a impressão dos resultados, ...
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
Uso:
Você precisa instalar o pypy (para o Python 2 !!!). O módulo python paralelo não é portado para o Python 3. Então você deve instalar o módulo python paralelo pp-1.6.4.zip . Extraia-o cd
na pasta e ligue pypy setup.py install
.
Então você pode ligar para o meu programa com
pypy you-do-the-math.py 15
Ele determinará automaticamente o número de CPUs. Pode haver algumas mensagens de erro após o término do programa, apenas as ignore. n = 16
deve ser possível em sua máquina.
Resultado:
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%
Notas e idéias:
- Eu tenho um processador i7-4600m com 2 núcleos e 4 threads. Não importa se eu uso 2 ou 4 threads. O uso da CPU é de 50% com 2 threads e 100% com 4 threads, mas ainda leva a mesma quantidade de tempo. Não sei porque. Eu verifiquei, que cada thread tem apenas a metade dos dados, quando existem 4 threads, verifiquei os resultados, ...
- Eu uso muitas listas. Python não é muito eficiente no armazenamento, eu tenho que copiar muitas listas, ... Então pensei em usar um número inteiro. Eu poderia usar os bits 00 (para 0) e 11 (para 1) no vetor A e os bits 10 (para -1), 00 (para 0) e 01 (para 1) no vetor B. Para o produto de A e B, eu só precisaria calcular
A & B
e contar os blocos 01 e 10. O ciclismo pode ser feito com a mudança do vetor e o uso de máscaras ... Na verdade, implementei tudo isso, você pode encontrá-lo em alguns dos meus commits mais antigos no Github. Mas acabou sendo mais lento que nas listas. Eu acho que o pypy realmente otimiza as operações da lista.