QISKit: 量子コンピュータ(実機) で1+1を計算する

下記の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 の実装方法には いくつかあるが今回は以下のものを採用。 f:id:goropikarikun:20180613103014p:plain

シミュレーター

まずはシミュレーターで回路を作ってみる。

# This software includes the work that is distributed in the Apache License 2.0
# https://github.com/QISKit/qiskit-tutorial/blob/master/hello_world/quantum_world.ipynb

import getpass, time, sys
from qiskit import ClassicalRegister, QuantumRegister, load_qasm_string
from qiskit import QuantumCircuit,  available_backends, execute, register, get_backend

# import basic plot tools
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)

f:id:goropikarikun:20180613202152p:plain f:id:goropikarikun:20180613202535p:plain

いい感じ♪

実機で実験

同じプログラムを実機(ibmqx4)で走らせてみる。

# This software includes the work that is distributed in the Apache License 2.0
# https://github.com/QISKit/qiskit-tutorial/blob/master/hello_world/quantum_world.ipynb

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])
# circuit_drawer(qc)
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
ibmqx2
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub

ibmqx4
ibmqx4
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub

ibmqx5
ibmqx5
qiskit-backend-information/version_log.md at master · QISKit/qiskit-backend-information · GitHub

f:id:goropikarikun:20180613234817j:plain
今回必要なのはこの赤線の方向。

この図を見て「どの量子計算機使っても1+1の回路実現できないじゃん。だから、実機で実験している人が少ないのか〜。納得。」と一度納得してしまったし、さらに言えば「実機だと1+1も計算できないのか〜」とがっかりもしてしまったが、そう思ってしまったのは単に私が勉強不足なだけであった。

実機で実験 part 2

IBMが出しているドキュメントを読んで思い出したが、CNOTの両側に Hadamard gate を作用させれば controlled と target を逆転させることができるのであった。今にして思えば、Mermin を読んだ時にそんなことが書いてあった気がする。 これを元に回路を組み直してみる。

実装 Part 1

下図の回路は ibmqx4 用の回路。 もともとの回路だと{ \mathrm{CNOT}_{03} }, { \mathrm{CNOT}_{13} } が必要になるが ibmqx4 の制限上それは出来ない。そのため、2nd qubit と 3rd qubit をswap した後、{ \mathrm{CNOT}_{02} }, { \mathrm{CNOT}_{12} } し最後に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: # simulator
        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)

# 1 + 1
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)
# job_exp = execute(qc, backend='local_qasm_simulator', 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)

f:id:goropikarikun:20180613220130p:plain

plot_histogram(job_exp.result().get_counts(qc))

f:id:goropikarikun:20180613220155p:plain

実装 Part 2

上の回路は ibmqx4 にはあまり適していない({ \mathrm{CNOT}_{02} }, { \mathrm{CNOT}_{12} } のために Hadamard gate の数が多くなっている)ので、入力に 3rd, 4th qubit を使う回路にしてみる。

# ibmqx4 に優しい(?)実装
backend = 'ibmqx4'
q = QuantumRegister(5)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

# 1 + 1
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)
# job_exp = execute(qc, backend='local_qasm_simulator', 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)

f:id:goropikarikun:20180613230346p:plain

plot_histogram(job_exp.result().get_counts(qc))

f:id:goropikarikun:20180613230358p:plain

正しい結果を得られる確率が大きくなった。

実装 Part 3

まだ上記の回路には無駄がある。下図の赤で囲った部分の Hadamard gate はなくても SWAP になるので消す。 f:id:goropikarikun:20180613233006j:plain

# さらに ibmqx4 に優しそうな実装
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)

f:id:goropikarikun:20180613232614p:plain

plot_histogram(job_exp.result().get_counts(qc))

f:id:goropikarikun:20180613232637p:plain

正しい結果を得られる確率がさらに上がった!

まとめ

  • エラーを減らしたいならゲートは少なくしたほうが良い。
  • IBM Q の実機の結果は結構ゆらぐので今回の結果を真面目に受け止めてはいけない。
  • 時には OpenQASM を直書きしたくなる。
  • 回路図は qasm2circ のほうがきれい。
  • 量子計算難しい!!!

gist9b32d2e2b415d113766d06cf5a2015d6

参考