AI要約
前回の法15に続き、今回は法を21に変更してショアのアルゴリズムの周期発見に挑みます。コントロール10ビット、ターゲット5ビットの回路を構築し、底が2や4の場合の周期をシミュレーションで導き出しました。
はじめに
ショアのアルゴリズムを学ぶシリーズ。前回は「15個溜まったら捨てて余りが1個だけになる」タイミングを計算しました。
ネット検索では法が15以外の例があまり見つかりません。今回は「21個溜まったら捨てる」でも計算できるか試してみましょう。
問題
問題をおさらいします。



倍々に増える栗まんじゅうを21個ずつ処分して、ちょうど1個だけ余るタイミングはあるかな?それを僕が食べてトドメを刺すよ!

つまり、1→2→4→8個…と増えていったときに、21の倍数+1になるタイミングが知りたいんだね!
ということで、問題はこちら。
一定時間ごとに2倍に分裂する栗まんじゅうが1個ある。21個溜まるごとに処分する場合、手元に1個だけ残るのは何回分裂したあとか?
手計算してみる
今回も手計算で求めてみましょう。
| 分裂回数 | 個数 | mod 21 |
|---|---|---|
| 0回 | 1 | 1 |
| 1回 | 2 | 2 |
| 2回 | 4 | 4 |
| 3回 | 8 | 8 |
| 4回 | 16 | 16 |
| 5回 | 32 | 11 |
| 6回 | 64 (22) | 1 |
| 7回 | 128 (2) | 2 |
| 8回 | 256 (4) | 4 |
( )の中の数字は、21個溜まる度にこまめに処分した場合です。たくさん溜めてから処分するのも逐一処分するのも、mod 21した数は同じなのでどちらの派閥でも構いません。表より周期は6、答えは「6回分裂後」とわかります。
コードで確認
前回のコードからいくつか微修正があります。法が21なので、ターゲットビットを4個→5個に増やしました。それに伴い、コントロールビットも10個に増やしました。コードを見比べればだいたい理解できると思います。
import numpy as np
import matplotlib.pyplot as plt
from qiskit_aer import AerSimulator
from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import UnitaryGate
#コントロール10ビット + ターゲット5ビット
qc = QuantumCircuit(15)
#ターゲットビットの最下位を |1> に初期化
qc.x(10)
#コントロールビットすべてにアダマールを適用
for i in range(10):
qc.h(i)
# --- モジュロべき乗演算 ---
#行列を用いて x -> (x * a_power) mod N の変換ゲートを作る関数
def get_mod_matrix(a_power):
U = np.zeros((32, 32)) #ターゲットが5ビットなので 2^5
for x in range(32):
if x < 21:
y = (x * a_power) % 21
U[y, x] = 1
else:
U[x, x] = 1 #N以上は恒等変換
return U
#コントロールビットすべてにゲートを適用
for i in range(10):
#aを決め、a^(2^i) mod N を計算
a_power = pow(2, 2**i, 21)
U_matrix = get_mod_matrix(a_power)
#行列を制御ゲートに変換
c_U = UnitaryGate(U_matrix).control(1)
#コントロールビット i から、ターゲットレジスタすべてへ適用
qc.append(c_U, [i] + list(range(10, 15)))
qc.barrier()
# --- 逆量子フーリエ変換 (IQFT) ---
#ターゲットビットすべてに適用
for j in range(5):
qc.swap(j, 10 - 1 - j)
#コントロールビットすべてに適用
for i in range(10):
for m in range(i):
qc.cp(-np.pi / (2**(i - m)), i, m)
qc.h(i)
qc.barrier()
#計算
qc.measure_all()
backend = AerSimulator(method='matrix_product_state')
t_qc = transpile(qc, backend) #トランスパイルが必要
result = backend.run(t_qc, shots=1000).result().get_counts()
#解の確認
for r in result:
print(f'{r[::-1]} | {result[r]}/1000')
#解のコントロールビットを集計して確認
all_pattern = {}
for r in result:
pattern = r[::-1][:10][::-1] #上位ビットから並ぶようにひっくり返しておく
if pattern in all_pattern:
all_pattern += result[r]
else:
all_pattern = result[r]
print(all_pattern)
#ピークを確認するためのグラフ
x = range(2**10)
y = [0] * 2**10
for pattern in all_pattern:
y[int(pattern, 2)] += all_pattern
plt.plot(x, y)
plt.xticks(range(0, 2**10 + 1, 128))
plt.show()010101010000010 | 5/1000
110101010011010 | 15/1000
111010101001000 | 1/1000
001010101101000 | 3/1000
(中略)
110010101100001 | 1/1000
001010101000001 | 1/1000
001010101110000 | 1/1000
001111001000010 | 1/1000
{'0101010101': 120, '1101010110': 28, (中略), '1010010101': 1, '0010000001': 1}量子譜↓

ピークを見るグラフ↓

さあどうでしょうか。コントロールレジスタの範囲0~1024を6等分するようにピークが現れました。よって周期=6、「6回分裂後」と求まりました。
低を変えてみる
問題を変えてみます。
一定時間ごとに4倍に分裂する栗まんじゅうが1個ある。21個溜まるごとに処分する場合、手元に1個だけ残るのは何回分裂したあとか?
手計算では、
| 分裂回数 | 個数 | mod 21 |
|---|---|---|
| 0回 | 1 | 1 |
| 1回 | 4 | 4 |
| 2回 | 16 | 16 |
| 3回 | 64 | 1 |
| 4回 | 256 (4) | 4 |
| 5回 | 1024 (16) | 16 |
| 6回 | 4096 (64) | 1 |
| 7回 | 16384 (4) | 4 |
| 8回 | 65536 (16) | 16 |
周期は3と短いですね。
コードで確認
コード中の底「a」の値を4に変えるだけです。
#aを決め、a^(2^i) mod N を計算
a_power = pow(4, 2**i, 21)000000000010000 | 104/1000
100101010110000 | 1/1000
010111101000100 | 1/1000
110010101010000 | 3/1000
(中略)
010110101000001 | 1/1000
000011010100001 | 1/1000
011100101000100 | 1/1000
000101010100001 | 1/1000
{'0000000000': 347, '1010101001': 8, (中略), '0101001110': 1, '1010101000': 1}
範囲を3等分するピークが得られました。よって周期=3、「3回分裂後」です。
おわりに
興味があれば、底a=5, 8でも試してみてください。
ちなみにa=3, 6, 7では数学的に余りが1に戻ることがないため問題が成立しません。これは21とaが互いに素でないことをユークリッドの互除法で確認することで証明できます。
次回はいよいよ57の素因数分解に挑戦します。




