このチュートリアルでは、個々の量子ビットで動作する基本的な量子プログラムを作成してシミュレートする方法を示します。
Q# は主に大規模な量子プログラム用の高レベルのプログラミング言語として作成されていますが、特定の量子ビットに直接対処する下位レベルの量子プログラムを調べる場合にも使用できます。 特にこのチュートリアルでは、多くの大規模な量子アルゴリズムに不可欠なサブルーチンである量子フーリエ変換 (QFT) について詳しく見ていきます。
このチュートリアルでは、次の作業を行う方法について説明します。
- で量子演算を定義します Q#。
- 量子フーリエ変換回路を書き込む
- 量子ビットの割り当てから測定出力までの量子演算をシミュレートします。
- 量子システムのシミュレートされたウェーブ関数が操作全体でどのように進化するかを観察します。
Note
量子情報処理のこの下位ビューは、システムの特定の量子ビットに対するゲートの順次適用 (つまり "操作") を表す量子回路の観点からよく説明されていることに注意してください。 したがって、順次適用される単一およびマルチ量子ビット操作は、回路図で簡単に表すことができます。 たとえば、このチュートリアルで使用される完全な 3 量子ビット量子フーリエ変換には、回線として次の表現があります。
ヒント
量子コンピューティングの取り組みを加速させる場合は、Microsoft Quantum Web サイトのユニークな機能である Azure Quantum を使用したコードをご覧ください。 ここでは、組み込みの Q# サンプルまたは独自の Q# プログラムを実行し、プロンプトから新しい Q# コードを生成し、ワンクリックで VS Code for the Web でコードを開いて実行し、量子コンピューティングに関する Copilot の質問をすることができます。
前提条件
最新バージョンの Visual Studio Code または VS Code on the Web を開きます。
AzureQuantum Development Kit (QDK) 拡張機能の最新バージョン。 インストールの詳細については、「QDK 拡張機能を設定する」を参照してください。
Jupyter Notebooks を使用する場合は、 Python と Jupyter 拡張機能、および最新の
qdkPython パッケージをjupyter追加でインストールする必要があります。 これを行うには、ターミナルを開き、次のコマンドを実行します。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引数を受け取ることはありません。ここでは、Python で C# または空のタプルUnitを返すvoidのに似たオブジェクトを返Tuple[()]します。
後で、測定結果の配列を返すように操作を変更します。
量子ビットの割り当て
操作内で Q# 、キーワードを使用して 3 つの量子ビットのレジスタを use 割り当てます。
use では、量子ビットは $\ket{0}$ 状態で自動的に割り当てられます。
use qs = Qubit[3]; // allocate three qubits
Message("Initial state |000>:");
DumpMachine();
実際の量子計算と同じように、Q# では、量子ビット状態に直接アクセスすることはできません。 ただし、DumpMachine 操作は、target コンピューターの現在の状態が出力されるため、完全な状態シミュレーターと組み合わせて使用すると、デバッグと学習に関する貴重な洞察を得ることができます。
単一量子ビットと制御された操作を適用する
次に、操作自体を構成する操作を Main 適用します。
Q# には、これらの多くの基本的な量子操作が名前空間に既に Std.Intrinsic 含まれています。
Note
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 の対応するインデックス位置に、更新と再割り当てのステートメントを使用して追加されます。
Note
このステートメントの構文は 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#プログラミング演習です。