オルトプラスエンジニアの日常をお伝えします!

量子コンピューティング++(1)

はじめに

こんにちは、最近はScalaにハマっているid:mitsutaka-takedaです。

MicrosoftやGoogleが続々と量子コンピュータのサービスやライブラリを公開しつつあります。数年後には「量子コンピュータ元年」が来るかもしれません。

C/C++でも量子コンピュータのシミュレータが多数実装されています。今回はgithubで公開されているQuantum++シミュレータで、量子コンピューティングの時代を少し覗いてみましょう。

Disclaimer

この記事は、量子コンピューティングを勉強し始めた素人が書いています。理解不足から間違えた記載が多々あると思いますので、参考資料を読んで下さい。

Qubit

現在のコンピュータの基本単位はbitで、量子コンピュータの基本単位はqubitです。

bitでは01かの2つの状態を取ります。一方、qubitも|0>|1>という2つの状態を取ります。 bitでは01か、どちらか2つの状態しか取れません。qubitは|0>|1>と、さらにその組み合わせの状態(superposition)を取ることができます。

ψ>=α0>+β1> |\\psi> = \\alpha |0> + \\beta |1>

α\\alphaβ\\betaは、ψ>|\\psi>を観測した時に0>|0>1>|1>になる確率を表現する係数です。確率を表現するため係数の絶対値の2乗の和が1となります(α2+β2=1|\\alpha|^2 + |\\beta|^2 = 1)。qubitは確率を含んだ状態であることと、bitのような特定の状態(0または1)にあるのを確定するにはqubitを観測する必要があることが、現在のコンピュータとの大きな違いです。

表と裏が出る確率が12\\frac{1}{2}づつの公平なコインをqubitで表現してみましょう。|0>を表、|1>を裏で表現すると、公平なコインは、α2=12|\\alpha|^2 = \\frac{1}{2}β2=12|\\beta|^2 = \\frac{1}{2}となるような係数α\\alphaβ\\betaで表現できます。

coin> =0>2+1>2 |coin> \\ = \\frac{|0>}{\\sqrt{2}} + \\frac{|1>}{\\sqrt{2}}

bitも複数つなげることができるようにqubitも複数つなげることができます。例えば、2 qubitは、0と1を2つ並べた|00>|01>|10>|11>の重ね合わせを表現することができます。確率的に4つの状態を取るものを表現できるため、例えば、出す手が3分の1づつのジャンケンは、|00>をグー、|01>をチョキ、|10>をパーとすると、以下のように表現できます。

rock paper cissors> =00>3+01>3+10>3+011> | rock\\ paper\\ cissors >\\ = \\frac{|00>}{\\sqrt{3}} + \\frac{|01>}{\\sqrt{3}} + \\frac{|10>}{\\sqrt{3}} + 0 \\cdot |11>

|11>に対応する手がないので係数は0になります。

ここで量子コンピュータをシミュレートするQuantum++を使用して、コインとジャンケンのqubitを表現するコードを書いてみます。

#include <iostream> #include "qpp.h" // Qubitsを表現するユーザ定義型リテラル。 template <char... Bits> qpp::ket operator "" _q(){ constexpr char bits[sizeof...(Bits) + 1] = {Bits..., '\0'}; qpp::ket q = qpp::ket::Zero(sizeof...(Bits)*2); q(std::stoi(bits, nullptr, 2)) = 1; return q; } int main() { // ( 表 ) + ( 裏 ) qpp::ket const coin = 0_q/std::sqrt(2) + 1_q/std::sqrt(2); std::cout << "= コイン =\n" << qpp::disp(coin) << '\n'; // ( グー ) + ( チョキ ) + ( パー ) qpp::ket const rock_paper_cissors = 00_q/std::sqrt(3) + 01_q/std::sqrt(3) + 10_q/std::sqrt(3); std::cout << "= ジャンケン =\n" << qpp::disp(rock_paper_cissors) << '\n'; }

このコードの実行結果は以下のようになります。実行結果の中で"#"以降は実行結果に追加したコメントです。標準出力にqubitを出力すると、各状態の係数が表示されます。120.707107\\frac{1}{\\sqrt {2}} \\approx 0.707107130.57735\\frac{1}{\\sqrt{3}} \\approx 0.57735です。

= コイン = 0.707107 # 表|0>の係数。 0.707107 # 裏|1>の係数。 = ジャンケン = 0.57735 # グー |00>の係数。 0.57735     # チョキ |01>の係数。 0.57735 # パー |10>の係数。 0 # 空き |11>の係数。

Qubitの観測

qubitは、複数の状態とその状態になりえる確率を掛けた重ね合わせの状態です。qubitを観測することで、qubitは特定のビットに収束します。

例えば、公平なコインの例では「表」|0>、「裏」|1>の出る割合が1/21/2でした。公平なコインを表現するqubitを観測すると1/21/2の確率で|0>、または、|1>に収束します。

coin>before=0>2+1>2 |coin>\_{before} = \\frac{|0>}{\\sqrt{2}} + \\frac{|1>}{\\sqrt{2}}
coin>measured={0>(1/2 probabilities)1>(1/2 probabilities) |coin>\_{measured} = \\begin{cases} |0> \\qquad (1/2\\space probabilities)\\\\ |1> \\qquad (1/2\\space probabilities) \\end{cases}

ジャンケンの場合は1/31/3づづの確率で「グー」、「チョキ」、「パー」を表現するビットに収束します。

rock paper cissors>before=00>3+01>3+10>3+011> |rock\\ paper\\ cissors>\_{before}\\enspace = \\frac{|00>}{\\sqrt{3}} + \\frac{|01>}{\\sqrt{3}} + \\frac{|10>}{\\sqrt{3}} + 0 \\cdot |11>
rock paper cissors>measured={00>(1/3 probabilities)01>(1/3 probabilities)10>(1/3 probabilities) |rock\\space paper\\space cissors>\_{measured}\\enspace = \\begin{cases} |00> \\qquad (1/3\\space probabilities)\\\\ |01> \\qquad (1/3\\space probabilities)\\\\ |10> \\qquad (1/3\\space probabilities) \\end{cases}

Quantum++で、コインを表現するqubitを観測してみます。

#include <iostream> #include "qpp.h" // Qubitsを表現するユーザ定義型リテラル。 template <char... Bits> qpp::ket operator "" _q(){ constexpr char bits[sizeof...(Bits) + 1] = {Bits..., '\0'}; qpp::ket q = qpp::ket::Zero(sizeof...(Bits)*2); q(std::stoi(bits, nullptr, 2)) = 1; return q; } int main() { // ( 表 ) + ( 裏 ) qpp::ket const coin = 0_q/std::sqrt(2) + 1_q/std::sqrt(2); // コインの表か裏か観測する。 // qpp::gt.Id2は、2-by-2の行列で、列ベクタは観測用のベクタ。1列目が|0>で|1>。 auto const& [result, ignore0, ignore1] = qpp::measure(coin, qpp::gt.Id2, {0}); std::cout << "観測結果 = " << (result == 0 ? "表" : "裏") << "\n"; // 1万回、観測して表と裏の回数を数える。 int heads_counter = 0, tails_counter = 0; for(int i = 0; i < 10000; ++i){ auto const& [result, ignore0, ignore1] = qpp::measure(coin, qpp::gt.Id2, {0}); (result == 0? heads_counter : tails_counter)++; } std::cout << "表の回数 = " << heads_counter << ", 裏の回数 = " << tails_counter << "\n"; }

以下が私の手元でのある実行の結果です。この結果は毎回実行するたびに異なりますが、表の回数、裏の回数はともに約5,000回になります。qubitは公平なコインを上手く表現できてるようです。

観測結果 = 裏 表の回数 = 5047, 裏の回数 = 4953

最後に

今回はQubitが確率的に表現されることと、観測することによって特定の状態に収束することをQuantam++で見てみました。次回は、qubitへの操作など見てみようと思います。

参考

ビルド・インストラクション

サンプル・コードはclang 5.0でビルドしています。qppをgithubからクローンして、Eigenをプロジェクト・ページからダウンロード。qppのディレクトリと同レベルにEigenのディレクトリを配置します。

以下のコマンドでqppがビルドできます。サンプルコードをビルドするには、qppのCMakeLists.txtを編集してサンプルコードをビルド対象に含めるか。

CC=clang CXX=clang++ cmake -DCMAKE_CXX_FLAGS="-std=c++17 -stdlib=libc++" -DWITH_OPENMP=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ~/src/cpp/qpp/ && cmake --build ./