本教學課程說明如何撰寫和模擬在個別量子位上運作的基本量子程式。
雖然 Q# 主要是建立為大規模量子程式的高階程序設計語言,但它也可以用來探索較低層級的量子程序設計,也就是直接尋址特定的量子位。 具體而言,本教學課程會深入探討 Quantum Fourier Transform (QFT),這是許多大型量子演算法中不可或缺的子程序。
在本教學課程中,您將瞭解如何:
- 在Q#中定義量子操作。
- 撰寫 Quantum Fourier 轉換線路
- 模擬量子位配置到測量輸出的量子運算。
- 觀察量子系統的模擬波函數在整個運作過程中如何演進。
注意
在量子資訊處理的較低層檢視中,通常會以量子電路來描述,這表示將閘門或作業循序應用到系統中特定的量子位之上。 因此,您循序套用的單一和多量子位作業可以在電路圖中輕鬆表示。 例如,本教學課程中使用的完整三量子位元的量子傅立葉變換,其線路的表示如下圖所示:
提示
如果您想要加速量子運算之旅,可以查看 Azure Quantum 程式碼,這是 Microsoft Quantum 網站 的一項獨特功能。 在這裡,您可以執行內建 Q# 範例或您自己的 Q# 程式,從提示產生新 Q# 程式碼,一鍵在 VS Code 網頁版 中開啟並執行程式碼,並向 Copilot 詢問有關量子運算的問題。
必要條件
請使用 最新版本的 Visual Studio Code(VS Code) 或開啟 VS Code for the Web。
最新版的 Azure Quantum Development Kit (QDK) 擴充功能。 如需安裝詳細資料,請參閱 設定 QDK 擴充功能。
如果你想用 Jupyter Notebooks,那你就得在 VS Code 裡安裝 Python 和 Jupyter 擴充功能。
最新
qdkPython 套件附帶額外功能jupyter。 安裝這些程式時,請開啟終端機並執行以下指令:pip install --upgrade "qdk[jupyter]"
建立新 Q# 檔案
- 在 VS Code 中,開啟 [檔案] 功能表,然後選擇 [新增文字檔案]。
- 將檔案儲存為 QFTcircuit.qs。 此檔案包含程式 Q# 程式代碼。
- 開啟 QFTcircuit.qs。
在中寫入 QFT 線路 Q#
本教學課程的第一個部分是由定義 Q# 作業 Main所組成,其會在三個量子位上執行量子 Fourier 轉換。
DumpMachine 函數用於觀察三量子位系統的模擬波函數如何在整個操作過程中演變。 在本教學課程的第二個部分中,您會新增測量功能,並比較量子比特測量前後的狀態。
您可以逐步建置作業。 將下列各節中的程式代碼複製並貼到 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永遠不會接受參數,而且目前暫時會返回Unit 物件,這類似於在 C# 中返回void或在 Python 中返回空的元組 (Tuple) Tuple[()]。
稍後,您可以修改作業以傳回度量結果的陣列。
配置量子位
在 Q# 作業中,使用 use 關鍵詞配置一個三個量子位的寄存器。 使用 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),請使用標準索引表示法。
因此,將 H 作業套用至緩存器 qs 的第一個量子位會採用下列格式:
H(qs[0]);
除了將 H 作業套用至個別量子位之外,QFT 線路主要包含受控 R1 旋轉。 一個 R1(θ, <qubit>) 操作通常會將量子位的 $\ket{0}$ 狀態維持不變,同時對 $\ket{1}$ 狀態應用 $e^{i\theta}$ 的旋轉。
Q# 可讓您輕鬆地在一或多個控制量子位上設定作業執行的條件。 一般而言,呼叫前面會加上 Controlled,而作業自變數會變更,如下所示:
Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))
請注意,控制量子位參數即使適用於單一量子位,也必須是陣列。
QFT 中的受控制作業是 R1 針對第一個量子位執行的作業(並由第二個和第三個量子位控制):
在您的 Q# 檔案中,使用下列語句呼叫這些作業:
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
PI() 函數用來定義以 pi 弧度表示的旋轉角度。
套用 SWAP 操作
將相關的 H 作業和受控制的旋轉套用至第二和第三個量子位之後,線路看起來會像這樣:
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
最後,您會將作業套用至第一個和第三個 SWAP 量子位,以完成線路。 此作業是必要的,因為量子 Fourier 轉換會以反向順序輸出量子位,因此交換可讓子程式順暢地整合到較大的演算法中。
SWAP(qs[2], qs[0]);
現在,您已完成將量子 Fourier 轉換的量子位層級作業寫入您的 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
}
執行量子傅立葉變換電路
目前, Main 作業不會傳回任何值 - 作業會傳 Unit 回值。 稍後,您可以修改作業以傳回度量結果的陣列(Result[])。
- 若要執行您的程式,請從前面Q#的功能表中選擇 ],或按
Main。 程式會在預設模擬器上執行Main作業。 -
Message和DumpMachine的輸出會出現在偵錯控制台中。
如果您對其他輸入狀態有何影響感到好奇,建議您在轉換之前嘗試套用其他量子位作業。
將度量新增至 QFT 線路
來自 DumpMachine 函式的顯示顯示了作業的結果,但不幸的是,量子力學的基石指出,真正的量子系統不能有這樣的 DumpMachine 函式。
相反地,資訊通常是透過測量來擷取,它不僅無法提供完整量子狀態的資訊,還可能大幅改變系統本身。
量子測量有許多種,但這裡的範例著重於最基本的:單一量子位的投影量值。 當在指定的基底上進行測量時(例如,計算基底 ${\ket{c0}, \ket{c1}}$),量子位狀態會投影到所測量的基底態,從而摧毀兩者之間的任何疊加。
修改 QFT 操作
若要在Q#程式中實施測量,請使用M作業,它會傳回Result型別。
首先,修改 Main 作業以傳回度量結果陣列, Result[]而不是 Unit。
operation Main() : Result[] {
定義和初始化 Result[] 陣列
配置量子位之前,請宣告並系結三個元素陣列(每個量子位各一個 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)使用update-and-reassign語句新增至resultArray中的對應索引位置。
注意
此語句的語法在Q#中是獨特的,但對應於其他語言中類似的變數重新賦值resultArray[i] <- M(qs[i]),例如 F# 和 R。
關鍵詞 set 總是用來重新指派通過 mutable 綁定的變數。
返回 resultArray
測量完所有三個量子位,並將結果加至 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 后迭加與基礎狀態。 測量只會傳回單一基礎狀態,機率取決於系統波浪函數中該狀態的幅度。 - 從測量后,您會看到度量
DumpMachine變更狀態本身,並將它從基礎狀態的初始迭加投影到對應至測量值的單一基礎狀態。
如果您多次重複此操作,您會看到結果統計數據開始呈現出量子傅立葉變換後的狀態的等權重疊加,這會導致每次測量產生隨機結果。 不過,除了效率低下,仍然不完善,這隻會重現基礎狀態的相對幅度,而不是它們之間的相對階段。 後者不是此範例中的問題,但如果對 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];
//...
因此,您可以針對任何指定數目的量子位套用適當的 QFT,而不必擔心在每個量子位上新增作業 H 和輪替。
相關內容
探索其他 Q# 教學課程:
- 量子隨機數產生器 示範如何撰寫一個程式,以在量子位的疊加態中生成隨機數。
- Grover 的搜尋演算法 示範如何撰寫 Q# 使用 Grover 搜尋演算法的程式。
- 量子糾纏 示範如何撰寫 Q# 程式,以操作和測量量子位,並示範迭加和糾纏的效果。
- Quantum Katas 是自我節奏的教學課程和程序設計練習,旨在同時教學量子運算和Q#程序設計元素。