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 Aproduz os mesmos números como probabilidades (mesmos números), o vector inicial Aquando 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] += 1por count[i] += cycle_number.
Isso reduz a complexidade de Theta(n) = 6^npara Theta(n) = 6^n / n. Portanto n = 13, é cerca de 13 vezes mais rápido que a minha versão anterior. Calcula n = 13em cerca de 2 minutos e 20 segundos. Pois n = 14ainda é 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 = 14em 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 Avetores e Bvetores
Notei a mesma simetria de espelho para os vetores Aque 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 Bvetores é 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 Bparece (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 Bdo formulário (1, 1, ?, ?, ?).
Podemos melhorar isso, se ignorarmos os valores de 1. Conforme observado na pergunta, os valores de i = 1sã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 = 1basicamente 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 = 15em 6 minutos.
editar 4:
Implementou rapidamente a grande ideia de kuroi neko, que diz isso Be -Bproduz os mesmos resultados. Aceleração x2. A implementação é apenas um hack rápido, no entanto. n = 15em 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 cdna 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 = 16deve 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 & Be 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.