Rustでゲート型の量子コンピュータのシミュレータライブラリを書いてみた

# はじめに

最近Q#を使っていてとても楽しかったのですが、.net系の言語は文法が今ひとつ馴染めません。また、文法にも不可解なところが多く（まああんまりドキュメントを熟読していないのでアレですが、おそらく実機での都合か何かかとは思います）、量子コンピューティング的には本質的ではないところで何回も引っかかってしまいました。

ということで、勉強がてら自分で0から（ゲート型の）量子コンピュータをシミュレートするライブラリを作ってみようと思い、[rusq](https://github.com/hajifkd/rusq)というライブラリを作ってみました。言語はストレスなく速いプログラムを書けるものということでRustをつかいました。

# 実装の詳細など

なんか量子コンピュータのシミュレーターとかいうと難しそうですが、かんたんです。$n$ Qubitは $2^n$ 次元複素Hilbert spaceに住んでいます。ですから、やることはただの $2^n$ 次元複素ベクトル空間での線形代数です。ということで、シンプルに`num::complex::Complex<f64>`を$2^n$個`Vec`にもたせて適当に操作します。
線形代数のライブラリとしては`ndarray`を用いました。たかだか$2^3\times2^3$程度の行列しか出てこないので、自分で書いても良かったのですが…。あとは測定の部分と部分空間を探す部分のコードが必要ですので、適当に書きました。
Qubit数の上限は試していません。メモリにもよりますがまあ30 Qubitくらいまでなら普通の時間でできると思います。一応内部実装はすべて64 bitです。

# つかいかた

`tests`以下にいくつか例をおきましたが、すこし解説しておきます。使いそうな構造体やtraitは`rusq::prelude::*`にあるので、これを`use`しておいてください。

まず、シミュレーター本体である`QuantumSimulator`のインスタンスを作ります。

```rust
let sim: QuantumSimulator = QunatumSimulator(2);
```

引数は、Qubitの数です。（大きいQubit数は試してないのでうっかりどこかで大きい配列をstackにおいちゃったりみたいなバグがあって動かなかったりするかもしれません。。）

`QuantumSimulator`のインスタンスができたら、Qubitを取得します。

```rust
let qubits: Vec<Qubit> = sim.get_qubits();
```

ちなみに`Qubit`自身は値を持っておらず、`QuantumSimulator`を通してのみ値を読める、という実装になっています。状態はすべて`QuantumSimulator`構造体が担っています。なお、`Qubit`の初期値は保証されません。また、初期値を定めるようなメソッドも作りませんでした。ですので、次のような関数を書く必要があります。

```rust
fn set(sim: &mut QuantumSimulator, qubit: &Qubit, r: MeasuredResult) {
    if sim.measure(qubit) != r {
        sim.X(qubit);
    }
}
```

測定操作に対応している`measure`メソッドが呼び出されると、Qubitの値が`MeasuredResult`型として返り、対応するQubitの状態が測られた`MeasuredResult`に対応する状態に射影されます。もしこの結果が欲しいものでなかったなら、`X`ゲートを施すことで状態を反転させます（`X`はQubitの0と1を入れ替える効果があります）。これによって、Qubitを望みの状態にしています。

（なお、Q#でこの実装になっている理由は、可観測量に対応する状態は一意に定まらないからだと思います）

ここで出てきた`X`のように、量子ゲートは`QuantumSimulator`のインスタンスメソッドとして実装されています。現時点では`X`以外のゲートは`Y, Z, H, ID, CNOT, CCNOT`があります。`X, Y, Z`はパウリ行列、`H`はHadamardゲート、`ID`は何もしない変換、`CNOT`は制御NOTで`CCNOT`はToffoliゲート（制御制御NOT）です。X, Y, Z, H, IDと異なり、CNOT, CCNOTは2こ、3このQubitを引数として取ります。たとえば

```rust
sim.CNOT(&qubits[0], &qubits[1]);
```

といった感じです。また、すでに出てきましたが、測定は`QuantumSimulator`の`measure`メソッドを呼んで下さい。値は`MeasuredResult`型で返ってきます。`MeasuredResult`型は`MeasuredResult::One`と`MeasuredResult::Zero`の二種の値を持ちます。

さて、このような操作を組み合わせる例として、EPR pairを作ってみるコードを掲載します。2つのQubit、`qubits[0]`と`qubits[1]`がエンタングルしていることがわかると思います。

```toml:Cargo.toml
[dependencies]
rusq = "0.1"
```

```rust:src/main.rs
extern crate rusq;

use rusq::prelude::*;

fn set(sim: &mut QuantumSimulator, qubit: &Qubit, r: MeasuredResult) {
    if sim.measure(qubit) != r {
        sim.X(qubit);
    }
}

fn main() {
    let mut sim = QuantumSimulator::new(2);
    let qubits = sim.get_qubits();
    let measure_count = 10000;

    for _ in 0..measure_count {
        set(&mut sim, &qubits[0], MeasuredResult::Zero);
        set(&mut sim, &qubits[1], MeasuredResult::Zero);

        sim.H(&qubits[0]);
        sim.CNOT(&qubits[0], &qubits[1]);

        assert_eq!(sim.measure(&qubits[0]), sim.measure(&qubits[1]));
    }
}
```

なお、このプログラム自体は[Q#のサンプルコード](https://docs.microsoft.com/en-us/quantum/quantum-writeaquantumprogram?view=qsharp-preview)からの丸パクリです。ライブラリの使い心地も似ていると思います。（いやホントは他の言語も触ってみるべきだったんですが…）

# 今後の方針など

（READMEとドキュメントを書きます…）

いまのところまだHadamard, $\sigma_i$, CNOT, CCNOTくらいしか実装していないので、他のゲートも実装します。（行列を書くだけなのですがどういうゲートがあってみたいな話をよく調べていない）
並列化も一応したいです。Rustでやったことないので、どれくらい大変かわかりませんが…。

バグなどもまだまだあると思います。issue, PR, またその他のご質問などもありましたらお気軽にどうぞ。

# 感想

作ってみたら拍子抜けするくらいかんたんでした。なんか間違っていそうで怖い。