1量子ビットの場合#
Show code cell content
from qiskit_ibm_provider import IBMProvider
from qiskit.providers.fake_provider import Fake7QPulseV1
from qiskit_dynamics import DynamicsBackend
from qiskit_ibm_runtime.fake_provider import FakeCasablanca
from qiskit.circuit import QuantumCircuit, Gate
from qiskit.pulse import builder, DriveChannel
from qiskit.transpiler import InstructionProperties
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from qiskit import pulse,schedule ,transpile
from qiskit.circuit import Parameter
from qiskit.circuit import QuantumCircuit, Gate
from qiskit import schedule
from qiskit.pulse import Schedule,Play,ControlChannel,Acquire,AcquireChannel,MemorySlot,DriveChannel,MeasureChannel
from qiskit_ibm_provider.job import job_monitor
import warnings
warnings.simplefilter('ignore')
第一章では\(X,Y,Z,H,CNOT\)ゲートに関して説明をしました.実は,これらはシュレディンガー方程式:
を解いて得られた量子状態の時間発展の特殊な例にすぎないのです.この章ではどのようなハミルトニアンになるか,どのようにしてシュレディンガー方程式を解くかなどに注目をして,1量子ビットのゲート操作に関して解説をしていきます.
注意
このセクションでは前のセクションで説明した周波数固定型トランズモン量子ビットと呼ばれる量子ビットを扱います.
1量子ビット(概略)#
何ができるようになりたいか?#
第一章で説明をしたようにすべての1量子ビットに対する量子ゲートはすべてBloch球上の回転に対応しています.
つまり,\(X,Y,Z\)方向の任意の角度の回転を表す実装ができればすべての1量子ビットに対する量子ゲートを実装することができるでしょう.
ただ,\(X,Y,Z\)3つの軸をすべて実装する必要はなく,実は\(X,Z\)軸の任意の角度の回転だけでBloch球のすべての状態を表現することができるのです.以下の\(U_3\)ゲートは,\(\theta,\phi,\lambda\)の3つの自由度からなるゲートです.この\(U_3\)ゲートによってBloch球の任意の位置を表現できます.
以上の式を見て分かるように,Bloch球の任意の位置を記述するためには,
\(X_{\frac{\pi}{2}}\)
\(Z_{任意}\) の二つのゲートを実装すればすべてのゲート操作を行うことができるのです.驚くことに\(Y\)ゲートの実装や\(\pi/2\)以外の\(X\)ゲートの回転ゲートの実装は不要になっています. つまり最終的な目標は
最終目標
任意の\(Z\)の回転ゲートと,\(X_{\frac{\pi}{2}}\) を実装すること
です.
実装#
以下の構造のように,量子ビットが\(V(t)\)に繋がっている場合を考えましょう:

トランズモン量子ビットが\(V(t)\)に繋がっている回路図#
この全体のハミルトニアンは,\(\Omega\)を定数,\(Q\)を量子ビット内の電荷だとして,
と表すことができます[Krantz et al., 2019].このハミルトニアンを下にV(t)によってどのようにして量子状態が変化するかに着目をします.よって,まず第一の目標としては任意の\(V(t)\)に応じて 量子ビットの初期状態に対してどのように時間発展をするかを計算をすること です.そこで,前のセクションで取り扱ったように,
と量子化して代入すると,
となります.ここで,前のセクションと同様にして2準位のみに固定すると,\(\hat{a} \to |0\rangle\langle 1|,\hat{a}^\dagger \to|1\rangle\langle 0| \)へ変換することができ,新たに\(\frac{C_d}{C_d + C} Q'= \Omega\)とすると,
となります.上式の導出の過程は以下の通りです.
\(H_0\)の項は前のセクションの導出を参考にしてください.以下は\(H_d\)の導出です.
となるので,それぞれを\(-i (a-a^{\dagger})\)に代入すると,
になります.よって導出ができました.
ここからの流れは単純です.このハミルトニアンを使って,シュレディンガー方程式を作ります:
そして,\(|\psi(t)\rangle\)が\(V(t)\)によってどのように変化をするかを解析すれば良いです.この\(|\psi(t)\rangle\)は例えば,\(|0\rangle\)と\(|1\rangle\)が行き来するかもしれませんし,\(|0\rangle\)にとどまっている可能性もあります.どちらにせよ,これからこの\(|\psi(t)\rangle\)を求めていきます.
しかし,このままでは解析が難しいため,\(Z\)軸中心で回転する座標系に移って考察をしていくことにします.
以上のことと,これから行う動作をまとめると以下の通りです.

回転系に移る.#
回転系に移るとは,観測する人(カメラ)がある軸を中心に回転をしながら観察するということです.例えば地球に住んでいる我々は地球の自転の軸を中心に回転をしながらすべてを観察しているため回転系に入っていると言えます.
回転系に入ることの恩恵は以下の例を考えると分かりやすいです.例では\(Z\)軸を中心に量子状態がぐるぐる回ってしまっている場合を思い浮かべましょう.くどいようですが,回転系ではない実験室系では量子状態がBloch球上で\(Z\)軸ををぐるぐる回っており,どのようにして状態が変化しているか分かりにくいですが,軸と一緒に回転する回転系に入って観察すると,以下の赤い軌跡のように綺麗に状態が変化していることが分かります.
以下ではこの回転系に入って議論するために,回転演算子というものを定義しておきます:
回転演算子
一般に角振動数\(\omega\)で回転座標系に入るために作用させる演算子は
で与えられます.(\(\sigma_i\)の\(i\)にはそれぞれ\(x,y,z\)などの軸が入ります.)
今回の場合は\(\omega_q\)の角振動数で,\(Z\)軸中心に回転する座標系に移ってみましょう. よって,今回の場合の回転演算子は
で与えられます.ある状態\(|\psi(t)\rangle\)があった時にこれが回転系に移ると,
と変換されるとします.ではこの状態が従うようなシュレディンガー方程式はどのような方程式になり,シュレディンガー方程式から導出されるハミルトニアン(\(\tilde{H}\))はどのような形になるでしょうか.
\(|\psi_{\text{rf}}(t)\rangle\)が従うシュレディンガー方程式は以下のようになります:
つまり,以上の式で定義した\(\tilde{H}\)が回転系でのハミルトニアンになります.このハミルトニアンを今考えている系\(\eqref{hamiltonian}\)で求めてみましょう!.
回転演算子の時間発展,共役は式\(\eqref{test}\)より次で与えれれます:
これらを\(\tilde{H}\)に代入すると,
となります.導出は以下の通りです.
ここで,実際に打つパルスを
という分布のパルスにしましょう!実際に図示をすると以下のような図になり,\(\sigma\)の値や\(V_0\)や\(\omega_d\)の値を動かすことで,実際のパルスの形を決めることができます.例えば\(\sigma\)は以下の図のようにパルスの幅を決定します.
Show code cell source
import plotly.graph_objects as go
import numpy as np
import plotly
from IPython.display import display, HTML
plotly.offline.init_notebook_mode(connected=False)
# Create figure
fig = go.Figure()
# Add traces, one for each slider step
for sigma in np.arange(0.1,5,0.1):
fig.add_trace(
go.Scatter(
visible=False,
line=dict(color="#00CED1", width=3),
name="v= " + str(sigma),
x=np.arange(0.1, 10, 0.01),
y=1*np.exp( -(np.arange(0.1, 10, 0.01)-5)**2/(2*sigma**2) )*np.sin(10*np.arange(0.1, 10, 0.01))
))
# Make 10th trace visible
fig.data[10].visible = True
# Create and add slider
steps = []
for i in range(len(fig.data)):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": "sigma=" + str(i)}], # layout attribute
)
step["args"][0]["visible"][i] = True # Toggle i'th trace to "visible"
steps.append(step)
sliders = [dict(
active=10,
currentvalue={"prefix": " "},
pad={"t": 50},
steps=steps
)]
fig.update_layout(
sliders=sliders
)
fig.show()
この\(V(t)\)を\(\tilde{H}\)に代入してみましょう:
ここで,三角関数を含む部分のみに注目をして整理すると,以下の式が導出できます.以下では回転波近似という近似を利用しています.
導出と近似方法(回転波近似)は以下の通りです.
上の式では,\(\omega_q\)が\(\omega_d\)のに十分に近く,\(\omega_d + \omega_q\)が\(\omega_d - \omega_q\)に比べて十分に大きく,例えば\(\sin((\omega_d + \omega_q)t) + \sin((\omega_d - \omega_q)t)\)の場合,後で十分に長い時間で積分を行うため,\(\omega_d + \omega_q\)の項が十分に早く振動して平均化して\(0\)になることを利用しています.(これを回転波近似と言います.) よって,他の項も同様にして上式のようになります.
ここで,実際に入射するパルスの周波数\(\omega_d\)をトランズモン量子ビットの周波数\(\omega_q\)に一致させてみましょう.すると,\(\tilde{H}\)は簡単な式になります:
このハミルトニアンの場合の時間発展を求めてみましょう!
改めてシュレディンガー方程式は
でした.ここで,\(t=0\)の時の状態は\(|\psi_{\text{rf}}(0)\rangle\)なので,
となります.これに先ほど求めた\(\tilde{H}\)を代入してガウス積分より積分をすると次のようになります:ただし,\(t\)は\(\sigma,t_0\)よりも十分に大きく,\(t_0\)は\(\sigma\)より十分に大きいものとします.
なおガウス積分は次の通りです.:
\(\theta = - V_0 \Omega\sigma \sqrt{2\pi}, \boldsymbol{n} = (\cos(\phi),\sin(\phi)), \boldsymbol{\sigma} = (\sigma_x, \sigma_y)\)とすると,
となることが分かります.これは以下の図のように,ブロッホ球上での\(XY\)平面状で\(\phi\)だけ傾けた向きに\(\theta\)だけ回転することに等しく,回転が実際に入射するパルスの大きさ\(V_0\)などに比例することが分かります.
また,\(t_0,\sigma\)よりも十分に長い\(t\)では上式のように\(|\psi_{\text{rf}}(t)\rangle\)は\(t\)に依存しなくなります.これはパルスを送りブロッホ球上で回転しきることに等しいことで説明がつきます.
実際の実験室では\(V_0\)や\(\sigma\)や\(\phi\)を調整することによって,目的の演算を行うことができるのでます.まとめると回転系での量子状態の時間発展は以下で表されています.
回転系での時間発展
初期状態\(|\psi_{\text{rf}}(0)\rangle\)に対しての時間発展は
で与えられます.
元の系に戻る#
上の場合は一つのパルスを送りましたが,他に続けてパルスを送り続けた場合は
となります.先ほど説明したようにそれぞれの\(\theta,\boldsymbol{n}\)を調整することによってブロッホ球を回転させることができます.しかし最後に一つ忘れていることがあります.それはまだ回転系に入っておりもとの系に戻る必要があるということです.例えばパルスを一つ打ち込み\(X\)軸中心に\(-\pi\)だけ回転させた時,\(t=0\)での状態を\(|0\rangle\)とすると,
となりますが,実際の系(実験室系)では,以下のアニメーションのように回転しながら\(X\)軸方向の回転をしています.一方これまで議論してきた回転系では\(Z\)軸を中心に回転する効果がなくBloch球上の状態が変化していることが分かります.
実験室系では\(Z\)方向の回転によって最終的に結果がごちゃごちゃになってしまうのではないかと不安に思う方もいるでしょう.しかし,この回転は問題ないのです.(だからこそ最初に宣言したように私は回転系に入って議論を続けました.)
第一章で説明したように,量子状態の「測定」は\(Z\)軸上の射影測定のみを考えます.つまり,実験室系で\(X,Y\)平面上で回転をしようがしまいが,Z軸への射影測定の結果は変化しないため,回転系上で議論をしても問題ないということです.
以上のことは数式上では次のように説明できます.回転系ではパルスを打つと,回転系では,
と表されます.元の実験室系に戻ると,\(|\psi_{\text{rf}}(t)\rangle = \hat{U}^{\dagger}_{\text{rf}}|\psi(t)\rangle\)であっため,
となり,最後に回転演算子を作用させたものが実験室系での状態です.すなわち,以下のアニメーションのように,回転系で演算を行い,最後に回転をさせて実験室系に戻ると,ただ単に同じ\(\theta\)上で回転をするだけになり,これは測定結果を変化させることがないということです.
以上の話をより一般的に書くと,
となりますが,実際に測定に関与する状態は\(\hat{U}^{\dagger}_{\text{rf}}\)は関係なく,
であるのです.
回転系に入るという動作を行うことによって綺麗にそれぞれのゲート操作を簡潔に説明することができました.よって,以下にまとめることができます.
回転系に入ることの意味
全体にかかってしまう\(Z\)軸中心の回転の影響を除去できて解析が簡単になる.
私が勘違いしてたこと:回転系の効果が見える?
第一章では,\(Z\)測定の他に\(X\)測定などの概念を紹介しました.すると.たしかに回転系に入った場合と実験室系では\(Z\)測定の結果は変化しないが,ある時間だけ遅らせて\(H\)測定をして,量子状態がぐるぐる回る様子も観察できないか?とこれを初めて勉強した時,思っていました.
結論から言うと,その様子を観察することはできません.というのも,Hゲートは回転系で定義されたゲートであり,実験室系で定義されていないからです.すなわち,\(H\)ゲートを作用させてH測定をしたいと思っても,実験室系では\(H\)ゲートさえもぐるぐる回転してしまうため,実験室系で定義された\(H\)ゲートは実現されないのです.
\(X_{\frac{\pi}{2}}\)ゲート#
最終目標の一つである\(X_{\frac{\pi}{2}}\)ゲートの実現方法を説明していきます,\(X_{\frac{\pi}{2}}\)とは\(X\)軸を中心に\(\pi/2\)だけ回転させるゲート操作です.
ということです.回転系の量子の状態は式(\(\eqref{Rotation_hamiltonian}\))より,
でした.つまり,\(\phi=0\)となるようなパルスを打ち込み,\(\theta\)の結果がちょうど\(\pi/2\)だけ回転させているようなパルスの振幅を見つければ良いということになります.このようなパルスを調整して正確なゲートを行える値を探し,それぞれの量子ビットに対してその正確な値を保存しておくことで,\(X_{\frac{\pi}{2}}\)ゲートを実現できます.この流れをキャリブレーションと言います.
キャリブレーション#
それでは量子ビットに入射するパルスの振幅\(V_0\)の値を変えて実験することでキャリブレーションを行いましょう!
本来であれば実際の量子コンピュータを使用しますが,後で説明する複数量子ビットで分かりやすい説明をするため,Qiskit Dynamicsというモジュールを用いて数値計算を行います.
バックエンドを定義する#
まずはどのようなバックエンドを使用するかを定義します.
# バックエンドのモデル
ibm_backend = Fake7QPulseV1()
# 使用するソルバー
qubits_to_model = [0,1,2,3,4]
solver_options = {
"method": "jax_odeint",
"atol": 1e-6,
"rtol": 1e-8,
"hmax": ibm_backend.configuration().dt}
# 数値計算を行うバックエンド
backend = DynamicsBackend.from_backend(
backend=ibm_backend,
subsystem_list=qubits_to_model,
solver_options=solver_options,
)
バックエンドからデフォルトの周波数を取り出す.#
パルスを送信するためには各量子ビットの周波数である\(\omega_q\)の値が必要でした.1番目に割り当てられている量子ビット(量子ビット0)の周波数を取り出すと以下の通りになります.
backend_defaults = ibm_backend.defaults()
# 単位の定義
GHz = 1.0e9
MHz = 1.0e6
us = 1.0e-6
ns = 1.0e-9
# 以下の量子ビットについて量子ビット周波数を探索する
qubit1 = 0
# 量子ビット0に関しての周波数を取り出す.
center_frequency1_Hz = backend_defaults.qubit_freq_est[qubit1]
print(f"量子ビット{qubit1}は {center_frequency1_Hz / GHz}GHz の周波数です.")
量子ビット0は 5.260483791030155GHz の周波数です.
パルスの形を決定する.#
パルスには\(V_0,\sigma,t_0,\omega_d,\phi\)の値,そしてパルスを出力している時間\(t_f\)が必要でした:
(図を挿入)
また,\(t_0\)の値は理想的に\(t_0=t_f/2\)になっているとすると,最終的にはパルスには\(V_0,\sigma,t_f,\omega_d,\phi\)の値が必要になります.それぞれ定義していきましょう.
今回は\(X\)軸を中心に回転させてキャリブレーションを行うため,\(V_0\)は\(0から0.3\)まで変化させる値とします:
V_0_min = 0
V_0_max = 0.3
num_x90_points = 200
V_0 = np.linspace(V_0_min, V_0_max, num_x90_points)
\(\sigma\)は0.015usであるとし,\(t_f\)は\(\sigma\)の\(8\)倍にすることで,ガウシアンが端で切れてしまう効果を弱くします.また\(\phi\)も設定します:
sigma = 0.015 * us
t_f = sigma * 8
phi = 0
パルスを設定する.#
(x90_calibration)という名前のパルスを量子ビット0に登録します.(drive_duration)と,(drive_sigma)にさきほど設定した\(t_f,\sigma\)を設定し,このパルスの周波数に当たる\(\omega_d\)を量子ビット0の周波数である(center_frequency1_Hz)を設定します.
# V_0をパラメータとする.
drive_amp = Parameter('drive_amp')
# パルスを設定する.
with pulse.build(backend=backend, default_alignment='sequential', name='x90_calibration') as x90_calibration_sched:
# 量子ビット0を登録する.
drive_chan = pulse.drive_channel(qubit1)
# t_f
drive_duration = pulse.seconds_to_samples(t_f)
# sigma
drive_sigma = pulse.seconds_to_samples(sigma)
# 周波数
pulse.set_frequency(center_frequency1_Hz, drive_chan)
# 位相
pulse.set_phase(phi,drive_chan)
# パルスを打つ
pulse.play(pulse.Gaussian(duration=drive_duration,
amp=drive_amp,
sigma=drive_sigma,
name='x90_calibration'), drive_chan)
量子回路を作成する.#
今回のキャリブレーションでは,(1)初期状態を\(|0\rangle\)として(2)先ほど定義した(x90_calibration)のパルスを当てて(3)測定します.
# パルスに対応するカスタムゲートを作成する.
x90_calibration = Gate("x90_calibration", 1, [drive_amp])
# 量子回路を作成 今回は1つの量子ビットが必要
qc_x90_calibration = QuantumCircuit(1)
# 初期状態は|0>であるため(2)を行う
qc_x90_calibration.append(x90_calibration, [0])
# (3)を行う
qc_x90_calibration.measure_all()
######################################
# カスタムゲートにパルスを対応させる.
qc_x90_calibration.add_calibration(x90_calibration, (0,), x90_calibration_sched, [drive_amp])
# 振幅V0を設定した範囲でキャリブレーションを行う
exp_x90_calibration_circs = [qc_x90_calibration.assign_parameters({drive_amp: a}, inplace=False) for a in V_0]
# バックエンドの設定で必要な操作
backend.target.add_instruction(
x90_calibration,
{(0,): InstructionProperties(calibration=x90_calibration_sched)},
)
量子回路は以下の通りになります.
qc_x90_calibration.draw('mpl')

パルスは以下のようになり,量子ビット0に(x90_calibration)というパルスを当てて,桃色の測定用のパルスを当てていることが分かります.
exp_x90_schedule = schedule(exp_x90_calibration_circs[-1], ibm_backend)
exp_x90_schedule.draw()

シミュレーションを行う.#
各振幅で500回測定を行いその平均値を返すとします.(以下のコードのnum_shots_per_pointが各振幅での測定回数でmeas_return='avg’で平均値を返すようにしています.)
import warnings
warnings.filterwarnings('ignore')
num_shots_per_point = 500
job = backend.run(exp_x90_calibration_circs,
meas_level=2,
meas_return='avg',
shots=num_shots_per_point)
job_monitor(job)
Show code cell output
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[11], line 4
2 warnings.filterwarnings('ignore')
3 num_shots_per_point = 500
----> 4 job = backend.run(exp_x90_calibration_circs,
5 meas_level=2,
6 meas_return='avg',
7 shots=num_shots_per_point)
8 job_monitor(job)
File ~\Desktop\qualsimu-textbook-new\.venv2\lib\site-packages\qiskit_dynamics\backend\dynamics_backend.py:452, in DynamicsBackend.run(self, run_input, validate, **options)
439 job_id = str(uuid.uuid4())
440 dynamics_job = DynamicsJob(
441 backend=backend,
442 job_id=job_id,
(...)
450 },
451 )
--> 452 dynamics_job.submit()
454 return dynamics_job
File ~\Desktop\qualsimu-textbook-new\.venv2\lib\site-packages\qiskit_dynamics\backend\dynamics_job.py:54, in DynamicsJob.submit(self)
52 if self._result is not None:
53 raise JobError("Dynamics job has already been submitted.")
---> 54 self._result = self._fn(job_id=self.job_id(), **self._fn_kwargs)
55 self._time_per_step["COMPLETED"] = datetime.now()
File ~\Desktop\qualsimu-textbook-new\.venv2\lib\site-packages\qiskit_dynamics\backend\dynamics_backend.py:472, in DynamicsBackend._run(self, job_id, t_span, schedules, measurement_subsystems_list, memory_slot_indices_list, num_memory_slots_list)
469 if y0 == "ground_state":
470 y0 = Statevector(self._dressed_states[:, 0])
--> 472 solver_results = self.options.solver.solve(
473 t_span=t_span, y0=y0, signals=schedules, **self.options.solver_options
474 )
476 # compute results for each experiment
477 experiment_names = [schedule.name for schedule in schedules]
File ~\Desktop\qualsimu-textbook-new\.venv2\lib\site-packages\qiskit_dynamics\solvers\solver_classes.py:532, in Solver.solve(self, t_span, y0, signals, convert_results, **kwargs)
524 if self.model.array_library not in ["numpy", "jax", "jax_sparse"]:
525 warn(
526 "Attempting to internally JAX-compile simulation of schedules, with "
527 'Solver.model.array_library not in ["numpy", "jax", "jax_sparse"]. If an error '
528 "is not raised, explicitly set array_library at Solver instantation to one of "
529 "these options to remove this warning."
530 )
--> 532 all_results = self._solve_schedule_list_jax(
533 t_span_list=t_span_list,
534 y0_list=y0_list,
535 schedule_list=signals_list,
536 convert_results=convert_results,
537 **kwargs,
538 )
539 else:
540 all_results = self._solve_list(
541 t_span_list=t_span_list,
542 y0_list=y0_list,
(...)
545 **kwargs,
546 )
File ~\Desktop\qualsimu-textbook-new\.venv2\lib\site-packages\qiskit_dynamics\solvers\solver_classes.py:662, in Solver._solve_schedule_list_jax(self, t_span_list, y0_list, schedule_list, convert_results, **kwargs)
659 for idx, sig in enumerate(all_signals):
660 all_samples[idx, 0 : len(sig.samples)] = np.array(sig.samples)
--> 662 results_t, results_y = jit_sim_function(
663 unp.asarray(t_span),
664 unp.asarray(y0),
665 unp.asarray(all_samples),
666 unp.asarray(y0_input),
667 y0_cls,
668 )
669 results = OdeResult(t=results_t, y=results_y)
671 if y0_cls is not None and convert_results:
KeyboardInterrupt:
結果を表示してキャリブレーションを完了する.#
シミュレーションが終わったので結果を表示します.まずはそれぞれ\(0,1\)を得た確率を表示します.
Show code cell source
import plotly.graph_objects as go
import plotly.io as pio
import plotly
from IPython.display import display, HTML
result = job.result()
zero_list = []
one_list = []
for i in range(200):
zero_list.append(result.get_memory(i).count('0')/500)
one_list.append(result.get_memory(i).count('1')/500)
layout = go.Layout(
font_size=15, # グラフ全体のフォントサイズ
)
fig = go.Figure(layout=layout)
plot =[
fig.add_trace(go.Scatter(x=V_0,
y=zero_list,
mode='lines',
name=r'$|0\rangle$'
)
),
fig.add_trace(go.Scatter(x=V_0,
y=one_list,
mode='lines',
name=r'$|1\rangle$',
)
)
]
fig.update_layout(
xaxis_title=r"振幅$V_0$",
yaxis_title="確率",
font=dict(
family="Meiryo",
size=15
)
)
fig.write_html("../../../animation/quantum_gate_plot1.html",include_mathjax ="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js")
たしかに振幅\(V_0\)によってそれぞれの確率が振動する様子を観察できました!\(0\)の確率を三角関数でフィッティングさせて,\(X_{\pi/2}\)を行えるパルスの振幅を求めましょう.
実際にフィッティングすると\(X_{\pi/2}\)を行えている振幅は
Show code cell source
from scipy import optimize
def approximation_expression(x,a,b):
return 0.5*np.sin(a*x+b)+0.5
popt, pcov = optimize.curve_fit(approximation_expression, V_0, zero_list,p0=[60,0])
x90_amp = np.pi/(2*popt[0])
print(x90_amp)
0.030798154536926158
となっておりフィッティング結果を図示すると分かるようにフィッティングできていることが分かります.
Show code cell source
layout = go.Layout(
font_size=15, # グラフ全体のフォントサイズ
)
plotly.offline.init_notebook_mode(connected=False)
fig = go.Figure(layout=layout)
plot =[
fig.add_trace(go.Scatter(x=V_0,
y=zero_list,
mode='lines',
name=r'$|0\rangle$'
)
),
fig.add_trace(go.Scatter(x=V_0,
y=approximation_expression(V_0,popt[0],popt[1]),
mode='lines',
name='フィッティング',
)
)
]
fig.update_layout(
xaxis_title=r"振幅$V_0$",
yaxis_title="確率",
font=dict(
family="Meiryo",
size=15
)
)
fig.write_html("../../../animation/quantum_gate_plot2.html",include_mathjax ="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js")
\(X_{\pi/2}\)のまとめ#
よって以上のようにキャリブレーションを行い\(X_{\frac{\pi}{2}}\)ゲートを実現できるような振幅を各量子ビットで求めて辞書のように保存しているのです.実際に\(X_{\pi/2}\)ゲートを作用させて測定を行った時を考えます.(Qiskit上では\(S_X\)ゲートと呼ばれており,\(X\)に平方根を取ったよなゲートになります.)
qc = QuantumCircuit(1)
qc.sx(0)
qc.measure_all()
qc.draw('mpl',style="clifford")

これをパルスレベルまで分解すると以下の表が得られます.一段目は\(X_{\pi/2}\)ゲートのパルスで,二段目は測定用のために打っているパルスです.たしかに,\(X_{\pi/2}\)ゲート自体はただ一つのパルスだけで実装されていることが分かります.このパルスの振幅は今キャリブレーションで求めたパルスの振幅になっているのです.
qc_trans = transpile(qc, ibm_backend)
pulse_schedule = schedule(qc_trans, ibm_backend)
pulse_schedule.draw()

\(Z_{\theta}\) ゲート#
\(X_{\frac{\pi}{2}}\)ゲートは比較的簡単に実現ことができました.しかし問題は\(Z\)ゲートです.
の中には\(Z\)軸方向を中心に回転できるような演算は見当たりません.(もし\(\exp\)の中に\(\sigma_z\)が含まれているとすると,\(Z\)軸中心に回転を行うことができるのですが…) そこで提案されており2023年現在実際に使われているのが仮想Zゲートという方法です.
\(X_{\frac{\pi}{2}}\)ゲートの説明をする時に,\(\phi\)は\(\phi=0\)で固定すると話しました.つまり,1つ目のパルスも,2つ目のパルスもそれぞれ打つパルスは\(\phi=0\)で固定をします.例えば以下のようになります:
しかし実際にパルスを生成する際には最初に\(\phi=0\)で決定したらもう変更ができないわけではなく,\(\phi\)の部分を各パルスで変更させることが可能です.つまり,どの向きを\(X\)軸にするかという基準を変えることができます.\(X\)軸の基準が変わるということがまさしく\(Z\)ゲートを操作させることと等価になっているのです.
つまり,\(\phi=\phi'\)で\(\theta\)だけ回転させたゲートを\(X_{\theta}^{(\phi')}\)などと書くことにすると,(以下は行列計算で確かめることができます.)
となっています.(\(Z_{\phi'}\)は最後に\(z\)軸を中心に回転させているだけなので測定結果に影響を与えません.) この例から見て分かるように,\(\phi'\)だけ基準をずらすと,\(-\phi'\)だけ\(z\)軸上に回転することが分かります.
実際の例を見てみましょう.初期状態\(|0\rangle\)に対して\(\phi=0\)で\(X_{\pi/2}\)を作用させ,次に \(\phi=\pi/3\) で \(X_{\pi/4}\) (つまり,\( X_{\pi/4}^{(\pi/3)})\) をさせた時,これは\(X_{\pi/2}\)を作用させて,\(Z_{-\pi/3}\)を作用させ,最後に\(X_{\pi/4}\)を作用させたものに等しくなっています.以下のアニメーションでは,左側が理想的なゲート操作,左側が仮想Zゲートの流れになっています.それぞれ矢印を動かして測定結果が同じになることを確認してみてください:
注意点としては,基準を変更したので基準を変えた場所から今後すべてのゲートの位相を変化させなくてはいけません.つまり,\(X_{\theta_2}^{(\phi')}X_{\theta_1}\)に対して\(Y\)ゲートを作用させたいと思ったら,\(Y^{(0)}\)ゲートではなくて,\(Y^{(\phi')}\)ゲートを作用させる必要があり,以下のようにたしかに,\(X_{\theta_1} \to Z_{-\phi'} \to X_{\theta_2} \to Y\)の順番で作用していることが分かります.
以上から分かるように仮想\(Z\)ゲートは次の性質を持ちます.
仮想\(Z\)ゲートの性質
\(Z\)ゲートはパルス不要であるため,エラー率や実行時間という概念がない.
そのため,1量子ビットを演算する際に実際のそれぞれの量子ビットに必要な情報は\(X_{\frac{\pi}{2}}\)を実現する\(\theta =- V_0 \Omega\sigma \sqrt{2\pi}\)をキャリブレーションした情報のみで良いことになります.
以上で見たように,\(Z_{\theta}\)ゲートは基本的に位相を変化させるだけで追加のパルスを必要としません.実際にパルスを必要としないことを見てみましょう. 以下は\(Z\)軸中心に\(\pi\)だけ回転させる演算を\(|0\rangle\)に作用させて測定する回路です.
qc = QuantumCircuit(1)
qc.rz(np.pi,0)
qc.measure_all()
qc.draw('mpl',style="clifford")

そして,パルスの様子を見てみると,以下のように\(Z_\theta\)ゲートではパルスを加えていないことが分かります.以下でパルスが加えられいるのは測定用のパルスです.
qc_trans = transpile(qc, ibm_backend)
pulse_schedule = schedule(qc_trans, ibm_backend)
pulse_schedule.draw()

他のゲート#
他のゲート操作は\(X_{\frac{\pi}{2}}\)と\(Z\)ゲートの2つを使って操作することができます.最初に説明したように以下の\(U_3\)ゲートを使って一般的にどの軸で回転させたいかを指定させて各量子ゲートを作成をすることができます:
例えば\(Y_{\theta}\)ゲートであれば,
となり,たしかに\(X_{\frac{\pi}{2}},Z_\theta\)のみで実装ができることが分かります.