クロネッカー積と複合 n 進数

{\def\bra#1{\mathinner{\left\langle{#1}\right|}} } {\def\ket#1{\mathinner{\left|{#1}\right\rangle}} } {\def\braket#1{\mathinner{\left\langle{#1} \right\rangle}} }

タイトルの ”複合n進数” というのは便宜上の用語で正式名称は知らない。 ここでは各桁ごとに繰り上がる数が違う数字列のことを "複合n進数" と呼ぶこととする。 例えば、{ \displaystyle x_1 x_0 } の0桁目*1は2進数、1桁目が3進数だとすると 00, 01, 10, 11, 20, 21 と数が増えていく。

配列をディラック表記で表示するという地味なJuliaのパッケージを作っているときにある問題に直面した

テンソル積(クロネッカー積)で作った配列のi番目ってどの基底の係数になるんだ?

と。

放っておくと忘れそうなので記録に残しておく。

Qubits 系の場合

量子コンピュータで扱われるような n-qubit 系なら話は簡単。 配列のi番目の要素は {
    \displaystyle \ket{i_2}
} ( {
    \displaystyle i_2
} は i の二進数表記を意味する) の係数となる。

例えば 2-qubit 状態を計算基底 { \displaystyle \mathcal{B} = \{ \ket{00}, \ket{01}, \ket{10}, \ket{11}  \} }で展開すると {
    \displaystyle \ket{\psi} = \alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11} =
    \begin{bmatrix}
        \alpha \\ \beta \\ \gamma \\ \delta
    \end{bmatrix}_\mathcal{B}
} となるが、行列の成分を 0 始まりで数えれば 2番目の成分は { \displaystyle \ket{2_2} = \ket{10} } の係数となる。

このようにqubit 系の場合は10進数を2進数に直すだけで基底がわかるので簡単。 問題は qudits系 *2の場合である。

Qudits 系の場合

今、n-qudit系の i 番目の標準基底を {
    \displaystyle \ket{i} = \ket{ i_{n-1} i_{n-2} \dots i_1 i_0 }
} とし、j 桁目の qudit の次元を { \displaystyle d_j } とする。

冒頭で述べたように、qutrit-qubit 系の場合の標準基底は { \displaystyle \mathcal{B} = \{ \ket{00}, \ket{01}, \ket{10}, \ket{11}, \ket{20}, \ket{21}  \} } である。

そして、今私がやりたいことは { \displaystyle i }{ \displaystyle d_j ~(j = 0, \cdots, n-1) } の情報から { \displaystyle i_{n-1} i_{n-2} \dots i_1 i_0 } の各桁の数を求めることである。 全ての qudit の次元が同じならば、qubit 程ではないが簡単に求めることができるが、より一般の状態では各 qudit の次元が違うので中々面倒な計算になる。

関係式を考える

とりあえず表にまとめてみた。
qutrit-qubit の場合

{ \displaystyle i } { \displaystyle i_1 } { \displaystyle i_0 }
0 0 0
1 0 1
2 1 0
3 1 1
4 2 0
5 2 1

qubit-qutrit-qubit の場合

{ \displaystyle i } { \displaystyle i_2 } { \displaystyle i_1 } { \displaystyle i_0 }
0 0 0 0
1 0 0 1
2 0 1 0
3 0 1 1
4 0 2 0
5 0 2 1
6 1 0 0
7 1 0 1
8 1 1 0
9 1 1 1
10 1 2 0
11 1 2 1

表と にらめっこして次のような関係式が成り立つと予測。
{ \displaystyle i =  i_0 + \sum_{k=1}^{n-1}  i_k \prod_{l=0}^{k-1}  d_l }
上記の表の値を具体的に代入してみても良さそう。

これより、 { \displaystyle i_j } は次のように求められそうだと予測。 \begin{align} i_{n-1} &= \mathrm{div} \left(i, ~~\prod_{k=0}^{n-2} d_k \right) \\ i &\leftarrow i - i_{n-1} \prod_{k=0}^{n-2} d_k \end{align}

\begin{align} i_{n-2} &= \mathrm{div} \left(i, ~~\prod_{k=0}^{n-3} d_k \right) \\ i &\leftarrow i - i_{n-2} \prod_{k=0}^{n-3} d_k \\ &~~~~\vdots\\ i_1 &= \mathrm{div}(i, d_0)\\ i_0 &= i - i_1 d_0 \end{align}

これで無事に求めたいものが得られた。

Julia*3 で書いてみると以下のよう

julia> versioninfo()
Julia Version 0.7.0-alpha.217
Commit f90d674057 (2018-06-21 10:01 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i5-4460T CPU @ 1.90GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, haswell)
Environment:
  JULIA_SHELL = /usr/bin/zsh

julia> function ind2Nary(m::Int, dims::Vector{Int})
    m = m - 1
    nq = length(dims)
    ar = zeros(Int, nq)
    product = prod(dims[2:end])
    for ith in 1:nq-1
        d = div(m, product)
        m = m - d * product
        product = div(product, dims[ith+1])
        ar[ith] = d
    end
    ar[end] = m
    return ar
end

julia> d = [2,3,2];

julia> for i in 1:prod(d)
           println(i-1, ": ", ind2Nary(i, d))
       end
0: [0, 0, 0]
1: [0, 0, 1]
2: [0, 1, 0]
3: [0, 1, 1]
4: [0, 2, 0]
5: [0, 2, 1]
6: [1, 0, 0]
7: [1, 0, 1]
8: [1, 1, 0]
9: [1, 1, 1]
10: [1, 2, 0]
11: [1, 2, 1]

いい感じ♪

せっかくなのでこれを使って QuantumOptics.jlプルリクを投げたら一応マージしてもらえた。
ちょっとだけ OSS に貢献したかもしれない。

*1:量子情報分野では一番左の桁が0番目ということが多いが、今回は一番右が0番目とする。

*2:qudit: ヒルベルト空間の次元が d

*3:Julia の場合、配列が 1-base であることに注意

QISKit: コンパイラーが空気を読んでくれる時くれない時

目次

環境

  • OS: ArchLinux
  • Python 3.6.5
  • QISKit 0.5.4

QISKit を使う利点

IBM量子コンピュータ を QISKit 経由で使うことの利点は、実機の物理的な制限を(多少)意識しなくても良いことだったり、Toffoli ゲート (ccx) や SWAP ゲート を実機でも使えることなどだと思う。*1

例えば、5 qubit の量子コンピュータである ibmqx4 の場合、CNOT をかけられる方向は下図のようである。

(IBM Q 5 Tenerife V1.x.x Version Log より。矢印の始点が controlled qubit 、終点が target qubit を表す。)

この図からわかるように、ibmqx4 の場合 { \mathrm{CNOT}_{01}  } を作用させることはできない*2。 そのため、Composer でそのような CNOT ゲート を置こうとすると "You can't drop the gate there" と言われてしまう。

f:id:goropikarikun:20180621121411p:plain

一方で QISKit を経由すると、 { \mathrm{CNOT}_{01}  } を含んでいても実行できる。

これがなぜできるかというと、実機で実行される前に ibmqx4 でも実行できるような等価な回路に変換されるためである。

QISKit を使った場合の CNOT ゲート

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)

qc.cx(q[0], q[1])

qc.measure(q, c)
circuit_drawer(qc)

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
print(job_exp.status)

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

backend = 'ibmqx4'
circuit = qiskit.compile(qc, backend=backend)   
print(circuit['circuits'][0]['compiled_circuit_qasm'])  
circuit_drawer(load_qasm_string(circuit['circuits'][0]['compiled_circuit_qasm']))

f:id:goropikarikun:20180621142025p:plain
コンパイル前の回路図
f:id:goropikarikun:20180621142029p:plain
f:id:goropikarikun:20180621142034p:plain
コンパイル後の回路図

\begin{align} \mathrm{CNOT}_{01} &= (H \otimes H) \mathrm{CNOT}_{10} (H \otimes H) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} \end{align} f:id:goropikarikun:20180621194434p:plain
の関係を使っていい感じに変換してくれる。

( { \mathrm{CNOT}_{13}  } のように直接つながっていないものは QISKit 使ってもよしなにしてくれないけれども。。。)

QISKit を使った場合の Toffoli ゲート

q = QuantumRegister(3)
c = ClassicalRegister(3)
qc = QuantumCircuit(q,c)

qc.x(q[0])
qc.x(q[1])
qc.ccx(q[0], q[1], q[2])

qc.measure(q, c)
circuit_drawer(qc)

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
print(job_exp.status)

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

backend = 'ibmqx4'
circuit = qiskit.compile(qc, backend=backend)
print(circuit['circuits'][0]['compiled_circuit_qasm'])
circuit_drawer(load_qasm_string(circuit['circuits'][0]['compiled_circuit_qasm']))

f:id:goropikarikun:20180621143102p:plain
f:id:goropikarikun:20180621143105p:plain
f:id:goropikarikun:20180621143110p:plain

Composer を使って Toffoli ゲート を実装しようとすると1個1個ゲートをドラッグ・アンド・ドロップしなければならないため大変面倒、かつ、再利用性に乏しい*3 けれど、QISKit を使えば Toffoli ゲート も勝手にいい感じに実装してくれる。

ゲートの省略

Z ゲートを2回作用させると { Z^2  = I  } となるので、このような場合はゲートを省略したほうが良い*4がそのへんの最適化も QISKit 経由だと勝手にやってくれる。

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)

qc.z(q[0])
qc.z(q[0])

qc.measure(q, c)
circuit_drawer(qc)

backend = 'ibmqx4'
circuit = qiskit.compile(qc, backend=backend)
print(circuit['circuits'][0]['compiled_circuit_qasm'])
circuit_drawer(load_qasm_string(circuit['circuits'][0]['compiled_circuit_qasm']))

f:id:goropikarikun:20180621161132p:plain
コンパイル

f:id:goropikarikun:20180621161135p:plain
コンパイル後の回路では Z ゲートが省略された。

QISKit が空気を読んでくれない時

恒等演算子への変換編

先の例では Z ゲートが省略されたが、ここでおもむろに Qubit の数を 1 に減らしてみる。

q = QuantumRegister(1)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)

qc.z(q[0])
qc.z(q[0])

qc.measure(q, c)
circuit_drawer(qc)

backend = 'ibmqx4'
circuit = qiskit.compile(qc, backend=backend)
print(circuit['circuits'][0]['compiled_circuit_qasm'])
circuit_drawer(load_qasm_string(circuit['circuits'][0]['compiled_circuit_qasm']))

f:id:goropikarikun:20180621162243p:plain
コンパイル

f:id:goropikarikun:20180621162249p:plain
コンパイル

するとどうだろう。何故か Z ゲートが省略されなくなってしまった。 私は IBM Q の実機でどのようにゲート操作を実現しているのか知らないので、この違いがどれほどのエラーを引き起こすのかは知らないが、Qubit の数を減らすと最適化のされ方が変わるというのは不思議である。

SWAP ゲート編

f:id:goropikarikun:20180621195036p:plain
SWAP ゲートは CNOT ゲートを3つ使って上図のように実装できる*5。 これを ibmqx4 向けにコンパイルすると次のようになる。

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)

qc.swap(q[0],q[1])

qc.measure(q, c)
circuit_drawer(qc)

backend = 'ibmqx4'
circuit = qiskit.compile(qc, backend=backend)
print(circuit['circuits'][0]['compiled_circuit_qasm'])
circuit_drawer(load_qasm_string(circuit['circuits'][0]['compiled_circuit_qasm']))

f:id:goropikarikun:20180621195446p:plain
コンパイル
f:id:goropikarikun:20180621195442p:plain
コンパイル

コンパイル後の回路を Hadamard ゲートで書き直すと次のようになっている。 f:id:goropikarikun:20180621200804p:plain
これは
f:id:goropikarikun:20180621201011p:plain
{ \mathrm{CNOT}_{01} = (H \otimes H) \mathrm{CNOT}_{10} (H \otimes H) } を使って変換しているわけですが、両端の Hadamard ゲートがない
f:id:goropikarikun:20180621201408p:plain
でも SWAP ゲートになるので QISKit のコンパイラが吐き出した OpenQASM のコードには無駄がある。
常に Hadamard ゲートが少ない方の SWAP になるように QISKit のコードを編集しようと思ったが該当箇所がどこなのかわからなかったので諦めました。

まとめ

  • シミュレーターを使うならばコンパイル後の回路について気にしなくても良い。
  • 実機で実験する場合、コンパイル後の回路図も見ておいたほうがよさそう。

余談

慶應大学が使える 20 qubits の量子コンピュータは写真を見る限り斜め方向にも CNOT が掛けられるようなので回路が組みやすそうですね。まぁ、私のような無学な人間には一生縁のない大学ですが。
- 慶應大の量子コンピューター研究拠点「IBM Qハブ」が注目の理由 —— 量子ネイティブ人材育成、三菱UFJら4社参画 | BUSINESS INSIDER JAPAN
- IBM Q Experience

参考

Jupyter Notebook

gistedf4a2674d1cb98c65fcf83abf6e3cae

*1:他にもあるだろうが、私みたいな素人目線からのメリットはこんな感じ

*2: { \mathrm{CNOT}_{ij}  } は i が controlled qubit, j が target qubit を表す。

*3:OpenQASM をコピペすれば多少再利用性が上がりますが。

*4:ゲートの数が増えるとその分ゲートエラーが増える。それと長くなるとデコヒーレンスするので量子性が失われていく。

*5:$$ \mathrm{CNOT}_{01} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} $$

\begin{align} \mathrm{CNOT}_{10} &= (H \otimes H) \mathrm{CNOT}_{01} (H \otimes H) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ \end{bmatrix} \end{align}

\begin{align} \mathrm{SWAP} &= \mathrm{CNOT}_{01} \mathrm{CNOT}_{10} \mathrm{CNOT}_{01} \\ &= \mathrm{CNOT}_{10} \mathrm{CNOT}_{01} \mathrm{CNOT}_{10} \\ &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \end{align}

QISKit: OpenQASMを読み込む

環境

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

OpenQASM 文法で書いた文字列から回路を作る

from qiskit import load_qasm_string
from qiskit.tools.visualization import circuit_drawer
sys.path.append('./')
qc = load_qasm_string("""OPENQASM 2.0; // OPENQASM 2.0; はなくても良い
include "qelib1.inc";
qreg q[2];
h q[0];
cx q[0], q[1];
""")

print(qc.qasm())
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
cx q[0],q[1];
circuit_drawer(qc)

f:id:goropikarikun:20180616201329p:plain

OpenQASM ファイルから回路を作る

bell.qasm

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
cx q[0],q[1];
qc = load_qasm_string("""
OPENQASM 2.0; // "OPENQASM 2.0;" is not necessary.
include "qelib1.inc";
qreg q[2];
h q[0];
cx q[0], q[1];
""")

circuit_drawer(qc)

f:id:goropikarikun:20180616201329p:plain

Jupyter notebook

gist371969f3861b2abc3742f17dc3f7ffda

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

参考

QISKit: Toffoli gate の実装

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
from qiskit import ClassicalRegister, QuantumRegister
from qiskit import QuantumCircuit,  available_backends, execute, register, get_backend

# import basic plot tools
from qiskit.tools.visualization import plot_histogram, circuit_drawer

実装1

def toffoli1(c0, c1, t, q, qc):
    qc.h(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.t(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.t(q[c1])
    qc.t(q[t])
    qc.cx(q[c0], q[c1])
    qc.h(q[t])
    qc.t(q[c0])
    qc.tdg(q[c1])
    qc.cx(q[c0], q[c1])
    
q = QuantumRegister(3)
qc = QuantumCircuit(q)
toffoli1(0, 1, 2, q, qc)
circuit_drawer(qc)

f:id:goropikarikun:20180613103014p:plain

実装2

def toffoli2(c0, c1, t, q, qc):
    qc.h(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.t(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.tdg(q[c1])
    qc.t(q[t])
    qc.cx(q[c0], q[c1])
    qc.h(q[t])
    qc.tdg(q[c1])
    qc.cx(q[c0], q[c1])
    qc.t(q[c0])
    qc.s(q[c1])
    
q = QuantumRegister(3)
qc = QuantumCircuit(q)
toffoli2(0, 1, 2, q, qc)
circuit_drawer(qc)

f:id:goropikarikun:20180613103026p:plain

実装3

def toffoli3(c0, c1, t, q, qc):
    qc.h(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.t(q[t])
    qc.cx(q[c1], q[t])
    qc.tdg(q[c1])
    qc.tdg(q[t])
    qc.cx(q[c0], q[t])
    qc.cx(q[c0], q[c1])
    qc.t(q[c0])
    qc.tdg(q[c1])
    qc.t(q[t])
    qc.cx(q[c0], q[c1])
    qc.s(q[c1])
    qc.h(q[t])
    
q = QuantumRegister(3)
qc = QuantumCircuit(q)
toffoli3(0, 1, 2, q, qc)
circuit_drawer(qc)

f:id:goropikarikun:20180613103044p:plain

実装4

def toffoli4(c0, c1, t, q, qc):
    qc.h(q[t])
    qc.tdg(q[c0])
    qc.t(q[c1])
    qc.t(q[t])
    qc.cx(q[c0], q[c1])
    qc.cx(q[t], q[c0])
    qc.tdg(q[c0])
    qc.cx(q[c1], q[t])
    qc.cx(q[c1], q[c0])
    qc.tdg(q[c0])
    qc.tdg(q[c1])
    qc.t(q[t])
    qc.cx(q[t], q[c0])
    qc.s(q[c0])
    qc.cx(q[c1], q[t])
    qc.cx(q[c0], q[c1])
    qc.h(q[t])

    
q = QuantumRegister(3)
qc = QuantumCircuit(q)
toffoli4(0, 1, 2, q, qc)
circuit_drawer(qc)

f:id:goropikarikun:20180613103054p:plain

参考

クラウド量子計算入門―IBMの量子シミュレーションと量子コンピュータ, 中山 茂, ISBN-13: 978-4877834081