Qiskit pulseを用いて状態操作

[前回](https://qiita.com/tare_/items/e00ab16d3622f233a470)，Qutipでpulse操作のシミュレーションを行ったので，今回はQiskitを用いて実際にパルス操作を行なってみたいと思います．参考にした文献は最後に記しました．

# qiskit pulse

まず，ガウシアンパルスの振幅の設定（[前回](https://qiita.com/tare_/items/e00ab16d3622f233a470)の$\Omega(t)$に対応）を行います．

```python
from qiskit import IBMQ
import numpy as np
from qiskit import pulse                  
from qiskit.circuit import Parameter      
from qiskit.circuit import QuantumCircuit, Gate


#IBMQ.save_account("") # 最初はtokenの入力が必要
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
backend = provider.get_backend('') # 使えるbackendを選んでください

backend_config = backend.configuration()
backend_defaults = backend.defaults()

GHz = 1.0e9 # Gigahertz
MHz = 1.0e6 # Megahertz
us = 1.0e-6 # Microseconds
ns = 1.0e-9 # Nanoseconds

dt = backend_config.dt
print(f"Sampling time: {dt * 1e9} ns") 
# Sampling time: 0.5 ns

granularity = backend_config.timing_constraints['granularity']
print(granularity)
# 8

# value を base_number の倍数に丸める関数
def get_closest_multiple_of(vaule, base_number):
    return int(vaule + base_number/2) - (int(vaule + base_number/2) % base_number)

# num を granularity の倍数に丸める関数
def get_closest_multiple_of_granu(num):
    return get_closest_multiple_of(num, granularity)


# ガウシアンパルスのパラメータ
drive_sigma_sec = 15 * ns  # ガウシアンの幅
drive_duration_sec = 120 * ns  # 全体の時間幅
drive_amp = 0.05 #振幅
```

ここで，granularityは「時間粒度」，つまり操作できる最小の単位時間を表しています．上記の例だと，sampling timeが$0.5\mathrm{ns}$であり，これが粒度の単位になっています．今回 granularity$=8$なので，時間粒度は$4\mathrm{ns}$となります．全ての操作は$4\mathrm{ns}$の倍数の時間で操作しなければなりません．よって，

```python
# ベーススケジュールを作成
freq = Parameter('freq')
with pulse.build(backend=backend, default_alignment='sequential', name='Frequency sweep') as sweep_sched:
    drive_duration = get_closest_multiple_of_granu(pulse.seconds_to_samples(drive_duration_sec))
    drive_sigma = pulse.seconds_to_samples(drive_sigma_sec)
    drive_chan = pulse.drive_channel(qubit)
    pulse.set_frequency(freq, drive_chan)
    # Drive pulse samples
    pulse.play(pulse.Gaussian(duration=drive_duration,
                              sigma=drive_sigma,
                              amp=drive_amp,
                              name='freq_sweep_excitation_pulse'), drive_chan)
```
のようにガウシアンパルスを設定すると，以下のような，$4$nsの倍数の時間だけ作用するパルスを作ることができます．
![granu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/7c789dec-4c40-f5a2-54ad-c2f834ad82b0.png)
ちなみに，``seconds_to_samples``では，秒で与えた時間を，サンプル数に変換しています．例えば，全体の時間幅は$120\mathrm{ns}$なので，サンプル数は$240$となっています（sampling timeが$0.5\mathrm{ns}$）[^1]．

さて，次にガウシアンパルスの周波数（[前回](https://qiita.com/tare_/items/e00ab16d3622f233a470)の$\omega_d$に対応）の設定を行います．ここでは，量子ビットのエネルギー差に対応する周波数$\omega_q$のおおよその値が与えられており，パルスの周波数$\omega_d$を量子ビットの周波数$\omega_q$周辺で変化させていくことにより，測定結果がどのように変わるかを見ていきます．

```python

# 周波数を探索する量子ビット
qubit = 0

# デフォルトの推定周波数を中心値としてスイープを行う
center_frequency_Hz = backend_defaults.qubit_freq_est[qubit] 
print(f"量子ビット {qubit} の推定周波数は {center_frequency_Hz / GHz} GHz")
#量子ビット 0 の推定周波数は 4.908828173067439 GHz

# 推定周波数を中心に100MHzの間でスイープする
frequency_span_Hz = 100 * MHz
frequency_step_Hz = 1 * MHz
frequency_min = center_frequency_Hz - frequency_span_Hz / 2
frequency_max = center_frequency_Hz + frequency_span_Hz / 2

# 周波数の配列をGHz単位で作成
frequencies_GHz = np.arange(frequency_min / GHz,
                            frequency_max / GHz,
                            frequency_step_Hz / GHz)
print(f"スイープは {frequency_min / GHz} GHz から {frequency_max / GHz} GHz まで、{frequency_step_Hz / MHz} MHz のステップで行われる")
#スイープは 4.858828173067439 GHz から 4.958828173067439 GHz まで、1.0 MHz のステップで行われる
```
これで準備は全て終わりました．ガウシアンパルスのゲートを作り，それを作用させていきます．

```python
sweep_gate = Gate("sweep", 1, [freq]) #"1"は1量子ビットゲートという意味

qc_sweep = QuantumCircuit(1, 1)
qc_sweep.append(sweep_gate, [0])
qc_sweep.measure(0, 0)
qc_sweep.add_calibration(sweep_gate, (0,), sweep_sched, [freq])

frequencies_Hz = frequencies_GHz*GHz
exp_sweep_circs = [qc_sweep.assign_parameters({freq: f}, inplace=False) for f in frequencies_Hz]
```
このスケジュールを図示すると，次のようになります．
```python
from qiskit import schedule

sweep_schedule = schedule(exp_sweep_circs[0], backend)
sweep_schedule.draw(backend=backend)
```
![schedule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/16b83ea8-b50a-6c93-9777-7e38b0b063ac.png)

これを実行してみます．

```python
import matplotlib.pyplot as plt

num_shots_per_frequency = 1024

job = backend.run(exp_sweep_circs,
                  meas_level=1,
                  meas_return='avg',
                  shots=num_shots_per_frequency)

# 結果
frequency_sweep_results = job.result(timeout=120) 

# データを見やすくするスケーリング
scale_factor = 1e-7

sweep_values = []
for i in range(len(frequency_sweep_results.results)):
    #　i番目の周波数での実験結果
    res = frequency_sweep_results.get_memory(i)*scale_factor
    sweep_values.append(res[qubit])

plt.scatter(frequencies_GHz, np.real(sweep_values), marker='x', color='black') # plot real part of sweep values
plt.xlim([min(frequencies_GHz), max(frequencies_GHz)])
plt.xlabel("Frequency [GHz]")
plt.ylabel("Measured signal [a.u.]")
plt.show()
```
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/4630f685-161c-7d0a-df0c-8360fcd95d94.png)

[前回](https://qiita.com/tare_/items/e00ab16d3622f233a470)のシミュレーション結果と似たような結果が得られました．

# 参考文献 
- [QiskitのOpenPulseでパルス制御【共鳴周波数の測定】](https://sjsy.hatenablog.com/entry/20200804/1596547368)
- [Qiskit Pulseによる量子ビットの較正](https://qiskit.org/textbook/ja/ch-quantum-hardware/calibrating-qubits-pulse.html#calib)

[^1]:振幅は``drive_amp=0.05``としましたが，これの単位が不明なため，シミュレーション結果とどの程度一致するのかどうかは分かりません．