派遣で働くエンジニアのスキルアップを応援するサイト

PRODUCED BY RECRUIT

初心者でもわかる量子コンピュータの計算の仕組み【第5話】量子プログラミングをやってみよう

量子コンピュータに関して専門知識がなくても理解できるようになることを目指した本連載。量子ゲートと量子回路について学んだ第4話はご覧いただけましたか?

これまでの連載では、具体的に手で計算することにより、量子コンピュータを使ったルールを学びました。今回と次回は、いよいよ量子プログラミングを行い、量子コンピュータに計算をさせる方法を学びます。まず今回は、量子プログラミングを導入し、シミュレータを使って実行します。

一歩ずつゆっくり実際に動かすことで、量子コンピュータを馴染みのあるものにしていきましょう。

【筆者】束野 仁政さん
量子コンピュータ・プログラマ。学生時代に数学を専攻したのち、ソフトウェア・エンジニアを経て、現在は研究機関にて量子コンピュータの仕事をしている。量子コンピュータの面白さを多くの人に広めたいと思い、雑誌記事や同人誌の執筆、勉強会での発表等を行う。著書は「Elasticsearch NEXT STEP」(インプレスR&D)・Twitterアカウント(https://twitter.com/snuffkin

■免責
本連載は情報の提供のみを目的としています。

本連載の内容を実行・適用・運用したことで何が起きようとも、それは実行・適用・運用した人自身の責任であり、著者や関係者はいかなる責任も負いません。
■商標
本連載に登場するシステム名や製品名は、関係各社の商標または登録商標です。 また本書では、™、®、© などのマークは省略しています。

そもそも、今量子プログラミングを学んだほうがいい理由

これまでの連載で量子コンピュータの計算の仕組みを学んできましたが、座学だけでは実感がなかったり、しばらくすると忘れてしまったりしやすいと思います。そこで、今回と次回は、量子コンピュータ向けのプログラミング(量子プログラミング)を行い、どのように動作するのか確認します。

量子プログラミングを実際に行うことで、漠然とした量子コンピュータのイメージではなく、実体験をともなった理解を深めていきましょう。理解が深まることで、量子コンピュータのニュースなども「よく分からないけど凄そう」ではなく、よりリアルに見ることができるようになります。

量子プログラミング言語・ライブラリ

量子コンピュータで計算するためのプログラミング言語・ライブラリは、すでにいくつも存在します。特に、量子コンピュータのハードウェアを開発している企業は、プログラミング言語・ライブラリも開発していることが多いです。

IBMが量子コンピュータの実機を無料で利用できるサービスを提供しているため、ここでは、IBMのライブラリ「Qiskit(キスキット)」を紹介します。
今回は、Qiskitのバージョンは0.37.1を利用します。
バージョンによってAPIが変わることがあるため、注意が必要です。

■Qiskitをつかって量子プログラミングをシミュレートするってどういうこと?

量子コンピュータの実機を動かしたいですが、一般公開されている量子コンピュータは台数が少ないです。実機は世界中のユーザが使用するため、実行する際に待ち時間が発生し、動作確認には向いていません。そのため、量子プログラミングしたものを実機で実行する前に、量子コンピュータの動きを真似するシミュレータを使って動作確認します。シミュレータは普通のコンピュータで動作可能です。

今回はIBMから提供されているJupyterLabの環境で、シミュレータで実行し、次回は実機で実行します。

■Qiskitの実行方法

このコラムでは、IBMが提供している「IBM Quantum」というサービスを利用し、Qiskitで量子プログラミングを行う方法を紹介します。
PythonやJupyterLabの基本的な処理を実行できる前提とします。

まず、IBM Quantumにログインするため、次のURLをブラウザで表示してください。

https://quantum-computing.ibm.com/

▲図1.1: IBM Quantumのログイン画面

「Create an IBMid account.」をクリックし、アカウントを作成してログインしてください。

ログインすると次の画面が表示されます。「Launch Lab」ボタンをクリックして、JupyterLabを立ち上げます。

▲図1.2: IBM Quantumのダッシュボード

しばらくすると、JupyterLabが起動します。Notebookの欄の左側にある地球のマークのボタンをクリックして、ノートブックを立ち上げます。

▲図1.3: IBM QuantumのJupyterLab

すると、ノートブックが起動します。図1.4のように、コードがあらかじめ入力された状態になっています。

▲図1.4: Qiskit用のノートブック

これで、量子プログラミングの準備が整いました。

基本的な量子プログラミング

図1.5の量子回路を例に、量子プログラミングについて紹介します。実行前に、この量子回路を実行するとどのような結果になるか、理論的に確認してみましょう。

▲図1.5: 量子回路

今回と次回のコラムでは、Qiskitの記法に合わせて、バイト・オーダは右から左に向かって数値が大きくなるようにします。 たとえば$|0⟩|1⟩$と書いた場合、0番目の量子ビット$|1⟩$で、1番目の量子ビットが$|0⟩$です。

0番目の量子ビットに$H$ゲートを$H_0$と書きます。 また、0番目の量子ビットを制御ビット、1番目の量子ビットを対象ビットとする$CNOT$ゲートを$CNOT0,1 $ と書きます。 $|0⟩|0⟩$からユニタリ発展で変化していく様子を記述すると、次のようになります。

$\begin{array}{lll} \Ket{0}\Ket{0} &\xrightarrow{H_0} &\frac{1}{\sqrt{2}}\Ket{0}(\Ket{0}+\Ket{1}) \\ &\xrightarrow{CNOT_{0,1}} &\frac{1}{\sqrt{2}}(\Ket{0}\Ket{0}+\Ket{1}\Ket{1}) \\ \end{array}$

最後の量子状態を測定すると、$\Ket{0}\Ket{0}$と$\Ket{1}\Ket{1}$がそれぞれ確率$\left|\frac{1}{\sqrt{2}}\right|^2=\frac{1}{2}$で得られます。
このことを頭の片隅に置いておきましょう。

では実装に入ります。
ノートブックの最初のセルに、リスト1.1のコードがあらかじめ入力された状態になっています。これを実行し、必要なライブラリをインポートしましょう。

▼リスト1.1:インポート

import numpy as np

# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile, Aer, IBMQ
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from ibm_quantum_widgets import *
from qiskit.providers.aer import QasmSimulator

# Loading your IBM Quantum account(s)
provider = IBMQ.load_account()

次に、リスト1.2を実装し、量子回路を作成します。

▼リスト1.2:量子回路の作成

circuit = QuantumCircuit(2, 2) # 量子回路を初期化

# 量子回路の組み立て
circuit.h(0) # アダマール行列を適用
circuit.cx(0, 1) # CNOTを適用

# 測定
circuit.measure([0, 1], [0, 1])

■量子回路の初期化

1行目で量子回路QuantumCircuitの初期化を行います。最初の引数は量子レジスタ(量子ビットを保持する領域)の数、2番目の引数は古典レジスタ(古典ビットを保持する領域)の数です。量子ビットを測定した結果は古典レジスタにコピーされるため、古典レジスタを利用できるよう初期化が必要です。ここでは、それぞれ2ビット分の領域を初期化します。

■量子回路の組み立て

4行目-5行目で量子回路を組み立てています。
4行目で、量子ビット0に対してアダマール行列を適用します。 図1.5の左側にある$q_0$が量子ビット0に該当します。 $q$のインデックスと、プログラム中の配列$qr$のインデックスが対応します。
5行目で、$CNOT$ゲートを適用します。制御ビットは量子ビット0で、標的ビットは量子ビット1です。
このように、ユニタリ発展を組み合わせ、量子回路を組み立てます。古典プログラミング言語と比較すると、かなり雰囲気が異なります。古典プログラミング言語ではソートなどの抽象度の高い関数がありますが、このサンプルではレジスタを自分で操作する必要があります。アセンブリ言語で実装するイメージに近いです。

■測定

8行目のmeasure関数で量子状態を測定し、測定値を古典レジスタにコピーします。最初の引数は測定する量子レジスタのインデックスのリスト、2番目の引数は測定結果を書き込む古典レジスタ(古典ビットを保持する領域)のインデックスのリストです。

measure関数を実行すると、測定により量子状態は変化します。
ここまでが、量子回路の組み立てです。組み立てた時点では、まだ量子回路は実行されていない点に注意してください。 リスト1.2 に続けてリスト1.3 を実行してください。量子回路の実行し、結果取得を行います。

▼リスト1.3:実行と結果取得

from qiskit import execute

# 実行と結果取得
backend = Aer.get_backend('qasm_simulator') # デバイス指定 
job = execute(circuit, backend) # 量子プログラムを実行
result = job.result() # 結果を取得
print(result.get_counts(circuit)) # 結果をテキスト表示

■実行と結果取得

4行目のget_backend関数の引数には利用する量子デバイスを指定します。「qasm_simulator」は量子コンピュータを模したシミュレータで、実際の量子コンピュータではありません。
5行目のexecute関数で量子プログラムを実行します。デフォルトでは1024回実行されますが、引数shotsで変更できます。測定値が確率的であることや、実機でノイズが入ることを考慮し、何度か実行して測定値の分布を確認します。

リスト 1.3 を実行すると、リスト 1.4 の測定値を出力します。

▼リスト1.4:実行結果

{'00': 507, '11': 517}

リスト1.4は$\Ket{00}$が507回得られ、$\Ket{11}$が517回得られたことを表しています。おおよそ半々の確率になっており、理論上の値と整合しています。

■測定値は確率的

これまでに学んだように、測定値は確率的に得られるため、実行する毎に結果の回数が変化します。そのため、リスト1.4の値はあくまでも「おおよそ半々」です。 このような動作は古典コンピュータとは異なりますが、量子コンピュータとしては正しいです。これはあくまでもシミュレータで実行したプログラムですが、量子コンピュータのこのような現象もシミュレートしています。「おおよそ半々」からあまりにも外れた場合は、プログラムの誤りを疑いましょう。

実行結果と量子回路の可視化

量子プログラムのデバッグに使える便利な機能を紹介します。リスト1.4の出力だけでは、視覚的に結果を捉えづらいと感じます。Qiskitには測定値の確率分布を出力する機能があるため、これを使ってみましょう。Jupyter Notebookのように可視化可能な実行環境が必要ですが、リスト1.3に続けてリスト1.5を実装してください。

▼リスト1.5:確率分布表示

from qiskit.visualization import plot_distribution

# 確率分布表示
plot_distribution(job.result().get_counts(circuit))

4行目のplot_distribution関数を実行すると、図1.6の確率分布を表示します。

▲図1.6: 確率分布表示

図1.6は、$\Ket{00}$が確率0.495で得られ、$\Ket{11}$が確率0.505で得られたことを表しています。

リスト1.2に続けてリスト1.6のように実装し、量子回路を可視化します。

▼リスト1.6:量子回路を描画

# 量子回路を描画
circuit.draw(output='mpl')

リスト1.6の実行結果を表示したものが、図1.5になります。
量子回路を図として出力する機能は便利です。このコラムの量子回路の図は、この機能を利用して作成したものをベースにしています。

ゲートと関数の対応

前回のコラムで説明した量子ゲートと、Qiskitの関数名の対応は表1.1の通りです。

▼表1.1: ゲートと関数の対応

基本的にはゲート名を小文字にすればQiskitの関数名になりますが、名前が多少異なるものもあります。

ビット反転を行うゲートは、古典コンピュータではNOT、量子コンピュータではパウリ行列$X$です。 このため、$CNOT$(Controlled NOT)はControlled Xともいいます。 $CNOT$に対応するQiskitの関数名が$cx$になっているのは、このためです。

今回はここまでとなります。
シミュレータを使って、簡単な量子プログラミングを行いました。
次回は、無料で公開されている量子コンピュータの実機を使ってみます。

(コラム)意外と知られていない量子コンピュータを支える技術

意外と知られていない量子コンピュータを支える技術として、今回は量子コンパイラをご紹介します。

CPUがプログラムを実行するとき、みなさんが書いたプログラムを直接実行するわけではありません。CPUは足し算やメモリへのアクセスなど、単純な命令しか実行できません。例えば、ソートのような命令はCPUには用意されていません。また、「x=1」のような文字列を直接CPUに渡すことはできません。CPUはバイナリで表現された命令を処理します。

しかし、CPU用の単純な命令を人間が書くとソート処理を実装するのも大変ですし、人間がバイナリでプログラムを記述するのも現実的ではありません。そのため、複雑な命令をサポートし、人間が分かる表現でプログラムを記述します。それをCPUで実行できる命令に変換するのが、コンパイラの役割です。
実は、量子コンピュータにもコンパイラが存在します。量子コンピュータ向けのコンパイラを量子コンパイラと呼びます。

■量子コンパイラの機能

量子コンパイラはどのような機能があるのでしょうか。 ここでは、現在の量子コンパイラに備わっている機能をいくつかご紹介します。

  1. ハードウェアのトポロジーを考慮した量子回路に変換
  2. ハードウェアで実行できるゲートを考慮した量子回路に変換
  3. 短い量子回路に変換

それぞれの要素について、簡単に解説します。

■ハードウェアのトポロジーを考慮した量子回路に変換

Qiskitをはじめとした量子プログラミング・ライブラリは、ハードウェアの制約に捕らわれず実装できます。 しかし、実際のハードウェアには、様々な制約があます。

たとえば、量子プログラミング・ライブラリでは、$CNOT$ゲートの制御ビットと対象ビットを自由に指定できます。 ですが、実際の量子コンピュータではそうはいきません。

ここでは、IBMの127量子ビットの量子コンピュータ「ibm_washington」を例に説明します。 ibm_washingtonの量子ビットは図1.7のように配置されています。
このような量子ビットの配置のことをトポロジー(topology)と呼びます。

▲図1.7: ibm_washingtonのトポロジー

図の中の数字は量子ビットのインデックスを表しており、線でつながった量子ビット同士だけ$CNOT$ゲートを実行できます。

図の左上の「0」と隣接しているのは「1」と「14」だけです。
そのため、「0」の量子ビット$CNOT$操作ができるのは、「1」と「14」だけです。
したがって、隣接していない量子ビットを操作する場合は、量子ビットを入れ替えるゲート($SWAP$ゲートと呼びます)を利用して隣接させてから$CNOT$を実行します。

「0」と「2」で$CNOT$を行う場合は、次の手順になります。

  • $SWAP$で「1」と「2」を入れ替える
  • 「0」と(元々は「2」だった)「1」の間で$CNOT$を実行する
  • $SWAP$で「1」と「2」を入れ替える(元の配置に戻る)

量子プログラミングを行うとき、トポロジーを意識しながら実装するのは面倒ですし、ハードウェアに依存した実装になってしまいます。
そのため、トポロジーを気にせずに実装し、コンパイラがトポロジーを意識したプログラムに変換します。
古典コンパイラがCPUアーキテクチャに依存したプログラムに変換するのと同じように、量子コンパイラもハードウェアに依存したプログラムに変換します。

また、先ほどとは別の手順でも、「0」と「2」で$CNOT$を実行できます。
たとえば、「0」と「1」を入れ替えた後に、「1」と「2」の間で$CNOT$を実行してもよいです。
この辺りは、コンパイラの実装に依存します。

■ハードウェアで実行できるゲートを考慮した量子回路に変換

量子プログラミング・ライブラリでは表1.1に挙げたものをはじめ、多様なゲートを記述できます。しかし、実際にハードウェアで直接実行できるゲートは限られています。CPUが単純な命令しか実行できないのと同様です。

たとえば、先ほどの「ibm_washington」で直接実行できるゲートは$I, X, RZ, SX, CNOT$に限られます。 ここで、$RZ$は$Z$を拡張したゲートです。 また、$SX$は2回実行すると$X$と同じ操作になるゲートです。

これらの中に$H$はないため、$H$は直接実行できません。
実は、$H = RZ(\pi) \cdot SX \cdot RZ(\pi)$という等式が成り立つため、コンパイラが$RZ$と$SX$を組み合わせた量子回路に変換します。

このように、量子コンパイラは、ハードウェアで直接実行できるゲートに変換します。

■短い量子回路に変換

図1.8のような量子回路があったとします。
この量子回路をそのまま実行すると、3個の量子ゲートを実行することになります。

▲図1.8: 最適化前の量子回路

しかし、$X = H \cdot Z \cdot H$という等式が成り立つため、図1.9の量子回路を実行しても同じ結果になります。

▲図1.9: 最適化後の量子回路

こちらは、1個の量子ゲートを実行するだけで結果が出ます。量子コンピュータの実機ではノイズを無視できないため、ゲート数が少ないほど計算精度が上がります。また、量子状態は時間が経つと壊れてしまうため、計算時間が短い(ゲート数が少ない)ほど計算精度が上がります。

そのため、同じ結果を出力する量子回路であれば、より少ないゲート数の量子回路を実行したいです。量子コンパイラは、より少ないゲート数で同じ結果を返す量子回路に最適化を行います。

■その他の機能

その他にも、

  • 量子ビットの制御装置が解釈できる低レイヤの制御命令に変換する
  • (将来的には)ifやwhileなどの柔軟な条件分岐に対応する(現在の量子コンピュータでは柔軟な条件分岐はまだ実行できません)
  • (将来的には)量子誤り訂正を考慮した命令に変換する

などの機能が量子コンパイラは必要です。量子コンピュータが発展するにつれて必要な機能は増えていきます。計算精度にも影響するため、量子コンパイラは重要な技術のひとつです。

 

▼これまでの「初心者でもわかる量子コンピュータの計算の仕組み」
【第1話】数式なしで量子コンピュータの現状を理解しよう
【第2話】量子コンピュータで使用する計算の土台を学ぼう
【第3話】量子コンピュータの基本のルールを学ぼう
【第4話】基本の量子ゲートと量子回路をおさえよう
【第5話】量子プログラミングをやってみよう
【最終話】現在の量子コンピュータの限界とこれから

束野仁政さんの著書『量子コンピュータの頭の中――計算しながら理解する量子アルゴリズムの世界(技術評論社)』もあわせてご覧ください。(2023年7月追記)