振幅エンコーディング (2)
データ木構造を用いた振幅エンコーディング
前回はQRAMをもちいて確率的にエンコードする方法をみました。
プログラム実装を考えるとユニタリ変換のみで構成されているほうが、なにかとやりやすいです。
Prakash氏によって提案されたデータ木構造を用いた振幅エンコーディングであれば実装できそうだったので、詳細を書き下していきます。
2量子ビットの場合
ことはじめとして、
今回の最初のお題は以下のベクトル
なお、このベクトルは、あるデータ行列
-
をベクトル\bm{v'} へ規格化します。\bm{v} \bm{v} = \frac{1}{\|v'\|} v' = \begin{pmatrix}0.4 \\ -0.4 \\ -0.8 \\ 0.2 \end{pmatrix} 最終的に欲しい量子状態
をイメージして書き下します。\ket{\phi} \ket{\phi} = 0.4 \ket{00} - 0.4 \ket{01} - 0.8 \ket{10} + 0.2 \ket{11}
-
初期状態の準備
QRAMアドレス用のレジスタと、そのアドレスに対応するデータの格納用レジスタを準備します。
\ket{0}_{addr}\ket{0^n}
-
QRAM操作を仮定し、
の各成分を\bm{v} bit でビット列表示し、重ね合わせ状態で呼び出します。n \frac{1}{\sqrt{N}} \sum_{i=1}^{N} \ket{i} \ket{0^n} \xrightarrow{QRAM} \frac{1}{\sqrt{N}} \sum_{i=1}^{N} \ket{i} \ket{b(v_i)}
-
さらに
bitのレジスタを用意し、データ行列p のA 行目のベクトルi を確率振幅へエンコードする操作は、どういう操作と同等なのか考えます。v^T p = logN \\ \frac{1}{\sqrt{N}} \sum_{i=1}^{N} \ket{i} \ket{b(v_i)} \ket{0^p} \xrightarrow{U_{what?}} 以降は、
の場合を考えます。p = \log 4 = 2 \frac{1}{\sqrt{N}} \sum_{i=1}^{N} \ket{i} \ket{b(v_i)} \ket{00} \xrightarrow{U_{what?}}
-
最上位ビットから考えてみる。
最上位ビットが
の確率、つまり、\ket{0} あるいは\ket{00} の観測確率が下のようになればいいですね\ket{01}
( は規格化されていることに注意)。v (0.4)^2 + (-0.4)^2 = 0.32 一方で、
あるいは\ket{10} の観測確率は、\ket{11} (-0.8)^2 + (0.2)^2 = 0.68 そのようなユニタリ変換を実行すればよいので、
\ket{00} \xrightarrow{U_{rot1}} \left(\sqrt{0.32}\ket{0} + \sqrt{0.68}\ket{1} \right)\ket{0}
-
次の上位ビットについて考える。
以降は、QRAM(データレジスタ)部分を省略して記載します。一つ上(=親)に対して条件付き確率を考えると、
\left(\sqrt{0.32}\ket{0} + \sqrt{0.68}\ket{1} \right)\ket{0} \xrightarrow{U_{rot2}} \left[ \sqrt{0.32}\ket{0} \frac{1}{\sqrt{0.32}} \left(\sqrt{0.16}\ket{0} - \sqrt{0.16}\ket{1} \right) + \sqrt{0.68}\ket{1} \frac{1}{\sqrt{0.68}} \left(-\sqrt{0.64}\ket{0} + \sqrt{0.04}\ket{1} \right) \right] \\ = \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} - \sqrt{0.64}\ket{10} + \sqrt{0.04}\ket{11} \\ = 0.4\ket{00} - 0.4\ket{01} - 0.8\ket{10} + 0.2\ket{11}
- 符号について
特定の量子ビットの符号を反転させる操作は制御 ゲートを利用すれば実現できるので、制御Z ゲートやX ゲートを用いて狙った量子状態に”しるし”を付けることが可能です。SWAP
-
QRAM操作って要る?要らない?
シミュレータで計算したい場合、arccos関数の値域に注意すると、回転ゲートさえ適用できれば振幅エンコードできそうです(小数の2進数表示の近似計算に
bit精度を確保したい場合、その分の量子ビットは必要になります。)m
ただし、アドレスビットとして使用する量子ビットに対して、上位ビットの状態に応じ制御回転ゲートを適用していく必要がありそうです。
上位ビットを
で表すとすると、2量子ビットの場合、状態\ket{k} に対して、4.のような操作を行いたい場合を考えます。\ket{k}\ket{0}
の場合k=0 最上位ビットを
ゲートで反転させたのち、最上位ビットを制御ビットとして第2量子ビットに制御回転ゲートを適用すればよさそうです。その後、逆演算でリセットすれば下式に状態になります。X \left(\sqrt{0.32}\ket{0} + \sqrt{0.68}\ket{1} \right)\ket{0} \xrightarrow{X_{q_1}} \left(\sqrt{0.32}\ket{1} + \sqrt{0.68}\ket{0} \right)\ket{0} \\ \xrightarrow{CRy_{q_1,q_0}} \sqrt{0.32}\ket{1} \frac{1}{\sqrt{0.32}} \left(\sqrt{0.16}\ket{0} + \sqrt{0.16}\ket{1} \right) + \sqrt{0.68}\ket{0} \ket{0} \\ \xrightarrow{CZ_{q_1,q_0}} \sqrt{0.32}\ket{1} \frac{1}{\sqrt{0.32}} \left(\sqrt{0.16}\ket{0} - \sqrt{0.16}\ket{1} \right) + \sqrt{0.68}\ket{0} \ket{0} \\ \xrightarrow{X_{q_1}} \sqrt{0.32}\ket{0} \frac{1}{\sqrt{0.32}} \left(\sqrt{0.16}\ket{0} - \sqrt{0.16}\ket{1} \right) + \sqrt{0.68}\ket{1} \ket{0} \\ = \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} + \sqrt{0.68}\ket{10} の場合k=1 最上位ビットを反転させる必要がないため、第2量子ビットに制御回転ゲートを適用すればよさそうです。
また、符号を反転させたいところ(ただし、
のところ)があるので、\ket{0} ゲートで反転させたのち、CX ゲートを適用させれば良さそうです。CZ またその後、逆演算でリセットすれば下式に状態になります。
\sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} + \sqrt{0.68}\ket{10} \\ \xrightarrow{CRy_{q_1,q_0}} \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} + \sqrt{0.68}\ket{1} \frac{1}{\sqrt{0.68}} \left(\sqrt{0.64}\ket{0} + \sqrt{0.04}\ket{1} \right) \\ = \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} + \sqrt{0.64}\ket{10} + \sqrt{0.04}\ket{11} \\ \xrightarrow{CX{q_1,q_0}} \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} + \sqrt{0.64}\ket{11} + \sqrt{0.04}\ket{10} \\ \xrightarrow{CZ{q_1,q_0}} \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} - \sqrt{0.64}\ket{11} + \sqrt{0.04}\ket{10} \\ \xrightarrow{CX{q_1,q_0}} \sqrt{0.16}\ket{00} - \sqrt{0.16}\ket{01} - \sqrt{0.64}\ket{10} + \sqrt{0.04}\ket{11}
-
最後に
これでめでたく振幅エンコーディングが実装できそうです。振幅エンコーディングを Blueqat を用いて実装し、実行してみました。なお、
です。m=16 norm_vec = normalize(tgt_vec) print("target vec: %s, norm: %s" % (norm_vec, np.linalg.norm(norm_vec))) state = qc.run(backend="numpy") nqubit = int(np.log2(np.size(state))) for i in range(2**nqubit): if state[i] != 0: print('+({:.6f})*|{number:0{width}b}>'.format(state[i],number=i,width=nqubit),end='') print("\n state: ", bin(i)[2:].zfill(nqubit), "amplitude: ", state[i])
target vec: [ 0.4 -0.4 -0.8 0.2], norm: 1.0 +(0.400033+0.000000j)*|0000000000000000000> state: 0000000000000000000 amplitude: (0.4000327997380376+0j) +(-0.400033+0.000000j)*|0100000000000000000> state: 0100000000000000000 amplitude: (-0.4000327997380378+0j) +(-0.799973+0.000000j)*|1000000000000000000> state: 1000000000000000000 amplitude: (-0.7999733486681582+0j) +(0.199975+0.000000j)*|1100000000000000000> state: 1100000000000000000 amplitude: (0.19997539770718653+0j)
プログラム実装時の注意点
-
制御回転ゲート適用後の逆演算
制御回転ゲートの実行ごとに逆演算を実行し、データを格納しているレジスタを初期化する必要があることに注意。逆演算は適用していた量子回路を逆向きに作用させていけば良いので、慣れると特に問題にならないと思います。
-
の2進数表示への\frac{1}{\pi}\cos^{-1}{(v_i)} bit精度の近似計算m \frac{1}{\pi}\cos^{-1}{(v_i)} \approx \sum_{k=0}^{m-1} 2^{-k-1} b_k \left(\frac{1}{\pi} \cos^{-1}{(v_i)} \right) の計算をプログラムで実装する必要があります。付け焼き刃で準備したものを、備忘録として参考に残します。
小数を二進数表示した
bit目について、k を乗じるか否かのフラグ multi_flag を返します。2^{-k} from decimal import Decimal def bit_precision(f, bit_precision): # 0 < f(= (1/pi) * arccos(v_i)) < 1 を (bit_precision) bit精度で表現 # Used for calculating the number of times to multiply by 2^-k dec=Decimal(str(f)) Bin='' nth=0 first=0 multi_flag = [] rot_rad = [] while dec: if nth == bit_precision: break if dec >= 1: Bin += '1' dec = dec -1 if first==0: first=nth multi_flag.append(1) rot_rad.append(2**(-nth)) else: if nth!=0: Bin += '0' multi_flag.append(0) rot_rad.append(0) elif nth==0: Bin +='0.' # don't append because it's first part dec*=2 nth+=1 if nth-first==bit_precision: break return sum(rot_rad), multi_flag
3量子ビット及びそれ以上の量子ビットの場合
基本的にやることは同じですが、時間があるときに具体的に備忘録として残していきます。
うまく実装できると下記のようになります(
対象とするベクトルの要素に0が入っていたとしても同様で、それに対応したアドレス量子ビットの確率振幅が0になっていることが分かります。
norm_vec = normalize(tgt_vec)
print("target vec: %s, norm: %s" % (norm_vec, np.linalg.norm(norm_vec)))
state = qc.run(backend="numpy")
nqubit = int(np.log2(np.size(state)))
for i in range(2**nqubit):
if state[i] != 0:
print('+({:.6f})*|{number:0{width}b}>'.format(state[i],number=i,width=nqubit),end='')
print("\n state: ", bin(i)[2:].zfill(nqubit), "amplitude: ", state[i])
target vec: [-0.41702883 -0.41702883 0. -0.20851441 0.62554324 -0.20851441
0. -0.41702883], norm: 1.0
+(-0.417081+0.000000j)*|00000000000000000000>
state: 00000000000000000000 amplitude: (-0.4170806865237128+0j)
+(-0.417081+0.000000j)*|00100000000000000000>
state: 00100000000000000000 amplitude: (-0.4170806865237128+0j)
+(-0.208500+0.000000j)*|01100000000000000000>
state: 01100000000000000000 amplitude: (-0.2085004203847322+0j)
+(0.625526+0.000000j)*|10000000000000000000>
state: 10000000000000000000 amplitude: (0.6255264988585799+0j)
+(-0.208444+0.000000j)*|10100000000000000000>
state: 10100000000000000000 amplitude: (-0.2084435314653194+0j)
+(-0.416993+0.000000j)*|11100000000000000000>
state: 11100000000000000000 amplitude: (-0.41699264978317346+0j)
今後の興味あること
- 量子機械学習
- VQLSのようなNISQで実行可能なアルゴリズムのシミュレータを用いた実行
(ラプラス方程式やポアソン方程式をとりあえず想定しています) - メタバース上でシミュレーション可視化及びデバッグ作業の効率化
- より少ない量子ビットで多様なデータを効率的にエンコードする方法
- 数値誤差が今後の演算により増幅するのか或いは減衰するのか、その仕組みへの理解
Discussion