練習 - 實作適用於圖形著色問題的量子 Oracle
已完成
50 分鐘
在這段時間,請忘記有雜訊的訊息和頻寬,並只考慮色彩和頂點。 您有以邊緣連接的點,而且您想知道如何為點上色。
在此部分中,您將針對圖形著色問題來執行量子 oracle。
注意
在此課程模組中,我們將著重在量子 Oracles 與 (在接下來的單元中) Grover 搜尋演算法的高階行為。 我們鼓勵您自行更深入發掘程式碼,特別是在 Q# 文件中查閱任何不熟悉的作業與語言建構。
建立專案
在 [檢視] 功能表上,選取 [命令選擇區]。
輸入 Q#:Create New Project (建立新專案)。
選取 [Standalone console application] (獨立主控台應用程式)。
選取要持有您專案的目錄,例如您的主目錄。 輸入 ExploringGroversSearch 作為專案名稱,然後選取 [建立專案]。
從出現在底部的視窗中,選取 [Open new project] (開啟新專案)。
您會看到兩個檔案:專案檔和 qs,其中包含起始程式碼。
針對此課程模組中的每個程式碼片段,您應該複製整個程式碼片段,以取代檔案 Program.qs 的內容。 之後,開啟整合式終端機 (從 [終端機] 功能表中,選取 [新增終端機]),然後執行 dotnet run:
dotnet run
表示圖形
我們需要兩個參數來表示圖形:頂點數目與邊緣清單。
在 Q # 中,我們會將頂點的數目儲存 nVertices 為整數,並將邊緣清單儲存 edges 為元組的陣列。 每個元組都會將圖形的一個邊緣描述為此邊緣所連接之頂點的一對索引;我們將使用以零為基底的索引,讓索引值可以介於0和 nVertices -1 之間。
我們的範例圖形結構可以表示如下:
namespace ExploringGroversSearchAlgorithm {
@EntryPoint()
operation SolveGraphColoringProblem() : Unit {
// Graph description: hardcoded from the example
// The number of vertices is an integer
let nVertices = 5;
// The list of edges is an array of tuples, and each tuple is a pair of integers
let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];
}
}
表示頂點著色
我們將依色彩的陣列描述圖形著色 nVertices 。 在我們的範例中,我們會尋找四個圖形的色彩-使用最多四個色彩的色彩,並以整數0到3進行編碼。
我們需要在位元字串中表示著色,因此,我們將使用長度為 2 * nVertices 的位元字串,其中第一對位元以頂點 0 的色彩編碼,第二對則以頂點 1 的色彩編碼,依此類推。 我們會將位元儲存成布林值,其中 0 與 1 個位元分別編碼為 false 與 true。 這對位元將使用位元組由小到小的標記法,對整數色彩索引進行編碼,亦即,整數 1 會編碼為 10,且最低有效位元會先儲存。
以下是如何編碼和解讀範例圖形的色彩:
namespace ExploringGroversSearchAlgorithm {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Intrinsic;
@EntryPoint()
operation SolveGraphColoringProblem() : Unit {
// Graph description: hardcoded from the example
let nVertices = 5;
let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];
// Graph coloring: hardcoded from the example
let coloring = [false, false, true, false, false, true, true, true, true, false];
let colors = ["red", "green", "blue", "yellow"];
// Interpret the coloring: split the bit string into 2-bit fragments and convert them to colors.
let colorBits = Chunks(2, coloring);
for i in 0 .. nVertices - 1 {
let colorIndex = BoolArrayAsInt(colorBits[i]);
Message($"Vertex {i} - color #{colorIndex} ({colors[colorIndex]})");
}
}
}
這段程式碼會產生下列輸出:
Vertex 0 - color #0 (red)
Vertex 1 - color #1 (green)
Vertex 2 - color #2 (blue)
Vertex 3 - color #3 (yellow)
Vertex 4 - color #1 (green)
當我們在量子程式中使用圖形著色時,我們會使用相同的編碼方式,但搭配基礎狀態 $|0\rangle$ 與 $|1\rangle$,而不是傳統的位元 false 與 true。 相同的色著色以 10 個量子位元狀態 $|0010011110\rangle$ 表示。
實作 Oracle
針對指定的函式實作量子 Oracle 的一般方法如下所示:
將傳統函式分解成容易實作的小型建置組塊。
您可以使用基本邏輯閘道實作任何布林值函式。 您可以直接使用基本邏輯閘道取得函式的低層級表示法,或利用由 Q# 程式庫作業所實作的更高層級建置組塊。
將每個傳統區塊取代為將其實作為標記 Oracle 的一系列量子閘道。
您可以使用一或多個量子閘道實作每個基本邏輯閘道。 有時候我們需要配置額外的量子位元來保存閘道的運算結果。 例如,
傳統 NOT 閘道相當於 X 閘道。
傳統 XOR 閘道可以使用 CNOT 閘道 (控制的 X 閘道) 實作。
傳統 AND 閘道可以使用 Toffoli 閘道 (雙重控制的 X 閘道) 與一個額外的量子位元來實現。
如果演算法要求階段 Oracle,請將標記 Oracle 轉換成階段 Oracle。
此步驟會使用稱為「階段 Kickback」的標準技巧:亦即,將標記 Oracle 套用到量子位元的輸入陣列,而特定狀態的目標量子位元會在將階段 Oracle 套用到輸入陣列時,具有相同的效果。
讓我們來看看這種方法如何解決我們的頂點著色問題!
步驟 1: 檢查兩個頂點的色彩是否相同
檢查指定的圖形著色是否有效的最小建置組塊會採用一對由邊緣連接的頂點,並檢查其指派的色彩是否相同。
實作此檢查的作業 (MarkColorEquality) 必須採用兩個 2 量子位元陣列作為輸入,以代表頂點的色彩,以及我們將在色彩相同時,透過翻轉其狀態來標示比較結果的量子位元。
為比較量子位元陣列,我們要其對應的位元互相比較;如果所有成對的位元都相同,則陣列相同,而且儲存在這些陣列中的頂點色彩會相同。
若要比較一對位,我們可以計算其 XOR:如果結果為0,則位相同;否則就不同。
以下是執行這項檢查的 Q # 程式碼,並用它來比較兩個量子位陣列: $ | 00 \ rangle $ 狀態中的第一個,而第二個則是所有基礎狀態的相同迭加。
namespace ExploringGroversSearchAlgorithm {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Intrinsic;
operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
within {
// Iterate over pairs of qubits in matching positions in c0 and c1.
for (q0, q1) in Zipped(c0, c1) {
// Compute XOR of bits q0 and q1 in place (storing it in q1).
CNOT(q0, q1);
}
} apply {
// If all computed XORs are 0, the bit strings are equal - flip the state of the target.
(ControlledOnInt(0, X))(c1, target);
}
}
@EntryPoint()
operation ShowColorEqualityCheck() : Unit {
use (c0, c1, target) = (Qubit[2], Qubit[2], Qubit());
// Leave register c0 in the |00⟩ state.
// Prepare a quantum state that is a superposition of all possible colors on register c1.
ApplyToEach(H, c1);
// Output the initial state of qubits c1 and target.
// We do not include the state of qubits in the register c0 for brevity,
// since they will remain |00⟩ throughout the program.
Message("The starting state of qubits c1 and target:");
DumpRegister((), c1 + [target]);
// Compare registers and mark the result in target qubit.
MarkColorEquality(c0, c1, target);
Message("");
Message("The state of qubits c1 and target after the equality check:");
DumpRegister((), c1 + [target]);
// Return the qubits to |0⟩ state before releasing them.
ResetAll(c1 + [target]);
}
}
within ... apply 陳述式會在量子運算中實作為通用模式:套用 within 與 apply 區塊的陳述式,然後「復原」within 區塊。 我們將其用於確認套用我們的檢查並不會對輸入量子位元產生非預期的效果。
注意
DumpRegister 函式類似於先前的課程模組中使用的 DumpMachine 函式。 不過,其會列印量子位元「子集」狀態的相關資訊,而不是程式所使用的所有量子位元。 在目前的完整狀態模擬器實作中,只有在該暫存器未與其餘的量子位元相互糾纏時,才可以使用 DumpRegister。
以下是此程式碼的輸出:
The starting state of qubits c1 and target:
# wave function for qubits with ids (least to most significant): 2;3;4
∣0❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣1❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣2❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣3❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣4❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣5❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣6❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣7❭: 0.000000 + 0.000000 i == [ 0.000000 ]
The state of qubits c1 and target after the equality check:
# wave function for qubits with ids (least to most significant): 2;3;4
∣0❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣1❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣2❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣3❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣4❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣5❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣6❭: 0.000000 + 0.000000 i == [ 0.000000 ]
∣7❭: 0.000000 + 0.000000 i == [ 0.000000 ]
注意
提醒您,DumpMachine/DumpRegister 輸出中的索引是以位元組由小到大編碼。 因此,索引 |1❭ 會對應到位元字串 100,且最低有效位元會先儲存。
我們看到,一開始系統狀態是
$$|00\rangle_\textrm{c0} \otimes \frac12\big(|00\rangle + |10\rangle + |01\rangle + |11\rangle\big)_\textrm{c1} \otimes |0\rangle_\textrm{target}.$$
套用相等檢查之後,暫存器 c0 的狀態不會變更 (您可以透過新增另一個 DumpRegister 呼叫來進行驗證),但暫存器 c1 與 target 量子位元組合狀態的幅度則會變更。
$|00\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target}$ 狀態的幅度變成 0,而 $|00\rangle_\textrm{c1} \otimes |1\rangle_\textrm{target}$ 狀態的幅度則變成 $0.25$。 請注意,這兩個幅度值不只會變更,而且還會隨著套用此檢查的結果而交換。
實際上,由於狀態 $|00\rangle_\textrm{c0} \otimes |00\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target}$ 中編碼的色彩相等,因此,此基礎狀態的 target 量子位元狀態會翻轉,從而得到所產生的狀態
$$|00\rangle_\textrm{c0} \otimes \frac12\big(|00\rangle_\textrm{c1} \otimes |1\rangle_\textrm{target} + |10\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} + |01\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} + |11\rangle_\textrm{c1} \otimes |0\rangle_\textrm{target} \big).$$
注意
請注意,target 量子位元會變成與暫存器 c1 互相糾纏:您無法再將其狀態分開!
如果我們要評估的函式值對所有輸入都是相同的,則目標量子位元將會與輸入暫存器維持未互相糾纏狀態,並改為儲存此值。 在我們的案例中,有些輸入會產生 $f(x) = 0$,而有些輸入則會產生 $f(x) = 1$,因此您無法再將輸入相關資訊與輸出相關資訊分開。
步驟 2: 檢查頂點著色是否有效
既然我們已經知道如何檢查任兩個頂點的色彩是否不同,我們就可以表示頂點著色驗證,如下所示:
逐一查看以邊緣連接的所有成對頂點。
針對每個配對,檢查這些頂點的色彩是否不同。
如果所有成對的頂點都符合此條件,則著色是有效的。
若要將這些步驟實作為量子作業,我們必須配置額外的量子位元來儲存成對色彩比較的結果,每個邊緣一個量子位元。 我們從 $|0\rangle$ 狀態中的這些量子位元開始,並使用我們在上面看到的 MarkColorEquality 作業,比較每個配對中的頂點色彩;如果對應的頂點配對色彩相同,其會將量子位元的狀態翻轉為 $|1\rangle$。
最後,我們會計算最終的結果。 如果所有配置的額外量子位元都處於 $|0\rangle$ 狀態,我們會翻轉目標量子位元的狀態,表示頂點著色是有效的。
以下是可驗證頂點著色是否有效的 Q # 程式碼。
namespace ExploringGroversSearchAlgorithm {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Intrinsic;
operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
within {
// Iterate over pairs of qubits in matching positions in c0 and c1.
for (q0, q1) in Zipped(c0, c1) {
// Compute XOR of bits q0 and q1 in place (storing it in q1).
CNOT(q0, q1);
}
} apply {
// If all computed XORs are 0, the bit strings are equal - flip the state of the target.
(ControlledOnInt(0, X))(c1, target);
}
}
operation MarkValidVertexColoring(
edges : (Int, Int)[],
colorsRegister : Qubit[],
target : Qubit
) : Unit is Adj+Ctl {
let nEdges = Length(edges);
// Split the register that encodes the colors into an array of two-qubit registers, one per color.
let colors = Chunks(2, colorsRegister);
// Allocate one extra qubit per edge to mark the edges that connect vertices with the same color.
use conflictQubits = Qubit[nEdges];
within {
for ((start, end), conflictQubit) in Zipped(edges, conflictQubits) {
// Check that the endpoints have different colors: apply MarkColorEquality operation;
// if the colors are the same, the result will be 1, indicating a conflict.
MarkColorEquality(colors[start], colors[end], conflictQubit);
}
} apply {
// If there are no conflicts (all qubits are in 0 state), the vertex coloring is valid.
(ControlledOnInt(0, X))(conflictQubits, target);
}
}
@EntryPoint()
operation ShowColoringValidationCheck() : Unit {
// Graph description: hardcoded from the example
let nVertices = 5;
let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)];
// Graph coloring: hardcoded from the example
let coloring = [false, false, true, false, false, true, true, true, false, true];
use (coloringRegister, target) = (Qubit[2 * nVertices], Qubit());
// Encode the coloring in the quantum register:
// apply an X gate to each qubit that corresponds to "true" bit in the bit string.
ApplyPauliFromBitString(PauliX, true, coloring, coloringRegister);
// Apply the operation that will check whether the coloring is valid.
MarkValidVertexColoring(edges, coloringRegister, target);
// Print validation result.
let isColoringValid = M(target) == One;
Message($"The coloring is {isColoringValid ? "valid" | "invalid"}");
// Return the qubits to |0⟩ state before releasing them.
ResetAll(coloringRegister);
}
}
以下是此程式碼的輸出:
The coloring is valid
額外練習
對著色與圖形結構進行實驗,以了解哪些有效以及哪些無效。 此圖形的無效著色範例為 [false, false, true, false, false, true, true, true, true, true],其描述具有頂點 3 與 4 的圖形獲指派相同的色彩。
修改程式碼以便在輸入疊加上執行,並查看發生什麼情況。
步驟 3: 將標記 Oracle 轉換為階段 Oracle
現在,我們有一個標記 Oracle,亦即,標示表示額外量子位元狀態中有效著色之量子位元狀態的作業。 如何將其用於實作階段 Oracle (亦即,使用其階段標示此類狀態的另一個作業)?
我們可以使用所謂的「階段 Kickback 技巧」來執行此動作:
在 $\frac{1}{\sqrt2}(|0\rangle - |1\rangle)$ 狀態下配置額外的量子位元。
使用這個額外的量子位址作為目標,套用標示 Oracle $U_\textrm{mark}$。
在此步驟中,對著色編碼的暫存器會發生什麼狀況?
如果基礎狀態 $ | x\rangle $ 編碼不正確 色彩,則狀態不會變更。
如果基礎狀態 $|x\rangle$ 對「有效」著色進行編碼,則作業 $U_\textrm{mark} 會翻轉額外量子位元的狀態,並將其轉換成 $\frac{1}{\sqrt2}(|1\rangle - |0\rangle)$,這相當於將整個狀態乘以 $-1$。
如果您將這些步驟套用到基礎狀態,就無法分辨差異,亦即,將無法觀察全域階段。 但是,如果您將這些步驟套用至疊加狀態,就會看到對有效著色進行編碼的基本狀態會取得 $-1$ 相對階段,這完全是我們需要階段作業得到的效果!
以下是階段 kickback 技巧在 Q # 中的樣子。 我們將使用實作色彩檢查的作業 (可讓您更容易在輸出中看到效果),但是您可以對實作標記 Oracle 的任何作業使用相同的技巧。
namespace ExploringGroversSearchAlgorithm {
open Microsoft.Quantum.Arrays;
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Intrinsic;
operation MarkColorEquality(c0 : Qubit[], c1 : Qubit[], target : Qubit) : Unit is Adj+Ctl {
within {
// Iterate over pairs of qubits in matching positions in c0 and c1.
for (q0, q1) in Zipped(c0, c1) {
// Compute XOR of bits q0 and q1 in place (storing it in q1).
CNOT(q0, q1);
}
} apply {
// If all computed XORs are 0, the bit strings are equal - flip the state of the target.
(ControlledOnInt(0, X))(c1, target);
}
}
operation ApplyMarkingOracleAsPhaseOracle(
markingOracle : ((Qubit[], Qubit[], Qubit) => Unit is Adj),
c0 : Qubit[],
c1 : Qubit[]
) : Unit is Adj {
use target = Qubit();
within {
// Put the target qubit into the |-⟩ state.
X(target);
H(target);
} apply {
// Apply the marking oracle; since the target is in the |-⟩ state,
// flipping the target if the register state satisfies the condition
// will apply a -1 relative phase to the register state.
markingOracle(c0, c1, target);
}
}
@EntryPoint()
operation ShowPhaseKickbackTrick() : Unit {
use (c0, c1) = (Qubit[2], Qubit[2]);
// Leave register c0 in the |00⟩ state.
// Prepare a quantum state that is a superposition of all possible colors on register c1.
ApplyToEach(H, c1);
// Output the initial state of qubits c1.
// We do not include the state of qubits in the register c0 for brevity,
// since they will remain |00⟩ throughout the program.
Message("The starting state of qubits c1:");
DumpRegister((), c1);
// Compare registers and mark the result in their phase.
ApplyMarkingOracleAsPhaseOracle(MarkColorEquality, c0, c1);
Message("");
Message("The state of qubits c1 after the equality check:");
DumpRegister((), c1);
// Return the qubits to |0⟩ state before releasing them.
ResetAll(c1);
}
}
The starting state of qubits c1:
# wave function for qubits with ids (least to most significant): 2;3
∣0❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣1❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣2❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣3❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
The state of qubits c1 after the equality check:
# wave function for qubits with ids (least to most significant): 2;3
∣0❭: -0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 3.14159 rad ]
∣1❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣2❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
∣3❭: 0.500000 + 0.000000 i == ***** [ 0.250000 ] --- [ 0.00000 rad ]
您可以看到,$|00\rangle$ 狀態的幅度實際上已變更為 $-0.5$,因此,相較於其他基礎狀態,其相對階段現在為 $-1$。
恭喜,space explorer! 現在您知道如何為圖形著色問題建置一個完整的量子 Oracle!
在下一個單元中,您最後會練習您的技能,以執行格羅弗的搜尋演算法來判斷我們需要指派的最小頻寬數目。