2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

"Groverのアルゴリズムで簡単なパズルを解く" のフォローアップ

Last updated at Posted at 2023-07-23

本記事の動機

Qiitaに投稿があった以下の記事について、勉強して、フォローアップしてみます。
量子アルゴリズムで二次元パズルを解いています。
https://qiita.com/mi_yuyu/items/7a5756c8462fad8eefc3

改良点

  • 上記記事では、二次元のパズルを特定の初期状態(全零|00...0>)から解いていますが、任意の初期状態から開始できるようにします。
  • 解として求まった手順で、実際にパズルが解けているかを可視化するようにします。

実装

変更点のみ記載します。
pandasとmatplotlibを使います。

まず、ボタン番号と、押した時に反応する隣接ボタンをlook up table にしておきます。可読性が若干上がるため。

LUT = [{'button_num':3, 'neighbor':(3,2,4), 'matrix_index':(0,0)},\
      {'button_num':4, 'neighbor':(4,0,3,5), 'matrix_index':(1,0)},\
      {'button_num':5, 'neighbor':(5,4,6), 'matrix_index':(2,0)},\
      {'button_num':2, 'neighbor':(2,0,1,3), 'matrix_index':(0,1)},\
      {'button_num':0, 'neighbor':(0,2,4,6,8), 'matrix_index':(1,1)},\
      {'button_num':6, 'neighbor':(6,0,5,7), 'matrix_index':(2,1)},\
      {'button_num':1, 'neighbor':(1,2,8), 'matrix_index':(0,2)},\
      {'button_num':8, 'neighbor':(8,0,1,7), 'matrix_index':(1,2)},\
      {'button_num':7, 'neighbor':(7,6,8), 'matrix_index':(2,2)}]
DF = pd.DataFrame(LUT)

任意の初期盤面をセットできるようにします。

# 任意の初期盤面を設定
def state_preparation(qc, initial_state=[0,0,0,0,0,0,0,0,0]):
    for i,state in enumerate(initial_state):
        if state == 1:
            qc.x(9+i)

解として求まるのは ボタンを押す順番(操作手順) なので、盤面を教えてはくれません。(実は補助ビットを測定すれば知ることはできますけどね)
なので、操作手順を古典的に実行して盤面を計算する関数を作ります。

# 測定結果の古典ビット列(ボタン操作履歴)から盤面の終状態を計算する
def calc_button_state(initial_state = [0,0,0,0,0,0,0,0,0], button_str='010101011'):
    # str to list
    button = []
    for i in button_str:
        button.append(int(i))
    button = button[::-1]

    state = initial_state.copy()
#     botton_state = [0,1,0,1,0,1,0,1,1]
#     botton_state = botton_state[::-1]
    for i in range(9):
        if button[i] == 1:
            # get neighbor
            neighbors = DF.query("button_num==@i")["neighbor"].values[0]
            for neighbor in neighbors:
                if state[neighbor] == 1:
                    state[neighbor] = 0
                elif state[neighbor] == 0:
                    state[neighbor] = 1
        else:
            pass
    return state

盤面を2次元プロットします。

# 盤面の状態を可視化する
def plot_puzzle(state = [0,1,0,1,1,1,1,0,0]):
    state_matrix = np.array(state).reshape(3,3)
    plt.imshow(state_matrix, cmap='gray', interpolation='none')
    for i in range(3):
        for j in range(3):
            pos = (i,j)
#             print(pos)
            button_num = DF.query("matrix_index==@pos")["button_num"].values[0]
#             print(button_num)
            plt.text(x=i,y=j,s=str(button_num),color='r',fontsize=18)
    plt.xlim([-0.5,2.5])
    plt.ylim([-0.5,2.5])
    plt.xticks([])
    plt.yticks([])
    plt.show()

image.png

メイン関数です。
initial stateを指定しているところしか違いません。
なお、Diffuserにおいて、このinitial state preparationをuncomputeする必要はありませんので、Grover部分は変わりません。


# 量子ビット数と古典ビット数
n = 9 #ボタンの数
q = 2 * n #量子ビット数. 2倍なのはボタン操作用に記録するビットがそれぞれあるから
c = 9 #古典ビット数
initial_state = [0,0,0,0,0,0,0,0,0] # 9-elements list consits of 0 or 1.
# initial_state = [0,0,1,0,1,0,1,0,1] # 9-elements list consits of 0 or 1.
    
# 最初に全ての状態(input)を同じ確率で重ね合わせる
circ_init = QuantumCircuit(q, c)
circ_init.h(range(n))

# 状態(state)をset
state_preparation(circ_init, initial_state = initial_state)

続きを前述の参考記事通りに実行して、得られた結果から盤面を計算します。

button_str = sorted_counts_sim[0][0] # most frequent bit pattern
state = calc_button_state(initial_state=initial_state, button_str=button_str)
print(state)

`[0, 0, 0, 0, 0, 0, 0, 0, 0]

となりました。
つまり、確かに量子アルゴリズムで得られた手順をやれば、盤面は一色(解状態)になります。
可視化しておきます。

plot_puzzle(state)

image.png

初期状態を変えてみる

initial_state = [0,1,1,0,0,0,1,0,1]
image.png

でやってみます。

image.png

これも解けました。
正しく実装できています。

解でない状態

Groverアルゴリズムにおいて、解が100%の確率で得られるとは限りません。
Groverアルゴリズムは、整数回の反復で解を増幅するのですが、整数であるがゆえに、ちょうど100%で狙って止めることは一般には出来ません。
例えばGroverアルゴリズムの実際の出力は、

[('010100110', 8186), ('110111011', 2), ('100010010', 1), ('100101100', 1), ('100010110', 1), ('100100010', 1)]

のように、解でない状態も一部得られます。
これらはどのような盤面に対応しているでしょうか。

「あと1手で解であるような、惜しい手順」だと思いますか?
実際に可視化してみます。

image.png

image.png

image.png

image.png

別にそういうわけではない ということがわかりますね。
Groverアルゴリズムは、解と解でない状態をTrueかFalseかで区別しているイメージになるので、解でない状態達は、それが解に”近い”か”遠い”かによらず、等しい確率で残っています。
これは原理的な話です。

でも、もし解に近い状態も回収したい(成功率を上げたい)と思ったら?
オラクル(解であることを定義する条件)を緩和するか、「解との”距離”(ハミング距離?)という数値を考慮する修正Groverアルゴリズム」を考えればよさそうですね。
そういう研究はすでにあるのではないかと思います。
やりすぎると、QAOAになりますね。

結論

今回は、QiitaにあったGroverアルゴリズムの記事を引用して、少し付加価値をつけてみました。
Groverアルゴリズムの実例はまだまだQiitaにも数少ないので、増やしていけたらと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?