量子ビット、ゲート、回路
Kifumi Numata (2024年4月19日)
元の講義のPDFはこちらからダウンロードできます。コードスニペットの一部は静止画像のため、将来的に非推奨になる可能性があります。
この実験のおよそのQPU実行時間は5秒です。
1. はじめに
ビット、ゲート、回路は量子コンピューティングの基本的な構成要素です。量子ビットとゲートを使った回路モデルによる量子計算を学び、重ね合わせ、測定、エンタングルメントについても復習します。
このレッスンで学ぶ内容:
- 1量子ビットゲート
- ブロッホ球
- 重ね合わせ
- 測定
- 2量子ビットゲートとエンタングルメント状態
この講義の最後では、ユーティリティスケールの量子コンピューティングに不可欠な回路深度についても学びます。
2. 図としての計算
量子ビットやビットを使う際には、手元の入力を必要な出力に変換するため、それらを操作する必要があります。ビット数が非常に少ない最もシンプルなプログラムでは、この処理を回路図と呼ばれる図で表すと便利です。
左下の図は古典回路の例で、右下の図は量子回路の例です。どちらの場合も、入力は左側にあり出力は右側にあります。演算は記号で表され、その記号は主に歴史的な理由から「ゲート」と呼ばれています。
3. 1量子ビット量子ゲート
3.1 量子状態とブロッホ球
Qubit の状態は と の重ね合わせとして表されます。任意の量子状態は次のように表されます
ここで と は を満たす複素数です。
と は2次元複素ベクトル空間内のベクトルです:
したがって、任意の量子状態は次のようにも表されます
これにより、量子ビットの状態が、 と の正規直交基底を持つ2次元複素内積空間における単位ベクトルであることがわかります。それは1に正規化されています。
は状態ベクトルとも呼ばれます。
単一量子ビットの量子状態は次のようにも表されます
ここで と は、次の図における Bloch 球の角度です。
In the next few code cells, we will build up basic calculations from constituent pieces in Qiskit. We'll construct an empty circuit and then add quantum operations, discussing the gates and visualizing their effects as we go.
You can run the cell by "Shift" + "Enter". Import the libraries first.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Import the qiskit library
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from qiskit_ibm_runtime import Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram
量子回路の準備
1量子ビット回路を作成して描画します。
# Create the single-qubit quantum circuit
qc = QuantumCircuit(1)
# Draw the circuit
qc.draw("mpl")
Xゲート
Xゲートはブロッホ球の 軸周りの 回転です。 Xゲートを に適用すると になり、Xゲートを に適用すると になります。これは古典的なNOTゲートと似た演算であり、ビット反転とも呼ばれます。Xゲートの行列表現は以下のとおりです。
qc = QuantumCircuit(1) # Prepare the single-qubit quantum circuit
# Apply a X gate to qubit 0
qc.x(0)
# Draw the circuit
qc.draw("mpl")
IBM Quantum® では初期状態が に設定されているため、上記の量子回路を行列表現で書くと次のようになります。
次に、状態ベクトルシミュレーターを使ってこの回路を実行してみましょう。
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))
縦ベクトルは行ベクトルとして表示されており、複素数の虚部は でインデックスされています。
Hゲート
アダマールゲートは、ブロッホ球上の 軸と 軸の中間にある軸周りの 回転です。Hゲートを に適用すると のような重ね合わせ状態が生成されます。Hゲートの行列表現は以下のとおりです。
qc = QuantumCircuit(1) # Create the single-qubit quantum circuit
# Apply an Hadamard gate to qubit 0
qc.h(0)
# Draw the circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.70710678+0.j, 0.70710678+0.j],
dims=(2,))
This is
この重ね合わせ状態は非常に一般的で重要なため、独自の記号が与えられています:
に ゲートを適用することで、 と の重ね合わせを作成しました。この状態を計算基底(Bloch 球の図でいう z 軸に沿った方向)で測定すると、それぞれの状態が等しい確率で得られます。
状態
対応する 状態が存在することに気づいたかもしれません:
この状態を作成するには、まず X ゲートを適用して を作り、それから H ゲートを適用します。
qc = QuantumCircuit(1) # Create the single-qubit quantum circuit
# Apply a X gate to qubit 0
qc.x(0)
# Apply an Hadamard gate to qubit 0
qc.h(0)
# draw the circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([ 0.70710678+0.j, -0.70710678+0.j],
dims=(2,))
This is
に ゲートを適用すると、 と の等確率の重ね合わせが得られますが、 の符号が負になります。
3.2 単一量子ビットの量子状態とユニタリ発展
これまでに見てきたすべてのゲートの操作はユニタリです。つまり、ユニタリ演算子で表すことができます。言い換えると、出力状態は初期状態にユニタリ行列を作用させることで得られます。
ユニタリ行列とは、以下を満たす行列です。
量子コンピュータの操作の観点からは、量子ビットに量子ゲートを適用することで量子状態が発展すると言えます。代表的な単一量子ビットゲートには次のものがあります。
パウリゲート:
ここで、外積は次のように計算されます。
その他の代表的な単一量子ビットゲート:
これらの意味と使い方については、量子情報の基礎コースで詳しく説明しています。
演習 1
Qiskit を使って、以下に示す状態を準備する量子回路を作成してください。次に、各回路を状態ベクトルシミュレータで実行し、得られた状態をブロッホ球上に表示してください。ボーナスとして、ブロッホ球上のゲートと回転に関する直感を使って、最終状態がどうなるか予測できるか試してみてください。
(1)
(2)
(3)
ヒント:Zゲートは以下のように使用できます。
qc.z(0)
解答:
### (1) XX|0> ###
# Create the single-qubit quantum circuit
qc = QuantumCircuit(1) ##your code goes here##
# Add a X gate to qubit 0
qc.x(0) ##your code goes here##
# Add a X gate to qubit 0
qc.x(0) ##your code goes here##
# Draw a circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))
### (2) HH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.h(0)
qc.draw("mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))
### (3) HZH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)
qc.draw("mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))
3.3 測定
測定は理論的には非常に複雑なトピックです。しかし実用的には、 方向に沿った測定(すべての IBM® 量子コンピュータが行うように)は、量子ビットの状態 を または のどちらかに強制し、その結果を観測するものです。
- は測定時に が得られる確率です。
- は測定時に が得られる確率です。
したがって、 と は確率振幅と呼ばれます(「ボルン則」を参照)。
例えば、 は測定時に または になる確率が等しいです。 は75%の確率で になります。
Qiskit Aer シミュレータ
次に、上記の等確率の重ね合わせ状態を準備する回路を測定してみましょう。 Qiskit Aer シミュレータはデフォルトでノイズのない理想的な量子ハードウェアをシミュレートするため、測定ゲートを追加する必要があります。注意:Aer シミュレータは実際の量子コンピュータに基づくノイズモデルを適用することもできます。ノイズモデルについては後ほど取り上げます。
# Create a new circuit with one qubits (first argument) and one classical bits (second argument)
qc = QuantumCircuit(1, 1)
qc.h(0)
qc.measure(0, 0) # Add the measurement gate
qc.draw(output="mpl")
これで Aer シミュレータ上で回路を実行する準備が整いました。この例ではデフォルトの shots=1024 を適用します。つまり、1024 回測定します。その後、カウント結果をヒストグラムにプロットします。
# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()
# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)
# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()
# Print the results
counts = result[0].data.c.get_counts()
print(counts)
# Plot the counts in a histogram
plot_histogram(counts)
{'0': 521, '1': 503}
0 と 1 がほぼ 50% ずつの確率で測定されていることがわかります。ここではノイズはシミュレートされていませんが、状態は依然として確率的です。そのため、おおよそ 50-50 の分布が期待されますが、ちょうどその値になることはほとんどありません。コインを 100 回投げてもそれぞれがぴったり 50 回ずつになることはほとんどないのと同じです。
4. 多量子ビット量子ゲートとエンタングルメント
4.1 多量子ビット量子回路
次のコードで 2 量子ビットの量子回路を作成できます。各量子ビットに H ゲートを適用します。
# Create the two qubits quantum circuit
qc = QuantumCircuit(2)
# Apply an H gate to qubit 0
qc.h(0)
# Apply an H gate to qubit 1
qc.h(1)
# Draw the circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
dims=(2, 2))
注意:Qiskit のビット順序
Qiskit は量子ビットとビットの順序にリトルエンディアン表記を使用しており、量子ビット 0 はビット列の最も右側のビットとなります。例: は q0 が 、q1 が であることを意味します。量子コンピューティングの一部の文献ではビッグエンディアン表記(量子ビット 0 が最も左のビット)が使用されており、量子力学の文献でも同様の場合が多いため、注意が必要です。
また、量子回路を表現する際、 は常に回路の一番上に配置されます。 これを踏まえると、上記の回路の量子状態は単一量子ビットの量子状態のテンソル積として記述できます。
( )
Qiskit の初期状態は であり、各量子ビットに を適用すると等しい重ね合わせ状態に変化します。
測定の規則は単一量子ビットの場合と同じで、 が測定される確率は です。
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)

次に、この回路を測定してみましょう。
# Create a new circuit with two qubits (first argument) and two classical bits (second argument)
qc = QuantumCircuit(2, 2)
# Apply the gates
qc.h(0)
qc.h(1)
# Add the measurement gates
qc.measure(0, 0) # Measure qubit 0 and save the result in bit 0
qc.measure(1, 1) # Measure qubit 1 and save the result in bit 1
# Draw the circuit
qc.draw(output="mpl")
次に、Aer シミュレータを再び使用して、すべての可能な出力状態の相対確率がほぼ等しいことを実験的に確認します。
# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()
# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)
# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()
# Print the results
counts = result[0].data.c.get_counts()
print(counts)
# Plot the counts in a histogram
plot_histogram(counts)
{'10': 262, '01': 246, '00': 265, '11': 251}
期待通り、、、、 の各状態がほぼ 25% ずつ測定されました。
4.2 複数量子ビットの量子ゲート
CNOTゲート
CNOTゲート(「制御NOTゲート」またはCXゲート)は2量子ビットゲートです。つまり、その動作は制御量子ビットとターゲット量子ビットの2つの量子ビットに同時に作用します。CNOTゲートは、制御量子ビットが のときのみ、ターゲット量子ビットを反転させます。
| 入力 (ターゲット, 制御) | 出力 (ターゲット, 制御) |
|---|---|
| 00 | 00 |
| 01 | 11 |
| 10 | 10 |
| 11 | 01 |
まず、q0 と q1 の両方が である場合のこの2量子ビットゲートの動作をシミュレートし、出力の状態ベクトルを得てみましょう。使用する Qiskit の構文は qc.cx(control qubit, target qubit) です。
# Create a circuit with two quantum registers and two classical registers
qc = QuantumCircuit(2, 2)
# Apply the CNOT (cx) gate to a |00> state.
qc.cx(0, 1) # Here the control is set to q0 and the target is set to q1.
# Draw the circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
dims=(2, 2))
予想通り、 にCNOTゲートを適用しても状態は変化しませんでした。これは制御量子ビットが の状態にあるためです。 次に、CNOT演算に戻ります。今度は にCNOTゲートを適用して、何が起きるかを見てみましょう。
qc = QuantumCircuit(2, 2)
# q0=1, q1=0
qc.x(0) # Apply a X gate to initialize q0 to 1
qc.cx(0, 1) # Set the control bit to q0 and the target bit to q1.
# Draw the circuit
qc.draw(output="mpl")
# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
dims=(2, 2))
CNOTゲートを適用することで、 の状態が に変化しました。
シミュレーターで回路を実行し、これらの結果を確認してみましょう。
# Add measurements
qc.measure(0, 0)
qc.measure(1, 1)
# Draw the circuit
qc.draw(output="mpl")
# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()
# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)
# Run the job
sampler = Sampler(backend)
job = sampler.run([isa_qc])
result = job.result()
# Print the results
counts = result[0].data.c.get_counts()
print(counts)
# Plot the counts in a histogram
plot_histogram(counts)
{'11': 1024}
結果を見ると、 が100%の確率で測定されていることが確認できます。
4.3 量子もつれと実機での実行
まず、量子計算において特に重要なもつれ状態を紹介し、その後「もつれ」という用語を定義します。
この状態はベル状態と呼ばれます。
もつれ状態とは、量子状態 と からなる状態 であり、個々の量子状態のテンソル積では表現できないものです。
以下の が2つの状態 と を持つとします。
これら2つの状態のテンソル積は次のようになります。
しかし、この2つの方程式を同時に満たす係数 、、、および は存在しません。したがって、 は個々の量子状態 と のテンソル積では表現できません。これは、 がもつれ状態であることを意味しています。
ベル状態を生成し、実際の量子コンピューターで実行してみましょう。ここでは、Qiskitパターンと呼ばれる量子プログラムを書くための4つのステップに従います。
-
問題を量子回路と演算子にマッピングする
-
ターゲットハードウェア向けに最適化する
-
ターゲットハードウェアで実行する
-
結果を後処理する
ステップ1:問題を量子回路と演算子にマッピングする
量子プログラムにおいて、量子回路は量子命令を表現するためのネイティブな形式です。回路を作成する際には、通常、新しい QuantumCircuit オブジェクトを作成し、その後、命令を順番に追加していきます。
次のコードセルは、上で紹介した特定の2量子ビットのもつれ状態であるベル状態を生成する回路を作成します。
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure(0, 0)
qc.measure(1, 1)
qc.draw("mpl")
ステップ2:ターゲットハードウェア向けに最適化する
Qiskit は抽象的な回路を、ターゲットハードウェアの制約を考慮した QISA(量子命令セットアーキテクチャ)回路に変換し、回路のパフォーマンスを最適化します。最適化の前に、ターゲットハードウェアを指定します。
qiskit-ibm-runtime がインストールされていない場合は、最初にインストールする必要があります。詳細については、API リファレンスをご確認ください。
# Install
# !pip install qiskit-ibm-runtime
ターゲットハードウェアを指定します。
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
service.backends()
# You can specify the device
# backend = service.backend('ibm_kingston')
# You can also identify the least busy device
backend = service.least_busy(operational=True)
print("The least busy device is ", backend)
回路のトランスパイルは、また別の複雑なプロセスです。簡単に言うと、回路を「ネイティブゲート」(特定の量子コンピューターが実行できるゲート)を使って論理的に等価な回路に書き換え、回路内の量子ビットをターゲット量子コンピューター上の最適な実量子ビットにマッピングします。トランスパイルの詳細については、こちらのドキュメントを参照してください。
# Transpile the circuit into basis gates executable on the hardware
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
target_circuit = pm.run(qc)
target_circuit.draw("mpl", idle_wires=False)
トランスパイルによって回路が新しいゲートを使って書き換えられていることが確認できます。詳細については、ECRGate のドキュメントを参照してください。
ステップ3:ターゲット回路を実行する
では、実機でターゲット回路を実行します。
sampler = Sampler(backend)
job_real = sampler.run([target_circuit])
job_id = job_real.job_id()
print("job id:", job_id)
実機での実行では、量子コンピューターは貴重なリソースであり、非常に需要が高いため、キューでの待機が必要になる場合があります。job_id は、後でジョブの実行状況や結果を確認するために使用されます。
# Check the job status (replace the job id below with your own)
job_real.status(job_id)
IBM Quantumのダッシュボードからジョブの状態を確認することもできます:https://quantum.cloud.ibm.com/workloads
# If the Notebook session got disconnected you can also check your job status
# by running the following code
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
job_real = service.job(job_id) # Input your job-id between the quotations
job_real.status()
# Execute after job has successfully run
result_real = job_real.result()
print(result_real[0].data.c.get_counts())
ステップ4:結果を後処理する
最後に、結果を値やグラフなど期待される形式の出力に変換するために後処理を行います。
plot_histogram(result_real[0].data.c.get_counts())
ご覧のように、 と が最も頻繁に観測されています。期待されるデータ以外にもいくつかの結果が含まれていますが、これはノイズと量子ビットのデコヒーレンスによるものです。量子コンピューターにおけるエラーとノイズについては、このコースの後続レッスンでさらに詳しく学びます。
4.4 GHZ状態
もつれの概念は、2量子ビットを超えるシステムにも拡張できます。GHZ状態(Greenberger-Horne-Zeilinger状態)は、3つ以上の量子ビットの最大もつれ状態です。3量子ビットのGHZ状態は次のように定義されます。
これは以下の量子回路で生成できます。
qc = QuantumCircuit(3, 3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)
qc.draw("mpl")
量子回路の「深さ(デプス)」は、量子回路を説明するための有用でよく使われる指標です。量子回路を左から右へとたどり、複数量子ビットのゲートで接続されているときのみ量子ビットを切り替えます。そのパス上のゲートの数を数えます。回路を通るあらゆるパスにおけるゲート数の最大値がデプスです。現代のノイズのある量子コンピューターでは、デプスの浅い回路ほどエラーが少なく、良好な結果が得られやすいです。非常に深い回路はそうではありません。
QuantumCircuit.depth() を使って量子回路のデプスを確認できます。上記の回路のデプスは4です。最上位の量子ビットは測定を含め3つのゲートしか持ちませんが、最上位の量子ビットから量子ビット1または量子ビット2へのパスにはさらにCNOTゲートが含まれます。
qc.depth()
4
演習2
8量子ビットシステムのGHZ状態は次のとおりです。
できるだけ浅い回路でこの状態を準備するコードを書いてください。測定ゲートを含む最も浅い量子回路のデプスは5です。
解答:
# Step 1
qc = QuantumCircuit(8, 8)
##your code goes here##
qc.h(0)
qc.cx(0, 4)
qc.cx(4, 6)
qc.cx(6, 7)
qc.cx(4, 5)
qc.cx(0, 2)
qc.cx(2, 3)
qc.cx(0, 1)
qc.barrier() # for visual separation
# measure
for i in range(8):
qc.measure(i, i)
qc.draw("mpl")
# print(qc.depth())
print(qc.depth())
5
from qiskit.visualization import plot_histogram
# Step 2
# For this exercise, the circuit and operators are simple, so no optimizations are needed.
# Step 3
# Run the circuit on a simulator to get the results
backend = AerSimulator()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=1024)
result = job.result()
counts = result[0].data.c.get_counts()
print(counts)
# Step 4
# Plot the counts in a histogram
plot_histogram(counts)
{'11111111': 535, '00000000': 489}
5. まとめ
量子ビットとゲートを用いた回路モデルによる量子計算を学び、重ね合わせ、測定、もつれについて復習しました。また、実際の量子デバイスで量子回路を実行する方法も学びました。
GHZ回路を作成する最後の演習では、回路のデプスを削減しようとしました。これは、ノイズのある量子コンピューターでユーティリティスケールの解を得るための重要な要素です。このコースの後続レッスンでは、ノイズとエラー軽減手法について詳しく学びます。このレッスンでは入門として理想的なデバイスにおける回路デプスの削減を考えましたが、実際には量子ビット接続性などの実デバイスの制約も考慮する必要があります。これについては、このコースの後続レッスンでさらに詳しく学びます。
# See the version of Qiskit
import qiskit
qiskit.__version__
'2.0.2'