rust
量子コンピュータ

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

はじめに

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

ということで、勉強がてら自分で0から(ゲート型の)量子コンピュータをシミュレートするライブラリを作ってみようと思い、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のインスタンスを作ります。

let sim: QuantumSimulator = QunatumSimulator(2);

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

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

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

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

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を引数として取ります。たとえば

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

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

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

Cargo.toml
[dependencies]
rusq = "0.1"
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#のサンプルコードからの丸パクリです。ライブラリの使い心地も似ていると思います。(いやホントは他の言語も触ってみるべきだったんですが…)

今後の方針など

(READMEとドキュメントを書きます…)

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

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

感想

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