チュートリアル: Q# で量子乱数ジェネレーターを実装する

このチュートリアルでは、量子力学の性質を利用して乱数を生成する基本的な量子プログラム Q# を記述する方法について説明します。

このチュートリアルでは、次のことについて説明します。

  • Q# プログラムを作成します。
  • Q# プログラムの主要なコンポーネントを確認する。
  • 問題のロジックを定義する。
  • 従来の演算と量子演算を組み合わせて問題を解決する。
  • 量子ビットと重ね合わせを使用して、量子乱数ジェネレーターを構築します。

前提条件

Visual Studio Code (VS Code) と Jupyter Notebook でコード サンプルを開発して実行するには、次をインストールします。

  • 最新バージョンの VS Code または Web 用の VS Code を開きます。

  • Microsoft Quantum Development Kit (QDK) 拡張子の最新バージョン。 インストールの詳細については、「QDK 拡張機能を設定する」を参照してください。

  • VS Code 用の Python および Jupyter 拡張機能。

  • 最新のqdk Python ライブラリ、jupyterのエクストラ付き。 これらをインストールするには、ターミナルを開き、次のコマンドを実行します。

    pip install --upgrade "qdk[jupyter]"
    

問題を定義する

古典的なコンピューターでは、乱数ではなく、擬似乱数が生成されます。 擬似乱数ジェネレーターは、シードと呼ばれるいくつかの初期値に基づいて、数値の決定論的シーケンスを生成します。 ランダムな値に近づけるために、このシードは CPU のクロックの現在時刻になることがよくあります。

一方、量子コンピューターは真の乱数を生成できます。 これは、重ね合わせにおける量子ビットの測定が確率的プロセスであるためです。 測定の結果はランダムであり、結果を予測する方法はありません。 これが量子乱数ジェネレーターの基本原則です。

量子ビットは、重ね合わせ可能な量子情報の単位です。 測定する場合、量子ビットは 0 状態または 1 状態でのみ使用できます。 ただし、測定前の量子ビットの状態は、測定値で 0 または 1 を読み取る確率を表します。

まず、0 などの基礎状態で量子ビットを取得します。 乱数ジェネレーターの最初の手順は、Hadmard 演算を使用して量子ビットを等しい重ね合わせに配置することです。 この状態を測定すると、各結果の確率が 50% の 0 または 1 が生じ、真にランダムなビットになります。

重ね合わせでの量子ビットの測定後に何が得られるかを知る方法はなく、コードが呼び出されるたびに結果は異なる値になります。 しかし、この動作をどのように使えば、より大きな乱数を生成できるのでしょうか?

このプロセスを 4 回繰り返すと、次のような 2 進数の数列が生成されます。

$${0, 1, 1, 0}$$

これらのビットをビット文字列に連結 (結合) すると、より大きな数値を使用できます。 この例では、ビット シーケンス ${0110}$ は 10 進数の 6 に相当します。

$${0110_{\ 2 進} \equiv 6_{\ 10 進}}$$

このプロセスを何度も繰り返すと、複数のビットを組み合わせてあらゆる大きな数値を形成できます。 この方法を使用すると、安全なパスワードとして使用する数値を作成できます。これは、ハッカーが一連の測定値の結果を特定できないことを確認できるためです。

乱数ジェネレーターのロジックを定義する

乱数ジェネレーターのロジックの概要を説明します。

  1. 生成する最大数として max を定義します。
  2. 生成する必要がある乱数ビットの数を定義します。 これは、ビット数を計算することによって行われ、 nBits最大 maxで整数を表す必要があります。
  3. 長さが nBits の乱数ビット文字列を生成します。
  4. ビット文字列が max より大きい数値を表す場合、ステップ 3 に戻ります。
  5. それ以外の場合、プロセスは終了です。 生成された数値を整数として返します。

例として、max を 12 に設定します。 つまり、パスワードとして使用する最大の数は 12 です。

0 から 12 までの数値を表すには、${\lfloor ln(12) / ln(2) + 1 \rfloor}$ ビット、つまり 4 ビットが必要です。 任意の整数を受け取り、それを表すために必要なビット数を返す組み込み関数 BitSizeIを使用できます。

たとえば、ビット文字列 ${1101_{\ binary}}$ を生成したとします。これは ${13_{\ decimal}}$ と等価です。 13 は 12 より大きいため、この処理を繰り返します。

次に、ビット文字列 ${0110_{\ binary}}$ を生成したとします。これは ${6_{\ decimal}}$ と等価です。 6 は 12 未満であるため、この処理を終了します。

量子乱数ジェネレーターは、パスワードとして数値 6 を返します。 実際には、最大としてより大きな数値を設定します。小さい数値だと、使用可能な全パスワードを試すだけで簡単に解読できるためです。 実際、パスワードの推測や解析をより困難にするために、ASCII コードを使用してバイナリをテキストに変換し、数字、記号、および大文字と小文字が混在した文字を使用してパスワードを生成することもできます。

ランダム ビット ジェネレーターを書き込む

最初の手順では、ランダム ビットを Q# 生成する操作を記述します。 この操作は、乱数ジェネレーターの構成要素の 1 つになります。

operation GenerateRandomBit() : Result {
    // Allocate a qubit.
    use q = Qubit();

    // Set the qubit into superposition of 0 and 1 using the Hadamard 
    H(q);

    // At this point the qubit `q` has 50% chance of being measured in the
    // |0〉 state and 50% chance of being measured in the |1〉 state.
    // Measure the qubit value using the `M` operation, and store the
    // measurement value in the `result` variable.
    let result = M(q);

    // Reset qubit to the |0〉 state.
    // Qubits must be in the |0〉 state by the time they are released.
    Reset(q);

    // Return the result of the measurement.
    return result;
}

次に、新しいコードを見てみます。

  • GenerateRandomBit 操作を定義します。これには入力が不要であり、型 Result の値が生成されます。 Result 型は測定の結果を表し、Zero または One の 2 つの値を使用できます。
  • キーワードを使用して 1 つの量子ビットを use 割り当てます。 割り当てられると、量子ビットは常に |0〉 状態になります。
  • この演算を H 使用して、量子ビットを等しい重ね合わせに配置します。
  • 演算を M 使用して量子ビットを測定し、測定値 (Zero または One) を返します。
  • この操作を Reset 使用して、量子ビットを |0〉 状態にリセットします。

H 操作を使用して量子ビットを重ね合わせに入れ、それを M 操作で測定することで、コードを呼び出すたびに結果は異なる値になります。

Bloch 球を Q# 使用してコードを視覚化する

ブロッホ球では、北極は古典的な値 0 を表し、南極は古典的な値 1 を表します。 重ね合わせは球上の点で表わすことができます (矢印で表わされています)。 矢印の端が極に近づけば近づくほど、測定時、その極に割り当てられる古典的な値にキュービットがなる確率が高くなります。 たとえば、次の図の矢印で表される量子ビットの状態は、測定した場合に値 0 を与える確率が高くなります。

ゼロを測定する確率が高い量子ビット状態を示す図。

この表現を利用し、コードの動作を視覚化できます。

  1. まず、|0〉 状態で初期化された量子ビットから開始し、 H 演算を適用して、0 と 1 の確率が同じ等しい重ね合わせを作成します。

    ハダマールゲートを適用した重ね合わせにおける量子ビットの準備を示す図。
  2. 次に、量子ビットを測定し、出力を保存します。

    量子ビットの測定と出力の保存を示す図。

測定の結果はランダムであり、0 と 1 を測定する確率は同じであるため、完全にランダムなビットを取得しました。 この操作を複数回呼び出し、整数を作成できます。 たとえば、操作を 3 回呼び出してランダム ビットを 3 つ取得する場合、ランダムの 3 ビット数 (つまり、0 から 7 までの乱数) を構築できます。

完全な乱数ジェネレーターを書き込む

  1. まず、必要な名前空間を標準ライブラリからプログラムに Q# インポートする必要があります。 コンパイラはQ#多くの一般的な関数と操作を自動的に読み込みますが、完全な乱数ジェネレーターの場合は、2 つのQ#名前空間Std.MathStd.Convertからいくつかの追加の関数と操作が必要です。

    import Std.Convert.*;
    import Std.Math.*;
    
  2. 次に、操作を定義します GenerateRandomNumberInRange 。 この演算は GenerateRandomBit 演算を繰り返し呼び出して、ビット文字列を構築します。

    /// Generates a random number between 0 and `max`.
    operation GenerateRandomNumberInRange(max : Int) : Int {
        // Determine the number of bits needed to represent `max` and store it
        // in the `nBits` variable. Then generate `nBits` random bits which will
        // represent the generated random number.
        mutable bits = [];
        let nBits = BitSizeI(max);
        for idxBit in 1..nBits {
            bits += [GenerateRandomBit()];
        }
        let sample = ResultArrayAsInt(bits);
    
        // Return random number if it is within the requested range.
        // Generate it again if it is outside the range.
        return sample > max ? GenerateRandomNumberInRange(max) | sample;
    }
    
    

    少し時間を取って新しいコードを確認してみましょう。

    • 最大 max の整数を表現するために必要なビット数を計算する必要があります。 Std.Math 名前空間の BitSizeI 関数は、整数を表現するのに必要なビット数を返します。
    • SampleRandomNumberInRange 演算は for ループを使用して、max 以下の乱数を生成するまで乱数を生成します。 for ループは、他のプログラミング言語の for ループとまったく同じように動作します。
    • 変数 bits は変更可能な変数です。 変更可能な変数は、計算中に変更できる変数です。 set ディレクティブを使用して、変更可能な変数の値を変更することができます。
    • 関数は ResultArrayAsInt 、既定 Std.Convert の名前空間から、ビット文字列を正の整数に変換します。
  3. 最後に、エントリ ポイントをプログラムに追加します。 既定では、コンパイラは Q# 操作を Main 検索し、そこで処理を開始します。 この操作を GenerateRandomNumberInRange 呼び出して、0 ~ 100 の乱数を生成します。

    operation Main() : Int {
        let max = 100;
        Message($"Sampling a random number between 0 and {max}: ");
    
        // Generate random number in the 0..max range.
        return GenerateRandomNumberInRange(max);
    }
    

    let ディレクティブは、計算中に変更されない変数を宣言します。 ここでは、最大値を 100 として定義します。

    操作の詳細Mainについては、「エントリ ポイント」を参照してください

  4. 乱数ジェネレーターの完全なコードは次のとおりです。

import Std.Convert.*;
import Std.Math.*;

operation Main() : Int {
    let max = 100;
    Message($"Sampling a random number between 0 and {max}: ");

    // Generate random number in the 0..max range.
    return GenerateRandomNumberInRange(max);
}

/// Generates a random number between 0 and `max`.
operation GenerateRandomNumberInRange(max : Int) : Int {
    // Determine the number of bits needed to represent `max` and store it
    // in the `nBits` variable. Then generate `nBits` random bits which will
    // represent the generated random number.
    mutable bits = [];
    let nBits = BitSizeI(max);
    for idxBit in 1..nBits {
        bits += [GenerateRandomBit()];
    }
    let sample = ResultArrayAsInt(bits);

    // Return random number if it is within the requested range.
    // Generate it again if it is outside the range.
    return sample > max ? GenerateRandomNumberInRange(max) | sample;
}

operation GenerateRandomBit() : Result {
    // Allocate a qubit.
    use q = Qubit();

    // Set the qubit into superposition of 0 and 1 using a Hadamard operation
    H(q);

    // At this point the qubit `q` has 50% chance of being measured in the
    // |0〉 state and 50% chance of being measured in the |1〉 state.
    // Measure the qubit value using the `M` operation, and store the
    // measurement value in the `result` variable.
    let result = M(q);

    // Reset qubit to the |0〉 state.
    // Qubits must be in the |0〉 state by the time they are released.
    Reset(q);

    // Return the result of the measurement.
    return result;
}

乱数ジェネレータープログラムを実行する

VS Code の QDK 拡張機能か、Jupyter Notebookの QDK Python ライブラリのどちらかの好みの開発環境を選択します。

  1. VS Code で、[ ファイル ] メニューを開き、[ 新しいテキスト ファイル ] を選択して新しいファイルを作成します。

  2. このファイルを RandomNumberGenerator.qs として保存します。 このファイルには、プログラムのコードが Q# 含まれます。

  3. 次のコードを RandomNumberGenerator.qs ファイルにコピーしてください。

    import Std.Convert.*;
    import Std.Math.*;
    
    operation Main() : Int {
        let max = 100;
        Message($"Sampling a random number between 0 and {max}: ");
    
        // Generate random number in the 0..max range.
        return GenerateRandomNumberInRange(max);
    }
    
    /// # Summary
    /// Generates a random number between 0 and `max`.
    operation GenerateRandomNumberInRange(max : Int) : Int {
        // Determine the number of bits needed to represent `max` and store it
        // in the `nBits` variable. Then generate `nBits` random bits which will
        // represent the generated random number.
        mutable bits = [];
        let nBits = BitSizeI(max);
        for idxBit in 1..nBits {
            bits += [GenerateRandomBit()];
        }
        let sample = ResultArrayAsInt(bits);
    
        // Return random number if it is within the requested range.
        // Generate it again if it is outside the range.
        return sample > max ? GenerateRandomNumberInRange(max) | sample;
    }
    
    /// # Summary
    /// Generates a random bit.
    operation GenerateRandomBit() : Result {
        // Allocate a qubit.
        use q = Qubit();
    
        // Set the qubit into superposition of 0 and 1 using the Hadamard 
        // operation `H`.
        H(q);
    
        // At this point the qubit `q` has 50% chance of being measured in the
        // |0〉 state and 50% chance of being measured in the |1〉 state.
        // Measure the qubit value using the `M` operation, and store the
        // measurement value in the `result` variable.
        let result = M(q);
    
        // Reset qubit to the |0〉 state.
        // Qubits must be in the |0〉 state by the time they are released.
        Reset(q);
    
        // Return the result of the measurement.
        return result;
    
        // Note that Qubit `q` is automatically released at the end of the block.
    }
    
  4. プログラムを実行するには、の前にあるコマンドの一覧から Main()] を選択するか、Ctrl キーを押しながら F5 キーを押します。 プログラムは、既定の Main() シミュレーターで操作を実行します。

    CodeLens にある実行コマンドの場所を示す Visual Studio Code のスクリーンショット。

  5. 出力がデバッグ コンソールに表示されます。

  6. プログラムを再度実行すると、異なる結果が表示されます。

QIR target プロファイルが Unrestricted に設定されていない場合は、プログラムの実行時にエラーが発生します。 このプログラムでは、プロファイルを自分で設定しない限り、コンパイラによって target プロファイルが 自動的に Unrestricted に設定されます。

頻度ヒストグラムをプロットする

量子プログラムを複数回実行して得られた結果の分布を視覚化してみましょう。 頻度ヒストグラムは、これらの結果の確率分布を視覚化するのに役立ちます。

  1. [ 表示] -> コマンド パレット を選択し、「 ヒストグラム」と入力します。これにより、 QDK: ファイルの実行とヒストグラムの表示 コマンドが表示されます。 Main()の前にあるコマンドの一覧からも、ヒストグラムを選択できます。 ヒストグラム ウィンドウを開くには、このオプションを Q# 選択します。

    CodeLens でヒストグラム コマンドを見つける場所を示した Visual Studio Code のスクリーンショット。

  2. プログラムを実行するショット数 (100 ショットなど) を入力し、Enter キーを押します。 ヒストグラムは Q# ヒストグラム ウィンドウに表示されます。

  3. ヒストグラムの各バーは、考えられる結果に対応し、その高さは結果が観察される回数を表します。 ヒストグラムを実行するたびに異なる結果の数が異なる場合があります。

    Visual Studio Code の Q# ヒストグラム ウィンドウのスクリーンショット。

    ヒント

    マウス スクロール ホイールまたはトラックパッド ジェスチャを使用して、ヒストグラムをズームできます。 拡大すると、スクロール中に 'Alt' キーを押してグラフをパンできます。

  4. バーを選択すると、その結果の 割合 が表示されます。

  5. 左上 の設定アイコン を選択して、オプションを表示します。 上位 10 件の結果、上位 25 件の結果、またはすべての結果を表示できます。 また、結果を高から低、低から高に並べ替えることもできます。

    設定の表示方法を示す、Visual Studio Code の Q# ヒストグラム ウィンドウのスクリーンショット。

このコード スニペットは現在、使用可能な Azure Quantum ハードウェア targetsでは実行されません。呼び出し可能な ResultArrayAsInt には 、完全な計算プロファイルを持つ QPU が必要です。

Q# のその他のチュートリアルを確認します。