下記のQiitaの記事を読んで実機でも試してみたいと思ったので試してみた。
qiita.com
※注意: 私は量子コンピュータの専門家ではありません。以下の内容は間違って入る可能性が十分にあります。
追記: 以下では Toffoli, CNOT, SWAP を自分で定義していますが、そんなことをする必要はありません。QisKitの標準のゲートで同じことが出来ます。
目次
環境
OS: ArchLinux
import sys, qiskit
print(sys.version)
print("qiskit version:", qiskit.__version__)
3.6.5 (default, May 11 2018, 04:00:52)
[GCC 8.1.0]
qiskit version: 0.5.4
Toffoli gate の実装方法には いくつかあるが今回は以下のものを採用。
シミュレーター
まずはシミュレーターで回路を作ってみる。
import getpass, time, sys
from qiskit import ClassicalRegister, QuantumRegister, load_qasm_string
from qiskit import QuantumCircuit, available_backends, execute, register, get_backend
from qiskit.tools.visualization import plot_histogram, circuit_drawer
def toffoli(c0, c1, t, q, qc):
qc.h(q[2])
qc.cx(q[1],q[2])
qc.tdg(q[2])
qc.cx(q[0],q[2])
qc.t(q[2])
qc.cx(q[1],q[2])
qc.tdg(q[2])
qc.cx(q[0],q[2])
qc.t(q[1])
qc.t(q[2])
qc.h(q[2])
qc.cx(q[0],q[1])
qc.t(q[0])
qc.tdg(q[1])
qc.cx(q[0],q[1])
q = QuantumRegister(4)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)
qc.x(q[0])
qc.x(q[1])
toffoli(0, 1, 2, q, qc)
qc.cx(q[0], q[3])
qc.cx(q[1], q[3])
qc.measure(q[2], c[1])
qc.measure(q[3], c[0])
circuit_drawer(qc)
いい感じ♪
実機で実験
同じプログラムを実機(ibmqx4)で走らせてみる。
APItoken = getpass.getpass('Please input your token and hit enter: ')
qx_config = {
"APItoken": APItoken,
"url":"https://quantumexperience.ng.bluemix.net/api"}
try:
register(qx_config['APItoken'], qx_config['url'])
print('\nYou have access to great power!')
print(available_backends({'local': False, 'simulator': False}))
except:
print('Something went wrong.\nDid you enter a correct token?')
def lowest_pending_jobs():
"""Returns the backend with lowest pending jobs."""
list_of_backends = available_backends(
{'local': False, 'simulator': False})
device_status = [get_backend(backend).status
for backend in list_of_backends]
best = min([x for x in device_status if x['available'] is True],
key=lambda x: x['pending_jobs'])
return best['name']
backend = lowest_pending_jobs()
print("The best backend is " + backend)
q = QuantumRegister(4)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)
qc.x(q[0])
qc.x(q[1])
toffoli(0, 1, 2, q, qc)
qc.cx(q[0], q[3])
qc.cx(q[1], q[3])
qc.measure(q[2], c[1])
qc.measure(q[3], c[0])
job_exp = execute(qc, backend='ibmqx4', shots=1024, max_credits=3)
lapse = 0
interval = 10
while not job_exp.done:
print('Status @ {} seconds'.format(interval * lapse))
print(job_exp.status)
time.sleep(interval)
lapse += 1
if lapse > 6:
break
print(job_exp.status)
plot_histogram(job_exp.result().get_counts(qc))
Status @ 0 seconds
{'job_id': None, 'status': <JobStatus.INITIALIZING: 'job is being initialized'>, 'status_msg': 'job is begin initialized please wait a moment'}
WARNING:IBMQuantumExperience.IBMQuantumExperience:Got a 400 code response to https://quantumexperience.ng.bluemix.net/api/Jobs?access_token=8y8ATzUiJVXr8jEPo3YjSS5ADOM8QKTTasnqMwELguUUEamasxwjEDGWZLe2cEbB: {"error":{"status":400,"message":"Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked","code":"QASM_NOT_VALID","statusCode":400}}
WARNING:IBMQuantumExperience.IBMQuantumExperience:Got a 400 code response to https://quantumexperience.ng.bluemix.net/api/Jobs?access_token=8y8ATzUiJVXr8jEPo3YjSS5ADOM8QKTTasnqMwELguUUEamasxwjEDGWZLe2cEbB: {"error":{"status":400,"message":"Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked","code":"QASM_NOT_VALID","statusCode":400}}
WARNING:IBMQuantumExperience.IBMQuantumExperience:Got a 400 code response to https://quantumexperience.ng.bluemix.net/api/Jobs?access_token=8y8ATzUiJVXr8jEPo3YjSS5ADOM8QKTTasnqMwELguUUEamasxwjEDGWZLe2cEbB: {"error":{"status":400,"message":"Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked","code":"QASM_NOT_VALID","statusCode":400}}
WARNING:IBMQuantumExperience.IBMQuantumExperience:Got a 400 code response to https://quantumexperience.ng.bluemix.net/api/Jobs?access_token=8y8ATzUiJVXr8jEPo3YjSS5ADOM8QKTTasnqMwELguUUEamasxwjEDGWZLe2cEbB: {"error":{"status":400,"message":"Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked","code":"QASM_NOT_VALID","statusCode":400}}
WARNING:IBMQuantumExperience.IBMQuantumExperience:Got a 400 code response to https://quantumexperience.ng.bluemix.net/api/Jobs?access_token=8y8ATzUiJVXr8jEPo3YjSS5ADOM8QKTTasnqMwELguUUEamasxwjEDGWZLe2cEbB: {"error":{"status":400,"message":"Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked","code":"QASM_NOT_VALID","statusCode":400}}
Status @ 10 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
Status @ 20 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
Status @ 30 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
Status @ 40 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
Status @ 50 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
Status @ 60 seconds
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
{'job_id': None, 'status': <JobStatus.ERROR: 'job incurred error'>, 'status_msg': None}
---------------------------------------------------------------------------
QISKitError Traceback (most recent call last)
<ipython-input-13-7ae4f6cd7acb> in <module>()
12 print(job_exp.status)
13
---> 14 plot_histogram(job_exp.result().get_counts(qc))
~/.local/lib/python3.6/site-packages/qiskit/_result.py in get_counts(self, circuit)
248 """
249 try:
--> 250 return self.get_data(circuit)['counts']
251 except KeyError:
252 raise QISKitError('No counts for circuit "{0}"'.format(circuit))
~/.local/lib/python3.6/site-packages/qiskit/_result.py in get_data(self, circuit)
207 raise exception
208 else:
--> 209 raise QISKitError(str(exception))
210 if isinstance(circuit, QuantumCircuit):
211 circuit = circuit.name
QISKitError: '"{\'status\': 400, \'message\': \'Error parsing QASM. Error parsing qasm number 0. Gates after a measure are blocked\', \'code\': \'QASM_NOT_VALID\', \'statusCode\': 400}"'
ありゃりゃ。エラーが出て全く実行できない。
実機で実行してみて初めて知ったが、実機の場合CNOTを作用させる方向に制限があるらしい。
CNOTの制限
下図の矢印の方向にだけCNOTが掛けられるとのこと。(矢印の始点が controlled qubit 、終点が target qubit に対応する)
ibmqx2
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub
ibmqx4
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub
ibmqx5
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub
今回必要なのはこの赤線の方向。
この図を見て「どの量子計算機使っても1+1の回路実現できないじゃん。だから、実機で実験している人が少ないのか〜。納得。」と一度納得してしまったし、さらに言えば「実機だと1+1も計算できないのか〜」とがっかりもしてしまったが、そう思ってしまったのは単に私が勉強不足なだけであった。
実機で実験 part 2
IBMが出しているドキュメントを読んで思い出したが、CNOTの両側に Hadamard gate を作用させれば controlled と target を逆転させることができるのであった。今にして思えば、Mermin を読んだ時にそんなことが書いてあった気がする。
これを元に回路を組み直してみる。
実装 Part 1
下図の回路は ibmqx4 用の回路。
もともとの回路だと, が必要になるが ibmqx4 の制限上それは出来ない。そのため、2nd qubit と 3rd qubit をswap した後、, し最後に2nd, 3rd qubit を測定している。
def toffoli(c0, c1, t, q, qc, backend):
qc.h(q[t])
cx(c1, t, q, qc, backend)
qc.tdg(q[t])
cx(c0, t, q, qc, backend)
qc.t(q[t])
cx(c1, t, q, qc, backend)
qc.tdg(q[t])
cx(c0, t, q, qc, backend)
qc.t(q[c1])
qc.t(q[t])
cx(c0, c1, q, qc, backend)
qc.h(q[t])
qc.t(q[c0])
qc.tdg(q[c1])
cx(c0, c1, q, qc, backend)
def cx(con, tar, q, qc, backend):
ibmqx2config = [[0,1],[0,2],[1,2],[3,2],[3,4],[4,2]]
ibmqx4config = [[1,0],[2,0],[2,1],[4,2],[3,2],[3,4]]
ibmqx5config = [[1,0], [1,2], [2,3], [3,4], [3,14], [5,4], [6,5], [6,7], [6,11], [7,10], [8,7], [9,8], [9,10], [11,10], [12,5], [12,11], [12,13], [13,4], [13,14], [15,0], [15,2], [15,14]]
devices = {'ibmqx2':ibmqx2config, 'ibmqx4':ibmqx4config, 'ibmqx5':ibmqx5config}
if backend in devices.keys():
if [con, tar] in devices[backend]:
qc.cx(q[con], q[tar])
elif [tar, con] in devices[backend]:
qc.h(q[con])
qc.h(q[tar])
qc.cx(q[tar], q[con])
qc.h(q[con])
qc.h(q[tar])
else:
raise ValueError('Don\'t support such CNOT gate')
else:
qc.cx(q[con], q[tar])
def swap(q0, q1, q, qc, backend):
cx(q0, q1, q, qc, backend)
cx(q1, q0, q, qc, backend)
cx(q0, q1, q, qc, backend)
backend = 'ibmqx4'
q = QuantumRegister(5)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)
qc.x(q[0])
qc.x(q[1])
toffoli(0, 1, 2, q, qc, backend)
swap(0,2,q,qc,backend)
swap(2,4,q,qc,backend)
swap(1,2,q,qc,backend)
cx(4,3,q,qc,backend)
cx(2,3,q,qc,backend)
qc.measure(q[0], c[1])
qc.measure(q[3], c[0])
job_exp = execute(qc, backend=backend, shots=1024, max_credits=3)
lapse = 0
interval = 10
while not job_exp.done:
print('Status @ {} seconds'.format(interval * lapse))
print(job_exp.status)
time.sleep(interval)
lapse += 1
print(job_exp.status)
circuit_drawer(qc)
plot_histogram(job_exp.result().get_counts(qc))
実装 Part 2
上の回路は ibmqx4 にはあまり適していない(, のために Hadamard gate の数が多くなっている)ので、入力に 3rd, 4th qubit を使う回路にしてみる。
backend = 'ibmqx4'
q = QuantumRegister(5)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)
qc.x(q[3])
qc.x(q[4])
toffoli(4, 3, 2, q, qc, backend)
swap(0,2,q,qc,backend)
cx(3,2,q,qc,backend)
cx(4,2,q,qc,backend)
qc.measure(q[0], c[1])
qc.measure(q[2], c[0])
job_exp = execute(qc, backend=backend, shots=1024, max_credits=3)
lapse = 0
interval = 10
while not job_exp.done:
print('Status @ {} seconds'.format(interval * lapse))
print(job_exp.status)
time.sleep(interval)
lapse += 1
if lapse > 6:
break
print(job_exp.status)
circuit_drawer(qc)
plot_histogram(job_exp.result().get_counts(qc))
正しい結果を得られる確率が大きくなった。
実装 Part 3
まだ上記の回路には無駄がある。下図の赤で囲った部分の Hadamard gate はなくても SWAP になるので消す。
qc = load_qasm_string("""OPENQASM 2.0;
include "qelib1.inc";
qreg q[5];
creg c[2];
x q[3];
x q[4];
h q[2];
cx q[3],q[2];
tdg q[2];
cx q[4],q[2];
t q[2];
cx q[3],q[2];
tdg q[2];
cx q[4],q[2];
t q[3];
t q[2];
h q[4];
h q[3];
cx q[3],q[4];
h q[4];
h q[3];
h q[2];
t q[4];
tdg q[3];
h q[4];
h q[3];
cx q[3],q[4];
h q[4];
h q[3];
cx q[2],q[0];
h q[0];
h q[2];
cx q[2],q[0];
h q[0];
h q[2];
cx q[2],q[0];
cx q[3],q[2];
cx q[4],q[2];
measure q[0] -> c[1];
measure q[2] -> c[0];""")
backend = 'ibmqx4'
job_exp = execute(qc, backend=backend, shots=1024, max_credits=3)
lapse = 0
interval = 10
while not job_exp.done:
print('Status @ {} seconds'.format(interval * lapse))
print(job_exp.status)
time.sleep(interval)
lapse += 1
if lapse > 6:
break
print(job_exp.status)
circuit_drawer(qc)
plot_histogram(job_exp.result().get_counts(qc))
正しい結果を得られる確率がさらに上がった!
まとめ
- エラーを減らしたいならゲートは少なくしたほうが良い。
- IBM Q の実機の結果は結構ゆらぐので今回の結果を真面目に受け止めてはいけない。
- 時には OpenQASM を直書きしたくなる。
- 回路図は qasm2circ のほうがきれい。
- 量子計算難しい!!!
gist9b32d2e2b415d113766d06cf5a2015d6
参考