pypyとppを使用したPython 2:3分でn = 15
また、単純な総当たり攻撃です。おもしろいことに、C ++で黒井猫とほぼ同じ速度が得られます。私のコードはn = 12
約5分で届きます。そして、1つの仮想コアでのみ実行します。
編集:検索スペースを1分の1に削減 n
を繰り返したときA*
に、の循環ベクトルがA
元のベクトルと同じ数値(同じ数値)を生成することに気付きA
ましたB
。例えば、ベクターは、(1, 1, 0, 1, 0, 0)
ベクターのそれぞれと同じ確率を有し(1, 0, 1, 0, 0, 1)
、(0, 1, 0, 0, 1, 1)
、(1, 0, 0, 1, 1, 0)
、(0, 0, 1, 1, 0, 1)
および(0, 1, 1, 0, 1, 0)
ランダムを選択するときB
。したがって、私は、これらの6つのベクトルの各々を反復処理する必要がありますが、たったの約1と交換しないcount[i] += 1
とcount[i] += cycle_number
。
これにより、複雑さがからTheta(n) = 6^n
に減少しTheta(n) = 6^n / n
ます。そのためn = 13
、以前のバージョンの約13倍の速さです。n = 13
約2分20秒で計算されます。n = 14
それはまだ少し遅すぎます。約13分かかります。
編集2:マルチコアプログラミング
次の改善にあまり満足していません。また、プログラムを複数のコアで実行しようとすることにしました。2 + 2コアではn = 14
、約7分で計算できます。わずか2の改善点。
コードは、このgithubリポジトリ:Linkで入手できます。マルチコアプログラミングは少しTheいものです。
編集3:A
ベクトルおよびB
ベクトルの検索スペースを削減する
A
クロイネコと同じように、ベクトルのミラー対称性に気付きました。まだわからない、なぜこれが機能するのか(そして、それがそれぞれに機能するかどうかn
)。
B
ベクトルの検索スペースの削減は少し賢いです。ベクトルの生成(itertools.product
)を独自の関数に置き換えました。基本的には、空のリストから始めて、スタックに配置します。スタックが空になるまで、リストを削除します。リストと同じ長さでない場合は、n
他の3つのリストを生成し(-1、0、1を追加して)、スタックにプッシュします。リストの長さはと同じでn
、合計を評価できます。
ベクトルを自分で生成したので、合計= 0に到達できるかどうかに応じてベクトルをフィルタリングできます。たとえば、ベクターA
が(1, 1, 1, 0, 0)
で、ベクターB
が見える場合、値を(1, 1, ?, ?, ?)
埋めることができないことがわかり?
ますA*B = 0
。そのB
ため、フォームのこれらの6つのベクトルすべてを反復処理する必要はありません(1, 1, ?, ?, ?)
。
1の値を無視すると、これを改善できます。質問で述べたように、の値i = 1
はシーケンスA081671です。それらを計算する方法はたくさんあります。単純な繰り返しを選択します:a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
。i = 1
基本的に短時間で計算できるため、のベクトルをさらにフィルタリングできますB
。例えばA = (0, 1, 0, 1, 1)
とB = (1, -1, ?, ?, ?)
。最初のが? = 1
であるため、A * cycled(B) > 0
これらすべてのベクトルについて、ベクトルを無視できます。フォローしていただければ幸いです。それはおそらく最良の例ではありません。
これによりn = 15
、6分で計算できます。
編集4:
すぐに黒井猫の素晴らしいアイデアを実装し、それは言う、それは同じ結果B
を-B
生み出します。スピードアップx2。ただし、実装は簡単なハックにすぎません。n = 15
3分で。
コード:
完全なコードについては、Githubにアクセスしてください。次のコードは、主な機能の単なる表現です。インポート、マルチコアプログラミング、結果の印刷は省略しました...
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
使用法:
pypyをインストールする必要があります(Python 2の場合!!!)。並列pythonモジュールはPython 3用に移植されていません。その後、並列pythonモジュールpp-1.6.4.zipをインストールする必要があります。それをcd
フォルダに展開し、を呼び出しますpypy setup.py install
。
次に、私のプログラムを呼び出すことができます
pypy you-do-the-math.py 15
CPUの数を自動的に決定します。プログラムの終了後にエラーメッセージが表示される場合がありますが、無視してください。n = 16
あなたのマシンで可能になるはずです。
出力:
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%
メモとアイデア:
- 2つのコアと4つのスレッドを備えたi7-4600mプロセッサを使用しています。2スレッドまたは4スレッドを使用しても問題ありません。CPU使用率は2スレッドで50%、4スレッドで100%ですが、それでも同じ時間がかかります。理由はわかりません。4つのスレッドがある場合、各スレッドはデータの半分までしか持っていないことを確認し、結果を確認しました...
- 私は多くのリストを使用します。Pythonは格納するのに非常に効率的ではありません。たくさんのリストをコピーする必要があります。ベクトルAのビット00(0の場合)および11(1の場合)、およびベクトルBのビット10(-1の場合)、00(0の場合)および01(1の場合)を使用できます。 AとBの場合
A & B
、01ブロックと10ブロックのみを計算してカウントする必要があります。サイクリングは、ベクトルをシフトし、マスクを使用することで実行できます。実際にこれをすべて実装しました。Githubでの古いコミットのいくつかで見つけることができます。しかし、リストよりも遅くなることが判明しました。pypyはリスト操作を本当に最適化していると思います。