The new Qiskit Textbook beta is now available. Try it out now
実験 2. 量子測定
from qiskit import *
import numpy as np
from numpy import linalg as la
from qiskit.tools.monitor import job_monitor
import qiskit.tools.jupyter

パート 1: 量子ビットの状態を測定する


Goal

量子ビットのブロッホ成分を決定する

量子コンピューターの操作の基本は、1つまたは複数の量子ビットのブロッホ成分を計算する機能です。これらの成分は、パウリ演算子$X, Y, Z$の期待値に対応し、量子化学や最適化などのアプリケーションにとって重要な量です。 残念ながら、これらの値を同時に計算することは不可能であるため、同じ回路を何度も実行する必要があります。さらに、測定は計算基底(Z基底)に制限されているため、xおよびy成分にアクセスするには、各パウリ演算子を標準基底に回転させる必要があります。 ここでは、ブロッホ球上のランダムベクトルの場合を考慮してメソッドを検証します。

📓 1. 計算基底で任意の量子状態$|q\rangle$に対するパウリ演算子の期待値を計算する。

例として、パウリ$Z$ゲートの期待値の場合を示します。

パウリ$Z$ゲートの対角表現(スペクトル形式または正規直交分解とも呼ばれます)とパウリゲート間の関係(ここを参照)を使用すると、$ X, Y, Z $ ゲートの期待値は次のように記述できます。

$$ \begin{align} \langle Z \rangle &=\langle q | Z | q\rangle =\langle q|0\rangle\langle 0|q\rangle - \langle q|1\rangle\langle 1|q\rangle =|\langle 0 |q\rangle|^2 - |\langle 1 | q\rangle|^2\\\\ \langle X \rangle &= \\\\ \langle Y \rangle &= \end{align} \\ $$

したがって、量子ビット状態のパウリ演算子の期待値$|q\rangle$は、対応する軸の基底を回転させて、標準基底で測定を行うことによって取得できます。上記の式が示すように、2つの可能な結果0と1を取得する確率を使用して、目的の期待値を評価します。

2. qasmシミュレーターを使用して、量子ビットのブロッホ座標を測定し、ベクトルをブロッホ球にプロットします

📓ステップA. 回路メソッドを使用して量子ビット状態を作成し、パラメーターとして2つのランダムな複素数でinitializeします。

関数initializeの使用方法については、こちらを確認してください。 (arbitrary initializationに移動します。)

qc = QuantumCircuit(1)

#### ここにコードを記入します

📓 ステップB. 質問1の回答に基づいて、$X, Y, Z$ ゲートの期待値を測定する回路を構築します。以下のセルを実行して、qasmシミュレーターを使用してstep Aの量子ビットのブロッホ座標を推定します。

例として$Z$ ゲート測定の回路を示します。

# 量子ビット0のz測定
measure_z = QuantumCircuit(1,1)
measure_z.measure(0,0)

# 量子ビット0のx測定
measure_x = QuantumCircuit(1,1)
# ここにコードを記入します







# 量子ビット0のy測定
measure_y = QuantumCircuit(1,1)
# ここにコードを記入します







shots = 2**14 # 統計に使うサンプル数
sim = Aer.get_backend('qasm_simulator')
bloch_vector_measure = []
for measure_circuit in [measure_x, measure_y, measure_z]:
    
    # 選択した測定で回路を実行し、各ビット値を出力するサンプル数を求めます
    counts = execute(qc+measure_circuit, sim, shots=shots).result().get_counts()

    # 各ビット値に対して確率を計算します
    probs = {}
    for output in ['0','1']:
        if output in counts:
            probs[output] = counts[output]/shots
        else:
            probs[output] = 0
            
    bloch_vector_measure.append( probs['0'] -  probs['1'] )

# ブロッホ球ベクトルの正規化
bloch_vector = bloch_vector_measure/la.norm(bloch_vector_measure)

print('The bloch sphere coordinates are [{0:4.3f}, {1:4.3f}, {2:4.3f}]'
      .format(*bloch_vector))    

ステップ C. ブロッホ球にベクトルをプロットします。

IQXで作業しない限り、interactive bloch_sphereの次のセルは正しく実行されないことに注意してください。interactiveでない場合にはplot_bloch_vectorを使用するか、以下をターミナルで実行して kaleidoscopeをインストールできます。

pip install kaleidoscope

また、インストール後にカーネルを再起動する必要があります。 interactive bloch_sphereの使用方法の詳細については、こちらをご覧ください。

from kaleidoscope.interactive import bloch_sphere

bloch_sphere(bloch_vector, vectors_annotation=True)
from qiskit.visualization import plot_bloch_vector

plot_bloch_vector( bloch_vector )

パート 2: エネルギーの測定


Goal

qasmシミュレータを使用して、水素の基底状態のエネルギーレベルを求めます。

量子系のエネルギーは、パート1で習得した手順を通じて、エルミート演算子であるハミルトニアンの期待値を測定することで推定できます。

水素の基底状態は、単一の固有の状態として定義されていませんが、実際には、電子と陽子のスピンのために4つの異なる状態が含まれています。この実験ラボのパート2では、パウリ演算子で表されたハミルトニアンで2つのスピンのシステムのエネルギー期待値を計算することにより、hyperfine splitting(超微細分割)によるこれら4つの状態間のエネルギー差を求めます。 hyperfine structure(超微細構造)の詳細については、こちらをご覧ください

2つの量子ビット相互作用ハミルトニアン$H = A(XX+YY+ZZ)$のシステムを考えてみましょう。ここで、$A = 1.47e^{-6} eV$で、$X, Y, Z$はパウリゲートです。次に、システムのエネルギー期待値は、ハミルトニアンの各項の期待値を組み合わせることによって評価できます。 この場合、$E = \langle H\rangle = A( \langle XX\rangle + \langle YY\rangle + \langle ZZ\rangle )$です。

📓 1. 計算基底で、任意の2量子ビット状態 $|\psi \rangle$ に対するハミルトニアンの各項の期待値を表します。

例として、$\langle ZZ\rangle$ の場合を示します。

$$ \begin{align} \langle ZZ\rangle &=\langle \psi | ZZ | \psi\rangle =\langle \psi|(|0\rangle\langle 0| - |1\rangle\langle 1|)\otimes(|0\rangle\langle 0| - |1\rangle\langle 1|) |\psi\rangle =|\langle 00|\psi\rangle|^2 - |\langle 01 | \psi\rangle|^2 - |\langle 10 | \psi\rangle|^2 + |\langle 11|\psi\rangle|^2\\\\ \langle XX\rangle &= \\\\ \langle YY\rangle &= \end{align} $$

2. 2つの量子ビットがエンタングル状態のときに、qasmシミュレーターを使用してシステムのエネルギー期待値を測定します。ベル基底に関しては、4つの異なるエンタングル状態があります。

📓ステップA. 4つの異なるベル状態を準備するための回路を構築します。

各ベル状態に次のラベルを付けます。 $$ \begin{align} Tri1 &= \frac{1}{\sqrt2} (|00\rangle + |11\rangle)\\ Tri2 &= \frac{1}{\sqrt2} (|00\rangle - |11\rangle)\\ Tri3 &= \frac{1}{\sqrt2} (|01\rangle + |10\rangle)\\ Sing &= \frac{1}{\sqrt2} (|10\rangle - |01\rangle) \end{align} $$

# 状態Tri1の回路
Tri1 = QuantumCircuit(2)
# ここにコードを記入します






# 状態Tri2の回路
Tri2 = QuantumCircuit(2)
# ここにコードを記入します





# 状態Tri3の回路
Tri3 = QuantumCircuit(2)
# ここにコードを記入します






# 状態Singの回路
Sing = QuantumCircuit(2)
# ここにコードを記入します

📓ステップB. 質問1への回答に基づいて、ハミルトニアンの各項の期待値を測定する回路を作成します。

# <ZZ> 
measure_ZZ = QuantumCircuit(2)
measure_ZZ.measure_all()

# <XX>
measure_XX = QuantumCircuit(2)
# ここにコードを記入します





# <YY>
measure_YY = QuantumCircuit(2)
# ここにコードを記入します

ステップC. 以下のセルを実行して、qasmシミュレーターで回路を実行し、各状態のエネルギー期待値を求めます。

shots = 2**14 # 統計に使うサンプル数

A = 1.47e-6 #Aの単位はeV
E_sim = []
for state_init in [Tri1,Tri2,Tri3,Sing]:
    Energy_meas = []
    for measure_circuit in [measure_XX, measure_YY, measure_ZZ]:
    
        # 選択した測定値で回路を動作させ、各ビット値を出力するサンプル数を求めます
        qc = state_init+measure_circuit
        counts = execute(qc, sim, shots=shots).result().get_counts()

        # それぞれの計算基底での確率を計算
        probs = {}
        for output in ['00','01', '10', '11']:
            if output in counts:
                probs[output] = counts[output]/shots
            else:
                probs[output] = 0
            
        Energy_meas.append( probs['00'] - probs['01'] - probs['10'] + probs['11'] )
 
    E_sim.append(A * np.sum(np.array(Energy_meas)))
# このセルを実行して結果を表示します

print('状態Tri1のエネルギー期待値 : {:.3e} eV'.format(E_sim[0]))
print('状態Tri2のエネルギー期待値 : {:.3e} eV'.format(E_sim[1]))
print('状態Tri3のエネルギー期待値 : {:.3e} eV'.format(E_sim[2]))
print('状態Singのエネルギー期待値 : {:.3e} eV'.format(E_sim[3]))

ステップD. 結果を理解する。

エネルギーの期待値が正常に見つかった場合、三重項状態($|Tri1\rangle, |Tri2\rangle, |Tri3\rangle$)に対してまったく同じ値$A (= 1.47e^{-6} eV)$ が得られます。一重項状態$|Sing\rangle$は$-3A (= -4.41e^{-6} eV)$ です。

ここで行ったことは、水素の基底状態に対応する4つの異なるスピン状態のエネルギーを測定し、スピン-スピン結合によって引き起こされるエネルギーレベルで hyperfine structure(超微細構造)を観測したことです。 一重項状態と三重項状態の間のこの小さなエネルギーの違いが、銀河の構造をマッピングするために使用される有名な21cmの波長の放射の理由です。

下のセルでは、三重項状態と一重項状態の間の遷移からの発光の波長を確認します。

# 換算プランク定数(eV)と光の速度(cgs単位)
hbar, c = 4.1357e-15, 3e10

# 三重項状態と一重項状態の間のエネルギー差
E_del = abs(E_sim[0] - E_sim[3])

# エネルギー差に伴う周波数
f = E_del/hbar

# 周波数を波長(cm)に変換
wavelength = c/f

print('超微細構造の遷移による放射の波長は : {:.1f} cm'.format(wavelength))

パート3:量子コンピューターで回路を実行する


Goal

IBM量子システムで回路を再実行します。結果に対して測定誤差の軽減を実行して、エネルギー推定の精度を向上させます。

ステップA. 次のセルを実行してアカウントを読み込み、バックエンドを選択します

provider = IBMQ.load_account()
backend = provider.get_backend('ibmq_athens')

ステップB. 量子システム上で回路を実行する

実験1では、実際の量子システム上で複数の回路を実行する際に、各回路を別々のジョブとして投入し、複数のジョブIDを生成していました。今回は、すべての回路をリスト化し、そのリストを1つのジョブとして実行します。こうすることで、すべての回路を一度に実行することができ、キューでの待ち時間を短縮できる可能性があります。

また、実行する回路はすべて1つまたは2つの量子ビットゲートで構成されているため、ここでは transpile は使用しません。 execute 関数を使って、initial_layoutとoptimization_levelを指定することは可能です。 transpile を使用しない場合、トランスパイルされた回路にはアクセスできませんが、今回の場合は問題ではありません。

📓 バックエンドの設定情報を確認し、ウィジェットを通してエラーマップを確認し、initial_layoutを決定します。.

# このセルを実行すると、ウィジェットを通じてバックエンドの情報が得られます
backend
# 初期レイアウトの選択をリスト変数 `initial_layout` に代入
initial_layout = 

以下のセルを実行して、バックエンドのinitial_layoutを使った回路を実行します。

qc_all = [state_init+measure_circuit for state_init in [Tri1,Tri2,Tri3,Sing] 
          for measure_circuit in [measure_XX, measure_YY, measure_ZZ] ]  

shots = 8192
job = execute(qc_all, backend, initial_layout=initial_layout, optimization_level=3, shots=shots)
print(job.job_id())
job_monitor(job)
# ジョブの結果を得ます
results = job.result()
## 完了したジョブの結果にアクセスするには
#results = backend.retrieve_job('job_id').result()

ステップ C. 以下のセルを実行して、前のステップの結果から基底状態のエネルギーレベルを推定します。

def Energy(results, shots):
    """水素の基底状態のエネルギー準位を計算します。
    
    Parameters:
        results (obj): 結果, ハミルトニアンを測定する回路を実行した結果です。
        shots (int): ショット数, 回路を実行するショット数です。
        
    Returns:
        Energy (list): 4つの異なる水素の基底状態のエネルギーの値です。
    """
    E = []
    A = 1.47e-6

    for ind_state in range(4):
        Energy_meas = []
        for ind_comp in range(3):
            counts = results.get_counts(ind_state*3+ind_comp)
        
            # 各計算基底における確率を計算します
            probs = {}
            for output in ['00','01', '10', '11']:
                if output in counts:
                    probs[output] = counts[output]/shots
                else:
                    probs[output] = 0
            
            Energy_meas.append( probs['00'] - probs['01'] - probs['10'] + probs['11'] )

        E.append(A * np.sum(np.array(Energy_meas)))
    
    return E
E = Energy(results, shots)

print('状態Tri1のエネルギー期待値 : {:.3e} eV'.format(E[0]))
print('状態Tri2のエネルギー期待値 : {:.3e} eV'.format(E[1]))
print('状態Tri3のエネルギー期待値 : {:.3e} eV'.format(E[2]))
print('状態Singのエネルギー期待値 : {:.3e} eV'.format(E[3]))

ステップ D. 測定誤差の軽減

量子システム上で回路を動作させて得られた結果は、エネルギー緩和、位相緩和、量子ビット間のクロストークなど、さまざまな原因によるノイズのため、正確なものではありません。このステップでは、測定誤差を軽減することで、ノイズの影響を軽減していきます。先に、こちらのビデオをご覧ください。

from qiskit.ignis.mitigation.measurement import *

📓関数'complete_meas_cal'を使用して、すべての基底状態の測定誤差をプロファイリングする回路を構築します。測定フィルター・オブジェクト'meas_filter'を取得して、ノイズの多い結果に適用し、読み出し(測定)誤差を軽減します。

このタスクを完了するためのさらに役立つ情報は、こちらにあります。

# meas_calibsの回路を作成するコードをこちらに記入します
meas_calibs, state_labels = 



# 選択したバックエンドにおいてmeas_calibsを実行します
job = execute(meas_calibs, backend, shots = shots)
print(job.job_id())
job_monitor(job)
cal_results = job.result()
## 完了したジョブの結果にアクセスするには
#cal_results = backend.retrieve_job('job_id').result()


# 測定フィルター・オブジェクト'meas_filter'を得るためのコードをここに記入します
results_new = meas_filter.apply(results)
E_new = Energy(results_new, shots)

print('状態Tri1のエネルギー期待値 : {:.3e} eV'.format(E_new[0]))
print('状態Tri2のエネルギー期待値 : {:.3e} eV'.format(E_new[1]))
print('状態Tri3のエネルギー期待値 : {:.3e} eV'.format(E_new[2]))
print('状態Singのエネルギー期待値 : {:.3e} eV'.format(E_new[3]))

ステップ E. 結果を解釈する。

📓 測定誤差を軽減した場合としない場合の、4つの状態すべてのエネルギー値の相対的な誤差(または小数の誤差)を計算します。

# シミュレーションによるエネルギー推定の結果
# 量子システム上で誤差を軽減しないで実行した場合と
# 誤差を軽減した場合の numpy 配列形式での表示
Energy_exact, Energy_exp_orig, Energy_exp_new = np.array(E_sim), np.array(E), np.array(E_new)
# エラー軽減なしでエネルギー値の相対エラーを計算し
# サイズ4のnumpy配列変数 `Err_rel_orig`に割り当てます 
Err_rel_orig = 
# エラー軽減ありでエネルギー値の相対エラーを計算し
# サイズ4のnumpy配列変数 Err_rel_new`に割り当てます 
Err_rel_new = 
np.set_printoptions(precision=3)

print('測定誤差を軽減しない場合の4つのベル基底のエネルギー値の相対誤差 : {}'.format(Err_rel_orig))
np.set_printoptions(precision=3)

print('測定誤差を軽減した場合の4つのベル基底のエネルギー値の相対誤差  : {}'.format(Err_rel_new))

📓 測定エラーの軽減前後のエラーのサイズを比較し、選択したバックエンドのエラーマップ情報に関する読み出しエラーの影響について話し合います。

あなたの回答は: