Skip to content

[Qiskit] 使用量子電路實作半加法器(Half Adder)

隨著時代的進步,現在人們已經可以在舒適的家裡進行量子電腦的程式設計了。但是話說回來,我們究竟要創造什麼呢?什麼是量子程式設計呢?說到底,什麼又是量子電腦呢?

這些問題的答案,其實可以通過與傳統數位電腦的比較來得到。

當然,也並不是所有人對於傳統電腦的工作原理都十分清楚。所以在本篇文章當中,我們來稍微了解一下這些設備背後的工作原理,並為了過渡到量子運算(quantum computing),我們也會使用一些基於量子的工具(其實也就是 Qiskit)來實作一個半加法器(half adder)。

為了使用 Qiskit 套件,我們需要確認已經閱讀過:

並已經使用過 Python、同時在 Python 環境中也安裝好了 Qiskit 套件。

我們的實作內容分成以下幾個部分:

  1. 將資訊拆分為 bits位元
  2. 使用圖(diagram)來計算
  3. 建立第一個量子電路
  4. 範例:建立一個加法器(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 來完成一個半加法器的電路。這將包含編碼後的輸入、計算的電路、以及最終提取結果的部分。

量子位元上的的 q0q1 是我們的輸入,而最右側的 q2q3 則是我們的輸出。我們要做的計算其實很簡單,只有四種情況:

  • 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 做了兩件事:

  1. 它會檢查兩個輸入是否一致
  2. 它會把是否一致的結果儲存在目標量子位元(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_0q_1 並不相同,覆蓋 q_11

後面兩個 M 符號的裝置是測量的裝置,只是為了擷取 q_0q_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_0q_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_0q_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,我們就可以建立程式來完成任意尺寸的輸入資料集。


References


Read More

Leave a Reply