動的回路で量子テレポーテーションを実行する

量子テレポーテーションは、アリスがボブに量子情報を転送するアルゴリズムです。この有名なアルゴリズムを2022年末に新たにリリースされた「[動的回路 (Dynamic Circuits)](https://www.ibm.com/blogs/think/jp-ja/quantum-dynamic-circuits/)」の機能を使ってIBM Quantumの実際の量子コンピューターで実行してみます。

# 動的回路 (Dynamic Circuits)
量子コンピューターでの動的回路とは、量子回路計算を実行している途中で、測定した結果によって、その後の回路を変更し量子計算を続ける回路のことです。シンドローム測定を使った量子誤り訂正が実装できるようになるなど、動的回路の実装は、量子コンピューターの開発において重要な機能の一つです。


# 量子テレポーテーションは途中でアリスが測定をする

アリスがある量子情報を遠く離れた場所にいるボブに送りたい場合、未知の量子状態は一般に複製できないため、情報をコピーして送ることができません。アリスとボブが事前に量子もつれ状態の量子ビットのペア（EPRペア）をそれぞれ持っている場合、アリスからボブに古典情報を2ビット送ることで、未知の1量子ビットの量子状態をアリスからボブに転送することができます。これが量子テレポーテーションです。

このアルゴリズムは、アリスが送りたい量子状態（量子ビット）と手元のEPRペアの1つをエンタングルさせ、**測定**して、その結果をボブに古典的な手段で送り、ボブはその古典情報によって、ボブの手元のEPRペアの1つに対して **結果に合わせた量子演算を行う** ことで、ボブの手元にアリスの送りたかった量子状態が転送されるというものです。つまり、 **途中で測定された結果に依存して演算を変更するという「動的回路」** が含まれます。
（詳しくは、[Qiskitテキストブックの「量子テレポーテーション」の章](https://ja.learn.qiskit.org/course/ch-algorithms/quantum-teleportation)をご覧ください。
また、動的回路を実装した量子テレポーテーションのチュートリアル英語版は[こちら](https://quantum-computing.ibm.com/services/programs/docs/runtime/manage/systems/dynamic-circuits/Teleportation)にあります。）

この「動的回路」をIBM Quantumの実際の量子コンピューターで実行してみます。

# Qiskitの量子テレポーテーションのコード
量子テレポーテーションのコードをQiskitで作ります。
量子テレポーテーションには3量子ビット必要です。また、今回は、アリスの送りたい未知の量子状態 $|\psi \rangle$ を`Ry`(Rotation-Yゲート)で作ります。

```
# Qiskitライブラリーを導入
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
import numpy as np

# 3量子ビット回路を用意
qr = QuantumRegister(3)    
a_0 = ClassicalRegister(1)
a_1 = ClassicalRegister(1)
b_0 = ClassicalRegister(1)

qc = QuantumCircuit(qr,a_0,a_1,b_0)    

# Aliceのもつ未知の量子状態ψをRyで作ります。
qc.ry(np.pi/5,0)
qc.barrier()

# EveがEPRペアを作ってq1をAliceにq2をBobに渡します
qc.h(1)
qc.cx(1, 2)
qc.barrier()

# AliceがCNOTとHでψと自分のEPRペアをエンタングルさせ測定します。
qc.cx(0, 1)
qc.h(0)
qc.barrier()
qc.measure(0, a_0)
qc.measure(1, a_1)

# Aliceが測定結果をBobに送り、Bobが結果に合わせて操作します
with qc.if_test((a_0, 1)):
    qc.z(2)
with qc.if_test((a_1, 1)):
    qc.x(2)
qc.barrier()

# 未知の量子状態ψの逆向きの演算をかけて０が測定できるか確かめます
qc.ry(-np.pi/5, 2)    
qc.measure(2, b_0)

# 回路を描画
qc.draw(output="mpl")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151117/1fd9bccb-702a-73ac-f5ac-89f202bfb369.png)



ここでのポイントは、アリスの測定後のIF文が[`qiskit.circuit.QuantumCircuit.if_test`](https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.if_test.html)で書かれているところです。
```
# Aliceが測定結果をBobに送り、Bobが結果に合わせて操作します
with qc.if_test((a_0, 1)):
    qc.z(2)
with qc.if_test((a_1, 1)):
    qc.x(2)
```

これまでは実機のIBM Quantumシステムを使って実行する際には、測定後にその結果を使って量子計算を続けることができなかったため、IF文は使えませんでした。（シミュレーターを使った実行の場合には計算を続行できましたが、Qiskitテキストブックなどでは[`qiskit.circuit.Gate.c_if`](https://qiskit.org/documentation/locale/ja_JP/stubs/qiskit.circuit.Gate.c_if.html)を使って、以下のように書かれています。）
```
# これまで
qc.z(2).c_if(a_0, 1)
qc.x(2).c_if(a_1, 1)
```

# シミュレーターで実験
まず量子シミュレーターで実験してみます。
```
# QASMシミュレーターで実験
from qiskit import Aer
backend = Aer.get_backend('qasm_simulator')
job_sim = backend.run(qc)
result_sim = job_sim.result()

#  測定された回数を表示
counts = result_sim.get_counts()
print(counts)

# ヒストグラムで測定された確率をプロット
from qiskit.tools.visualization import plot_histogram
plot_histogram(counts)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151117/69c3d54c-8144-508e-c49b-e998f9923c0f.png)

Qiskitのビット配列は右端から数えるので、ボブのビット(3ビット目）は左端です。
今回は、最後に逆向きのゲートをかけて0にしているため、ボブのビットが0になっていれば良いので、結果を確認すると左端のビットが0になっていることから量子テレポーテーションが成功したことが分かります。

# 量子コンピューターで実験
実機の量子コンピューターで新機能「動的回路 (Dynamic Circuits)」を使って実行します。
IBM Quantumのアカウント情報のロードはこれまでの`IBMQ.load_account()`ではなく、`qiskit_ibm_provider`を使います。(参照：[移行ガイド](https://qiskit.org/documentation/partners/qiskit_ibm_provider/tutorials/Migration_Guide_from_qiskit-ibmq-provider.html))

```
# アカウント情報をロードします
from qiskit_ibm_provider import IBMProvider
provider = IBMProvider()
provider = IBMProvider(instance="ibm-q/open/main")
```
2023年1月現在、無償のデバイスの中で動的回路が実行できるのは、`ibm_nairobi`, `ibm_oslo`, `ibmq_manila` です。（参考：https://quantum-computing.ibm.com/services/resources?tab=yours&resourceType=systems から「OpenQASM 3」マークの付いているデバイスを探します。）
今回は、`ibmq_manila`を選びます。

```
# 使うデバイスを指定します
real_backend = provider.get_backend('ibmq_manila')
```
回路を実機のバックエンドでの実行に最適な回路に`transpile`で変換し、`dynamic=True`を指定して実行します。
```
# 実機のバックエンドでの実行に最適な回路に変換します
from qiskit import transpile
qc_compiled = transpile(qc, real_backend)

# 動的回路「dynamic=True」を入れて実行します
job = real_backend.run(qc_compiled, shots=1024, dynamic=True)
```
```
# ジョブの実行状態を確認します
from qiskit.tools.monitor import job_monitor
print(f"Job ID: {job.job_id()}")
job_monitor(job)
```
```
# 結果を確認します
real_result= job.result()
print(real_result.get_counts(qc))
plot_histogram(real_result.get_counts(qc))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151117/77f85ee5-fdc0-d1c2-f4b6-6be627832049.png)

ボブのビット(3ビット目）は左端で、0になっていれば成功です。見にくいので、ボブのビットだけ抜き出します。
```
from qiskit.result import marginal_counts
bobs_qubit = 2
real_counts = real_result.get_counts(qc)
bobs_counts = marginal_counts(real_counts, [bobs_qubit])
plot_histogram(bobs_counts)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151117/ef23bf7e-51e9-7fbd-4e71-0bbc0113f3de.png)


今回の実行による量子テレポーテーションの成功率は8割くらいでした。

実際の量子コンピューターでの測定結果には、まだエラーが含まれますが、動的回路を使って量子コンピューターで量子テレポーテーションを実行することができました。

参考文献：IBM Quantum compute resources の DocsのDynamic circuitsのチュートリアル "Quantum teleportation" (https://quantum-computing.ibm.com/services/resources/docs/resources/manage/systems/dynamic-circuits/Teleportation)