量子コンピュータでALUを作ってみた(実装編)
はじめに
オンライン公開伴走型量子コンピューティング講座QC4U2卒業生のluna_moonlightです。前回、ALUを量子回路で表現する方法について解説しました。前回の記事を読んでいない方は、ぜひ読んでみてください。
今回は、前回作成した量子ALUを実際に量子コンピュータに渡して計算する方法について解説していきます。
量子コンピュータ実機
前回作成した量子回路版ALUの回路図を下に示します。これを元に早速実装していきましょう。
実装
まず、IBMの量子コンピュータを使うために、IBM Quantumでアカウントを作成します。
次に、必要な外部ライブラリをインストールしましょう。量子コンピュータを使うためにqiskit, qiskit_ibm_providerと測定結果の可視化のためにmatplotlibをインストールします。
pip install qiskit
pip install qiskit-ibm-provider
pip install matplotlib
必要な外部ライブラリをインストールしたところで、さっそくプログラムを書いていきましょう。コードの全体像はこちらです。
コードの全体像
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_provider import IBMProvider
'''----------------パラメータ----------------'''
shots = 10000 # タスクを実行する回数
IBM_token = 'XXXXX' # API token(個人のものを使用)
solver = 'ibm_kyoto' # 量子コンピュータ
'''----------------------------------------'''
# 半加算器
def HalfAdder():
qc = QuantumCircuit(4)
qc.ccx(0, 1, 2)
qc.cx(0, 3)
qc.cx(1, 3)
Uhalf_adder = qc.to_gate()
Uhalf_adder.name = "Half-Adder"
return Uhalf_adder
# 全加算器
def FullAdder():
HA = HalfAdder()
qc = QuantumCircuit(8)
qc.append(HA, [0, 1, 3, 4])
qc.append(HA, [4, 2, 5, 7])
qc.cx(3, 6)
qc.cx(5, 6)
qc.ccx(3, 5, 6)
Ufull_adder = qc.to_gate()
Ufull_adder.name = "Full-Adder"
return Ufull_adder
# マルチプレクサ
def MUX():
qc = QuantumCircuit(6)
qc.ccx(1, 2, 3)
qc.x(2)
qc.ccx(0, 2, 4)
qc.cx(3, 5)
qc.cx(4, 5)
qc.ccx(3, 4, 5)
qc.x(2)
Umux = qc.to_gate()
Umux.name = "MUX"
return Umux
# ALU
FA = FullAdder()
mux = MUX()
qc = QuantumCircuit(32, 2)
'''----------------入力----------------'''
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
# qc.x(8) # B0
# qc.x(21) # A1
# qc.x(22) # B1
'''-----------------------------------'''
qc.cx(1, 9)
qc.cx(8, 9)
qc.append(mux, [1, 2, 3, 4, 5, 6])
qc.append(FA, [7, 9, 6, 10, 11, 12, 13, 14])
qc.append(mux, [14, 13, 0, 15, 16, 17])
qc.cx(1, 23)
qc.cx(22, 23)
qc.append(mux, [13, 2, 3, 18, 19, 20])
qc.append(FA, [21, 23, 20, 24, 25, 26, 27, 28])
qc.append(mux, [28, 27, 0, 29, 30, 31])
qc.measure(17, 0)
qc.measure(31, 1)
# タスクの実行
provider = IBMProvider(token=IBM_token)
backend = provider.get_backend(solver)
qc = transpile(qc, backend=backend)
job = backend.run(qc, shots=shots)
ここからは、このコードを分けて解説していきます。
初期化
以下のコードは、必要なライブラリのインポートや変数、定数を定義しています。パラメータと書かれている部分を変更することで、実行回数や使う量子コンピュータを変更することができます。
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_provider import IBMProvider
'''----------------パラメータ----------------'''
shots = 10000 # タスクを実行する回数
IBM_token = 'XXXXX' # API token(個人のものを使用)
solver = 'ibm_kyoto' # 量子コンピュータ
'''----------------------------------------'''
半加算器の定義
ここでは、量子ゲートで表現した半加算器を定義しています。
# 半加算器
def HalfAdder():
qc = QuantumCircuit(4)
qc.ccx(0, 1, 2)
qc.cx(0, 3)
qc.cx(1, 3)
Uhalf_adder = qc.to_gate()
Uhalf_adder.name = "Half-Adder"
return Uhalf_adder
全加算器の定義
ここでは、量子ゲートで表現した全加算器を定義しています。
# 全加算器
def FullAdder():
HA = HalfAdder()
qc = QuantumCircuit(8)
qc.append(HA, [0, 1, 3, 4])
qc.append(HA, [4, 2, 5, 7])
qc.cx(3, 6)
qc.cx(5, 6)
qc.ccx(3, 5, 6)
Ufull_adder = qc.to_gate()
Ufull_adder.name = "Full-Adder"
return Ufull_adder
マルチプレクサの定義
ここでは、量子ゲートで表現したマルチプレクサを定義しています。
# マルチプレクサ
def MUX():
qc = QuantumCircuit(6)
qc.ccx(1, 2, 3)
qc.x(2)
qc.ccx(0, 2, 4)
qc.cx(3, 5)
qc.cx(4, 5)
qc.ccx(3, 4, 5)
qc.x(2)
Umux = qc.to_gate()
Umux.name = "MUX"
return Umux
ALUの定義
ここでは、量子ゲートで表現したALUを定義しています。
# ALU
FA = FullAdder()
mux = MUX()
qc = QuantumCircuit(32, 2)
'''----------------入力----------------'''
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
# qc.x(8) # B0
# qc.x(21) # A1
# qc.x(22) # B1
'''-----------------------------------'''
qc.cx(1, 9)
qc.cx(8, 9)
qc.append(mux, [1, 2, 3, 4, 5, 6])
qc.append(FA, [7, 9, 6, 10, 11, 12, 13, 14])
qc.append(mux, [14, 13, 0, 15, 16, 17])
qc.cx(1, 23)
qc.cx(22, 23)
qc.append(mux, [13, 2, 3, 18, 19, 20])
qc.append(FA, [21, 23, 20, 24, 25, 26, 27, 28])
qc.append(mux, [28, 27, 0, 29, 30, 31])
qc.measure(17, 0)
qc.measure(31, 1)
タスクの実行
ここでは、定義したALUをIBMの量子コンピュータに渡して実行しています。
# タスクの実行
provider = IBMProvider(token=IBM_token)
backend = provider.get_backend(solver)
qc = transpile(qc, backend=backend)
job = backend.run(qc, shots=shots)
結果
上のコード、つまり
from qiskit.visualization import plot_distribution
from qiskit_ibm_provider import IBMProvider
import matplotlib.pyplot as plt
'''----------------パラメータ----------------'''
IBM_token = 'XXXXX' # API token(個人のものを使用)
job_ID = 'XXXXX' # 実行したジョブのID
'''----------------------------------------'''
# 結果の取得
provider = IBMProvider(token=IBM_token)
job = provider.retrieve_job(job_id=job_ID)
counts = job.result().get_counts()
# 結果の表示
plot_distribution(counts)
plt.show()
すると、以下のような出力が得られました。残念なことに
シミュレータ
先ほどのように、実機の量子コンピュータでは正しい結果は得られませんでした。なので、エラーのないシミュレータで前回作ったALUの計算を実行しようと思います。
実装
必要な外部ライブラリをインストールしましょう。量子コンピュータのシミュレーションを行うためにqiskit_aerを追加でインストールします。
pip install qiskit-aer
必要な外部ライブラリをインストールしたところで、さっそくプログラムを書いていきましょう。コードの全体像はこちらです。
コードの全体像
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_distribution
from qiskit_aer import Aer
import matplotlib.pyplot as plt
'''----------------パラメータ----------------'''
shots = 10000 # シミュレーション回数
'''----------------------------------------'''
# 半加算器
def HalfAdder():
qc = QuantumCircuit(4)
qc.ccx(0, 1, 2)
qc.cx(0, 3)
qc.cx(1, 3)
Uhalf_adder = qc.to_gate()
Uhalf_adder.name = "Half-Adder"
return Uhalf_adder
# 全加算器
def FullAdder():
HA = HalfAdder()
qc = QuantumCircuit(8)
qc.append(HA, [0, 1, 3, 4])
qc.append(HA, [4, 2, 5, 7])
qc.cx(3, 6)
qc.cx(5, 6)
qc.ccx(3, 5, 6)
Ufull_adder = qc.to_gate()
Ufull_adder.name = "Full-Adder"
return Ufull_adder
# マルチプレクサ
def MUX():
qc = QuantumCircuit(6)
qc.ccx(1, 2, 3)
qc.x(2)
qc.ccx(0, 2, 4)
qc.cx(3, 5)
qc.cx(4, 5)
qc.ccx(3, 4, 5)
qc.x(2)
Umux = qc.to_gate()
Umux.name = "MUX"
return Umux
# ALU
FA = FullAdder()
mux = MUX()
qc = QuantumCircuit(32, 2)
'''----------------入力----------------'''
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
# qc.x(8) # B0
# qc.x(21) # A1
# qc.x(22) # B1
'''-----------------------------------'''
qc.cx(1, 9)
qc.cx(8, 9)
qc.append(mux, [1, 2, 3, 4, 5, 6])
qc.append(FA, [7, 9, 6, 10, 11, 12, 13, 14])
qc.append(mux, [14, 13, 0, 15, 16, 17])
qc.cx(1, 23)
qc.cx(22, 23)
qc.append(mux, [13, 2, 3, 18, 19, 20])
qc.append(FA, [21, 23, 20, 24, 25, 26, 27, 28])
qc.append(mux, [28, 27, 0, 29, 30, 31])
qc.measure(17, 0)
qc.measure(31, 1)
# シミュレーションの実行
simulator = Aer.get_backend('qasm_simulator')
qc = transpile(qc, simulator)
job = simulator.run(qc, shots=shots)
counts = job.result().get_counts(qc)
# 結果の表示
plot_distribution(counts)
plt.show()
ここからは、このコードを分けて解説していきます。
初期化
以下のコードは、必要なライブラリのインポートや変数、定数を定義しています。パラメータと書かれている部分を変更することで、シミュレーションの実行回数を変更することができます。
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_distribution
from qiskit_aer import Aer
import matplotlib.pyplot as plt
'''----------------パラメータ----------------'''
shots = 10000 # シミュレーション回数
'''----------------------------------------'''
半加算器の定義
ここでは、量子ゲートで表現した半加算器を定義しています。これは、実機量子コンピュータのときと変わりません。
# 半加算器
def HalfAdder():
qc = QuantumCircuit(4)
qc.ccx(0, 1, 2)
qc.cx(0, 3)
qc.cx(1, 3)
Uhalf_adder = qc.to_gate()
Uhalf_adder.name = "Half-Adder"
return Uhalf_adder
全加算器の定義
ここでは、量子ゲートで表現した全加算器を定義しています。これは、実機量子コンピュータのときと変わりません。
# 全加算器
def FullAdder():
HA = HalfAdder()
qc = QuantumCircuit(8)
qc.append(HA, [0, 1, 3, 4])
qc.append(HA, [4, 2, 5, 7])
qc.cx(3, 6)
qc.cx(5, 6)
qc.ccx(3, 5, 6)
Ufull_adder = qc.to_gate()
Ufull_adder.name = "Full-Adder"
return Ufull_adder
マルチプレクサの定義
ここでは、量子ゲートで表現したマルチプレクサを定義しています。これは、実機量子コンピュータのときと変わりません。
# マルチプレクサ
def MUX():
qc = QuantumCircuit(6)
qc.ccx(1, 2, 3)
qc.x(2)
qc.ccx(0, 2, 4)
qc.cx(3, 5)
qc.cx(4, 5)
qc.ccx(3, 4, 5)
qc.x(2)
Umux = qc.to_gate()
Umux.name = "MUX"
return Umux
ALUの定義
ここでは、量子ゲートで表現したALUを定義しています。これは、実機量子コンピュータのときと変わりません。
# ALU
FA = FullAdder()
mux = MUX()
qc = QuantumCircuit(32, 2)
'''----------------入力----------------'''
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
# qc.x(8) # B0
# qc.x(21) # A1
# qc.x(22) # B1
'''-----------------------------------'''
qc.cx(1, 9)
qc.cx(8, 9)
qc.append(mux, [1, 2, 3, 4, 5, 6])
qc.append(FA, [7, 9, 6, 10, 11, 12, 13, 14])
qc.append(mux, [14, 13, 0, 15, 16, 17])
qc.cx(1, 23)
qc.cx(22, 23)
qc.append(mux, [13, 2, 3, 18, 19, 20])
qc.append(FA, [21, 23, 20, 24, 25, 26, 27, 28])
qc.append(mux, [28, 27, 0, 29, 30, 31])
qc.measure(17, 0)
qc.measure(31, 1)
シミュレーションの実行
ここでは、定義したALUのシミュレーションを実行し、結果を取得しています。
# シミュレーションの実行
simulator = Aer.get_backend('qasm_simulator')
qc = transpile(qc, simulator)
job = simulator.run(qc, shots=shots)
counts = job.result().get_counts(qc)
結果の表示
ここでは、実行結果をヒストグラムで表示しています。
# 結果の表示
plot_distribution(counts)
plt.show()
結果
上のコードを実行すると以下のようなエラーが出てしまいました。前回作成したALUは32qubits使用しています。しかし、30qubitsまでしかtranspileできないためこのようなエラーが発生してしまいました。
qiskit.transpiler.exceptions.TranspilerError: 'Number of qubits (32) in circuit-167 is greater than maximum (30) in the coupling_map'
ビット数削減
先ほどのエラーを解消するために、2qubitsの削減を行なっていきます。前回作成したALUの量子回路をみると、
XORゲート
先ほど紹介したXORゲートは、CXゲートを使って下のように表せます。
計算すると下のようになり、確かにXORゲートの結果と一致しています。
行列計算
ALU
先ほどのXORゲートを使ってALUのビット数を削減すると下のように表せます。
これに伴ってプログラムのALU定義部分を以下のように変更します。これで準備が整ったので、早速ALUの計算をしてみましょう。
# ALU
FA = FullAdder()
mux = MUX()
qc = QuantumCircuit(30, 2)
'''----------------入力----------------'''
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
# qc.x(8) # B0
# qc.x(20) # A1
# qc.x(21) # B1
'''-----------------------------------'''
qc.cx(1, 8)
qc.append(mux, [1, 2, 3, 4, 5, 6])
qc.append(FA, [7, 8, 6, 9, 10, 11, 12, 13])
qc.append(mux, [13, 12, 0, 14, 15, 16])
qc.cx(1, 21)
qc.append(mux, [12, 2, 3, 17, 18, 19])
qc.append(FA, [20, 21, 19, 22, 23, 24, 25, 26])
qc.append(mux, [26, 25, 0, 27, 28, 29])
qc.measure(16, 0)
qc.measure(29, 1)
加算
# qc.x(2) # W
# qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
qc.x(7) # A0
qc.x(8) # B0
# qc.x(20) # A1
# qc.x(21) # B1
すると、以下のような出力が得られました。
減算
# qc.x(2) # W
qc.x(1) # X
# qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
qc.x(8) # B0
# qc.x(20) # A1
# qc.x(21) # B1
すると、以下のような出力が得られました。
bitwise AND
# qc.x(2) # W
# qc.x(1) # X
qc.x(3) # Y
qc.x(0) # Z
qc.x(7) # A0
# qc.x(8) # B0
qc.x(20) # A1
qc.x(21) # B1
すると、以下のような出力が得られました。
bitwise OR
qc.x(2) # W
# qc.x(1) # X
qc.x(3) # Y
qc.x(0) # Z
qc.x(7) # A0
# qc.x(8) # B0
# qc.x(20) # A1
# qc.x(21) # B1
すると、以下のような出力が得られました。
bitwise XOR
# qc.x(2) # W
# qc.x(1) # X
qc.x(3) # Y
# qc.x(0) # Z
qc.x(7) # A0
# qc.x(8) # B0
qc.x(20) # A1
qc.x(21) # B1
すると、以下のような出力が得られました。
bitwise XNOR
qc.x(2) # W
# qc.x(1) # X
qc.x(3) # Y
# qc.x(0) # Z
# qc.x(7) # A0
qc.x(8) # B0
qc.x(20) # A1
qc.x(21) # B1
すると、以下のような出力が得られました。
重ね合わせ入力
せっかくなので、量子コンピュータらしいことをしてみましょう。試しに、重ね合わせ状態でANDの計算をしてみましょう。この演算を行うには、プログラムの入力部分を以下のように変更して実行します。
# qc.x(2) # W
# qc.x(1) # X
qc.x(3) # Y
qc.x(0) # Z
qc.h(7) # A0
qc.h(8) # B0
qc.h(20) # A1
qc.h(21) # B1
すると、以下のような出力が得られました。
理想 | ||||
結果 |
真理値表
まとめ
この記事では、前回作成した量子ALUを実際に量子コンピュータに渡して計算する方法について解説しました。残念ながら、実機の量子コンピュータではエラーが非常に多く、正しく計算できているかわからなかったので、シミュレータでの計算方法についても解説しました。ぜひ自分でも入力値をいじって遊んでみてください。
Discussion