RigettiのQuntum Cloud Services(QCS)を使ってみた

$$
\def\bra#1{\mathinner{\left\langle{#1}\right|}}
\def\ket#1{\mathinner{\left|{#1}\right\rangle}}
\def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}
$$

本日Rigetti ComputingのQuantum Cloud Servicesのopen betaが発表され、いくつかのアプリケーションが発表されました。[Rigettiの発表記事はこちら](https://medium.com/rigetti/quantum-cloud-services-opens-in-public-beta-31989e15e36e)

RigettiのQPUを一足速く使わせてもらったので使い方をまとめます。
2018年12月に始めて触った時に書いたのでUIが古かったり、Fidelityが低かったりします。

QCSの概要やローカルでの実行方法は[カドラさんの記事](http://kadora.hatenablog.com/entry/2018/12/08/000008
)を参考にどうぞ。

# 特徴
他のIBMなどのプラットフォームと違うのはRigettiのサーバーにSSHでログインして使い、予約した時間独占的に使えることです。
variationalなアルゴリズムを動かしたい場合、量子コンピュータで実行した結果を受けて、古典コンピュータで量子ゲートのパラメータをアップデートを行って再度計算を行い、それを結果が収束するまで繰り返します。
他のプラットフォームでは量子ゲートのパラメータをアップデートした後に量子コンピュータに再度計算を投げるとキューに登録されて計算の順番待ちをするので利用者が多いと計算が終わるのにとても時間がかかります。
QCSでは古典ー量子通信が高速化されている上に、予約した時間内では、古典コンピュータと量子コンピュータ独占的に回し放題なので、ハイブリッドアルゴリズム動かためには理想的な環境です。（逆に言うと古典計算機の利用時間も課金されます・・笑）
Rigettiが開発したActive Resetなどによって、1000サンプルが50msで可能になるようです。
詳しくは[Rigettiのエンジニアの記事](https://medium.com/rigetti/fast-hybrid-programming-with-quantum-cloud-services-7573f48b93b5)をご覧ください。
# サインアップ
まず、以下のようなメールが送られて招待されます。下のCreate Accountからアカウントの作成を行いましょう。
<img width="589" alt="Screen Shot 0030-12-10 at 0.19.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/128898/5915709f-56ce-f77e-45f9-94b7122f2917.png">

SSH keyの登録などを行えば下のような画面が出て来ます。
（※ SSH keyは一度登録したら変更できない仕様に現時点でなっています。変更可能にする予定のようですが、お気をつけください。）
<img width="858" alt="Screen Shot 0030-12-10 at 0.33.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/128898/35731128-bb29-3bb3-e210-2c8897819ed1.png">

# fidelityとお値段
気になるfidelityとお値段は以下の表にあります。
f1QRBは1qubitゲートのランダムベンチマーク、fCZはCZゲートのfidelityです。
実用には厳しい精度だけど、128qubit作るための3次元実装で作ったので、3次元実装としては上出来って感じですね。
お値段は2qubitだと$1.33/min（15分：2,200円）で、16qubitだと$21.67/min（15分：3.7万円）になります。今の所最低利用時間15分です。（遊びに使うには高いですね・・）

<img width="779" alt="Screen Shot 0031-01-08 at 18.50.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/128898/c91c7e61-987d-e29e-4cd6-afe7fa2d308f.png">
<img width="673" alt="Screen Shot 0031-01-08 at 18.50.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/128898/28bba34d-b6dc-0242-8534-b543df3bc134.png">


# 使ってみる
QCSのマイページトップにあるConnect to your QMIでコピーされるコマンドをターミナルで実行すればrigettiのリモート環境にログインされます。
この環境にはvimやtmuxがプレインストールされているので（素晴らしい）.vimrcや.tmux.confなどの設定ファイルを作って自分が慣れているようにカスタマイズしましょう。

### 参考ドキュメント
これからの手順は基本的にIntroduction to Quantum Cloud Servicesという以下のドキュメントに沿っています。
https://www.rigetti.com/qcs/docs/intro-to-qcs

### 環境を立ち上げる
デフォルトで環境が準備されていてとても便利です。
Anacondaで自分が好きな環境を作るのも良いと思います。
`source ~/.virtualenvs/venv/bin/activate`

### latticeを選ぶ
使えるlattice一覧の表示するコマンド。
`qcs lattices`

こんな感じに値段とどのqubitが使えるのか表示されます。
多分fidelityが高いlatticeが上の方に表示されています。

```powershell

(venv) [forest@2717-qmi ~]$ qcs lattices

LATTICE
Name: Aspen-1-2Q-B
  Device: Aspen-1
  Number of qubits: 2
  Qubits: 14,15
  Price (per min.): $1.33


LATTICE
Name: Aspen-1-3Q-B
  Device: Aspen-1
  Number of qubits: 3
  Qubits: 14,15,16
  Price (per min.): $2.50


....（途中省略）


LATTICE
Name: Aspen-1-9Q-B
  Device: Aspen-1
  Number of qubits: 9
  Qubits: 0,1,10,11,13,14,15,16,17
  Price (per min.): $11.75


LATTICE
Name: Aspen-1-16Q-A
  Device: Aspen-1
  Number of qubits: 16
  Qubits: 0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17
  Price (per min.): $21.67
```

また、さらに細かいスペック知りたい場合は、以下のコマンドでf1QRB, fCZのfidelityを調べます。
４時間に１回キャリブレーションを行っているらしいので、今のfidelity見てlatticeを予約しても実際使う時に使うべきqubitは変わるかもしれません。
なので、とりあえずお金が許すのであれいば16qubit全部予約するのが良いと思います。

```python

from pyquil import get_qc
# 以下のコマンドの'Aspen-1-16Q-A'を変更すれば他のlatticeでのfidelityになります。
qc = get_qc('Aspen-1-16Q-A',as_qvm=True)
for device_specs in qc.device.get_specs():
    for qubits_specs in device_specs:
        print(qubits_specs)
```

出力結果は以下の通り。結構ばらつきがあります。

```powershell
_QubitSpecs(id=0, fRO=0.90375, f1QRB=0.965025481504762, T1=7.53e-06, T2=5.08e-06, fActiveReset=0.984)
_QubitSpecs(id=1, fRO=0.955625, f1QRB=0.944122940728585, T1=2e-06, T2=4e-06, fActiveReset=0.995)
_QubitSpecs(id=2, fRO=0.848125, f1QRB=0.942935877541994, T1=7e-06, T2=1.2e-05, fActiveReset=0.992)
_QubitSpecs(id=3, fRO=0.94875, f1QRB=0.963757922569767, T1=1e-05, T2=1.6e-05, fActiveReset=0.995)
_QubitSpecs(id=4, fRO=0.904375, f1QRB=0.984590818888406, T1=5e-06, T2=6e-06, fActiveReset=0.996)
_QubitSpecs(id=5, fRO=0.950625, f1QRB=0.9424855334454, T1=9e-06, T2=1.2e-05, fActiveReset=0.992)
_QubitSpecs(id=6, fRO=0.924375, f1QRB=0.924003066872656, T1=4e-06, T2=8e-06, fActiveReset=0.992)
_QubitSpecs(id=7, fRO=0.944375, f1QRB=0.893583171194206, T1=1.4e-05, T2=1.7e-05, fActiveReset=0.997)
_QubitSpecs(id=10, fRO=0.954375, f1QRB=0.85431882380743, T1=7e-06, T2=1.2e-05, fActiveReset=0.996)
_QubitSpecs(id=11, fRO=0.940625, f1QRB=0.976492554417723, T1=1.2e-05, T2=1.2e-05, fActiveReset=0.996)
_QubitSpecs(id=12, fRO=0.951875, f1QRB=0.867967088662703, T1=5e-06, T2=9e-06, fActiveReset=0.992)
_QubitSpecs(id=13, fRO=0.944375, f1QRB=0.871889064502283, T1=1e-05, T2=1.7e-05, fActiveReset=0.981)
_QubitSpecs(id=14, fRO=0.96125, f1QRB=0.955681814872076, T1=7e-06, T2=1.3e-05, fActiveReset=0.997)
_QubitSpecs(id=15, fRO=0.96125, f1QRB=0.951698360526057, T1=1.4e-05, T2=1.4e-05, fActiveReset=0.994)
_QubitSpecs(id=16, fRO=0.979375, f1QRB=0.990301341211977, T1=1e-05, T2=1.6e-05, fActiveReset=0.994)
_QubitSpecs(id=17, fRO=0.936875, f1QRB=0.966817305227463, T1=1.2e-05, T2=1.5e-05, fActiveReset=0.999)
_QubitQubitSpecs(targets=[0, 1], fBellState=0.935671021676211, fCZ=0.79, fCPHASE=None)
_QubitQubitSpecs(targets=[0, 7], fBellState=0.900354139396714, fCZ=0.9561115391562, fCPHASE=None)
_QubitQubitSpecs(targets=[1, 2], fBellState=0.826616408572586, fCZ=0.82, fCPHASE=None)
_QubitQubitSpecs(targets=[1, 16], fBellState=0.939287423764337, fCZ=0.92, fCPHASE=None)
_QubitQubitSpecs(targets=[2, 3], fBellState=0.945825587987286, fCZ=0.93, fCPHASE=None)
_QubitQubitSpecs(targets=[2, 15], fBellState=0.914257475214935, fCZ=0.88, fCPHASE=None)
_QubitQubitSpecs(targets=[3, 4], fBellState=0.881075015004663, fCZ=0.93, fCPHASE=None)
_QubitQubitSpecs(targets=[4, 5], fBellState=0.897846708121696, fCZ=0.89, fCPHASE=None)
_QubitQubitSpecs(targets=[5, 6], fBellState=0.886703785654333, fCZ=0.94, fCPHASE=None)
_QubitQubitSpecs(targets=[6, 7], fBellState=0.8294293850824, fCZ=0.9, fCPHASE=None)
_QubitQubitSpecs(targets=[10, 11], fBellState=0.94921876852442, fCZ=0.93, fCPHASE=None)
_QubitQubitSpecs(targets=[10, 17], fBellState=0.949734868281531, fCZ=0.985747587763476, fCPHASE=None)
_QubitQubitSpecs(targets=[11, 12], fBellState=0.972331500943258, fCZ=0.92, fCPHASE=None)
_QubitQubitSpecs(targets=[12, 13], fBellState=0.960638395229783, fCZ=0.88, fCPHASE=None)
_QubitQubitSpecs(targets=[13, 14], fBellState=0.967684517573856, fCZ=0.95, fCPHASE=None)
_QubitQubitSpecs(targets=[14, 15], fBellState=0.933652586403049, fCZ=0.95, fCPHASE=None)
_QubitQubitSpecs(targets=[15, 16], fBellState=0.931805549597859, fCZ=0.9, fCPHASE=None)
_QubitQubitSpecs(targets=[16, 17], fBellState=0.838354454449079, fCZ=0.88, fCPHASE=None)
```

### latticeの予約を行う

使いたいlatticeの予約をしましょう。埋まっている場合は一番近い時間を推薦してくれます。
lattice: 上で選んだlatticeを指定する。
start: 予約開始時間。JST（日本標準時）も使える。指定しない場合は直近の時間を推薦してくれる。
duration: 何分間使用するか（最低利用時間15分の模様）
```qcs reserve --lattice Aspen-1-16Q-A --start "12/8/18 2pm JST" --duration 30m```

そしたら、予約確定メールが届く。Happy quantum computing!笑
<img width="566" alt="Screen Shot 0030-12-10 at 1.15.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/128898/b3073bf2-5f7f-d8cd-77d5-96d8e5ab7a1c.png">

### コードを準備する
量子コンピュータが使えたらとりあえず全部エンタングルさせたいですよね？（押し付け）
ということで 1/2の確率で $\ket{0}^{\otimes 16}$ か $\ket{1}^{\otimes 16}$ になるコードを実行させてみましょう。
qubit番号の割り振りは8進数で、8,9などが抜けていることに注意。

```python

import math

from pyquil import Program, get_qc
from pyquil.api import QVM
from pyquil.gates import RX, CNOT, MEASURE

import numpy as np

def hello_qmi(device_name: str = "9q-generic-qvm") -> None:

    # Initialize your Quil program
    program = Program()
    # Declare 16 bits of memory space for the readout results of all three qubits
    readout = program.declare('ro', 'BIT', 16)
    # Apply a pulse to move the qubit's state halfway between the 0 state and the 1 state
    # Apply CNOT to all qubits.
    program += RX(math.pi / 2, 1)

    program += CNOT(1,0) 
    program += CNOT(0,7) 
    program += CNOT(7,6) 
    program += CNOT(6,5) 

    program += CNOT(1,2) 
    program += CNOT(2,3) 
    program += CNOT(3,4) 

    program += CNOT(1,16) 
    program += CNOT(16,17) 
    program += CNOT(17,10) 
    program += CNOT(10,11) 

    program += CNOT(16,15) 
    program += CNOT(15,14) 
    program += CNOT(14,13) 
    program += CNOT(13,12) 

    # Add measurement instructions to measure the qubits and record the result
    # into the respective bit in the readout register
    program += MEASURE(0, readout[0])
    program += MEASURE(1, readout[1])
    program += MEASURE(2, readout[2])
    program += MEASURE(3, readout[3])
    program += MEASURE(4, readout[4])
    program += MEASURE(5, readout[5])
    program += MEASURE(6, readout[6])
    program += MEASURE(7, readout[7])

    program += MEASURE(10, readout[8])
    program += MEASURE(11, readout[9])
    program += MEASURE(12, readout[10])
    program += MEASURE(13, readout[11])
    program += MEASURE(14, readout[12])
    program += MEASURE(15, readout[13])
    program += MEASURE(16, readout[14])
    program += MEASURE(17, readout[15])

    # This tells the program how many times to run the above sequence
    program.wrap_in_numshots_loop(100)

    # Get the quantum computer we want to run our experiment on
    qc = get_qc(device_name, as_qvm=True)

    # Compile the program, specific to which quantum computer we are using
    compiled_program = qc.compile(program)

    # Run the program and get the 100 x 16 array of results
    results = np.array(qc.run(compiled_program))
    
    np.savetxt('entanglement.txt', results, fmt='%.0f')
    # Print the results. We expect to see 16qubit 0's or 1's
    print(f"Your{' virtual' if isinstance(qc.qam, QVM) else ''} quantum "
          f"computer, {device_name}, greets you with:\n", results)

if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        hello_qmi(device_name=sys.argv[1].strip())
    else:
        hello_qmi()
```

以上のコードを実行してみてqvm上で上手く動いたら、as_qvm=Trueをas_qvm=FalseにしてQVMで走らせていた設定をQPUで走らせるように指定しましょう。

### 実験の予約を行う
QCS環境にログインしたディレクトリにあるexec_on_engage.shファイルが予約開始時間に自動で実行されるので、これを編集すれば予約時間に自動実行されるようになります。

### 結果
実行結果は以下の通りです。全部見たい人は[Githubにある結果](https://github.com/TenninYan/qcs_test/blob/master/qpu_entanglement.txt
)を参考にどうぞ。
正解から2つ反転してしまってる結果などがあるものの、全て0や全て1である正解は返してくれなかったです。fidelity90%程度しかない2qubitゲート打ちまくるとこの程度でしょう・・

```
1 1 1 0 0 0 1 0 1 1 1 0 0 0 0 0
0 1 1 1 1 1 0 1 1 1 0 0 1 1 0 1
0 0 0 0 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 0
0 0 0 1 1 0 1 0 1 1 0 1 1 1 0 0
0 1 1 1 1 0 0 1 0 1 1 0 1 0 0 0
0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 0 0 1 1 1 0 0 0 0
1 0 1 1 0 1 0 0 0 0 0 1 1 0 1 0
0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0
1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 1
0 1 0 0 0 1 0 0 1 1 1 1 0 0 0 0
0 0 0 0 0 0 1 0 0 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0
0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0
0 0 1 1 0 1 0 0 0 0 0 1 1 1 0 1
0 0 0 1 0 1 0 0 1 1 1 1 0 0 0 0
1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 1 0 1 1 0 1 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 1
....

```

## 疑問点
1. なんでlatticeを予約するの？
1つのQPUを複数の人で使えるようにしてるのか？と思いましたが、Rigettiの人はそんなことないと言ってました。そんなことしたら毎回違うランダムなノイズが乗りますもんね・・
そのうち予約なしで、pyquilで書いたファイルを実行キューに登録する形になるかもしれません。

2. 高くない？
シミュレータで簡単にシミュレーションできるサイズなのに強気な値段設定ですね。
しかも基本料金が月額100ドルかかるみたいです。個人で趣味で遊ぶよりはenterprise向けだと思います。

## 最後に
fidelityの改善などのハードウェアの進歩とともにこんなnoisyな状況でもどう使いこなすかのソフトウェア提案が必要になることは量子コンピュータを使えば使うほど感じます。QunaSysではそういう所に取り組んでいきます！


※ 本記事はRigetti Computingの許可を得て投稿しています。