Blueqatとは
量子ゲート方式の量子コンピュータ回路をシミュレートするためのPythonライブラリです。
今のところはメインでは私が開発していて、他に数名、協力いただいています。
GitHub: https://github.com/Blueqat/Blueqat
チュートリアル(日本語): https://github.com/mdrft/Blueqat_tutorials_ja
チュートリアル(英語): https://github.com/Blueqat/Blueqat-tutorial
以前はnumpyで計算する仕組みでしたが、クラウドに投げて計算する仕組みもついて、そのついでに、計算方法をユーザが定義して動かせるようにもなりました。
他の類似ライブラリ
他に似たライブラリとして有名なものに、IBMのQiskit, GoogleのCirq, RigettiのPyQuilなどがあります。
それらと比べて、Blueqatは、手軽に使えるのが売りかと思っています。
量子コンピュータやBlueqatの学び方
AI Academyの量子コンピュータ入門編や、チュートリアル(日本語と英語)がおすすめです。
また、東京を中心に、よく勉強会を開催しています。大体は無料です。こちらから参加登録ができます。
この記事は?
Blueqat 0.3は、いろいろ改造してるうちに、割と大型アップデートとなってしまいました。
Blueqat自体と、新機能と、APIや開発方針などを自分のため、そしてBlueqatに興味をお持ちいただいた方のために残しておきます。
この記事はどちらかといえば、Blueqat開発者向けです。Blueqatを利用してみたい方は日本語版チュートリアルをご利用ください。
Circuitの定義とゲートの追加
from blueqat import Circuit
c = Circuit().x[0].h[1]
c.cx[2,3]
c.h[:2].m[:]
のようにゲートを追加できます。
メソッドチェーンのように繋げて書けるので、回路を手で書くのがそんなに辛くないのが特徴です。
Circuitのコピー
Circuitはゲートを追加すると変化してしまうので、コピーを取っておきたいときはcopy()
メソッドを使います。
numpyバックエンドでのrun
回路はrunメソッドで計算できます。何も指定しない場合は、numpyで計算が行われます。
Circuit().x[0].h[0,1].run()
のようにすると、状態ベクトルが返ってきます。
また、状態ベクトルではなく観測結果を取ることもできて、
Circuit().x[0].h[0,1].m[:].run(shots=100)
とすると、100回観測を行った結果が返ってきます。
以前のバージョンのBlueqatでは、last_resultメソッドやrun_historyなどを使っていましたが、新しいバージョンでは使えません。
MDR Quantum Cloudでのrun
[追記] MDR Quantum Cloudは現在、サービスを停止しているため、本機能は利用できません。
MDR Quantum Cloudのトークンを持っている場合、次のようにクラウドで動かせます。
Circuit().x[0].h[0,1].m[:].run(backend="mqc", token="YOUR TOKEN HERE", shots=100)
今のところ、クラウド版では状態ベクトルは取れません。
量子ビット数が増えると膨大なデータ量になると思われるためです。(量子ビット数に対して、指数関数的にデータ量が増えます)
今後やるとしても、下手したら数GBになるデータを、非同期などの仕組みもなしに直接受け取ることにはならないんじゃないかなぁ、と思ってます。
Gate API
このAPIは暫定です。今後変わる可能性があります。
[追記]Gate APIの利用はバックエンドも絡んできて少し難しいため、現行版のBlueqatでは、代替の機能としてマクロ機能を用意しました。マクロ機能についてはこちらの解説をご参照下さい。
新Blueqatでは、独自のゲートを追加することができます。
ただし、ゲートごとの動作はバックエンドにより決まるので、独自ゲートをサポートしたバックエンドを作るか、あるいは、以下に述べるfallbackの仕組みを使うことになります。
Tゲートの実装を示します。
class TGate(OneQubitGate):
"""T ($\\pi/8$) gate"""
lowername = "t"
def fallback(self, n_qubits):
return self._make_fallback_for_target_iter(n_qubits, lambda t: [RZGate(t, math.pi / 4)])
-
class TGate(OneQubitGate):
- ゲートは
blueqat.gate.Gate
や、そのサブクラスのblueqat.gate.OneQubitGate
,blueqat.gate.TwoQubitGate
を継承することが推奨されます
- ゲートは
-
lowername = "t"
- ゲートの名前(小文字)を定義します。この名前はバックエンドのAPIで使われます
-
fallback(self, n_qubits)
メソッド- バックエンドは、このゲートの動作を定義しているかもしれないし、していないかもしれません。定義していなかった場合で、別のゲートを使って書き表せる場合は、ゲートのリストを返します
- Tゲートの場合、RZゲートでZ軸をπ/4回転するのと同じですので、RZゲートが1つ入ったリストを返しています
- ゲートは複数返してもよく、例えばToffoliゲートの実装は、代替するのに15個のゲートを使っています
- 実装がなければ何もしなくてもいい場合は、
[]
を返します。例えばIゲートはそうしています - fallbackメソッドを定義しなかった場合、
Gate
クラスが継承されていれば、NotImplementedError
がraiseされます - fallbackは再帰的に解決されます。fallbackの定義が循環していれば(例: ゲートAのfallbackにゲートBが使われ、ゲートBのfallbackにゲートAが使われる)、fallbackの解決ができなくなるのでご注意ください
-
_make_fallback_for_target_iter
- これは
OneQubitGate
に定義されています。操作対象のビットの指定にスライス記法Circuit().t[0:5]
などが使われることがありますが、それを自分で展開せずに済ませるためのメソッドです
- これは
- バックエンドは、このゲートの動作を定義しているかもしれないし、していないかもしれません。定義していなかった場合で、別のゲートを使って書き表せる場合は、ゲートのリストを返します
ゲートの登録
BlueqatGlobalSetting.register_gate
によってゲートを登録します。
from blueqat import BlueqatGlobalSetting
class HelloGate(Gate):
lowername = "hello"
def fallback(self, n_qubits):
print("Hello")
return []
BlueqatGlobalSetting.register_gate("hello", HelloGate)
BlueqatGlobalSetting.register_gate("yeah", HelloGate)
c = Circuit().hello[0].yeah[0]
c.run()
hello
とyeah
の2つの名前で、HelloGate
を登録しています。
runしたときに、"hello"と2度出力されます。
※fallbackが実行される回数はbackendに依存します。一度のrunで複数回呼び出されることもありえますし、一度runしたら二度目は呼ばれないこともありえます。今回の例では、1度ずつ呼ばれています。
バックエンド API
このAPIは暫定です。今後変わる可能性があります。
ゲートのリストを受け取って、実行結果を返すのがバックエンドの役割になります。
バックエンドにはblueqat.backends.backendbase.Backend
を継承することが推奨されます。
各Circuit
オブジェクトは、必要なバックエンドのインスタンスを持ちます。ユーザが明示的に操作しない限りは、このインスタンスがCircuit
間で共有されることはなく、Circuit.copy()
が使われたときであっても、バックエンドは共有ではなくコピーされます。
(特殊なコピーが必要な場合はcopy
メソッドを実装してください。Backend
オブジェクトを継承している場合、copy()
は単に、deepcopyが行われます)
Circuit.run()
は複数回呼ばれうること、次に呼ばれたときには回路が伸びている可能性があること、複数の呼び出しでインスタンスの作り直しは行われないことに注意してください。
バックエンドの実装方法は、次の2種類があります
-
run()
メソッドを実装する- 自分自身で
run()
が呼び出されたときの挙動を定義する方法です。動作を自由に作り込めます
- 自分自身で
-
_preprocess_run()
,_postprocess_run()
,gate_XX()
を定義し、デフォルトのrun()
メソッドを使う- 自由度は下がりますが、自分で
run
を全部実装するよりは簡単です -
Backend.run()
を再実装しなかった場合、以下の処理が行われます- 初めに
gates, ctx = _preprocess_run(...)
- 各ゲートごとに
ctx = gate_XX(gate, ctx)
- 最後に
return _postprocess_run(ctx)
- 初めに
-
_preprocess_run
では、その後の処理のための準備をします-
gates
とctx
の2つを返します -
gates
は、引数から渡された実行すべきゲートのリストを、加工したい場合は加工して返します。加工する必要がない場合はそのまま返します -
ctx
は任意のオブジェクトです。状態を持たせるためのものです
-
-
gate_XX
のXX
の部分は、ゲートAPIで定義したlowername
が入ります- 例えばXゲートは
lowername
がx
なので、gate_x
です -
gate_x
にctx
が渡されるので、Xゲート適用後のctx
を返します - 他ゲートも同様です
- 例えばXゲートは
-
_postprocess_run
では、ctx
オブジェクトをもとに、run
の結果を返します
- 自由度は下がりますが、自分で
_preprocess_run
, _postprocess_run
, gate_XX
の定義例として、Xゲートだけを実装したバックエンドを作ってみます。
class OnlyXGateBackend(Backend):
def _preprocess_run(self, gates, n_qubits, args, kwargs):
return gates, [0] * n_qubits
def _postprocess_run(self, ctx):
return '|' + ''.join(str(bit) for bit in ctx) + '>'
def gate_x(self, gate, ctx):
for idx in gate.target_iter(len(ctx)):
ctx[idx] = int(not ctx[idx])
return ctx
-
_preprocess_run
では、ゲートは引数で受け取ったものをそのまま返し、ctxとして[0]をQubitの数だけ並べたリストを返しています -
gate_x
では、指定されたインデックスのビットを反転させています -
_postprocess_run
では、ctxをケット表記に変換したものを返しています
バックエンドの登録
BlueqatGlobalSetting.register_backend
で登録を行います。
class OnlyXGateBackend(Backend):
def _preprocess_run(self, gates, n_qubits, args, kwargs):
return gates, [0] * n_qubits
def _postprocess_run(self, ctx):
return '|' + ''.join(str(bit) for bit in ctx) + '>'
def gate_x(self, gate, ctx):
for idx in gate.target_iter(len(ctx)):
ctx[idx] = int(not ctx[idx])
return ctx
BlueqatGlobalSetting.register_backend("mybackend", OnlyXGateBackend)
Circuit().x[1].x[3].x[4].run(backend="mybackend") # => |01011>
Circuit().x[0].run_with_mybackend() # |1>
デフォルトのバックエンド
デフォルトのバックエンドは、通常、numpyになっていますが、変えることができます。
BlueqatGlobalSetting.set_default_backend('バックエンド名')
また、Circuitごとにデフォルトのバックエンドを設定することも出来ます。この設定はBlueqatGlobalSetting
でのデフォルトよりも優先されます。
c = Circuit(); c.set_default_backend('バックエンド名')
今後やりたいこと
あくまで私が勝手に思ってるだけです。開発者、協力者も増え始めているので、独断で進められなくなる時期はそろそろ来ているのかな、と。
太字は追記箇所
-
MDR Quantum Cloud関連サービス停止アカウントやトークンを毎回いちいち打たなくてもいいように何か用意したい今後のMDR Quantum Cloudの機能追加などに追従する (現状は何も決まってない)
- ゲート追加
- U1, U2, U3ゲート(IBM準拠)とCU1, CU2, CU3ゲートを追加
-
developブランチでU1, U2, U3は実装済みいずれも追加済み
-
- U1, U2, U3ゲート(IBM準拠)とCU1, CU2, CU3ゲートを追加
- backend追加
- せっかくだからもう少し面白いバックエンドを加えてみたい
- 他フレームワークで動かすためのbackendも追加したい
- 現状、IBMのクラウドに対応。また、ユニタリ行列を出すバックエンドも追加
- アーキテクチャ
- 今回、Circuitからbackendを分離したことで、Circuitは、回路を保存しておく箱 + 計算に使うbackendオブジェクトを溜めておいて計算処理を呼び出す箱という役割が見えてきた。回路を保存しておく箱を別クラスに切り出したい
- backend呼び出し処理のインタフェースで妥協したところがあるので、今後、少し変える可能性がある
- 品質管理
- ドキュメント整備、せめてdocstringは書く
- Pythonのtype hintingを使うなど、引数の型、返り値の型を明示する
- テストのさらなる充実
- test_circuitは、Circuitのテストとbackendのテストが混ざってしまっているので、整理したい