Help us understand the problem. What is going on with this article?

Blueqat 0.3 開発メモ

More than 1 year has passed since last update.

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()

helloyeahの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では、その後の処理のための準備をします
      • gatesctxの2つを返します
      • gatesは、引数から渡された実行すべきゲートのリストを、加工したい場合は加工して返します。加工する必要がない場合はそのまま返します
      • ctxは任意のオブジェクトです。状態を持たせるためのものです
    • gate_XXXXの部分は、ゲートAPIで定義したlowernameが入ります
      • 例えばXゲートはlowernamexなので、gate_xです
      • gate_xctxが渡されるので、Xゲート適用後のctxを返します
      • 他ゲートも同様です
    • _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は実装済み いずれも追加済み
  • backend追加
    • せっかくだからもう少し面白いバックエンドを加えてみたい
    • 他フレームワークで動かすためのbackendも追加したい
    • 現状、IBMのクラウドに対応。また、ユニタリ行列を出すバックエンドも追加
  • アーキテクチャ
    • 今回、Circuitからbackendを分離したことで、Circuitは、回路を保存しておく箱 + 計算に使うbackendオブジェクトを溜めておいて計算処理を呼び出す箱という役割が見えてきた。回路を保存しておく箱を別クラスに切り出したい
    • backend呼び出し処理のインタフェースで妥協したところがあるので、今後、少し変える可能性がある
  • 品質管理
    • ドキュメント整備、せめてdocstringは書く
    • Pythonのtype hintingを使うなど、引数の型、返り値の型を明示する
    • テストのさらなる充実
      • test_circuitは、Circuitのテストとbackendのテストが混ざってしまっているので、整理したい
gyu-don
来世はパンダになりたい。
https://github.com/gyu-don/
mdrft
量子コンピュータのアプリケーション、ミドルウェア、ハードウェアをフルスタックで開発
https://blueqat.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away