IBMQのクラウドシミュレーターを使ってみる;実行時間、バッチ処理など

# IBMQのクラウドシミュレーター

あまり知られていませんが、IBMQクラウド上には実機だけではなくシミュレーターも用意されています。
例えば `ibmq_qasm_simulator` は、32量子ビットまでのシミュレーションができます。[^1]
もちろん無料です。
他にも色々なシミュレーターがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/38e48376-ab23-330a-83ca-c4dfc7b492e9.png)

今回は、一番シンプルな `ibmq_qasm_simulator`を使ってみます。

# ibmq_qasm_simulator

## バッチ化
`ibmq_qasm_simulator`も、実機同様に複数の回路をまとめて1つのjobとして投げることができます。
バッチ化といいます。
これは重要です。

そもそもクラウドアクセスでは、jobをひとつ投げて結果を回収する遅延だけで数秒を要します。
例えば`ibmq_qasm_simulator`では早くて10秒ぐらい、類似のaws上のstatevector simulator(SV1)でも早くて数秒ぐらいです。
`ibmq_qasm_simulator`は無料なこともあって、遅延（キュー待ちも含んでいる？？）はまちまちで、
10秒で返ることもあれば数十秒かかることもあります。
なので、回路をできるだけ1つのjobに集積することが重要です。

`ibmq_qasm_simulator`の場合、1つのjobに300回路まで入れることができます。

https://quantum-computing.ibm.com/lab/docs/iql/manage/simulator/
`The following simulation methods are currently available, and support a maximum of 300 circuits and 8192 shots per job.`

これに違反するとエラーが返ります。
`'Unable to retrieve result for job xxxxxxxxxxxx. Job has failed: The number of experiments in the Qobj (400) is higher than the number of experiments supported by the device (300). Error code: 1102.'
`
また、shot数は8192までだそうです。


## 実際に使ってみる

```python
from qiskit import IBMQ, execute, Aer
# IBMQ.save_account("YourToken")
provider = IBMQ.load_account()


from qiskit import QuantumCircuit
n_qubits = 10
qc = QuantumCircuit(n_qubits) 
for i in range(n_qubits):
    qc.h(i)
    qc.cnot(i,(i+1)%n_qubits)
    qc.x(i)
    qc.cnot(i,(i+2)%n_qubits)
qc.measure_all()

qc.draw('mpl')

qc_list = []
for _ in range(100):
    qc_list.append(qc)
```

100個の回路を1つのjob(回路のリスト)にまとめました。

まず、1個の回路をローカルシミュレータ（PC)上で実行した場合は

```python
# execute on qasm simulator
%time results = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1024).result()
```

`Wall time: 49.6 ms`

続いて100個の回路を1つのjobとして、ローカルシミュレータ（PC)上で実行した場合は

```python
# execute on qasm simulator
%time results = execute(qc_list, backend=Aer.get_backend('qasm_simulator'), shots=1024).result()
```

`Wall time: 4.46 s`

このようになります。
ローカルシミュレータ上では回路を並列に処理できず、逐次的に処理するので**100倍**の時間を要します。

続いて、クラウド上のシミュレータである`ibmq_qasm_simulator`の場合です。
1個の回路の場合は、

```python
%time results = execute(qc, backend=provider.get_backend('ibmq_qasm_simulator'), shots=100).result()
```

`Wall time: 33.7 s`

このようになります。実行時間は、10秒から1分ぐらいまで結構ゆらぎが出ます。無料なので仕方ないですね。

続いて100個の回路を1つのjobとした場合は、

```python
%time results = execute(qc_list, backend=provider.get_backend('ibmq_qasm_simulator'), shots=100).result()
```
`Wall time: 12.9 s`

このようになります。実行時間は**100倍にはなっていない**ことがわかります。
シミュレータ上では回路が並列に処理されていると考えられます。[^2]

なお、IBMQのマイページ上では以下のように見えます。
num. of circuits が100となっています。バッチ化しない場合はここが1です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/facc8fb6-528e-4c03-3ec2-38841a14772b.png)


## バッチ化がどんなときに重要か

バッチ化が効果を発揮する時は、**回路の実行数が多いとき**です。
例えば、

1. データセット(=入力データ数)が大きい量子機械学習

2. パラメータ数が多い変分量子回路（をパラメータシフトルールによる勾配法で最適化するとき）。

3. オブザーバブルに同時測定できないパウリ項が多数含まれるとき。VQEなど。

の時だと考えます。

1.の場合、データ毎に異なる回路を立てて実行する必要があります。100個の回路(=100個の入力データに対応)をバッチ化すれば、それぞれ実行させる時の100倍高速となります。

2.の場合、パラメータシフトルールによる勾配計算はパラメータ数を$p$とするとき$2p$個の異なる回路実行を要しますから、これら$2p$個をバッチ化すれば、1度の回路実行と同じ時間で処理されます。$2p$倍高速といえます。[^3]

3.の場合、同時測定できないパウリ項が$k$個あるとすると、$k$回だけ回路を走らせる必要がありますので、これをバッチ化すれば1度の回路実行と同じ時間で処理されます。$k$倍高速といえます。[^4]

このような状況(1,2,3)は、NISQアルゴリズムでは頻出しますので、抑えておきましょう。

# まとめ

回路実行数が多い時は、積極的にバッチ化していきましょう。

[^1]: お手元のPCによるローカルシミュレータですと、たぶん25量子ビットを超えたあたりからRAMが食いつぶされて実行できなくなると思います。30量子ビットとなると、おそらく絶望する領域です。テンソルネットワークシミュレーションは置いとくとして。

[^2]: 本当にクラウドの向こうで並列計算されているのかはわかりませんが、少なくともインターネット経由で100回jobを投げているわけではない、という意味です。

[^3]: pennylaneでaws上のSV1 simulator を叩く場合、この並列化が自動的にサポートされます。ただし条件1や3への対処もちゃんと考えないと、依然として膨大な回路実行数になることがありますので注意しましょう。

[^4]: ただしシミュレータの場合は、本来は「同時測定不能」という量子力学による制約は無関係なので、そもそも1度の回路実行で全パウリ項を評価することは可能なはずです。それにも関わらず、（おそらくSDK側が実機と挙動を揃えたいために）実機同様にパウリ項を分割してからjobを投げてしまう仕様が主流なようです。なので、シミュレータでもバッチ化を意識して行う必要があります。