Skip to main content
IBM Quantum Platform

ドイッチュのアルゴリズム

Deutschのアルゴリズムは、次のような特殊な場合のパリティ問題を解きます。 n=1.n = 1. 量子コンピュータの文脈では、この問題は Deutschの問題と呼ばれることがあり、このレッスンでもその呼び方に従います。

正確には、入力は1ビットから1ビットへの関数( f:ΣΣf:\Sigma \rightarrow \Sigma )で表される。 このような機能は4つある:

af1(a)0010af2(a)0011af3(a)0110af4(a)0111\rule[-10mm]{0mm}{10mm} \begin{array}{c|c} a & f_1(a)\\ \hline 0 & 0\\ 1 & 0 \end{array} \qquad \begin{array}{c|c} a & f_2(a)\\ \hline 0 & 0\\ 1 & 1 \end{array} \qquad \begin{array}{c|c} a & f_3(a)\\ \hline 0 & 1\\ 1 & 0 \end{array} \qquad \begin{array}{c|c} a & f_4(a)\\ \hline 0 & 1\\ 1 & 1 \end{array}

これらの関数の最初と最後は定数であり、真ん中の2つは釣り合いが取れている。つまり、関数の2つの可能な出力値は、入力を範囲指定したときに同じ回数だけ発生する。 ドイッチュの問題は、入力関数がこの2つのカテゴリーのどちらに属するかを決定することである。

Deutsch's problem

入力:関数 f:{0,1}{0,1}f:\{0,1\}\rightarrow\{0,1\} \ 出力: ff が定数なら 00, ff が釣り合いなら 11

ドイチュの問題の入力関数 ff を文字列へのランダムアクセスと見なせば、2ビットの文字列を考えていることになる: f(0)f(1).f(0)f(1).

functionstringf100f201f310f411\begin{array}{cc} \mathsf{function} & \mathsf{string}\\ \hline f_1 & 00 \\ f_2 & 01 \\ f_3 & 10 \\ f_4 & 11 \end{array}

このように考えると、ドイッチュの問題は、2つのビットのパリティ(あるいは等価的に排他的論理和)を計算することである。

この問題を正しく解く古典的なクエリーアルゴリズムは、 f(0)f(0) と の両方のビットをクエリーしなければならない。 f(1).f(1). 例えば、 f(1)=1,f(1) = 1, を知ったとしても、 f(0)=1f(0) = 1f(0)=0,f(0) = 0, かによって、答えは 001,1, になる。 2つのビットのうち1つだけを知っても、そのパリティについてはまったく情報が得られないのだ。 つまり、前節で説明したブール回路は、この問題を解くのに必要なクエリーの数という点で、私たちができる最善のものなのだ。


量子回路記述

Deutschのアルゴリズムは、単一のクエリーを用いてDeutschの問題を解くため、古典的な計算に対する量子の定量的な利点を提供する。 これはささやかな利点かもしれない。 科学の進歩は時として、一見地味な起源を持つ。

ドイッチュのアルゴリズムを説明する量子回路を紹介しよう:

ドイッチュのアルゴリズム

解析

Deutschのアルゴリズムを解析するために、上の回路の動作をトレースし、この図が示唆する時間における量子ビットの状態を特定する:

ドイチュのアルゴリズム中の状態

初期状態は 10,\vert 1\rangle \vert 0 \rangle,、回路の左辺にある2つのハダマード演算は、この状態を次のように変換する

π1=+=12(01)0+12(01)1.\vert \pi_1 \rangle = \vert - \rangle \vert + \rangle = \frac{1}{2} \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \vert 0\rangle + \frac{1}{2} \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \vert 1\rangle.

(いつものように、Qiskitの量子ビットの並び順の規則に従っている。上の量子ビットを右に、下の量子ビットを左に置く) この積の状態を部分的に分散して書くのは直感的でないと感じるかもしれないが(量子ビット1の状態を因数分解したままにしておく)、こうすることで後の式がよりコンパクトになる。

次に、 UfU_fUfU_f ゲートの定義によれば、一番上/右端の量子ビットの古典的状態に対する関数 ff の値が一番下/左端の量子ビットにXORされ、 π1\vert \pi_1\rangle が次の状態に変換される

π2=12(0f(0)1f(0))0+12(0f(1)1f(1))1.\vert \pi_2 \rangle = \frac{1}{2} \bigl( \vert 0 \oplus f(0) \rangle - \vert 1 \oplus f(0) \rangle \bigr) \vert 0 \rangle + \frac{1}{2} \bigl( \vert 0 \oplus f(1) \rangle - \vert 1 \oplus f(1) \rangle \bigr) \vert 1 \rangle.

この式を単純化するには、次の式を使用する

0a1a=(1)a(01)\vert 0 \oplus a\rangle - \vert 1 \oplus a\rangle = (-1)^a \bigl( \vert 0\rangle - \vert 1\rangle \bigr)

の両方の可能な値に対して機能する。 aΣ.a\in\Sigma. より明確には、2つのケースは以下の通りである。

0010=01=(1)0(01)0111=10=(1)1(01)\begin{aligned} \vert 0 \oplus 0\rangle - \vert 1 \oplus 0\rangle & = \vert 0 \rangle - \vert 1 \rangle = (-1)^0 \bigl( \vert 0\rangle - \vert 1\rangle \bigr)\\ \vert 0 \oplus 1\rangle - \vert 1 \oplus 1\rangle & = \vert 1 \rangle - \vert 0\rangle = (-1)^1 \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \end{aligned}

したがって、 π2\vert\pi_2\rangle を次のように表現することもできる:

π2=12(1)f(0)(01)0+12(1)f(1)(01)1=((1)f(0)0+(1)f(1)12).\begin{aligned} \vert\pi_2\rangle & = \frac{1}{2} (-1)^{f(0)} \bigl( \vert 0 \rangle - \vert 1 \rangle \bigr) \vert 0 \rangle + \frac{1}{2} (-1)^{f(1)} \bigl( \vert 0 \rangle - \vert 1 \rangle \bigr) \vert 1 \rangle \\ & = \vert - \rangle \biggl( \frac{(-1)^{f(0)} \vert 0\rangle + (-1)^{f(1)} \vert 1\rangle}{\sqrt{2}}\biggr). \end{aligned}

面白いことが起きた! 標準的な基底状態に対する UfU_f ゲートの動作は、一番上/右端の量子ビットはそのままにしておき、一番下/左端の量子ビットに関数値をXORします。しかし、ここでは、一番上/右端の量子ビットの状態が(一般的に)変化している一方で、一番下/左端の量子ビットの状態は同じままであることがわかります。具体的には、 UfU_f ゲートが実行される前と後では、 \vert - \rangle の状態になっています。 この現象はフェイズ・キックバックとして知られており、これについては後ほど詳しく説明する。

最後の単純化として、 (1)f(0)(-1)^{f(0)} の因子を和の外側に引っ張り出すことで、状態 π2\vert\pi_2\rangle の式が得られる:

π2=(1)f(0)(0+(1)f(0)f(1)12)={(1)f(0)+if f(0)f(1)=0(1)f(0)if f(0)f(1)=1.\begin{aligned} \vert\pi_2\rangle & = (-1)^{f(0)} \vert - \rangle \biggl( \frac{\vert 0\rangle + (-1)^{f(0) \oplus f(1)} \vert 1\rangle}{\sqrt{2}}\biggr) \\ & = \begin{cases} (-1)^{f(0)} \vert - \rangle \vert + \rangle & \text{if $f(0) \oplus f(1) = 0$}\\[1mm] (-1)^{f(0)} \vert - \rangle \vert - \rangle & \text{if $f(0) \oplus f(1) = 1$}. \end{cases} \end{aligned}

この式では、純粋に代数的な観点から予想される f(1)f(0),f(1) - f(0), とは対照的に、 1-1 の指数に f(0)f(1)f(0) \oplus f(1) が使われていることに注目してほしい。 これは、任意の整数 kk に対する値 (1)k(-1)^k が、 kk が偶数か奇数かにのみ依存するからである。

最後のハダマードゲートをトップ量子ビットに適用すると、次のような状態になる

π3={(1)f(0)0if f(0)f(1)=0(1)f(0)1if f(0)f(1)=1,\vert \pi_3 \rangle = \begin{cases} (-1)^{f(0)} \vert - \rangle \vert 0 \rangle & \text{if $f(0) \oplus f(1) = 0$}\\[1mm] (-1)^{f(0)} \vert - \rangle \vert 1 \rangle & \text{if $f(0) \oplus f(1) = 1$}, \end{cases}

右/一番上の量子ビットが測定されたとき、確率 11、正しい結果を導く。


位相キックバックに関する補足説明

次に進む前に、位相のキックバック現象を解明するために、上記の分析を少し違った角度から見てみよう。

まず、以下の式がすべてのビットの選択に対して有効であることに注目してほしい。 b,cΣ.b,c\in\Sigma.

bc=Xcb\vert b \oplus c\rangle = X^c \vert b \rangle

これは、 c=0c = 0c=1c = 1 の2つの可能な値についてチェックすることで検証できる:

b0=b=Ib=X0bb1=¬b=Xb=X1b.\begin{aligned} \vert b \oplus 0 \rangle & = \vert b\rangle = \mathbb{I} \vert b \rangle = X^0 \vert b \rangle\\ \vert b \oplus 1 \rangle & = \vert \neg b\rangle = X \vert b \rangle = X^1 \vert b \rangle. \end{aligned}

この式を使うと、次のようになる

Uf(ba)=bf(a)a=(Xf(a)b)aU_f \bigl(\vert b\rangle \vert a \rangle\bigr) = \vert b \oplus f(a) \rangle \vert a \rangle = \bigl(X^{f(a)}\vert b \rangle\bigr) \vert a \rangle

あらゆるビットの選択に対して a,bΣ.a,b\in\Sigma. この式は b=0b=0b=1,b=1, について成り立つので、線形性によって次のことがわかる。

Uf(ψa)=(Xf(a)ψ)aU_f \bigl( \vert \psi \rangle \vert a \rangle \bigr) = \bigl(X^{f(a)}\vert \psi \rangle\bigr) \vert a \rangle

すべての量子ビット状態ベクトルについて ψ,\vert \psi\rangle,、したがって

Uf(a)=(Xf(a))a=(1)f(a)a.U_f \bigl( \vert - \rangle \vert a \rangle \bigr) = \bigl(X^{f(a)} \vert - \rangle \bigr) \vert a \rangle = (-1)^{f(a)} \vert - \rangle \vert a \rangle.

この機能を実現する鍵は以下の通りである。 X=.X\vert - \rangle = - \vert - \rangle. 数学用語で言えば、ベクトル \vert - \rangle は、 固有値を持つ行列 XX固有ベクトルである。 1.-1.

固有ベクトルと固有値については、次回の「 位相推定と因数分解 」のレッスンでさらに詳しく説明します。このレッスンでは、位相キックバック現象を他のユニタリー演算に一般化します。

スカラーはテンソル積を通して自由に浮遊することを念頭に置いて、上記の分析において、 UfU_f という演算が π1\vert \pi_1\rangleπ2\vert \pi_2\rangle に変換する方法を推論する別の方法を見つける:

π2=Uf(+)=12Uf(0)+12Uf(1)=((1)f(0)0+(1)f(1)12).\begin{aligned} \vert \pi_2 \rangle & = U_f \bigl( \vert - \rangle \vert + \rangle \bigr)\\ & = \frac{1}{\sqrt{2}} U_f \bigl(\vert - \rangle \vert 0\rangle \bigr) + \frac{1}{\sqrt{2}} U_f \bigl(\vert - \rangle \vert 1\rangle \bigr)\\ & = \vert - \rangle \biggl( \frac{(-1)^{f(0)} \vert 0\rangle + (-1)^{f(1)} \vert 1\rangle}{\sqrt{2}}\biggr). \end{aligned}

Qiskitでの実装

では、DeutschのアルゴリズムをQiskitでどのように実装できるかを見てみよう。 まずはバージョンチェックから始め、この実装だけに必要なインポートを実行する。 この後に続く他のアルゴリズムの実装については、モジュール性を高めるために、必要なインポートを個別に行うことにする。

from qiskit import __version__

print(__version__)

Output:

2.1.1
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

まず、先に説明した1ビットから1ビットへの4つの関数 f1,f_1, f2,f_2, f3,f_3, f4f_4 のうちの1つに対するクエリーゲートを実装する量子回路を定義する。 すでに述べたように、クエリゲートの実装はドイチュのアルゴリズムそのものではない; ここでは基本的に、クエリーゲートの回路実装という形で、入力を準備する一つの方法を示しているに過ぎない。

def deutsch_function(case: int):
    # This function generates a quantum circuit for one of the 4 functions
    # from one bit to one bit

    if case not in [1, 2, 3, 4]:
        raise ValueError("`case` must be 1, 2, 3, or 4.")

    f = QuantumCircuit(2)
    if case in [2, 3]:
        f.cx(0, 1)
    if case in [3, 4]:
        f.x(1)
    return f

draw 、各回路がどのように見えるかを見ることができる。 この機能の回路は以下の通りである。 f3.f_3.

display(deutsch_function(3).draw(output="mpl"))

Output:

Output of the previous code cell

次に、クエリーゲートを引数として与えられた量子回路実装に置き換えて、ドイチュのアルゴリズムの実際の量子回路を作成する。 まもなく、先に定義した関数 deutsch_function で定義された4つの回路のうちの1つを接続する。 バリアは、クエリーゲートの実装と回路の他の部分との間の視覚的な分離を示すために含まれている。

def compile_circuit(function: QuantumCircuit):
    # Compiles a circuit for use in Deutsch's algorithm.

    n = function.num_qubits - 1
    qc = QuantumCircuit(n + 1, n)

    qc.x(n)
    qc.h(range(n + 1))

    qc.barrier()
    qc.compose(function, inplace=True)
    qc.barrier()

    qc.h(range(n))
    qc.measure(range(n), range(n))

    return qc

もう一度、 draw

display(compile_circuit(deutsch_function(3)).draw(output="mpl"))

Output:

Output of the previous code cell

最後に、先に定義した回路を1回実行し、適切な結果を出力する関数を作成する:"コンスタント "または "バランス "である

def deutsch_algorithm(function: QuantumCircuit):
    # Determine if a one-bit function is constant or balanced.

    qc = compile_circuit(function)

    result = AerSimulator().run(qc, shots=1, memory=True).result()
    measurements = result.get_memory()
    if measurements[0] == "0":
        return "constant"
    return "balanced"

これでDeutschのアルゴリズムを、上で定義した4つの関数のいずれかに対して実行することができる。

f = deutsch_function(3)
display(deutsch_algorithm(f))

Output:

'balanced'
このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。