Blueqat 0.3 開発メモ


# 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の量子コンピュータ入門編](https://aiacademy.jp/texts/#quantum)や、チュートリアル([日本語](https://github.com/mdrft/Blueqat_tutorials_ja)と[英語](https://github.com/Blueqat/Blueqat-tutorial))がおすすめです。
また、東京を中心に、よく勉強会を開催しています。大体は無料です。[こちら](https://qnn.connpass.com/)から参加登録ができます。

## この記事は?
Blueqat 0.3は、いろいろ改造してるうちに、割と大型アップデートとなってしまいました。
Blueqat自体と、新機能と、APIや開発方針などを自分のため、そしてBlueqatに興味をお持ちいただいた方のために残しておきます。

この記事はどちらかといえば、Blueqat開発者向けです。Blueqatを利用してみたい方は[日本語版チュートリアル](https://github.com/mdrft/Blueqat_tutorials_ja/tree/master/docs)をご利用ください。

# Circuitの定義とゲートの追加

```py
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で計算が行われます。

```py
Circuit().x[0].h[0,1].run()
```

のようにすると、状態ベクトルが返ってきます。
また、状態ベクトルではなく観測結果を取ることもできて、

```py
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のトークンを持っている場合、次のようにクラウドで動かせます。

```py
Circuit().x[0].h[0,1].m[:].run(backend="mqc", token="YOUR TOKEN HERE", shots=100)
```

今のところ、クラウド版では状態ベクトルは取れません。
量子ビット数が増えると膨大なデータ量になると思われるためです。(量子ビット数に対して、指数関数的にデータ量が増えます)
今後やるとしても、下手したら数GBになるデータを、非同期などの仕組みもなしに直接受け取ることにはならないんじゃないかなぁ、と思ってます。

# Gate API
このAPIは暫定です。今後変わる可能性があります。
**[追記]Gate APIの利用はバックエンドも絡んできて少し難しいため、現行版のBlueqatでは、代替の機能としてマクロ機能を用意しました。マクロ機能については[こちらの解説](https://qiita.com/gyu-don/items/4b72dd31e0212ea0721a#blueqat%E3%81%AE%E3%83%9E%E3%82%AF%E3%83%AD%E6%A9%9F%E8%83%BD)をご参照下さい。**

新Blueqatでは、独自のゲートを追加することができます。
ただし、ゲートごとの動作はバックエンドにより決まるので、独自ゲートをサポートしたバックエンドを作るか、あるいは、以下に述べるfallbackの仕組みを使うことになります。

Tゲートの実装を示します。

```py
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`によってゲートを登録します。

```py
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`を返します
        - 他ゲートも同様です
    - `_postprocess_run`では、`ctx`オブジェクトをもとに、`run`の結果を返します


`_preprocess_run`, `_postprocess_run`, `gate_XX`の定義例として、Xゲートだけを実装したバックエンドを作ってみます。

```py
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`で登録を行います。

```py
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のテストが混ざってしまっているので、整理したい