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

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

December 2019

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]);
}

Part I. Problem Statement and Classical Algorithm解説

Deutsch-Jozsaアルゴリズム(ドイチェジョザアルゴリズム)に関するチュートリアルです。Deutsch-Jozsaアルゴリズムは量子コンピューティングの世界では非常に有名なアルゴリズムで、量子コンピューターの有益性を理解するためには良い題材です。ただ実際の世界においてはあまり有効に活用できるようなロジックではないためここでは量子コンピュータの優位性を理解することにフォーカスします。

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

「Part 1: 問題の定義と古典アルゴリズムについて」は
Deutsch-Jozsaが解決しようとする問題を理解し、古典コンピューターで解こうとした場合の問題点を理解します。

Deutsch–Jozsa Algorithmついて

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

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

Q#の基本的な文法について

この辺りからQ#の基本的な文法についてある程度知っておかないと、if文やfor/whileループの書き方なに戸惑ってしまいます。
Q#の基本的な文法についてはこちらにまとめています。


Exercise 1: Implement a classical function in Q#


Most Significant Bitが0か1かを判定する関数を作成します。

%kata E1_ClassicalFunction_Test 

function Function_MostSignificantBit (x : Int, N : Int) : Int {
    let msb = x >>> (N-1);
    return msb;
}

古典アルゴリズムについて

古典コンピュータの場合、組み合わせを求めるためにループを回す必要があり計算量が\(2^n\)のオーダーになります。実際にプログラムを書いてみて確かめてみましょう。

Exercise 2: Implement the classical algorithm!


ここでは古典コンピュータの古典的なアルゴリズムを使って関数\(f(x)\)がConstantかBalancedかを判定する処理を作ります。ループを\(2^{(n-1)}\)回まわす処理が必要で、前提条件からループが回りきらせる必要はないですが、Constantと判断するには最低でも全入力の51%\((n^{(n/2 +1)}回)\)は試す必要があります。

%kata E2_ClassicalAlgorithm_Test 

operation IsFunctionConstant_Classical (N : Int, f : (Int -> Int)) : Bool {
    mutable ret = true;
    let ret0 = f(0);
    for (i in 1 .. 1 .. (2^N - 1)) {
        let ret_i = f(i);
        if (ret0 != ret_i){
            set ret = false;
            return ret;
        }
    }
    return ret;
}

変数の代入

Q#で変数(mutableな変数)を使用する場合、mutableで宣言する必要があります。
        mutable i = 0;
        set i = 1;
mutableで変数を宣言した後にsetを使用して変数を変更することができます。

letで定義してしまうとimmutableな変数となってしまい、エラーが出ます。
        let i = 0;                                                                      Message ($"{i}");
        set i = 1;

QS6303: An immutable identifier cannot be modified.

メッセージ表示

Q#にはprint, printfやC#にあるConsole.WriteLine関数はありません。
コンソールにメッセージを表示する場合はMessage関数を使います。

準備

"Microsoft.Quantum.Diagnostics"をオープンします。
open Microsoft.Quantum.Diagnostics;

文字列の表示

文字列の表示の場合は単純にMessage関数の引数に文字列を渡します。
Message("test");

変数の値の表示

変数の値を表示する場合文字列の手前に $ を付け、表示したい変数を文字列の中で {}付きで挿入します。.
    let i = 1;
    Message($"Display i={i}");
This shows below.
Display i=1

For ループ

forループの引数の記載の方法は "start .. step .. end"で書きます。
この場合 iは初期値1 から1ずつ増加していき、10まで進みます。
    for (i in 1 .. 1 .. 5 ) {
        Message ($"{i}");
    }

実行結果はこのようになります。
1
2
3
4
5


 


チュートリアル Multi-Qubit Gates解説および解答例

Github
https://github.com/microsoft/QuantumKatas/tree/master/tutorials/MultiQubitGates

(いきなりこのページに飛んできた人で最初から勉強されたい方はこちらをご参照ください)

いよいよ基礎的な学習は最終回です。今回はMulti-Qubit Gatesということで、複数のQubitに対するオペレーションを学習していきます。

Exercise 1: Compound Gate

3Qubitに対するオペレーションを作成します。 8x8の行列で3つのQubitに対する操作を表していますが、これを3つのSingle Qubitに対する操作に分割します。

まず
- 右上のquadrant(4x4)と左下のquadrant(4x4)がゼロになっていること
- 左上のquadrant(4x4)と右下のquadrant(4x4)がiをかけた関係になっていること
に着目し
\[ Q = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix} \otimes \begin{bmatrix} 0 & -i & 0 & 0 \\ i & 0 & 0 & 0 \\ 0 & 0 & 0 & -i \\ 0 & 0 & i & 0 \end{bmatrix} \]
を導き出します。さらに
- 右上のquadrant(2x2)と左下のquadrant(4x4)がゼロになっていること
- 左上のquadrant(2x2)と右下のquadrant(4x4)が同じになっていること
から
\[ Q = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix} \otimes \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \otimes \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} = S \otimes I \otimes Y \]
3つのSingle Bitのオペレーションに分解することができました。

%kata T1_CompoundGate_Test

operation CompoundGate (qs : Qubit[]) : Unit is Adj {
    S(qs[0]);
    I(qs[1]);
    Y(qs[2]);
}
(参考)テンソル積の簡単なイメージ \[ \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \otimes \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 1 \cdot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} & 2 \cdot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} \\ 3 \cdot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} & 4 \cdot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} \end{bmatrix} \] 

Exercise 2: Preparing a Bell state

CNOTゲートの使い方に関する学習です。CNOTゲートは2Qubitゲートで、最初のビットの状態が\(|1\rangle\)であれば、2Qubit目を反転させます。

解説にあるようにCNOTゲートは量子もつれ状態を作るのに有効です。
\[ \big(\alpha|0\rangle + \beta|1\rangle\big) \otimes |0\rangle = \alpha|00\rangle + \beta|10\rangle \] この状態にCNOTをかけると
\[ \alpha|00\rangle + \beta|11\rangle \] ができます。

ここではBell Stateを作り出すことが目的ですが、Bell Steteは不可分な量子もつれ状態ですので、1Qubitずつの操作に分解することはできません。
\[ \Phi^+ = \frac{1}{\sqrt{2}}\big(|00\rangle + |11\rangle\big) = \frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ 0 \\ 0 \\ 1
\end{bmatrix} \]

この問題では
\[ \frac{1}{\sqrt{2}}\big(|00\rangle + |10\rangle\big) = \frac{1}{\sqrt{2}}\big(|0\rangle + |1\rangle\big) \otimes |0\rangle \] という状態を作り出しCNOTゲートを適用させます。
\( \frac{1}{\sqrt{2}}\big(|0\rangle + |1\rangle\big) \)はHゲートで作り出すことができますので、HゲートとCNOTゲートで対応できます。
%kata T2_BellState_Test

operation BellState (qs : Qubit[]) : Unit is Adj {
    H(qs[0]);
    CNOT(qs[0],qs[1]);
}

Exercise 3: Swapping two qubits

SWAPゲートも2Qubitに対する同時操作です。仕様もシンプルでチュートリアルの解答はすぐにわかりますが、使用用途がまだよくわかりません。
%kata T3_QubitSwap_Test

operation QubitSwap (qs : Qubit[], index1 : Int, index2 : Int) : Unit is Adj {
    SWAP(qs[index1], qs[index2]);
}

Exercise 4: Controlled Rotation

Controlledは指定した制御ビットの状態を見て、ターゲットビットに操作を加えるオペレーションです。オペレーションはSingle Bitのオペレーションも使用できますしSWAPのような2ビットオペレーションも使用できます。
// Apply X gate to target bit
Controlled
X([control], target);
// Apply SWAP to targets
Controlled
SWAP([control], (q1, q2));
第一引数の制御ビットを[]でくくらないといけないというのにはまりました。制御ビットは配列で複数指定できるのでそのようになっているようです。

%kata T4_ControlledRotation_Test

operation ControlledRotation (qs : Qubit[], theta : Double) : Unit is Adj {
    Controlled Rx([qs[0]], (theta, qs[1]));
}

Exercise 5: Arbitrary controls

ControlledOnBitStringは複数の制御ビットに対して任意(True or False)の判定を行い、条件が一致する場合指定されたオペレーションを実行します。
Q#の文法の説明が少なくてよくわからないですね。
%kata T5_MultiControls_Test

operation MultiControls (controls : Qubit[], target : Qubit, controlBits : Bool[]) : Unit is Adj {
     (ControlledOnBitString(controlBits, X))(controls, target);
}


チュートリアル Multi-Qubit Systems解説および解答例

Githubリンク
https://github.com/microsoft/QuantumKatas/tree/master/tutorials/MultiQubitSystems

(いきなりこのページに飛んできた人で最初から勉強されたい方はこちらをご参照ください)

前回は1Qubitの操作を学んできました、今回は複数Qubit(主に2Qubit)の操作を学習していきます。

ポイント

複数ビットになって数字の数が増えて複雑になっていますが、ここでのポイントは複数ビットの状態は単独ビットのテンソル積の状態に分割可能な場合があり、今回の学習では基本的に分割できる問題ばかりなので、まずは分割してどのような状態にすればよいかを考えていきます。(テンソル積はLinear Algebra Part 2で出てきていますのでお忘れの方はこちらで復習を。)

一般的に書くと下記のようになります。
\[ \begin{bmatrix} \alpha \color{red}\gamma \\ \alpha \color{red}\delta \\ \beta \color{red}\gamma \\ \beta \color{red}\delta \end{bmatrix} = \begin{bmatrix} \alpha \\ \beta \end{bmatrix} \otimes \begin{bmatrix} \color{red}\gamma \\ \color{red}\delta \end{bmatrix} \]


Exercise 1: Show that the state is separable

自分が悩んだポイントを補足しておきます。
\[ \frac{1}{2} \begin{bmatrix} 1 \\ i \\ -i \\ 1 \end{bmatrix} = \begin{bmatrix} ? \\ ? \end{bmatrix} \otimes \begin{bmatrix} ? \\ ? \end{bmatrix} \]
これを上記の一般式に当てはめると
\[ \begin{cases} \alpha\gamma = \frac{1}{2} \\ \alpha\delta = \frac{i}{2} \\ \beta \gamma = \frac{-i}{2} \\ \beta \delta = \frac{1}{2} \\ \end{cases} \]
となります。
さらにこれらを解いていくと \( \frac{\alpha}{\beta} = \frac{-1}{i} = i \) となります。
ここで \( |\alpha|^2 + |\beta|^2 = 1 \)を考慮すると
\[ \alpha = \frac{1}{\sqrt2}, \beta = \frac{-i}{\sqrt2}, \gamma = \frac{1}{\sqrt2}, \delta = \frac{i}{\sqrt2} \] となり解を導くことができます。 問題文には出てきませんが、Qubitの条件 \( |\alpha|^2 + |\beta|^2 = 1 \)を考慮することで解を導くことができます。

いろいろ計算が複雑ですが、ここではヒントもあるのでまずは下記の4つの活用を考えればよいと思います。
\[ \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix},   \begin{bmatrix} 0 \\ 1 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix},   \begin{bmatrix} 0 \\ 0 \\ 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix},  \begin{bmatrix} 0 \\ 0 \\ 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix} \]

Demo解説

解説を読んでるだけではわからないところも多いので、実際にDemoを動かしてみて理解を深めていきましょう。

量子ビットの配置

Qubitを配列で配置します。初期状態は|00>です。
    // This allocates an array of 2 qubits, each of them in state |0⟩.
    // The overall state of the system is |00⟩
    using (qs = Qubit[2]) {
つまり
\[ \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} \]
という状態です。

1Qubit目の反転

qs[0]=1ビット目をXゲートで反転させます。
        // X gate changes the first qubit into state |1⟩
        // The entire system is now in state |10⟩
        X(qs[0]);
\[ \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} \]
の状態の1ビット目を反転させるので
\[ \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 1\\ 0 \\ 0 \end{bmatrix}  \]
になります。
結果は
System in state |10⟩ = |1⟩:
# wave function for qubits with ids (least to most significant): 0;1
∣0❭:	 0.000000 +  0.000000 i	 == 	                     [ 0.000000 ]                   
∣1❭:	 1.000000 +  0.000000 i	 == 	******************** [ 1.000000 ]     --- [  0.00000 rad ]
∣2❭:	 0.000000 +  0.000000 i	 == 	                     [ 0.000000 ]                   
∣3❭:	 0.000000 +  0.000000 i	 == 	                     [ 0.000000 ]        
コメントが"System in state |10⟩ = |1⟩:"になっていますが、"System in state |10⟩ = |10⟩:"のほうが正確ですかね。

アダマールゲート適用

2ビット目にアダマールゲートを適用します。
        // This changes the second qubit into state |+⟩ = (1/sqrt(2))(|0⟩ + |1⟩).
        // The entire system is now in state (1/sqrt(2))(|10⟩ + |11⟩)
        H(qs[1]);
\[ \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ 1 \\ 0 \end{bmatrix}  \]
の状態の2ビット目にHゲートを適用するので2ビット目が
\( H|1\rangle = |+\rangle = \frac{1}{\sqrt{2}}\big(|0\rangle + |1\rangle\big) \)
となり
\[ \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\ 1 \end{bmatrix} = \frac{1}{\sqrt{2}}\begin{bmatrix} 0 \\ 1 \\ 0 \\ 1 \end{bmatrix}  \]
となります。
System in state (1/sqrt(2))(|10⟩ + |11⟩) = (1/sqrt(2))(|1⟩ + |3⟩):
# wave function for qubits with ids (least to most significant): 0;1
∣0❭:	 0.000000 +  0.000000 i	 == 	                     [ 0.000000 ]                   
∣1❭:	 0.707107 +  0.000000 i	 == 	***********          [ 0.500000 ]     --- [  0.00000 rad ]
∣2❭:	 0.000000 +  0.000000 i	 == 	                     [ 0.000000 ]                   
∣3❭:	 0.707107 +  0.000000 i	 == 	***********          [ 0.500000 ]     --- [  0.00000 rad ]

1ビット目にもアダマールゲート適用

1ビット目は|1>なので|->になるので、
        // This changes the first qubit into state |-⟩ = (1/sqrt(2))(|0⟩ - |1⟩)
        // The entire system is now in state 0.5(|00⟩ + |01⟩ - |10⟩ - |11⟩)
        H(qs[0]);
\[ \frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\ -1 \end{bmatrix} \otimes \frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\ 1 \end{bmatrix} = \frac{1}{2}\begin{bmatrix} 1 \\ -1 \\ 1 \\ -1 \end{bmatrix}  \]
となります。
System in state 0.5(|00⟩ + |01⟩ - |10⟩ - |11⟩) = 0.5(|0⟩ + |2⟩ - |1⟩ - |3⟩):
# wave function for qubits with ids (least to most significant): 0;1
∣0❭:	 0.500000 +  0.000000 i	 == 	******               [ 0.250000 ]     --- [  0.00000 rad ]
∣1❭:	-0.500000 +  0.000000 i	 == 	******               [ 0.250000 ] ---     [  3.14159 rad ]
∣2❭:	 0.500000 +  0.000000 i	 == 	******               [ 0.250000 ]     --- [  0.00000 rad ]
∣3❭:	-0.500000 +  0.000000 i	 == 	******               [ 0.250000 ] ---     [  3.14159 rad ]  

量子もつれの生成

ここまではQubitを分解してそれぞれのQubitの状態を変化させテンソル積で複数Qubitの状態を表してきましたが、ここではEntangle状態(分割不可)の状態を作り出します。
1ビットずつの操作ではなく2ビットまとめて操作することでEntangle状態を作り出すことができます。
        // The next lines entangle the qubits.
        // Don't worry about what exactly they do for now
        H(qs[1]);
        CNOT(qs[0], qs[1]);
CNOTゲートはのちのチュートリアルで出てくるのでここでは細かい説明を省略しますが、1ビット目の状態を見て、2ビット目を反転させる処理になります。
Entangled state 0.5(|00⟩ - |11⟩):
# 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 ] ---     [  3.14159 rad ]
この状態はExercise 2で出てきた不可分な状態(量子もつれ状態)ですね。

Exercise 3: Prepare a basis state

デモの内容が理解できれば簡単だと思います。
初期状態は分解可能なので
\( \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} =
\begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} \) から  \( \begin{bmatrix} 0 \\ 0 \\ 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix} \)
を作り出します。 それぞれのビットを反転させるだけですね。
%kata T1_PrepareState1_Test

operation PrepareState1 (qs : Qubit[]) : Unit is Adj+Ctl {
    X(qs[0]);
    X(qs[1]);
}

Exercise 4: Prepare a superposition of two basis states

ヒントをみて
\[ |0\rangle \otimes \frac{1}{\sqrt2}\big(|0\rangle - |1\rangle\big) = \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \frac{1}{\sqrt2}\begin{bmatrix} 1 \\ -1 \end{bmatrix} \]
を作り出すことを理解すればそれほど難しくありません。
2ビット目を\(|-\rangle \)にすればよいので、Xゲート、Hゲートの順に適用します。
%kata T2_PrepareState2_Test

operation PrepareState2 (qs : Qubit[]) : Unit is Adj+Ctl {
    X(qs[1]);
    H(qs[1]);
}

Exercise 5: Prepare a superposition with real amplitudes

この問題もヒントをみて
\[ \frac{1}{\sqrt2}\big(|0\rangle + |1\rangle\big) \otimes \frac{1}{\sqrt2}\big(|0\rangle - |1\rangle\big) = \frac{1}{\sqrt2} \begin{bmatrix} 1 \\ 1 \end{bmatrix} \otimes \frac{1}{\sqrt2}\begin{bmatrix} 1 \\ -1 \end{bmatrix} \]
を作り出すことがわかれば簡単です。2ビット目は先ほどのExercise 4と同じなので1ビット目を \(|+\rangle \) にするためにHゲートを適用します。
%kata T3_PrepareState3_Test

operation PrepareState3 (qs : Qubit[]) : Unit is Adj+Ctl {
    H(qs[0]);
    X(qs[1]);
    H(qs[1]);
}

Exercise 6: Prepare a superposition with complex amplitudes

ヒントに書かれていることを導き出すのが難しいかもしれませんが、ヒントがあるのでヒントに書かれていることを実装すればよいです。それぞれ\(|+\rangle \)にSゲート、Tゲートを適用すればよいです。
%kata T4_PrepareState4_Test

operation PrepareState4 (qs : Qubit[]) : Unit is Adj+Ctl {
    H(qs[0]);
    S(qs[0]);
    H(qs[1]);
    T(qs[1]);
}


チュートリアル Single-Qubit Gates解答例

Githubリンク
https://github.com/microsoft/QuantumKatas/tree/master/tutorials/SingleQubitGates

(いきなりこのページに飛んできた人で最初から勉強されたい方はこちらをご参照ください)

このチュートリアルは実際にQ#を使ってQubitの値を操作していきます。古典コンピュータで言うAND, OR, ビットシフトみたいな位置づけだと思いますが、古典コンピュータよりもけっこう複雑ですね。

Exercise 1: The  𝑌  gate

α|0> + β|1>にYゲートを適用します。
これはそのままですね。戻り値はUnitなので何も戻す必要がありません。

%kata T1_ApplyY_Test

operation ApplyY (q : Qubit) : Unit is Adj+Ctl {
    Y(q);
}

Exercise 2: Applying a global phase  𝑖

これも実は本チュートリアルにあるDemoの動き通りですね。
XYZゲートの動きをきちんと理解しましょう。
%kata T2_GlobalPhaseI_Test

operation GlobalPhaseI (q : Qubit) : Unit is Adj+Ctl {
    X(q);
    Z(q);
    Y(q);
}

Exercise 3*: Applying a  −1  phase to  |0⟩  state

XYZゲートの動きを理解して、何を組み合わせれば目的のα=-1とβ=1を導けるかを考えます。
%kata T3_SignFlipOnZero_Test

operation SignFlipOnZero (q : Qubit) : Unit is Adj+Ctl {
    X(q);
    Z(q);
    X(q);
}

Exercise 4: Preparing a  |−⟩  state

前回のQubitで紹介しましたが\( i = \ e^{i\pi/2}  \)なので、Sゲートは\( \pi/2 \)ずつPhaseをずらしていきます。順に適用していくと |+> ⇒ |-> ⇒ |i> ⇒ |-i> ⇒ |+> という順に状態が変化します。こちらを理解していれば簡単です。
まずは|0>から|+>を作るためにHゲートを使用し、あとはシフトさせていきます。
%kata T4_PrepareMinus_Test

operation PrepareMinus (q : Qubit) : Unit is Adj+Ctl {
    H(q);
    S(q);
    S(q);
}

Exercise 5: Three-fourths phase

Phase Shiftゲートの動きを理解しましょう。
%kata T5_ThreeQuatersPiPhase_Test

operation ThreeQuatersPiPhase (q : Qubit) : Unit is Adj+Ctl {
    S(q);
    T(q);
}

理論上は下記でも同等です。
\[ e^{i\pi/4} = \frac{1}{\sqrt{2}} + \frac{i}{\sqrt{2}} \\ e^{i\pi/2} = i \\ e^{i\pi} = -1 \\ e^{2i\pi} = 1 \]
であることを考えると、Tゲート2回とSゲート1回が同等となります。
%kata T5_ThreeQuatersPiPhase_Test

operation ThreeQuatersPiPhase (q : Qubit) : Unit is Adj+Ctl {
    T(q);
    T(q);
    T(q);
}

Exercise 6: Preparing a rotated state


回転系のGateに入って少し雰囲気が変わりますが、R系のGateの定義をきちんと読めばそれほど難しくありません。|0>から目的の\( \alpha|0\rangle -i\beta|1\rangle \)を作り出すためには\( R_x() \)を使います。
今回初めてQ#での変数の代入処理が発生しますが、チュートリアルに書いてある通り、
let num = function();
という形で代入することができます。

%kata T6_PrepareRotatedState_Test

open Microsoft.Quantum.Math;

operation PrepareRotatedState (alpha : Double, beta : Double, q : Qubit) : Unit is Adj+Ctl {
    let theta = ArcTan2(beta, alpha) * 2.0;
    Rx(theta, q);
}

Exercise 7**: Preparing an arbitrary state


次は回転系のゲートの組み合わせです。R系ゲートの定義をきちんと読めば\(R_y\)と\(R_1\)の組み合わせで実現可能なことがわかります。
\( \beta = \sqrt{1 - \alpha^2} \)という書き方をしていますが、\( \alpha^2 + \beta^2 = 1 \)と同義ですよね。何かのヒントなのかもしれませんが意図が理解できてません。

%kata T7_PrepareArbitraryState_Test

open Microsoft.Quantum.Math;

operation PrepareArbitraryState (alpha : Double, beta : Double, theta : Double, q : Qubit) : Unit is Adj+Ctl {
    let theta1 = ArcTan2(beta, alpha) * 2.0;
    Ry(theta1, q);
    R1(theta, q);
}



↑このページのトップヘ