甚だしく今更感はありますが、Python ベースの量子回路シミュレータオープンソース・ソフトウェアはそれぞれどのくらい見た目・書き味が違うのだろうと思い、比較してみようと思いました。
比較ポイントは以下です:
- 回路図の書き方
- 複数回路の合成
- 量子回路を単純に print したときにどう見えるか
- 量子回路のユニタリ行列表示
- シミュレーション結果の表示
Blueqat
量子回路をメソッドチェーン形式で書ける。
元になる状態があって、メソッドチェーンで作用を書き連ねるので、数式とは逆に右に向かって積み上げていくイメージ(図式表現と同じ向きなので、図を見ながら書くには分かりやすい)。
回路の合成は + でできる。
from blueqat import Circuit
def epr(a, b):
return Circuit().h[a].cx[a, b]
def alice(x, a):
return Circuit().cx[x, a].h[x]
def bob(x, a, b):
return Circuit().cx[a, b].cz[x, b]
x = 0
a = 1
b = 2
# 回路の合成は * でなく + で行う
c = epr(a, b) + alice(x, a) + bob(x, a, b)
print(u'---- 量子回路の表示')
print(c)
print(u'---- ユニタリ行列の表示')
print(c.run(backend="sympy_unitary"))
print(u'---- シミュレーション結果の表示')
print(c.m[:].run(shots=100))
---- 量子回路の表示
Circuit(3).h[1].cx[1, 2].cx[0, 1].h[0].cx[1, 2].cz[0, 2]
---- ユニタリ行列の表示
Matrix([[1/2, 0, 1/2, 0, 0, 1/2, 0, -1/2], [1/2, 0, 1/2, 0, 0, -1/2, 0, 1/2], [1/2, 0, -1/2, 0, 0, 1/2, 0, 1/2], [1/2, 0, -1/2, 0, 0, -1/2, 0, -1/2], [0, 1/2, 0, -1/2, 1/2, 0, 1/2, 0], [0, 1/2, 0, -1/2, -1/2, 0, -1/2, 0], [0, 1/2, 0, 1/2, 1/2, 0, -1/2, 0], [0, 1/2, 0, 1/2, -1/2, 0, 1/2, 0]])
---- シミュレーション結果の表示
Counter({'010': 34, '110': 24, '100': 23, '000': 19})
簡易的に量子回路を表示する obaq もある。
# obaq
import obaq
c.run(backend='obaq', returns='draw')
Cirq
回路はリストで与える。数式とは逆に右に向かって積み上げていくイメージ
(図式表現と同じ向きなので、図を見ながら書くには分かりやすい)。
回路の合成は append でできる。
import cirq
def epr(a, b):
return [cirq.H(a), cirq.CNOT(a, b)]
def alice(x, a):
return [cirq.CNOT(x, a), cirq.H(x)]
def bob(x, a, b):
return [cirq.CNOT(a, b), cirq.CZ(x, b)]
x = cirq.NamedQubit('X')
a = cirq.NamedQubit('a')
b = cirq.NamedQubit('b')
c = cirq.Circuit()
c.append(epr(a, b) + alice(x, a) + bob(x, a, b))
print(u'---- 量子回路の表示')
print(c)
print(u'---- ユニタリ行列の表示')
print(cirq.unitary(c))
print(u'---- シミュレーション結果の表示')
print(cirq.Simulator().simulate(c))
---- 量子回路の表示
X: ───────────@───H───@───
│ │
a: ───H───@───X───@───┼───
│ │ │
b: ───────X───────X───@───
---- ユニタリ行列の表示
[[ 0.5+0.j 0. +0.j 0.5+0.j 0. +0.j 0. +0.j 0.5+0.j 0. +0.j -0.5+0.j]
[ 0. +0.j 0.5+0.j 0. +0.j 0.5+0.j 0.5+0.j 0. +0.j -0.5+0.j 0. +0.j]
[ 0.5+0.j 0. +0.j -0.5+0.j 0. +0.j 0. +0.j 0.5+0.j 0. +0.j 0.5+0.j]
[ 0. +0.j 0.5+0.j 0. +0.j -0.5+0.j 0.5+0.j 0. +0.j 0.5+0.j 0. +0.j]
[ 0.5+0.j 0. +0.j 0.5+0.j 0. +0.j 0. +0.j -0.5+0.j 0. +0.j 0.5+0.j]
[-0. +0.j -0.5+0.j -0. +0.j -0.5+0.j 0.5-0.j -0. +0.j -0.5+0.j -0. +0.j]
[ 0.5+0.j -0. +0.j -0.5+0.j -0. +0.j -0. +0.j -0.5+0.j -0. +0.j -0.5+0.j]
[ 0. -0.j -0.5+0.j 0. -0.j 0.5-0.j 0.5-0.j 0. -0.j 0.5-0.j 0. -0.j]]
---- シミュレーション結果の表示
measurements: (no measurements)
output vector: 0.5|000⟩ + 0.5|010⟩ + 0.5|100⟩ + 0.5|110⟩
Qiskit
メソッドチェーンは使えない。時系列順に作用させていく感じ。
回路の合成は + でもできるが、DeprecatedWarning が出るので compose を使うのが良さそう。
from qiskit import Aer
from qiskit import assemble
from qiskit import QuantumCircuit, QuantumRegister
def epr(q, a, b):
# return QuantumCircuit(q).h(q[a]).cx(q[a], q[b])
# とは書けない
c = QuantumCircuit(q)
c.h(q[a])
c.cx(q[a], q[b])
return c
def alice(q, x, a):
c = QuantumCircuit(q)
c.cx(q[x], q[a])
c.h(q[x])
return c
def bob(q, x, a, b):
c = QuantumCircuit(q)
c.cx(q[a], q[b])
c.cz(q[x], q[b])
return c
x = 0
a = 1
b = 2
q = QuantumRegister(3, 'q')
# c = epr(q, a, b) + alice(q, x, a) + bob(q, x, a, b)
# と書くと DeprecateWarning が出る。
c = epr(q, a, b)
c = c.compose(alice(q, x, a))
c = c.compose(bob(q, x, a, b))
print(u'---- 量子回路の表示')
print(c.draw(output='text'))
print(u'---- ユニタリ行列の表示')
print(Aer.get_backend('unitary_simulator').run(c).result().get_unitary(
c, decimals=3))
print(u'---- シミュレーション結果の表示')
sv_sim = Aer.get_backend('aer_simulator')
c.save_statevector()
job = sv_sim.run(assemble(c, shots=100))
print(job.result().get_counts())
---- 量子回路の表示
┌───┐
q_0: ────────────■──┤ H ├─■─
┌───┐ ┌─┴─┐└───┘ │
q_1: ┤ H ├──■──┤ X ├──■───┼─
└───┘┌─┴─┐└───┘┌─┴─┐ │
q_2: ─────┤ X ├─────┤ X ├─■─
└───┘ └───┘
---- ユニタリ行列の表示
[[ 0.5+0.j 0. +0.j 0.5-0.j 0. +0.j 0. +0.j 0.5-0.j 0. +0.j -0.5+0.j]
[ 0.5+0.j 0. +0.j 0.5-0.j 0. +0.j 0. +0.j -0.5+0.j 0. +0.j 0.5-0.j]
[ 0.5+0.j 0. +0.j -0.5+0.j 0. +0.j 0. +0.j 0.5-0.j 0. +0.j 0.5-0.j]
[ 0.5+0.j 0. +0.j -0.5+0.j 0. +0.j 0. +0.j -0.5+0.j 0. +0.j -0.5+0.j]
[ 0. +0.j 0.5-0.j 0. +0.j -0.5+0.j 0.5+0.j 0. +0.j 0.5-0.j 0. +0.j]
[-0. +0.j 0.5-0.j -0. +0.j -0.5+0.j -0.5+0.j -0. +0.j -0.5+0.j -0. +0.j]
[ 0. +0.j 0.5-0.j 0. +0.j 0.5-0.j 0.5+0.j 0. +0.j -0.5+0.j 0. +0.j]
[-0. +0.j 0.5-0.j -0. +0.j 0.5-0.j -0.5+0.j -0. +0.j 0.5-0.j -0. +0.j]]
---- シミュレーション結果の表示
{'000': 0.25, '001': 0.25, '010': 0.25, '011': 0.25}
SymPy
回路の合成は * でできる。
数式を書くときと同様に左に向かって積み上げていく形で回路を記述する。
CZ ゲートはない? CGate を使って自分で作る?
from sympy.physics.quantum.gate import CNOT, CGate
from sympy.physics.quantum.gate import X,Z,H
from sympy.physics.quantum.qapply import qapply
from sympy.physics.quantum.qubit import Qubit
from sympy.physics.quantum.qubit import measure_all
from sympy.physics.quantum.represent import represent
def CZ(c, x):
# return H(x) * CNOT(c, x) * H(x) でも可。
return CGate((c,), Z(x))
def epr(a, b):
return CNOT(a, b) * H(a)
def alice(x, a):
return H(x) * CNOT(x, a)
def bob(x, a, b):
return CZ(x, b) * CNOT(a, b)
x = 2
a = 1
b = 0
c = bob(x, a, b) * alice(x, a) * epr(a, b)
print(u'量子回路の表示')
print(c)
print(u'ユニタリ行列の表示')
print(represent(c, nqubits=3))
print(u'シミュレーション結果の表示')
for m in measure_all(qapply(c * Qubit('000'))):
print(m)
---- 量子回路の表示
C((2),Z(0))*CNOT(1,0)*H(2)*CNOT(2,1)*CNOT(1,0)*H(1)
---- ユニタリ行列の表示
Matrix([[1/2, 0, 1/2, 0, 0, 1/2, 0, -1/2], [0, 1/2, 0, 1/2, 1/2, 0, -1/2, 0], [1/2, 0, -1/2, 0, 0, 1/2, 0, 1/2], [0, 1/2, 0, -1/2, 1/2, 0, 1/2, 0], [1/2, 0, 1/2, 0, 0, -1/2, 0, 1/2], [0, -1/2, 0, -1/2, 1/2, 0, -1/2, 0], [1/2, 0, -1/2, 0, 0, -1/2, 0, -1/2], [0, -1/2, 0, 1/2, 1/2, 0, 1/2, 0]])
---- シミュレーション結果の表示
(|000>, 1/4)
(|010>, 1/4)
(|100>, 1/4)
(|110>, 1/4)
まとめ
細かい違いはありますが、簡単な量子回路を書くくらいならば大きな違いはないと感じました。
基本的な回路の記法 | 回路の合成 | 作用の記述順序 | 量子ビットの表示順序 | print での回路表示 | |
---|---|---|---|---|---|
Blueqat | メソッドチェーン | + でつなげる | → | 0 から順に左から右へ | プログラム記述と同様の形式 |
Cirq | リスト | リストの連結、あるいは append メソッド | → | NamedQubit に与えた名前のアルファベット順で左から右へ | 図式表示 |
Qiskit | 時系列順操作 | compose メソッド | ↓ | 0 から順に右から左へ | 図式表示 |
SymPy | 作用素の積 | * でつなげる | ← | 0 から順に右から左へ | プログラム記述と同様の形式 |