このチュートリアルでは、個々の量子ビットで動作する基本的な量子プログラムを記述してシミュレートする方法について説明します。
Q# は主に大規模な量子プログラム用の高レベルのプログラミング言語として作成されていますが、特定の量子ビットに直接対処する下位レベルの量子プログラムを調べる場合にも使用できます。 特にこのチュートリアルでは、多くの大規模な量子アルゴリズムに不可欠なサブルーチンである量子フーリエ変換 (QFT) について詳しく見ていきます。
このチュートリアルでは、次の作業を行う方法について説明します。
- で量子演算を定義します Q#。
- 量子フーリエ変換回路を記述します。
- 量子ビットの割り当てから測定出力までの量子演算をシミュレートします。
- 量子システムのシミュレートされたウェーブ関数が操作全体でどのように進化するかを観察します。
注記
量子情報処理のこの下位ビューは、システムの特定の量子ビットに対するゲートの順次適用 (つまり "操作") を表す量子回路の観点からよく説明されていることに注意してください。 したがって、順次適用される単一およびマルチ量子ビット操作は、回路図で簡単に表すことができます。 たとえば、このチュートリアルで使用される完全な 3 量子ビット量子フーリエ変換には、回線として次の表現があります。
前提条件
最新バージョンの Visual Studio Code (VS Code) または WEB 用の VS Code を開きます。
Microsoft Quantum Development Kit (QDK) 拡張子の最新バージョン。 インストールの詳細については、「QDK 拡張機能を設定する」を参照してください。
Jupyter Notebook を使用するには、VS Code に Python 拡張機能と Jupyter 拡張機能をインストールします。
qdkに追加のjupyterが付いた最新のPythonパッケージ。 これらをインストールするには、ターミナルを開き、次のコマンドを実行します。pip install --upgrade "qdk[jupyter]"
新しいQ#ファイルを作成する
- VS Code で、[ ファイル ] メニューを開き、[ 新しいテキスト ファイル] を選択します。
- ファイルを QFTcircuit.qs として 保存します。 このファイルには、プログラムの Q# コードが含まれています。
- QFTcircuit.qs を開きます。
で QFT 回線を書き込む Q#
このチュートリアルの最初の部分では、Q# 操作 Main を定義します。この操作では、3 つの量子ビットで量子フーリエ変換を実行します。
DumpMachine 関数を使用して、3 つの量子ビット システムのシミュレートされた波動関数が、操作を通してどのように変化するかを観察します。 チュートリアルの 2 番目の部分では、測定機能を追加し、量子ビットの測定前と測定後の状態を比較します。
操作の構築はステップ バイ ステップで行います。 次のセクションのコードをコピーし、QFTcircuit.qs ファイルに貼り付けます。
このセクションの完全なQ#コードを参照として表示できます。
必要な Q# ライブラリをインポートする
ファイル内で Q# 、関連するStd.* 名前空間をインポートします。
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
// operations go here
引数と戻り値を使用して操作を定義する
次に、Main 操作を定義します。
operation Main() : Unit {
// do stuff
}
このMain操作は引数を受け取ることはなく、現時点では、C# のUnitを返したり、Python の空のタプルvoidに似たオブジェクトをTuple[()]として返します。
後で、測定結果の配列を返すように操作を変更します。
量子ビットの割り当て
Q# の操作において、use キーワードを使って3量子ビットのレジスタを割り当てます。
use では、量子ビットは $\ket{0}$ 状態で自動的に割り当てられます。
use qs = Qubit[3]; // allocate three qubits
Message("Initial state |000>:");
DumpMachine();
実際の量子計算と同じように、Q# では、量子ビット状態に直接アクセスすることはできません。 ただし、DumpMachine 操作は、target コンピューターの現在の状態が出力されるため、完全な状態シミュレーターと組み合わせて使用すると、デバッグと学習に関する貴重な洞察を得ることができます。
単一量子ビットと制御された操作を適用する
次に、 Main 操作自体を構成する操作を適用します。
Q# には、これらの多くの基本的な量子操作が既に Std.Intrinsic 名前空間に含まれています。
注記
Std.Intrinsic は、すべての Q# プログラムに対してコンパイラによって自動的に読み込まれるため、以前のコード スニペットでは他の名前空間と共にインポートされませんでした。
最初に適用される操作は、最初の量子ビットに対する H (Hadamard) 操作です。
レジスタから特定の量子ビットに操作を適用するには (たとえば、配列 Qubit の 1 つの Qubit[])、標準のインデックス表記を使用します。
そのため、H 操作をレジスタ qs の最初の量子ビットに適用すると、次のような形式になります。
H(qs[0]);
H 操作を個々の量子ビットに適用するだけでなく、QFT 回線は主に制御された R1 回転で構成されます。
R1(θ, <qubit>)一般的な演算では、量子ビットの $\ket{0}$ コンポーネントは変更されず、$\ket{1}$ コンポーネントに $e^{i\theta}$ の回転が適用されます。
Q# を使用すると、1 つまたは複数のコントロール量子ビットに対して操作を実行する条件を簡単にすることができます。 一般に、呼び出しの前に Controlled を付けると、操作の引数は次のように変更されます。
Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))
コントロールキュービット引数は、1つのキュービットの場合でも、配列として指定する必要があることに注意してください。
QFT で制御される操作は、最初の R1 量子ビットに作用する操作です (2 番目と 3 番目の量子ビットによって制御されます)。
Q# ファイルで、次のステートメントを使用してこれらの操作を呼び出します。
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
PI() 関数を使用して、回転が pi ラジアンで定義されます。
SWAP 操作の適用
関連する H 演算と制御された回転を 2 番目と 3 番目の量子ビットに適用すると、回路は次のようになります。
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
最後に、1 番目と 3 番目の量子ビットに演算を適用 SWAP して、回線を完了します。 この操作が必要なのは、量子フーリエ変換によって量子ビットが逆順に出力されるためです。そのため、スワップにより、サブルーチンをより大きなアルゴリズムにシームレスに統合できます。
SWAP(qs[2], qs[0]);
Q#演算には、量子フーリエ変換の量子ビット レベルの演算が含まれるようになりました。
量子ビットの割り当て解除
最後の手順では、DumpMachine() を再度呼び出して操作後の状態を確認し、量子ビットの割り当てを解除します。 量子ビットの割り当て時の状態は $\ket{0}$ で、ResetAll 操作を使用して初期状態にリセットする必要があります。
すべての量子ビットを明示的に $\ket{0}$ にリセットすることを要求することは、他の Q#操作がそれらの同じ量子ビット (リソース不足) の使用を開始したときにその状態を正確に知ることができるため、基本的な機能です。 また、システム内の他の量子ビットとの間では、もつれた状態にならないことが保証されます。
use 割り当てブロックの終了時にリセットが実行されない場合は、ランタイム エラーが発生する可能性があります。
Q# ファイルに次の行を追加します。
Message("After:");
DumpMachine();
ResetAll(qs); // deallocate qubits
完全な QFT 操作
Q# プログラムが完了しました。 これで 、QFTcircuit.qs ファイルは次のようになります。
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
operation Main() : Unit {
use qs = Qubit[3]; // allocate three qubits
Message("Initial state |000>:");
DumpMachine();
//QFT:
//first qubit:
H(qs[0]);
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
SWAP(qs[2], qs[0]);
Message("After:");
DumpMachine();
ResetAll(qs); // deallocate qubits
}
QFT 回線を実行する
現時点では、操作は値を Main 返しません。操作は値を返します Unit 。 後で、測定結果の配列を返すように操作を変更します (Result[])。
- プログラムを実行するには、Q#の前にあるメニューから [] を選択するか、
Mainを押します。 プログラムは、既定のMainシミュレーターで操作を実行します。 -
Message出力とDumpMachine出力がデバッグ コンソールに表示されます。
他の入力状態がどのように影響を受けるか興味がある場合は、変換の前に他の量子ビット演算を適用することを試してください。
QFT 回路に測定を追加する
DumpMachine 関数の表示により操作の結果が示されましたが、残念ながら、量子力学の基礎として、実際の量子系ではこのような DumpMachine 関数を持つことはできません。
代わりに、情報は測定値を使用して抽出されます。これは一般に、完全な量子状態に関する情報を提供できないだけでなく、システム自体を大幅に変更する可能性もあります。
量子測定にはさまざまな種類がありますが、ここでの例では、最も基本的な 1 つの量子ビットの射影測定に焦点を当てます。 指定された基底 (たとえば、計算基底 $ { \ket{0}、\ket{1} } $) の測定値に基づいて、量子ビット状態は、測定されたいずれかの基底状態に投影されるため、この 2 つの間のあらゆる重ね合わせは破棄されます。
QFT 操作を変更する
Q# プログラム内で測定を実装するには、M 型を返す Result 操作を使用します。
まず、Main ではなく、測定結果の配列 Result[] を返すように Unit 操作を変更します。
operation Main() : Result[] {
Result[] 配列の定義と初期化
量子ビットを割り当てる前に、3 要素配列 (量子ビットごとに 1 つ Result ) を宣言してバインドします。
mutable resultArray = [Zero, size = 3];
mutableキーワードの先頭resultArrayを使用すると、たとえば、測定結果を追加するときに、コードの後半で変数を変更できます。
for ループで測定を実行し、結果を配列に追加する
QFT 変換操作の後に、次のコードを挿入します。
for i in IndexRange(qs) {
resultArray w/= i <- M(qs[i]);
}
配列 (たとえば、量子ビットの配列 IndexRange) で呼び出される qs 関数は、配列のインデックスの範囲を返します。
ここでは、for ループで使用して、M(qs[i]) ステートメントを使用して各量子ビットを順番に測定します。
各測定 Result 型 (Zero または One) は、resultArray の対応するインデックス位置に、更新と再割り当てのステートメントを使用して追加されます。
注記
このステートメントの構文は Q# に固有ですが、F# や R などの他の言語で見られるような変数の再割り当て resultArray[i] <- M(qs[i]) に相当します。
キーワード set は、mutable を使用してバインドされた変数を再割り当てするために常に使用されます。
返品resultArray
3 つの量子ビットがすべて測定され、結果が resultArray に追加されたので、以前と同じように量子ビットをリセットし、割り当てを解除できます。 測定値を取得するには、次のように挿入します:
return resultArray;
測定を使用して QFT 回路を実行する
測定の前後に状態を出力するように DumpMachine 関数の配置を変更してみましょう。
最終的な Q# コードは、次のようになります。
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
operation Main() : Result[] {
mutable resultArray = [Zero, size = 3];
use qs = Qubit[3];
//QFT:
//first qubit:
H(qs[0]);
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
SWAP(qs[2], qs[0]);
Message("Before measurement: ");
DumpMachine();
for i in IndexRange(qs) {
resultArray w/= i <- M(qs[i]);
}
Message("After measurement: ");
DumpMachine();
ResetAll(qs);
Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
return resultArray;
}
ヒント
コードに変更を導入するたびにファイルを保存してから、もう一度実行してください。
- プログラムを実行するには、Q#の前にあるメニューから [] を選択するか、
Mainを押します。 プログラムは、既定のMainシミュレーターで操作を実行します。 -
Message出力とDumpMachine出力がデバッグ コンソールに表示されます。
出力は次のようになります。
Before measurement:
Basis | Amplitude | Probability | Phase
-----------------------------------------------
|000⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|001⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|010⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|011⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|100⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|101⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|110⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|111⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
After measurement:
Basis | Amplitude | Probability | Phase
-----------------------------------------------
|010⟩ | 1.0000+0.0000𝑖 | 100.0000% | 0.0000
Post-QFT measurement results [qubit0, qubit1, qubit2]:
[Zero, One, Zero]
この出力は、次のようないくつかの点を示しています:
- 返された結果を測定前の
DumpMachineと比較する場合、基準状態に対する QFT 後重ね合わせは明らかに示 されていません 。 測定によって観測される単一の基底状態は、その確率と、システムの波動関数 (wavefunction) におけるその状態の振幅によって決まります。 - 測定後の
DumpMachineでは測定が状態自体を変更し、最初の重ね合わせ状態から測定値に対応する単一の基準状態へと投影します。
この操作を何度も繰り返すと、結果の統計情報は、各ショットでランダムな結果が生じる等しい重み付けのポストQFT状態を示し始めます。 ただし、非効率的で不完全ではありますが、これによって、基本的な状態の相対的な振幅のみが再現されます。基本的な状態の間の相対的なフェーズは再現されません。 後者はこの例では問題ではありませんが、QFT への入力が $\ket{000}$ よりも複雑な場合は、相対フェーズが表示されます。
操作を Q# 使用して QFT 回路を簡略化する
概要で説明したように、Q# のほとんどの機能では、個々の量子ビットを扱う心配をしなくてすみます。
実際、フルスケールで適用可能な量子プログラムを開発するには、特定のローテーションの前後にH操作があるのを気にすることはかえって遅れの原因になります。 Azure Quantum には操作が ApplyQFT 用意されており、任意の数の量子ビットを使用して適用できます。
最初の
H操作からSWAP操作までを含めて、すべてを次のように置き換えます。ApplyQFT(qs);コードは次のようになります。
import Std.Diagnostics.*; import Std.Math.*; import Std.Arrays.*; operation Main() : Result[] { mutable resultArray = [Zero, size = 3]; use qs = Qubit[3]; //QFT: //first qubit: ApplyQFT(qs); Message("Before measurement: "); DumpMachine(); for i in IndexRange(qs) { resultArray w/= i <- M(qs[i]); } Message("After measurement: "); DumpMachine(); ResetAll(qs); Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: "); return resultArray; }プログラムをもう Q# 一度実行し、出力が前と同じであることに注意してください。
Q# を使用する本当の利点を見つけるには、量子ビットの数を
3とは異なるものに変更します。
mutable resultArray = [Zero, size = 4];
use qs = Qubit[4];
//...
そのため、各量子ビットに新しい H 演算とローテーションを追加することを心配することなく、任意の数の量子ビットに適切な QFT を適用できます。
関連するコンテンツ
Q# のその他のチュートリアルを確認します。
- 量子乱数ジェネレーター は、重ね合わせの量子ビットから乱数を生成するプログラムを記述 Q# する方法を示します。
- グローバーの検索アルゴリズムは、グローバーの検索アルゴリズム を Q# 使用するプログラムを記述する方法を示しています。
- 量子エンタングルメント は、量子ビットを操作および測定し、重ね合わせとエンタングルメントの効果を示すプログラムを記述 Q# する方法を示します。
- Quantum Katas は、量子コンピューティングとプログラミングの要素を同時に教えることを目的とした、自習型のチュートリアルとQ#プログラミング演習です。