量子プログラミング入門→量子コンピューティングサービス構築入門

量子コンピューター初心者の筆者が日々学びながら、量子コンピュータ上で量子プログラミングできるようなるまでの学習日記を記録していきます。 内容は量子コンピューター入門者向けで、専門家の方からするとおかしな内容があるかもしれません。その際はコメント等でお知らせください!

Grover's Search Algorithm(グローバーの検索アルゴリズム)解説

グローバーのアルゴリズムについて

グローバーの検索アルゴリズムは量子コンピューティングのアルゴリズムで最も有名なアルゴリズムの一つです。
グローバーのアルゴリズムはデータベース検索アルゴリズムと知られています。
グローバーの論文の中の例を要約すると

完全にランダムに並べられたN個の名前を含む電話帳の中から特定の名前を50%の確率で見つけ出すには、古典コンピュータを使うと\(frac{N}{2}\)の名前を見る必要があります。量子コンピュータでは重ね合わせの状態を使うことにより複数の名前を同時に調べることができます。

しかし、より正確にはグローバーのアルゴリズムは逆関数の生成と考えた方がよいでしょう。
このアルゴリズムはinverting a function(関数の逆算)と言った方がよいでしょう。
つまり関数\(y=f(x)\)に対して、ある\(y\)を導出する\(x\)を求めるのに利用することができます。

このチュートリアルの目的

このチュートリアルではグローバーのアルゴリズムの実践的使用法でより一般的な問題(SAT問題)をQ#を使って解いていきます。このチュートリアルではグローバーのアルゴリズムの実装などは取り扱いません。

Part I. Problem Statement (問題定義)

SAT問題・充足可能性問題とは

充足可能性問題とは、入力xをN個のboolean変数\((x_0, x_1, ..., x_{N-1})\)とし、0 が(false)で1が(true)の場合、この複数ビットの入力\(x\)に対して論理和論理積であらわされる関数\(f(x)\)の出力をTrue(1)にする\(x\)の組み合わせが存在するかを確認する問題です。
例えば、\(f(x_0, x_1) = x_0 \wedge (\neg x_0 \vee \neg x_1)\)に関して、\(\wedge\)はAND(論理積)、\(\vee\)をOR(論理和)、\(\neg\)をNOT(論理否定)とした場合、この式を満たす\(x_0, x_1\)の値は\(x_0=1, x_1=0\)になります。
一方で\(f(x_0) = x_0 \wedge \neg x_0\)を考えた場合、この式を満たす\(x_0\)は存在しません。

このQ#を使ったチュートリアルではSAT問題を2次元の配列であらわします。
1次元目の配列要素のインデックス\(i\)は\(i\)番目の要素\(x_i\)を示し、各要素は(Int, Bool)のタプル形式で表現されます。Intは0, 1の値をとり、Boolはx_iがNOT(論理否定)かそうでないかを表現します。
\(f(x_0, x_1) = x_0 \wedge (\neg x_0 \vee \neg x_1)\)の例では[[(0, true)], [(0, false), (1, false)]]と表され、[]間をつなぐ","はANDで、[]内の()をつなぐ","はORになります。

Part II. Grover's Search Algorithm(グローバーの検索アルゴリズムを使ってみる)

複数ビットの入力に対してシングルビットを返す量子オラクル\(f(x)\)を想定します。入力ビット\(|x\rangle\)に加えて結果を表示するビット\(|y\rangle\)も考慮し、下記のような量子ゲートを想定します。
\(U_f |\vec{x} \rangle |y\rangle = |\vec{x} \rangle |y \oplus f(x) \rangle\)

oracle_Images



Groverの検索アルゴリズムをざっくりと説明すると
1) 量子システムを既知の初期状態にする
2) "Grover iteration"と呼ばれる手順を複数回実行する。各操作ではblack boxの量子オラクルが1回だけ呼ばれる。
3) 量子ビットを観測すると、高い確率で期待した答えを入手することができる。
です。

Exercise 2: Run Grover's algorithm to solve a SAT problem

実際にグローバーのアルゴリズムを使ってSATプログラムを解いてみます。
まずはプログラムに記載されている問題\(f(x_0, x_1) = x_0 \wedge (\neg x_0 \vee \neg x_1)\)をそのまま動かしてみると\(x_0 = True, x_1 = False\)で正しい答えが出てきます。(場合によっては出てこないこともある?)
次に問題を\(f(x) = x_0 \wedge \neg x_0\)に置き換えて実行してみます。問題を置き換えるのは下記のようにproblemとvariableCountの2行だけ入れ替えればよいです。
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Measurement;
open Quantum.Kata.ExploringGroversAlgorithm;

operation RunGroversAlgorithm_SimpleLoop () : Unit {
    let problem = [[(0, true)], [(0, false)]];
    let variableCount = 1;
    
    Message($"Solving SAT problem {SATInstanceAsString(problem)}...");    
    let oracle = CreateOracleForSATInstance(problem);    
    let iterationCount = 1;
    using (register = Qubit[variableCount]) {
        GroversAlgorithm_Loop(register, oracle, iterationCount);
        let assignment = ResultArrayAsBoolArray(MultiM(register));
        Message($"{VariableAssignmentAsString(assignment)}");
        ResetAll(register);
    }
}
この問題は解がないので答えが出るはずがないのですが...
Solving SAT problem (x0) ∧ (¬x0)...
x0 = True
()
なんか出てきましたが、これがどういう意味なのか実行結果を\(y\)を使って観測する必要があります。

Part III. Verifying the algorithm output (結果の観測)

\(|y\rangle\)を用意することで、\(U_f |\vec{x} \rangle |y\rangle = |\vec{x} \rangle |y \oplus f(x) \rangle\)となります。ここでであることを考慮すると
観測ビット\(|y\rangle\)を\(|0\rangle\)としてやることで、\(U_f |\vec{x} \rangle |0\rangle = |\vec{x} \rangle |f(x) \rangle\)となり\(f(x)\)の値をそのまま観測することができるようになります。
\(f(x)\)の値が1であれば解を満たす\(x_i\)の値が存在すること、0であれば解を満たす\(x_i\)の値が存在しないこととなります。
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Measurement;
open Quantum.Kata.ExploringGroversAlgorithm;

operation RunGroversAlgorithm_OutputVerification () : Unit {
    // Use a different SAT instance to get a higher probability of failure
    let problem = [[(0, true), (1, true)], [(0, false), (1, false)]];
    let variableCount = 2;
    
    Message($"Solving SAT problem {SATInstanceAsString(problem)}...");
    
    let oracle = CreateOracleForSATInstance(problem);
    let iterationCount = 1;
    
    using (register = Qubit[variableCount]) {
        GroversAlgorithm_Loop(register, oracle, iterationCount);
        let assignment = ResultArrayAsBoolArray(MultiM(register));
        Message($"{VariableAssignmentAsString(assignment)}");

        // ========================== Check that the answer is correct. ==========================
        
        // Allocate another qubit to keep the result of evaluating the function. 
        // You can allocate a single qubit with the following syntax in using statement: <variable name> = Qubit()
        using (result = Qubit()) {
            // After measuring "register" on line 17, the qubits in "register" end up 
            // in the state that encodes the answer produced by the algorithm (decoded in "assignment" variable).
            // Call oracle with "register" as the first parameter (function input x) 
            // and the newly allocated qubit as the second parameter (function output f(x))
            // to evaluate the function on that answer.
            oracle(register, result);
            
            // Measure the newly allocated qubit and check if the measurement result is Zero or One;
            // One means the algorithm returned correct answer, Zero - incorrect.
            // You can measure the qubit and reset it immediately using MResetZ operation, 
            // and compare the measurement result to the constants Zero or One using "==" operator.
            if (M(result) == One) {
                // Report the correct/incorrect result using Message function.
                Message("Correct");
            } else {
                Message("Incorrect");
            }
            Reset(result);
        }

        ResetAll(register);
    }
}

今回取り扱っている問題は\(f(x_0, x_1) = (x_0 \vee x_1) \wedge (\neg x_0 \vee \neg x_1)\)なので正しい解\(x_0 = True, x_1 = False\)等が存在しますので"Correct"が表示されるべきですが、表示されましたでしょうか?どうも表示されない場合があるようです。

Exercise 4: Re-run Grover's algorithm in case of incorrect answer

正しい結果が得られなかった人もいると思いますし、正しい結果が得られた人もいると思います。
今度は同じプログラムをループを使って正しい結果が出るまで流してみましょう。
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Measurement;
open Quantum.Kata.ExploringGroversAlgorithm;

operation RunGroversAlgorithm_RerunIncorrectAnswer () : Unit {
    // Use a different SAT instance to get a higher probability of failure
    let problem = [[(0, true), (1, true)], [(0, false), (1, false)]];
    let variableCount = 2;
    
    Message($"Solving SAT problem {SATInstanceAsString(problem)}...");
    
    let oracle = CreateOracleForSATInstance(problem);
    let iterationCount = 1;
    
    using (register = Qubit[variableCount]) {
    
        // ======== Use repeat-until-success loop to re-run algorithm in case of a failure ========
        
        // Define a mutable variable to serve as the exit condition.
        mutable isAnswerCorrect = false;
        // Define a mutable variable to store the answer once you've found a correct one.
        mutable finalAnswer = [false, false];
        
        repeat {
            GroversAlgorithm_Loop(register, oracle, iterationCount);
            let assignment = ResultArrayAsBoolArray(MultiM(register));
            Message($"{VariableAssignmentAsString(assignment)}");
            using (result = Qubit()) {
                oracle(register, result);
                if (M(result) == One) {
                    Message("Correct");
                    set isAnswerCorrect = true;
                } else {
                    Message("Incorrect");
                }
                Reset(result);
            }
            ResetAll(register);
        } until (isAnswerCorrect);
        
        // Output the final answer.
        Message($"{finalAnswer}");
    }
}

Part IV. Exploring the success probability of the algorithm (正しい結果が得られる確率について)


ここまでいろんなパターンを流して来たら、正しい結果が観測できる確率が異なることに気づいたと思います。正しい結果が得られる確率は様々な要素によって影響されます。
- 検索スーペースのサイズ(変数の数や問題の式)
- 式を満たす解の種類の数
- グローバーのアルゴリズムを実行する回数

ここでは実際にいろいろな問題に対してグローバーのアルゴリズムを動作させて、実際に正しい結果が観測できる確率を測定してみましょう。
コードの中のコメントに複数の問題が記載されているので、問題を入れ替えていろいろ動かしてみましょう。
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Measurement;
open Quantum.Kata.ExploringGroversAlgorithm;

operation RunGroversAlgorithm_CalculateSuccessProbability () : Unit {
    // Here are the SAT instances with different success probabilities mentioned earlier to get you started
    // 1 solution: [[(0, true)], [(0, false), (1, false)], [(1, true), (2, true)]]
    // 2 solutions: [[(0, true), (1, true)], [(0, false), (1, false)], [(1, true), (2, true)], [(1, false), (2, false)]]
    // 3 solutions: [[(1, false)], [(0, true), (2, true)]]
    // 4 solutions: [[(0, true), (1, true)], [(0, false), (1, false)]]
    let variableCount = 3;
    let problem = [[(1, false)], [(0, true), (2, true)]];
    
    Message($"Solving SAT problem {SATInstanceAsString(problem)}...");
    
    let oracle = CreateOracleForSATInstance(problem);
    let iterationCount = 1;
    
    using (register = Qubit[variableCount]) {
        // Define a mutable variable to store the number of times the algorithm succeeded.
        mutable successCount = 0;
        
        for (i in 1 .. 100) {
            // Loop body: run Grover's search using "GroversAlgorithm_Loop",
            // measure the answer and check whether it is correct.
            // Use "set successCount += 1;" to increment the success counter.
            GroversAlgorithm_Loop(register, oracle, iterationCount);
            let assignment = ResultArrayAsBoolArray(MultiM(register));
            Message($"{VariableAssignmentAsString(assignment)}");
            // Check that the answer is correct. 
            using (result = Qubit()) {
                oracle(register, result);
                if (M(result) == One) {
                    Message("Correct");
                    set successCount += 1;
                } else {
                    Message("Incorrect");
                }
                Reset(result);
            }
            // Remember to reset the qubits in "register" at the end of each iteration!
            ResetAll(register);
        }
        
        // Output the success probability of the algorithm.
        Message($"The algorithm succeeds with {successCount}% probability.");
    }
}


下記のように記載すれば一度にすべてのプログラムを流すことができます。
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Measurement;
open Quantum.Kata.ExploringGroversAlgorithm;

operation RunGroversAlgorithm_CalculateSuccessProbability () : Unit {
    // Here are the SAT instances with different success probabilities mentioned earlier to get you started
    // 1 solution: [[(0, true)], [(0, false), (1, false)], [(1, true), (2, true)]]
    // 2 solutions: [[(0, true), (1, true)], [(0, false), (1, false)], [(1, true), (2, true)], [(1, false), (2, false)]]
    // 3 solutions: [[(1, false)], [(0, true), (2, true)]]
    // 4 solutions: [[(0, true), (1, true)], [(0, false), (1, false)]]

    let problems = [[[(0, true)], [(0, false), (1, false)], [(1, true), (2, true)]], 
                    [[(0, true), (1, true)], [(0, false), (1, false)], [(1, true), (2, true)], [(1, false), (2, false)]],
                    [[(1, false)], [(0, true), (2, true)]],
                    [[(0, true), (1, true)], [(0, false), (1, false)]]
                    ];
    let variableCounts = [3, 3, 3, 2];
    mutable variableIndex = 0;

    for (problem in problems) {
        let variableCount = variableCounts[variableIndex];
        set variableIndex +=1;
    
        Message($"Solving SAT problem {SATInstanceAsString(problem)}...");

        let oracle = CreateOracleForSATInstance(problem);
        let iterationCount = 1;
    
        using (register = Qubit[variableCount]) {
            // Define a mutable variable to store the number of times the algorithm succeeded.
            mutable successCount = 0;
        
            for (i in 1 .. 100) {
                // Loop body: run Grover's search using "GroversAlgorithm_Loop",
                // measure the answer and check whether it is correct.
                // Use "set successCount += 1;" to increment the success counter.
                GroversAlgorithm_Loop(register, oracle, iterationCount);
                let assignment = ResultArrayAsBoolArray(MultiM(register));
                //Message($"{VariableAssignmentAsString(assignment)}");
                // Check that the answer is correct. 
                using (result = Qubit()) {
                    oracle(register, result);
                    if (M(result) == One) {
                        set successCount += 1;
                    }
                    if (i % 10 == 0) {
                        Message($"counter i={i} successCount={successCount}");
                    }
                    Reset(result);
                }
                // Remember to reset the qubits in "register" at the end of each iteration!
                ResetAll(register);
            }
        
            // Output the success probability of the algorithm.
            Message($"The algorithm succeeds with {successCount}% probability.");
        }
    }
}


Demo: Exploring the success probability of Grover's algorithm

 正しい結果が得られる確率について実験するデモはこちらです。


以上でチュートリアルは終わりです。次はKatasにある問題に取り組んでいきたいと思います。

Part IV. Running the Algorithm

今回は全4部のうちの最終回Part 4です。

Deutsch-JozsaのTutorialは4部に分かれています。
Part 1: 問題の定義と古典アルゴリズムについて
Part 2: 量子オラクルについて
Part 3: 量子アルゴリズムについて
Part 4: 実際にDeutsch-Jozsaを量子アルゴリズムで動かしてみる

Fact関数を使って実際に動かしてみる

ここまで来たら難しいことは何もなく、テストコードを書いて動かすだけです。
balancedの関数は期待値をfalseにしてメッセージを修正する必要があります。

open Microsoft.Quantum.Diagnostics;

operation Run_DeutschJozsaAlgorithm () : String {
    // You can use Fact function to check that the return value of DeutschJozsaAlgorithm operation matches the expected value.
    // Uncomment the next line to run it.
    
    Fact(DeutschJozsaAlgorithm(4, PhaseOracle_Zero) == true, "f(x) = 0 not identified as constant");
    
    // Run the algorithm for the rest of the oracles
    Fact(DeutschJozsaAlgorithm(4, PhaseOracle_One) == true, "f(x) = 0 not identified as constant");
    Fact(DeutschJozsaAlgorithm(4, PhaseOracle_Xmod2) == false, "f(x) = 1 not identified as balanced");
    Fact(DeutschJozsaAlgorithm(4, PhaseOracle_OddNumberOfOnes) == false, "f(x) = 1 not identified as balanced");
    Fact(DeutschJozsaAlgorithm(4, PhaseOracle_MostSignificantBit) == false, "f(x) = 1 not identified as balanced");
    
    // If all tests pass, report success!
    return "Success!";
}




Part III. Quantum Algorithm

今回は全4部のうちのPart 3です。

Deutsch-JozsaのTutorialは4部に分かれています。
Part 1: 問題の定義と古典アルゴリズムについて
Part 2: 量子オラクルについて
Part 3: 量子アルゴリズムについて
Part 4: 実際にDeutsch-Jozsaを量子アルゴリズムで動かしてみる

Deutsch–Jozsa Algorithm問題定義

もう一度Deutsch–Jozsaの問題定義をおさらいしておきます。
Deutsch-Jozsaアルゴリズムとは中身のわからないブラックボックスな関数\(f(x)\)について、
1) すべての入力\(x\)に対して、常に0を返す、あるいは、常に1を返す場合、constant
2) 入力\(x\)に対して50%ずつの確率で0と1をそれぞれ返す場合balanced
と関数の性質を判定するアルゴリズムです。

古典コンピューターの場合、関数の性質を判定するのに最悪の場合、総当たり的に入力を与えないと解が求まらないのに対して、量子コンピュータを使うと多項式時間で解が求まることを検証していきます。

Deutsch–Jozsa Algorithm概要

アルゴリズムで実施すること自体はシンプルです。
  1. Apply the H gate to each qubit.
    各ビットにアダマールゲートを適用します。
  2. Apply the oracle.
    量子オラクルを適用します。
  3. Apply the H gate to each qubit again.
    各ビットに再度アダマールゲートを適用します。
  4. Measure each qubits.
    各ビットを測定します。
  5. If all qubits were measured in  |0⟩  state, the function is constant, otherwise it is balanced.
    全ビットが\(|0\rangle\)に読めれば関数\(f(x)\)はconstant, 1つでも\(|1\rangle\)が読めればbalancedと判断
なぜこの手順で判断できるのかを考えてみましょう。

Deutsch–Jozsa Algorithm詳細

1) Apply the H gate to each qubit. 各ビットにアダマールゲートを適用


入力を\(\frac{1}{\sqrt2} \big(|0\rangle + |1\rangle \big)\)の状態に変換する。この形は\(|0\rangle\)と\(|1\rangle\)を同じ確率で取りうる重ね合わせ状態

2Qubitの\(|00\rangle\)にアダマール変換をかけた場合 \[(H \otimes H) |00\rangle = \big(H |0\rangle \big) \otimes \big(H |0\rangle\big) = \left(\frac{1}{\sqrt2} \big(|0\rangle + |1\rangle \big)\right) \otimes \left(\frac{1}{\sqrt2} \big(|0\rangle + |1\rangle \big)\right) = \frac{1}{2} \big(|00\rangle + |01\rangle + |10\rangle + |11\rangle \big)\] となる。これはすべてのbasis stateを同じ確率で重ね合わせた状態である。

これをもとにN-Qubitの場合を考えると
\[ H^{\otimes N} |0\rangle^{\otimes N} = \big( H|0\rangle \big)^{\otimes N} = \left( \frac{1}{\sqrt2} \big(|0\rangle + |1\rangle \big) \right)^{\otimes N} = \frac{1}{\sqrt{2^N}} \sum_{x=0}^{2^N-1} |x\rangle = \frac{1}{\sqrt{2^N}} \big(|0\rangle + |1\rangle + |2\rangle + \ldots +|2^N- 1\rangle \big) = \frac{1}{\sqrt{2^N}} \big(|x_0\rangle \otimes |x_1\rangle \otimes |x_2\rangle \otimes \ldots \otimes |x_N\rangle \big) \]
となる。 
\(\sum\)で書かれると一気によくわからなくなるが、ここは0から\(2^N-1\)までの重ね合わせ状態とばらして考えればよく、各ビットのテンソル積にばらせる場合は0からNビット目までのテンソル積にばらすのと同等と考えてよい。

2) Apply the oracle. 量子オラクルを適用

ここで\(|x\rangle\)にOracleをかける
\[U_f \left(\frac{1}{\sqrt{2^N}} \sum_{x=0}^{2^N-1} |x\rangle \right) = \frac{1}{\sqrt{2^N}} \sum_{x=0}^{2^N-1} U_f|x\rangle = \frac{1}{\sqrt{2^N}} \sum_{x=0}^{2^N-1} (-1)^{f(x)} |x\rangle \]


3) Apply the H gate to each qubit again. 各ビットに再度アダマールゲートを適用


\[ \begin{align}\dfrac{1}{2^N} \sum_{x=0}^{2^N-1} (-1)^{f(x)} \sum_{y=0}^{2^N-1} (-1)^{x \cdot y} \lvert y \rangle = \sum_{y=0}^{2^N-1} \left( \dfrac{1}{2^N} \sum_{x=0}^{2^N-1} (-1)^{f(x)} (-1)^{x \cdot y} \right) \lvert y \rangle\end{align} \]

4) Measure each qubits. 各ビットを測定します。

全N-Qubitを観測して、\({\lvert 0 \rangle ^{\otimes N}}\)が読める確率を考えてみます。
\[\left( \dfrac{1}{2^N} \sum_{x=0}^{2^N-1} (-1)^{f(x)} (-1)^{x \cdot y} \right) \lvert y \rangle^{\otimes N}\]
と考えて、係数の部分に着目し\(|y\rangle={\lvert 0 \rangle ^{\otimes N}}\)=のケースを考えます。
\(x \cdot y\)は\(x\)と\(y\)のBitwise Inner Productで、単純に\(y_N = |0\rangle\)の場合、\(x \times 0\)で0と考えてよいです。つまり、
\[\left( \dfrac{1}{2^N} \sum_{x=0}^{2^N-1} (-1)^{f(x)} \right) \lvert 0 \rangle^{\otimes N}
=\left( \dfrac{1}{2^N} \sum_{x=0}^{2^N-1} (-1)^{f(x)} \right) \left( \lvert 0 \rangle \otimes \lvert 0 \rangle \otimes\ldots \lvert 0 \rangle\right) \]
となり、\(f(x)\)がconstantの場合係数が1または-1、つまり\({\lvert 0 \rangle ^{\otimes n}}\)が読める確率が100%(\(1^2=1, -1^2=1\))、\(f(x)\)がbalancedの場合係数が0、\({\lvert 0 \rangle ^{\otimes N}}\)が読める確率は0%(\(0^2=1\))となります。

Exercise 4: Implement the quantum algorithm!


ここまで来たらあとの実装は簡単だと思います。

operation DeutschJozsaAlgorithm (N : Int, oracle : (Qubit[] => Unit)) : Bool {
    // Create a boolean variable for storing the return value.
    // You'll need to update it later, so it has to be declared as mutable.
    mutable isConstant = true;

    // Allocate an array of N qubits for the input register x.
    using (x = Qubit[N]) {
        // Newly allocated qubits start in the |0⟩ state.
        // The first step is to prepare the qubits in the required state before calling the oracle.
        // A qubit can be transformed from the |0⟩ state to the |+⟩ state by applying a Hadamard gate H.
        ApplyToEach(H, x);

        // Apply the oracle to the input register.
        // The syntax is the same as for applying any function or operation.
        oracle(x);

        // Apply a Hadamard gate to each qubit of the input register again.
        ApplyToEach(H, x);

        // Measure each qubit of the input register in the computational basis using the M operation.
        // You can use a for loop to iterate over the range of indexes 0..N-1.
        // Note that you can't return the answer in the middle of a loop,
        // you have to update the variable isConstant using the "set" keyword.
        for (i in 0 .. N-1) {
            let a = M(x[i]);
            if (a == One) {
                set isConstant = false;
                ResetAll(x);
            }
        }

        // Before releasing the qubits make sure they are all in the |0⟩ state
        // (otherwise you'll get a ReleasedQubitsAreNotInZeroState exception).
        // You can use the library operation Reset which measures a qubit and applies a correction if necessary.
        // The library operation ResetAll does the same for a register of qubits.
        ResetAll(x);
    }
    
    // Return the value of the boolean variable.
    return isConstant;
}



疑問

CNOTゲートは、制御ビットの状態を判断してターゲットビットにNOTをかける操作です。制御ビットとターゲットビットをそれぞれ単独のQubitに分離可能な場合は、簡単に理解できるのですが、Bell Basis\(\frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ 0 \\ 0 \\ 1 \end{bmatrix}\)のような分離不可能な状態にCNOTをかける場合どのように考えればいいのでしょう?
CNOTゲートの説明を見てるとEntangled(量子もつれ)状態のQubitにもCNOTをかけれると書いてありますが、考え方がよくわかりません。

Experiment with Q# QDK Simulator

とりあえず、Q# QDKのSimulatorを使ってどのように動くかを見てみます。
まず、2 Qubit用意し、ビット0にアダマール変換をかけて、ビット0を制御ビット、ビット1をターゲットビットにしてCNOTをかけることでBell Basisを作ります。

TestOperations.qs
namespace TestOperations
{
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;

    operation EntangledCnot () : Unit {
        using (x = Qubit[2]){
            H(x[0]);
            CNOT(x[0],x[1]); 
	    // 2Qbits are Bell basis and entangled
            DumpRegister((), [x[0]]);
            DumpRegister((), [x[1]]);
            DumpMachine();
	    // 2Qbits are Bell basis and entangled
            CNOT(x[0],x[1]);
            DumpMachine();
            ResetAll(x);
        }
    }
}

最初のCNOTをかけた後に2つのQubitの状態をDumpRegister()とDumpMachineで見てみます。
DumpRegisterはQubitの状態を1ビットずつ見る機能で、DumpMachineは全体のQubitの状態を見る機能です。
この時点で2つのQubitはBell Basisになっており、Bell BasisはEntangled状態なのでDumpRegisterは1Qubitずつに分けて状態を見ることはできないと返ってきます。これは期待通りの動作です。
DumpMachineではBell Basisになっていることを確認できます。
-----DumpRegister---------------->
# wave function for qubits with ids (least to most significant): 0
## Qubits were entangled with an external qubit. Cannot dump corresponding wave function. ##
# wave function for qubits with ids (least to most significant): 1
## Qubits were entangled with an external qubit. Cannot dump corresponding wave function. ##
-----DumpMachine----------------->
# wave function for qubits with ids (least to most significant): 0;1
∣0❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]
∣1❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣2❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
∣3❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]


次にこの状態でさらにCNOTをかけてみます。
-----DumpRegister---------------->
# wave function for qubits with ids (least to most significant): 0
∣0❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]
∣1❭:     0.707107 +  0.000000 i  ==     ***********          [ 0.500000 ]     --- [  0.00000 rad ]
# wave function for qubits with ids (least to most significant): 1
∣0❭:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
∣1❭:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
-----DumpMachine----------------->
# wave function for qubits with ids (least to most significant): 0;1 ∣0❭: 0.707107 + 0.000000 i == *********** [ 0.500000 ] --- [ 0.00000 rad ] ∣1❭: 0.707107 + 0.000000 i == *********** [ 0.500000 ] --- [ 0.00000 rad ] ∣2❭: 0.000000 + 0.000000 i == [ 0.000000 ] ∣3❭: 0.000000 + 0.000000 i == [ 0.000000 ]

そうすると、最初のCNOTをかける前の状態に戻りました。

基本的な考え方 

量子もつれ状態のQubitにCNOTをかける場合は、テンソル積に分解することを考える前にcomputationla basisの足し算に分解します。今回の場合Bell basisは下記のように表現できます。

\[|\psi\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)\]
この状態にそれぞれCNOTをかけてやります。\(|00\rangle\)では何も起こらず\(|11\rangle\)にはNOTが働くので下記の結果になります。
\[CNOT(\frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)) = \frac{1}{\sqrt{2}}(|00\rangle + |10\rangle) \]




Part II. Quantum Oraclesの解説


今回は全4部のうちのPart 2です。

Deutsch-JozsaのTutorialは4部に分かれています。
Part 1: 問題の定義と古典アルゴリズムについて
Part 2: 量子オラクルについて
Part 3: 量子アルゴリズムについて
Part 4: 実際にDeutsch-Jozsaを量子アルゴリズムで動かしてみる

自分で書いていて怪しい部分もあるので、間違い等あれば指摘していただけると助かります。

Quantum Oracle(量子オラクル)とは

Oracleを普通に辞書で調べても「神託」とかデータベースのオラクルとか、ここではよくわからない翻訳しか出てこなくてよくわかりません。
量子コンピュータでのオラクルとは
QuantumKatasからの引用
A quantum oracle is a "black box" operation which is used as input to another algorithm. This operation is implemented in a way which allows to perform calculations not only on individual inputs, but also on superpositions of inputs.
Oracles are often defined using a classical function, in the case of Deutsch-Jozsa algorithm the function  \(f : \{0, 1\}^N \to \{0, 1\}\) takes an  \(N\)-bit binary input and produces an 1-bit binary output.
The oracle has to act on quantum states instead of classical values. To enable this, integer input  \(x\) is represented in binary \(x = (x_{0}, x_{1}, \dots, x_{N-1})\) and encoded into an \(N\)-qubit register: \(|\vec{x} \rangle = |x_{0} \rangle \otimes |x_{1} \rangle \otimes \cdots \otimes |x_{N-1} \rangle\)
その他参考リンク

\(f : \{0, 1\}^N \to \{0, 1\}^M\)はN-bitのバイナリデータを入力としM-bitのバイナリデータを出力とするという意味で、Deutsch–JozsaではM=1のケースを扱います。
N-bitのバイナリデータは\(|\vec{x} \rangle = |x_{0} \rangle \otimes |x_{1} \rangle \otimes \cdots \otimes |x_{N-1} \rangle\)で、各ビット(\(x_{0}\)など)は\(|0\rangle\)か\(|1\rangle\)のみをとり、そのテンソル積が入力となります。

Oracleの考え方は出力ビットyを用いた方法Phase Oracleを用いた方法と2つありますが、 このチュートリアルでは \(U_f |\vec{x} \rangle = (-1)^{f(x)} |\vec{x} \rangle\) を基本としたPhase Oracleを中心に考えます。ここでは\(U_f |x\rangle\)のOperationを作ることを考えます。 \(f(x)\)は0か1しかとらないので、\(x=0\)の場合は、\(-1^0=1\)となり位相の変化なし、\(x=0\)の場合は\(-1^1=-1\)となり位相の変化が発生します。

これだけ見るとよくわからないですが、実際にDeutsch algorithmを適用した場合に
\(U_f \left( \frac{1}{\sqrt2} \big( |0\rangle + |1\rangle \big) \right) = \frac{1}{\sqrt2} \big( U_f |0\rangle + U_f |1\rangle \big) = \frac{1}{\sqrt2} \big( (-1)^{f(0)} |0\rangle + (-1)^{f(1)} |1\rangle \big) \)
となり
\(f(0) = f(1)\)の場合すべてのbasis stateの位相が同じとなり、最終的な結果が\(|+\rangle = \frac{1}{\sqrt2} \big( |0\rangle + |1\rangle \big)\)となります。
\(f(0) \neq f(1)\)の場合、\(-1^0\)と\(-1^1\)で位相が異なり、最終的な結果が \(|-\rangle = \frac{1}{\sqrt2} \big( |0\rangle - |1\rangle \big)\)となります。

\(|+\rangle\)と \(|-\rangle\)この状態はアダマール変換をかけるとそれぞれ\(|0\rangle\)と\(|1\rangle\)になるため、観測による状態判定可能になります。つまり、\(f(x)\)がConstantな場合は最終的に0が観測でき、Balancedな場合は1が観測できます。

Exercise 3: Implement a quantum oracle in Q#

Most Significant Bitが1の場合、そのbasis stateに-1のPhaseを与えるという問題。
\(f(x) = x_{0}\), となり、
\[U_f = Z \otimes \cdots \otimes \mathbb{1} \otimes \mathbb{1}\]
\[U_f |x\rangle = (-1)^{f(x)} |x\rangle = (-1)^{x_{0}} |x\rangle = (-1)^{x_{0}}|x_{0} \rangle \otimes \cdots \otimes |x_{N-2} \rangle \otimes |x_{N-1}\rangle\]
となります。
%kata E3_QuantumOracle_Test

operation PhaseOracle_MostSignificantBit (x : Qubit[]) : Unit {
    Z(x[0]);
}

↑このページのトップヘ