はじめに
最近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]);
といった感じです。また、すでに出てきましたが、測定はQuantumSimulator
のmeasure
メソッドを呼んで下さい。値はMeasuredResult
型で返ってきます。MeasuredResult
型はMeasuredResult::One
とMeasuredResult::Zero
の二種の値を持ちます。
さて、このような操作を組み合わせる例として、EPR pairを作ってみるコードを掲載します。2つのQubit、qubits[0]
とqubits[1]
がエンタングルしていることがわかると思います。
[dependencies]
rusq = "0.1"
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, またその他のご質問などもありましたらお気軽にどうぞ。
感想
作ってみたら拍子抜けするくらいかんたんでした。なんか間違っていそうで怖い。