簡単に量子プログラミングが始められるBlueqatライブラリ
を開発しています。
https://github.com/Blueqat/Blueqat
Blueqatバックエンド?
Blueqatは、numpyのシミュレータを用意していますが、
- もっと高速なシミュレータを動かしたい
- シミュレータじゃなく実機を動かしたい
- その他、違うこともしたい
- 回路のユニタリ行列を得る
- 回路をOpenQASM形式で出力する
 
などの用途にも使えるよう、「バックエンド」という形で機能の追加ができるように設計しています。
「何かシミュレータを自作したけど、インタフェース作るの面倒だなぁ」って場合なんかに、適当にバックエンドを作ってしまうと便利かもしれません。
今回は、OpenQASMの入力を受け入れられるシミュレータのバックエンドの作り方を説明します。
OpenQASMの入力が受け入れられるのは滅多にないかもしれませんが、可能な場合は、バックエンド作りが著しく簡単になります。
OpenQASMの入力を受け取って、結果を返すバックエンド
もし、自作シミュレータがOpenQASM入力を受け付けるなら、この方法で作るのが最も簡単です。
blueqat.backends.qasm_parser_backend_generator.generate_backend関数に、OpenQASMを受け取って結果を返す関数を渡すだけです。
IBM Qバックエンドは、その方法でバックエンドを作っているので、見ていきましょう。
https://github.com/Blueqat/Blueqat/blob/master/blueqat/backends/ibmq_backend.py
def _qasm_runner_qiskit(qasm, qiskit_backend=None, shots=None, returns=None, **kwargs):
    if returns is None:
        returns = "shots"
    elif returns not in ("shots", "draw", "_exception",
                         "qiskit_circuit", "qiskit_job", "qiskit_result"):
        raise ValueError("`returns` shall be None, 'shots', 'draw', " +
                         "'qiskit_circuit', 'qiskit_job', 'qiskit_result' or '_exception'")
    import_error = None
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            from qiskit import Aer, QuantumCircuit, execute
    except Exception as e:
        import_error = e
    if import_error:
        if returns == "_exception":
            return e
        if isinstance(import_error, ImportError):
            raise ImportError("Cannot import qiskit. To use this backend, please install qiskit." +
                              " `pip install qiskit`.")
        else:
            raise ValueError("Unknown error raised when importing qiskit. To get exception, " +
                             'run this backend with arg `returns="_exception"`')
    else:
        if returns == "_exception":
            return None
        qk_circuit = QuantumCircuit.from_qasm_str(qasm)
        if returns == "qiskit_circuit":
            return qk_circuit
        if returns == "draw":
            return qk_circuit.draw(**kwargs)
        if shots is None:
            shots = 1024
        if qiskit_backend is None:
            qiskit_backend = Aer.get_backend("qasm_simulator")
        job = execute(qk_circuit, backend=qiskit_backend, shots=shots, **kwargs)
        if returns == "qiskit_job":
            return job
        result = job.result()
        if returns == "qiskit_result":
            return result
        counts = Counter({bits[::-1]: val for bits, val in result.get_counts().items()})
        return counts
ibmq_backend = generate_backend(_qasm_runner_qiskit)
Circuit().h[0].m[:].run(backend=‘ibmq’, qiskit_backend=..., ...)のように書くと、_qasm_runner_qiskit(回路をOpenQASMに変換したもの, qiskit_backend=..., ...)のように呼び出されます。
呼び出されたとき、どういう挙動をするか見てみましょう。
    if returns is None:
        returns = "shots"
    elif returns not in ("shots", "draw", "_exception",
                         "qiskit_circuit", "qiskit_job", "qiskit_result"):
        raise ValueError("`returns` shall be None, 'shots', 'draw', " +
                         "'qiskit_circuit', 'qiskit_job', 'qiskit_result' or '_exception'")
このあたりは、受け取った引数の処理をしています。
Blueqatのバックエンドは、多くの場合、returnsという引数を受け入れるようにしていて、ここに、どういうタイプの結果を返してほしいか指定できるようにしています。
デフォルトのnumpyバックエンドでは、状態ベクトルか、測定結果か、などが選べて、ibmqでは、測定結果のほか、Qiskitでの回路やJobオブジェクトなどを取れるようにしています。また、デバッグ用に内部で使っているオブジェクトを返す、などの方法も考えられます。
ただし、returnsを指定しなくても動くよう、デフォルトの挙動を提供することを強く推奨します。
    import_error = None
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            from qiskit import Aer, QuantumCircuit, execute
    except Exception as e:
        import_error = e
    if import_error:
        if returns == "_exception":
            return e
        if isinstance(import_error, ImportError):
            raise ImportError("Cannot import qiskit. To use this backend, please install qiskit." +
                              " `pip install qiskit`.")
        else:
            raise ValueError("Unknown error raised when importing qiskit. To get exception, " +
                             'run this backend with arg `returns="_exception"`')
上は、qiskitがインストールされていなくてもBlueqat自体は動くようにするため、関数呼び出し時にimportするようにしています。
また、一時期、qiskitをimportするとWarningが出たので、それを抑える処理もしています。
    else:
        if returns == "_exception":
            return None
        qk_circuit = QuantumCircuit.from_qasm_str(qasm)
        if returns == "qiskit_circuit":
            return qk_circuit
        if returns == "draw":
            return qk_circuit.draw(**kwargs)
        if shots is None:
            shots = 1024
        if qiskit_backend is None:
            qiskit_backend = Aer.get_backend("qasm_simulator")
Qiskitのシミュレータを動かす準備をしながら、returnsの内容によっては、シミュレータを動かす前に結果を返しています。
        job = execute(qk_circuit, backend=qiskit_backend, shots=shots, **kwargs)
        if returns == "qiskit_job":
            return job
Qiskitのシミュレータを動かしています。もしQiskitのジョブを返すようにreturnsを指定していた場合、ジョブを返します。
        result = job.result()
        if returns == "qiskit_result":
            return result
        counts = Counter({bits[::-1]: val for bits, val in result.get_counts().items()})
        return counts
ジョブから結果を得て、Blueqatの他のバックエンドでも使われている形式に整形して結果を返します。
ibmq_backend = generate_backend(_qasm_runner_qiskit)
上で見た関数を渡してバックエンドを作ります。
バックエンドの登録と使用
作ったバックエンドは、登録しないとBlueqatでは使えません。
from blueqat import BlueqatGlobalSetting
# BlueqatGlobalSetting.register_backend(バックエンド名, バックエンド)
BlueqatGlobalSetting.register_backend(”myibmq”, ibmq_backend)
myibmqという名前で、バックエンドを登録できました。
Circuit().h[0].m[:].run_with_myibmq()
# または
Circuit().h[0].m[:].run(backend=”myibmq”)
run_with_バックエンド名()の形で登録したものが使えます。
BlueqatGlobalSetting.set_default_backend(”myibmq”)
登録したバックエンドをデフォルトに設定できました。
まとめ
OpenQASMを入力として受け入れられるシミュレータだと、かなり簡単にBlueqat対応ができることがわかりました。
そうでないものについても、Blueqatは、ちょっとの手間で実装できるようになっています。それについては、今後見ていきます。

