Last Updated on 2022-12-15 by Clay
隨著時代的進步,現在人們已經可以在舒適的家裡進行量子電腦的程式設計了。但是話說回來,我們究竟要創造什麼呢?什麼是量子程式設計呢?說到底,什麼又是量子電腦呢?
這些問題的答案,其實可以通過與傳統數位電腦的比較來得到。
當然,也並不是所有人對於傳統電腦的工作原理都十分清楚。所以在本篇文章當中,我們來稍微了解一下這些設備背後的工作原理,並為了過渡到量子運算(quantum computing),我們也會使用一些基於量子的工具(其實也就是 Qiskit)來實作一個半加法器(half adder)。
為了使用 Qiskit 套件,我們需要確認已經閱讀過:
並已經使用過 Python、同時在 Python 環境中也安裝好了 Qiskit 套件。
我們的實作內容分成以下幾個部分:
- 將資訊拆分為 bits(位元)
- 使用圖(diagram)來計算
- 建立第一個量子電路
- 範例:建立一個加法器(Adder)電路
1. 將資訊拆分為 bits(位元)
首先我們得知道『位元』(bits)是什麼。簡單來說,這世界上所有的東西,我們都可以用最單純的兩個符號來表示:0 或是 1。在傳統數位電路中是以電位高低來區分,低電位代表 0、高電位代表 1。
使用 0 和 1 來表示資訊又是怎麼辦到的呢?一個簡單的例子就是數字(number)。
我們日常所使用的數字,都是基於十進制來表示的。例如 9213 這個數字,我們可以表達成:
如果這樣表示不出來十進制的意思,我們再換成另外一種表達方式:
我們都能很清楚地理解這個表達式的意思。但是在 0 和 1 的二進制表示方式中,9213 是表示成 10001111111101。
這串數字什麼什麼意思呢?
其實跟十進制相同,每一個位置 n 的數字,都會乘以 2 的 n-1 次方。然後在數值等於 2 時往前進位。
例如:
1 可以表示成 001(前面的 0 可以省略)
2 可以表示成 010
3 可以表示成 011
4 可以表示成 100
... 依此類推
而不僅僅是數字。無論是文字或標點符號,你都可以找到一串代表他們的二進制編碼。
這就是二進制在數位世界中工作的模樣。我們可以透過這些位元資料來儲存文件、圖片、音訊...... 等等各式各樣的檔案。
就像傳統數位電腦一般,量子電腦也是基於同樣的方式來運作,不過使用的卻是一種被稱為量子位元(qubits)的特殊位元。在本篇文章中,我們並不會深入探討量子力學,所以我們就假設我們能像操作傳統位元一般操作量子位元。
2. 使用圖(diagram)來計算
無論是位元還是量子位元,我們都需要操作它們,好讓我們的輸入(inputs)變成我們所需要的輸出(outputs)。而想要表示這中間的過程,一個常見的方法是電路圖(circuit diagram)。
在下圖中,左邊是我們的輸入、右邊則是我們的輸出。中間的操作被稱為門(gate),如果有其他常見的翻譯名稱,歡迎隨時告訴我,我不確定中文翻譯,因為大二大三上的電路課程都是原文書,加上那時候的我混到爆炸... QwQ
不過上圖是一個經典的、位元為主的傳統電腦的電路圖,在這裡可以不用確實了解這個電路的細節(至少這裡還不用),只是為了和量子電路做出一個比較。
下面是使用量子電路呈現出一樣的操作過程。
在下面的小節,我們將會解釋該如何建立這樣的電路。最後,你會知道該如何建立上圖的電路、它做了什麼、以及它為什麼很有用。
3. 建立第一個量子電路
在電路中,我們通常做三件事情:
- 把輸入編碼(encoding),亦即把輸入表示成數值型態
- 做實際的運算
- 擷取出輸出結果
在第一個量子電路中,我們來製作關於最後一個步驟:輸出結果。
我們來建立一個量子電路,它有 8 個量子位元(qubits)以及 8 個輸出(outputs)。
首先,匯入所有會用到的套件。
from qiskit import QuantumCircuit, assemble, Aer
from qiskit.visualization import plot_histogram
然後建立擁有 8 個量子位元的量子電路。
qc_output = QuantumCircuit(8)
qc_output.draw(initial_state=True)
Output(現在我們還什麼都看不到):
如果我們希望能看到一些結果,我們可以使用名為 measure_all()
的操作來做到。在測量中,每個量子位元都會得到一個特定值。
qc_output.measure_all()
qc_output.draw(initial_state=True)
Output:
我們可以看到,量子位元一開始總是被初始化為 0,這也是因為我們並沒有設定它們。同時,全 0 這是我們看到它們的最終結果,因為我們中間沒有任何操作。
所以我們最終的結果是:00000000
。
而如果我們多次運行該電路,並把結果繪製成直方圖(histogram),我們也會不斷得到 00000000
這一結果。
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc_output).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
當然,在這段程式中,我們所選用的後端(backend)並不是真正的量子電腦,而是由傳統電腦所模擬的。模擬只有可能模擬少量的量子位元(大約 30 個),但在建立第一個電路時仍是十分好用的工具。
如果要在真實的設備上運行,可以直接將程式碼中的 sim = Aer.get_backend("aer_simulator")
aer_simulator 替換成你所要使用的裝置。
4. 建立一個加法器(Adder)
現在我們來看如何替一段輸入進行編碼(encode),而我們需要的,是 NOT Gate。在傳統電腦中,它就像是翻牌一樣,把 1 變成 0、把 0 變成 1。
在量子位元中,這是一個被稱之為 x
的操作,它完成的工作跟 NOT 一樣。
qc_encode = QuantumCircuit(8)
qc_encode.x(7)
qc_encode.draw()
Output:
然後我們再次提取運作之後的結果。
qc_encode.measure_all()
qc_encode.draw()
Output:
接著是畫出模擬之後的直方圖結果。
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc_encode).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
現在的話,電路產生的結果都是 10000000
。通過翻轉了第 7 個位置的量子位元,現在我們擁有了數字 128(2^7)。
接下來,我們做點簡單的加法。在十進制的世界中,加法對我們來說十分容易。比方說下面的題目:
9213 + 1854 = ?
我們可能會從右到左逐項加起。 3+4=7、1+5=6、2+8=10(進位)、9+1+1=11(進位)...... 最後我們會得到 11067 這個數字。
當然,在二進制的世界中其實也是一樣。
我們從右到左,逐項加起。只要知道 1 + 1 之後會進位,這個加法對我們而言就沒有難度。而在二進制的加法中,一共也只有四種變化:
0+0 = 00 (in decimal, this is 0+0=0)
0+1 = 01 (in decimal, this is 0+1=1)
1+0 = 01 (in decimal, this is 1+0=1)
1+1 = 10 (in decimal, this is 1+1=2)
這被稱為 half adder(半加法器)。我們可以通過電腦來實現它,之後就能利用它做各式各樣的事情。
現在,我們透過 Qiskit 來完成一個半加法器的電路。這將包含編碼後的輸入、計算的電路、以及最終提取結果的部分。
量子位元上的的 q0
和 q1
是我們的輸入,而最右側的 q2
和 q3
則是我們的輸出。我們要做的計算其實很簡單,只有四種情況:
- 0 + 0 = 00
- 0 + 1 = 01
- 1 + 0 = 01
- 1 + 1 = 10
我們會注意到最右邊的結果,在輸入都相同的情況下永遠都是 0;而在輸入不同的情況下則皆為 1。利用我們觀察到的這個狀況,我們可以使用一種叫做 XOR gate 的裝置來完成。在量子電路中,XOR 的工作是由一種叫做 controlled-NOT gate 的裝置來完成,由於名稱很長,所以也稱為 CNOT。
我們來繪制 CNOT 在電路中的模樣:
qc_cnot = QuantumCircuit(2)
qc_cnot.cx(0, 1)
qc_cnot.draw()
Output:
我們會發現它需要使用到兩個量子位元,這是因為上方的小點是用來接受輸入,下方帶有 X 的圖示則是做為目標量子位元(target qubit)。
簡單來說,CNOT 做了兩件事:
- 它會檢查兩個輸入是否一致
- 它會把是否一致的結果儲存在目標量子位元(target qubit)(要注意的是,CNOT的兩個輸入若相同是儲存 0、不相同是儲存 0)
我們來看一個實際使用 CNOT 的例子。
qc = QuantumCircuit(2, 2)
qc.x(0)
qc.cx(0, 1)
qc.measure(0, 0)
qc.measure(1, 1)
qc.draw()
Output:
還記得我們說量子位元預設都是 0 嗎?所以我們可以想像,在 q_0
的量子位元上有一個 NOT gate,所以經過翻轉,q_0
的值會是 1。
接著,我們在 q_1
量子位元上看到了 CNOT,它會檢查到 q_0
和 q_1
並不相同,覆蓋 q_1
為 1。
後面兩個 M
符號的裝置是測量的裝置,只是為了擷取 q_0
和 q_1
的輸出的。
好了,那麼我們的最終輸出會是什麼呢?其實剛剛都已經算好了,q_0
是 1、q_1
也是 1。我們來完善一下程式碼來繪製出結果吧。
qc = QuantumCircuit(2, 2)
qc.x(1)
qc.cx(0, 1)
qc.measure(0, 0)
qc.measure(1, 1)
qc.draw()
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
輸出結果是 11,跟我們設計得一模一樣。
如果我們今天把 q_0
上的 NOT gate 拿掉的話,結果會變成什麼樣子呢?顯然,q_0
和 q_1
都是 0,然後在 CNOT 中由於檢查到兩個輸入相同,所以還是覆蓋掉 q_1
為 0,也就是說一切都沒有變化,最終輸出跟初始值一樣會是 00。
qc = QuantumCircuit(2, 2)
qc.cx(0, 1)
qc.measure(0, 0)
qc.measure(1, 1)
qc.draw()
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
但是我們應該不想要覆蓋掉我們的 q_1
,而是希望儲存在另外一個位元上,好用來完成 XOR 的功能,(因為這就是半加法器最右邊的位元所需要的操作)我們該怎麼辦呢?答案是用上兩個 CNOT。
qc_ha = QuantumCircuit(4, 2)
# Encode inputs in qubits 0 and 1
qc_ha.x(0) # For a=0, remove this line. For a=1, leave it.
qc_ha.x(1) # For b=0, remove this line. For b=1, leave it.
qc_ha.barrier()
# Use cnots to write the XOR of the inputs on qubit 2
qc_ha.cx(0, 2)
qc_ha.cx(1, 2)
qc_ha.barrier()
# Extract outputs
qc_ha.measure(2, 0) # Extract XOR value
qc_ha.measure(3, 1)
qc_ha.draw()
Output:
這個電路圖包含了一個很簡單的邏輯操作:只要輸入(q_0
和 q_1
)同時和 q_2
不同,那麼 CNOT 翻轉位元就會執行兩次,等於什麼都沒變 q_2
還是 0;但若是都與 q_2
相同,等於完全不會翻轉,所以 q_2
還是 0。
qc_ha = QuantumCircuit(4, 2)
# Encode inputs in qubits 0 and 1
qc_ha.x(0) # For a=0, remove this line. For a=1, leave it.
qc_ha.x(1) # For b=0, remove this line. For b=1, leave it.
qc_ha.barrier()
# Use cnots to write the XOR of the inputs on qubit 2
qc_ha.cx(0, 2)
qc_ha.cx(1, 2)
qc_ha.barrier()
# Extract outputs
qc_ha.measure(2, 0) # Extract XOR value
qc_ha.measure(3, 1)
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc_ha).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
這意味著除了輸入的兩個位元都不同,否則我們的輸出永遠都是 00。只有輸入不同時,我們的輸出才會是 01。大家可以自由地試試看。
目前為止,我們的半加法器已經完成了最右邊的位元的運算了。剩下的其實就是進位(carry)了。
進位的條件只有一個,就是輸入兩者都是 1 的情況。所以我們需要有一個機制去檢查兩個輸入都是 1。
這個新的門叫做 Toffoli,但是以傳統電路的說法來看,它實現的功能無非就是 AND gate。(AND 就是兩者都要是 1 才會輸出 1)
在 Qiskit 中,是使用 ccx()
來實作。
qc_ha = QuantumCircuit(4, 2)
# Encode inputs in qubits 0 and 1
qc_ha.x(0) # For a=0, remove this line. For a=1, leave it.
qc_ha.x(1) # For b=0, remove this line. For b=1, leave it.
qc_ha.barrier()
# Use cnots to write the XOR of the inputs on qubit 2
qc_ha.cx(0, 2)
qc_ha.cx(1, 2)
qc_ha.barrier()
# Use ccx to write the AND of the inputs on qubit 3
qc_ha.ccx(0, 1, 3)
qc_ha.barrier()
# Extract outputs
qc_ha.measure(2, 0) # Extract XOR value
qc_ha.measure(3, 1)
qc_ha.draw()
Output:
這是電路圖的模樣,接著我們直接繪製出結果。
qc_ha = QuantumCircuit(4, 2)
# Encode inputs in qubits 0 and 1
qc_ha.x(0) # For a=0, remove this line. For a=1, leave it.
qc_ha.x(1) # For b=0, remove this line. For b=1, leave it.
qc_ha.barrier()
# Use cnots to write the XOR of the inputs on qubit 2
qc_ha.cx(0, 2)
qc_ha.cx(1, 2)
qc_ha.barrier()
# Use ccx to write the AND of the inputs on qubit 3
qc_ha.ccx(0, 1, 3)
qc_ha.barrier()
# Extract outputs
qc_ha.measure(2, 0) # Extract XOR value
qc_ha.measure(3, 1)
sim = Aer.get_backend("aer_simulator")
result = sim.run(qc_ha).result()
counts = result.get_counts()
plot_histogram(counts)
Output:
終於完成了半加法器了!只要熟悉使用 NOT、CNOT、Toffoli,我們就可以建立程式來完成任意尺寸的輸入資料集。