diff --git a/source/en/addition_on_ibmq.md b/source/en/addition_on_ibmq.md new file mode 100644 index 00000000..4666a577 --- /dev/null +++ b/source/en/addition_on_ibmq.md @@ -0,0 +1,440 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【参考】足し算を実機で行う + +実習の内容の延長です。ここでは並列足し算回路を実機で実行します。 +This task is an extension of the exercise. Here, you will implement a parallel addition circuit on an actual QC. + +## 効率化前後の回路の比較 + +実習のおさらいをすると、まずもともとの足し算のロジックをそのまま踏襲した回路を作り、それではゲート数が多すぎるので効率化した回路を作成しました。 +To briefly review the exercise, we created a circuit based on an original addition logic, and then we created an optimized circuit, as the original circuit had too many gates. + +実は効率化した回路でもまだゲートの数が多すぎて、4ビット+4ビットの計算では答えがスクランブルされてしまいます。回路が小規模になればそれだけ成功確率も上がりますので、$(n_1, n_2)$の値として(4, 4)以外に(3, 3)、(2, 2)、(1, 1)も同時に試すことにしましょう。 +Actually, there are still too many gates in the optimized circuit, and the answer when performing calculations on 4 bits + 4 bits are scrambled. The smaller the scale of a circuit, the higher its probability of success, so for values ($n_1$, $n_2$), in addition to (4, 4), let's also try out (3, 3), (2, 2), and (1, 1). + +```{code-cell} ipython3 +:tags: [remove-output] + +# まずは全てインポート +# First, import everything +import numpy as np +import matplotlib.pyplot as plt +from qiskit import QuantumRegister, QuantumCircuit, transpile +from qiskit.tools.monitor import job_monitor +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_ibm_provider.accounts import AccountNotFoundError +from qiskit_experiments.library import CorrelatedReadoutError +from qc_workbook.optimized_additions import optimized_additions +from qc_workbook.utils import operational_backend, find_best_chain + +print('notebook ready') +``` + +実習と同様、`'ibm-q/open/main'`のプロバイダでは(1, 1)の回路しか扱えないので、フェイクバックエンドを使います。 + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +instance = 'ibm-q/open/main' + +try: + provider = IBMProvider(instance=instance) +except IBMQAccountCredentialsNotFound: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) +``` + +```{code-cell} ipython3 +:tags: [remove-output] + +if instance == 'ibm-q/open/main': + from qiskit.test.mock import FakeGuadalupe + + backend = FakeGuadalupe() + +else: + backend_list = provider.backends(filters=operational_backend(min_qubits=13)) + backend = least_busy(backend_list) + +print(f'Using backend {backend.name()}') +``` + +実習と全く同じ`setup_addition`関数と、次のセルで効率化前の回路を返す`make_original_circuit`関数を定義します。 +Use the following cell to implement a `setup_addition` function used in the exercise and `make_original_circuit` function that returns the unoptimized circuit. + +```{code-cell} ipython3 +def setup_addition(circuit, reg1, reg2, reg3): + """Set up an addition subroutine to a circuit with three registers + """ + + # Equal superposition in register 3 + circuit.h(reg3) + + # Smallest unit of phi + dphi = 2. * np.pi / (2 ** reg3.size) + + # Loop over reg1 and reg2 + for reg_ctrl in [reg1, reg2]: + # Loop over qubits in the control register (reg1 or reg2) + for ictrl, qctrl in enumerate(reg_ctrl): + # Loop over qubits in the target register (reg3) + for itarg, qtarg in enumerate(reg3): + # C[P(phi)], phi = 2pi * 2^{ictrl} * 2^{itarg} / 2^{n3} + circuit.cp(dphi * (2 ** (ictrl + itarg)), qctrl, qtarg) + + # Insert a barrier for better visualization + circuit.barrier() + + # Inverse QFT + for j in range(reg3.size // 2): + circuit.swap(reg3[j], reg3[-1 - j]) + + for itarg in range(reg3.size): + for ictrl in range(itarg): + power = ictrl - itarg - 1 + reg3.size + circuit.cp(-dphi * (2 ** power), reg3[ictrl], reg3[itarg]) + + circuit.h(reg3[itarg]) + +def make_original_circuit(n1, n2): + """A function to define a circuit with the original implementation of additions given n1 and n2 + """ + n3 = np.ceil(np.log2((2 ** n1) + (2 ** n2) - 1)).astype(int) + + reg1 = QuantumRegister(n1, 'r1') + reg2 = QuantumRegister(n2, 'r2') + reg3 = QuantumRegister(n3, 'r3') + + # QuantumCircuit can be instantiated from multiple registers + circuit = QuantumCircuit(reg1, reg2, reg3) + + # Set register 1 and 2 to equal superpositions + circuit.h(reg1) + circuit.h(reg2) + + setup_addition(circuit, reg1, reg2, reg3) + + circuit.measure_all() + + return circuit +``` + +(4, 4)から(1, 1)までそれぞれオリジナルと効率化した回路の二通りを作り、全てリストにまとめてバックエンドに送ります。 + +```{code-cell} ipython3 +# count_ops()の結果をテキストにする関数 +def display_nops(circuit): + nops = circuit.count_ops() + text = [] + for key in ['rz', 'x', 'sx', 'cx']: + text.append(f'N({key})={nops.get(key, 0)}') + + return ', '.join(text) + +# オリジナルと効率化した回路を作る関数 +def make_circuits(n1, n2, backend): + print(f'Original circuit with n1, n2 = {n1}, {n2}') + circuit_orig = make_original_circuit(n1, n2) + + print(' Transpiling..') + circuit_orig = transpile(circuit_orig, backend=backend, optimization_level=3) + + print(f' Done. Ops: {display_nops(circuit_orig)}') + circuit_orig.name = f'original_{n1}_{n2}' + + print(f'Optimized circuit with n1, n2 = {n1}, {n2}') + circuit_opt = optimized_additions(n1, n2) + + n3 = np.ceil(np.log2((2 ** n1) + (2 ** n2) - 1)).astype(int) + + print(' Transpiling..') + initial_layout = find_best_chain(backend, n1 + n2 + n3) + circuit_opt = transpile(circuit_opt, backend=backend, routing_method='basic', + initial_layout=initial_layout, optimization_level=3) + + print(f' Done. Ops: {display_nops(circuit_opt)}') + circuit_opt.name = f'optimized_{n1}_{n2}' + + return [circuit_orig, circuit_opt] +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# テキスト作成用のセル + +import os +if os.getenv('JUPYTERBOOK_BUILD') == '1': + shots = backend.configuration().max_shots + del backend +``` + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +# List of circuits +circuits = [] +for n1, n2 in [(4, 4), (3, 3), (2, 2), (1, 1)]: + circuits += make_circuits(n1, n2, backend) + +# バックエンドで定められた最大のショット数だけ各回路を実行 +shots = backend.configuration().max_shots + +print(f'Submitting {len(circuits)} circuits to {backend.name()}, {shots} shots each') + +if backend.provider() is None: + # Case: fake backend + # フェイクバックエンドには何らかのバグがあり、一度に8つの回路を実行すると0000..という答えが多発する(2022-04-05 YI) + counts_list = [] + for circuit in circuits: + job = backend.run(circuit, shots=shots) + counts_list.append(job.result().get_counts()) + + print('Job Status: job has successfully run') + +else: + job = backend.run(circuits, shots=shots) + + job_monitor(job, interval=2) + + counts_list = job.result().get_counts() +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# テキスト作成用のセル + +import pickle +if os.getenv('JUPYTERBOOK_BUILD') == '1': + with open('data/quantum_computation_fake_data.pkl', 'rb') as source: + counts_list = pickle.load(source) +``` + +ジョブが返ってきたら、正しい足し算を表しているものの割合を調べてみましょう。 +Once the job returns, let's check the fraction of the 8,192 shots that produced correct addition results. + +```{code-cell} ipython3 +:tags: [remove-output] + +def count_correct_additions(counts, n1, n2): + """Extract the addition equation from the counts dict key and tally up the correct ones.""" + + correct = 0 + + for key, value in counts.items(): + # cf. plot_counts() from the SIMD lecture + x1 = int(key[-n1:], 2) + x2 = int(key[-n1 - n2:-n1], 2) + x3 = int(key[:-n1 - n2], 2) + + if x1 + x2 == x3: + correct += value + + return correct + + +icirc = 0 +for n1, n2 in [(4, 4), (3, 3), (2, 2), (1, 1)]: + for ctype in ['Original', 'Optimized']: + n_correct = count_correct_additions(counts_list[icirc], n1, n2) + r_correct = n_correct / shots + print(f'{ctype} circuit ({n1}, {n2}): {n_correct} / {shots} = {r_correct:.3f} +- {np.sqrt(r_correct * (1. - r_correct) / shots):.3f}') + icirc += 1 +``` + +ちなみに、`ibm_kawasaki`というマシンで同じコードを走らせると、下のような結果が得られます。 + +
+Original circuit with n1, n2 = 4, 4
+  Transpiling..
+  Done. Ops: N(rz)=170, N(x)=3, N(sx)=67, N(cx)=266
+Optimized circuit with n1, n2 = 4, 4
+  Transpiling..
+  Done. Ops: N(rz)=175, N(x)=1, N(sx)=64, N(cx)=142
+Original circuit with n1, n2 = 3, 3
+  Transpiling..
+  Done. Ops: N(rz)=120, N(x)=5, N(sx)=57, N(cx)=90
+Optimized circuit with n1, n2 = 3, 3
+  Transpiling..
+  Done. Ops: N(rz)=117, N(x)=0, N(sx)=48, N(cx)=84
+Original circuit with n1, n2 = 2, 2
+  Transpiling..
+  Done. Ops: N(rz)=50, N(x)=0, N(sx)=20, N(cx)=56
+Optimized circuit with n1, n2 = 2, 2
+  Transpiling..
+  Done. Ops: N(rz)=67, N(x)=0, N(sx)=32, N(cx)=41
+Original circuit with n1, n2 = 1, 1
+  Transpiling..
+  Done. Ops: N(rz)=25, N(x)=0, N(sx)=15, N(cx)=13
+Optimized circuit with n1, n2 = 1, 1
+  Transpiling..
+  Done. Ops: N(rz)=27, N(x)=0, N(sx)=16, N(cx)=13
+
+ ++++ {"tags": ["remove-input"]} + +
+Original circuit (4, 4): 990 / 32000 = 0.031 +- 0.001
+Optimized circuit (4, 4): 879 / 32000 = 0.027 +- 0.001
+Original circuit (3, 3): 2435 / 32000 = 0.076 +- 0.001
+Optimized circuit (3, 3): 2853 / 32000 = 0.089 +- 0.002
+Original circuit (2, 2): 3243 / 32000 = 0.101 +- 0.002
+Optimized circuit (2, 2): 7994 / 32000 = 0.250 +- 0.002
+Original circuit (1, 1): 25039 / 32000 = 0.782 +- 0.002
+Optimized circuit (1, 1): 26071 / 32000 = 0.815 +- 0.002
+
+ ++++ + +回路が均一にランダムに$0$から$2^{n_1 + n_2 + n_3} - 1$までの数を返す場合、レジスタ1と2のそれぞれの値の組み合わせに対して正しいレジスタ3の値が一つあるので、正答率は$2^{n_1 + n_2} / 2^{n_1 + n_2 + n_3} = 2^{-n_3}$となります。実機では、(4, 4)と(3, 3)でどちらの回路も正答率がほとんどこの値に近くなっています。(2, 2)では効率化回路で明らかにランダムでない結果が出ています。(1, 1)では両回路とも正答率8割です。 +If the circuit uniformly and randomly returns values between $0$ and $2^{n_1 + n_2 + n_3} - 1$, there will be only one correct value for register 3 for any given combination of registers 1 and 2, so the probability of being correct will be $2^{n_1 + n_2} / 2^{n_1 + n_2 + n_3} = 2^{-n_3}$. +```If we compare the results of the actual QC, we will unfortunately find that for (4, 4), (3, 3), and (2, 2), both the original and the optimized circuits have accuracy rates roughly equivalent to this. For (1, 1), the accuracy rate is just slightly higher than chance (2^(-2) ).``` + +フェイクバックエンドでは実機のエラーもシミュレートされていますが、エラーのモデリングが甘い部分もあり、$2^{-n_3}$よりは遥かに良い成績が得られています。いずれのケースも、回路が短い効率化バージョンの方が正答率が高くなっています。 + ++++ + +## Quantum Volume + +量子コンピュータがエラーを起こす頻度は、マシンによって違います。さらに言えば個々の量子ビットごとに1量子ビットゲートのエラー率と測定のエラー率[^measurement_error]があり、また量子ビット間の接続ごとにCNOTのエラー率があります。これまで使ってきた`find_best_chain()`という関数は、与えられたバックエンドの中でこれらのエラー率の積が最も小さくなるような量子ビットの並びを選んでくるものでした。 + +マシン自体のパフォーマンスを比較するには、`find_best_chain`でやるように各マシンの中から最も性能の良い量子ビットの組み合わせを選んできて、その上でベンチマークとなる回路を実行します。IBMQではそのようにして量子コンピュータ一つ一つをQuantum Volume(QV、量子体積){cite}`PhysRevA.100.032328`という指標で評価しています。[^qv]。QVは簡単に言えば「量子コンピュータ上である特定の形を持った回路を安定的に実行できる量子ビット数と回路の長さ」を測っていて、QVの値が大きいマシンほど高性能と言えます。 +QVにはゲートや測定のエラー率だけでなく、トランスパイラの性能なども関係します。2022年4月現在、IBMQのマシンでQVの最大値は128です。 + +`open`のプロバイダで使える5量子ビットマシンのうちでも、QVの値が異なるものがあるので、1ビット+1ビットの自明な例にはなってしまいますが、バックエンドを選んで足し算の正答率を比較してみましょう。 + +[^measurement_error]: 測定のエラーとは、具体的には状態$\ket{0}$や$\ket{1}$を測定したときにどのくらいの割合で0や1でない結果を得るかということを表した値です。 +[^qv]: QVはハードウェアの詳細に依存しないように定義されているので、量子ビット型の量子コンピュータであればIBMのマシンに限らずすべてQVで評価できます。実際、業界で徐々にQVを標準ベンチマークとして使う動きが広がってきているようです。 + +[^qv]: QV is defined to not be reliant on hardware details, so it can be used to evaluate all quantum bit-based quantum computers, not just IBM machines. The industry as a whole is moving towards using QV as a standard benchmark. + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +backend_qv8 = least_busy(provider.backends(filters=operational_backend(qv=8))) +backend_qv16 = least_busy(provider.backends(filters=operational_backend(qv=16))) +backend_qv32 = least_busy(provider.backends(filters=operational_backend(qv=32))) + +print(f'Using backends {backend_qv8.name()} (QV 8), {backend_qv16.name()} (QV 16), {backend_qv32.name()} (QV 32)') +``` + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +n1 = n2 = 1 + +jobs = [] +for backend in [backend_qv8, backend_qv16, backend_qv32]: + circuits = make_circuits(n1, n2, backend) + shots = backend.configuration().max_shots + job = backend.run(circuits, shots=shots) + jobs.append(job) + +for job, qv in zip(jobs, [8, 16, 32]): + print(f'QV {qv} job') + job_monitor(job, interval=2) + +for job, qv in zip(jobs, [8, 16, 32]): + counts_list = job.result().get_counts() + + for counts, ctype in zip(counts_list, ['Original', 'Optimized']): + n_correct = count_correct_additions(counts, n1, n2) + shots = sum(counts.values()) + r_correct = n_correct / shots + print(f'QV {qv} {ctype} circuit ({n1}, {n2}): {n_correct} / {shots} = {r_correct:.3f} +- {np.sqrt(r_correct * (1. - r_correct) / shots):.3f}') +``` + +QVから期待される正答率の序列にならないかもしれません。量子コンピュータという恐ろしく複雑な機械の性能を一つの数値で表すことの限界がここにあり、今のように単純な回路を実行する場合は、ケースバイケースで特定の量子ビットや特定のゲートのエラー率が結果に大きな影響を及ぼしたりするのです。 + +じっさい、`find_best_chain`関数が各バックエンドで算出したエラー率の積を比較すると、QVの大きいマシンが必ずしも低いエラー率を持っているわけではないことがわかります。 + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +# find_best_chainで4量子ビットの列を探し、エラー率の積が最小になる組み合わせにおいてCNOTゲートエラー率の積と測定エラー率の積(の対数)をそれぞれ取得 +_, log_gate_error_qv8, log_readout_error_qv8 = find_best_chain(backend_qv8, 4, return_error_prod=True) +_, log_gate_error_qv16, log_readout_error_qv16 = find_best_chain(backend_qv16, 4, return_error_prod=True) +_, log_gate_error_qv32, log_readout_error_qv32 = find_best_chain(backend_qv32, 4, return_error_prod=True) + +print(f'QV 8 error rates: {log_gate_error_qv8:.2f} (CNOT), {log_readout_error_qv8} (readout)') +print(f'QV 16 error rates: {log_gate_error_qv16:.2f} (CNOT), {log_readout_error_qv16} (readout)') +print(f'QV 32 error rates: {log_gate_error_qv32:.2f} (CNOT), {log_readout_error_qv32} (readout)') +``` + ++++ {"tags": ["remove-output", "raises-exception"]} + +(measurement_error_mitigation)= +## 測定エラーの緩和 + +{doc}`extreme_simd`でも軽く触れましたが、現状ではCNOTゲートのエラー率は1量子ビットゲートのエラー率より一桁程度高くなっています。CNOTを含むゲートで発生するエラーは本格的なエラー訂正が可能になるまでは何も対処しようがなく、そのため上ではCNOTを極力減らす回路を書きました。しかし、そのようなアプローチには限界があります。 + +一方、測定におけるエラーは、エラー率が実は決して無視できない高さであると同時に、統計的にだいたい再現性がある(あるビット列$x$が得られるべき状態から別のビット列$y$が得られる確率が、状態の生成法に依存しにくい)という性質があります。そのため、測定エラーは事後的に緩和(部分的補正)できます。そのためには$n$ビットレジスタの$2^n$個すべての計算基底状態について、相当するビット列が100%の確率で得られるべき回路を作成し、それを測定した結果を利用します。 + +例えば$n=2$で状態$\ket{x} \, (x = 00, 01, 10, 11)$を測定してビット列$y$を得る確率が$\epsilon^x_y$だとします。このとき実際の量子計算をして測定で得られた確率分布が$\{p_y\}$であったとすると、その計算で本来得られるべき確率分布$\{P_x\}$は連立方程式 + +$$ +p_{00} = P_{00} \epsilon^{00}_{00} + P_{01} \epsilon^{01}_{00} + P_{10} \epsilon^{10}_{00} + P_{11} \epsilon^{11}_{00} \\ +p_{01} = P_{00} \epsilon^{00}_{01} + P_{01} \epsilon^{01}_{01} + P_{10} \epsilon^{10}_{01} + P_{11} \epsilon^{11}_{01} \\ +p_{10} = P_{00} \epsilon^{00}_{10} + P_{01} \epsilon^{01}_{10} + P_{10} \epsilon^{10}_{10} + P_{11} \epsilon^{11}_{10} \\ +p_{11} = P_{00} \epsilon^{00}_{11} + P_{01} \epsilon^{01}_{11} + P_{10} \epsilon^{10}_{11} + P_{11} \epsilon^{11}_{11} +$$ + +を解けば求まります。つまり、行列$\epsilon^x_y$の逆をベクトル$p_y$にかければいいわけです[^actually_fits]。 + +Qiskitでは測定エラー緩和用の関数やクラスが提供されているので、それを使って実際にエラーを求め、上の足し算の結果の改善を試みましょう。 + +[^actually_fits]: 実際には数値的安定性などの理由から、単純に逆行列をかけるわけではなくフィッティングが行われますが、発想はここで書いたものと変わりません。 + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# QV32のマシンでの結果の改善を試みる +qubits = find_best_chain(backend_qv32, 4) + +# 測定エラー緩和の一連の操作(2^4通りの回路生成、ジョブの実行、結果の解析)がCorrelatedReadoutErrorクラスの内部で行われる +experiment = CorrelatedReadoutError(qubits) +experiment.analysis.set_options(plot=True) +result = experiment.run(backend_qv32) + +# mitigatorオブジェクトが上でいうε^x_yの逆行列を保持している +mitigator = result.analysis_results(0).value +``` + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +# jobs配列の最後がQV32。get_counts(1)で最適化された回路での結果を得る +raw_counts = jobs[-1].result().get_counts(1) +shots = backend_qv32.configuration().max_shots +# ここから下はおまじない +quasiprobs = mitigator.quasi_probabilities(raw_counts, shots=shots) +mitigated_probs = quasiprobs.nearest_probability_distribution().binary_probabilities() +mitigated_counts = dict((key, value * shots) for key, value in mitigated_probs.items()) + +n_correct = count_correct_additions(mitigated_counts, n1, n2) +r_correct = n_correct / shots +print(f'QV 32 optimized circuit with error mitigation ({n1}, {n2}): {n_correct} / {shots} = {r_correct:.3f} +- {np.sqrt(r_correct * (1. - r_correct) / shots):.3f}') +``` diff --git a/source/en/chsh_inequality.md b/source/en/chsh_inequality.md new file mode 100644 index 00000000..16ab3c8f --- /dev/null +++ b/source/en/chsh_inequality.md @@ -0,0 +1,795 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# CHSH不等式の破れを確認する + ++++ + +In the first exercise, you will confirm that the quantum computer realizes quantum mechanical states -- entanglement, in particular. You will be introduced to the concepts of quantum mechanics and the fundamentals of quantum computing through this exercise. + +```{contents} 目次 +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ +$\newcommand{\rmI}{\mathrm{I}}$ +$\newcommand{\rmII}{\mathrm{II}}$ +$\newcommand{\rmIII}{\mathrm{III}}$ +$\newcommand{\rmIV}{\mathrm{IV}}$ + ++++ + +## 本当に量子コンピュータなのか? + +このワークブックの主旨が量子コンピュータ(QC)を使おう、ということですが、QCなんて数年前までSFの世界の存在でした。それが今やクラウドの計算リソースとして使えるというわけですが、ではそもそも私たちがこれから使おうとしている機械は本当にQCなのでしょうか。どうしたらそれが調べられるでしょうか。 +The aim of this workbook is to use a quantum computer (QC), but until just a few years ago, QCs were something that only existed in science fiction. They can now be used as computational resources over the cloud -- but are the devices that we are going to use in these exercises really QCs? How can we check? + +QCの基本的な仕組みは、「何らかの物理的な系(超電導共振器や冷却原子など)をうまく操作して、求める計算の結果がその系の量子状態に表現されるようにする」ということです。つまり、量子状態が長く保たれてかつ思うように操作できる対象と、「計算」という実体のなさそうなものを具体的な「量子操作」に対応させるアルゴリズムの両方があって初めてQCが成り立ちます。アルゴリズムの部分はこのワークブックを通じて少しずつ紹介していくので、今回は「量子状態が保たれ、それを操作できる」ということを確認してみましょう。 +The fundamental way that QCs work is that they skillfully operate a physical system (such as superconducting resonators or cold atoms) such that the results of computations are expressed in the quantum state of the system. In other words, a computer can be called a quantum computer only when it can maintain its quantum states for long periods of time, be used to freely manipulate them, and offer algorithms that link immaterial "calculation" with physical "quantum manipulation." The algorithm portion will be introduced little by little through this workbook, so here, let's confirm that the QC can really maintain quantum states for long periods of time and make it possible to freely manipulate them. + ++++ + +## CHSH不等式 + +量子力学的状態が実際に存在するかどうかを確かめる実験として、2022年のノーベル物理学賞でも取り上げられたCHSH不等式{cite}`chsh`の検証というものがあります。かいつまんで言うと、CHSH不等式とは「二体系の特定の観測量について、エンタングルメントなど量子力学固有の現象がなければ保たれる不等式」です。やや回りくどいロジックですが、つまりQC(だと考えられる機械)で測ったこの観測量の値がCHSH不等式を破っていれば、その機械は実際に量子現象を利用しているかもしれないということになります。 +One way to experimentally verify if quantum mechanical states really exist in a QC is to verify the CHSH inequality [CHSH69,NC00]. To put it briefly, the CHSH inequality is an inequality of specific observables in a two-body system that is maintained unless there are quantum mechanics-specific phenomena such as entanglement. The logic is somewhat circuitous, but if the values of these observables, when measured by a device that is (believed to be) a QC, violate the CHSH inequality, the device may actually be using quantum phenomena. + +通常このような実験を行うには高度なセットアップ(レーザーと非線形結晶、冷却原子など)が必要ですが、クラウドQCではブラウザひとつしか要りません。このワークブックではJupyter NotebookでPythonのプログラムを書き、IBM Quantumの量子コンピュータを利用します。 +Normally, this type of experiment would require a complicated setup (involving a laser, non-linear crystal, cold atoms, etc.), but with a cloud-based QC, all that's needed is a simple browser. In this workbook, you will use Jupyter Notebook to write a Python program and then use quantum computer through IBM Quantum. + ++++ + +## Qiskitの基本構造 + +IBM QuantumのQCで量子計算を実行するには、IBMの提供するQiskitというPythonライブラリを利用します。Qiskitの基本的な使い方は + +1. 使用する量子ビットの数を決め、量子計算の操作(ゲート)をかけて、量子回路を作る +1. 回路を実行して計算結果を得る。ここでは二通りのオプションがあり、 + - 回路をQCの実機に送り、実行させる。 + - 回路をシミュレートする。 +1. 計算結果を解析する。 + +です。以下でこの流れを一通り、重要な概念の説明を混ぜながら実行してみましょう。ただし、今回は実機のみ利用します。回路のシミュレーションに関しては{doc}`第一回の課題 `を参照してください。 + +Qiskit, a Python library provided by IBM, is used when implementing quantum calculation on a IBM Q System One QC. The basic procedure for using Qiskit is as follows: +1. Decide on the number of quantum bits to use +2. Apply quantum calculation operations (gates) to the quantum bits to create a quantum circuit +3. Implement the circuit and produce calculation results. Here, there are two options: + - Send the circuit to the actual QC device and implement it. + - Simulate the circuit. +4. Analyze the calculation results. + +You will perform this process using the procedure below, which includes explanations of important concepts. In this exercise, you will only use the actual device. Please refer to the {doc}`first assignment ` for information on simulating circuits. + +Qiskitの機能は上のような基本的な量子回路の設計・実行だけではなく、非常に多岐に渡ります。基本的な使い方に関しても多少複雑なところがあるので、わからないことがあればQiskitのドキュメンテーションをあたってみましょう。 + ++++ + +### 量子ビット、量子レジスタ + +**量子ビット**(qubit=キュビット)とは量子コンピュータの基本構成要素のことで、量子情報の入れ物の最小単位です。そして、量子ビットの集まりを量子レジスタと呼びます。 +**Quantum bits**, or qubits, are the fundamental elements that make up quantum computers. They are the smallest possible unit of quantum information. A number of quantum bits gathered together is referred to as a quantum register. + +量子レジスタは量子コンピュータ中で常に一つの「状態」にあります。量子レジスタの状態を物理学の習わしに従ってしばしば「ケット」という$\ket{\psi}$のような記号で表します[^mixed_state]。量子力学に不慣れな方はこの記法で怯んでしまうかもしれませんが、ケット自体はただの記号なのであまり気にしないでください。別に「枠」なしで$\psi$と書いても、絵文字を使って🔱と書いても、何でも構いません。 +Quantum registers in quantum computers always have one "state." Following from a common practice used in physics, the states of quantum registers are referred to as "kets" and indicated with the form $\ket{\psi}$[^mixed_state]. If you are unfamiliar with quantum mechanics, this notation method may look intimidating, but the ket itself is merely a symbol, so there is no need to be overly concerned. You could also write $\psi$ by itself, without enclosing, or even use the 🔱 emoji. Anything would work. + +重要なのは各量子ビットに対して2つの**基底状態**が定義できることで、量子計算の習わしではそれらを$\ket{0}$と$\ket{1}$で表し、「計算基底」とも呼びます[^basis]。そして、量子ビットの任意の状態は、2つの複素数$\alpha, \beta$を使って +What's important is that each two **basis states** can be defined for each quantum bit. By quantum calculation custom, these are represented as $\ket{0}$ and $\ket{1}$, and together are called the computational basis[^basis]. Any state of a quantum bit can then be expressed through a "superimposition" of the two basis states, using the complex numbers $\alpha$ and $\beta$, as shown below. + +$$ +\alpha \ket{0} + \beta \ket{1} +$$ + +と2つの基底の「重ね合わせ」で表せます。ここで$\alpha, \beta$を確率振幅、もしくは単に**振幅**(amplitude)と呼びます。繰り返しですが別に表記法自体に深い意味はなく、例えば同じ状態を$[\alpha, \beta]$と書いてもいいわけです[^complexarray]。 +Here, $\alpha$ and $\beta$ are called probability amplitudes, or simply **amplitudes**. Again, the actual formatting used is not particularly significant. The states could just as well be written as $[\alpha, \beta]$[^complexarray]. + +量子ビットの任意の状態が2つの複素数で表せるということは、逆に言えば一つの量子ビットには2つの複素数に相当する情報を記録できるということになります。ただこれには少し注釈があって、量子力学の決まりごとから、$\alpha$と$\beta$は +Because any state of a quantum bit can be expressed using two complex numbers, the amount of information that can be contained by any single quantum bit is equivalent to two complex numbers. However, it is also important to note that, due to the rules of quantum mechanics, the relationship between $\alpha$ and $\beta$ must also satisfy the following requirement. + +$$ +|\alpha|^2 + |\beta|^2 = 1 +$$ + +という関係を満たさなければならず、かつ全体の位相(global phase)は意味を持たない、つまり、任意の実数$\theta$に対して +Furthermore, the global phase is not significant. In other words, for an arbitrary real number $\theta$: + +$$ +\alpha \ket{0} + \beta \ket{1} \sim e^{i\theta} (\alpha \ket{0} + \beta \ket{1}) +$$ + +(ここで $\sim$ は「同じ量子状態を表す」という意味)である、という制約があります。 +(Here, $\sim$ means "expresses the same quantum state") + +複素数1つは実数2つで書けるので、$\alpha$と$\beta$をあわせて実数4つ分の情報が入っているようですが、2つの拘束条件があるため、実際の自由度は 4-2=2 個です。自由度の数をあらわにして量子ビットの状態を記述するときは、 +A single complex number can be written using two real numbers, so $\alpha$ and $\beta$ together would appear to have the same amount of information as four real numbers, but due to these two constraints, the actual degree of freedom is 4-2=2. The state of the quantum bit, indicating the degree of freedom, can be expressed as shown below. + +$$ +e^{-i\phi/2}\cos\frac{\theta}{2}\ket{0} + e^{i\phi/2}\sin\frac{\theta}{2}\ket{1} +$$ + +と書いたりもします。この表記法をブロッホ球表現と呼ぶこともあります。 +This method of notation is called Bloch sphere notation. + +面白くなるのは量子ビットが複数ある場合です。例えば量子ビット2つなら、それぞれに$\ket{0}, \ket{1}$の計算基底があるので、任意の状態は +Where things get interesting is when one is working with multiple quantum bits. For example, if there are two quantum bits, each has a $\ket{0}$,$\ket{1}$ computational basis, so any state is a superimposition produced using four complex numbers. + +$$ +\alpha \ket{0}\ket{0} + \beta \ket{0}\ket{1} + \gamma \ket{1}\ket{0} + \delta \ket{1}\ket{1} +$$ + +と4つの複素数を使った重ね合わせになります。2つの量子ビットの基底を並べた$\ket{0}\ket{0}$のような状態が、このレジスタの計算基底ということになります。$\ket{00}$と略したりもします。 +The state in which the basis states of the two quantum bits is $\ket{0}\ket{0}$ is this register's computational basis. It can be abbreviated as $\ket{00}$. + +上で登場した量子力学の決まりごとはこの場合 +Because of the rules of quantum mechanics discussed above, the following are true. + +$$ +|\alpha|^2 + |\beta|^2 + |\gamma|^2 + |\delta|^2 = 1 +$$ + +と +and + +$$ +\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11} \sim e^{i\theta} (\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11}) +$$ + +となります。量子ビットがいくつあっても拘束条件は2つだけです。 +No matter how many quantum bits there are, there are only two constraints. + +つまり、量子ビット$n$個のレジスタでは、基底の数が$2^n$個で、それぞれに複素数の振幅がかかるので、実数$2 \times 2^n - 2$個分の情報が記録できることになります。これが量子計算に関して「指数関数的」という表現がよく用いられる所以です。 +In other words, a register with n quantum bits has $2^n$ basis states, and each has the amplitude of a complex number, so the amount of real numbers of information that can be recorded is $2 \times 2^n - 2$. This is why the word "exponential" is often used when discussing quantum calculation. + +量子レジスタの計算基底状態の表記法としては、上に書いたようにケットを$n$個並べたり$n$個の0/1を一つのケットの中に並べたりする方法がありますが、さらにコンパクトなのが、0/1の並び(ビット列)を二進数とみなして、対応する(十進数の)数字で表現する方法です。例えば4量子ビットのレジスタで状態$\ket{0000}$と$\ket{1111}$はそれぞれ$\ket{0}$と$\ket{15}$と書けます。 +The computational basis state of the quantum register can be expressed in various ways, such as by lining up $n$ kets, as described above, or by placing $n$ 0/1s within a single ket. An even more compact approach is to look at the string of 0/1s as a binary string (bit string) and representing it with the corresponding (decimal) number. For example, the four quantum bit register states $\ket{0000}$ and $\ket{1111}$ can be expressed as $\ket{0}$ and $\ket{15}$, respectively. + +ただし、ここで注意すべきなのは、左右端のどちらが「1の位」なのか事前に約束しないといけないことです。$\ket{0100}$を$\ket{4}$(右端が1の位)とするか$\ket{2}$(左端が1の位)とするかは約束次第です。このワークブックでは、Qiskitでの定義に従って、右端を1の位とします。同時に、レジスタの最初の量子ビットが1の位に対応するようにしたいので、ケットや0/1を並べて計算基底を表現するときは、右から順にレジスタの量子ビットを並べていくことにします。 +However, when doing so, care must be taken to first indicate which end, the left or the right, is the ones' place. Whether $\ket{0100}$ becomes $\let{4}$ (because the rightmost place is the ones' place) or $\ket{2}$ (because the leftmost place is the ones' place) depends on which convention is used. In this workbook, in accordance with Qiskit's definition, the rightmost place is the ones' place. We want to make the first quantum bit of the register correspond to the ones' place, so when expressing a computational basis with a string of kets or 0/1s, the register's quantum bits will be arranged in order starting from the right. + +Qiskitには量子レジスタオブジェクトがあり、 +Qiskit has quantum register objects. +```{code-block} python +from qiskit import QuantumRegister +register = QuantumRegister(4, 'myregister') +``` +のように量子ビット数(この場合4)と名前(`'myregister'`)を指定して初期化します。初期状態では、量子ビットはすべて$\ket{0}$状態にあります。レジスタオブジェクトはこのままではあまり使い道がなく、基本的には次に紹介する量子回路の一部として利用します。 +These objects are initialized by specifying the number of quantum bits (in this case, 4) and the name ('myregister'), as shown above. By default, all quantum bits will be in the |0⟩ state. There aren't many uses for a register object by itself. Instead, they are, as a general rule, used as parts in quantum circuits, which are introduced below. + +[^mixed_state]: 正確には、状態がケットで表せるのはこのレジスタが他のレジスタとエンタングルしていないときに限られますが、ここでは詳細を割愛します。 +[^basis]: ここで言う「基底」は線形代数での意味(basis)で、「線形空間中の任意のベクトルを要素の線形和で表せる最小の集合」です。基底となる量子状態だから「基底状態」と呼びます。化学や量子力学で言うところのエネルギーの最も低い状態「基底状態」(ground state)とは関係ありません。 +[^complexarray]: 実際に量子計算のシミュレーションをコンピュータ上で行う時などは、量子レジスタの状態を複素数の配列で表すので、この表記の方がよく対応します。 +[^mixed_state] Strictly speaking, states can be expressed with kets only when this register is not entangled with other registers, but we'll skip the details here. +[^basis] The "basis state" here is being used in its linear algebra sense, as the "minimum set in which any given vector in linear space can be expressed as the linear sum of its elements." It is called the basis state because it is the base quantum state. This is unrelated to the ground state, used in chemistry and quantum mechanics to refer to the lowest possible energy state. +[^complexarray]] When actually performing quantum calculation simulations on computers, quantum register states are expressed using arrays of complex numbers, so this notation method corresponds closely. + + ++++ + +### ゲート、回路、測定 + +量子計算とは、端的に言えば、量子レジスタに特定の状態を生成し、その振幅を利用することと言えます。 +One could even go so far as to say that quantum calculation consists of generating a certain state in a quantum register and then using its amplitude. + +とは言っても、いきなり「えいや」と好きな量子状態を作れるわけではなく、パターンの決まった単純操作($\ket{0}$と$\ket{1}$を入れ替える、ブロッホ球表現での位相角度$\phi$を増減させる、など)を順番に組み合わせて複雑な状態を作っていきます。この単純操作のオペレーションのことを一般に量子**ゲート**といい、ゲートの種類や順番を指定したプログラムに相当するものを量子**回路**と呼びます。 +However, you can't simply up and create whatever quantum state you want. Instead, complex states are created by combining, in order, simple operations with defined patterns (such as swapping $\ket{0}$ and $\ket{1}$, amplifying the phase angle $\phi$ in Bloch sphere representation, etc.). These simple operations are generally referred to as quantum **gates**, and programs which specify types and sequences of these gates are called quantum **circuits**. + +Qiskitでは、量子回路を`QuantumCircuit`オブジェクトで表します。 +Qiskit represents quantum circuits using `QuantumCircuit` objects. Below is an example. +```{code-block} python +from qiskit import QuantumCircuit, QuantumRegister +register = QuantumRegister(4, 'myregister') +circuit = QuantumCircuit(register) +``` +という具合です。 + +作られた量子回路は、量子ビットの数が決まっているもののゲートが一つもない「空っぽ」の状態なので、そこにゲートをかけていきます。例えば下で説明するアダマールゲートをレジスタの2個目の量子ビットに作用させるには +The quantum circuit that was created has a defined number of quantum bits, but it contains no gates -- it is empty. You need to add gates. For example, the following is used to apply a Hadamard gate, explained below, to the second quantum bit of a register. +```{code-block} python +circuit.h(register[1]) +``` +とします。 + +上で「振幅を利用する」という曖昧な表現をしましたが、それはいろいろな利用の仕方があるからです。しかし、どんな方法であっても、必ず量子レジスタの**測定**という操作を行います。量子コンピュータから何かしらの情報を得るための唯一の方法が測定です。Qiskitでは`measure_all`というメソッドを使って測定を行います。 +Above, we used the vague expression "using its amplitude." The reason for this vagueness is that there are many ways to use the amplitude. However, no matter the method of using the amplitude, the quantum register is always **measured**. Measurement is the only way to obtain information from a quantum computer. +```{code-block} python +circuit.measure_all() +``` + +測定は量子レジスタの状態を「覗き見る」ような操作ですが、一回の測定操作で具体的に起きることは、各量子ビットに対して0もしくは1という値が得られるというだけです。つまり、量子状態が$2^n$個の計算基底の複雑な重ね合わせであったとしても、測定をすると一つの計算基底に対応するビット列が出てくるだけということになります。しかも、一度測定してしまった量子ビットはもう状態を変えてしまっていて、複雑な重ね合わせは失われてしまいます。 +Measurement is like "peeking" at the state of the quantum register, but what specifically happens in each measurement operation is simply obtaining a 0 or a 1 for each quantum bit. In other words, even if there is a computational basis with a complex superposition of 2n quantum states, when measurement is performed, a bit sequence that corresponds to a single computational basis is output. What's more, when a quantum bit is measured, its state changes, and it loses its complex superposition. + +ではこの「一つの計算基底」がどの基底なのかというと、実は特殊な場合を除いて決まっていません。全く同じ回路を繰り返し実行して測定すると、毎回ランダムにビット列が決まります。ただし、このランダムさには法則があって、**特定のビット列が得られる確率は、対応する計算基底の振幅の絶対値自乗**となっています。つまり、$n$ビットレジスタの状態$\sum_{j=0}^{2^n-1} c_j \ket{j}$があるとき、測定でビット列$k$が得られる確率は$|c_k|^2$です。根本的には、この確率の分布$|c_0|^2, |c_1|^2, \dots, |c_{2^n-1}|^2$こそが量子計算の結果です。 +So what basis state is this "one computational basis"? Actually, except in special cases, it isn't fixed. You can perform measurement repeatedly on the exact same circuit, and the bit sequence will be decided randomly each time. However, this randomness is constrained by rules, and **the percentage likelihood of obtaining a specific bit sequence is the square of the absolute value of the amplitude of the corresponding computational basis**. In other words, when the state of an $n$-bit register is $\sum_{j=0}^{2^n-1} c_j \ket{j}$, the likelihood of bit string $|c_k|^2$ being obtained is $|c_k |^2$. + ++++ + +### 量子計算結果の解析 + +回路の実行と測定を何度も繰り返して、それぞれのビット列が現れる頻度を記録すれば、だんだん$|c_j|^2$の値がわかっていきます。例えば、2量子ビットの回路を1000回実行・測定して、ビット列00、01、10、11がそれぞれ246回、300回、103回、351回得られたとすれば、統計誤差を考慮して$|c_0|^2=0.24 \pm 0.01$、$|c_1|^2=0.30 \pm 0.01$、$|c_2|^2=0.11 \pm 0.01$、$|c_3|^2=0.35 \pm 0.01$という具合です。しかし、わかるのは$c_j$の絶対値だけで、複素位相については知る術なしです。どうもすっきりしませんが、これが量子コンピュータから情報を得る方法です。 +Therefore, if you repeatedly run the circuit, perform measurements, and record the frequency with which each bit sequence occurs, you can gradually determine the value of $|c_j|^2$ and see the quantum state of the register. However, what you will have determined is only the absolute value of $c_j$. You will have no way of knowing the complex phase. That may be unsatisfying, but that's how you obtain information from quantum computers. + +逆に、指数関数的な内部の情報量をうまく使って計算を行いつつ、測定という限定的な方法でも答えが読み出せるように工夫するのが、量子アルゴリズム設計の真髄ということになります。例えば理想的には、何か計算の答えが整数$k$であり、それを計算する回路の終状態が単純に$\ket{k}$となるようであれば、一度の測定で答えがわかる(上でいった特殊な場合に相当)わけです。単純に$\ket{k}$でなくても、重ね合わせ$\sum_{j=0}^{2^n-1} c_j \ket{j}$において$|c_k| \gg |c_{j \neq k}|$を実現できれば、数回の測定で答えが高確率でわかります。{doc}`shor`で紹介する位相推定アルゴリズムはその好例です。 +Conversely, the true essence of quantum algorithm design lies in skillfully performing calculations using the exponential amount of internal information and using creative techniques to produce results through the limited method of measurement. For example, ideally, if the answer to a computation is the integer k, and the final state of a circuit used to perform that computation is simply |k⟩, then you will know the answer after a single measurement (except in the special cases mentioned above). Even if it is not simply |k⟩, if ∑_(j=0)^(2^((n-1) ))▒〖c_j |j⟩ 〗is possible with superposition |c_k |≫|c_(j≠k) |, the answer can be determined with a high likelihood after several measurements The phase estimation algorithm introduced in the "Learning about the prime factorization algorithm" section is a good example of this. + +一度の測定で答えがわかるケースを除いて、基本的には多数回の試行から確率分布を推定することになるので、量子回路を量子コンピュータの実機やシミュレータに送って実行させる時には必ず繰り返し数(「ショット数」と呼びます)を指定します。ショット数$S$でビット列$k$が$n_k$回得られた時、$|c_k|^2$の推定値は$z_k = n_k/S$、その統計誤差は($S, n_k, S-n_k$が全て十分大きい場合)$\sqrt{z_k (1-z_k) / S}$で与えられます。 + ++++ + +(common_gates)= +### よく使うゲート + +IBM Q System Oneのような超電導振動子を利用した量子コンピュータでは、実際に使用できるゲートは量子ビット1つにかかるものと2つにかかるものに限定されます。しかし、それらを十分な数組み合わせれば、$n$量子ビットレジスタにおいてどのような状態も実現できることが、数学的に証明されています。 +In quantum computers that use superconducting oscillators, like the IBM Q System One, the only gates that can be used are gates that apply to one quantum bit and gates that apply to two. However, it has been mathematically proven that, if these are sufficiently combined, they can create any state for an n-quantum bit register. + +#### 1量子ビットの操作 + +1量子ビットの操作でよく使われるゲートには、以下のようなものがあります。(表中コードの`i`, `j`は量子ビットの番号) +The following gates are often used with single quantum bit operations. (`i` and `j` in the code are the quantum bit number) + +```{list-table} +:header-rows: 1 +* - Gate name + - Explanation + - Qiskit code +* - $X$ + - Switches \ket{0} and \ket{1}. + - `circuit.x(i)` +* - $Z$ + - Multiplies the amplitude of \ket{1} by -1. + - `circuit.z(i)` +* - $H$(Hadamard gate) + - Applies the following transformation to each computational basis. + ```{math} + H\ket{0} = \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \\ + H\ket{1} = \frac{1}{\sqrt{2}} (\ket{0} - \ket{1}) + ``` + (「量子状態にゲートを作用させる」ことをケットの記法で書くときは、ゲートに対応する記号をケットに左からかけます。)
+ 例えば状態$\ket{\psi} = \alpha\ket{0} + \beta\ket{1}$に対しては、 + (When using ket notation to indicate that a gate is applied to a quantum state, the symbol of the gate is written to the left of the ket.) + For example, $\ket{\psi} = \alpha\ket{0} + \beta\ket{1}$, it would be as follows. + + ```{math} + \begin{align} + H\ket{\psi} & = \alpha \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) + \beta \frac{1}{\sqrt{2}} (\ket{0} - \ket{1}) \\ + & = \frac{1}{\sqrt{2}} (\alpha + \beta) \ket{0} + \frac{1}{\sqrt{2}} (\alpha - \beta) \ket{1} + \end{align} + ``` + となる。 + - `circuit.h(i)` +* - $R_{y}$ + - Takes parameter $\theta$ and applies the following transformation to each computational basis. + ```{math} + R_{y}(\theta)\ket{0} = \cos\frac{\theta}{2}\ket{0} + \sin\frac{\theta}{2}\ket{1} \\ + R_{y}(\theta)\ket{1} = -\sin\frac{\theta}{2}\ket{0} + \cos\frac{\theta}{2}\ket{1} + ``` + - `circuit.ry(theta, i)` +* - $R_{z}$ + - Takes parameter $\phi$ and applies the following transformation to each computational basis. + ```{math} + R_{z}(\phi)\ket{0} = e^{-i\phi/2}\ket{0} \\ + R_{z}(\phi)\ket{1} = e^{i\phi/2}\ket{1} + - `circuit.rz(phi, i)` +``` + +それでは、2量子ビットレジスタの第0ビットに$H, R_y, X$の順にゲートをかけて、最後に測定をする回路をQiskitで書いてみましょう。 +Let's create a circuit with Qiskit that applies $H$, $R_y$, and $X$ gates to the 0th bit of a 2-quantum bit register, in that order, and then measures it. + +```{code-cell} ipython3 +:tags: [remove-output] + +# First, import all the necessary python modules +import numpy as np +import matplotlib.pyplot as plt +from qiskit import QuantumCircuit, transpile +from qiskit.providers.ibmq import least_busy +from qiskit.tools.monitor import job_monitor +from qiskit.visualization import plot_histogram +from qiskit_ibm_provider import IBMProvider +from qiskit_ibm_provider.accounts import AccountNotFoundError +# qc_workbookはこのワークブック独自のモジュール(インポートエラーが出る場合はPYTHONPATHを設定するか、sys.pathをいじってください) +from qc_workbook.utils import operational_backend + +print('notebook ready') +``` + +```{code-cell} ipython3 +circuit = QuantumCircuit(2) # You can also create a circuit by specifying the number of bits, without using a register +circuit.h(0) # In that case, directly specify the number of the quantum bit for the gate, not register[0] +circuit.ry(np.pi / 2., 0) # θ = π/2 +circuit.x(0) +# 実際の回路では出力を得るためには必ず最後に測定を行う +circuit.measure_all() + +print(f'This circuit has {circuit.num_qubits} qubits and {circuit.size()} operations') +``` + +最後のプリント文で、ゲートが3つなのにも関わらず "5 operations" と出てくるのは、各量子ビットの測定も一つのオペレーションとして数えられるからです。 +The reason the last print statement says "5 operations" despite there only being three gates is that each quantum bit measurement is also counted as an operation. + +量子計算に慣れる目的で、この$H, R_y(\pi/2), X$という順の操作で第0ビットに何が起こるかを辿ってみましょう。初期状態は$\ket{0}$で、ケット記法では操作は左からかけていく(ゲート操作が右から順に並ぶ)ので、$X R_y(\pi/2) H \ket{0}$を計算することになります。 +Let's look, in order, at what happens to the 0th bit through these $H$, $R_y(\pi/2)$, $X$ operations in order to become more accustomed to quantum calculation. From the default \ket{0} state, the gates are applied from the left, according to gate notation, so $X R_y(\pi/2) H \ket{0}$ is calculated. + +$$ +\begin{align} +X R_y\left(\frac{\pi}{2}\right) H \ket{0} & = X R_y\left(\frac{\pi}{2}\right) \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \\ +& = \frac{1}{\sqrt{2}} X \left[\left(\cos\left(\frac{\pi}{4}\right)\ket{0} + \sin\left(\frac{\pi}{4}\right)\ket{1}\right) + \left(-\sin\left(\frac{\pi}{4}\right)\ket{0} + \cos\left(\frac{\pi}{4}\right)\ket{1}\right)\right] \\ +& = \frac{1}{\sqrt{2}} X \frac{1}{\sqrt{2}} \left[\left(\ket{0} + \ket{1}\right) + \left(-\ket{0} + \ket{1}\right)\right] \\ +& = X \ket{1} \\ +& = \ket{0} +\end{align} +$$ + +なので、結局$\ket{0}$状態に戻る操作でした。 +These operations eventually take us back to the $\ket{0}$ state. + +#### 2量子ビットの操作 + +2量子ビットの操作は、量子ビットの超電導素子での実装の都合上、全て「制御ゲート」(controlled gates)という方式で行われます。この方式では、2つのビットのうち片方を制御(control)、もう片方を標的(target)として、制御ビットが1の時だけ標的ビットに何らかの操作がかかります。 +Operations on 2-quantum bits are always performed using controlled gates for reasons related to how quantum bits are implemented using superconducting elements. With this method, one of the two bits is called the control and the other is called the target. An operation is performed on the target bit only when the control bit's value is 1. + +例として、任意の1ビットゲート$U$を制御ゲート化した$C^i_j[U]$を考えます。ここで$i$が制御、$j$が標的ビットとします。ケットの添字でビットの番号を表して(reminder: 並べて書くときは右から順に番号を振ります) +For example, consider a 1-bit gate, U, as $C^i_j[U]$, a controlled gate. Here, $i$ is the control bit and $j$ is the target bit. Representing the bit number with a gate subscript produces the following. (Reminder: when writing in a line, numbers are assigned in order starting from the right.) + +$$ +\begin{align} +C^1_0[U](\ket{0}_1\ket{0}_0) & = \ket{0}_1\ket{0}_0 \\ +C^1_0[U](\ket{0}_1\ket{1}_0) & = \ket{0}_1\ket{1}_0 \\ +C^1_0[U](\ket{1}_1\ket{0}_0) & = \ket{1}_1U\ket{0}_0 \\ +C^1_0[U](\ket{1}_1\ket{1}_0) & = \ket{1}_1U\ket{1}_0 +\end{align} +$$ + +です。 + +上で紹介した頻出する1ビットゲート$X, Z, H, R_y, R_z$のうち、$H$以外は制御ゲート化バージョンもよく使われます。特に$C[X]$はCXやCNOTとも呼ばれ、量子計算の基本要素として多様されます。実際、全ての2量子ビット制御ゲートはCNOTと1量子ビットゲートの組み合わせに分解できます。 +Of the $X$, $Z$, $H$, $R_y$, and $R_z$ frequently used 1-bit gates introduced above, all other than the $H$ gate also have frequently used controlled gate versions. $C[X]$, also known as CX and CNOT, is particularly often used as a basic element in quantum calculation. In fact, all 2-quantum bit controlled gates can be broken down into combinations of CNOT gates and 1-quantum bit gates. + +```{list-table} +:header-rows: 1 +* - Gate name + - Explanation + - Qiskit code +* - $C^i_j[X]$, CX, CNOT + - Performs the operation of gate $X$ on bit $j$ for a computational basis in which the value of bit $i$ is 1. + - `circuit.cx(i, j)` +* - $C^i_j[Z]$ + - Reverses the sign of the computational basis when the values of bits $i$ and $j$ are 1. + - `circuit.cz(i, j)` +* - $C^i_j[R_{y}]$ + - Obtains parameter $\theta$ and performs the operation of gate $R_y$ on bit $j$ for a computational basis in which the value of bit $i$ is 1. + - `circuit.cry(theta, i, j)` +* - $C^i_j[R_{z}]$ + - Obtains parameter $\phi$ and performs the operation of gate $R_z$ on bit $j$ for a computational basis in which the value of bit $i$ is 1. + - `circuit.crz(phi, i, j)` +``` + +Qiskitで2ビットレジスタに制御ゲートを用い、計算基底$\ket{0}, \ket{1}, \ket{2}, \ket{3}$の振幅の絶対値自乗が$1:2:3:4$の比になるような状態を作ってみましょう。さらに$C^0_1[Z]$ゲートを使って$\ket{3}$だけ振幅の符号が他と異なるようにします。 +Let's use Qiskit to apply a controlled gate to a 2-bit register and make the squared absolute value of the amplitudes of computational bases $\ket{0}$, $\ket{1}$, $\ket{2}, and $\ket{3}$ have the ratio $1:2:3:4$. Furthermore, let's use a $C_1^0[Z]$ gate to make the sign of the amplitude of $\ket{3}$ alone different than the others. + +```{code-cell} ipython3 +theta1 = 2. * np.arctan(np.sqrt(7. / 3.)) +theta2 = 2. * np.arctan(np.sqrt(2.)) +theta3 = 2. * np.arctan(np.sqrt(4. / 3)) + +circuit = QuantumCircuit(2) +circuit.ry(theta1, 1) +circuit.ry(theta2, 0) +circuit.cry(theta3 - theta2, 1, 0) # C[Ry] 1 is the control and 0 is the target +circuit.cz(0, 1) # C[Z] 0 is the control and 1 is the target (in reality, for C[Z], the results are the same regardless of which the control is) + +circuit.measure_all() + +print(f'This circuit has {circuit.num_qubits} qubits and {circuit.size()} operations') +``` + +This is a little complex, but let's follow the computation steps in order. First, given the definitions of angles $θ_1$,$θ_2$, and $θ_3$, the following relationships are satisfied. + +$$ +\begin{align} +R_y(\theta_1)\ket{0} & = \sqrt{\frac{3}{10}} \ket{0} + \sqrt{\frac{7}{10}} \ket{1} \\ +R_y(\theta_2)\ket{0} & = \sqrt{\frac{1}{3}} \ket{0} + \sqrt{\frac{2}{3}} \ket{1} \\ +R_y(\theta_3 - \theta_2)R_y(\theta_2)\ket{0} & = R_y(\theta_3)\ket{0} = \sqrt{\frac{3}{7}} \ket{0} + \sqrt{\frac{4}{7}} \ket{1}. +\end{align} +$$ + +したがって、 +Therefore: + +$$ +\begin{align} +& C^1_0[R_y(\theta_3 - \theta_2)]R_{y1}(\theta_1)R_{y0}(\theta_2)\ket{0}_1\ket{0}_0 \\ += & C^1_0[R_y(\theta_3 - \theta_2)]\left(\sqrt{\frac{3}{10}} \ket{0}_1 + \sqrt{\frac{7}{10}} \ket{1}_1\right) R_y(\theta_2)\ket{0}_0\\ += & \sqrt{\frac{3}{10}} \ket{0}_1 R_y(\theta_2)\ket{0}_0 + \sqrt{\frac{7}{10}} \ket{1}_1 R_y(\theta_3)\ket{0}_0 \\ += & \sqrt{\frac{3}{10}} \ket{0}_1 \left(\sqrt{\frac{1}{3}} \ket{0}_0 + \sqrt{\frac{2}{3}} \ket{1}_0\right) + \sqrt{\frac{7}{10}} \ket{1}_1 \left(\sqrt{\frac{3}{7}} \ket{0}_0 + \sqrt{\frac{4}{7}} \ket{1}_0\right) \\ += & \sqrt{\frac{1}{10}} \ket{00} + \sqrt{\frac{2}{10}} \ket{01} + \sqrt{\frac{3}{10}} \ket{10} + \sqrt{\frac{4}{10}} \ket{11} +\end{align} +$$ + +最初の行で、ビット0と1にかかる$R_y$ゲートをそれぞれ$R_{y0}, R_{y1}$と表しました。 +On the first line, the $R_y$ gates that are applied to bits 0 and 1 are denoted as $R_{y0}$ and $R_{y1}$. + +最後に$C[Z]$をかけると、$\ket{11}$だけ符号が反転します。 +When $C[Z]$ is applied at the end, only the sign of $\ket{11}$ is reversed. + ++++ + +### 回路図の描き方と読み方 + +量子回路を可視化する方法として、「回路図」の標準的な描き方が決まっています。Qiskitでは`QuantumCircuit`オブジェクトの`draw()`というメソッドを使って自動描画できます。 +There is a standard way of drawing the circuit diagrams that are used to visualize quantum circuits. With Qiskit, you can use the QuantumCircuit object draw() method to automatically draw circuit diagrams. + +```{code-cell} ipython3 +circuit.draw('mpl') +``` + + ここで`draw()`の引数`'mpl'`はmatplotlibライブラリを使ってカラーで描くことを指定しています。実行環境によっては対応していないこともあるので、その場合は引数なしの`draw()`を使います。結果は`mpl`の場合に比べて見劣りしますが、内容は同じです。 + Here, the `draw()` argument `'mpl'` is used to draw the circuit diagram in color, using the matplotlib library. Some operating environments may not support this. In those cases, use `draw()` by itself, with no argument. The result will not be as visually appealing as the circuit diagrams produced by `mpl`, but the content will be the same. +```{code-cell} ipython3 +circuit.draw() +``` + +回路図は左から右に読んでいきます。水平の2本の実線が上からそれぞれ第0、第1量子ビットに対応し、その上にかぶさっている四角がゲート、最後にある矢印が下に伸びている箱が測定を表します。1ビットゲートから伸びている先端の丸い縦線は制御を表します。一番下の二重線は「古典レジスタ」(量子現象のない物理学を「古典物理学」と呼ぶので、量子でない通常のコンピュータにまつわる概念にはよく「古典 classical」という接頭辞をつけます)に対応し、測定結果の0/1が記録される部分です。 +Circuit diagrams are read from left to right. The two horizontal solid lines represent, from top to bottom, quantum bits 0 and 1. The squares on top of the lines are gates. The boxes at the end, with arrows extending downwards, represent measurements. The vertical lines with circles at their ends extending from the 1 bit gate represent control. The double line at the very bottom corresponds to the "classical register," and is the portion where measurement results of 0 or 1 are recorded. + ++++ + +## CHSH不等式を計算する回路を書く + +それではいよいよ本題に入りましょう。CHSH不等式を「ベル状態」$1/\sqrt{2}(\ket{00} + \ket{11})$で検証します。ベル状態は「どちらの量子ビットについても$\ket{0}$でも$\ket{1}$でもない状態」つまり、全体としては一つの定まった(純粋)状態であるにも関わらず、部分を見ると純粋でない状態です。このような時、**二つの量子ビットはエンタングルしている**といいます。エンタングルメントの存在は量子力学の非常に重要な特徴です。 +Let us now get to the main part of this exercise. The CHSH inequality is an inequality involving four observables of a two-body system, so we will prepare four 2-bit circuits. Each will represent the Bell state $1/\sqrt{2}(\ket{00} + \ket{11})$. The Bell state is one in which "the states of both of the quantum bits are neither $\ket{0}$ nor $\ket{1}$." In other words, despite the fact that the overall state is pure, its individual parts are not. In situations such as this, we say that the two quantum bits are entangled. The existence of entanglement is an extremely important feature of quantum mechanics. + +ベル状態はアダマールゲートとCNOTゲートを組み合わせて作ります。詳しい説明は{doc}`課題 `に譲りますが、CHSH不等式の検証用の観測量を作るために、4つの回路I, II, III, IVを使います。回路IとIIIでは量子ビット1に対し測定の直前に$R_y(-\pi/4)$、IIとIVでは同様に$R_y(-3\pi/4)$を作用させます。また回路IIIとIVでは量子ビット0に$R_y(-\pi/2)$を同じく測定の直前に作用させます。4つの回路を一度にIBMQに送るので、`circuits`というリストに回路を足していきます。 +Let's create a Bell state by combining Hadamard gates and CNOT gates. We will create four circuits, so we will use a loop to add circuits to the circuits array. + +```{code-cell} ipython3 +circuits = [] + +# 回路I - H, CX[0, 1], Ry(-π/4)[1]をかける +circuit = QuantumCircuit(2, name='circuit_I') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-np.pi / 4., 1) +circuit.measure_all() +# 回路リストに追加 +circuits.append(circuit) + +# 回路II - H, CX[0, 1], Ry(-3π/4)[1]をかける +circuit = QuantumCircuit(2, name='circuit_II') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-3. * np.pi / 4., 1) +circuit.measure_all() +# 回路リストに追加 +circuits.append(circuit) + +# 回路III - H, CX[0, 1], Ry(-π/4)[1], Ry(-π/2)[0]をかける +circuit = QuantumCircuit(2, name='circuit_III') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-np.pi / 4., 1) +circuit.ry(-np.pi / 2., 0) +circuit.measure_all() +# 回路リストに追加 +circuits.append(circuit) + +# 回路IV - H, CX[0, 1], Ry(-3π/4)[1], Ry(-π/2)[0]をかける +circuit = QuantumCircuit(2, name='circuit_IV') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-3. * np.pi / 4., 1) +circuit.ry(-np.pi / 2., 0) +circuit.measure_all() +# 回路リストに追加 +circuits.append(circuit) + +# draw()にmatplotlibのaxesオブジェクトを渡すと、そこに描画してくれる +# 一つのノートブックセルで複数プロットしたい時などに便利 +fig, axs = plt.subplots(2, 2, figsize=[12., 6.]) +for circuit, ax in zip(circuits, axs.reshape(-1)): + circuit.draw('mpl', ax=ax) + ax.set_title(circuit.name) +``` + +それぞれの回路で2ビットレジスタの基底$\ket{00}, \ket{01}, \ket{10}, \ket{11}$が現れる確率を計算してみましょう。 +Let's calculate the likelihood of basis states $\ket{00}$, $\ket{01}$, $\ket{10}$, and $\ket{11}$ appearing in the 2-bit register of each circuit. Circuit 0's state is as follows: + +回路Iの状態は +The state for circuit 1 is as follows: + +$$ +\begin{align} +R_{y1}\left(-\frac{\pi}{4}\right) C^0_1[X] H_0 \ket{0}_1\ket{0}_0 = & R_{y1}\left(-\frac{\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ += & \frac{1}{\sqrt{2}} \big[(c\ket{0}_1 - s\ket{1}_1)\ket{0}_0 + (s\ket{0}_1 + c\ket{1}_1)\ket{1}_0\big]\\ += & \frac{1}{\sqrt{2}} (c\ket{00} + s\ket{01} - s\ket{10} + c\ket{11}). +\end{align} +$$ + +簡単のため$c = \cos(\pi/8), s = \sin(\pi/8)$とおきました。 +For simplicity's sake, we have set $c = \cos(\pi/8)$ and $s = \sin(\pi/8)$. + +したがって回路Iでの確率$P^{\rmI}_{l} \, (l=00,01,10,11)$は +Therefore probability $P^{\rmI}_{l} \, (l=00,01,10,11)$ for circuit I is as follows: + +$$ +P^{\rmI}_{00} = P^{\rmI}_{11} = \frac{c^2}{2} \\ +P^{\rmI}_{01} = P^{\rmI}_{10} = \frac{s^2}{2} +$$ + +同様に、回路IIの状態は +Likewise, the state for circuit II is as follows: + +```{math} +:label: eqn-circuit1 +R_{y1}\left(-\frac{3\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) = \frac{1}{\sqrt{2}} (s\ket{00} + c\ket{01} - c\ket{10} + s\ket{11}) +``` + +で確率$P^{\rmII}_{l}$は +Therefore probability $P^{\rmII}_{l}$ is: + +$$ +P^{\rmII}_{00} = P^{\rmII}_{11} = \frac{s^2}{2} \\ +P^{\rmII}_{01} = P^{\rmII}_{10} = \frac{c^2}{2} +$$ + +です。回路IIIの状態は +Circuit III's state is as follows: + +$$ +\begin{align} +& R_{y1}\left(-\frac{\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ += & \frac{1}{\sqrt{2}} \left[ \frac{1}{\sqrt{2}} (c\ket{0}_1 - s\ket{1}_1) (\ket{0}_0 - \ket{1}_0) + \frac{1}{\sqrt{2}} (s\ket{0}_1 + c\ket{1}_1) (\ket{0}_0 + \ket{1}_0) \right] \\ += & \frac{1}{2} \big[ (s+c)\ket{00} + (s-c)\ket{01} - (s-c)\ket{10} + (s+c)\ket{11} \big] +\end{align} +$$ + +で確率$P^{\rmIII}_{l}$は +Probability $P^{\rmIII}_{l}$ is: + +$$ +P^{\rmIII}_{00} = P^{\rmIII}_{11} = \frac{(s + c)^2}{4} \\ +P^{\rmIII}_{01} = P^{\rmIII}_{10} = \frac{(s - c)^2}{4} +$$ + +同様に回路IVの状態と確率$P^{\rmIV}_l$は +Likewise, the state and probability $P^{\rmIV}_l$ of circuit IV are: + +$$ +\begin{align} +& R_{y1}\left(-\frac{3\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ += & \frac{1}{2} \big[ (s+c)\ket{00} - (s-c)\ket{01} + (s-c)\ket{10} + (s+c)\ket{11} \big] +\end{align} +$$ + +$$ +P^{\rmIV}_{00} = P^{\rmIV}_{11} = \frac{(s + c)^2}{4} \\ +P^{\rmIV}_{01} = P^{\rmIV}_{10} = \frac{(s - c)^2}{4} +$$ + +となります。 + +それぞれの回路でビット0と1で同じ値が観測される確率$P^{i}_{00} + P^{i}_{11}$から異なる値が観測される確率$P^{i}_{01} + P^{i}_{10}$を引いた値を$C^{i}$と定義します。 +The probability $P^{i}_{00} + P^{i}_{11}$ of the same value being observed for bits 0 and 1 on each circuit minus the probability $P^{i}_{01} + P^{i}_{10}$ of different values being observed is defined as $C^i$. + +$$ +C^{\rmI} = c^2 - s^2 = \cos\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\ +C^{\rmII} = s^2 - c^2 = -\frac{1}{\sqrt{2}} \\ +C^{\rmIII} = 2sc = \sin\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\ +C^{\rmIV} = 2sc = \frac{1}{\sqrt{2}} +$$ + +なので、これらの組み合わせ$S = C^{\rmI} - C^{\rmII} + C^{\rmIII} + C^{\rmIV}$の値は$2\sqrt{2}$です。 +Therefore, combining these, the value of $S = C^{\rmI} - C^{\rmII} + C^{\rmIII} + C^{\rmIV}$ is $2\sqrt{2}$. + +実は、エンタングルメントが起こらない場合、この観測量$S$の値は2を超えられないことが知られています。例えば$R_y$ゲートをかける前の状態がベル状態ではなく、確率$\frac{1}{2}$で$\ket{00}$、確率$\frac{1}{2}$で$\ket{11}$という「混合状態」である場合、 +Actually, if entanglement does not occur, the value of this observable $S$ is known to not exceed 2. For example, if the state before the $R_y$ gate is applied is not a Bell state, there is a $\frac{1}{2}$ probability that the value is $\ket{00}$ and a $\frac{1}{2}$ probability that it is $\ket{11}$, a mixed state. + +$$ +C^{\rmI} = \frac{1}{\sqrt{2}} \\ +C^{\rmII} = -\frac{1}{\sqrt{2}} \\ +C^{\rmIII} = 0 \\ +C^{\rmIV} = 0 +$$ + +となり、$S = \sqrt{2} < 2$です。これがCHSH不等式です。 +Therefore, $S = \sqrt{2} < 2$. This is the CHSH inequality. + +それでは、IBMQの「量子コンピュータ」が実際にエンタングル状態を生成できるのか、上の四つの回路から$S$の値を計算して確認してみましょう。 +So let's check if the IBMQ "quantum computer" really generates entangled states by using the four circuits above to calculate the value of $S$. + ++++ + +## 回路を実機で実行する + +まずはIBMQに認証・接続します。IBM Quantum Experience (IBM Quantumウェブサイト上のJupyter Lab)で実行している、もしくは自分のラップトップなどローカルの環境ですでに{ref}`認証設定が保存されている `場合は`provider = IBMProvider()`で接続ができます。設定がない場合は`IBMProvider`のコンストラクタに{ref}`トークン `を渡してIBMQに接続します。 + +```{code-cell} ipython3 +:tags: [remove-output, raises-exception] + +# 利用できるインスタンスが複数ある場合(Premium accessなど)はここで指定する +# instance = 'hub-x/group-y/project-z' +instance = None + +try: + provider = IBMProvider(instance=instance) +except AccountNotFoundError: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) +``` + +認証が済んだら、利用する量子コンピュータ(「バックエンド」と呼びます)を選びます。 +Once authentication has been completed, choose the quantum computer you wish to use (called a "backend"). + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# バックエンド(実機)のうち量子ビット数2個以上のもののリストをプロバイダから取得する +# A list of backends (actual devices) with 2 or more quantum bits is acquired from the provider +# operational_backendはこのワークブック用にqc_workbook.utilsで定義された関数 +backend_list = provider.backends(filters=operational_backend(min_qubits=2)) + +# リストの中から一番空いているものを選ぶ +# The one with the highest availability is selected +backend = least_busy(backend_list) + +print(f'Jobs will run on {backend.name()}') +``` + +回路をバックエンドに送るには、`transpile`という関数とバックエンドの`run`というメソッドを使います。`transpile`については次回{ref}`transpilation`で説明するので、今は「おまじない」だと思ってください。`run`で回路を送るとき、前述したように同時にショット数を指定します。バックエンドごとに一度のジョブでの最大ショット数が決められており、8192、30000、100000などとさまざまです。回路をバックエンドに渡し、`shots`回実行させることをジョブと呼びます。 +Use the execute function to send a circuit to the backend. Use the shots argument to specify how many times the circuit is to be run and measured. The maximum number of shots per job performed on each backend is defined. In most cases, it is 8192 (=213). + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# バックエンドごとに決められている最大ショット数 +shots = backend.configuration().max_shots +print(f'Running four circuits, {shots} shots each') + +# transpileの説明は次回の実習にて +circuits = transpile(circuits, backend=backend) +# バックエンドで回路をshots回実行させ、測定結果を返させる +job = backend.run(circuits, shots=shots) + +# ジョブが終了するまで状態を表示しながら待つ(正常に完了、エラーで停止、など終了する理由は一つではない) +job_monitor(job, interval=2) +``` + +これで回路がバックエンドに送られ、キューに入りました。ジョブの実行結果は`run`メソッドの返り値であるジョブオブジェクトから参照します。 +This will send the circuits to the backend as a job, which is added to the queue. The job execution results is checked using the job object, which is the return value of the execute function. + +IBMQのバックエンドは世界中からたくさんのユーザーに利用されているため、場合によっては予約されているジョブが多数あってキューにかなりの待ち時間が生じることがあります。 +IBMQ backends are used by many users around the world, so in some cases there may be many jobs in the queue and it may take a long time for your job to be executed. + +バックエンドごとのキューの長さはIBM Quantumのバックエンド一覧ページから確認できます。バックエンドを一つクリックすると詳細が表示され、現在の全ジョブ数が Total pending jobs として表示されます。また、一番下の Your access providers という欄でバックエンドのジョブあたりの最大ショット数と最大回路数を確認できます。 +The length of the queue for each backend can be seen at right on the IBM Quantum Experience website. Clicking one of the backends in the column at right will display details about the backend. In the bottommost field, "Your access providers," you can see the maximum number of shots and the maximum number of jobs for the backend. + +また、自分の投じたジョブのステータスはジョブ一覧ページから確認できます。 +Use the "job list page" to see the status of jobs you have submitted. + +Qiskitプログラム中からもジョブのステータスを確認できます。いくつか方法がありますが、シンプルに一つのジョブをテキストベースでモニターするだけなら上のように`job_monitor`を使います。 +You can check the status of your jobs from within the Qiskit program. There are several methods for doing so. If you simply want to monitor the status of a single job, in text form, you can use `job_monitor`. + ++++ + +## 量子測定結果の解析 + +ジョブオブジェクトの`result()`というメソッドを呼ぶと、ジョブが完了して結果が帰ってくるまでコードの実行が止まります。実行結果はオブジェクトとして返され、それの`get_counts`というメソッドを使うと、各ビット列が何回観測されたかというヒストグラムデータがPythonのdictとして得られます。 +Calling the `result()` method for a job object will stop the execution of code until the job is complete and a result arrives. The execution results will be returned as an object. Use the `get_counts method` on this object to obtain histogram data on how many times each bit sequence was observed in the form of a Python dict. + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +result = job.result() + +# 4つの回路のヒストグラムデータを入れるリスト +counts_list = [] + +# 回路ごとの結果をresultから抽出する +for idx in range(4): + # get_counts(i)で回路iのヒストグラムデータが得られる + counts = result.get_counts(idx) + # データをリストに足す + counts_list.append(counts) + +print(counts_list) +``` + +```{code-cell} ipython3 +:tags: [remove-cell] + +# テキスト作成用のダミーセルなので無視してよい +try: + counts_list +except NameError: + counts_list = [ + {'00': 3339, '01': 720, '10': 863, '11': 3270}, + {'00': 964, '01': 3332, '10': 3284, '11': 612}, + {'00': 3414, '01': 693, '10': 953, '11': 3132}, + {'00': 3661, '01': 725, '10': 768, '11': 3038} + ] + + shots = 8192 +``` + +````{tip} +ノートブックの接続が切れてしまったり、過去に走らせたジョブの結果を再び解析したくなったりした場合は、ジョブIDを使って`retrieve_job`というメソッドでジョブオブジェクトを再構成することができます。過去に走らせたジョブはIBM Quantumのホームページにリストされているので、そこにあるジョブID(cgr3kaemln50ss91pj10のような)をコピーし、 + +```{code-block} python +backend = provider.get_backend('__backend_you_used__') +job = backend.retrieve_job('__job_id__') +``` + +とすると、`backend.run`によって返されたのと同じようにジョブオブジェクトが生成されます。 +```` + +Qiskitから提供されている`plot_histogram`関数を使って、この情報を可視化できます。プロットの縦軸は観測回数を全測定数で割って、観測確率に規格化してあります。 +This information can be visualized using Qiskit's `plot_histogram` function. The vertical axis of the histogram is a standardized representation of the observation probability, determined by dividing the number of observations by the total number of measurements. + +```{code-cell} ipython3 +fig, axs = plt.subplots(2, 2, sharey=True, figsize=[12., 8.]) +for counts, circuit, ax in zip(counts_list, circuits, axs.reshape(-1)): + plot_histogram(counts, ax=ax) + ax.set_title(circuit.name) + ax.yaxis.grid(True) +``` + +$c^2/2 = (s + c)^2/4 = 0.427$, $s^2/2 = (s - c)^2 / 4 = 0.073$なので、得られた確率は当たらずとも遠からずというところでしょうか。 +$c^2/2 = (s + c)^2/4 = 0.427$, $s^2/2 = (s - c)^2 / 4 = 0.073$, so the probability was fairly close to the mark. + +実は現在の量子コンピュータにはまだ様々なノイズやエラーがあり、計算結果は往々にして理論的な値から統計誤差の範囲を超えてずれます。特定のエラーに関しては多少の緩和法も存在しますが、全て防げるわけでは決してありません。現在の量子コンピュータを指して "*Noisy* intermediate-scale quantum (NISQ) device" と呼んだりしますが、このNoisyの部分はこのような簡単な実験でもすでに顕著に現れるわけです。 +The reality is that modern quantum computers still have various types of noise and errors, so calculation results sometimes deviate from theoretical values by amounts that exceed the bounds of statistical error. There are methods for mitigating specific errors, to some degree, but there is no way to prevent all errors. Modern quantum computers are known as "Noisy intermediate-scale quantum (NISQ) devices." This "noisy" aspect is very evident even in simple experiments like this. + +逆に、NISQデバイスを有効活用するには、ノイズやエラーがあっても意味のある結果が得られるようなロバストな回路が求められます。{doc}`vqe`で紹介する変分量子回路を用いた最適化などがその候補として注目されています。 +To use NISQ devices effectively requires robust circuits that produce meaningful results despite noise and errors. A great deal of attention is being turned to methods for achieving this, such as optimization using the variational quantum circuits introduced in the "Learning about the variational method and variational quantum eigensolver method" section. + +さて、それでは最後にCHSH不等式の破れを確認してみましょう。$C^{\rmI}, C^{\rmII}, C^{\rmIII}, C^{\rmIV}$を計算して$S$を求めます。 +Let us finish this section by confirming that the CHSH inequality has been violated. Let us determine $S$ by calculating $C^{\rmI}$, $C^{\rmII}$, $C^{\rmIII}$, and $C^{\rmIV}$. + +下のコードで`counts`という辞書オブジェクトからキー`'00'`などに対応する値を取り出す際に`counts['00']`ではなく`counts.get('00', 0)`としています。二つの表現は`counts`に`'00'`というキーが定義されていれば全く同義ですが、キーが定義されていないときは、前者の場合エラーとして実行が止まるのに対して、後者ではデフォルト値として2個目の引数で指定されている`0`が返ってきます。qiskitの結果データは一度も測定されなかったビット列についてキーを持たないので、常に`get`でカウント数を抽出するようにしましょう。 + +```{code-cell} ipython3 +# C^I, C^II, C^III, C^IVを一つのアレイにする +#(今の場合ただのリストにしてもいいが、純粋な数字の羅列にはnumpy arrayを使うといいことが多い) +C = np.zeros(4, dtype=float) + +# enumerate(L)でリストのインデックスと対応する要素に関するループを回せる +for ic, counts in enumerate(counts_list): + # counts['00'] でなく counts.get('00', 0) - 上のテキストを参照 + C[ic] = counts.get('00', 0) + counts.get('11', 0) - counts.get('01', 0) - counts.get('10', 0) + +# 4つの要素を同時にshotsで規格化(リストではこういうことはできない) +C /= shots + +S = C[0] - C[1] + C[2] + C[3] + +print('C:', C) +print('S =', S) +if S > 2.: + print('Yes, we are using a quantum computer!') +else: + print('Armonk, we have a problem.') +``` + +無事、$S$が2を超えました。 +$S$ was, indeed, greater than 2. \ No newline at end of file diff --git a/source/en/circuit_from_scratch.md b/source/en/circuit_from_scratch.md new file mode 100644 index 00000000..7ebb814b --- /dev/null +++ b/source/en/circuit_from_scratch.md @@ -0,0 +1,1113 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 単純な量子回路をゼロから書く + ++++ + +{doc}`chsh_inequality`で、量子回路の基本的な書き方と実機での実行の仕方を学びました。そこで出てきたように、QCでは量子レジスタに特定の量子状態を実現することが計算を行うこととなります。そこで今回は、基本的なゲートを組み合わせて様々な量子状態を作る実習を行います。 +In the {doc}`chsh_inequality` section, you learned about the basic notation used to draw quantum circuits and how to implement circuits in an actual QC. As discussed in that section, calculations are performed by reproducing a specific quantum state in a quantum register in the QC. In this section, you will combine basic gates to form various quantum states. + +```{contents} 目次 +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ +$\newcommand{\braket}[2]{\langle #1 | #2 \rangle}$ + ++++ + +## 状態ベクトルシミュレータで量子状態を調べる + +第一回の課題で利用したQCシミュレータ`AerSimulator`は、デフォルトの設定では実機と同様に量子回路の測定結果をヒストグラムとして返します。測定結果を返すということは、その出力からは量子状態の複素位相を含めた振幅は読み取れません。今回は作った量子状態をより詳細に調べるために、`AerSimulator`の状態ベクトルシミュレーション機能を利用します。状態ベクトルシミュレーションは、回路の終状態におけるすべての計算基底の確率振幅、つまりその量子状態に関する最も完全な情報を返します。 +The QC simulator you used in the first assignment, `AerSimulator`, returns quantum circuit measurement results in the form of a histogram, in the same way as an actual QC. Because it returns measurement results, you cannot use the output to read the amplitude, including the complex phase of the quantum state. In this section, in order to explore the quantum state in more detail, we will use state vector simulator of `AerSimulator`. The state vector simulator returns all computational basis probability amplitudes from the final state of the circuit. In other words, it returns the most complete information about the quantum state. + +```{code-cell} ipython3 +:tags: [remove-output] + +# まずは全てインポート +# First, import everything +import numpy as np +import matplotlib.pyplot as plt +from IPython.display import Math +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile +from qiskit_aer import AerSimulator +# qc_workbookはこのワークブック独自のモジュール(インポートエラーが出る場合はPYTHONPATHを設定するか、sys.pathをいじってください) +# qc_workbook is a module unique to this workbook (if you receive an import error, configure PYTHONPATH or modify sys.path) +from qc_workbook.show_state import statevector_expr + +print('notebook ready') +``` + +```{code-cell} ipython3 +simulator = AerSimulator(method='statevector') +print(simulator.name()) +``` + +シミュレートする回路を作ります。ここでは例として前半で登場した回路1を使います。測定以外のゲートをかけていきます。 +Let us create the circuit to be simulated. For this example, we will use circuit 1 from the first half of the section. We will then add gates other than measurement gates. + +```{code-cell} ipython3 +circuit = QuantumCircuit(2) +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-3. * np.pi / 4., 1) + +circuit.draw('mpl') +``` + +```{caution} +回路に測定操作(`measure_all()`)を加えてしまうと、状態ベクトルシミュレータを利用して測定前の回路の量子状態を確認することができなくなります。 +If you add a measurement operation (measure_all()) to the circuit, you will become unable to confirm the quantum state of the circuit before performing measurement using statevector_simulator. +``` + +状態ベクトルシミュレーションを実行する際も`transpile`とバックエンドの`run`関数を使い、ジョブオブジェクトから結果を得ます。ただし、今回は計算結果からカウントではなく状態ベクトル(=量子振幅の配列)を得るので`get_counts`ではなく`result.data()['statevector']`を参照します。 +State vector simulations are also executed using the `transpile` and `run` function of the backend, and results are obtained from the job function. However, this time, instead of using the count from the calculation results, we will obtain the state vector (=array of quantum amplitudes), so instead of `get_counts` we will look at `result.data()['statevector']`. + +```{code-cell} ipython3 +def get_statevector_array(circuit): + # 渡された回路のコピーを使う + circuit = circuit.copy() + # 量子回路の終状態の状態ベクトルを保存するインストラクション + circuit.save_statevector() + # 再び「おまじない」のtranspileをしてから、run()に渡す + circuit = transpile(circuit, backend=simulator) + job = simulator.run(circuit) + result = job.result() + qiskit_statevector = result.data()['statevector'] + + # result.data()['statevector']は通常の配列オブジェクト(ndarray)ではなくqiskit独自のクラスのインスタンス + # ただし np.asarray() で numpy の ndarray に変換可能 + return np.asarray(qiskit_statevector) + +statevector = get_statevector_array(circuit) +print(type(statevector), statevector.dtype) +print(statevector) +``` + +状態ベクトルは`np.asarray()`でnumpy配列に変換できます。データタイプは128ビットの複素数(実部と虚部それぞれ64ビット)です。配列のインデックスがそのまま二進数としてみなした計算基底の値に対応しています。 +The state vector can be converted to a numpy array object using `np.ndarray()`. Its data type is a 128-bit complex number (64 bits for the real component and 64 bits for the imaginary component). The array's index will correspond to the value of the computational basis, treating it as a binary string. + +状態ベクトルデータから数式の文字列を作る[`statevector_expr`という関数](https://github.com/UTokyo-ICEPP/qc-workbook/tree/master/source/qc_workbook/show_state.py)はこのワークブックのレポジトリからインポートしています。 + +```{code-cell} ipython3 +expr = statevector_expr(statevector) + +# Math()はLaTeXをタイプセットする関数 +Math(expr) +``` + +前回より、{eq}`回路1 `は$\frac{1}{\sqrt{2}} (s\ket{00} + c\ket{01} - c\ket{10} + s\ket{11})$($s:=\sin(\pi/8)=0.271\sqrt{2}$, $c:=\cos(\pi/8)=0.653\sqrt{2}$)という状態を作るもののはずでした。実際に計算された終状態の状態ベクトルが一致しています。 + ++++ + +(other_gates)= +## その他のゲート + +{doc}`chsh_inequality`で、よく使われる1量子ビットゲートと2量子ビット制御ゲートを紹介しましたが、他にも知っておくと便利なゲートがいくつかあります。 +In the {doc}`chsh_inequality` section, we introduced commonly used 1-quantum bit gates and 2-quantum bit controlled gates, but there are several other gates which would be handy to know. + ++++ + +### 1、2量子ビット + +```{list-table} +:header-rows: 1 +* - Gate name + - Number of affected quantum bits + - Explanation + - Qiskit code +* - $\sqrt{X}$, SX + - 1 + - Applies the following transformation to each computational basis. + ```{math} + \sqrt{X} \ket{0} = \frac{1}{2} \left[(1 + i) \ket{0} + (1 - i) \ket{1}\right] \\ + \sqrt{X} \ket{1} = \frac{1}{2} \left[(1 - i) \ket{0} + (1 + i) \ket{1}\right] + ``` + - circuit.sx(i) +* - $P$ + - 1 + - Takes parameter $\phi$ and applies phase $e^{i\phi}$ to $\ket{1}$. $P(\phi)$ and $R_z($\phi)$ are equivalent, so the same results are obtained when using it as a 1-quantum bit gate. + - `circuit.p(phi, i)` +* - $C^i_j[P]$ + - 2 + - Takes parameter $\phi$ and shifts the phase of the computational basis by $\phi$ when the values of bits $i$ and $j$ are 1. + ```{math} + C^i_j[P(\phi)] \ket{00} = \ket{00} \\ + C^i_j[P(\phi)] \ket{01} = \ket{01} \\ + C^i_j[P(\phi)] \ket{10} = \ket{10} \\ + C^i_j[P(\phi)] \ket{11} = e^{i\phi} \ket{11} + ``` + - `circuit.cp(phi, i, j)` +* - SWAP + - 2 + - Swaps the quantum states of two quantum bits. This is achieved by applying three CNOT gates. + ```{math} + \begin{align} + & C^0_1[X]C^1_0[X]C^0_1[X] (\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11}) \\ + = & C^0_1[X]C^1_0[X] (\alpha \ket{00} + \beta \ket{01} + \gamma \ket{11} + \delta \ket{10}) \\ + = & C^0_1[X] (\alpha \ket{00} + \beta \ket{11} + \gamma \ket{01} + \delta \ket{10}) \\ + = & \alpha \ket{00} + \beta \ket{10} + \gamma \ket{01} + \delta \ket{11} + \end{align} + ``` + - `circuit.swap(i, j)` +``` + ++++ + +### 3量子ビット以上 + +量子レジスタに対する全ての操作が1量子ビットゲートと2量子ビット制御ゲート(実際にはCNOTのみ)の組み合わせで表現できることはすでに触れましたが、実際に量子回路を書くときには3つ以上の量子ビットにかかるゲートを使用したほうがアルゴリズムをコンパクトに表現できます(最終的には1量子ビットゲートと2量子ビット制御ゲートに分解して実行されます)。 +As we've already touched upon, all operations on quantum registers can be performed by combining 1-quantum bit gates and 2-quantum bit controlled gates (in reality, just CNOT), but when creating actual quantum circuits, algorithms can be expressed more compactly by applying gates to three or more quantum bits. To run circuits containing these kinds of gates on an actual QC, "transpilation" must first be performed to convert the circuit. Gates for three or more quantum bits are automatically broken down into 1-quantum bit gates and CNOTs alone. + +3量子ビット以上にかかるゲートも基本は制御ゲートで、よく使われるのは制御ビットが複数あるような多重制御(multi-controlled)ゲート$C^{ij\dots}_{k}[U]$です。そのうち制御ビットが2つで標的ビット1つに$X$をかける$C^{ij}_{k}[X]$は特別多用され、Toffoliゲートとも呼ばれます。 +Gates which work on three or more quantum bits are basically controlled gates. A commonly used type of these gates is the multi-controlled gate, $C^{ij\dots}_{k}[U]$, in which multiple control bits are used. The $C^{ij}_{k}[X]$ gate, which has two control bits and applies $X$ to a target bit, is used especially often. This gate is also called a Toffoli gate. + +多重制御ゲートでない3量子ビットゲートの例として、controlled SWAPというものも存在します。制御ビット1つで他の2つの量子ビットの間の量子状態交換を制御します。当然制御ビットを増やしたmulti-controlled SWAPというものも考えられます。 +An example of 3-quantum bit gate that is not a multi-controlled gate is the controlled SWAP gate. It has one control bit and swaps the quantum states of the other two quantum bits. It is also possible, of course, to have multi-controlled SWAP gates with more control bits. + +Qiskitで多重制御ゲートに対応するコードは一般に`circuit.mc?(parameters, [controls], target)`という形を取ります。例えばToffoliゲートは +In Qiskit, codes corresponding to multi-controlled gates generally take the form of `circuit.mc?(parameters, [controls], target)`. For example, the Toffoli gate is represented like this: + +```{code-block} python +circuit.mcx([i, j], k) +``` + +で、$C^{ij}_{k}[R_y]$は +$C^{ij}_{k}[R_y]$ is represented like this: + +```{code-block} python +circuit.mcry(theta, [i, j], k) +``` + +となります。 + ++++ + +## 量子状態生成 + +それでは、これまでに登場したゲートを使って量子状態生成のエクササイズをしましょう。 +Now, let's do an exercise in which we generate a quantum state using the gates that we have introduced so far. + +実は`statevector_expr`関数には回路オブジェクトを直接渡すこともできるので(内部でシミュレータを実行し状態ベクトルオブジェクトを取得する)、ここからはその機能を利用します。また、コード中登場する`amp_norm`や`phase_norm`は、表示される数式において振幅や位相の共通因子をくくりだすために設定されています。 + +各問題のコードセルには、ヒントとして回路が一部分だけ書かれています。残りの`circuit.?`の部分を埋めてみましょう。答えは一行とは限りません。また、同じ状態を作る量子回路は無数に存在するので、ヒントを消して独自の実装をしても構いません。 + ++++ + +### 問題1: 1量子ビット、相対位相付き + +**問題** + +1量子ビットに対して状態 +Create the following state for one quantum bit. + +$$ +\frac{1}{\sqrt{2}}\left(\ket{0} + i\ket{1}\right) +$$ + +を作りなさい。 + +```{code-cell} ipython3 +:tags: [remove-output] + +circuit = QuantumCircuit(1) +################## +### EDIT BELOW ### +################## +circuit.h(0) +# circuit.? +################## +### EDIT ABOVE ### +################## + +expr = statevector_expr(circuit, amp_norm=(np.sqrt(0.5), r'\frac{1}{\sqrt{2}}')) +Math(expr) +``` + +**解答** + +````{toggle} +正解は無限に存在しますが、上の回路ですでにアダマールゲートがかかっているので、最も手っ取り早いのは +There are an infinite number of correct answers, but because the circuit above already has a Hadamard gate, the simplest is probably: + +```{code-block} python +circuit.h(0) +circuit.p(np.pi / 2., 0) +``` + +でしょう。自分で入力して確認してみてください。 +Enter it and try it out yourself. + +また、上で触れたように、単一量子ビットの場合、$U_3$ゲートを使えば任意の状態を一つのゲート操作で生成できます。$U_3$を使った解答は +As touched on above, for a single quantum bit, the $U_3$ gate can be used to generate any desired state with a single gate operation. An example of a solution using $U_3$ is shown below. + +```{code-block} python +circuit.u3(np.pi / 2., np.pi / 2., 0., 0) +``` + +です。 +```` + ++++ + +### 問題2: ベル状態、相対位相付き + +**問題** + +2量子ビットに対して状態 +Create the following state for two quantum bits. + +$$ +\frac{1}{\sqrt{2}}\left(\ket{0} + i\ket{3}\right) +$$ + +を作りなさい。 + +```{code-cell} ipython3 +:tags: [remove-output] + +circuit = QuantumCircuit(2) + +################## +### EDIT BELOW ### +################## +# circuit.? +circuit.cx(0, 1) +################## +### EDIT ABOVE ### +################## + +expr = statevector_expr(circuit, amp_norm=(np.sqrt(0.5), r'\frac{1}{\sqrt{2}}')) +Math(expr) +``` + +**解答** + +````{toggle} +問題1の結果を利用すると簡単です。初期状態は両ビットとも$\ket{0}$なので、2ビットレジスタに対して問題1の操作をすると状態$1/\sqrt{2}(\ket{0} + i\ket{1})\ket{0}$ができます。ここでビット0から1にCNOTをかけると、$\ket{1}\ket{0} \rightarrow \ket{1}\ket{1}$となるので、望む状態が実現します。 +This is easy to solve if we use the results from Task 1. In the initial state, both bits are $\ket{0}$, so if the operation in Task 1 is performed on the 2-bit register, the state will be $1/\sqrt{2}(\ket{0} + i\ket{1})\ket{0}$. If we then apply CNOT from bits 0 to 1, the state will change from $\ket{1}\ket{0} \rightarrow \ket{1}\ket{1}$, which is the desired outcome. + +```{code-block} python +circuit.h(0) +circuit.p(np.pi / 2., 0) +circuit.cx(0, 1) +``` +```` + ++++ + +### 問題3: GHZ状態 + +**問題** + +3量子ビットに対して状態 +Create the following state for three quantum bits. + +$$ +\frac{1}{\sqrt{2}} (\ket{0} + \ket{7}) +$$ + +を作りなさい。 + +```{code-cell} ipython3 +:tags: [remove-output] + +circuit = QuantumCircuit(3) + +################## +### EDIT BELOW ### +################## +# circuit.? +circuit.mcx([0, 1], 2) +################## +### EDIT ABOVE ### +################## + +expr = statevector_expr(circuit, amp_norm=(np.sqrt(0.5), r'\frac{1}{\sqrt{2}}')) +Math(expr) +``` + ++++ {"tags": ["hide-cell"]} + +**解答** + +````{toggle} +Bell状態の生成と発想は同じですが、多重制御ゲート$C^{01}_2[X]$を利用します。 +The concept is the same as generating a Bell state, but we use the $C^{01}_2[X]$ multi-controlled gate. + +```{code-block} python +circuit.h(0) +circuit.cx(0, 1) +circuit.mcx([0, 1], 2) +``` +```` + ++++ + +### 問題4: Equal superposition + +**問題** + +一般の$n$量子ビットに対して状態 +Think of a circuit for creating the following state for $n$ general quantum bits. + +$$ +\frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^n-1} \ket{k} +$$ + +を作る回路を考え、$n=4$のケースを実装しなさい。 +Implement the circuit for $n=4$. + +```{code-cell} ipython3 +:tags: [remove-output] + +num_qubits = 4 + +circuit = QuantumCircuit(num_qubits) + +################## +### EDIT BELOW ### +################## +# circuit.? +################## +### EDIT ABOVE ### +################## + +sqrt_2_to_n = 2 ** (num_qubits // 2) +expr = statevector_expr(circuit, amp_norm=(1. / sqrt_2_to_n, r'\frac{1}{%d}' % sqrt_2_to_n)) +Math(expr) +``` + ++++ {"tags": ["hide-cell"]} + +**解答** + +````{toggle} +Equal superpositionと呼ばれるこの状態は、量子計算でおそらく一番多様される状態です。一見複雑に思えますが、$n$桁の二進数で$0$から$2^n-1$までの数を表現すると、全ての0/1の組み合わせが登場するということを考えると、 +This state, which is called equal superposition, is probably the most often used state in quantum calculation. It may look complex at first glance, but if you use an n-digit binary string to express every number between 0 and $2^{n-1}$, every combination of 0s and 1s will appear. Therefore: + +$$ +\frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^n-1} \ket{k} = \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \otimes \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \otimes \cdots \otimes \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) +$$ + +と、$n$個の$1/\sqrt{2}(\ket{0} + \ket{1})$の積と等しいことがわかります。つまり各ビットに対してアダマールゲートをかけた状態にほかならず、 +As you can see, this is equivalent to $n$ products of $1/\sqrt{2}(\ket{0} + \ket{1})$. In other words, this is the same as applying a Hadamard gate to each bit, so the answer is as follows. + +```{code-block} python +for i in range(4): + circuit.h(i) +``` +が正解です。 +```` + ++++ + +### 問題5: 特定の基底の符号を反転させる + +**問題** + +問題4の4ビットequal superposition状態において、基底$\ket{5}$の符号を反転させなさい。 +With the 4-bit equal superposition state from Task 4, reverse the sign of basis state $\ket{5}$. + +```{code-cell} ipython3 +:tags: [remove-output] + +num_qubits = 4 + +circuit = QuantumCircuit(num_qubits) + +################## +### EDIT BELOW ### +################## +for i in range(num_qubits): + circuit.h(i) + +# circuit.? +################## +### EDIT ABOVE ### +################## + +sqrt_2_to_n = 2 ** (num_qubits // 2) +expr = statevector_expr(circuit, amp_norm=(1. / sqrt_2_to_n, r'\frac{1}{%d}' % sqrt_2_to_n)) +Math(expr) +``` + ++++ {"tags": ["hide-cell"]} + +**解答** + +````{toggle} +$\ket{5}=\ket{0101}$の符号を反転させるには、4つの量子ビットが0, 1, 0, 1という値を取っている状態のみに作用するゲートを使います。必然的に多重制御ゲートが用いられますが、残念ながら制御ゲートはビットの値が1であるときにしか働きません。そこで、まず$\ket{0101}$を$\ket{1111}$に変換し、その上で$C^{012}_{3}[P(\pi)]$を使います[^mcz]。符号を反転させたら、最初の操作の逆操作をして$\ket{1111}$を$\ket{0101}$に変換します。 +To switch the sign of $\ket{5}=\ket{0101}$, we use a gate that only acts on the value of each of the four bits, 0, 1, 0, 1. By necessity, we will use a multi-controlled gate, but, unfortunately, controlled gates only operate when the bit value is 1. We must therefore first convert $\ket{0101}$ to $\ket{1111}$, and then use $C^{012}_{3}[P(\pi)]$. After we have reversed the sign, we then perform the previous operations in reverse to turn $\ket{1111}$ back into $\ket{0101}$. + +```{code-block} python +for i in range(4): + circuit.h(i) + +circuit.x(1) +circuit.x(3) +circuit.mcp(np.pi, [0, 1, 2], 3) +circuit.x(1) # Xの逆操作はX +circuit.x(3) +``` + +ビット1と3にかかっている$X$ゲートは当然他の基底にも影響するので、例えば$\ket{0011}$は$\ket{0110}$となります。しかし、1と3に$X$をかけて$\ket{1111}$になるのは$\ket{0101}$だけなので、逆操作でちゃんと「後片付け」さえすれば、この一連の操作で$\ket{5}$だけをいじることができます。 +The $X$ gate, which is applied to bits 1 and 3, of course also affects the other basis states as well, so, for example, $\ket{0011}$ becomes $\ket{0110}$. However, the only thing that would turn into $\ket{1111}$ were applied to bits 1 and 3 is $\ket{0101}$, so as long as we "clean up after ourselves," as it were, this string of operations would only act on $\ket{5}$. + +[^mcz]: (多重)制御$P$ゲートはどのビットを制御と呼んでも標的と呼んでも同じ作用をするので、$C^{123}_0[P(\pi)]$でもなんでも構いません。 +[^mcz]: (Multi-) control gate $P$ functions identically regardless of which bit is called the control bit and which bit is called the target bit, so any notation, such as $C^{123}_0[P(\pi)]$, could be used. +```` + ++++ + +(equal_superposition_with_phase)= +### 問題6: Equal superpositionに位相を付ける + +**問題** + +一般の$n$量子ビットに対して状態 +Think of a circuit for creating the following state for $n$ general quantum bits. + +$$ +\frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i s k/2^n} \ket{k} \quad (s \in \mathbb{R}) +$$ + +を作る回路を考え、$n=6, s=2.5$のケースを実装しなさい。 +Implement the circuit for $n=6, s=2.5$. + +```{code-cell} ipython3 +:tags: [output_scroll, remove-output] + +num_qubits = 6 + +circuit = QuantumCircuit(num_qubits) + +s = 2.5 + +################## +### EDIT BELOW ### +################## +#for i in range(num_qubits): +# circuit.h(i) +# +# circuit.? +################## +### EDIT ABOVE ### +################## + +sqrt_2_to_n = 2 ** (num_qubits // 2) +amp_norm = (1. / sqrt_2_to_n, r'\frac{1}{%d}' % sqrt_2_to_n) +phase_norm = (2 * np.pi / (2 ** num_qubits), r'\frac{2 \pi i}{%d}' % (2 ** num_qubits)) +expr = statevector_expr(circuit, amp_norm, phase_norm=phase_norm) +Math(expr) +``` + ++++ {"tags": ["hide-cell"]} + +**解答** + +````{toggle} +問題4と同じように、この状態も個々の量子ビットにおける$\ket{0}$と$\ket{1}$の重ね合わせの積として表現できます。そのためには$k$の二進数表現$k = \sum_{m=0}^{n-1} 2^m k_m \, (k_m=0,1)$を用いて、$\ket{k}$の位相を +As in Task 4, this state can be expressed as the product of superpositions of $\ket{0}$ and $\ket{0}$ of individual quantum bits. We therefore use the binary representation $k = \sum_{m=0}^{n-1} 2^m k_m \, (k_m=0,1)$ of $k$ to break the phase of $\ket{k}$ down as shown below. + +$$ +\exp\left(2\pi i \frac{sk}{2^n}\right) = \exp \left(2\pi i \frac{s}{2^n} \sum_{m=0}^{n-1} 2^m k_m\right) = \prod_{m=0}^{n-1} \exp \left(2\pi i \frac{s}{2^{n-m}} k_m\right) +$$ + +と分解します。一方 +Also: + +$$ +\ket{k} = \ket{k_{n-1}} \otimes \ket{k_{n-2}} \otimes \cdots \ket{k_0} +$$ + +なので、 +Therefore: + +$$ +e^{2\pi i sk/2^n} \ket{k} = e^{2\pi i sk_{n-1}/2} \ket{k_{n-1}} \otimes \cdots \otimes e^{2\pi i sk_{1}/2^{n-1}} \ket{k_1} \otimes e^{2\pi i sk_{0}/2^n} \ket{k_{0}}. +$$ + +最後に、$k_m = 0$ならば$e^{2\pi i sk_{m}/2^n} = 1$であることから、 +Lastly, if $k_m = 0$, then $e^{2\pi i sk_{m}/2^n} = 1$, therefore we can see that: + +$$ +\frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i sk/2^n} \ket{k} = \frac{1}{\sqrt{2}} \left(\ket{0} + e^{2\pi i s/2} \ket{1}\right) \otimes \cdots \otimes \frac{1}{\sqrt{2}} \left(\ket{0} + e^{2\pi i s/2^n} \ket{1}\right) +$$ + +がわかります。 +The correct answer is therefore: + +```{code-block} python +for m in range(num_qubits): + circuit.h(m) + circuit.p(2. * np.pi * s / (2 ** (num_qubits - m)), m) +``` + +が正解となります。 +```` + ++++ + +## 量子計算プリミティブ + +量子計算をする回路を実装するにあたって、いくつか知っておくと便利な「パーツ」があります。これらは単体で「アルゴリズム」と呼べるものではなく、量子コンピュータを使った様々な計算の過程で、状況に合わせて応用する回路の雛形です。 + ++++ + +### 条件分岐 + +古典的には、ほとんどのプログラムが条件分岐`if ... else ...`がなければ成り立ちません。量子コンピュータのプログラムである量子回路で、条件分岐に対応するものはなんでしょうか? + +#### 測定結果に基づいた分岐 + +一つ考えうるのは、「回路のある時点である量子レジスタを測定し、その結果に応じて異なるゲートを他のレジスタにかける」という構造です。これまで測定とは量子回路の一番最後で行うものだと言ってきましたが、実は測定も一つのオペレーションとしてどこで行っても構いません。ただし、例えば状態$\ket{\psi} = \sum_{k} c_k \ket{k}$にあるレジスタを計算基底で測定し、$j$というビット列が得られたとすると、そのレジスタは$\ket{j}$という状態に「射影」されてしまい、元々の$\{c_k\}$という振幅の情報は失われます。 + +例として、サイズ4のレジスタ1にequal superpositionを実現し、測定して得られた計算基底$j$に応じて$R_y(2 \pi \frac{j}{16})$をレジスタ2の量子ビットにかける回路を書いてみます。 + +```{code-cell} ipython3 +register1 = QuantumRegister(4, name='reg1') +register2 = QuantumRegister(1, name='reg2') +output1 = ClassicalRegister(4, name='out1') # 測定結果を保持する「古典レジスタ」オブジェクト + +circuit = QuantumCircuit(register1, register2, output1) + +# register1にequal superpositionを実現 +circuit.h(register1) +# register1を測定し、結果をoutput1に書き込む +circuit.measure(register1, output1) + +# output1の各位iの0/1に応じて、dtheta * 2^iだけRyをかけると、全体としてRy(2pi * j/16)が実現する +dtheta = 2. * np.pi / 16. + +for idx in range(4): + # circuit.***.c_if(classical_bit, 1) <- classical_bitが1のときに***ゲートをかける + angle = dtheta * (2 ** idx) + circuit.ry(angle, register2[0]).c_if(output1[idx], 1) + +circuit.draw('mpl') +``` + +測定オペレーションが入っているので、この回路をstatevector simulatorに渡すと、シミュレーションを実行するごとにレジスタ1の状態がランダムに決定されます。次のセルを複数回実行して、上の回路が狙い通り動いていることを確認してみましょう。 + +入力と出力のレジスタの値が別々に表示されるよう、`statevector_expr`の`register_sizes`という引数を利用して、5ビットの回路を4ビットと1ビットに分けて解釈するよう指定します。 + +```{code-cell} ipython3 +Math(statevector_expr(circuit, register_sizes=[4, 1])) + +# cos(pi*0/16) = 1.000, sin(pi*0/16) = 0.000 +# cos(pi*1/16) = 0.981, sin(pi*1/16) = 0.195 +# cos(pi*2/16) = 0.924, sin(pi*2/16) = 0.383 +# cos(pi*3/16) = 0.831, sin(pi*3/16) = 0.556 +# cos(pi*4/16) = 0.707, sin(pi*4/16) = 0.707 +# cos(pi*5/16) = 0.556, sin(pi*5/16) = 0.831 +# cos(pi*6/16) = 0.383, sin(pi*6/16) = 0.924 +# cos(pi*7/16) = 0.195, sin(pi*7/16) = 0.981 +# cos(pi*8/16) = 0.000, sin(pi*8/16) = 1.000 +# cos(pi*9/16) = -0.195, sin(pi*9/16) = 0.981 +# cos(pi*10/16) = -0.383, sin(pi*10/16) = 0.924 +# cos(pi*11/16) = -0.556, sin(pi*11/16) = 0.831 +# cos(pi*12/16) = -0.707, sin(pi*12/16) = 0.707 +# cos(pi*13/16) = -0.831, sin(pi*13/16) = 0.556 +# cos(pi*14/16) = -0.924, sin(pi*14/16) = 0.383 +# cos(pi*15/16) = -0.981, sin(pi*15/16) = 0.195 +``` + +#### 条件分岐としての制御ゲート + +上の方法は古典プログラミングとの対応が明らかですが、あまり「量子ネイティブ」な計算の仕方とは言えません。量子計算でより自然なのは、重ね合わせ状態の生成を条件分岐とみなし、全ての分岐が回路の中で同時に扱われるようにすることです。それは結局制御ゲートを使うということに他なりません。 + +上の回路の量子条件分岐版は以下のようになります。 + +```{code-cell} ipython3 +register1 = QuantumRegister(4, name='reg1') +register2 = QuantumRegister(1, name='reg2') + +circuit = QuantumCircuit(register1, register2) + +circuit.h(register1) + +dtheta = 2. * np.pi / 16. + +for idx in range(4): + circuit.cry(dtheta * (2 ** idx), register1[idx], register2[0]) + +circuit.draw('mpl') +``` + +今度は全ての分岐が重ね合わさった量子状態が実現しています。 + +```{code-cell} ipython3 +lines = statevector_expr(circuit, register_sizes=[4, 1], terms_per_row=6) +Math(r' \\ '.join(lines)) +``` + +### 関数 + +整数値変数$x$を引数に取り、バイナリ値$f(x) \in \{0, 1\}$を返す関数$f$を量子回路で実装する一つの方法は、 + +$$ +U_{f}\ket{y}\ket{x} = \ket{y \oplus f(x)}\ket{x} \quad (y \in \{0, 1\}) +$$ + +となるようなゲートの組み合わせ$U_{f}$を見つけることです。ここで、$\oplus$は「2を法とする足し算」つまり和を2で割った余りを表します($y=0$なら$y \oplus f(x) = f(x)$、$y=1$なら$y \oplus f(x) = 1 - f(x)$)。要するに、$U_{f}$は「$f(x)$の値が0だったら何もせず、1だったら右のケットをビット反転させる」というオペレーションです。 + +このような関数回路は量子アルゴリズムを考える上で頻出します。二点指摘しておくと、 +- 単に$U_{f}\ket{y}\ket{x} = \ket{f(x)}\ket{x}$とできないのは、量子回路は可逆でなければいけないという制約があるからです。もともと右のレジスタにあった$y$という情報を消してしまうような回路は不可逆なので、そのような$U_{f}$の回路実装は存在しません。 +- 関数の返り値がバイナリというのはかなり限定的な状況を考えているようですが、一般の整数値関数も単に「1の位を返す関数$f_0$」「2の位を返す関数$f_1$」...と重ねていくだけで表現できます。 + +さて、このような$U_f$を実装する時も、やはり鍵となるのは制御ゲートです。例えば、実際に4ビット整数値関数に拡張した$f=f_3 f_2 f_1 f_0$を$x \in \{0, \dots, 7\}$を15から引く関数としたとき、それに対応する回路は + +```{code-cell} ipython3 +input_register = QuantumRegister(3, name='input') +output_register = QuantumRegister(4, name='output') + +circuit = QuantumCircuit(input_register, output_register) + +# input_registerに適当な値(6)を入力 +circuit.x(input_register[1]) +circuit.x(input_register[2]) + +circuit.barrier() + +# ここからが引き算をするU_f +# まずoutput_registerの全てのビットを立てる +circuit.x(output_register) +for idx in range(3): + # その上で、CNOTを使ってinput_registerでビットが1である時にoutput_registerの対応するビットが0にする + circuit.cx(input_register[idx], output_register[idx]) + +circuit.draw('mpl') +``` + +```{code-cell} ipython3 +Math(statevector_expr(circuit, register_sizes=[3, 4])) +``` + +入力レジスタの状態を色々変えて、状態ベクトルの変化を見てみましょう。 + +### 状態ベクトルの内積 + +次に紹介するのは、二つの状態$\ket{\psi}$と$\ket{\phi}$の間の内積の絶対値自乗$|\braket{\psi}{\phi}|^2$を計算するテクニックです。 + +まず、量子計算において二つの状態ベクトルの間の内積は、 + +$$ +\begin{split} +\ket{\psi} = \sum_{k=0}^{2^n-1} c_k \ket{k} \\ +\ket{\phi} = \sum_{k=0}^{2^n-1} d_k \ket{k} +\end{split} +$$ + +に対して + +```{math} +:label: inner_product +\braket{\psi}{\phi} = \sum_{k=0}^{2^n-1} c^{*}_k d_k +``` + +で定義できます。内積の重要な性質として + +$$ +\begin{split} +0 \leq |\braket{\psi}{\phi}|^2 \leq 1 \\ +|\braket{\psi}{\phi}|^2 = 1 \; \text{iff} \; \ket{\psi} \sim \ket{\phi} +\end{split} +$$ + +(単位ベクトルにおけるCauchy-Schwarzの不等式)があります。二つの状態ベクトルが物理的に同一であるときに内積の絶対値が最大になるので、内積の絶対値は状態の「近さ」を表す指標になります。また、内積が0になるとき、二つのベクトルは直交すると言います。 + +それから、状態ベクトルの内積と密接に関わる量として、状態ベクトルの重ね合わせ + +$$ +\ket{\psi} + \ket{\phi} = \sum_k (c_k + d_k) \ket{k} = \sum_k z_k \ket{k} +$$ + +の2-ノルム + +$$ +\lVert \ket{\psi} + \ket{\phi} \rVert_2 = \sum_k |z_k|^2 +$$ + +があります。単一の状態ベクトル$\ket{\psi}$では「量子力学の決まりごと」$\braket{\psi}{\psi} = \sum_k |c_k|^2 = 1$($\ket{\psi}$は単位ベクトル)より2-ノルムが常に1ですが、上のような重ね合わせに対しては + +$$ +\begin{split} +\sum_k |z_k|^2 & = \sum_k (c_k + d_k)^* (c_k + d_k) \\ +& = \sum_k \left( |c_k|^2 + |d_k|^2 + c_k^* d_k + d_k^* c_k \right) \\ +& = 2 \left(1 + \mathrm{Re}\braket{\psi}{\phi} \right) +\end{split} +$$ + +となり、2-ノルムが内積$\braket{\psi}{\phi}$に依存します。 + +内積や2-ノルムの計算は量子計算の様々な局面で登場します。それではその計算をする量子回路の組み方を見ていきましょう。実は2通りの異なる方法があります。 + ++++ + +#### SWAPテスト + +状態$\ket{\psi}$と$\ket{\phi}$(具体的に実装するためここでは3量子ビットの状態とする)が二つのレジスタに実現しているような次の回路を実行し、`test_output`で0が出る確率を$P_0$、1が出る確率を$P_1$とすると、 + +```{math} +:label: swap_test +P_0 - P_1 = |\braket{\psi}{\phi}|^2 +``` + +が成り立ちます。 + +```{code-cell} ipython3 +data_width = 3 + +fig, axs = plt.subplots(1, 2) + +# 適当な状態|ψ>を作る回路 +psi_circuit = QuantumCircuit(data_width, name='|ψ>') +psi_circuit.ry(0.7, 2) +psi_circuit.cx(2, 1) +psi_circuit.rz(0.5, 1) +psi_circuit.cx(1, 0) +psi_circuit.draw('mpl', ax=axs[0]) +axs[0].set_title(r'$\psi$') + +# 適当な状態|φ>を作る回路 +phi_circuit = QuantumCircuit(data_width, name='|φ>') +phi_circuit.rx(1.2, 0) +phi_circuit.ry(2.1, 1) +phi_circuit.cx(0, 2) +phi_circuit.cz(1, 2) +phi_circuit.ry(0.8, 2) +phi_circuit.draw('mpl', ax=axs[1]) +axs[1].set_title(r'$\phi$') + +# パーツが全て揃ったので、内積を計算する回路を作る +reg_data1 = QuantumRegister(data_width, name='data1') +reg_data2 = QuantumRegister(data_width, name='data2') +reg_test = QuantumRegister(1, name='test') +out = ClassicalRegister(1, name='out') + +circuit = QuantumCircuit(reg_data1, reg_data2, reg_test, out, name='SWAP_test') +# 状態|ψ>と|φ>をデータレジスタに実現 +# 他の回路や別に定義したゲートを回路オブジェクトに組み込むときはappend()メソッドを使う +# qargsでもとの回路の量子ビットを組み込み先のどの量子ビットに対応させるかを指定する +circuit.append(psi_circuit, qargs=reg_data1) +circuit.append(phi_circuit, qargs=reg_data2) + +# 回路図が見やすくなるようにバリアを入れる(計算上は何もしない操作) +circuit.barrier() + +# ここからがSWAPテスト +circuit.h(reg_test) + +for idx in range(data_width): + circuit.cswap(reg_test[0], reg_data1[idx], reg_data2[idx]) + +circuit.h(reg_test) + +circuit.measure(reg_test, out) +``` + +```{code-cell} ipython3 +circuit.draw('mpl') +``` + +式{eq}`swap_test`を導いてみましょう。 + +```{toggle} +全体回路の状態は初期状態$\ket{0}_{\mathrm{t}}\ket{0}_{\mathrm{d2}}\ket{0}_{\mathrm{d1}}$から始めて + +$$ +\begin{align} +& \ket{0}_{\mathrm{t}} \ket{\phi}_{\mathrm{d2}}\ket{\psi}_{\mathrm{d1}} \\ +\rightarrow & \frac{1}{\sqrt{2}} (\ket{0} + \ket{1})_{\mathrm{t}} \ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} \\ +\rightarrow & \frac{1}{\sqrt{2}} (\ket{0}_{\mathrm{t}} \ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} + \ket{1}_{\mathrm{t}} \ket{\psi}_{\mathrm{d2}} \ket{\phi}_{\mathrm{d1}}) \\ +\rightarrow & \frac{1}{2} \left[ (\ket{0} + \ket{1})_{\mathrm{t}} \ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} + (\ket{0} - \ket{1})_{\mathrm{t}} \ket{\psi}_{\mathrm{d2}} \ket{\phi}_{\mathrm{d1}} \right] \\ += & \frac{1}{2} \left[ \ket{0}_{\mathrm{t}} (\ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} + \ket{\psi}_{\mathrm{d2}} \ket{\phi}_{\mathrm{d1}}) + \ket{1}_{\mathrm{t}} (\ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} - \ket{\psi}_{\mathrm{d_2}} \ket{\phi}_{\mathrm{d1}}) \right] +\end{align} +$$ + +と移っていきます。最後にテストレジスタ$\mathrm{t}$を測定します。 + +ここでポイントは、一般に二つのレジスタ$a, b$上に作られる量子状態 + +$$ +\sum_{kl} z_{kl} \ket{l}_b \ket{k}_a +$$ + +のレジスタ$b$だけを測定したとき、結果$j$が得られる確率は + +$$ +P_j = \sum_k |z_{kj}|^2 = \left\lVert \sum_k z_{kj} \ket{k}_a \right\rVert_2 +$$ + +つまり$\ket{j}_b$の「係数」になっている$a$レジスタのベクトルの2-ノルムである、という点です。 + +したがって、テストレジスタで0と1が測定される確率$P_0$と$P_1$は + +$$ +\begin{align} +P_0 & = \frac{1}{4} \lVert (\ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} + \ket{\psi}_{\mathrm{d2}} \ket{\phi}_{\mathrm{d1}}) \rVert_2 = \frac{1}{2} (1 + \mathrm{Re}\braket{\psi}{\phi}\braket{\phi}{\psi}) \\ +P_1 & = \frac{1}{4} \lVert (\ket{\phi}_{\mathrm{d2}} \ket{\psi}_{\mathrm{d1}} - \ket{\psi}_{\mathrm{d2}} \ket{\phi}_{\mathrm{d1}}) \rVert_2 = \frac{1}{2} (1 - \mathrm{Re}\braket{\psi}{\phi}\braket{\phi}{\psi}) +\end{align} +$$ + +となります。$\braket{\psi}{\phi}\braket{\phi}{\psi} = |\braket{\psi}{\phi}|^2$は実数なので、たしかに式{eq}`swap_test`が成り立ちます。 +``` + +実際の回路でも確認してみましょう。入力の状態は + +```{code-cell} ipython3 +Math(statevector_expr(psi_circuit, state_label=r'\psi')) +``` + +```{code-cell} ipython3 +Math(statevector_expr(phi_circuit, state_label=r'\phi')) +``` + +で、$|\braket{\psi}{\phi}|^2$は + +```{code-cell} ipython3 +sv_psi = get_statevector_array(psi_circuit) +sv_phi = get_statevector_array(phi_circuit) +print(np.square(np.abs(np.sum(sv_psi.conjugate() * sv_phi)))) +``` + +それに対し上の回路を1000000回実行した時の$P_0 - P_1$は + +```{code-cell} ipython3 +simulator = AerSimulator() +shots = 1000000 + +circuit = transpile(circuit, backend=simulator) +counts = simulator.run(circuit, shots=shots).result().get_counts() + +print((counts['0'] - counts['1']) / shots) +``` + +です。 + ++++ + +(inverse_circuit)= +#### 逆回路の利用 + +上の回路では入力状態$\ket{\psi}$や$\ket{\phi}$を適当な量子回路で作りましたが、見ればわかるようにSWAPテストは入力の生成過程によらず成り立ちます。例えばなんらかの「量子メモリ」デバイスがあって、そこから二つのデータレジスタに直接$\ket{\psi}$と$\ket{\phi}$がロードされてもいいわけです。ところが、現在の実際の量子コンピューティングにおいては、量子コンピュータ中の状態というのは全て$\ket{0}$にゲートをかけていくことでしか作れません。 + +入力状態が既知の回路で作られる場合は、SWAPテストよりもコンパクトで効率的な内積の絶対値自乗の計算方法があります。次のような回路を組むと、測定値が0である確率を$P_0$として + +```{math} +:label: inverse_circuit +P_0 = |\braket{\psi}{\phi}|^2 +``` + +が成り立ちます。 + +```{code-cell} ipython3 +reg_data = QuantumRegister(data_width, name='data') +circuit = QuantumCircuit(reg_data) + +circuit.append(phi_circuit, qargs=reg_data) +# psi_circuit.inverse() -> psi_circuitの逆回路 +circuit.append(psi_circuit.inverse(), qargs=reg_data) + +circuit.measure_all() + +circuit.draw('mpl') +``` + +ポイントは`psi_circuit.inverse()`で得られる`psi_circuit`の逆回路です。逆回路とは、ゲートの順番を反転させ、全てのゲートをその逆操作で置き換えたものです。$\ket{0}$から$\ket{\psi}$を作る回路を$U_{\psi}$と表すと、逆回路は$U_{\psi}^{-1}$もしくは$U_{\psi}^{\dagger}$と書かれ、 + +$$ +U_{\psi}^{-1} U_{\psi} \ket{0} = \ket{0} +$$ + +です。一般に、状態$\ket{\lambda} = \sum_k z_k \ket{k}$を測定して0を得る確率$|z_0|^2$は内積の定義{eq}`inner_product`から$|\braket{0}{\lambda}|^2$と書けるので、上の回路の測定前の状態を$U_{\psi}^{-1} U_{\phi} \ket{0} =: \ket{U^{-1}_{\psi} \phi}$と表すと、 + +$$ +P_0 = |\braket{0}{U^{-1}_{\psi} \phi}|^2 +$$ + +となります。ここから式{eq}`inverse_circuit`を導くには、量子力学と線形代数をよりはっきり結びつけて後者の言葉を使えば簡単ですが、量子回路と量子状態の概念を出発点として式{eq}`inverse_circuit`を証明することもできます。少し本筋から外れてしまいますが、以下に証明を載せておきます。 + ++++ + +```{toggle} +まず、任意の量子回路を表す写像$U$と任意の量子状態$\ket{\lambda}, \ket{\theta}$を考えます。量子回路は量子状態を量子状態に移すので、$U\ket{\lambda}$は単位ベクトル、つまり + +$$ +\lVert U \ket{\lambda} \rVert^2 = 1 +$$ + +です。$\chi$を複素数としたとき、ベクトル$(\ket{\lambda} + \chi \ket{\theta}) / \lVert \ket{\lambda} + \chi \ket{\theta} \rVert$も単位ベクトルなので、 + +$$ +\left\lVert U \frac{\ket{\lambda} + \chi \ket{\theta}}{\lVert \ket{\lambda} + \chi \ket{\theta} \rVert} \right\rVert^2 = 1 +$$ + +したがって + +$$ +\begin{align} +2 + 2 \mathrm{Re}(\chi \braket{U \lambda}{U \theta}) = 2 + 2 \mathrm{Re}(\chi\braket{\lambda}{\theta}) \\ +\therefore \mathrm{Re}(\chi \braket{U \lambda}{U \theta}) = \mathrm{Re}(\chi \braket{\lambda}{\theta}) +\end{align} +$$ + +ここで$\chi=1$と$\chi=i$をそれぞれ考えると、$\braket{U \lambda}{U \theta}$と$\braket{\lambda}{\theta}$の実部と虚部が共に等しいので + +$$ +\braket{U \lambda}{U \theta} = \braket{\lambda}{\theta}. +$$ + +ところが右辺は + +$$ +\braket{\lambda}{\theta} = \braket{\lambda}{U^{-1} U \theta} +$$ + +でもあり、これが任意の$\theta$で成立するので、結局任意の状態$\rho$で + +$$ +\braket{\lambda}{U^{-1} \rho} = \braket{U \lambda}{\rho} +$$ + +が言えます。これより + +$$ +P_0 = |\braket{0}{U^{-1}_{\psi} \phi}|^2 = |\braket{\psi}{\phi}|^2 +$$ + +です。 +``` + ++++ + +上の回路で式{eq}`inverse_circuit`を確認しましょう。 + +```{code-cell} ipython3 +shots = 1000000 + +circuit = transpile(circuit, backend=simulator) +counts = simulator.run(circuit, shots=shots).result().get_counts() + +print(counts['000'] / shots) +``` + +## 複製不可能定理とテレポーテーション + +量子アルゴリズムを考え、実装する時に非常に重要なのが、**任意の未知の量子状態の独立な複製を作る量子回路は存在しない**という複製不可能定理(no-cloning theorem)です。この定理は具体的には、入力状態$\ket{\psi}_{\mathrm{in}}$と、同じビット数の初期化した出力レジスタ$\ket{0}_{\mathrm{out}}$がある時、$\psi$に依存しない回路$U$で + +$$ +U (\ket{\psi}_{\mathrm{in}} \ket{0}_{\mathrm{out}}) \sim \ket{\psi}_{\mathrm{in}} \ket{\psi}_{\mathrm{out}} +$$ + +を実現するものはないということを主張しています。 + +証明は簡単です。 + ++++ + +```{toggle} +二つの入力状態$\psi$と$\phi$を考え、左辺の内積を取ると(上の逆回路での内積計算の証明を利用して) + +$$ +|\braket{U(\psi_{\mathrm{in}}, 0_{\mathrm{out}})}{U(\phi_{\mathrm{in}}, 0_{\mathrm{out}})}| = |\braket{\psi}{\phi}\braket{0}{0}| = |\braket{\psi}{\phi}|. +$$ + +ところが右辺の内積は + +$$ +|\braket{\psi_{\mathrm{in}}, \psi_{\mathrm{out}}}{\phi_{\mathrm{in}}, \phi_{\mathrm{out}}}| = |\braket{\psi}{\phi}|^2. +$$ + +等価関係$\sim$は位相を無視して同値ということだったので、両辺の内積のノルムは等しいはずであり、 + +$$ +|\braket{\psi}{\phi}| = |\braket{\psi}{\phi}|^2. +$$ + +これは$|\braket{\psi}{\phi}|=0$または$|\braket{\psi}{\phi}|=1$を意味し、$\ket{\psi}$と$\ket{\phi}$が任意の状態であるという仮定と矛盾します。 +``` + ++++ + +複製不可能定理は量子回路で実行できる計算のクラスを大幅に制限します。例えば、何かの計算を途中で打ち切ってその時点での量子状態をなんらかのメモリデバイスに保存できたとして、その状態をコピーすることができないので、結局そこから計算を再開できるのは一度だけということになります。 + +任意の量子状態を複製することはできませんが、あるレジスタの量子状態を他のレジスタに移し替えることは可能です。このような操作を量子テレポーテーションと呼びます。テレポーテーションでは元々のレジスタの量子状態が失われるので、状態をコピーしたことにはなりません。 + +1ビットの情報を転送する回路は以下のようになります。 + +```{code-cell} ipython3 +# まずは入力ビットを適当な状態にする回路を作る +# circuit.u (U3 gate)は3パラメータで一つの量子ビットを完全にコントロールするゲート +prep_circuit = QuantumCircuit(1, name='prep') +prep_circuit.u(0.7, 1.8, 2.1, 0) + +reg_in = QuantumRegister(1, name='in') +reg_out = QuantumRegister(2, name='out') +res_in = ClassicalRegister(1) +res_ent = ClassicalRegister(1) + +circuit = QuantumCircuit(reg_in, reg_out, res_in, res_ent) + +# まずreg_inをprep_circuitの状態にする +circuit.append(prep_circuit, qargs=reg_in) + +# reg_outはベル状態に用意する +circuit.h(reg_out[0]) +circuit.cx(reg_out[0], reg_out[1]) + +# reg_inとreg_outの第一ビットをエンタングルさせる +circuit.cx(reg_in[0], reg_out[0]) + +# reg_inにアダマールゲートをかけ、測定する +circuit.h(reg_in[0]) +circuit.measure(reg_in[0], res_in[0]) + +# reg_outのエンタングルしたビットも測定する +circuit.measure(reg_out[0], res_ent[0]) + +# reg_out[1]にreg_in, reg_entの測定結果に応じたゲートをかける +circuit.x(reg_out[1]).c_if(res_ent[0], 1) +circuit.z(reg_out[1]).c_if(res_in[0], 1) + +circuit.draw('mpl') +``` + +入力ビットの状態は + +```{code-cell} ipython3 +Math(statevector_expr(prep_circuit, state_label=r'\text{in}')) +``` + +回路の終状態は + +```{code-cell} ipython3 +Math(statevector_expr(circuit, register_sizes=(1, 1, 1))) +``` + +上のセルを何度か実行すると、右二つのレジスタは実行するごとにランダムに違う値を取るものの、二つの項で共通で、一番左のレジスタが$\ket{\text{in}}$と同じ状態にあることがわかります。このようにテレポーテーションが実際に起こることを、上の回路を元に数式でも確認してみるといいでしょう。 diff --git a/source/en/dynamics_simulation.md b/source/en/dynamics_simulation.md new file mode 100644 index 00000000..d43c844d --- /dev/null +++ b/source/en/dynamics_simulation.md @@ -0,0 +1,641 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 物理系を表現する + +量子コンピュータの並列性を利用した計算の代表例として、量子系のダイナミクスシミュレーションについて学びます。 +You will learn about creating a dynamic simulation of a quantum system as a typical example of calculation using the parallel calculation capabilities of a quantum computer. + +```{contents} 目次 +--- +local: true +--- +``` + +$\newcommand{\bra}[1]{\langle #1 |}$ +$\newcommand{\ket}[1]{| #1 \rangle}$ +$\newcommand{\upket}{\ket{\!\uparrow}}$ +$\newcommand{\downket}{\ket{\!\downarrow}}$ +$\newcommand{\rightket}{\ket{\!\rightarrow}}$ +$\newcommand{\leftket}{\ket{\!\leftarrow}}$ + ++++ + +## 量子系のダイナミクスとは + +量子力学について少しでも聞いたことのある方は、量子力学の根幹にシュレーディンガー方程式というものが存在することを知っているかと思います。この方程式は +If you have even the most passing familiarity with quantum mechanics, you'll know that the Schrödinger equation lies at the heart of quantum mechanics. This equation can be expressed as shown below. + +$$ +i \hbar \frac{\partial}{\partial t} \ket{\psi (t)} = H \ket{\psi (t)} +$$ + +などと表現され、時刻$t$のある系の状態$\ket{\psi (t)}$の時間微分(左辺)が$\ket{\psi (t)}$へのハミルトニアンという演算子の作用で定まる(右辺)ということを表しています。ただしこの「微分形」の方程式は我々の目的には少し使いづらいので、ここでは等価な「積分形」にして +This shows that the derivative with respect to time of the state $\ket{\psi (t)}$ of a system with time $t$ (the left side of the equation) is decided by the action of an operator called a Hamiltonian on $\ket{\psi (t)}$ (the right side of the equation). This differential form equation is somewhat difficult for us to use, so we'll change it into its equivalent integral form, as shown below. + +$$ +\ket{\psi (t_1)} = T \left[ \exp \left( -\frac{i}{\hbar} \int_{t_0}^{t_1} H dt \right) \right] \ket{\psi (t_0)} +$$ + +と書いておきます。$T[\cdot]$は「時間順序演算子」と呼ばれ重要な役割を持ちますが、説明を割愛し、以下ではハミルトニアン$H$が直接時間に依存しない場合の +If the Hamiltonian $H$ is not directly dependent on the time, this can be written as follows. + +$$ +\ket{\psi (t_1)} = \exp \left( -\frac{i}{\hbar} H (t_1 - t_0) \right) \ket{\psi (t_0)} +$$ + +のみを考えます。量子状態に対する演算子(線形演算子)の指数関数もまた演算子なので、積分形のシュレーディンガー方程式は「$e^{-i/\hbar H (t_1-t_0)}$という演算子が系を時刻$t_0$の初期状態$\ket{\psi(t_0)}$から時刻$t_1$の状態$\ket{\psi(t_1)}$に発展させる」と読めます。さらに、定義上ハミルトニアンは「エルミート演算子」であり、それに虚数単位をかけて指数の冪にした$e^{-i/\hbar H t}$(以下これを時間発展演算子$U_H(t)$と呼びます)は「ユニタリ演算子」です(このあたりの線形代数の用語にあまり馴染みがなくても、そういうものかと思ってもらえれば結構です)。 +Below, we will consider only this case. The exponential functions of operators used on quantum states (linear operators) are also operators, so the integral Schrödinger equation can be read as "the operator $e^{-i/\hbar H (t_1-t_0)}$ expands the system from its initial state of $\ket{\psi(t_0)}$ at $t_0$ to its state of $\ket{\psi(t_1)}$ at time $t_1$. Furthermore, by its definition, the Hamiltonian is a Hermitian operator, and $e^{-i/\hbar H t}$, which multiplies it by an imaginary unit and raises it to a power is a unitary operator (hereafter, this is referred to as the time evolution operator $U_H(t)$) (if you're not that familiar with these linear algebra terms, just keep this somewhere in the back of your mind). + +ユニタリ演算子は量子計算の言葉で言えばゲートにあたります。したがって、ある量子系に関して、その初期状態を量子レジスタで表現でき、時間発展演算子を量子コンピュータの基本ゲートの組み合わせで実装できれば、その系のダイナミクス(=時間発展)シミュレーションを量子コンピュータで行うことができます。 +A unitary operator, in quantum calculation terminology, is a gate. Therefore, for a quantum system, if we can express the initial state using a quantum register and implement the time evolution operator through a combination of quantum computer basic gates, we can perform a simulation of the system's dynamics (=time evolution) using a quantum computer. + ++++ + +### 例:核磁気の歳差運動 + +シミュレーションの詳しい話をする前に、これまで量子力学と疎遠だった方のために、ハミルトニアンや時間発展とは具体的にどういうことか、簡単な例を使って説明します。 +Before getting into a detailed discussion of the simulation, for the benefit of readers who may become a bit rusty when it comes to quantum mechanics, let's go over what, specifically, Hamiltonians and time evolution are, using some basic examples. + +空間中に固定されたスピン$\frac{1}{2}$原子核一つを考えます。ある方向(Z方向とします)のスピン$\pm \frac{1}{2}$の状態をそれぞれ$\upket, \downket$で表します。量子力学に馴染みのない方のための説明例で大いに量子力学的な概念を使っていますが、何の話かわからなければ「2つの基底ケットで表現される、量子ビットのような物理系がある」と考えてください。量子ビットのような物理系なので、系の状態は一般に$\upket$と$\downket$の重ね合わせになります。 +onsider an atomic nucleus with a spin of $\frac{1}{2}$, suspended in a fixed point in space. Its spin $\pm \frac{1}{2}$ status in a given direction (let's call it the Z direction) can be expressed as $\upket$ and $\downket$で. This explanation example for people who are unfamiliar with quantum mechanics uses many quantum mechanics concepts, so if you don't follow, just think of this as being a "physical system like a quantum bit which can be expressed with two basis state kets." This physical system is like a quantum bit, so the system is normally a superposition of |↑⟩ and |↓⟩. + +時刻$t_0$で系が$\ket{\psi(t_0)} = \upket$にあるとします。時刻$t_1$での系の状態を求めることは +Let us say that the state of the system at $t_0$ is $\ket{\psi(t_0)} = \upket$. At $t_1$, we can determine the state of the system using the following equation. + +$$ +\ket{\psi (t_1)} = \alpha (t_1) \upket + \beta (t_1) \downket +$$ + +の$\alpha (t_1)$と$\beta (t_1)$を求めることに相当します。ここで$\alpha (t_0) = 1, \beta (t_0) = 0$です。 +Determining the state of the system is equivalent to determining the state of $\alpha (t_1)$ and $\beta (t_1)$. Here, $\alpha (t_0) = 1$, $beta (t_0) = 0$. + +この原子核に$X$方向の一定磁場をかけます。非ゼロのスピンを持つ粒子はスピンベクトル$\vec{\sigma}$と平行な磁気モーメント$\vec{\mu}$を持ち、磁場$\vec{B}$のもとでエネルギー$-\vec{B}\cdot\vec{\mu}$を得ます。ハミルトニアンとは実は系のエネルギーを表す演算子なので、この一定磁場だけに注目した場合の系のハミルトニアンは、何かしらの定数$\omega$とスピンベクトルの$X$成分$\sigma^X$を用いて$H = \hbar \omega \sigma^X$と書けます。 +Now let us apply a constant magnetic field to the atomic nucleus in the $X$ direction. Particles with non-zero spin will have a magnetic moment $\vec{\mu}$ parallel to the spin vector $\vec{\sigma}$, and in magnetic field $\vec{B}$ will gain $-\vec{B}\cdot\vec{\mu}$ energy. The Hamiltonian is an operator that expresses the energy of the system. The Hamiltonian for a system with only a constant magnetic field, like this example, can be written as $H = \hbar \omega \sigma^X$, using a constant $\omega$ and the spin vector's $X$ component $\sigma^X$. + +量子力学では$\sigma^X$は演算子であり、$\upket$と$\downket$に対して +In quantum mechanics, $\sigma^X$ is an operator and acts on $\upket$ and $\downket$ as below. + +$$ +\sigma^X \upket = \downket \\ +\sigma^X \downket = \upket +$$ + +と作用します。時間発展演算子$U_H(t)$は +$U_H (t)$ is a time evolution operator. + +$$ +U_H(t) = \exp (-i \omega t \sigma^X) = \sum_{n=0}^{\infty} \frac{1}{n!} (-i \omega t)^n (\sigma^X)^n = I + (-i \omega t) \sigma^X + \frac{1}{2} (-i \omega t)^2 (\sigma^X)^2 + \frac{1}{6} (-i \omega t)^3 (\sigma^X)^3 \cdots +$$ + +ですが($I$は恒等演算子)、上の$\sigma^X$の定義からわかるように$(\sigma^X)^2 = I$なので +($I$ is an identity operator.) Given the definition for $\sigma^X$ above, $(\sigma^X)^2 = I$, so this can be written as follows. + +```{math} +:label: exp_sigmax +\begin{align} +\exp (-i \omega t \sigma^X) & = \left[ 1 + \frac{1}{2} (-i \omega t)^2 + \cdots \right] I + \left[(-i \omega t) + \frac{1}{6} (-i \omega t)^3 + \cdots \right] \sigma^X \\ +& = \cos(\omega t) I - i \sin(\omega t) \sigma^X +\end{align} +``` + +と書けます。したがって、 +Therefore: + +```{math} +:label: spin_exact +\begin{align} +\ket{\psi (t_1)} = U_H(t_1 - t_0) \ket{\psi (t_0)} & = \exp [-i \omega (t_1 - t_0) \sigma^X] \upket \\ +& = \cos[\omega (t_1 - t_0)] \upket - i \sin[\omega (t_1 - t_0)] \downket +\end{align} +``` + +です。任意の時刻$t_1$のスピンの状態が基底$\upket$と$\downket$の重ね合わせとして表現されました。 +At any given time, $t_1$, the spin state can be expressed as the superposition of basis states $\upket$ and $\downket$. + +このように、系のエネルギーの表式からハミルトニアンが決まり、その指数関数を初期状態に作用させることで時間発展後の系の状態が求まります。 +In this way, the energy formula for the system determines its Hamiltonian, and it can be applied to the initial state of the exponential function to determine the state of the system after time evolution. + +ちなみに、$\ket{\psi (t_1)}$は$t_1 = t_0$で$\upket$、$t_1 = t_0 + \pi / (2 \omega)$で$(-i)\downket$となり、以降$\upket$と$\downket$を周期的に繰り返します。実は、その間の状態はスピンが$Y$-$Z$平面内を向いている状態に相当します。スピンが0でない原子核に磁場をかけると、スピンと磁場の方向が揃っていなければ磁場の方向を軸にスピンが歳差運動(すりこぎ運動)をします。これはコマが重力中で起こす運動と同じで、核磁気共鳴(NMR、さらに医学応用のMRI)の原理に深く関わります。 +When $t_1=t_0$, $\ket{\psi (t_1)}$ is $\upket$, while when $t_1 = t_0 + \pi / (2 \omega)$, it is $(-i)\downket$. \upket$ and \downket$ then repeat cyclically. In reality, the states in between correspond to spins on the $Y$ and $Z$ planes. When a magnetic field is applied to an atomic nucleus with non-zero spin, if the direction of the spin and the atomic field do not match, the spin will undergo precessional movement along the axis of the direction of the magnetic field. This is the same as the motion of a spinning top within a gravitational field, and is deeply tied to the principles of nuclear magnetic resonance, or NMR (a form of MRI used in medical applications). + ++++ + +### 量子コンピュータ上での表現 + +すでに触れましたが、上の例で核のスピンは量子ビットのように2つの基底ケットを持ちます(2次元量子系です)。さらに、お気づきの方も多いと思いますが、$\sigma^X$の$\upket$と$\downket$への作用は$X$ゲートの$\ket{0}$と$\ket{1}$への作用そのものです。このことから、核磁気の歳差運動が極めて自然に量子コンピュータでシミュレートできることがわかるかと思います。 +As we've already briefly touched on, in the example above the spin of the nucleus has two basis state kets, like a quantum bit (it is a two-dimensional quantum system). Furthermore, many readers have likely also noticed that $\sigma^X$ acts on $\upket$ and $\downket$ like the $X$ gate acts on $\ket{0}$ and $\ket{1}$. You can therefore see that the precessional movement of nuclear magnetism can be simulated in a quantum computer in an extremely natural manner. + +実際には、時間発展演算子は$\sigma^X$そのものではなくその指数関数なので、量子コンピュータでも$\exp (-i \frac{\theta}{2} X)$に対応する$R_{x} (\theta)$ゲートを利用します。これまで紹介されませんでしたが、$R_{x}$ゲートはパラメータ$\theta$をとり、 +In reality, the time evolution operator is not $\sigma^X$ itself, but an exponential function of it, so quantum computers also use the $R_{x} (\theta)$ gate, which corresponds to $\exp (-i \frac{\theta}{2} X)$. We have not previously introduced this, but the $R_x$ gate uses parameter $\theta$ and performs the following transformation. + +$$ +R_{x}(\theta)\ket{0} = \cos\frac{\theta}{2}\ket{0} - i\sin\frac{\theta}{2}\ket{1} \\ +R_{x}(\theta)\ket{1} = -i\sin\frac{\theta}{2}\ket{0} + \cos\frac{\theta}{2}\ket{1} +$$ + +という変換を行います。上の核スピン系を量子コンピュータでシミュレートするには、1量子ビットで$R_{x} (2 \omega (t_1 - t_0)) \ket{0}$を計算する以下の回路を書けばいいだけです。 +To simulate the above nuclear spin system with a quantum computer, you could calculate $R_{x} (2 \omega (t_1 - t_0)) \ket{0}$ with 1 quantum bit using the following circuit. + +```{code-cell} ipython3 +:tags: [remove-input] + +import numpy as np +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.circuit import Parameter +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +circuit = QuantumCircuit(QuantumRegister(1, 'q'), ClassicalRegister(1, 'c')) +circuit.rx(Parameter(r'$2 \omega (t_1 - t_0)$'), 0) +circuit.measure(0, 0) +circuit.draw('mpl', initial_state=True) +``` + +### ハミルトニアンの対角化 + +再び量子コンピュータを離れて、量子・古典に関わらずデジタル計算機で量子ダイナミクスのシミュレーションをする際の一般論をします。 +Let us step away from quantum computers again and talk in general about how quantum mechanics are simulated by digital computers, whether quantum or classical. + +上の核スピンの例ではハミルトニアンが単純だったので、式{eq}`spin_exact`のように厳密解が求まりました。特に、導出において$(\sigma^X)^2 = I$という恒等式が非常に重要でした。しかし、一般のハミルトニアンでは、何乗しても恒等演算子の定数倍にたどり着く保証がありません。 +In the nuclear spin example above, the Hamiltonian was simple, so we were able to determine the exact solution in Formula {eq}`spin_exact`. In deriving it, the identity formula of $(\sigma^X)^2 = I$ was extremely important. However, there is no guarantee that with ordinary Hamiltonians, raising to a certain power will produce a multiple of the identity operator. + +累乗して恒等演算子にならないようなハミルトニアンであっても、系の次元が小さい場合は「対角化」という作業で厳密解を得られます。ハミルトニアンの対角化とは、ハミルトニアンの作用が実数をかけることと等しくなるようなケットを探してくること、つまり +Even for Hamiltonians which do not become identity operators when raised to other powers, if the system's dimensions are small, exact solutions can be determined through diagonalization. Hamiltonian diagonalization consists of searching for kets for which applying the Hamiltonian produces the same results as multiplying by a real number. In other words, it refers to searching for a $\ket{\phi_j}$ that would make the following true. + +$$ +H\ket{\phi_j} = \hbar \omega_j \ket{\phi_j}, \quad \omega_j \in \mathbb{R} +$$ + +が成り立つような$\ket{\phi_j}$を見つけることを指します。このような$\ket{\phi_j}$を「固有値$\hbar \omega_j$を持つ$H$の固有ベクトル」と呼びます。「エネルギー固有状態」と呼ぶこともあります。系の次元が$N$であれば、独立な固有ベクトルが$N$個存在します。 +In this case, $\ket{\phi_j}$ is called an $H$ eigenvector with eigenvalue $\hbar \omega_j$. It is also called an energy eigenstate. If the system has $N$ dimensions, there are $N$ independent eigenvectors. + +例えば上の例では$H = \hbar \omega \sigma^X$ですが、 +For example, in the above example, $H = \hbar \omega \sigma^X$. However, consider the following two states. + +```{math} +:label: left_right_kets +\rightket := \frac{1}{\sqrt{2}}(\upket + \downket) \\ +\leftket := \frac{1}{\sqrt{2}}(\upket - \downket) +``` + +という2つの状態を考えると +This results in the following. + +$$ +\sigma^X \rightket = \rightket \\ +\sigma^X \leftket = -\leftket +$$ + +なので、これらが固有値$\pm \hbar \omega$の$H$の固有ベクトルとなっていることがわかります。 +As you can see, these are the $H$ eigenvectors for eigenvalues $\pm \hbar \omega$. + +固有値$\hbar \omega_j$のハミルトニアン$H$の固有ベクトル$\ket{\phi_j}$は自動的に時間発展演算子$U_H(t)$の固有値$e^{-i\omega_j t}$の固有ベクトルでもあります。 +The eigenvector $\ket{\phi_j}$ of Hamiltonian $H$ for eigenvalue $\hbar \omega_j$ is, automatically, also an eigenvector for eigenvalue $e^{-i\omega_j t}$ of time evolution operator $U_H (t)$. + +$$ +U_H(t) \ket{\phi_j} = \exp \left( -\frac{i}{\hbar} H t \right) \ket{\phi_j} = \exp (-i \omega_j t) \ket{\phi_j}. +$$ + +したがって、系の初期状態$\ket{\psi (t_0)}$が +Therefore, if the initial state of the system, $\ket{\psi (t_0)}$, is as follows: + +$$ +\ket{\psi (t_0)} = \sum_{j=0}^{N} c_j \ket{\phi_j} +$$ + +であれば、時刻$t_1$での状態は +Then the state at time $t_1$ is: + +$$ +\ket{\psi (t_1)} = \sum_{j=0}^{N} c_j U_H(t_1 - t_0) \ket{\phi_j} = \sum_{j=0}^{N} e^{-i \omega_j (t_1 - t_0)} c_j \ket{\phi_j}, +$$ + +つまり、各固有ベクトルの振幅に、対応する位相因子をかけるだけで求まります。 +In other words, the amplitude of each eigenvector can be determined simply by multiplying the corresponding phase factors. + +再び核スピンの例を見ると、初期状態$\ket{\psi(t_0)} = \upket = 1/\sqrt{2} (\rightket + \leftket)$なので、 +Looking at the example of the nuclear spin, the initial state is $\ket{\psi(t_0)} = \upket = 1/\sqrt{2} (\rightket + \leftket)$. + +$$ +\begin{align} +\ket{\psi(t_1)} & = \frac{1}{\sqrt{2}} \left( e^{-i\omega (t_1 - t_0)} \rightket + e^{i\omega (t_1 - t_0)} \leftket \right) \\ +& = \frac{1}{2} \left[ \left( e^{-i\omega (t_1 - t_0)} + e^{i\omega (t_1 - t_0)} \right) \upket + \left( e^{-i\omega (t_1 - t_0)} - e^{i\omega (t_1 - t_0)} \right) \downket \right] \\ +& = \cos [\omega (t_1-t_0)] \upket - i \sin [\omega (t_1-t_0)] \downket +\end{align} +$$ + +となり、式{eq}`spin_exact`が再導出できます。 +We can therefore rederive Formula {eq}`spin_exact`. + +このように、ハミルトニアンの対角化さえできれば、量子ダイナミクスのシミュレーションは位相をかけて足し算をするだけの問題に帰着します。しかし、上で言及したように、計算量の問題から、ハミルトニアンが対角化できるのは主に系の次元が小さいときに限ります。「対角化」という言葉が示唆するように、この操作は行列演算(対角化)を伴い、その際の行列の大きさは$N \times N$です。上の核スピンの例では$N=2$でしたが、もっと実用的なシミュレーションの場合、系の量子力学的次元は一般的に関係する自由度の数(粒子数など)の指数関数的に増加します。比較的小規模な系でもすぐに対角化にスーパーコンピュータが必要なスケールになってしまいます。 +In this way, if we can simply perform Hamiltonian diagonalization, the simulation of the quantum dynamics becomes simply a matter of multiplying phases and then adding. However, as mentioned above, due to the issue of computational volume, Hamiltonian diagonalization can usually only be performed for systems with small dimensions. As the word "diagonalization" suggests, this operation involves matrix operations (diagonalization), so the size of the array will be $N \times N$. In the above nuclear spin example, $N=2$, but in a more practical simulation, the number of quantum mechanical dimensions in a system generally increases exponentially with respect to the related degree of freedom (number of particles, etc.). Even a relatively small system would require the use of a supercomputer to perform diagonalization. ++++ + +### 鈴木・トロッター分解 + +ハミルトニアンが対角化できない場合、ダイナミクスシミュレーションをするには、結局式{eq}`spin_exact`のように初期状態に時間発展演算子を愚直にかけていくことになります。これは、式{eq}`exp_sigmax`のように$U_H(t)$を閉じた形式で厳密に書けるなら簡単な問題ですが、そうでない場合は数値的に近似していく必要があります。その場合の常套手段は、行いたい時間発展$(t_1 - t_0)$を短い時間 +When Hamiltonian diagonalization is not possible, to perform a dynamics simulation, we would ultimately have to simply multiply the initial state by a time evolution operator, as in Formula {eq}`spin_exact`. If $U_H (t)$ could strictly expressed in closed form, as in Formula {eq}`exp_sigmax`, this would be a simple issue, but when it can't, we must numerically approximate it. The standard method for doing this is to break down the time evolution $(t_1 - t_0)$ finely, as follows: + +$$ +\Delta t = \frac{t_1 - t_0}{M}, \quad M \gg 1 +$$ + +に分割し、$\Delta t$だけの時間発展$U_H(\Delta t)$を考えることです。もちろん、$U_H(t)$が閉じた形式で書けないのなら当然$U_H(\Delta t)$も書けないので、時間を分割しただけでは状況は変わりません。しかし、$\Delta t$が十分短いとき、$U_H(\Delta t)$に対応する計算可能な近似演算子$\tilde{U}_{H;\Delta t}$を見つけることができる場合があり、この$\tilde{U}_{H;\Delta t}$での状態の遷移の様子がわかるのであれば、それを$M$回繰り返すことで、求める終状態が近似できることになります。 + +例えば、通常$H$はわかっており、任意の状態$\ket{\psi}$に対して$H\ket{\psi}$が計算できるので、$\mathcal{O}((\Delta t)^2)$を無視する近似で +Normally, we know $H$, so we can calculate $H\ket{\psi}$ for any state $\ket{\psi}$. For example, if we approximate, ignoring $\mathcal{O}((\Delta t)^2)$, we get the following: + +$$ +\tilde{U}_{H;\Delta t} = I - \frac{i \Delta t}{\hbar} H +$$ + +とすれば、まず$H\ket{\psi(t_0)}$を計算し、それを$i\Delta t/\hbar$倍して$\ket{\psi(t_0)}$から引き、その結果にまた$H$をかけて、…という具合に$\ket{\psi(t_1)}$が近似計算できます[^exact_at_limit]。 +First, we calculate $H\ket{\psi(t_0)}$ and then multiply that by $i\Delta t/\hbar$ and subtract from $\ket{\psi(t_0)}$. We multiply the result by $H$...and in this fashion we can approximately calculate $\ket{\psi(t_1)}$[^exact_at_limit]. + +しかし、このスキームは量子コンピュータでの実装に向いていません。上で述べたように量子コンピュータのゲートはユニタリ演算子に対応するのに対して、$I - i\Delta t / \hbar H$はユニタリでないからです。代わりに、量子コンピュータでのダイナミクスシミュレーションでよく用いられるのが鈴木・トロッター分解という近似法です{cite}`nielsen_chuang_dynamics`。 +However, this approach is not well-suited to implementation in a quantum computer. As discussed above, while quantum computer gates correspond to unitary operators, $I - i\Delta t / \hbar H$ is not unitary. Instead, what is often used in dynamics simulations on quantum computers is the Suzuki-Trotter transformation approximation method{cite}`nielsen_chuang_dynamics`. + +鈴木・トロッター分解が使えるケースとは、 + +- $U_H(t)$は量子回路として実装が難しい。 +- ハミルトニアンが$H = \sum_{k=1}^{L} H_k$のように複数の部分ハミルトニアン$\{H_k\}_k$の和に分解できる。 +- 個々の$H_k$に対しては$U_{H_k}(t) = \exp(-\frac{i t}{\hbar} H_k)$が簡単に実装できる。 + +のような場合です。もしも$H$や$H_k$が演算子ではなく単なる実数であれば、$\exp\left(\sum_k A_k\right) = \prod_k e^{A_k}$なので、$U_H(t) = \prod_k U_{H_k}(t)$となります。ところが、一般に線形演算子$A, B$に対して、特殊な条件が満たされる($A$と$B$が「可換」である)場合を除いて + +$$ +\exp(A + B) \neq \exp(A)\exp(B) +$$ + +なので、そのような簡単な関係は成り立ちません。しかし、 + +$$ +\exp \left(- \frac{i \Delta t}{\hbar} H \right) = \prod_{k=1}^{L} \exp \left(-\frac{i \Delta t}{\hbar} H_k \right) + \mathcal{O}((\Delta t)^2) +$$ + +という、Baker-Campbell-Hausdorfの公式の応用式は成り立ちます。これによると、時間分割の極限では、 + +$$ +\lim_{\substack{M \rightarrow \infty \\ \Delta t \rightarrow 0}} \left[ \prod_{k=1}^{L} \exp \left(-\frac{i \Delta t}{\hbar} H_k \right) \right]^M = \exp \left(-\frac{i}{\hbar} H (t_1 - t_0) \right). +$$ + +つまり、$U_H(\Delta t)$を + +$$ +\tilde{U}_{H;\Delta t} = \prod_k U_{H_k}(\Delta t) +$$ + +で近似すると、$[\tilde{U}_{H;\Delta t}]^M$と$U_H(t_1 - t_0)$の間の誤差は$\Delta t$を短くすることで[^sufficiently_small]いくらでも小さくできます。 + +鈴木・トロッター分解とは、このように全体の時間発展$U_H(t_1 - t_0)$を短い時間発展$U_H(\Delta t)$の繰り返しにし、さらに$U_H(\Delta t)$をゲート実装できる部分ユニタリの積$\prod_k U_{H_k}(\Delta t)$で近似する手法のことを言います。 + +[^exact_at_limit]: 実際、この手続きは$M \rightarrow \infty$の極限で厳密に$U(t_1 - t_0)$による時間発展となります。 +[^sufficiently_small]: 具体的には、$\Omega = H/\hbar, \Omega_k = H_k/\hbar$として$\exp(-i\Delta t \Omega) - \prod_{k} \exp(-i\Delta t \Omega_k) = (\Delta t)^2/2 \sum_{k \neq l} [\Omega_k, \Omega_l] + \mathcal{O}((\Delta t)^3)$なので、任意の状態$\ket{\psi}$について$(\Delta t)^2 \sum_{k \neq l} \bra{\psi} [\Omega_k, \Omega_l] \ket{\psi} \ll 1$が成り立つとき、$\Delta t$が十分小さいということになります。 +[^exact_at_limit]: This procedure produces the exact time evolution using $U(t_1 - t_0)$ at the limit of $M \rightarrow \infty$. +[^sufficiently_small]: Specifically, as $\Omega = H/\hbar$ and $\Omega_k = H_k/\hbar$, \exp(-i\Delta t \Omega) - \prod_{k} \exp(-i\Delta t \Omega_k) = (\Delta t)^2/2 \sum_{k \neq l} [\Omega_k, \Omega_l] + \mathcal{O}((\Delta t)^3)$, so when $(\Delta t)^2 \sum_{k \neq l} \bra{\psi} [\Omega_k, \Omega_l] \ket{\psi} \ll 1$ for a given state $\ket{\psi}$, $\Delta t$ will be sufficiently small. + ++++ + +### なぜ量子コンピュータが量子ダイナミクスシミュレーションに向いているか + +鈴木・トロッター分解がダイナミクスシミュレーションに適用できるには、ハミルトニアンが都合よくゲートで実装できる$H_k$に分解できる必要があります。これが常に成り立つかというと、答えはyes and noです。 +For the Suzuki-Trotter transformation to be applied to a dynamics simulation, it must be possible to conveniently break the Hamiltonian down into $H_k$ that can be implemented using gates. Is this normally the case? Well, yes and no. + +まず、$2^n$次元線形空間に作用するエルミート演算子は、$n$個の2次元部分系に独立に作用する基底演算子$\{I, \sigma^X, \sigma^Y, \sigma^Z\}$の積の線形和に分解できます。$\sigma^X$以外のパウリ演算子$\sigma^Y$と$\sigma^Z$はここまで登場しませんでしたが、重要なのは、2次元量子系に作用する$\sigma^X, \sigma^Y, \sigma^Z$がそれぞれ量子ビットに作用する$X, Y, Z$ゲート[^ygate]に、パウリ演算子の指数関数がそれぞれ$R_x, R_y, R_z$ゲート(総じて回転ゲートと呼びます)に対応するということです。つまり、対象の物理系の量子レジスタへの対応付けさえできれば、そのハミルトニアンは必ず基本的なゲートの組み合わせで表現できます。 +First, the Hermitian operators that can be used in a $2^n$-dimension linear space can be broken down into the linear sum of basis state operators $\{I, \sigma^X, \sigma^Y, \sigma^Z\}$ that act independently in n number of two dimensional systems. While we have seen Pauli operator $\sigma^X$, we have not yet seen Pauli operators $\sigma^Y$ and $\sigma^Z$. However, what is important is that $\sigma^X$, $\sigma^Y$, and $\sigma^Z$, which act on two-dimensional quantum systems, correspond to the $X$, $Y$, and $Z$ gates used on their respective quantum bits[^ygate], and that the Pauli operator exponential functions correspond, respectively, to gates $R_x$, $R_y$, and $R_Z$ (collectively, this is referred to as a rotation gate). In other words, if we can associate them with the quantum registers of the physical system, the Hamiltonian can always be expressed using a combination of basic gates. + +しかし、$n$ビットレジスタに作用する基底演算子の組み合わせは$4^n$通りあり、最も一般のハミルトニアンではその全ての組み合わせが寄与することも有りえます。その場合、指数関数的に多くのゲートを用いてしか時間発展演算子が実装できないことになります。それでは「都合よく分解できる」とは言えません。 +However, there are $4^n$ combinations of basis state operators in an $n$-bit register, and the most common Hamiltonian could apply to all of those combinations. In that case, the only way to implement the time evolution operator would be to use an exponentially large number of gates. This could not even charitably be described as "conveniently breaking it down." + +そもそも量子コンピュータで量子ダイナミクスシミュレーションを行う利点は、その計算効率にあります。 +The whole advantage of performing a quantum dynamics simulation on a quantum computer is supposed to be its computation efficiency. + +シミュレートする量子系の次元を$2^n$としたとき、古典計算機では、仮にハミルトニアンが対角化できても$2^n$回の位相因子の掛け算と同じ回数だけの足し算を行う必要があります。ハミルトニアンが対角化できず、時間を$M$ステップに区切って近似解を求めるとなると、必要な計算回数は$\mathcal{O}(2^nM)$となります。 +When the number of dimensions in a quantum system to be simulated is $2^n$, with a classical computer, even if it were possible to diagonalize the Hamiltonian, you would need to perform the same number of additions as the product of $2^n$ phase factors. If you were unable to diagonalize the Hamiltonian, and instead tried to approximate by slicing the time up into $M$ steps, you would need to perform $\mathcal{O}(2^nM)$ calculations. + +一方、同じ計算に$n$ビットの量子コンピュータを使うと、対角化できない場合のステップ数$M$は共通ですが、各ステップで必要な計算回数(=ゲート数)はハミルトニアン$H$の基底演算子への分解$H_k$の項数$L$で決まります。個々の$H_k$は一般に$\mathcal{O}(n)$ゲート要するので、計算回数は$\mathcal{O}(nLM)$です。したがって、$L$が$\mathcal{O}(1)$であったり$\mathcal{O}(\mathrm{poly}(n))$($n$の多項式)であったりすれば、量子コンピュータでの計算が古典のケースよりも指数関数的に早いということになります。 +However, if you used an $n$-bit quantum computer to perform the same calculations, if you were unable to perform diagonalization, the number of steps, $M$, would be the same, but the number of calculations during each step (=the number of gates) would be decided by the number of terms, $L$, when breaking Hamiltonian $H$ into basis state operators $H_k$. Each individual $H_k$ would generally require an $\mathcal{O}(n)$ gate, so the number of computations would be $\mathcal{O}(nLM)$. Therefore, if $L$ were $\mathcal{O}(1)$ or $\mathcal{O}(\mathrm{poly}(n))$ (an $n$th polynomial), the calculation by the quantum computer would be exponentially faster than calculation with a classical computer. + +したがって、逆に、ハミルトニアンが$4^n$通りの基底演算子に分解されてしまっては($L=4^n$)、量子コンピュータの利点が活かせません[^exponential_memory]。 +Therefore, conversely, if a Hamiltonian were broken down into $4^n$ basis state operators, then $L = 4^n$, and thus the advantages of the quantum computer could not be leveraged[^exponential_memory]. + +幸いなことに、通常我々がシミュレートしたいと思うような物理系では、$L$はせいぜい$\mathcal{O}(n^2)$で、$\mathcal{O}(n)$ということもしばしばあります。2体相互作用のある量子多体系などが前者にあたり、さらに相互作用が隣接した物体間のみである場合、後者が当てはまります。 +Fortunately, for the types of physical systems we usually wish to emulate, $L$ is often at most $\mathcal{O}(n^2)$, and often $\mathcal{O}(n)$. The former would correspond to a quantum many-body system with two-body interaction, and the latter would correspond to a system in which there is only mutual interaction with an adjacent body. + +[^ygate]: $Y$ゲートは変換$Y\ket{0} = i\ket{1}$、$Y\ket{1} = -i\ket{0}$を引き起こします。 +[^exponential_memory]: 古典計算機でのシミュレーションでは、一般的には全ての固有ベクトルの振幅を記録しておくためのメモリ($\mathcal{O}(2^n)$)も必要です。一方量子コンピュータでは(測定時に限られた情報しか取り出せないという問題はありますが)そのような制約がないので、指数関数的に多くのゲートを用いるハミルトニアンでも、一応後者に利点があると言えるかもしれません。 +[^ygate]: The $Y$ gate causes transformations $Y\ket{0} = i\ket{1}$ and $Y\ket{1} = -i\ket{0}$. +[^exponential_memory]: In a simulation using a classical computer, generally speaking, you would need ($\mathcal{O}(2^n)$) of memory to record the amplitudes of all of the eigenvectors. With a quantum computer, on the other hand, (although there is the issue of not being able to extract information except when performing measurement), this restriction does not apply, so even for Hamiltonians with an exponentially large number of gates, the quantum computer's benefits would outweigh those of the classical computer. + ++++ + +## 実習:ハイゼンベルグモデルの時間発展 + +### モデルのハミルトニアン + +ハミルトニアンの分解と言われてもピンと来ない方もいるかもしれませんし、ここからはダイナミクスシミュレーションの具体例をQiskitで実装してみましょう。 +The concept of "breaking down the Hamiltonian" may not click with some readers, so from here on, let's implement a specific dynamics simulation in Qiskit. + +ハイゼンベルグモデルという、磁性体のトイモデルを考えます。空間中一列に固定された多数のスピンを持つ粒子(電子)の系で、隣接スピンの向きによってエネルギーが決まるような問題です。 +Let us consider a toy model of a magnetic body, the Heisenberg model. It is a system in which particles (electrons) with various spins are fixed in a line in space, and the amount of energy is decided by the direction of the spin of adjacent particles. + +例えば、$n$スピン系で簡単な形式のハミルトニアンは +For example, a simplified Hamiltonian for the $n$-spin system would be as follows. + +```{math} +:label: heisenberg +H = -J \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j}) +``` + +です。ここで、$\sigma^{[X,Y,Z]}_j$は第$j$スピンに作用するパウリ演算子です。 +Here, $\sigma^{[X,Y,Z]}_j$is a Pauli operator that acts on the $j$-th spin. + +ただし、式{eq}`heisenberg`の和の記法には実は若干の省略があります。例えば第$j$項をより正確に書くと、 +There is actually a slight omission in the addition notation in Formula {eq}`heisenberg`. For example, if we were to properly write out the $j$-th element, it would be as follows. + +$$ +I_{n-1} \otimes \dots \otimes I_{j+2} \otimes \sigma^X_{j+1} \otimes \sigma^X_{j} \otimes I_{j-1} \otimes \dots I_{0} +$$ + +です。ここで$\otimes$は線形演算子間の「テンソル積」を表しますが、聞き慣れない方は掛け算だと思っておいてください。重要なのは、式{eq}`heisenberg`の各項が、上で触れたように$n$個の基底演算子の積になっているということです。さらに、この系では隣接スピン間の相互作用しか存在しないため、ハミルトニアンが$n-1$個の項に分解できています。 +Here, $\otimes$ represents the tensor product between the linear operators. If you are unfamiliar with the tensor product, think of it as multiplication. What is important is that each element in Formula {eq}`heisenberg` is the product of $n$ basis state operators, as mentioned above. Furthermore, in this system there is only mutual interaction between adjacent spins, so the Hamiltonian is broken down into $n-1$ elements. + +この系では、隣接スピン間の向きが揃っている(内積が正)のときにエネルギーが低くなります[^quantum_inner_product]。少し考えるとわかりますが、すべてのスピンが完全に同じ方向を向いている状態が最もエネルギーの低いエネルギー固有状態です。そこで、最低エネルギー状態から少しだけずらして、スピンが一つだけ直角方向を向いている状態を始状態としたときのダイナミクスをシミュレートしてみましょう。 +In this system, the amount of energy is low when the adjacent spin directions are aligned (the inner product is positive)[^quantum_inner_product]. If you think about it, this makes sense. The state when all of the spins are perfectly aligned in the same direction has the lowest energy eigenstate. Let's simulate the dynamics using a starting state that is slightly different from the lowest energy state -- the state when the spin of just one of the particles is perpendicular to the rest. + +核スピンのケースと同様に、それぞれのスピンについて+$Z$方向を向いた状態$\upket$を量子ビットの状態$\ket{0}$に、-$Z$方向の状態$\downket$を$\ket{1}$に対応づけます。このとき、上で見たように、パウリ演算子$\sigma^X, \sigma^Y, \sigma^Z$と$X, Y, Z$ゲートとが対応します。また、$J=\hbar\omega/2$とおきます。 +As with the nuclear spin example, let us assign states in which the spin is in the +$Z$ direction, $\upket$, to the quantum bit state $\ket{0}$, and states in which the spin is in the -$Z$ direction, $\downket$, to the quantum bit state $\ket{1}$. Here, as we saw above, the Pauli operators $\sigma^X$, $\sigma^Y$, and $\sigma^Z$ correspond to gates $X$, $Y$, and $Z$. Let us also state that $J=\hbar\omega/2$. + +時間発展演算子は +The time evolution operator is as follows. + +$$ +U_H(t) = \exp \left[ \frac{i\omega t}{2} \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j}) \right] +$$ + +ですが、ハミルトニアンの各項が互いに可換でないので、シミュレーションでは鈴木・トロッター分解を用いて近似します。各時間ステップ$\Delta t$での近似時間発展は +The Hamiltonian elements are not mutually variable, so we will use Suzuki-Trotter transformation to perform approximation in this simulation. The approximate time evolution for each time step $\Delta t$ is as follows. + +$$ +\tilde{U}_{H;\Delta t} = \prod_{j=0}^{n-2} \exp\left( \frac{i \omega \Delta t}{2} \sigma^X_{j+1}\sigma^X_{j} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Y_{j+1}\sigma^Y_{j} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j} \right) +$$ + +です。 + +### 量子ゲートでの表現 + +これを回転ゲートと制御ゲートで表します。まず$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$について考えてみましょう。この演算子の$j$-$(j+1)$スピン系の4つの基底状態への作用は +Let us express this with a rotation gate and a controlled gate. First, let's consider $\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$. This operator acts on the four basis states of a $j$-$(j+1)$ spin system as follows. + +$$ +\begin{align} +\upket_{j+1} \upket_{j} \rightarrow e^{i \omega \Delta t / 2} \upket_{j+1} \upket_{j} \\ +\upket_{j+1} \downket_{j} \rightarrow e^{-i \omega \Delta t / 2} \upket_{j+1} \downket_{j} \\ +\downket_{j+1} \upket_{j} \rightarrow e^{-i \omega \Delta t / 2} \downket_{j+1} \upket_{j} \\ +\downket_{j+1} \downket_{j} \rightarrow e^{i \omega \Delta t / 2} \downket_{j+1} \downket_{j} +\end{align} +$$ + +です。つまり、2つのスピンの「パリティ」(同一かどうか)に応じて、かかる位相の符号が違います。 +In other words, the sign of the phase is dependent on the parity of the two spins. + +パリティに関する演算をするにはCNOTを使います。例えば以下の回路 +The CNOT gate is used to perform operations related to parity. For example, consider the following circuit. + +[^quantum_inner_product]: これは量子力学的な系なので、もっと正確な表現は「隣接スピン間の内積が正であるようなハミルトニアンの固有状態の固有値が、そうでない固有状態の固有値より小さい」です。 +[^quantum_inner_product]: This is a quantum mechanics system, so the more accurate way to express this would be to say that "the eigenvalues of the inherent states of the Hamiltonian when the inner products of adjacent spins are positive are greater than the eigenvalues of inherent states when that is not the case." + +```{code-cell} ipython3 +:tags: [remove-input] + +circuit = QuantumCircuit(QuantumRegister(2, 'q')) +circuit.cx(0, 1) +circuit.rz(Parameter(r'-$\omega \Delta t$'), 1) +circuit.cx(0, 1) +circuit.draw('mpl') +``` + +によって、計算基底$\ket{00}, \ket{01}, \ket{10}, \ket{11}$はそれぞれ +This circuit transforms the computational basis states $\ket{00}$, $\ket{01}$, $\ket{10}$, and $\ket{11}$ as follows (double-check yourself). + +$$ +\begin{align} +\ket{00} \rightarrow e^{i \omega \Delta t / 2} \ket{00} \\ +\ket{01} \rightarrow e^{-i \omega \Delta t / 2} \ket{01} \\ +\ket{10} \rightarrow e^{-i \omega \Delta t / 2} \ket{10} \\ +\ket{11} \rightarrow e^{i \omega \Delta t / 2} \ket{11} +\end{align} +$$ + +と変換するので(確認してください)、まさに$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$の表現になっています。 +This is exactly as expressed with $\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$. + +残りの2つの演算子も同様にパリティに対する回転で表せますが、CNOTで表現できるのは$Z$方向のパリティだけなので、先にスピンを回転させる必要があります。$\exp(\frac{i \omega \Delta t}{2} \sigma^X_{j+1}\sigma^X_{j})$による変換は +The remaining two operators can also be expressed as rotation with respect to parity, but CNOT can only be used to express parity in the $Z$ direction, so the spin must first be rotated. If we perform transformation using $\exp(\frac{i \omega \Delta t}{2} \sigma^X_{j+1}\sigma^X_{j})$, we get the following. + +$$ +\begin{align} +\rightket_{j+1} \rightket_{j} \rightarrow e^{i \omega \Delta t / 2} \rightket_{j+1} \rightket_{j} \\ +\rightket_{j+1} \leftket_{j} \rightarrow e^{-i \omega \Delta t / 2} \rightket_{j+1} \leftket_{j} \\ +\leftket_{j+1} \rightket_{j} \rightarrow e^{-i \omega \Delta t / 2} \leftket_{j+1} \rightket_{j} \\ +\leftket_{j+1} \leftket_{j} \rightarrow e^{i \omega \Delta t / 2} \leftket_{j+1} \leftket_{j} +\end{align} +$$ + +で、式{eq}`left_right_kets`から、次の回路が対応する変換を引き起こすことがわかります(これも確認してください)。 +Then, given Formula {eq}`left_right_kets`, we know that we must perform the transformation that corresponds to the next circuit (double-check this yourself, as well). + +```{code-cell} ipython3 +:tags: [remove-input] + +circuit = QuantumCircuit(QuantumRegister(2, 'q')) +circuit.h(0) +circuit.h(1) +circuit.cx(0, 1) +circuit.rz(Parameter(r'-$\omega \Delta t$'), 1) +circuit.cx(0, 1) +circuit.h(0) +circuit.h(1) +circuit.draw('mpl') +``` + +最後に、$\exp(\frac{i \omega \Delta t}{2} \sigma^Y_{j+1}\sigma^Y_{j})$に対応する回路は +Ultimately, the circuit that corresponds to $\exp(\frac{i \omega \Delta t}{2} \sigma^Y_{j+1}\sigma^Y_{j})$ is as follows[^sgate]. + +```{code-cell} ipython3 +:tags: [remove-input] + +circuit = QuantumCircuit(QuantumRegister(2, 'q')) +circuit.p(-np.pi / 2., 0) +circuit.p(-np.pi / 2., 1) +circuit.h(0) +circuit.h(1) +circuit.cx(0, 1) +circuit.rz(Parameter(r'-$\omega \Delta t$'), 1) +circuit.cx(0, 1) +circuit.h(0) +circuit.h(1) +circuit.p(np.pi / 2., 0) +circuit.p(np.pi / 2., 1) +circuit.draw('mpl') +``` + +です[^sgate]。 + +### 回路実装 + +やっと準備が整ったので、シミュレーションを実装しましょう。実機で走らせられるように、$n=5$, $M=10$, $\omega \Delta t = 0.1$とします。上で決めたように、ビット0以外が$\upket$、ビット0が$\rightket$という初期状態から始めます。各$\Delta t$ステップごとに回路のコピーをとり、それぞれのコピーで測定を行うことで、時間発展の様子を観察します。 +We have finally completed our preparations, so let's implement the simulation. Let us set $n=5$, $M=10$, and $\omega \Delta t = 0.1$ so that the circuit can be run on the actual QC. As we decided above, let us begin with all bits other than bit 0 in the $\upket$ state and bit 0 in the $\rightket$ state. We create a copy of the circuit for each $\Delta t$ step and perform measurement with each copy to observe the time evolution. + +[^sgate]: $P(\pi/2)$ゲートは$S$ゲートとも呼ばれます。$P(-\pi/2)$は$S^{\dagger}$です。 +[^sgate]: The $P(\pi/2)$ gate is also called an $S$ gate. $P(-\pi/2)$ is $S^{\dagger}$. + +```{code-cell} ipython3 +# まずは全てインポート +import numpy as np +from qiskit import QuantumCircuit, transpile +from qiskit.tools.monitor import job_monitor +from qiskit_aer import AerSimulator +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_ibm_provider.accounts import AccountNotFoundError +# このワークブック独自のモジュール +from qc_workbook.dynamics import plot_heisenberg_spins +from qc_workbook.utils import operational_backend +``` + +```{code-cell} ipython3 +n_spins = 5 +M = 10 +omegadt = 0.1 + +circuits = [] + +circuit = QuantumCircuit(n_spins) + +# 第0ビットを 1/√2 (|0> + |1>) にする +circuit.h(0) + +# Δtでの時間発展をM回繰り返すループ +for istep in range(M): + # ハミルトニアンのn-1個の項への分解に関するループ + for jspin in range(n_spins - 1): + # ZZ + circuit.cx(jspin, jspin + 1) + circuit.rz(-omegadt, jspin + 1) + circuit.cx(jspin, jspin + 1) + + # XX + circuit.h(jspin) + circuit.h(jspin + 1) + circuit.cx(jspin, jspin + 1) + circuit.rz(-omegadt, jspin + 1) + circuit.cx(jspin, jspin + 1) + circuit.h(jspin) + circuit.h(jspin + 1) + + # YY + circuit.p(-np.pi / 2., jspin) + circuit.p(-np.pi / 2., jspin + 1) + circuit.h(jspin) + circuit.h(jspin + 1) + circuit.cx(jspin, jspin + 1) + circuit.rz(-omegadt, jspin + 1) + circuit.cx(jspin, jspin + 1) + circuit.h(jspin) + circuit.h(jspin + 1) + circuit.p(np.pi / 2., jspin) + circuit.p(np.pi / 2., jspin + 1) + + # この時点での回路のコピーをリストに保存 + # measure_all(inplace=False) はここまでの回路のコピーに測定を足したものを返す + circuits.append(circuit.measure_all(inplace=False)) + +print(f'{len(circuits)} circuits created') +``` + +量子回路シミュレーターで実行し、各ビットにおける$Z$方向スピンの期待値をプロットしましょう。プロット用の関数は比較的長くなってしまいますが実習の本質とそこまで関係しないので、[別ファイル](https://github.com/UTokyo-ICEPP/qc-workbook/blob/master/source/utils/dynamics.py)に定義してあります。関数はジョブの実行結果、系のスピンの数、初期状態、ステップ間隔を引数にとります。 +Let's run this on a quantum circuit simulator and plot the expectation values of the $Z$ direction spins of each bit. The functions to be plotted are relatively long, but they aren't that important to the key points of this exercise, so they are defined in a [separate file](https://github.com/UTokyo-ICEPP/qc-workbook/blob/master/source/utils/dynamics.py). The functions use as their arguments the job execution results, the spin numbers of the system, the initial state, and the step spacing. + +```{code-cell} ipython3 +# 初期状態 |0> x |0> x |0> x |0> x 1/√2(|0>+|1>) は配列では [1/√2 1/√2 0 0 ...] +initial_state = np.zeros(2 ** n_spins, dtype=np.complex128) +initial_state[0:2] = np.sqrt(0.5) + +shots = 100000 + +simulator = AerSimulator() + +circuits_sim = transpile(circuits, backend=simulator) +sim_job = simulator.run(circuits_sim, shots=shots) +sim_counts_list = sim_job.result().get_counts() + +plot_heisenberg_spins(sim_counts_list, n_spins, initial_state, omegadt, add_theory_curve=True) +``` + +ビット0でのスピンの不整合が徐々に他のビットに伝搬していく様子が観察できました。 +As you can see, the mismatch of the spin of bit 0 gradually propagates to the other bits. + +また、上のように関数`plot_heisenberg_spins`に`add_theory_curve=True`という引数を渡すと、ハミルトニアンを対角化して計算した厳密解のカーブも同時にプロットします。トロッター分解による解が、厳密解から少しずつずれていっている様子も観察できます。興味があれば$\Delta t$を小さく($M$を大きく)して、ずれがどう変わるか確認してみてください。 +If the `add_theory_curve=True` argument is passed to the `plot_heisenberg_spins` function, as shown above, the curves of the exact solution, calculated after diagonalizing the Hamiltonian, can also be plotted. As you can see, the solution produced by the Suzuki-Trotter transformation is slightly off from the exact solution at each point. If you are interested, try making the $\Delta t$ smaller (making the $M$ larger) and check how this changes the amount of deviation from the exact solution. + +実機でも同様の結果が得られるか確認してみましょう。 +Let's confirm whether you get the same results when using the actual QC. + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# よりアクセス権の広いプロバイダを使える場合は、下を書き換える +instance = 'ibm-q/open/main' + +try: + provider = IBMProvider(instance=instance) +except IBMQAccountCredentialsNotFound: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) + +backend_list = provider.backends(filters=operational_backend(min_qubits=n_spins, min_qv=32)) +backend = least_busy(backend_list) + +print(f'Job will run on {backend.name()}') +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +circuits_ibmq = transpile(circuits, backend=backend) + +job = backend.run(circuits_ibmq, shots=8192) + +job_monitor(job, interval=2) + +counts_list = job.result().get_counts() +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +plot_heisenberg_spins(counts_list, n_spins, initial_state, omegadt) +``` diff --git a/source/en/extreme_simd.md b/source/en/extreme_simd.md new file mode 100644 index 00000000..0dcade8c --- /dev/null +++ b/source/en/extreme_simd.md @@ -0,0 +1,693 @@ +--- +jupytext: + formats: md:myst,ipynb + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 計算をする量子回路の実装 + ++++ + +量子計算をする回路の感覚がつかめてきたところで、量子計算をするとはどういうことかに話を移して行きましょう。 +Now that you've gotten a feeling for how to generate quantum states, let's move on to how to perform quantum calculations. + +```{contents} 目次 +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ + ++++ + +## 量子計算の特徴 + +量子計算をするとはどういうことかを一概に決めることはできませんし、それ自体がまだ世界中で盛んに研究されているトピックです。ただ、現在知られている量子コンピュータの実用法に共通する大まかな特徴を挙げるならば、以下の2点になります。 +There is no way to categorically describe how to perform quantum calculation. This is still an area of intensive research throughout the world. However, there are two major characteristics shared by the currently understood methods of using quantum computers. + +- **並列性を利用する**:Equal superposition状態で見たように、$n$量子ビットがあるとき、比較的簡単な操作で$2^n$個の計算基底が顕に登場する状態が作れます。また、この状態には全ての量子ビットが関与するため、どのビットに対するゲート操作も全ての計算基底に影響を及ぼします。つまり、各ゲート操作が常に$2^n$重の並列演算となります。 +- **干渉を利用する**:量子振幅は複素数なので、二つの振幅が足し合わされるとき、それぞれの位相によって和の振幅の値が変わります。特に絶対値が等しく位相が逆である($\pi$だけ異なる)場合和が0となるので、そのことを利用して回路の量子状態の重ね合わせから特定の計算基底を除くといったことが可能です。 +- **Using parallelism**: As you saw with the equal superposition state, if you have $n$ quantum bits, you can openly produce $2^n$ computational bases using fairly simple operations. All quantum bits are involved in this state, so if you apply a gate to one bit, it will affect all of the computational bases. In other words, each gate operation is always performed in parallel in $2^n$ instances. +- **Using interference**: The quantum amplitude is a complex number, so when two amplitudes are added, the phases of the two affect the value of the resulting amplitude. When absolute values are identical but phases are inverted (differing only by $\pi$), the sum is 0. Using this fact, superpositions of circuit quantum states can be used to eliminate specific computational bases. + +この2つの要素のうち、特に干渉を上手に使ったアルゴリズムを見出すのが難しいため、量子コンピュータの応用可能性にまだ未知数な部分が大きい、というのが現状です。 +Of these two factors, creating algorithms that use interference well is particularly difficult, so the reality is that there is still much that is unknown regarding the applicability of quantum computers. + +## 巨大SIMDマシンとしての量子コンピュータ + +SIMD (single instruction multiple data)とは並列計算パラダイムの一つで、プロセッサの命令(instruction)が多数のデータに同時に適用されるケースを指します。私達の身の回りの(古典)コンピュータのプロセッサにもSIMD用のインストラクションセットが搭載されており、例えば(2021年11月現在)最先端の商用CPUでは、16個の単精度浮動小数点数に対し同時に四則演算や平方根の計算を行えます。 +Single instruction multiple data (SIMD) is a parallel computing paradigm that refers to applying processor instructions to a large number of data simultaneously. The processors of the (classical) computers around us also have SIMD instruction sets. For example (as of February 2021), cutting-edge commercial CPUs can simultaneously perform basic arithmetic operations and square root calculations on 16 single precision floating point number. + +量子コンピュータでは、すべてのゲートがすべての計算基底に作用します。ゲート操作を命令、各計算基底の振幅をデータとして解釈すれば、これは常に$2^n$個のデータに命令を与えながら計算をしていることにあたります。量子コンピュータは巨大SIMDマシンとも考えられるのです。 +As mentioned in the first exercise, in quantum computers, gates are applied to all computational bases. If we think of gate operations as instructions and the amplitudes of computational bases as data, this means that they always perform computation while issuing instructions to $2^n$ items of data. A quantum computer can be thought of as a massive SIMD machine. + +ただし、すでに触れられたことですが、巨大並列計算ができたとしても、そのデータをすべて引き出すことはできません[^and_you_dont_want_to]ので、古典計算機のSIMDとはいろいろな意味で単純に比較できるものではありません。 +However, another thing that we have discussed briefly is that even if massively parallel calculation were possible, it is not possible to extract all of that data[^and_you_dont_want_to], so, in various senses, you can't simply compare QCs to classical computer SIMD devices. + +とはいえ、並列計算を行っているんだということを実感できると、量子コンピュータの使い道のイメージも湧きやすくなると思われますので、今回の実習では量子計算の並列性が顕著に現れるような例を見ていきましょう。 +That said, getting a sense that you're actually performing parallel computing will better acclimate you to using quantum computers, so in this section we're going to look at circuits for performing numerous parallel calculations using the simplest operation, addition. + +[^and_you_dont_want_to]: そもそも、例えば65量子ビットの計算機からすべてのデータを保存しようと思うと、各振幅を128(古典)ビットの浮動小数点複素数で表現したとすれば512EiB (エクサバイト)のストレージが必要です。これはだいたい現在インターネットを行き来する情報二ヶ月分に相当するので、保存するファシリティを作るにはそれなりの投資が必要です。 +[^and_you_dont_want_to]: If, for example, you attempted to save all of the data from a 65 quantum bit computer, using a 128 (classic) bit floating point complex number for each amplitude, you would need 512 EiB (exbibytes) of storage space. This is equivalent to roughly two months of the world's internet traffic, and building a facility capable of storing all of this data would be a major investment. ++++ + +(QFT)= +## 量子フーリエ変換 + +量子計算で並列性と振幅の干渉を巧みに利用した顕著な例として、量子フーリエ変換(Quantum Fourier transform, QFT)というサブルーチン(アルゴリズムの部品)があります。QFTは{doc}`Shorの素因数分解 `を含め多くのアルゴリズムに応用されています{cite}`nielsen_chuang_qft`。現在知られている中で最も重要な量子サブルーチンと言っても過言ではないでしょう。 + +QFTとは、$n$量子ビットレジスタの状態$\ket{j} \, (j \in \{0,1,\dots,2^n-1\})$を以下のように変換する操作です。 + +$$ +U_{\mathrm{QFT}} \ket{j} = \frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i jk/2^n} \ket{k} +$$ + +QFTは量子回路で実装でき、線形なので、状態$\ket{\psi} = \sum_{j=0}^{2^n-1} c_j \ket{j}$に対しては + +$$ +\begin{split} +U_{\mathrm{QFT}} \ket{\psi} & = \frac{1}{\sqrt{2^n}} \sum_{j=0}^{2^n-1} c_j \sum_{k=0}^{2^n-1} e^{2\pi i jk/2^n} \ket{k} \\ +& = \frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^n-1} \tilde{c}_k \ket{k} \quad \left( \tilde{c}_k = \sum_{j=0}^{2^n-1} c_j e^{2\pi i jk/2^n} \right) +\end{split} +$$ + +となり、振幅$\{c_j\}_j$の離散フーリエ変換が引き起こされることがわかります。 + +古典計算機で$2^n$個のサンプルの離散フーリエ変換を計算するには$\mathcal{O}(n2^n)$回の演算が必要であることが知られています。一方、QFTは最も効率的な実装{cite}`qft_nlogn`で$\mathcal{O}(n \log n)$個のゲートしか利用しません(下の実装では$\mathcal{O}(n^2)$)。つまり、QCは古典計算機に比べて指数関数的に早くフーリエ変換を実行できます。 +With a classical computer, calculating discrete Fourier transform of $2^n$ samples is known to require the calculation of $\mathcal{O}(n2^n)$. However, QFT uses only $\mathcal{O}(n \log n)$ gates with the most efficient implementation, {cite}`qft_nlogn` (in the implementation below, $\mathcal{O}(n^2)$). In other words, a QC can perform Fourier transforms exponentially faster than a classical computer. + +例として$n=6$の時のQFT回路を載せておきます。 + +```{code-cell} ipython3 +:tags: [remove-output] + +# まずは全てインポート +import numpy as np +import matplotlib.pyplot as plt +from IPython.display import Math +from qiskit import QuantumRegister, QuantumCircuit, transpile +from qiskit.tools.monitor import job_monitor +from qiskit_aer import AerSimulator +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_ibm_provider.accounts import AccountNotFoundError +from qc_workbook.show_state import statevector_expr +from qc_workbook.optimized_additions import optimized_additions +from qc_workbook.utils import operational_backend, find_best_chain + +print('notebook ready') +``` + +```{code-cell} ipython3 +num_qubits = 6 + +circuit = QuantumCircuit(num_qubits) + +# 具体的にするため、入力状態を|23>とする +j = 23 + +## jの2進数表現で値が1になっているビットに対してXを作用させる -> 状態|j>を作る + +# まずjの2進数表現を得るために、unpackbitsを利用(他にもいろいろな方法がある) +# unpackbitsはuint8タイプのアレイを引数に取るので、jをその形に変換してから渡している +j_bits = np.unpackbits(np.asarray(j, dtype=np.uint8), bitorder='little') +# 次にj_bitsアレイのうち、ビットが立っているインデックスを得る +j_indices = np.nonzero(j_bits)[0] +# 最後にcircuit.x() +for idx in j_indices: + circuit.x(idx) + +## Alternative method +#for i in range(num_qubits): +# if ((j >> i) & 1) == 1: +# circuit.x(i) + +circuit.barrier() + +## ここからがQFT + +# n-1から0まで標的ビットについてループ +for itarg in range(num_qubits - 1, -1, -1): + # 標的ビットにアダマールゲートをかける + circuit.h(itarg) + # target - 1から0まで制御ビットについてループ + for ictrl in range(itarg - 1, -1, -1): + # 標的と制御ビットのインデックスに応じた角度で制御Pゲートをかける + power = ictrl - itarg - 1 + num_qubits + circuit.cp((2 ** power) * 2. * np.pi / (2 ** num_qubits), ictrl, itarg) + + # 回路図を見やすくするためにバリアを入れる + circuit.barrier() + +# 最後にビットの順番を反転させる +for i in range(num_qubits // 2): + circuit.swap(i, num_qubits - 1 - i) + +## ここまでがQFT + +circuit.draw('mpl') +``` + +```{code-cell} ipython3 +sqrt_2_to_n = 2 ** (num_qubits // 2) +amp_norm = (1. / sqrt_2_to_n, r'\frac{1}{%d}' % sqrt_2_to_n) +phase_norm = (2 * np.pi / (2 ** num_qubits), r'\frac{2 \pi i}{%d}' % (2 ** num_qubits)) +expr = statevector_expr(circuit, amp_norm=amp_norm, phase_norm=phase_norm) +Math(expr) +``` + +上の回路でどうQFTが実現できるのか、初見で一目でわかる人は世界で数人しかいないでしょう。実際に状態を書き下しながら回路を追ってみましょう。 + +```{toggle} +まずビットn-1に着目します。前回の{ref}`equal_superposition_with_phase`の導出で行ったのと同様に、$j_m \, (m=0,\dots,n-1, \, j_m=0,1)$を使って$j$の二進数表現を$j=\sum_{m=0}^{n-1} 2^m j_m$としておきます。ビットn-1の初期状態は$\ket{j_{n-1}}_{n-1}$なので、アダマールゲートをかけると + +$$ +H\ket{j_{n-1}}_{n-1} = \frac{1}{\sqrt{2}} \left[\ket{0}_{n-1} + e^{2 \pi i \frac{j_{n-1}}{2}} \ket{1}_{n-1}\right] +$$ + +です。さらに、$j_{0}, \dots j_{n-2}$の情報をこのビットに盛り込むためにビット0からn-2まででそれぞれ制御した$C^m_{n-1}[P]$を使います。ただし、かける位相は制御ビットごとに異なります。 + +$$ +\begin{align} +& C^{n-2}_{n-1}\left[P\left(\frac{2^{n-2} \cdot 2 \pi}{2^n}\right)\right] \cdots C^{0}_{n-1}\left[P\left(\frac{2 \pi}{2^n}\right)\right] (H\ket{j_{n-1}}_{n-1}) \ket{j_{n-2}}_{n-2} \cdots \ket{j_0}_0 \\ += & \frac{1}{\sqrt{2}} \left[\ket{0}_{n-1} + \exp \left(2 \pi i \frac{\sum_{m=0}^{n-1} 2^{m} j_m}{2^n}\right) \ket{1}_{n-1}\right] \ket{j_{n-2}}_{n-2} \cdots \ket{j_0}_0 +\end{align} +$$ + +次に、ビットn-2にも同様の操作をします。ただし、制御はビットn-3以下からのみです。 + +$$ +\begin{align} +& C^{n-3}_{n-2}\left[P\left(\frac{2^{n-2} \cdot 2 \pi}{2^n}\right)\right] \cdots C^{0}_{n-2}\left[P\left(\frac{2 \cdot 2 \pi}{2^n}\right)\right] (H\ket{j_{n-2}}_{n-2}) \cdots \ket{j_0}_0 \\ += & \frac{1}{\sqrt{2}} \left[\ket{0}_{n-2} + \exp \left(2 \pi i \frac{2 \sum_{m=0}^{n-2} 2^{m} j_m}{2^n}\right) \ket{1}_{n-2}\right] \ket{j_{n-3}}_{n-3} \cdots \ket{j_0}_0 +\end{align} +$$ + +これをビット0まで繰り返します。制御は常に位数の小さいビットからです。 + +$$ +\begin{align} +& C^{n-4}_{n-3}\left[P\left(\frac{2^{n-2} \cdot 2 \pi}{2^n}\right)\right] \cdots C^{0}_{n-3}\left[P\left(\frac{2^2 \cdot 2 \pi}{2^n}\right)\right] (H\ket{j_{n-3}}_{n-3}) \cdots \ket{j_0}_0 \\ += & \frac{1}{\sqrt{2}} \left[\ket{0}_{n-3} + \exp \left(2 \pi i \frac{2^2 \sum_{m=0}^{n-3} 2^{m} j_m}{2^n}\right) \ket{1}_{n-3}\right] \ket{j_{n-4}}_{n-4} \cdots \ket{j_0}_0 +\end{align} +$$ + +$$ +\dots +$$ + +$$ +\begin{align} +& C^{0}_{1}\left[P\left(\frac{2^{n-2} \cdot 2 \pi}{2^n}\right)\right] (H\ket{j_{1}}_{1}) \ket{j_0}_0 \\ += & \frac{1}{\sqrt{2}} \left[\ket{0}_{1} + \exp \left(2 \pi i \frac{2^{n-2} \sum_{m=0}^{1} 2^{m} j_m}{2^n}\right) \ket{1}_{1}\right] \ket{j_0}_0 +\end{align} +$$ + +$$ +\begin{align} +& H\ket{j_0}_0 \\ += & \frac{1}{\sqrt{2}} \left[\ket{0}_0 + \exp \left(2 \pi i \frac{2^{n-1} \sum_{m=0}^{0} 2^{m} j_m}{2^n}\right) \ket{1}_{0}\right]. +\end{align} +$$ + +ここまでの全ての操作を順に適用すると、 + +$$ +(H_0) (C^{0}_{1}[P]H_1) \cdots (C^{n-2}_{n-1}[P] \cdots C^{0}_{n-1}[P]H_{n-1}) \, \ket{j_{n-1}}_{n-1} \ket{j_{n-2}}_{n-2} \cdots \ket{j_{0}}_0 \\ += \frac{1}{\sqrt{2^n}} \left[\ket{0}_{n-1} + \exp \left(2 \pi i \frac{\sum_{m=0}^{n-1} 2^{m} j_m}{2^n}\right) \ket{1}_{n-1}\right] \cdots \left[\ket{0}_0 + \exp \left(2 \pi i \frac{2^{n-1} \sum_{m=0}^{0} 2^{m} j_m}{2^n}\right) \ket{1}_0\right]. +$$ + +$\newcommand{tk}{\bar{k}}$ + +全ての量子ビットに$\ket{0}$と$\ket{1}$が現れるので、右辺は振幅$\{c_k\}_k$を用いて$\sum_{k=0}^{2^n-1} c_k \ket{k}$の形で表せます。後の利便性のために計算基底のラベルを$k$ではなく$\tk$とし、$\tk$の二進数表現$\tk=\sum_{l=0}^{n-1} 2^l \tk_{l}$を使うと + +$$ +c_{\tk} = \exp \left(\frac{2 \pi i}{2^n} \sum_{l=0}^{n-1}\sum_{m=0}^{l} 2^{n-1-l+m} j_m \tk_l \right). +$$ + +二重和が厄介な形をしていますが、$j_m, \tk_l \in {0, 1}$なので、$n-1-l+m >= n$のとき和の中身が$2^n$の倍数となり、$\exp (2 \pi i / 2^n \cdot a 2^n) = 1 \, \forall a \in \mathbb{N}$であることを用いると、$m$についての和を$n-1$までに拡張できます。さらに$k_l = \tk_{n-1 - l}$と定義すると、 + +$$ +c_{\tk} = \exp \left(\frac{2 \pi i}{2^n} \sum_{m=0}^{n-1} 2^m j_m \sum_{l=0}^{n-1} 2^l k_l \right). +$$ + +つまり、ここまでの操作で得られる状態は + +$$ +\frac{1}{\sqrt{2^n}} \sum_{k_l=0,1} \exp \left(\frac{2 \pi i}{2^n} j \sum_{l=0}^{n-1} 2^l k_l \right) \ket{k_0}_{n-1} \cdots \ket{k_{n-1}}_0 +$$ + +です。 + +最後にSWAPを使ってビット順序を逆転させると、 + +$$ +\begin{split} +& \frac{1}{\sqrt{2^n}} \sum_{k_l=0,1} \exp \left(\frac{2 \pi i}{2^n} j \sum_{l=0}^{n-1} 2^l k_l \right) \ket{k_{n-1}}_{n-1} \cdots \ket{k_{0}}_0 \\ += & \frac{1}{\sqrt{2^n}} \sum_{k} \exp \left(\frac{2 \pi i}{2^n} j k \right) \ket{k} +\end{split} +$$ + +が得られます。 +``` + ++++ + +(fourier_addition)= +## 量子フーリエ変換による足し算 + +これまで量子「計算機」の話をしていながら、単純であるはずの四則演算のやりかたについて触れていませんでした。理由は、実は量子コンピュータでは四則演算がそんなに単純でないから、です。 +Until now, we've been talking about quantum "computers," but we haven't actually talked about basic arithmetic operations, which should be simple. This is because in quantum computers, basic arithmetic operation are not so simple. + +足し算を行う量子サブルーチンはいくつか知られていますが、その中で量子ビットの数や用いるゲートの種類の面で効率的なのが、フーリエ変換を用いたものです{cite}`quantum_addition`。ただの足し算にフーリエ変換を持ち出すのは奇妙に思えますが、実際に動かしてみるとなかなかスマートな手法であることがわかります。 +There are several known subroutines for performing addition. Of these, {cite}`quantum_addition`, which uses the Fourier transform, is particularly efficient in terms of the number of quantum bits and the types of gates used. It may seem strange to use Fourier transform for something as simple as addition, but when you actually put the circuit to use, you'll see that this is quite a smart approach. + +このサブルーチンは自然数$a$と$b$が計算基底で表現されている二つの入力レジスタと一つの出力レジスタを使用し、以下のように状態を移します。 + +$$ +\ket{0}_{\mathrm{out}}\ket{b}_{\mathrm{in2}}\ket{a}_{\mathrm{in1}} \rightarrow \ket{a+b}_{\mathrm{out}}\ket{b}_{\mathrm{in2}}\ket{a}_{\mathrm{in1}} +$$ + +計算の流れをかいつまんで言うと、まず出力レジスタがequal superposition状態に初期化され、そこに二つの入力レジスタを制御とした$C[P]$ゲートがかけられていきます。ポイントは$C[P]$を利用することで出力レジスタの計算基底の位相に$a + b$が現れることで、最後にそこに対して逆フーリエ変換(Inverse QFT)を行うと、今度は出力レジスタが$a + b$に対応した計算基底状態になるという仕組みです。 + +次のセルで定義された`setup_addition`関数がサブルーチンの実装です。コード中`circuit.h()`に量子ビット番号ではなくレジスタオブジェクトを渡しています。`setup_addition`のコード中にも説明がありますが、Qiskitでは便利のために、`QuantumObject`クラスの1量子ビットゲートのメソッドに量子ビット番号だけでなく、番号のリストやレジスタを渡して、含まれるすべての量子ビットに同じ操作をかけることができるようになっています。 + +```{code-cell} ipython3 +:tags: [remove-output] + +def setup_addition(circuit, reg1, reg2, reg3): + # reg3にequal superpositionを生成 + # QuantumCircuitの1量子ビットゲートに対応するメソッド(circuit.hなど)に単一の量子ビットの代わりに + # レジスタや量子ビットのリストを渡すと、含まれる全ての量子ビットに同じゲートをかけてくれる + circuit.h(reg3) + + # 位相の単位(dphiの整数倍の位相をCPゲートでかけていく) + dphi = 2. * np.pi / (2 ** reg3.size) + + # reg1とreg2それぞれの量子ビットで制御する + for reg_ctrl in [reg1, reg2]: + # 制御ビットに関するループ + for ictrl, qctrl in enumerate(reg_ctrl): + # reg3の標的ビットに関するループ + for itarg, qtarg in enumerate(reg3): + # C[P(phi)], phi = 2pi * 2^{ictrl} * 2^{itarg} / 2^{n3} + circuit.cp(dphi * (2 ** (ictrl + itarg)), qctrl, qtarg) + + # 回路図を見やすくするためのバリア + circuit.barrier() + + # Inverse QFT + for j in range(reg3.size // 2): + circuit.swap(reg3[j], reg3[-1 - j]) + + for itarg in range(reg3.size): + for ictrl in range(itarg): + power = ictrl - itarg - 1 + reg3.size + circuit.cp(-dphi * (2 ** power), reg3[ictrl], reg3[itarg]) + + circuit.h(reg3[itarg]) + +print('Defined function setup_addition') +``` + +サブルーチンを具体的に数式で追ってみましょう。 + +```{toggle} +まず、2つの入力レジスタをそれぞれ状態$\ket{a}$と$\ket{b}$に用意します。出力レジスタの初期状態は$\ket{0}$です。それぞれのレジスタは十分に大きい(レジスタ$i$のビット数を$n_i$として$2^{n_1} > a$, $2^{n_2} > b$, $2^{n_3} > a + b$)とします。 + +量子フーリエ変換は、ビット数$n$のレジスタの計算基底$\ket{j}$を + +$$ +U_{\mathrm{QFT}}\ket{j} = \frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i jk/2^n} \ket{k} +$$ + +という状態に変える操作でした。では、その逆を考えると、自然数$a+b < 2^n$について + +$$ +U_{\mathrm{QFT}}^{-1} \frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1} e^{2\pi i (a+b)k/2^n} \ket{k} = \ket{a+b} +$$ + +ができることがわかります。 + +左辺の状態を作るには、これも量子フーリエ変換のアルゴリズムを参考にします。自然数$a, b, k$の二進分解 + +$$ +a = \sum_{m=0}^{n_1-1} 2^m a_m \\ +b = \sum_{m=0}^{n_2-1} 2^m b_m \\ +k = \sum_{m=0}^{n_3-1} 2^m k_m +$$ + +を用いて、 + +$$ +\exp\left(2\pi i \frac{(a+b)k}{2^{n_3}}\right) = \left[\prod_{l=0}^{n_1-1}\prod_{m=0}^{n_3-1} \exp\left(2\pi i \frac{2^{l+m} a_l k_m}{2^{n_3}}\right)\right]\left[\prod_{l=0}^{n_2-1}\prod_{m=0}^{n_3-1} \exp\left(2\pi i \frac{2^{l+m} b_l k_m}{2^{n_3}}\right)\right] +$$ + +と書けることを利用します。つまり、レジスタ1または2の各ビットとレジスタ3の各ビットを一つずつ組み合わせて、両方のビットが1である($a_l = k_m = 1$または$b_l = k_m = 1$の)ときに対応する分($2\pi 2^{l + m} / 2^{n_3}$)位相を進めれば、左辺の状態ができあがります。 + +具体的には、まずレジスタ3をequal superpositionに用意し、レジスタ1の各ビットを制御、レジスタ3の各ビットを標的とした$C[P]$ゲートをかけていきます。 + +$$ +\begin{align} +\ket{0}\ket{b}\ket{a} & \xrightarrow{H^{\otimes n_3}} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \ket{k} \ket{b}\ket{a} \\ +& \xrightarrow{C^{1;0}_{3;0}[P(2\pi \cdot 2^0 \cdot 2^0/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{a_0 k_0}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +& \xrightarrow{C^{1;0}_{3;1}[P(2\pi \cdot 2^0 \cdot 2^1/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{a_0 (k_0 + 2k_1)}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +\cdots & \\ +& \xrightarrow{C^{1;0}_{3;n_3 - 1}[P(2\pi \cdot 2^0 \cdot 2^{n_3 - 1}/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{a_0 k}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +& \xrightarrow{C^{1;1}_{3;0}[P(2\pi \cdot 2^1 \cdot 2^0/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{a_0 k + 2a_1 k_0}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +\cdots & \\ +& \xrightarrow{C^{1;n_1 - 1}_{3;n_3 - 1}[P(2\pi \cdot 2^{n_1-1} \cdot 2^{n_3 - 1}/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{a k}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} +\end{align} +$$ + +続いてレジスタ2のビットを制御として、同様の$C[P]$ゲートをかけていくと、 + +$$ +\begin{align} +& \xrightarrow{C^{2;0}_{3;0}[P(2\pi \cdot 2^0 \cdot 2^0/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{ak + b_0 k_0}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +& \xrightarrow{C^{2;0}_{3;1}[P(2\pi \cdot 2^0 \cdot 2^1/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{ak + b_0 (k_0 + 2k_1)}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +\cdots & \\ +& \xrightarrow{C^{2;0}_{3;n_3 - 1}[P(2\pi \cdot 2^0 \cdot 2^{n_3 - 1}/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{(a + b_0) k}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +& \xrightarrow{C^{2;1}_{3;0}[P(2\pi \cdot 2^1 \cdot 2^0/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{(a + b_0) k + 2b_1 k_0}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} \\ +\cdots & \\ +& \xrightarrow{C^{2;n_2 - 1}_{3;n_3 - 1}[P(2\pi \cdot 2^{n_2 - 1} \cdot 2^{n_3 - 1}/2^{n_3})]} \frac{1}{\sqrt{2^{n_3}}} \sum_{k=0}^{2^{n_3}-1} \exp \left( 2\pi i \frac{(a + b) k}{2^{n_3}} \right) \ket{k} \ket{b}\ket{a} +\end{align} +$$ + +となり、めでたく$\ket{a+b}$のフーリエ変換状態が実現されました。 +``` + +実際に`setup_addition`を使って足し算をしてみましょう。レジスタ1と2は4ビットとして、$a=9, b=13$を考えます。 + +```{code-cell} ipython3 +a = 9 +b = 13 + +# 入力の値を二進数表現できる最小のビット数を計算 +n1 = np.ceil(np.log2(a + 1)).astype(int) +n2 = np.ceil(np.log2(b + 1)).astype(int) +n3 = np.ceil(np.log2(a + b + 1)).astype(int) + +print(f'n1={n1}, n2={n2}, n3={n3}') + +reg1 = QuantumRegister(n1, 'r1') +reg2 = QuantumRegister(n2, 'r2') +reg3 = QuantumRegister(n3, 'r3') + +# QuantumCircuitは量子ビット数の代わりにレジスタを渡しても作成できる +circuit = QuantumCircuit(reg1, reg2, reg3) + +# reg1を|a>にする +a_bits = np.unpackbits(np.asarray(a, dtype=np.uint8), bitorder='little') +for idx in np.nonzero(a_bits)[0]: + circuit.x(reg1[idx]) + +# reg2を|b>にする +b_bits = np.unpackbits(np.asarray(b, dtype=np.uint8), bitorder='little') +for idx in np.nonzero(b_bits)[0]: + circuit.x(reg2[idx]) + +# 足し算ルーチンを呼ぶ +setup_addition(circuit, reg1, reg2, reg3) + +# 回路図を確認 +circuit.draw('mpl') +``` + +再び`statevector_expr`関数を使って終状態を確認してみましょう。 + +```{code-cell} ipython3 +expr = statevector_expr(circuit, register_sizes=(n1, n2, n3)) +Math(expr) +``` + +結果表示された状態は期待通り単一の計算基底$22:13:9$、つまりレジスタ1, 2, 3がそれぞれ$\ket{9}, \ket{13}, \ket{22}$となっている状態です(回路全体でビットを右から書いて状態を表示するため、レジスタも右から左に並びます)。つまり、めでたく状態の変遷 + +$$ +\ket{0}\ket{13}\ket{9} \rightarrow \ket{22}\ket{13}\ket{9} +$$ + +が実現しました。 + ++++ + +## 足し算の並列化 + +上では小学一年生ができる足し算を一回行うために13個の量子ビットと67個のゲートを利用しました。しかし、出来上がった回路は入力の値(9と13)によらず、4ビットで表現できる2つの整数すべてに対して成り立ちます(一般に2つの$n$ビット数の和は$n+1$ビットに収まるので、レジスタ3の大きさにも不足はありません)。さらに、量子演算は線形(つまり演算$U$について$U(\sum_{k} c_k \ket{k}) = \sum_{k} c_k U\ket{k}$)なので、初期状態としてレジスタ1と2がどんな計算基底の重ね合わせにあっても、それぞれの組み合わせに対してレジスタ3の状態が和を表してくれます。特に、初期状態がequal superpositionであれば、この回路は +Above, we used 13 quantum bits and 67 gates to solve an addition problem that even a first grader could solve. However, the circuit that we created works for all combinations of two integers that can be expressed in 4 bits, not just the input values of 9 and 13. (As a rule, the sum of two n-bit numbers can fit in $n+1$ bits, so register 3's size is sufficient.) Furthermore, quantum operations are linear (that is, for operation $U$, $U(\sum_{k} c_k \ket{k}) = \sum_{k} c_k U\ket{k}$), so no matter the superposition of computational bases of registers 1 and 2 in the initial state, the state of register 3 will express their combination. If the initial state is an equal superposition, this circuit will perform the following. + +$$ +\frac{1}{\sqrt{2^{n_1 + n_2}}} \sum_{j=0}^{2^{n_1}-1} \sum_{k=0}^{2^{n_2}-1} \ket{0}\ket{k}\ket{j} \rightarrow \frac{1}{\sqrt{2^{n_1 + n_2}}} \sum_{j=0}^{2^{n_1}-1} \sum_{k=0}^{2^{n_2}-1} \ket{j+k}\ket{k}\ket{j} +$$ + +を行うので、$\mathcal{O}\left((n_1 + n_2 + n_3) n_3\right)$個のゲートで$2^{n_1+n_2}$通りの足し算を並列に行います。実際にこれを確認してみましょう。 +This means that $\mathcal{O}\left((n_1 + n_2 + n_3) n_3\right)$ gates will perform $2^{n_1+n_2}$ addition operations in parallel. Let's actually confirm that. + +```{code-cell} ipython3 +n1 = 4 +n2 = 4 +n3 = np.ceil(np.log2((2 ** n1) + (2 ** n2) - 1)).astype(int) + +reg1 = QuantumRegister(n1, 'r1') +reg2 = QuantumRegister(n2, 'r2') +reg3 = QuantumRegister(n3, 'r3') + +circuit = QuantumCircuit(reg1, reg2, reg3) + +# reg1とreg2をequal superpositionにする +circuit.h(reg1) +circuit.h(reg2) + +setup_addition(circuit, reg1, reg2, reg3) + +expr = statevector_expr(circuit, register_sizes=(n1, n2, n3), amp_norm=(1. / np.sqrt(2 ** (n1 + n2)), r'\frac{1}{\sqrt{2^{n_1 + n_2}}}')) +Math(expr) +``` + +2022年4月現在、IBMの持つ最大の量子コンピュータは127量子ビットです。このマシンを最大限利用するならば、$n_1 = n_2 = 42, n_3 = 43$で$2^{84}$通り、つまり約$2 \times 10^{25}$(20𥝱)通りの足し算を同時に行うことができます。 +If the capabilities of a 65 quantum bit machine were used to its fullest, n_1 = n_2 = 21 and n_3 = 22, so 2^42 computations -- roughly four trillion computations -- could be performed simultaneously. + +もちろん、上で書いたようにここには重要な但し書きがあって、実機でこの計算をして測定から答えを得ようとすると、毎回の測定でどの組み合わせが得られるかをコントロールできないので、これはあまり実用的とは言えない回路です。強いて言えば毎日ランダムに12桁+12桁の正しい足し算を教えてくれる「日めくり足し算カレンダー」にくらいは使えます。$10^{23}$年程度使い続けられます。 +Of course, as mentioned above, this comes with an important caveat: if you tried to perform this calculation on an actual QC and determine the answer by measuring it, you wouldn't be able to control which combination was measured each time, so this kind of circuit would be far from practical. At best, you could use it like a "addition calculation of the day" calendar, giving you the correct result for the addition of two random six-digit numbers. With four trillion possible combinations, this calendar could last you around 10 billion years. + +## シミュレータでの実行 + +上の足し算回路の結果がランダムに出る様子をシミュレーションで確認しましょう。{doc}`addition_on_ibmq`では実機でも実行します。その際、上の回路実装では非効率的でエラーが出すぎるので、[専用に効率化した等価回路](https://github.com/UTokyo-ICEPP/qc-workbook/tree/master/source/qc_workbook/optimized_additions.py)を代わりに使用します。 +Let's confirm, through a simulation, that the results of the above addition circuit are produced randomly. We will perform {doc}`addition_on_ibmq` on an actual QC. The above circuit implementation is inefficient, and produces too many errors, so from here on we will use [an optimized version of the circuit](https://github.com/UTokyo-ICEPP/qc-workbook/tree/master/source/qc_workbook/optimized_additions.py) instead. + +```{code-cell} ipython3 +:tags: [remove-output] + +# 元の回路に測定を加える +circuit.measure_all() +circuit_original = circuit + +# 効率化した回路(測定付き) +circuit_optimized = optimized_additions(n1, n2) +print('Constructed an optimized addition circuit') +``` + +回路の効率化とは具体的にどういうことでしょうか。もともとの回路と効率化したものとを比べてみましょう。まずは、単純にオペレーションの数を比較します。ゲート一つ一つで一定の確率でエラーが起こるということは、同じことをする回路ならゲートの数が少ないほうがより正確な計算をしてくれます。 +What exactly do we mean when we talk about optimizing the circuit? Let's find out by comparing the original circuit to the optimized version. First, let's simply compare the number of operations. Because each gate has a certain probability of producing an error, the fewer gates a circuit has the more accurate its calculations will be. + +```{code-cell} ipython3 +print('Number of operations in the original circuit:', circuit_original.size()) +print('Number of operations in the optimized circuit:', circuit_optimized.size()) +``` + +効率化したはずの回路のほうがはるかにゲート数が多いという結果になりました。なぜでしょうか。 +However, the circuit which is supposedly optimized has far more gates. Why is that? + ++++ + +(transpilation)= +### トランスパイルと物理的回路 + +これまで何度も登場しましたが、量子回路オブジェクトを実機やシミュレータの`run`メソッドに渡す前に、必ず`transpile`という関数を呼んでいました。この関数はトランスパイルという変換を回路に施します。実機においてトランスパイルとは、様々な複合ゲートからなる論理的な回路から、実機のハードウェアに実装されている「基本ゲート」のみで書かれる物理的な回路を作ることを言います[^physical]。 + +基本ゲートとは何でしょうか。実は、{ref}`第一回 `や{ref}`第二回の前半 `で様々なゲートを紹介しましたが、量子コンピュータの物理的実体(超伝導振動子など)で実際に行える量子操作にはごく少数の種類しかありません。例えば、IBMのマシンでは$X$, $\sqrt{X}$, $R_{z}$, CNOTの4通りです。しかし、この4つの組み合わせで全てのゲートを作ることができます。そのようなゲートの集合を基本ゲートと呼んでいます。 + +足し算回路の愚直な実装と効率化した実装の比較に話を戻すと、上で比べていたのはあくまで複合ゲートを使った論理的な回路でした。論理的な回路はどのようにでも書ける(極端に言えば回路全体を一つの「足し算ゲート」と呼んでしまうこともできる)ので、回路のゲート数の比較はトランスパイル後でなければ意味がありません。 + +いい機会なので、実機での量子計算について少し詳細に考えてみましょう。トランスパイルがまさに論理的なアルゴリズムの世界と物理的実装の世界のインターフェースとなるので、この過程に注目します。 +This is a good opportunity to learn about quantum calculation on actual QCs in a bit more detail. Transpilation is an interface between the world of logical algorithms and the world of physical implementation, so let's focus on that aspect. + +トランスパイル時には、以下のような回路の変換が起こります。 + +- 冗長なゲートの削除 +- 多重制御ゲートのCNOTと1量子ビットゲートへの分解 +- 実機のトポロジーに即した量子ビットのマッピング(詳細は下) +- 物理的に隣接しない量子ビット間の制御ゲートを実行するためのSWAPの挿入 +- 1量子ビットゲートの基本ゲートへの分解 +- 物理的回路の最適化 + +When transpilation is performed, the following circuit transformations take place. +- Redundant gates are removed +- Multi-controlled gates are broken down into CNOTs and 1-quantum bit gates alone. +- Quantum bits are mapped to the topology of the actual QC (see detailed explanation below) +- SWAPs are introduced to operate controlled gates for quantum bits that are not physically adjacent +- quantum bit gates are broken down into basic gates +- The physical circuit is optimized + +実機のトポロジーとは、実際の量子プロセッサチップ上での量子ビット同士の繋がりかたのことを指します。2つの量子ビットが繋がっているとは、その間で基本制御ゲート(IBMQではCNOT)が実行できるということを意味します。これまで考慮してきませんでしたが、実はすべての量子ビットが繋がっているわけではないのです。例えば以前運用されていたibmq_16_melbourneというマシンは以下のようなトポロジーを持っていました。 +The topology of the actual QC refers to the connections between the quantum bits on the actual quantum processor chip. If two quantum bits are connected, this means that a basic controlled gate (in the case of IBMQ, a CNOT gate) can be executed between them. We haven't taken this aspect of actual QCs into consideration so far, but in reality, not every quantum bit is connected. For example, the ibmq_16_melbourne machine, which we will use later, has the following topology. + +```{image} figs/melbourne_topology.png +:height: 200px +``` + +図中、数字のついた丸が量子ビットを表し、線が量子ビット同士の繋がりを表します。 +The numbered circles in the figure represent quantum bits, and the lines between them represent their connections. + +このように実機ごとにトポロジーが違うことなどが理由で、`transpile`関数には回路オブジェクトだけでなくバックエンドを引数として渡す必要があります。 + +直接接続のない量子ビット間で制御ゲートを実行する場合、SWAPを使って2つの量子ビットが隣り合うように状態を遷移させていく必要があります。例えば上のibmq_16_melbourneでビット2と6の間のCNOTが必要なら、(いくつか方法がありますが)2↔3, 3↔4, 4↔5とSWAPを繰り返して、5と6の間でCNOTを行い、ビットの並びを元に戻す必要があれば再度5↔4, 4↔3, 3↔2とSWAPをすることになります。 +To use a controlled gate between two quantum bits that are not directly connected requires the use of SWAP gates to transition states so that the quantum bits are adjacent. For example, if you wish to use a CNOT gate between bits 2 and 6 in ibmq_16_melbourne, one of the many ways you can do this is to repeatedly use SWAP gates, following the sequence 2↔3, 3↔4, 4↔5, and then use a CNOT gate between bits 5 and 6. If you need to return the bits to their original positions, you would again use SWAP gates, following the sequence 5↔4, 4↔3, 3↔2. + +{ref}`ゲートの解説 `に出てきたように、SWAPは3つのCNOTに分解されます。つまり、直接接続のない量子ビット同士の制御ゲートが多出するような回路があると、莫大な数のCNOTが使われることになります。**CNOTのエラー率(ゲート操作一回あたりに操作の結果を間違える確率)は1量子ビットゲートのエラー率より一桁ほど高い**ので、これは大きな問題になります。そこで、論理的回路の量子ビットと実機の量子ビットとのマッピング(SWAPが発生すれば対応は変わっていくので、あくまで初期対応)と、回路中にどうSWAPを挿入していくかというルーティングの両方を上手に決めるということが、トランスパイルにおける中心的な課題です。 +As mentioned in the {ref}`explanation of gates `, SWAP gates can be broken down into three CNOT gates. In other words, circuits with a large number of controlled gates between quantum bits that are not directly connected require the use of a huge number of CNOT gates. **The error rate of CNOT gates (the probability of an erroneous result from a single gate operation) is an order of magnitude higher than the error rate of single quantum bit gates**, so this is a major problem. The central challenge in transpilation is making successful decisions regarding both how to map the quantum bits of logical circuits to the quantum bits of actual QCs (the way this is handled varies depending on whether SWAPs are performed, so this refers purely to initial handling) and how to insert SWAP gates into the circuit (known as routing). + +しかし、実は任意の回路に対して最適なマッピングとルーティングを探すという問題自体がいわゆるNP-hardな問題なので、qiskitのトランスパイル・ルーチンではこの問題の最適解を探してくれません。代わりにstochastic swapという、乱数を用いた手法が標準設定では利用されます。Stochastic swapは多くの回路で比較的効率のいいルーティングを作ることが知られていますが、乱数を利用するため実行のたびに異なるルーティングが出てくるなど、やや扱いにくい面もあります。また、単純な回路で事前に最適なルーティングがわかっている場合は、stochastic swapを使うべきではありません。 +However, the actual question of how to optimize mapping and routing for general circuits is an NP-hard question, so qiskit's transpilation routine does not search for an optimized solution to this problem. Instead, it is configured by default to use a random number-based method called stochastic swapping. Stochastic swapping is known to be effective at creating relatively efficient routing for many circuits. However, because it uses random numbers each time it is run, the routing also differs each time, making it somewhat difficult to work with. If you already know the optimal routing for a simple circuit, stochastic swapping should not be used. + +[^physical]: 「物理的」な回路もまだ実は論理的な存在であり、本当にハードウェアが理解するインストラクションに変換するには、さらに基本ゲートを特定のマイクロ波パルス列に直す必要があります。 +[^physical]: Even these "physical" circuits are actually still logical entities. Converting them into instructions that can be understood by the hardware requires the basic gates to be converted into microwave pulse strings. + ++++ + +### 回路の比較 + +上を踏まえて、改めて2つの足し算回路を比較してみましょう。 +Given the above, let us compare the two addition circuits again. + +これまで`transpile`関数を回路とバックエンド以外の引数を渡さずに実行してきましたが、実はこの関数の実行時に様々なオプションを使ってトランスパイルの設定を細かくコントロールすることができます。今回の効率化した回路は一列に並んだ量子ビット列上でSWAPを一切使わずに実装できるようになっているので、stochastic swapを使用しないよう設定を変更してトランスパイルをします。バックエンド上のどの量子ビット列を使うかは、`find_best_chain`という関数で自動的に決めます。 +Until now, we have executed circuits on the actual QC using the execute function, but if you provide this function with a circuit that has not yet undergone transpilation, transpilation will be performed on the circuit automatically, using standard settings. However, we can also explicitly transpile the circuit using the transpile function. This function is primarily used when you want to control transpilation settings with a high degree of granularity. Our optimized circuit was created for a specific mapping, so we will change the transpilation setting to not use stochastic swapping and then perform transpilation. The mapping has been decided in advance, based on ibmq_16_melbourne's topology and error rate, and is hard coded in the get_initial_layout function. + +本来は実機を使ってこの先の議論を進めたいところですが、2022年4月現在、`'ibm-q/open/main'`プロバイダを使っている場合、最大5量子ビットのマシンしか利用できないため、1ビット+1ビットの足し算回路しか作れず、意味のある比較になりません。そのため、openプロバイダを使っている場合は「フェイク」のバックエンド(実際のバックエンドに似せたシミュレータ)を使います。 + +```{code-cell} ipython3 +:tags: [remove-output] + +# よりアクセス権の広いプロバイダを使える場合は、下を書き換える +instance = 'ibm-q/open/main' + +if instance == 'ibm-q/open/main': + from qiskit.test.mock import FakeGuadalupe + + backend = FakeGuadalupe() + +else: + try: + provider = IBMProvider(instance=instance) + except AccountNotFoundError: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) + + backend_list = provider.backends(filters=operational_backend(min_qubits=13)) + backend = least_busy(backend_list) + +print(f'Using backend {backend.name()}') +``` + +```{code-cell} ipython3 +:tags: [remove-output] + +# オリジナルの回路をトランスパイルする。optimization_level=3は自動設定のうち、最も効率のいい回路を作る +# フェイクバックエンドの場合、少し時間がかかるので気長に待ってください +print('Transpiling the original circuit with standard settings') +circuit_original_tr = transpile(circuit_original, backend=backend, optimization_level=3) + +# 効率化した回路をトランスパイルする。マシンのトポロジーに従い、最も合計エラー率の低い量子ビット列にマッピングする +print('Transpiling the optimized circuit with trivial mapping onto a chain of qubits') +initial_layout = find_best_chain(backend, n1 + n2 + n3) +circuit_optimized_tr = transpile(circuit_optimized, backend=backend, + routing_method='basic', initial_layout=initial_layout, + optimization_level=3) + +# count_opsは回路に含まれる基本ゲートの数を辞書として返す +nops_orig = circuit_original_tr.count_ops() +nops_opt = circuit_optimized_tr.count_ops() + +print(f'Number of operations in the original circuit: {circuit_original_tr.size()}') +print(f' Breakdown: N(Rz)={nops_orig["rz"]}, N(X)={nops_orig["x"]}, N(SX)={nops_orig["sx"]}, N(CNOT)={nops_orig["cx"]}') +print(f'Number of operations in the optimized circuit: {circuit_optimized_tr.size()}') +print(f' Breakdown: N(Rz)={nops_opt["rz"]}, N(X)={nops_opt["x"]}, N(SX)={nops_opt["sx"]}, N(CNOT)={nops_opt["cx"]}') +``` + +上のセルを実行すると、今度は効率化回路のオペレーションの全数が元の回路の8割、CNOTの数は6割という結果になることがわかります。 + +元の回路と効率化した回路の違いは、後者では「数珠つなぎ」になった量子ビット列というトポロジーを仮定して、制御ゲートの順番を工夫して直接明示的にSWAPを挿入していることです。さらに、可能なところでは$C[P]$ゲートの分解で生じるCNOTとSWAPのCNOTが打ち消し合うことも利用しています。最後の逆フーリエ変換でもゲートの順番が工夫してあります。 +The difference between the original circuit and the optimized circuit is that the latter was made by envisioning the circuit topology as a string of quantum bits, and then applying ingenuity to the order of the controlled gates and explicitly and directly inserting SWAP gates. Furthermore, when possible, the CNOTs produced when breaking down $C[P]$ gates were used to cancel out SWAP CNOT gates. Ingenuity was also applied to the order of the gates in the final reverse Fourier transform. + +それでは、トランスパイルした回路を実行してみます。 +Now let's run the transpiled circuit. + +```{code-cell} ipython3 +:tags: [remove-output] + +simulator = AerSimulator() + +job_original = simulator.run(circuit_original_tr, shots=20) +counts_original = job_original.result().get_counts() + +job_optimized = simulator.run(circuit_optimized_tr, shots=20) +counts_optimized = job_optimized.result().get_counts() + +def plot_counts(counts, n1, n2, ax): + heights = [] + labels = [] + + for key, value in counts.items(): + heights.append(value) + + # countsのキーはひとつなぎの二進数なので、出力, 入力2, 入力1の値が読み取れるように切り分ける + # 4 + 4 桁なら + # 00110 0101 0001 -> 6 = 5 + 1 + # n3 n2 n1 + x1 = int(key[-n1:], 2) # last n1 digits + x2 = int(key[-n1 - n2:-n1], 2) # next-to-last n2 digits + x3 = int(key[:-n1 - n2], 2) # first n3 digits + labels.append('{} + {} = {}'.format(x1, x2, x3)) + + x = np.linspace(0., len(labels), len(labels), endpoint=False) + + # 棒グラフをプロット + ax.bar(x, heights, width=0.5) + + # ビジュアルを調整 + ax.set_xticks(x - 0.2) + ax.set_xticklabels(labels, rotation=70) + ax.tick_params('x', length=0.) + +# サブプロットが縦に二つ並んだフィギュアを作成 +fig, (ax_original, ax_optimized) = plt.subplots(2, figsize=[16, 10]) + +# サブプロット1: counts_original +plot_counts(counts_original, n1, n2, ax_original) + +# サブプロット2: counts_optimized +plot_counts(counts_optimized, n1, n2, ax_optimized) + +fig.subplots_adjust(bottom=-0.2) +``` + +両方の回路とも、正しい足し算の式がランダムに出現していることを確認してください。 +Confirm that correct addition formulas are produced automatically in both circuits. \ No newline at end of file diff --git a/source/en/grover.md b/source/en/grover.md new file mode 100644 index 00000000..ce16310c --- /dev/null +++ b/source/en/grover.md @@ -0,0 +1,767 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + ++++ {"pycharm": {"name": "#%% md\n"}} + +# Performing Database Search + ++++ + +In this unit, we'll introduce **Grover's algorithm**{cite}`grover_search,nielsen_chuang_search` and consider the problem of searching for an answer in an unstructured database using this algorithm. After that, We will implement Grover's algorithm using Qiskit. + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{| #1 \rangle}$ +$\newcommand{\bra}[1]{\langle #1 |}$ +$\newcommand{\braket}[2]{\langle #1 | #2 \rangle}$ + ++++ + +## Introduction + +In order to realize quantum advantage over classical computation, one has to exploit quantum algorithm that can take advantage of quantum properties. One example of such algorithms is Grover's algorithm. Grover's algorithm is suited for **searching in unstructured database**, and it has been proven that Grover's algorithm can find solutions with fewer computational resources than those required for classical counterparts. This algorithm is based on a method known as **amplitude amplification** and is widely used as a subroutine in various quantum algorithms. + ++++ + +(database)= +## Searching for Unstructured Data + +Let us consider that there is a list consisting of $N$ elements, and we want to find one element $w$ in that list. To find $w$ using a classical computer, out would have to query the list $N$ times in a worst case, or on average $N/2$ times. With Grover's algorithm, it is known that $w$ can be found by querying about $\sqrt{N}$ times. That it, Glover's algorithm allows one to search for unstructured data quadratically faster than the classical calculation. + ++++ + +(grover)= +## Grover's Algorithm + +Here we will consider $n$ qubits, and the list composed of every possible computational basis states. In other words, the list contains $N=2^n$ elements, composed of $\ket{00\cdots00}$, $\ket{00\cdots01}$, $\cdots$, $\ket{11\cdots11}$ ($\ket{0}$, $\ket{1}$, $\cdots$, $\ket{N-1}$ in decimal form). + ++++ + +(grover_phaseoracle)= +### Introduction of Phase Oracle + +The Grover's algorithm is characterized by the existence of phase oracle, which changes phase of a certain state. First, let us consider a phase oracle $U$ defined as $U\ket{x}=(-1)^{f(x)}\ket{x}$, that is, when acting on the state $\ket{x}$, it shifts the phase by $-1^{f(x)}$ with a certain function $f(x)$. If we consider the function $f(x)$ to be like below: + +$$ +f(x) = \bigg\{ +\begin{aligned} +&1 \quad \text{if} \; x = w \\ +&0 \quad \text{else} \\ +\end{aligned} +$$ + +this leads to the oracle (denoted as $U_w$) which inverts the phase for the answer $w$ that we want: + +$$ +U_w:\begin{aligned} +&\ket{w} \to -\ket{w}\\ +&\ket{x} \to \ket{x} \quad \forall \; x \neq w +\end{aligned} +$$ + +If we use a matrix form, the $U_w$ can be written as as $U_w=I-2\ket{w}\bra{w}$. Furthermore, if we think of another function $f_0(x)$: + +$$ +f_0(x) = \bigg\{ +\begin{aligned} +&0 \quad \text{if} \; x = 0 \\ +&1 \quad \text{else} \\ +\end{aligned} +$$ + +then we can get unitary $U_0$ that inverts phases for all the states except 0. + +$$ +U_0:\begin{aligned} +&\ket{0}^{\otimes n} \to \ket{0}^{\otimes n}\\ +&\ket{x} \to -\ket{x} \quad \forall \; x \neq 0 +\end{aligned} +$$ + +Here the matrix form of $U_0$ is given as $U_0=2\ket{0}\bra{ 0}^{\otimes n}-I$. + ++++ + +(grover_circuit)= +### Structure of Quantum Circuit + +The structure of the circuit used to implement Grover's algorithm is shown below. Starting with the $n$-qubit initial state $\ket{0}$, a uniform superposition state is created first by applying Hadamard gates to every qubits. Then, the operator denoted as $G$ is applied repeatedly. + +```{image} figs/grover.png +:alt: grover +:width: 600px +:align: center +``` + +$G$ is a unitary operator called **Grover Iieration** and consists of the following four steps. + +```{image} figs/grover_iter.png +:alt: grover_iter +:width: 550px +:align: center +``` + +The $U_w$ and $U_0$ are oracles that invert the phase of an answer $w$ and the phase of all states other than 0, respectively, as introduced above. + +Together with the Hadamard operator at the beginning of the circuit, we will look at steps involved in a single Grover iteration in detail below. + +```{image} figs/grover_iter1.png +:alt: grover_iter1 +:width: 600px +:align: center +``` + ++++ + +(grover_superposition)= +### Creation of Superposition State + +First, a uniform superposition state is produced by applying Hadamard gates to initial state $\ket{0}^{\otimes n}$ of the $n$-qubit circuit. + +$$ +\ket{s} = H^{\otimes n}\ket{0}^{\otimes n} = \frac{1}{\sqrt{N}}\sum_{x=0}^{N-1}\ket{x} +$$ + +This state is denoted as $\ket{s}$. + ++++ + +(grover_geometry)= +### Geometrical Representation + +Let's view this $\ket{s}$ state geometrically. First, consider a two-dimensional plane created by the superposition state $\ket{s}$ and the state $\ket{w}$, which is what we are trying to find. Since the state $\ket{w^{\perp}}$, which is orthogonal to $\ket{w}$, can be expressed as $\ket{w^{\perp}}:=\frac{1}{\sqrt{N-1}}\sum_{x \neq w}\ket{x}$, it corresponds to the axis orthogonal to $\ket{w}$ in this 2D plane. Therefore, $\ket{w^{\perp}}$ and $\ket{w}$ can be regarded as orthonormal basis states, i.e, $\ket{w^{\perp}}=\begin{bmatrix}1\\0\end{bmatrix}$, $\ket{w}=\begin{bmatrix}0\\1\end{bmatrix}$. + +In short, $\ket{s}$ can be expressed as a linear combination of the two vectors ($\ket{w^{\perp}}$, $\ket{w}$) on this 2D plane. +$$ +\begin{aligned} +\ket{s}&=\sqrt{\frac{N-1}{N}}\ket{w^{\perp}}+\frac1{\sqrt{N}}\ket{w}\\ +&=: \cos\frac\theta2\ket{w^{\perp}}+\sin\frac\theta2\ket{w}\\ +&= \begin{bmatrix}\cos\frac\theta2\\\sin\frac\theta2\end{bmatrix} +\end{aligned} +$$ + +In above equations, the amplitude of $\ket{w}$ is $\frac1{\sqrt{N}}$ and the amplitude of $\ket{w^{\perp}}$ is $\sqrt{\frac{N-1}{N}}$ because we want to find only one answer. If we define $\theta$ to fulfill $\sin\frac\theta2=\frac1{\sqrt{N}}$, then the $\theta$ is expressed as + +$$ +\theta=2\arcsin\frac{1}{\sqrt{N}} +$$ + +The $\ket{s}$ state on ($\ket{w^{\perp}}$, $\ket{w}$) plane is depicted as follows. + +```{image} figs/grover_rot1.png +:alt: grover_rot1 +:width: 300px +:align: center +``` + ++++ + +(grover_oracle)= +### Application of Oracle + +Next, we will apply the oracle $U_w$ oracle to $\ket{s}$. This oracle can be expressed on this plane as $U_w=I-2\ket{w}\bra{ w}=\begin{bmatrix}1&0\\0&-1\end{bmatrix}$. This indicates that the action of $U_w$ is equivalent to the inversion of $\ket{s}$ with respect to the $\ket{w^{\perp}}$ axis (see figure below), hence reversing the phase of $\ket{w}$. + +```{image} figs/grover_rot2.png +:alt: grover_rot2 +:width: 300px +:align: center +``` + ++++ + +(grover_diffuser)= +### Application of Diffuser + +Next is the application of $H^{\otimes n}U_0H^{\otimes n}$, and this operation is called Diffuser. Since $U_0=2\ket{0}\bra{0}^{\otimes n}-I$, if we define $U_s$ to be $U_s \equiv H^{\otimes n}U_0H^{\otimes n}$, then it is expressed as + +$$ +\begin{aligned} +U_s &\equiv H^{\otimes n}U_0H^{\otimes n}\\ +&=2H^{\otimes n}\ket{0}^{\otimes n}\bra{0}^{\otimes n}H^{\otimes n}-H^{\otimes n}H^{\otimes n}\\ +&=2\ket{s}\bra{ s}-I\\ +&=\begin{bmatrix}\cos\theta&\sin\theta\\\sin\theta&-\cos\theta\end{bmatrix} +\end{aligned} +$$ + +This means that the diffuser $U_s$ is an operator that inverts $U_w\ket{s}$ with respect to $\ket{s}$ (see figure below). + +```{image} figs/grover_rot3.png +:alt: grover_rot3 +:width: 300px +:align: center +``` + +In summary, the Grover iteration $G=U_sU_w$ is written as + +$$ +\begin{aligned} +G&=U_sU_w\\ +&= \begin{bmatrix}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{bmatrix} +\end{aligned} +$$ + +and is equivalent to rotating the $\ket{s}$ towards $\ket{w}$ by the angle $\theta$ (figure below). + +```{image} figs/grover_rot4.png +:alt: grover_rot4 +:width: 300px +:align: center +``` + +This correspondence between $G$ and the $\theta$ rotation means that if $G$ is applied $r$ times, $\ket{s}$ is rotated by $r\theta$. After that, the state of $\ket{s}$ becomes + +$$ +G^r\ket{s}=\begin{bmatrix}\cos\frac{2r+1}{2}\theta\\\sin\frac{2r+1}{2}\theta\end{bmatrix} +$$ + +This indicates that $\ket{s}$ would need to be rotated $r$ times so that $\frac{2r+1}2\theta\approx\frac{\pi}2$ to reach the desired answer of $\ket{w}$. If each rotation angle $\theta$ is small enough, then $\sin\frac\theta2=\frac{1}{\sqrt{N}}\approx\frac\theta2$, hence $r\approx\frac\pi4\sqrt{N}$. Now we have shown that {\cal O}(\sqrt{N})$ operations would allow us to reach the desired answer $\ket{w}$, meaning that it is quadratically faster than the classical calculation. + +Let's look at the diffuser's role a bit more. We can think of a certain state $\ket{\psi}$ and assume that it is written as a superposition of some bases $\ket{k}$ with amplitude $a_k$, $\ket{\psi}:=\sum_k a_k\ket{k}$. When we apply the diffuser to this state, + +$$ +\begin{aligned} +\left( 2\ket{s}\bra{ s} - I \right)\ket{\psi}&=\frac2N\sum_i\ket{i}\cdot\sum_{j,k}a_k\braket{j}{k}-\sum_k a_k\ket{k}\\ +&= 2\frac{\sum_i a_i}{N}\sum_k\ket{k}-\sum_k a_k\ket{k}\\ +&= \sum_k \left( 2\langle a \rangle-a_k \right)\ket{k} +\end{aligned} +$$ + +$\langle a \rangle\equiv\frac{\sum_i a_i}{N}$ is an average of the amplitudes. If you consider the amplitude $a_k$ of the state $\ket{k}$ to be expressed in the form of deviation from the average, $a_k=\langle a \rangle-\Delta$, then this equation could be better understood. That is, the amplitude will become $2\langle a \rangle-a_k=\langle a \rangle+\Delta$ after applying the diffuser. This means that the action of diffuser corresponds to the inversion of the amplitudes with respect to the average $\langle a \rangle$. + ++++ + +(grover_amp)= +### Visualize Amplitude Amplification + +Let us visualize how the amplitude of the state corresponding to correct answer is amplified. + +First, the Hadamard gates applied at the beginning of the circut create a superposition of all the computational basis states with equal amplitudes ((1) in the figure below). The horizontal axis shows $N$ computational basis states, and the vertical axis shows the magnitude of the amplitude for each basis state and it is $\frac{1}{\sqrt{N}}$ for all the states (the average is shown by a dotted red line). + +Next, applying the oracle $U_w$ inverts the phase of $\ket{w}$, making the amplitude to $-\frac{1}{\sqrt{N}}$ ((2) of the figure). In this state, the average amplitude is $\frac{1}{\sqrt{N}}(1-\frac2N)$, which is lower than that in state (1). + +Last, the diffuser is applied to the state and all the amplitudes are reversed with respect to the average ((3) of the figure). This increases the amplitude of $\ket{w}$ and decreases the amplitudes of all other basis states. As seen in the figure, the amplitude of $\ket{w}$ state is amplified roughly three times. Repeating this process will further increase the amplitude of $\ket{w}$, so we can expect that we will have higher chance of getting the correct answer. + +```{image} figs/grover_amp.png +:alt: grover_amp +:width: 800px +:align: center +``` + ++++ + +(grover_multidata)= +### Searching for Multiple Data + +We have only considered so far searching for a single data. What if we consider finding multiple data? For example, we want to find $M$ data $\{w_i\}\;(i=0,1,\cdots,M-1)$ from a sample of $N=2^n$ data. Just as before, we can discuss this situation on a two-dimensional plance with the state we want to find, $\ket{w}$, and its orthogonal state, $\ket{w^{\perp}}$. + +$$ +\begin{aligned} +&\ket{w}:=\frac{1}{\sqrt{M}}\sum_{i=0}^{M-1}\ket{w_i}\\ +&\ket{w^{\perp}}:=\frac{1}{\sqrt{N-M}}\sum_{x\notin\{w_0,\cdots,w_{M-1}\}}\ket{x} +\end{aligned} +$$ + +$\ket{s}$ can be expressed on this plane as follows: + +$$ +\begin{aligned} +\ket{s}&=\sqrt{\frac{N-M}{N}}\ket{w^{\perp}}+\sqrt{\frac{M}{N}}\ket{w}\\ +&=: \cos\frac\theta2\ket{w^{\perp}}+\sin\frac\theta2\ket{w}\\ +\end{aligned} +$$ + +If the amplitude $\sqrt{\frac{M}{N}}$ of the state $\ket{w}$ is defined as $\sin\frac\theta2$, then the angle $\theta$ is $\theta=2\arcsin\sqrt{\frac{M}{N}}$. Compared to the case of finding a single data, the angle rorated by the single Grover iteration is $\sqrt{M}$ times larger. As a result, we can reach the answer by a smaller number of rotations, $r\approx\frac\pi4\sqrt{\frac{N}{M}}$, than the single-data case. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(imp)= +## Implementation of Grover's Algorithm(Case of $N=2^6$) + +Now let's try to solve database search problem by implementing Grover's algorithm. + +This exercise is to find a single answer "45" in a list containing $N=2^6$ elements of $[0,1,2,\cdots,63]$ (Of course, you could look for another number and feel free to do that later if you like). That is, we try to find $\ket{45}=\ket{101101}$ using a 6-qubit quantum circuit. + ++++ + +(imp_qiskit)= +### Qiskit Implementation + +Set up the environment first. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Tested with python 3.8.12, qiskit 0.34.2, numpy 1.22.2 +import matplotlib.pyplot as plt +import numpy as np + +# Import Qiskit-related packages +from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, transpile +from qiskit.quantum_info import Statevector +from qiskit.visualization import plot_histogram +from qiskit.tools.monitor import job_monitor +from qiskit_aer import AerSimulator +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_ibm_provider.accounts import AccountNotFoundError + +# Modules necessary for this workbook +from qc_workbook.utils import operational_backend +``` + +Prepare a 6-qubit circuit `grover_circuit`. + +A quantum circuit to perform a single Grover iteration will be something like below, and please write a quantum circuit that implements gates outlined in red (phase oracle and the unitary corresponding to $2\ket{0}\bra{0}-I$ of the diffuser). + +```{image} figs/grover_6bits_45.png +:alt: grover_6bits_45 +:width: 600px +:align: center +``` + +Implement the oracle after generating a uniform superposition state $\ket{s}$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-output] +--- +Nsol = 45 +n = 6 + +grover_circuit = QuantumCircuit(n) + +grover_circuit.h(range(n)) + +# Create the oracle and implement it in the circuit +oracle = QuantumCircuit(n) + +################## +### EDIT BELOW ### +################## + +#oracle.? + +################## +### EDIT ABOVE ### +################## + +oracle_gate = oracle.to_gate() +oracle_gate.name = "U_w" +print(oracle) + +grover_circuit.append(oracle_gate, list(range(n))) +grover_circuit.barrier() +``` + +**Answer** + +````{toggle} + +```{code-block} python + +################## +### EDIT BELOW ### +################## + +oracle.x(1) +oracle.x(4) +oracle.h(n-1) +oracle.mcx(list(range(n-1)), n-1) +oracle.h(n-1) +oracle.x(1) +oracle.x(4) + +################## +### EDIT ABOVE ### +################## +``` + +```` + +Next, implement the diffuser circuit. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +def diffuser(n): + qc = QuantumCircuit(n) + + qc.h(range(n)) + + ################## + ### EDIT BELOW ### + ################## + + #qc.? + + ################## + ### EDIT ABOVE ### + ################## + + qc.h(range(n)) + + #print(qc) + U_s = qc.to_gate() + U_s.name = "U_s" + return U_s + +grover_circuit.append(diffuser(n), list(range(n))) +grover_circuit.measure_all() +grover_circuit.decompose().draw('mpl') +``` + +**Answer** + +````{toggle} + +```{code-block} python +def diffuser(n): + qc = QuantumCircuit(n) + + qc.h(range(n)) + + ################## + ### EDIT BELOW ### + ################## + + qc.rz(2*np.pi, n-1) + qc.x(list(range(n))) + + # multi-controlled Zゲート + qc.h(n-1) + qc.mcx(list(range(n-1)), n-1) + qc.h(n-1) + + qc.x(list(range(n))) + + ################## + ### EDIT ABOVE ### + ################## + + qc.h(range(n)) + + #print(qc) + U_s = qc.to_gate() + U_s.name = "U_s" + return U_s +``` + +```` + + +(imp_simulator)= +### Experiment with Simulator + +Once you have implemented the circuit, run the simulator and make a plot of the results. To make the results easy to understand, the measured bitstring is converted to integers before making the plot. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-output] +--- +simulator = AerSimulator() +grover_circuit = transpile(grover_circuit, backend=simulator) +results = simulator.run(grover_circuit, shots=1024).result() +answer = results.get_counts() + +# Plot the values along the horizontal axis in integers +def show_distribution(answer): + n = len(answer) + x = [int(key,2) for key in list(answer.keys())] + y = list(answer.values()) + + fig, ax = plt.subplots() + rect = ax.bar(x,y) + + def autolabel(rects): + for rect in rects: + height = rect.get_height() + ax.annotate('{:.3f}'.format(height/sum(y)), + xy=(rect.get_x()+rect.get_width()/2, height),xytext=(0,0), + textcoords="offset points",ha='center', va='bottom') + autolabel(rect) + plt.ylabel('Probabilities') + plt.show() + +show_distribution(answer) +``` + +If the circuit is implemented correctly, you will see that the state $\ket{101101}=\ket{45}$ is measured with high probability. + +However, as discussed above, a single Grover iteration will produce incorrect answers with non-negligible probabilities in the search of $N=2^6$ elements. Later, we will see if repeating Grover iteration can produce correct answers with higher probabilities. + ++++ + +(imp_qc)= +### Experiment with Quantum Computer + +Before attempting multiple Grover iterations, let us first run a single Grover iteration on a quantum computer. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Implementation on a quantum computer +instance = 'ibm-q/open/main' + +try: + provider = IBMProvider(instance=instance) +except IBMQAccountCredentialsNotFound: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) + +backend_list = provider.backends(filters=operational_backend(min_qubits=6)) +backend = least_busy(backend_list) +print("least busy backend: ", backend) +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Execute the circuit on the backend with the highest level of availability. Monitor job execution in the queue. + +grover_circuit = transpile(grover_circuit, backend=backend, optimization_level=3) +job = backend.run(grover_circuit, shots=1024) +job_monitor(job, interval=2) +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Calculation results +results = job.result() +answer = results.get_counts(grover_circuit) +show_distribution(answer) +``` + +As you can see, the results are much worse than what we got with the simulator. Unfortunately, this is a typical result of running a quantum circuit of Grover's search algorithm on the present quantum computer as it is. We can however expect that the quality of the results will be improved by employing {ref}`error mitigation ` techniques. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(imp_simulator_amp)= +### Confirm Amplitude Amplification + +Here we will see how the amplitude is amplified by running the Grover's iteration multiple times using simulator. + +For example, the circuit to execute the Grover's iteration three times is prepared and executed. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +# Repetition of Grover's iteration +Niter = 3 + +grover_circuit_iterN = QuantumCircuit(n) +grover_circuit_iterN.h(range(n)) +for I in range(Niter): + grover_circuit_iterN.append(oracle_gate, list(range(n))) + grover_circuit_iterN.append(diffuser(n), list(range(n))) +grover_circuit_iterN.measure_all() +grover_circuit_iterN.draw('mpl') +``` + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +grover_circuit_iterN_tr = transpile(grover_circuit_iterN, backend=simulator) +results = simulator.run(grover_circuit_iterN_tr, shots=1024).result() +answer = results.get_counts() +show_distribution(answer) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +You will see that the correct answer of $\ket{45}$ appears with higher probability. + +Next, we make a plot of showing the correlation between the number of Grover's iterations and how many times we observe the correct answer. Here the Grover's iteration is repated 10 times. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +x = [] +y = [] + +# Repeating Grover's iteration 10 times +for Niter in range(1,11): + grover_circuit_iterN = QuantumCircuit(n) + grover_circuit_iterN.h(range(n)) + for I in range(Niter): + grover_circuit_iterN.append(oracle_gate, list(range(n))) + grover_circuit_iterN.append(diffuser(n), list(range(n))) + grover_circuit_iterN.measure_all() + + grover_circuit_iterN_tr = transpile(grover_circuit_iterN, backend=simulator) + results = simulator.run(grover_circuit_iterN_tr, shots=1024).result() + answer = results.get_counts() + + x.append(Niter) + y.append(answer[format(Nsol,'b').zfill(n)]) + +plt.clf() +plt.scatter(x,y) +plt.xlabel('N_iterations') +plt.ylabel('# of correct observations (1 solution)') +plt.show() +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +The result will show that the correct answer is observed with the highest probability when repeating the Grover's iteration $5\sim6$ times. Please check if this is consistent with what we analytically obtained above as the most probable number of iterations to get a correct answer. + ++++ {"pycharm": {"name": "#%% md\n"}} + +Exercise : Consider the case of a single answer. Examine the most probable number of iterations obtained using simulator and the size of the list, $N$, when $N$ varies from $N=2^4$ to $N=2^{10}$. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(imp_simulator_multi)= +### Case of Searching for Multiple Data + +Now we consider searching for multiple data from the list. Let us modify the circuit to be able to find two integers $x_1$ and $x_2$, and make a plot of the number of Grover's iterations versus how many times we observe the correct answer. + +For example, $x_1=45$ and $x_2=26$, + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +N1 = 45 +N2 = 26 + +oracle_2sol = QuantumCircuit(n) + +# 45 +oracle_2sol.x(1) +oracle_2sol.x(4) +oracle_2sol.h(n-1) +oracle_2sol.mcx(list(range(n-1)), n-1) +oracle_2sol.h(n-1) +oracle_2sol.x(1) +oracle_2sol.x(4) + +# 26 +oracle_2sol.x(0) +oracle_2sol.x(2) +oracle_2sol.x(5) +oracle_2sol.h(n-1) +oracle_2sol.mcx(list(range(n-1)), n-1) +oracle_2sol.h(n-1) +oracle_2sol.x(0) +oracle_2sol.x(2) +oracle_2sol.x(5) + +oracle_2sol_gate = oracle_2sol.to_gate() +oracle_2sol_gate.name = "U_w(2sol)" +print(oracle_2sol) + +x = [] +y = [] +for Niter in range(1,11): + grover_circuit_2sol_iterN = QuantumCircuit(n) + grover_circuit_2sol_iterN.h(range(n)) + for I in range(Niter): + grover_circuit_2sol_iterN.append(oracle_2sol_gate, list(range(n))) + grover_circuit_2sol_iterN.append(diffuser(n), list(range(n))) + grover_circuit_2sol_iterN.measure_all() + #print('----- Niter =',Niter,' -----------') + #print(grover_circuit_2sol_iterN) + + grover_circuit_2sol_iterN_tr = transpile(grover_circuit_2sol_iterN, backend=simulator) + results = simulator.run(grover_circuit_2sol_iterN_tr, shots=1024).result() + answer = results.get_counts() + #show_distribution(answer) + + x.append(Niter) + y.append(answer[format(N1,'06b')]+answer[format(N2,'06b')]) + +plt.clf() +plt.scatter(x,y) +plt.xlabel('N_iterations') +plt.ylabel('# of correct observations (2 solutions)') +plt.show() +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +In this case, the number of iterations to have the highest probability is smaller than that in the case of single answer. Is this what you expected, right? diff --git a/source/en/grover_number_light.md b/source/en/grover_number_light.md new file mode 100644 index 00000000..ea4e7338 --- /dev/null +++ b/source/en/grover_number_light.md @@ -0,0 +1,167 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【Exercise】Finding Operations of Bit Reversing Board + +Here we consider a problem of converting a given number to another using a hypothetical board "Bit Reversing Board" and Grove's algorithm. + ++++ {"pycharm": {"name": "#%% md\n"}} + +## Problem Setup + +In {doc}`Grover's algorithm `, we considered the problem of finding 45 from the list containing $N=2^6$ elements ($=[0,1,2,\cdots,63]$) is considered. In this exercise, we extend this 6-qubit search problem, as follows. + +We have a board in our hand, and we write down a number in binary format. For example, 45 is written as $101101(=45)$ in the board with 6 slots. + +```{image} figs/grover_kadai1.png +:alt: grover_kadai1 +:width: 500px +:align: center +``` + +This board has a property that when one *pushes down* the bit of a certain digit, that bit and neighboring bits are *reversed*. For example, in the case of 45, if one pushes down the second bit from the highest digit, the number is changed to $010101(=21)$. + +```{image} figs/grover_kadai2.png +:alt: grover_kadai2 +:width: 500px +:align: center +``` + +In this exercise, we attemp to convert a certain number, say 45, to another number, e.g, 13 using this board. In particular, we want to find a **sequence (= the order of pushing down the bits) of the smallest number of bit operations** to reach the desired number. + +```{image} figs/grover_kadai3.png +:alt: grover_kadai3 +:width: 500px +:align: center +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +## Hint + +There are many approaches to tackle the problem, but we can consider a quantum circuit with 3 quantum registers and 1 classical register. + +- Quantum register to store a number on the board = *board* +- Quantum register to store a pattern of pushing down the board = *flip* +- Quantum register to store a bit whose phase is flipped when the number on the board is equal to the desired one = *oracle* +- Clasical register to hold the measurement result = *result* + +You could try the followings using this circuit: + +1. Set 45 as an initial state on the board register. +2. Create the superposition of *all possible patterns of pushing down the 6 qubits* in the flip register for a 6-bit number problem. +3. Implement quantum gates to reverse bits in the board register for individual bit operations. +4. Construct unitary to flip phase of the oracle register when the number on the board register is what we want. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Tested with python 3.8.12, qiskit 0.34.2, numpy 1.22.2 +from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, transpile +from qiskit_aer import AerSimulator +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Consider 6-qubit search problem +n = 6 # Number of qubits + +# Number of Grover iterations = Closest integer to pi/4*sqrt(2**6) +niter = 6 + +# Registers +board = QuantumRegister(n) # Register to store the board number +flip = QuantumRegister(n) # Register to store the pattern of pushing down the board +oracle = QuantumRegister(1) # Register for phase flip when the board number is equal to the desired one. +result = ClassicalRegister(n) # Classical register to hold the measurement result +``` + +Complete the following cell. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +qc = QuantumCircuit(board, flip, oracle, result) + +################## +### EDIT BELOW ### +################## + +# Write down the qc circuit here. + +################## +### ABOVE BELOW ### +################## +``` + +Run the code using simulator and check the result. Among the results of bit sequences, those with 10 highest occurrences are displayed below as the final score. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Run on simulator +backend = AerSimulator() +qc_tr = transpile(qc, backend=backend) +job = backend.run(qc_tr, shots=8000) +result = job.result() +count = result.get_counts() + +score_sorted = sorted(count.items(), key=lambda x:x[1], reverse=True) +final_score = score_sorted[0:10] + +print('Final score:') +print(final_score) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +**Items to submit** +- Quantum circuit to solve the problem. +- Result demonstrating that the pattern to convert 45 to 13 is successfully found. diff --git a/source/en/more_dynamics.md b/source/en/more_dynamics.md new file mode 100644 index 00000000..80b022f7 --- /dev/null +++ b/source/en/more_dynamics.md @@ -0,0 +1,483 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【課題】量子ダイナミクスシミュレーション・続 + +第三回の実習では量子計算の並列性と、その顕著な利用法としての量子ダイナミクスシミュレーションを取り上げました。また、実機で計算を行う際の実用的な問題として、回路の最適化や測定エラーの緩和についても議論しました。この課題はその直接の延長です。 +In the third exercise, we looked at the parallelism of quantum calculations and, as a prominent example of how that can be used, at simulations of quantum dynamics. We also discussed how to optimize circuits and mitigate measurement error, practical issues involved in performing computations on actual QC. This assignment is a direct extension of what we have been discussing. + +```{contents} 目次 +--- +local: true +--- +``` +$\newcommand{\ket}[1]{|#1\rangle}$ +$\newcommand{\plusket}{\ket{+}}$ +$\newcommand{\minusket}{\ket{-}}$ + ++++ + +## 問題1: ハイゼンベルグモデル、X方向のスピン + ++++ + +### 問題 + +実習ではハイゼンベルグモデルのシミュレーションをし、各スピンの$Z$方向の期待値の時間発展を追いました。しかし、シミュレーションそのものは最終的なオブザーバブル(観測量)によらず成立するので、(ほぼ)同じ回路を用いて系の他の性質を調べることもできます。そこで、各スピンの$X$方向の期待値の時間発展を測定する回路を書き、実習時と同様に時間に対してプロットしてください。 +In the exercise, we simulated a Heisenberg model and tracked the time evolution of expectation values for each spin in the Z direction. However, simulations stand without ultimate observables, so we can use a (mostly) identical circuit to explore other properties of the system. In this task, create a circuit for measuring the time evolution of expectation values for each spin in the $X$ direction and, as in the exercise, plot them over time. + +**Hint**: + +[プロット用関数`plot_heisenberg_spins`](https://github.com/UTokyo-ICEPP/qc-workbook/blob/master/source/utils/dynamics.py)で厳密解のカーブを書くとき、追加の引数`spin_component='x'`を渡すと$X$方向のスピンのプロットに切り替わります。ただし、実験結果の`counts_list`は相応する測定の結果となっていなければいけません。具体的には、各スピンについて「0が測定される=スピンが+$X$を向いている、1が測定される=スピンが-$X$を向いている」という対応付けが必要です。) +When we draw the curve of the exact solution using the [plotting function `plot_heisenberg_spins`](https://github.com/UTokyo-ICEPP/qc-workbook/blob/master/source/utils/dynamics.py), we can add the `spin_component='x'` argument to switch to plotting the spin in the $X$ direction. However, the counts_list of the experimental results must produce corresponding measurement results. Specifically, a measurement of $\theta$ must correspond to the spin being in the +$X$ direction, and a measurement of 1 must correspond to the spin being in the -$X$ direction. + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# 必要なモジュールを先にインポート +# First, import all required modules +import numpy as np +import matplotlib.pyplot as plt +from qiskit import QuantumCircuit, transpile +from qiskit.quantum_info import SparsePauliOp +from qiskit_aer import AerSimulator +# このワークブック独自のモジュール +# Modules unique to this workbook +from qc_workbook.dynamics import plot_heisenberg_spins, bit_expectations_sv, bit_expectations_counts, diagonalized_evolution +``` + +```{code-cell} ipython3 +:tags: [remove-output] + +n = 5 +M = 10 +omegadt = 0.1 + +shots = 100000 + +# Define the circuits +circuits = [] + +circuit = QuantumCircuit(n) + +# Bit 0 in state 1/sqrt(2)(|0> + |1>) +circuit.h(0) + +for istep in range(M): + for j in range(n - 1): + # ZZ + circuit.cx(j, j + 1) + circuit.rz(-omegadt, j + 1) + circuit.cx(j, j + 1) + + # XX + circuit.h(j) + circuit.h(j + 1) + circuit.cx(j, j + 1) + circuit.rz(-omegadt, j + 1) + circuit.cx(j, j + 1) + circuit.h(j) + circuit.h(j + 1) + + # YY + circuit.p(-np.pi / 2., j) + circuit.p(-np.pi / 2., j + 1) + circuit.h(j) + circuit.h(j + 1) + circuit.cx(j, j + 1) + circuit.rz(-omegadt, j + 1) + circuit.cx(j, j + 1) + circuit.h(j) + circuit.h(j + 1) + circuit.p(np.pi / 2., j) + circuit.p(np.pi / 2., j + 1) + + # Copy of the circuit up to this point + snapshot = circuit.copy() + + ################## + ### EDIT BELOW ### + ################## + + # Set up the observable for this snapshot + #snapshot.? + + ################## + ### EDIT ABOVE ### + ################## + + snapshot.measure_all() + circuits.append(snapshot) + +simulator = AerSimulator() + +circuits = transpile(circuits, backend=simulator) +sim_job = simulator.run(circuits, shots=shots) +sim_counts_list = sim_job.result().get_counts() + +# Initial state as a statevector +initial_state = np.zeros(2 ** n, dtype=np.complex128) +initial_state[0:2] = np.sqrt(0.5) + +plot_heisenberg_spins(sim_counts_list, n, initial_state, omegadt, add_theory_curve=True, spin_component='x') +``` + +**提出するもの** + +- 完成した回路のコードとシミュレーション結果によるプロット +- 一般の方向のスピンの期待値を測定するためにはどうすればいいかの説明 + +**Items to submit**e: +- The code for the completed circuit and the plotted simulation results +- An explanation of how to measure expectation values for spin in any direction + ++++ + +### おまけ: スピン総和 + +注:これは量子コンピューティングというより物理の問題なので、興味のある方だけ考えてみてください。 +Note: This is more of a physics problem than a quantum computing problem, so feel free to skip it if you are not interested. + +上のハイゼンベルグモデルのシミュレーションで、初期状態の$X$, $Y$, $Z$方向のスピン期待値の全系での平均値$m_x$, $m_y$, $m_z$はそれぞれ +In the above Heisenberg model simulation, the system-wide average values $m_x$, $m_y$, and $m_z$ for the expectation values of spin in the $X$, $Y$, and $Z$ directions in the initial state are as indicated below. + +$$ +m_x = \frac{1}{n} \sum_{j=0}^{n} \langle \sigma^{X}_j \rangle = \frac{1}{n} \\ +m_y = \frac{1}{n} \sum_{j=0}^{n} \langle \sigma^{Y}_j \rangle = 0 \\ +m_z = \frac{1}{n} \sum_{j=0}^{n} \langle \sigma^{Z}_j \rangle = \frac{n-1}{n} +$$ + +です。これらの平均値はどう時間発展するでしょうか。理論的議論をし、シミュレーションで数値的に確かめてください。 +How do these average values evolve with time? Consider this from a theoretical standpoint and then quantitatively confirm your conclusions through simulation. + ++++ + +## 問題2: シュウィンガーモデル + +これまで扱ったような、スピンに関連する現象とは異なる物理モデルのシミュレーションをしましょう。空間1次元、時間1次元の時空における量子電磁力学の模型「シュウィンガーモデル」を考えます。 +Instead of simulating phenomena related to spin, as we have so far, let's now perform a simulation of a different physical model. Let's consider the Schwinger model, a quantum electrodynamics model with one spatial dimension and one time dimension. + ++++ + +### シュウィンガーモデルの物理 + +簡単に物理の解説をします(ここは読み飛ばしても差し支えありません)。といっても、まともにゼロから解説をしたらあまりにも長くなってしまうので、かなり前提知識を仮定します。興味のある方は参考文献{cite}`shifman_schwinger,Martinez_2016`などを参照してください。特に{cite}`Martinez_2016`は実際にこれから実装する回路をイオントラップ型量子コンピュータで実行した論文です。 +First, let's briefly go over the physics (you may skip this section if you wish). Explaining everything from the basics would take an extremely long time, so we'll assume you have a great deal of prior knowledge. If you're interested, check reference materials{cite}`shifman_schwinger,Martinez_2016` in the Bibliography. {cite}`Martinez_2016`, in particular, is an essay about running the circuit we are about to create on a trapped-ion quantum computer. + +量子電磁力学とは量子場の理論の一種です。量子場の理論とは物質やその相互作用(力)をすべて量子力学的な「場」(時空中の各点に応じた値を持つ存在)で記述した理論で、素粒子論などで物質の根源的な性質を記述する際の基本言語です。量子場の理論において、一部の場を「物質場」とし、それに特定の対称性($U(1)$ゲージ対称性)を持たせると、「電荷」が生み出され、電荷を持った場の間の相互作用を媒介する「光子場」が生じます。電荷を持った場と光子場の振る舞いを記述するのが量子電磁力学です。 +Quantum electrodynamics are a type of quantum field theory. Quantum field theory describes all matter and mutual interaction (forces) as quantum mechanics fields (assigning numerical values to every point in space-time). It is the basic language used to indicate the fundamental properties of matter in elementary particle theory and the like. In quantum field theory, some fields are considered "matter fields." When they have specific symmetry ($U(1)$ gauge symmetry), "electric charges" are produced, and "photon fields," which act as the media through which fields with charges mutually interact, are created. Quantum electrodynamics express the behavior of fields with charges and photon fields. + +量子場の理論は「ラグランジアン」[^lagrangian]を指定すれば定まります。シュウィンガーモデルのラグランジアンは物質場(電子)$\psi$とゲージ場(光子)$A$からなり、 +Quantum field theory is defined by specifying the Lagrangian[^lagrangian]. The Lagrangian of the Schwinger model is made up of the matter field (electron) $\psi$ and the gauge field (photon) $A$, as follows. + +```{math} +:label: schwinger_lagrangian +\mathcal{L} = -\frac{1}{4g^2} F^{\mu\nu}F_{\mu\nu} + \bar{\psi} (i\gamma^{\mu}D_{\mu} - m) \psi +``` + +です。ただし、これまでの物理系を扱った話と異なり、ここでは場の量子論の一般慣習に従って、光速$c$とプランク定数$\hbar$がともに1である単位系を使っています。 +However, unlike the physical systems we've been dealing with so far, as is customary with quantum field theory, we will use a unit system in which both the speed of light, $c$, and the Planck constant, $\hbar$, are 1. + +式{eq}`schwinger_lagrangian`の指数$\mu, \nu$は0(時間次元)か1(空間次元)の値を取ります。$\frac{1}{2g} F_{\mu\nu}$は$A$の強度テンソル(電場)で +Exponents $\mu$ and $\nu$ in Formula{eq}`schwinger_lagrangian` are either 0 (time dimension) or 1 (spatial dimension). $\frac{1}{2g} F_{\mu\nu}$ is $A$'s strength tensor (electrical field). + +$$ +F_{\mu\nu} = \partial_{\mu} A_{\nu} - \partial_{\nu} A_{\mu} +$$ + +です。$\psi$は物質と反物質を表す2元スピノルで、$m$がその質量となります。$\{\gamma^0, \gamma^1\}$は2次元のクリフォード代数の表現です。 +$\psi$ is a two component spinor that represents matter and antimatter, and $m$ is the mass. $\{\gamma^0, \gamma^1\}$ is a two-dimensional Clifford algebra expression. + +このラグランジアンを元に、Kogut-Susskindの手法{cite}`PhysRevD.10.732`でモデルを空間格子(格子間隔$a$)上の場の理論に移すと、そのハミルトニアンは +Based on this Lagrangian, if we use the Kogut-Susskind method{cite}`PhysRevD.10.732` to move the model to field theory on a spatial lattice (with lattice spacing $a$), the Hamiltonian becomes as follows. + +```{math} +:label: kogut_susskind_hamiltonian +H = \frac{1}{2a} \bigg\{ -i \sum_{j=0}^{n-2} \left[ \Phi^{\dagger}_{j} e^{i\theta_{j}} \Phi_{j+1} + \Phi_{j} e^{-i\theta_{j}} \Phi^{\dagger}_{j+1} \right] + 2 J \sum_{j=0}^{n-2} L_{j}^2 + 2 \mu \sum_{j=0}^{n-1} (-1)^{j+1} \Phi^{\dagger}_{j} \Phi_{j} \bigg\} +``` + +となります。ここで$J = g^2 a^2 / 2$, $\mu = m a$, また$\Phi_j$はサイト$j$上の(1元)物質場、$\theta_j$は$j$上のゲージ場、$L_j$は格子$j$と$j+1$間の接続上の電場です。 +Here, $J = g^2 a^2 / 2$, $\mu = m a$, $\Phi_j$ is a (one-dimensional) matter field on site $j$, $\theta_j$ is a gauge field on $j$, and $L_j$ is an electrical field on the connection between lattice $j$ and $j+1$. + +Kogut-Susskindハミルトニアンにおける物質場はstaggered fermionsと呼ばれ、隣接サイトのうち片方が物質を、もう一方が反物質を表します。約束として、ここでは$j$が偶数のサイトを物質(電荷-1)に、奇数のサイトを反物質(電荷1)に対応付けます。一般に各サイトにおける物質の状態は、フェルミ統計に従って粒子が存在する・しないという2つの状態の重ね合わせです。サイト$j$の基底$\plusket_j$と$\minusket_j$を、$\Phi_j$と$\Phi^{\dagger}_j$が +The matter field of the Kogut-Susskind Hamiltonian are called staggered fermions, and on adjacent sites one side is matter while the other side is antimatter. Here, for $j$, even-numbered sites correspond to matter (electric charge -1) and odd-numbered sites correspond to antimatter (electric charge 1). Generally speaking, the state of matter at each site is a superposition of the two states of particles existing or not existing, in accordance with Fermi statistics. $\Phi_j$ and $\Phi^{\dagger}_j$ are defined as states that act on the basis states $\plusket_j$ and $\minusket_j$ of site $j$ as follows. + +```{math} +:label: creation_annihilation +\Phi_j \plusket_j = \minusket_j \\ +\Phi_j \minusket_j = 0 \\ +\Phi^{\dagger}_j \plusket_j = 0 \\ +\Phi^{\dagger}_j \minusket_j = \plusket_j +``` + +と作用する状態と定めます。質量項の符号から、偶数サイトでは$\minusket$が粒子が存在する状態、$\plusket$が存在しない状態を表現し、奇数サイトでは逆に$\plusket$が粒子あり、$\minusket$が粒子なしを表すことがわかります。つまり、$\Phi^{\dagger}_j$と$\Phi_j$はサイト$j$における電荷の上昇と下降を引き起こす演算子です。 +Based on the signs of the mass terms, for even-numbered sites, $\minusket$ indicates that a particle is present and $\plusket$ indicates that it is not. Conversely, for odd-numbered sites, $\plusket$ indicates that a particle is present, and $\minusket$ indicates that it is not. In other words, $\Phi^{\dagger}_j$ and $\Phi_j$ are operators that raise and lower the electric charge at site $j$. + ++++ + +### ハミルトニアンを物質場のみで記述する + +$\newcommand{\flhalf}[1]{\left\lfloor \frac{#1}{2} \right\rfloor}$ + +このままのハミルトニアンではまだデジタルモデルが構築しにくいので、ゲージを固定して$\theta$と$L$を除いてしまいます[^another_approach]。まず$\Phi_j$を以下のように再定義します。 +It would be difficult to create a digital model using this Hamiltonian as-is, so let's set the gauge as a constant and eliminate $\theta$ and $L$[^another_approach]. First, let's redefine $\Phi_j$ as follows. + +$$ +\Phi_j \rightarrow \prod_{k=0}^{j-1} e^{-i\theta_{k}} \Phi_j. +$$ + +また、ガウスの法則から、サイト$j$の電荷$\rho_j$が同じサイトの電場の発散と等しいので、 +Also, because of Gauss' law, the electric charge $\rho_j$ at site $j$ is equal to the divergence of the site's electric field, so the following is true. + +$$ +L_j - L_{j-1} = \rho_j \\ +\therefore L_j = \sum_{k=0}^{j} \rho_k +$$ + +となります。ただし、サイト0に系の境界の外から作用する電場はないもの($L_{-1} = 0$)としました。 +However, site 0 is defined as not having any electrical charges from outside the system's borders that act on it, ($L_{-1} = 0$). + +質量項と同様にサイトの偶奇を考慮した電荷は +Like with mass terms, each site's even or odd numbering is taken into consideration in the electrical charge, which is as follows. + +$$ +\rho_k = \Phi_{k}^{\dagger} \Phi_{k} - (k+1 \bmod 2) +$$ + +なので、 +And thus: + +$$ +L_j = \sum_{k=0}^{j} \Phi_{k}^{\dagger} \Phi_{k} - \flhalf{j} - 1 +$$ + +となります。ここで$\flhalf{j}$は切り捨ての割り算$[j - (j \bmod 2)]/2$(Pythonでの`j // 2`と同等)です。この電場を式{eq}`kogut_susskind_hamiltonian`に代入して +Here, the thick lined-fraction $\flhalf{j}$ is rounded down division $[j - (j \bmod 2)]/2$ (equivalent to `j // 2` in Python). Substituting this electrical field into Formula{eq}`kogut_susskind_hamiltonian` give us the following. + +$$ +H = \frac{1}{2a} \left\{ -i \sum_{j=0}^{n-2} \left[ \Phi^{\dagger}_{j} \Phi_{j+1} + \Phi_j \Phi^{\dagger}_{j+1} \right] + 2J \sum_{j=0}^{n-2} \left[\sum_{k=0}^{j} \Phi_{k}^{\dagger} \Phi_{k} - \flhalf{j} - 1 \right]^2 + 2\mu \sum_{j=0}^{n-1} (-1)^{j+1} \Phi^{\dagger}_{j} \Phi_{j} \right\} +$$ + +が得られます。 + ++++ + +### ハミルトニアンをパウリ行列で表現する + +最後に、$\plusket$と$\minusket$をスピン$\pm Z$の状態のようにみなして、$\Phi^{\dagger}_j\Phi_j$と$\Phi^{\dagger}_j\Phi_{j+1}$をパウリ行列で表現します。式{eq}`creation_annihilation`から +前者は +Lastly, we can consider $\plusket$ and $\minusket$ to be spin states $\pm Z$, and express $\Phi^{\dagger}_j\Phi_j$と and $\Phi^{\dagger}_j\Phi_{j+1}$ as Pauli matrices. Based on Formula{eq}`creation_annihilation`, the former can be expressed as follows. + +$$ +\Phi^{\dagger}_j\Phi_j \rightarrow \frac{1}{2} (\sigma^Z_j + 1) +$$ + +と表現できることがわかります。一方、$\Phi^{\dagger}_j\Phi_{j+1}$に関しては、やや込み入った議論{cite}`PhysRevD.13.1043`の末、 +With regard to $\Phi^{\dagger}_j\Phi_{j+1}$, as the result of some somewhat complex discussion{cite}`PhysRevD.13.1043`, we find that the following expression is correct. + +$$ +\Phi^{\dagger}_j\Phi_{j+1} \rightarrow i \sigma^-_{j+1} \sigma^+_{j} +$$ + +が正しい表現であることがわかっています。ここで、 +Here, + +$$ +\sigma^{\pm} = \frac{1}{2}(\sigma^X \pm i \sigma^Y) +$$ + +です。また、このワークブックでの約束に従って、右辺の$j$と$j+1$の順番をひっくり返してあります。 + +ハミルトニアンには$\Phi_j\Phi^{\dagger}_{j+1} \rightarrow i \sigma^+_{j+1}\sigma^-_{j}$も登場するので、二つの項を合わせると +$\Phi_j\Phi^{\dagger}_{j+1} \rightarrow i \sigma^+_{j+1}\sigma^-_{j}$ also appears, so combining the two terms produces the following. + +$$ +\Phi^{\dagger}_{j} \Phi_{j+1} + \Phi_j \Phi^{\dagger}_{j+1} \rightarrow \frac{i}{2} (\sigma^X_{j+1}\sigma^X_j + \sigma^Y_{j+1}\sigma^Y_j) +$$ + +となります。まとめると、 +In summary, we arrive at the following. + +$$ +H \rightarrow \frac{1}{4a} \left\{ \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_j + \sigma^Y_{j+1}\sigma^Y_j) + J \sum_{j=1}^{n-2} (n - j - 1) \sum_{k=0}^{j-1} \sigma^Z_j \sigma^Z_k + \sum_{j=0}^{n-1} \left[ (-1)^{j+1} \mu - J \flhalf{n-j} \right] \sigma^Z_j \right\} +$$ + +です。ただし、計算過程で現れる定数項(恒等演算子に比例する項)は時間発展において系の状態に全体位相をかける作用しか持たないため、無視しました。 +However, the constant terms that appear during the course of this calculation (terms that are proportional to the identity operator) only act on the overall phase of the system's state as it evolves over time, so we have disregarded them. + ++++ + +### 問題 + +上のシュウィンガーモデルのハミルトニアンによる時間発展シミュレーションを、$\plusket$と$\minusket$をそれぞれ$\ket{0}$と$\ket{1}$に対応させて、8ビット量子レジスタに対して実装してください。初期状態は真空、つまりどのサイトにも粒子・反粒子が存在しない状態$\ket{+-+-+-+-}$とし、系全体の粒子数密度の期待値 +Implement the following time evolution simulation of the Schwinger model using the Hamiltonian, associating $\plusket$ and $\minusket$ with $\ket{0}$ and $\ket{1}$, respectively, using an 8-bit quantum register. Set the initial state to a vacuum -- that is, $\ket{+-+-+-+-}$, and plot the system's overall particle density expectation value, shown in the formula below, as a function of time. + +$$ +\nu = \left\langle \frac{1}{n} \sum_{j=0}^{n-1} \frac{1}{2} \left[(-1)^{j+1} \sigma^Z_j + 1\right] \right\rangle +$$ + +を時間の関数としてプロットしてください。余裕があれば、各サイトにおける粒子数、電荷、サイト間の電場などの期待値の時間変化も観察してみましょう。 +If this is relatively easy for you, also try observing how expectation values such as the number of particles, electric charge, and electrical field between sites change over time at each site. + +ハミルトニアンのパラメターは、$J = 1$, $\mu = 0.5$とします(他の$J$や$\mu$の値もぜひ試してみてください)。$\omega = 1/(2a)$とおき、鈴木・トロッター分解における時間ステップ$\Delta t$の大きさ$\omega \Delta t = 0.2$として、時間$\omega t = 2$までシミュレーションをします。 +Use the following for Hamiltonian parameters: $J = 1$, $\mu = 0.5$ (also try it out using different $J$ and $\mu$ values). Set $\omega = 1/(2a)$ and the size of the Suzuki-trotter transformation time step $\Delta t$ to $\omega \Delta t = 0.2$, and then simulate time $\omega t = 2$. + +**解説**: +**Explanation**: + +偶数サイトでは$\plusket$が物質粒子の存在しない状態、奇数サイトでは$\minusket$が反物質粒子の存在しない状態を表すので、初期状態は粒子数密度0となります。しかし、場の量子論においては場の相互作用によって物質と反物質が対生成・対消滅を起こし、一般に系の粒子数の期待値は時間とともに変化します。 +At even-numbered sites, $\plusket$ represents the state when there is no matter particle present, while at odd-numbered sites, $\minusket$ represents the state when there is no matter particle present. Therefore, the particle density in the system's initial state is 0. However, in quantum field theory, the fields act on each other, generating pairs of matter and antimatter particles which annihilate each other, so in general the expectation values of the number of particles changes with time. + +**ヒント**: +**Hint**: + +上のハミルトニアンのパラメターの値は参考文献{cite}`Martinez_2016`と同一です(ただし$\sigma$積の順番は逆です)。したがって、$n=4$, $\omega \Delta t = \pi/8$とすれば、論文中の図3aを再現できるはずです。答え合わせに使ってください。 +The above Hamiltonian parameter values are identical to those indicated in reference material{cite}`Martinez_2016`. Therefore, if $n=4$ and $\omega \Delta t = \pi/8$, you should be able to replicate what is shown in Figure 3a of the paper. Use it to check your answer. + +また、問題を解くためのヒントではありませんが、ハイゼンベルグモデルと同様にこのモデルでも対角化による厳密解を比較的簡単にプロットできるように道具立てがしてあります。下のコードのテンプレートでは、シミュレーション回路と厳密解を計算するためのハミルトニアンのパウリ行列分解だけ指定すれば、`plot_heisenberg_spins`と同様のプロットが作成されるようになっています。パウリ行列分解を指定するには、`paulis`と`coeffs`という二つのリストを作り、Qiskitの`SparsePauliOp`というクラスに渡します。これらのリストの長さはハミルトニアンの項数で、`paulis`の各要素は対応する項のパウリ行列のリスト、`coeffs`の各要素はその項にかかる係数にします。例えば +Furthermore, although this is not a hint for solving the task, the groundwork has been laid for plotting the exact solution with relative ease by applying the same diagonalize to this model as you did to the Heisenberg model. With the code template below, you can perform plotting in the same way as with `plot_heisenberg_spins` simply by specifying the simulation circuit and the Pauli matrix decomposition of the Hamiltonian used to calculate the exact solution. To specify the Pauli matrix decomposition, create two lists, `paulis` and `coeffs`. The length of each of these lists is the number of Hamiltonian terms. In the `paulis` list, each of the elements corresponds to a term's Pauli matrix. In the `coeffs` list, each element is the coefficient of that term. For example, for the following Hamiltonian, the lists' contents would be as shown below. + +$$ +H = 0.5 \sigma^X_2 \sigma^Y_1 I_0 + I_2 \sigma^Z_1 \sigma^X_0 +$$ + +というハミルトニアンに対しては、 + +```{code-block} python +paulis = ['XYI', 'IZX'] +coeffs = [0.5, 1.] +``` + +です。 + +[^lagrangian]: ここで「ラグランジアン」と呼ばれているのは本来「ラグランジアン密度」で、正しくはこれを空間積分したものがラグランジアンですが、素粒子論の文脈で「ラグランジアン」と言った場合はほぼ100%積分する前のものを指します。 +[^another_approach]: 参考文献{cite}`Shaw2020quantumalgorithms`では、別のアプローチで同じハミルトニアンの量子回路実装をしています。 +[^lagrangian]: Here, what we call the "Lagrangian" is normally called the "Lagrangian density." Properly speaking, the Lagrangian is this integrated over space and time, but in the context of elementary particle theory, almost 100% of the time, the term "Lagrangian" refers the Lagrangian density before integration. +[^another_approach]: In reference material{cite}`Shaw2020quantumalgorithms`, a different approach is made to implement a quantum circuit with the same Hamiltonian. + + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +def number_density(bit_exp): + particle_number = np.array(bit_exp) # shape (T, n) + # Particle number is 1 - (bit expectation) on odd sites + particle_number[:, 1::2] = 1. - particle_number[:, 1::2] + + return np.mean(particle_number, axis=1) + + +n = 8 # number of sites +J = 1. # Hamiltonian J parameter +mu = 0.5 # Hamiltonian mu parameter + +## Quantum circuit experiment + +M = 10 # number of Trotter steps +omegadt = 0.2 # Trotter step size + +shots = 100000 + +# Define the circuits +circuits = [] + +circuit = QuantumCircuit(n) + +# Initial state = vacuum +circuit.x(range(1, n, 2)) + +for istep in range(M): + ################## + ### EDIT BELOW ### + ################## + + #circuit.? + + ################## + ### EDIT ABOVE ### + ################## + + circuits.append(circuit.measure_all(inplace=False)) + +# Run the circuits in the simulator +simulator = AerSimulator() + +circuits = transpile(circuits, backend=simulator) +sim_job = simulator.run(circuits, shots=shots) +sim_counts_list = sim_job.result().get_counts() +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +## Numerical solution through diagonalization + +################## +### EDIT BELOW ### +################## + +paulis = ['I' * n] +coeffs = [1.] + +################## +### EDIT ABOVE ### +################## + +hamiltonian = SparsePauliOp(paulis, coeffs).to_matrix() + +# Initial state as a statevector +initial_state = np.zeros(2 ** n, dtype=np.complex128) +vacuum_state_index = 0 +for j in range(1, n, 2): + vacuum_state_index += (1 << j) +initial_state[vacuum_state_index] = 1. + +## Plotting + +# Plot the exact solution +time_points, statevectors = diagonalized_evolution(hamiltonian, initial_state, omegadt * M) +_, bit_exp = bit_expectations_sv(time_points, statevectors) + +plt.plot(time_points, number_density(bit_exp)) + +# Prepend the "counts" (=probability distribution) for the initial state to counts_list +initial_probs = np.square(np.abs(initial_state)) +fmt = f'{{:0{n}b}}' +initial_counts = dict((fmt.format(idx), prob) for idx, prob in enumerate(initial_probs) if prob != 0.) +sim_counts_list_with_init = [initial_counts] + sim_counts_list + +# Plot the simulation results +time_points = np.linspace(0., omegadt * M, M + 1, endpoint=True) +_, bit_exp = bit_expectations_counts(time_points, sim_counts_list_with_init, n) + +plt.plot(time_points, number_density(bit_exp), 'o') +``` + ++++ {"tags": ["remove-output"]} + +**提出するもの** +- 完成した回路のコードとシミュレーション結果によるプロット + +**Items to submit**: +- The code for the completed circuit and the plotted simulation results diff --git a/source/en/nonlocal_correlations.md b/source/en/nonlocal_correlations.md new file mode 100644 index 00000000..d7eeb9d6 --- /dev/null +++ b/source/en/nonlocal_correlations.md @@ -0,0 +1,670 @@ +--- +jupyter: + jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.14.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 + language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【課題】量子相関を調べる + +$\newcommand{\ket}[1]{|#1\rangle}$ +$\newcommand{\bra}[1]{\langle#1|}$ +$\newcommand{\rmI}{\mathrm{I}}$ +$\newcommand{\rmII}{\mathrm{II}}$ +$\newcommand{\rmIII}{\mathrm{III}}$ +$\newcommand{\rmIV}{\mathrm{IV}}$ + +第一回の実習ではCHSH不等式の破れを調べるために、2つの量子ビットの相関関数$C^{i} \, (i=\rmI, \rmII, \rmIII, \rmIV)$という量を量子コンピュータを使って計算しました。この課題では、この量をもう少し細かく調べてみましょう。 +In the first exercise, you used a quantum computer to calculate the correlation function $C^{i} \, (i=\rmI, \rmII, \rmIII, \rmIV)$ for two quantum bits to investigate the violation of the CHSH inequality. In this assignment, you will investigate these quantities in a bit more detail. + +```{contents} 目次 +--- +local: true +--- +``` + + +## QCシミュレータの使い方 + +実習で見たように、QCで実現される量子状態は、量子力学の公理に基づいて理論的に計算・予測できます。そこで用いられる数学的操作も単なる足し算や掛け算(線形演算)なので、実はQCの量子状態は(古典)計算機で比較的簡単に計算できます。当然のことですが、QCは何も魔法のブラックボックスというわけではありません。 +As you saw in the exercise, quantum states produced by the QC can be logically calculated and predicted based on the principles of quantum mechanics. The mathematical operations used to do this consist simply of addition and multiplication (linear operations), so in reality a QC's quantum state can be calculated relatively easily with a (classical) computer. Needless to say, QCs are not some sort of magical black box. + +ただし、古典計算機で量子状態を再現するためには、特殊な場合を除いて、量子ビット数の指数関数的な量のメモリが必要になります。これも前半で見たように、$n$量子ビットあれば、系の自由度(degrees of freedom / dof: 実数自由パラメータの数)は$2^{n+1} - 2$ですので、例えば各自由度を64ビット(=8バイト)の浮動小数点で表現するとしたら、必要なメモリは(-2を無視して) +However, except in special cases, reproducing quantum states with a classical computer requires an exponential amount of memory in relation to the number of quantum bits. As you saw in the first half of this unit, a system with n quantum bits has $2^{n+1}-2$ degrees of freedom ("dof" - the number of free real number parameters), so, for example, a floating point decimal representation of 64 bits (=8 bytes) with each dof would require the following amount of memory (disregarding the -2): + +$$ +2^3\, \mathrm{(bytes / dof)} \times 2^{n+1}\, \mathrm{(dof)} = 2^{n+4}\, \mathrm{(bytes)} +$$ + +なので、$n=16$で1 MiB、$n=26$で1 GiB、$n=36$で1 TiBです。現在の計算機では、ハイエンドワークステーションでRAMが$\mathcal{O}(1)$ TiB、スパコン「富岳」で5 PB (~252 bytes)なのに対し、QCではすでに$n=127$のものが商用運用されているので、既に古典計算機でまともにシミュレートできない機械が存在していることになります。 +If $n=16$, it would require 1 MiB, $n=26$ would require 1 GiB, and $n=36$ would require 1 TiB of memory. Modern high-end workstations computers have $\mathcal{O}(1)$ TiB of RAM and the Fugaku supercomputer has 5 PB (roughly 252 bytes) of RAM. There are already $n=65$ QCs -- that is, there are already devices which cannot be properly simulated using classical computers. + +しかし、逆に言うと、$n \sim 30$程度までの回路であれば、ある程度のスペックを持った計算機で厳密にシミュレートできるということが言えます。じっさい世の中には数多くのシミュレータが存在します。Qiskitにも様々な高機能シミュレータが同梱されています。 +However, conversely, for circuits of up to roughly $n \sim 30$, the circuits could be strictly simulated by a computer with somewhat decent specs. There are many different simulators out there, and Qiskit also includes various advanced simulators. + +シミュレーションはローカル(手元のPythonを動かしているコンピュータ)で実行できるので、ジョブを投げて結果を待つ時間が省けます。この課題ではたくさんの細かい量子計算をするので、実機を使わず、`AerSimulator`というQiskitに含まれるシミュレータを利用します。 +Simulations can be performed locally (on a computer running Python), eliminating the need to submit jobs and wait for results. In this assignment, you will be performing many small quantum calculations, so instead of using an actual QC you will use the qasm_simulator simulator included with Qiskit. + +```python tags=["remove-output"] +# First, import everything +import numpy as np +import matplotlib.pyplot as plt +from scipy.optimize import minimize, Bounds +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator +from qiskit.visualization import plot_histogram + +print('notebook ready') +``` + +```python +simulator = AerSimulator() +print(simulator.name()) +``` + +実習の内容を再現してみましょう。 +Let's reproduce what we did in the exercise. + +```python +circuits = [] + +circuit = QuantumCircuit(2, name='circuit_I') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-np.pi / 4., 1) +circuit.measure_all() +circuits.append(circuit) + +circuit = QuantumCircuit(2, name='circuit_II') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-3. * np.pi / 4., 1) +circuit.measure_all() +circuits.append(circuit) + +circuit = QuantumCircuit(2, name='circuit_III') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-np.pi / 4., 1) +circuit.ry(-np.pi / 2., 0) +circuit.measure_all() +circuits.append(circuit) + +circuit = QuantumCircuit(2, name='circuit_IV') +circuit.h(0) +circuit.cx(0, 1) +circuit.ry(-3. * np.pi / 4., 1) +circuit.ry(-np.pi / 2., 0) +circuit.measure_all() +circuits.append(circuit) +``` + +```python +# シミュレータにはショット数の制限がないので、時間の許す限りいくらでも大きい値を使っていい +shots = 10000 + +# 実習と同じく transpile() - 今は「おまじない」と思ってよい +circuits = transpile(circuits, backend=simulator) +# シミュレータもバックエンドと同じように振る舞うので、runメソッドで回路とショット数を受け取り、ジョブオブジェクトを返す +sim_job = simulator.run(circuits, shots=shots) + +# シミュレータから渡されたジョブオブジェクトは実機のジョブと全く同じように扱える +sim_result = sim_job.result() + +C = np.zeros(4, dtype=float) +fig, axs = plt.subplots(2, 2, sharey=True, figsize=[12., 8.]) +for idx, (circuit, ax) in enumerate(zip(circuits, axs.reshape(-1))): + counts = sim_result.get_counts(idx) + plot_histogram(counts, ax=ax) + ax.set_title(circuit.name) + ax.yaxis.grid(True) + + C[idx] = counts.get('00', 0) + counts.get('11', 0) - counts.get('01', 0) - counts.get('10', 0) + +C /= shots + +S = C[0] - C[1] + C[2] + C[3] +print('S =', S) +``` + +上のように、`qasm_simulator`は実機と同様に`run`関数を実行でき、ヒストグラムデータを返します。実機ではショット数に制限がありますが、シミュレータにはありません。ただしショット数が多いほど、当然実行に時間がかかります。といってもこの程度の回路であれば常識的なショット数ならほぼ瞬間的にジョブの実行が終わるので、上の例では実習で使った`job_monitor()`関数を使用していません。また、シミュレータにはノイズがない[^simulator_noise]ので、$S$の計算結果が統計誤差の範囲内で理論値と一致していることが見て取れます。 +As shown above, `qasm_simulator` runs the same `run` function as the actual QC and returns histogram data. The actual QC has limits on the number of shots that can be run, but the simulator has no such limit. However, of course, the greater the number of shots, the longer it will take to run. That said, for a circuit as simple as this, a job with a reasonable number of shots would finish almost instantaneously, so the exercise example above does not use the `job_monitor()` function. Furthermore, there is no noise in the simulator[^simulator_noise], so, as you can see, the calculation results for S match theoretical calculations and fall within statistical error bounds. + +[^simulator_noise]: 標準設定において。実機の振る舞いに従うよう、あえてノイズを加えるような設定も存在します。 + + +## 測定基底の変換 + +さて、おさらいをすると、上の$C^{\rmI, \rmII, \rmIII, \rmIV}$を計算する4つの回路は以下のようなものでした。 +So, to review, the four circuits for calculating $C^{\rmI, \rmII, \rmIII, \rmIV}$ above are as shown below. + +```python tags=["remove-input"] +fig, axs = plt.subplots(2, 2, figsize=[12., 6.]) +for circuit, ax in zip(circuits, axs.reshape(-1)): + circuit.draw('mpl', ax=ax) + ax.set_title(circuit.name) +``` + +ベル状態を作るところまではすべての回路で共通で、その後それぞれ異なる角度の$R_y$ゲートをかけています。実習では深く立ち入りませんでしたが、この$R_y$ゲートはベル状態を$\{\ket{0}, \ket{1}\}$とは異なる基底で測定するために置かれています。どういうことか、以下で説明します。 +Up until the creation of the Bell state, all four circuits are identical. After that, $R_y$ gates with different angles are applied. We did not go into this deeply in the exercise, but this $R_y$ gate is used to perform measurement of the Bell state with a different basis state than $\{\ket{0}, \ket{1}\}$. This is explained below. + +ここまで「測定」とはレジスタの量子状態$\sum_{j=0}^{2^n-1} c_j \ket{j}$からビット列$j$を確率$|c_j|^2$で得る行為である、と説明してきました。しかし、本来「測定」はもっと一般的な概念です。それを理解するために、まず、量子力学的には計算基底状態$\ket{j}$は何ら特別な状態ではないということを理解しておきましょう。 +As we have explained, here, "measurement" refers to obtaining bit sequence $j$ with probability $|c_j|^2$ from a register with a quantum state of $\sum_{j=0}^{2^n-1} c_j \ket{j}$. However, normally "measurement" is a more everyday concept. To understand this, it is first important to understand that, from a quantum mechanics standpoint, computational basis state $\ket{j}$ is not a special state. + +例えば1量子ビットにおいて、計算基底状態は$\ket{0}$と$\ket{1}$ですが、以下のような状態$\ket{\theta_{+}}$と$\ket{\theta_{-}}$を考えることもできます。 +For example, when there is 1 quantum bit, the computational basis states are $\ket{0}$ and $\ket{1}$, but consider the following states $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$. + +```{math} +:label: theta_ket_def +\ket{\theta_{+}} := R_y(\theta)\ket{0} = \cos\frac{\theta}{2}\ket{0} + \sin\frac{\theta}{2}\ket{1} \\ +\ket{\theta_{-}} := R_y(\theta)\ket{1} = -\sin\frac{\theta}{2}\ket{0} + \cos\frac{\theta}{2}\ket{1} +``` + +すると、 +Therefore: + +```{math} +:label: theta_ket_inverse_def +\ket{0} = \cos\frac{\theta}{2}\ket{\theta_{+}} - \sin\frac{\theta}{2}\ket{\theta_{-}} \\ +\ket{1} = \sin\frac{\theta}{2}\ket{\theta_{+}} + \cos\frac{\theta}{2}\ket{\theta_{-}}, +``` + +つまり、計算基底状態が$\ket{\theta_{+}}$と$\ket{\theta_{-}}$の重ね合わせとして表現できます。量子ビットの任意の状態は$\ket{0}$と$\ket{1}$の重ね合わせで表現できるので、$\ket{\theta_{+}}$と$\ket{\theta_{-}}$の重ね合わせでも表現できるということになります。そのようなときは$\ket{\theta_{+}}$と$\ket{\theta_{-}}$を基底として状態を表しているわけです。 +In other words, the computational basis state can be indicated as the superposition of $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$. Any quantum bit state can be expressed as a superposition of $\ket{0}$ and $\ket{1}$, so it can also be expressed as a superimposition of $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$. In this situation, $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$ indicate the state of the basis. + +一般に、量子力学的には、2つの異なる状態$\ket{a} \nsim \ket{b}$を考えれば、それらの重ね合わせで量子ビットの任意の状態が表現できます。そして、$\ket{a}$と$\ket{b}$が直交する[^orthogonal]ときに状態$\ket{\psi}$が +Generally speaking, in quantum mechanics, if you consider to different states, $\ket{a} \nsim \ket{b}$, their superposition can be used to express any quantum bit state. When $\ket{a}$ and $\ket{b}$ intersect[^orthogonal], we can express state $\ket{\psi}$ as follows. + +$$ +\ket{\psi} = \alpha \ket{a} + \beta \ket{b} +$$ + +と表現されるならば、「基底$\ket{a}$と$\ket{b}$についての測定」という操作を考えることができます。$\ket{\psi}$に対してそのような測定をすると、状態$\ket{a}$が確率$|\alpha|^2$で、状態$\ket{b}$が確率$|\beta|^2$で得られます。 +In this case, we can consider "measuring basis states $\ket{a}$ and $\ket{b}$." When measuring $\ket{\psi}$ this way, state $\ket{a}$ is obtained with a probability of $|α|^2$ and state $\ket{b}$が is obtained with a probability of $|\beta|^2$. + +量子計算においても、アルゴリズムの一部として、計算の結果実現した状態を特定の基底で測定するということが多々あります。ところが、ここで若干問題があります。量子コンピュータは実装上、計算基底でしか測定ができないのです。量子力学の理論的には特別でない$\ket{0}$と$\ket{1}$ですが、量子コンピュータという実態にとっては具体的な対応物があるのです。 +In quantum calculation, as well, it is common, as part of an algorithm, to use a specific basis state to measure the state when calculation results have been produced. However, this involves a bit of a problem. Due to how quantum computers are implemented, measurement is only possible with the computational basis. In quantum mechanics, theoretically, $\ket{0}$ and $\ket{1}$ are not special, but in actual quantum computers they have specific counterparts. + +そこで、量子計算では、状態を任意の基底で測定することを諦め、反対に状態を変化させてしまいます。例えば、本当は上の$\ket{\theta_{+}}$と$\ket{\theta_{-}}$という基底で量子ビットの状態$\ket{\psi} = \alpha \ket{\theta_{+}} + \beta \ket{\theta_{-}}$を測定したいとします。しかし計算基底でしか測定ができないので、代わりに$R_y(-\theta)$を$\ket{\psi}$にかけます。すると式{eq}`theta_ket_def`から +This is why, in quantum calculation, we give up on the idea of measuring states with an arbitrary basis state, and instead change the state. For example, above, we actually wanted to measure the quantum bit state $\ket{\psi} = \alpha \ket{\theta_{+}} + \beta \ket{\theta_{-}}$ using the basis states $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$. However, since measurement can only be performed using the computational basis, we instead applied $R_y(-\theta)$ to $\ket{\psi}$. Therefore, based on formula{eq}`theta_ket_def` we get: + +$$ +R_y(-\theta)\ket{\theta_{+}} = \ket{0} \\ +R_y(-\theta)\ket{\theta_{-}} = \ket{1} +$$ + +なので、 +Which produces: + +$$ +R_y(-\theta)\ket{\psi} = \alpha \ket{0} + \beta \ket{1} +$$ + +が得られます。この$R_y(-\theta)\ket{\psi}$を計算基底で測定した結果は、$\ket{\psi}$を$\ket{\theta_{+}}, \ket{\theta_{-}}$基底で測定した結果と等価です。 +The results of measuring this $R_y(-\theta)\ket{\psi}$ with the computational basis is equivalent to measuring $\ket{\psi}$ with the $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$ basis states. + +このように、測定を行いたい基底(ここでは$\ket{\theta_{+}}, \ket{\theta_{-}}$)を$\ket{0}, \ket{1}$から得るための変換ゲート($R_y(\theta)$)の逆変換を測定したい状態にかけることで、計算基底での測定で求める結果を得ることができます。 +In this way, you can use measurement with the computational basis to obtain the results you want from the state you wish to measure by applying the inverse transformation of the conversion gate ($R_y(\theta)$ used to obtain the basis state you wish to measure (in this case, $\ket{\theta_{+}}$ and $\ket{\theta_{-}}$) from $\ket{0}$, $\ket{1}$. + + +## 観測量の期待値とその計算法 + +課題の説明に入る前に、さらに話が込み入ってきますが、量子計算でも多出する(ワークブックでは特に{doc}`vqe`以降)概念である「観測量の期待値」について説明します。 +Before explaining this assignment, let's learn about the expectation values of observables, a concept that appears often in quantum computing (in this workbook, the concept will come up particularly often in the {doc}`vqe` section or later). This will involve going even deeper than we have so far. + +観測量とはそのまま「観測できる量」のことで、量子状態から取り出せる(古典的)情報のこととも言えます。例えば、何かしらの粒子の運動を量子力学的に記述した場合、その粒子の位置や運動量などが観測量です。 +Observables are amounts that can be observed. They can also be called (classical) information that can be obtained from a quantum state. For example, if the movement of a particle is described using quantum mechanics, the position and momentum of the particle are observables. + +ケットで表される量子状態に対して、観測量はケットに作用する「エルミート演算子」で表現されます。エルミート以前にそもそも演算子という言葉をこれまで使っていませんが、量子力学において演算子とは、状態ケットを他の状態ケットに変換する作用で、特に線形なもの、すなわち +While quantum states are represented by kets, observables are represented, instead, by Hermitian operators. + +$$ +A (\ket{\psi} + \ket{\phi}) = A \ket{\psi} + A \ket{\phi} +$$ + +が成り立つような$A$のことを指します。例えば実習で紹介したいろいろな量子ゲートはすべて演算子[^unitary]です。 + +さて、それではエルミート演算子はというと、(細かい定義は参考文献に譲りここで必要な最小限のことだけ述べると、)**対角化可能で、実数の固有値を持つ**という性質を持つ演算子のことです。つまり、$A$が$N$次元の量子状態の空間のエルミート演算子であれば、 +Feel free to check the sources in the Bibliography for a precise definition, but here, keeping the explanation to a minimum, we can say that a Hermitian operator **can be diagonalized and has a real number eigenvalue**. In other words, if $A$ is a Hermitian operator of an $N$-dimensional quantum state space, there are $N$ combinations of state $\ket{\phi_j}$ and real number $a_j$ such that the following is true. + +$$ +A \ket{\phi_j} = a_j \ket{\phi_j} +$$ + +が成り立つような状態$\ket{\phi_j}$と実数$a_j$の組が$N$個存在し、各$\ket{\phi_j} \, (j=0,\dots,N-1)$は互いに直交します。このように演算子を状態にかけた結果がもとの状態の定数倍になるというのは実は特別なことで、例えば実習で出てきたゲートのうち計算基底にそのように作用するのは$Z$や$R_z$ゲートだけです。このような関係が成り立つとき、状態ケット$\ket{\phi_j}$を演算子$A$の固有ベクトル、$a_j$を固有値と呼びます。そして、この固有値$a_j$が、演算子$A$で表される観測量の値に対応します[^continuous_observables]。 +Furthermore, each $\ket{\phi_j} \, (j=0,\dots,N-1)$ intersects. In this case, $\ket{\phi_j}$ is called an eigenvector and $a_j$ is called an eigenvalue. Eigenvalue $a_j$ corresponds to the value of the observable expressed with operator A[3]. + +さて、$A$が$n$ビット量子レジスタに対する演算子であるとします。レジスタの状態$\ket{\psi}$が$A$の固有ベクトルで + +$$ +\ket{\psi} = \sum_{j=0}^{2^n-1} \gamma_j \ket{\phi_j} +$$ + +と分解されるとき、この状態を固有ベクトル基底$\{\ket{\phi_j}\}$で測定すると、状態$\ket{\phi_j}$が確率$|\gamma_j|^2$で得られます。そして、状態が$\ket{\phi_j}$であれば観測量$A$の値は$a_j$です。 +When this state is measured with eigenvector basis state $\{\ket{\phi_j}\}, state $\ket{\phi_j}$ is obtained with a probability of $|\gamma_j|^2$. If the state is $\ket{\phi_j}$, then observable $A$'s value is $a_j$. + +そのような測定を多数回繰り返して$A$の値の期待値を求めることを考えます。期待値の定義は「確率変数のすべての値に確率の重みを付けた加重平均」です。$A$の状態$\ket{\psi}$における期待値を$\bra{\psi} A \ket{\psi}$と表すと、$A$が確率分布$\{|\gamma|^2_j\}$に従って値$\{a_j\}$を取るので、 +This measurement can be performed many times to determine the expectation value of $A$. Expectation value is defined as the "average of all the values of random variables as weighted by their likelihood." $A$'s probability distribution is $\{|\gamma|^2_j\}$, so, taking $\{a_j\}$, we get the following: + +$$ +\bra{\psi} A \ket{\psi} = \sum_{j=0}^{2^n-1} a_j |\gamma_j|^2 +$$ + +となります。ここから、量子コンピュータにおいて観測量の期待値を計算する方法を見出すことができます。具体的には、 +From here, we can discover a way to calculate expectation values for observables with the quantum computer. Specifically, this is done using the following steps[^pauli_decomposition]. + +1. 観測量をエルミート演算子で表現する +1. 演算子を対角化し、固有値と対応する固有ベクトルを求める +1. 固有ベクトルを基底として、レジスタの状態を測定する +1. 測定から得られた確率分布を重みとして固有値の平均値を取る + +1. Express the observable using Hermitian operators +2. Diagonalize the operators and determine the eigenvector that corresponds to the eigenvalue. +3. Use the eigenvector as a basis state and measure the state of the register. +4. Overlap the probability distributions obtained from the measurements and determine the average eigenvalue. + +です[^pauli_decomposition]。3の測定の際には、上のセクションで説明した測定基底の変換を利用します。 +When performing the measurement of 3, we will use the measurement basis state conversion explained in the section above. + +## CHSH不等式の解釈 + +実習の中で、 +In the exercise, we calculated the following values. + +$$ +C = P_{00} - P_{01} - P_{10} + P_{11} +$$ + +という量を計算しました。ここで、$P_{lm}$は2つの量子ビットでそれぞれ$l, m \, (=0,1)$が得られる確率でした(コードで言えば`counts.get('lm') / shots`に対応)。実はこの量$C$は、2ビットレジスタにおけるある観測量の期待値として捉えることができます。 +Here, $P_{lm}$0 is the probability of obtaining the values $l, m \, (=0,1)$ for the two quantum bits. Actually, this value, $C$, can be taken as the observable expectation value for the 2-bit register. + +まず、1つの量子ビットに対して、固有値が$\pm 1$であるような観測量$\sigma^{\theta}$を考えます。そのような$\sigma^{\theta}$は無数に存在し、固有ベクトルで区別できます。$\theta$は固有ベクトルを決める何らかのパラメータです[^specifying_eigenvectors]。例えば$\sigma^0$という観測量を、計算基底を固有ベクトルとして、 +First, consider an observable $\sigma^{\theta}$ with an eigenvalue of $\pm 1$ for one quantum bit. There are an infinite number of such $\sigma^{\theta}$, and they can be distinguished by their eigenvectors. $\theta$ is some kind of parameter that defines eigenvectors[^specifying_eigenvectors]. For example, using the computational basis as an eigenvector, observable $\sigma^0$ can be defined as follows. + +$$ +\sigma^0 \ket{0} = \ket{0} \\ +\sigma^0 \ket{1} = -\ket{1} +$$ + +で定義できる、という具合です。 + +次に、2つの量子ビットA, Bからなるレジスタを考え、$\sigma^{\kappa}$をAの、$\sigma^{\lambda}$をBの観測量とします。また、それぞれの演算子の固有ベクトルを +Next, consider a register made up of two quantum bits, $A$ and $B$. Let us take $\sigma^{\kappa}$ as $A$'s observable and $\sigma^{\lambda}$ as $B$'s observable. We will define the eigenvectors of each operator as shown below. + +$$ +\sigma^{\kappa} \ket{\kappa_{\pm}} = \pm \ket{\kappa_{\pm}} \\ +\sigma^{\lambda} \ket{\lambda_{\pm}} = \pm \ket{\lambda_{\pm}} +$$ + +で定義します。これらの固有ベクトルを使って、レジスタの状態$\ket{\psi}$を +We can use these eigenvectors to break down register state $\ket{\psi}$ as follows: + +$$ +\ket{\psi} = c_{++} \ket{\lambda_+}_B \ket{\kappa_+}_A + c_{+-} \ket{\lambda_+}_B \ket{\kappa_-}_A + c_{-+} \ket{\lambda_-}_B \ket{\kappa_+}_A + c_{--} \ket{\lambda_-}_B \ket{\kappa_-}_A +$$ + +と分解します。ケットはAが右、Bが左になるよう並べました。すると、積$\sigma^{\lambda}_B \sigma^{\kappa}_A$の$\ket{\psi}$に関する期待値は、 +We place ket A at right and ket B at left. The expectation value for $\ket{\psi}$ for product $\sigma^{\lambda}_B \sigma^{\kappa}_A$ is as follows. + +$$ +\bra{\psi} \sigma^{\lambda}_B \sigma^{\kappa}_A \ket{\psi} = |c_{++}|^2 - |c_{+-}|^2 - |c_{-+}|^2 + |c_{--}|^2 +$$ + +です。 + +最後に、同じ結果を計算基底での測定で表すために、$\ket{\psi}$に対して基底変換を施します。$\{\ket{\kappa_{\pm}}, \ket{\lambda_{\pm}}\}$が何らかのパラメータ付きゲート$R(\theta)$を通して計算基底と +Finally, in order to express these results as a measurement with the computational basis, we perform a basis state transformation on $\ket{\psi}$. Let us pass $\{\ket{\kappa_{\pm}}, \ket{\lambda_{\pm}}\}$ through a gate $R(\theta)$ with parameters and link them to the computational basis as follows. + +$$ +\ket{\kappa_+} = R(\kappa) \ket{0} \\ +\ket{\kappa_-} = R(\kappa) \ket{1} \\ +\ket{\lambda_+} = R(\lambda) \ket{0} \\ +\ket{\lambda_-} = R(\lambda) \ket{1} +$$ + +で結びついているなら、状態$\ket{\psi'} = R^{-1}_B(\lambda) R^{-1}_A(\kappa) \ket{\psi}$を計算基底で測定したとき、 +When state $\ket{\psi'} = R^{-1}_B(\lambda) R^{-1}_A(\kappa) \ket{\psi}$ is measured with the computational basis, the following is true. + +$$ +P_{00} = |c_{++}|^2 \\ +P_{01} = |c_{+-}|^2 \\ +P_{10} = |c_{-+}|^2 \\ +P_{11} = |c_{--}|^2 +$$ + +が成り立ちます。確認のためはっきりさせておくと、左辺は$\ket{\psi'}$を計算基底で測定し、ビット列00, 01, 10, 11を得る確率です。つまり、最初の$C$は +To clearly confirm this, the left side is the probability of obtaining the bit sequence 00, 01, 10, 11 when measuring $\ket{\psi'}$ with the computational basis. In other words, the initial $C$ expressed the following. + +$$ +C = \bra{\psi'} \sigma^0_B \sigma^0_A \ket{\psi'} = \bra{\psi} \sigma^{\lambda}_B \sigma^{\kappa}_A \ket{\psi} +$$ + +を表していたのでした。 + +これを踏まえて、CHSH不等式の左辺は結局何を計算していたのか、見直してみましょう。ベル状態を +Based on this, let's look again at what the left side of the CHSH inequality was actually calculating. Let us represent the Bell state as follows. + +```{math} +:label: bell_state +\ket{\Psi} := \frac{1}{\sqrt{2}} \left(\ket{0}_B \ket{0}_A + \ket{1}_B \ket{1}_A\right) +``` + +とおきます。$R_y(\pi/4)\ket{0}, R_y(\pi/2)\ket{0}, R_y(3\pi/4)\ket{0}$が固有値$+1$の固有ベクトルとなるような演算子をそれぞれ$\sigma^{\pi/4}, \sigma^{\pi/2}, \sigma^{3\pi/4}$とすると、 +If $\sigma^{\pi/4}$, $\sigma^{\pi/2}$, and $\sigma^{3\pi/4}$ are operators that make $R_y(\pi/4)\ket{0}$, $R_y(\pi/2)\ket{0}$, and $R_y(3\pi/4)\ket{0}$ eigenvector $+1$ eigenvectors, then the following is true. + +$$ +S = C^{\rmI} - C^{\rmII} + C^{\rmIII} + C^{\rmIV} = \bra{\Psi} \sigma^{\pi/4}_B \sigma^0_A \ket{\Psi} - \bra{\Psi} \sigma^{3\pi/4}_B \sigma^0_A \ket{\Psi} + \bra{\Psi} \sigma^{\pi/4}_B \sigma^{\pi/2}_A \ket{\Psi} + \bra{\Psi} \sigma^{3\pi/4}_B \sigma^{\pi/2}_A \ket{\Psi} +$$ + +がわかります。 + +観測量$\sigma^{\theta}$を用いてCHSH不等式をより正確に表現すると、 +If we use observable $\sigma^{\theta}$ to more accurately express the CHSH inequality, we get the following. + +> 4つのパラメータ$\kappa, \lambda, \mu, \nu$を用いて +> If we use the four parameters $\kappa, \lambda, \mu, \nu$ to define the following amount +> $S(\kappa, \lambda, \mu, \nu) = \langle \sigma^{\lambda}\sigma^{\kappa} \rangle - \langle \sigma^{\nu}\sigma^{\kappa} \rangle + \langle \sigma^{\lambda}\sigma^{\mu} \rangle + \langle \sigma^{\nu}\sigma^{\mu} \rangle$ +> という量を定義すると、エンタングルメントのない古典力学において$|S| \leq 2$である。 +> then in classical mechanics with no entanglement, $|S| \leq 2$. + +となります。 + +[^orthogonal]: ここまで状態ケットの内積に関する説明をしていませんが、線形代数に慣れている方は「量子力学の状態ケットはヒルベルト空間の要素である」と理解してください。慣れていない方は、量子ビットの直行する状態とは$\ket{0}$と$\ket{1}$に同じゲートをかけて得られる状態のことを言うと覚えておいてください。 +[^unitary]: 量子ゲートは特にすべてユニタリ演算子と呼ばれるクラスの演算子です。 +[^continuous_observables]: 上で粒子の位置や運動量という例を挙げたので、$a_j \, (j=0,\dots,N-1)$と離散的な固有値が観測量の値だというと混乱するかもしれません。位置や運動量といった連続的な値を持つ観測量は、無数に固有値を持つエルミート演算子で表されます。そのような演算子のかかる状態空間は無限次元です。 +[^pauli_decomposition]: ただし、{doc}`dynamics_simulation`で議論するように、実際には量子ビット数が大きくなると演算子の対角化自体が困難になるので、まず観測量の演算子を「パウリ積」の和に分解し、個々のパウリ積の固有ベクトルを基底とした測定をします。 +[^specifying_eigenvectors]: 量子ビットの一般の状態は実パラメータ2つで決まるので、$\sigma^{\theta, \phi}$などと書いたほうがより明示的ですが、ここでの議論では結局1パラメータしか使わないので、「何か一般の(次元を指定しない)パラメータ」として$\theta$と置いています。 + +[^orthogonal]: We have not yet explained the inner products of state kets, but if you are used to linear algebra, you can think of quantum mechanics state kets as Hilbert space elements. If you do not feel comfortable with linear algebra, remember that intersecting quantum bits are the state produced when the same gate is applied to |0⟩ and |1⟩. +[^continuous_observables]: The example of the position and momentum of a particle was used above, so there may be some confusion about $a_j \, (j=0,\dots,N-1)$ and discrete eigenvalues being observable values. Observables with continuous values, such as position and momentum, are expressed using Hermitian operators, which have infinite eigenvalues. These operators have infinite dimensional state spaces. +[^specifying_eigenvectors]: The general states of quantum bits are defined by two actual parameters, so writing $\sigma^{\theta, \phi}$ or the like would be more explicit, but in this discussion we are only using one of the parameters, so we are using $\theta$ to represent "some parameter (without specifying the dimension)." + + +## 問題:ベル状態について調べる + +### 一般の$\sigma$演算子の期待値 + +上のように$R_y(\theta)\ket{0}$が固有値$+1$の固有ベクトルとなるような演算子を$\sigma^{\theta}$として、$\bra{\Psi} \sigma^{\chi}_B \sigma^{\theta}_A \ket{\Psi}$を計算してみましょう。まず基底変換を具体的に書き下します。 +Using operator $\sigma^{\theta}$ as the operator that makes $R_y(\theta)\ket{0}$ an eigenvalue $+1$ eigenvector, let us calculate $\bra{\Psi} \sigma^{\chi}_B \sigma^{\theta}_A \ket{\Psi}$. First, let us specifically write out the basis state conversion. + +$$ +\begin{align} +\ket{\Psi'} = & R_{y,B}(-\chi) R_{y,A}(-\theta) \frac{1}{\sqrt{2}} \left( \ket{00} + \ket{11} \right) \\ += & \frac{1}{\sqrt{2}} \left\{ \left[ \cos\left(\frac{\chi}{2}\right)\ket{0} - \sin\left(\frac{\chi}{2}\right)\ket{1} \right] \left[ \cos\left(\frac{\theta}{2}\right)\ket{0} - \sin\left(\frac{\theta}{2}\right)\ket{1} \right] \right. \\ +& \left. + \left[ \sin\left(\frac{\chi}{2}\right)\ket{0} + \cos\left(\frac{\chi}{2}\right)\ket{1} \right] \left[ \sin\left(\frac{\theta}{2}\right)\ket{0} + \cos\left(\frac{\theta}{2}\right)\ket{1} \right] \right\} \\ += & \frac{1}{\sqrt{2}} \left[ \cos\left(\frac{\theta - \chi}{2}\right)\ket{00} - \sin\left(\frac{\theta - \chi}{2}\right)\ket{01} + \sin\left(\frac{\theta - \chi}{2}\right)\ket{10} + \cos\left(\frac{\theta - \chi}{2}\right)\ket{11} \right]. +\end{align} +$$ + +したがって、 +Therefore: + +```{math} +:label: quantum_correlation +\begin{align} +\bra{\Psi} \sigma^{\chi}_B \sigma^{\theta}_A \ket{\Psi} & = \bra{\Psi'} \sigma^{0}_B \sigma^{0}_A \ket{\Psi'} \\ +& = \cos^2\left(\frac{\theta - \chi}{2}\right) - \sin^2\left(\frac{\theta - \chi}{2}\right) \\ +& = \cos(\theta - \chi) +\end{align} +``` + +となります。 + +### 実験1 + +上の計算結果を量子回路でも確認してみましょう。実習のように2ビット量子レジスタをベル状態にし、2つの量子ビットにそれぞれ適当な$R_y$ゲートをかけ、期待値$C$を$R_y$ゲートのパラメータの組み合わせについて二次元プロットに起こしてみます。 +Let us confirm the above calculation result using a quantum circuit. As in the exercise, we will make a 2-bit quantum register a Bell state, apply $R_y$ gates to the two quantum bits, and plot expectation value $C$ as a function of the differences in the values of the $R_y$ gate parameters. + +```python tags=["remove-output"] +# Consider 20 points each for theta and phi (400 points total) +ntheta = 20 +nchi = 20 + +thetas = np.linspace(0., np.pi, ntheta) +chis = np.linspace(0., np.pi, nchi) + +# Construct a circuit for each (theta, chi) pair +circuits = [] +# np.ndindex returns an iterator over a multi-dimensional array +# -> idx = (0, 0), (0, 1), ..., (1, 0), (1, 1), ... +for idx in np.ndindex(ntheta, nchi): + theta = thetas[idx[0]] + chi = chis[idx[1]] + + circuit = QuantumCircuit(2, name=f'circuit_{idx[0]}_{idx[1]}') + + # Create a circuit that forms a Bell state and then measures the two qubits + # along theta and chi bases + + ################## + ### EDIT BELOW ### + ################## + + #circuit.? + + ################## + ### EDIT ABOVE ### + ################## + + circuit.measure_all() + + circuits.append(circuit) + +# Execute all circuits in qasm_simulator and retrieve the results +simulator = AerSimulator() +shots = 10000 +circuits = transpile(circuits, backend=simulator) +sim_job = simulator.run(circuits, shots=shots) +result = sim_job.result() +``` + +```python tags=["remove-output"] +# Compute the C values for each (theta, chi) +c_values = np.zeros((ntheta, nchi), dtype=float) +for icirc, idx in enumerate(np.ndindex(ntheta, nchi)): + # This is the counts dict for the (theta, chi) pair + counts = result.get_counts(icirc) + + ################## + ### EDIT BELOW ### + ################## + + #c_values[idx] = ? + + ################## + ### EDIT ABOVE ### + ################## + +# Making a 2D plot using imshow() +# The theta dimension of c_values must be reversed because imshow() puts the origin at the top left corner +dtheta = (thetas[1] - thetas[0]) * 0.5 +dchi = (chis[1] - chis[0]) * 0.5 +plt.imshow(c_values[::-1], extent=(chis[0] - dchi, chis[-1] + dchi, thetas[0] - dtheta, thetas[-1] + dtheta)) +plt.xlabel(r'$\chi$') +plt.ylabel(r'$\theta$') +plt.colorbar(label='C') +# Place markers at theta and chi values that realize |S| = 2 sqrt(2) +plt.scatter([np.pi / 4., np.pi / 4., 3. * np.pi / 4.], [0., np.pi / 2., np.pi / 2.], c='red', marker='+') +plt.scatter([3. * np.pi / 4.], [0.], c='white', marker='+'); +``` + +プロット上に、合わせて$|S| = 2\sqrt{2}$となる時の$\theta, \chi$の値の組み合わせを表示してあります($\langle \sigma^{\chi} \sigma^{\theta} \rangle$を足す点は赤、引く点は白) + + +### ベル状態の何がすごいのか? + +ここまで、かなり天下り的に「エンタングルメント」や「ベル状態」が登場してきて、CHSH不等式が破れているから量子力学だ、と言われても、そもそも量子現象がないとしたら何が期待されるのか、なぜベル状態が不思議なのか、といったある種の「出発点」が欠けていると感じている方も多いかと思います(量子ネイティブを育成するという観点からは、出発点が量子力学でありエンタングルメントは当たり前、ということでいいのかもしれませんが)。量子現象のない「古典力学」とはすなわち、ボールを投げたら放物線を描いて地面に落ちる、といった我々の日常的な感覚に沿った物理法則体系とも言えます。そこで、一旦量子力学を忘れて、日常的な感覚でベル状態を解釈することを試みましょう。 + +**量子ビットAとBはそれぞれどんな状態にある?** + +量子コンピュータ上で実験をしているので、量子ビットAとBは1mmも離れていません(IBMQのような超電導型のコンピュータの実態は、数mm四方のシリコンチップ上にプリントされた金属の回路です)が、これまでの議論にそもそもAとBの間の距離は一切登場しませんでした。実際、AとBをエンタングルさせてから、何らかの方法で二つを100m引き離したとしても、AとBの関係は全く変わりません。そのようにバラバラにして扱える二つの物があるとしたら、日常的な感覚で言えば、それぞれの物が独立に何らかの状態にあると思うのが自然です。ケットの記法で言えば、$\ket{\psi}_B\ket{\phi}_A$というような状態(「可分状態」)です。 + +ところが、式{eq}`bell_state`を見るとわかるように、量子ビットAもBも、$\ket{0}$や$\ket{1}$という状態にはありません。少し計算をしてみると、AやBにどんな基底変換を施しても、ベル状態を可分状態にすることができないことがわかります。ベル状態にある二つの量子ビットは相関していて、別々の独立な状態になく、二つを合わせて一つの状態と理解しなければいけません。「それぞれどんな状態にあるか」という問いは見当外れだったということです。 + +**五分五分の確率で$\ket{00}$か$\ket{11}$という状態をとる?** + +ベル状態を計算基底で測定すると、00か11というビット列が等しい確率で得られます。ならば、二つの量子ビットが相関しているとしても、全体が$\ket{00}$という状態にあったり$\ket{11}$という状態にあったりして、それがランダムに切り替わっていると思えばいいのでしょうか? + +再び式{eq}`bell_state`を眺めると、この解釈も正しくないことがわかります。例えば式{eq}`theta_ket_inverse_def`を用いると + +```{math} +:label: bell_state_basis_change +\begin{align} +\ket{\Psi} = & \frac{1}{\sqrt{2}} \left(\ket{0}_B \ket{0}_A + \ket{1}_B \ket{1}_A\right) \\ += & \frac{1}{\sqrt{2}} \left[ +\left( \cos\frac{\theta}{2} \ket{\theta_+}_B - \sin\frac{\theta}{2} \ket{\theta_-}_B \right) +\left( \cos\frac{\theta}{2} \ket{\theta_+}_A - \sin\frac{\theta}{2} \ket{\theta_-}_A \right) \right. \\ +& \left. + \left( \sin\frac{\theta}{2} \ket{\theta_+}_B + \cos\frac{\theta}{2} \ket{\theta_-}_B \right) +\left( \sin\frac{\theta}{2} \ket{\theta_+}_A + \cos\frac{\theta}{2} \ket{\theta_-}_A \right) +\right] \\ += & \frac{1}{\sqrt{2}} \left( \ket{\theta_+}_B \ket{\theta_+}_A + \ket{\theta_-}_B \ket{\theta_-}_A \right) +\end{align} +``` + +となり、今度は全体が$\ket{\theta_+ \theta_+}$という状態にあったり$\ket{\theta_- \theta_-}$という状態にあったりするかのようにも見えます。 + +**全ての$\ket{\theta_{\pm}\theta_{\pm}}$が一様にランダムに含まれる?** + +式{eq}`bell_state_basis_change`によると、AとBをどんな基底で測定しても、基底が揃ってさえいれば、100%相関した測定結果が得られることになります。ならば、$\ket{00}$と$\ket{11}$だけでなく、あらゆる$\ket{\theta\theta}$が等しい確率で実現しているという解釈はどうでしょうか?($\ket{\theta_-} = \ket{(\theta + \pi)_+}$であり、全ての角度$\theta$を考えるので、$\pm$の添字を省略しています。) + +残念ながらそれも正しくありません。ポイントは、測定をする基底をベル状態の生成の後に決められる(回路で言えば$H$とCNOTの後に$R_{y}$ゲートを自由にかけられる)ということです。ベル状態が、生成されるごとに何か一つランダムに$\ket{\theta\theta}$を作るというのであれば、その状態を後から選んだ別の角度$\chi$を用いて測定してしまったら、完全な相関は得られません。どんな基底で測定しても完全な相関が得られるためには、それぞれの量子ビットの状態があらかじめ決まっていてはいけないのです。 + +結論として、ベル状態では、量子ビットAもBも特定の状態にはなく、Aを測定した時に初めてBの状態が決まる(もしくはその逆)、そしてAとBがどれだけ離れていてもそれが成り立つ、ということになります。こういう言い方をすると、あたかもAとBがテレパシーで繋がっているようで、これが例えばアインシュタインがエンタングルメントの概念を「spooky action at a distance」と称して嫌がった(量子力学の定式化に間違いがあると主張した)理由です。 + +実際にはテレパシーがあるわけではないのですが、量子力学には「部分系の状態」という概念そのものが成り立たないような状態が存在し、古典力学と本質的に異なることは確かなようです。ちなみに、AとBが可分状態にあるとすれば、CHSH不等式の左辺に登場する$\langle \sigma^{\lambda}\sigma^{\kappa} \rangle$などの期待値は全て$\langle \sigma^{\lambda} \rangle \langle \sigma^{\kappa} \rangle$とそれぞれの系での期待値の積で表せるはずなので、 + +$$ +\begin{align} +| S(\kappa, \lambda, \mu, \nu) | & = \big| \langle \sigma^{\lambda} \rangle \langle \sigma^{\kappa} \rangle - \langle \sigma^{\nu} \rangle \langle \sigma^{\kappa} \rangle + \langle \sigma^{\lambda} \rangle \langle \sigma^{\mu} \rangle + \langle \sigma^{\nu} \rangle \langle \sigma^{\mu} \rangle \big| \\ +& \leq | \langle \sigma^{\lambda} \rangle | | \langle \sigma^{\kappa} \rangle + \langle \sigma^{\mu} \rangle | + | \langle \sigma^{\nu} \rangle | | \langle \sigma^{\kappa} \rangle - \langle \sigma^{\mu} \rangle | \\ +& \leq | \langle \sigma^{\kappa} \rangle + \langle \sigma^{\mu} \rangle | + | \langle \sigma^{\kappa} \rangle - \langle \sigma^{\mu} \rangle | \\ +& \leq 2 +\end{align} +$$ + +です。最後の不等式では、$-1 \leq x, y \leq 1$を満たす$x, y$について$|x+y| + |x-y| \leq 2$が常に成り立つことを利用しました。 + +様々な可分状態がランダムに登場するとしても、全ての状態の組み合わせについて上の不等式が成り立つので、全体の平均は常に2以下となります。これが、「古典力学では$|S| \leq 2$」という命題の意味です。 + + +### 実験2 + +上で言及した「確率1/2で$\ket{00}$、確率1/2で$\ket{11}$」という状態(「混合状態」という状態の一種です)も、少し工夫をすると量子回路で再現することができます。まず量子ビットを3つ使って「GHZ状態」 + +$$ +\ket{\Phi} = \frac{1}{\sqrt{2}} \left( \ket{000} + \ket{111} \right) +$$ + +を作ります。右二つのケットに対応する量子ビットを今までと同様右からAとBと呼び、この状態を様々な基底で測定します。一番左のケットに対応する量子ビットCには何もせず、ただ測定をし、しかもその結果を無視します[^implicit_measurement]。 + +それでは、実験1のGHZバージョンを作ってみましょう。 + +[^implicit_measurement]: 実はただ量子ビットを放置するだけでも、測定をして結果を見ないのと同じ効果をもたらすことができます。これをthe principle of implicit measurementと呼びます。 + +```python tags=["remove-output"] +# Construct a circuit for each (theta, chi) pair +circuits_ghz = [] +# np.ndindex returns an iterator over a multi-dimensional array +# -> idx = (0, 0), (0, 1), ..., (1, 0), (1, 1), ... +for idx in np.ndindex(ntheta, nchi): + theta = thetas[idx[0]] + chi = chis[idx[1]] + + circuit = QuantumCircuit(3, name=f'circuit_{idx[0]}_{idx[1]}') + + # Create a circuit that forms a GHZ state and then measures the two qubits + # along theta and chi bases + + ################## + ### EDIT BELOW ### + ################## + + #circuit.? + + ################## + ### EDIT ABOVE ### + ################## + + circuit.measure_all() + + circuits_ghz.append(circuit) + +# Execute all circuits in qasm_simulator and retrieve the results +circuits_ghz = transpile(circuits_ghz, backend=simulator) +sim_job_ghz = simulator.run(circuits_ghz, shots=shots) +result_ghz = sim_job_ghz.result() +``` + +```python tags=["remove-output"] +def counts_ignoring_qubit2(counts, bitstring): + """Add the counts of cases where qubit C is 0 and 1""" + + return counts.get(f'0{bitstring}', 0) + counts.get(f'1{bitstring}', 0) + +# Compute the C values for each (theta, chi) +c_values_ghz = np.zeros((ntheta, nchi), dtype=float) +for icirc, idx in enumerate(np.ndindex(ntheta, nchi)): + # This is the counts dict for the (theta, chi) pair + counts = result_ghz.get_counts(icirc) + + ################## + ### EDIT BELOW ### + ################## + + #c_values_ghz[idx] = ? + + ################## + ### EDIT ABOVE ### + ################## + +# Making a 2D plot using imshow() +# The theta dimension of c_values must be reversed because imshow() puts the origin at the top left corner +plt.imshow(c_values_ghz[::-1], extent=(chis[0] - dchi, chis[-1] + dchi, thetas[0] - dtheta, thetas[-1] + dtheta)) +plt.xlabel(r'$\chi$') +plt.ylabel(r'$\theta$') +plt.colorbar(label='C'); +``` + +ベル状態と明らかに違う挙動をしているのがわかります。原始的な方法ですが、計算した`c_values_ghz`から総当たりで$|S|$の最大値を計算してみましょう。 + +```python tags=["remove-output"] +max_abs_s = 0. + +# Use ndindex to iterate over all index combinations +for ikappa, ilambda, imu, inu in np.ndindex(ntheta, nchi, ntheta, nchi): + abs_s = abs(c_values_ghz[ikappa, ilambda] - c_values_ghz[ikappa, inu] + c_values_ghz[imu, ilambda] + c_values_ghz[imu, inu]) + max_abs_s = max(abs_s, max_abs_s) + +print(f'max |S| = {max_abs_s}') +``` + +量子ビットAとBに「古典的」な状態が実現しているようです。なぜでしょうか。(有限のショット数でシミュレーションをしているため、統計誤差によって$|S|$の値が微妙に2を超えてしまうかもしれません) + +余談ですが、この実験は量子力学における測定という行為の一つのモデルとして考えることもできます。測定装置もその装置の出力を読み取る我々も究極的には量子力学的存在なので、測定とは対象系と測定装置の間にエンタングルメントを生じさせることに他なりません。そのエンタングルメントの結果、対象系(この実験では量子ビットAB)では量子力学的な重ね合わせ状態(ベル状態)が壊れ、混合状態が生じるというわけです。 + + +**提出するもの** + +- 完成した回路のコード(EDIT BELOW / EDIT ABOVEの間を埋める)とシミュレーション結果によるプロット +- 実験2で、「確率1/2で$\ket{00}$、確率1/2で$\ket{11}$」という状態が作られたメカニズムの考察 +- おまけ(評価対象外):実験2で、量子ビットCをどのような基底で測定しても、その結果を無視する限りにおいて$C$の値は変わらないということの証明 +- おまけ(評価対象外):実験2で、量子ビットCをある基底で測定し、その結果が0であった時のみを考慮すると、ABにベル状態を回復することができる。そのような基底の同定と、できれば実験2のように量子回路を組んで実験1と同じプロットが得られることの確認 + diff --git a/source/en/numerics.md b/source/en/numerics.md new file mode 100644 index 00000000..8573fdd7 --- /dev/null +++ b/source/en/numerics.md @@ -0,0 +1,102 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 予備知識:数値表現 + +```{contents} 目次 +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ + ++++ + +(signed_binary)= +## 符号付き二進数 + +(計算機中のバイナリ表現に慣れていない人のために) +(This information is presented for readers that are unfamiliar with the binary notation used in computers) + +$-2^{n-1}$から$2^{n}-1$までの整数$X$を$n$ビットレジスタで表現する方法は何通りもありえますが、標準的なのは最高位ビットの0を+、1を-の符号に対応させ、残りの$n-1$ビットで絶対値を表現する方法です。このとき、$X$が正なら通常の(符号なし)整数と同じ表現となり、負なら場合は$n-1$ビット部分が$2^{n-1} - |X|$になるようにとります。つまり、$100 \dots 0$は$-2^{n-1}$、$111 \dots 1$は$-1$に対応します。これは、別の言い方をすれば「$[-2^{n-1}, -1]$の負の数$X$に、$X + 2^n$という符号なし整数を対応させる」とも見て取れます。 +There are several ways to represent an integer $X$ with a value of $-2^{n-1}$ to $2^{n}-1$ with an $n$-bit register. The most typical way is to make the highest bit 0 to indicate + and 1 to indicate -, and to use the remaining $n-1$ bits to represent the absolute value. When doing this, if $X$ is positive, normally it is represented by a (unsigned) integer, and if it is negative, the $n-1$ bit portion takes the value of $2^{n-1} - |X|$. In other words, $100 \dots 0$ corresponds to $-2^{n-1}$ and $111 \dots 1$ corresponds to $-1$. This could be rephrased as "For $[-2^{n-1}, -1]$, negative number $X$ corresponds to the unsigned integer $X + 2^n$. + +正の数同士の足し算などの結果、符号付き$n$ビットレジスタに$2^{n-1}$以上の値が入ってしまうと、最高位ビットが1となり符号が反転して急に小さい数が現れてしまうことがあります。例えば、形式上$2^{n-1} - 1 = (011 \dots 1)_2$に1を足すと、$(100 \dots 0)_2 = -2^{n-1}$です。このような現象をオーバーフローと呼びます。 +If adding positive numbers would result in a value of $2^{n-1}$ or more being entered into a signed $n$-bit register, the uppermost bit would become 1, which would cause the sign to flip and the number to suddenly become an extremely small number. For example, if 1 were added to $2^{n-1} - 1 = (011 \dots 1)_2$, it would become $(100 \dots 0)_2 = -2^{n-1}$. This is called an overflow. + ++++ + +(nonintegral_fourier)= +## 負・非整数のフーリエ変換 + +{doc}`extreme_simd`では、$n$ビットレジスタでの逆フーリエ変換$1/\sqrt{2^n} \sum_{k} \exp (2 \pi i j k / 2^n) \ket{k} \rightarrow \ket{j}$を扱いました。ここでは$k$も$j$も$0$以上$2^{n-1}-1$以下の整数に限定していました。それでは、$j$が負であったり非整数であったりする場合の逆フーリエ変換はどのようになるでしょうか。 +In the {doc}`extreme_simd`, we worked with the inverse Fourier transform $1/\sqrt{2^n} \sum_{k} \exp (2 \pi i j k / 2^n) \ket{k} \rightarrow \ket{j}$, used on an $n$-bit register. Here, both $k$ and $j$ were limited to integers with values between 0 and $2^{n-1}-1$. What would happen to the inverse Fourier transform if $j$ were a negative number or a non-integer number? + +まず、$j$が負の場合、正の整数$a$で必ず +First, if $j$ were negative, with positive integer $a$ the following would always be true. + +$$ +\exp (2 \pi i j k / 2^n) = \exp [2 \pi i (j + 2^n a) k / 2^n] +$$ + +が成り立つので、$0 \leq j + 2^n a < 2^n$となる$a$を選べば、逆フーリエ変換は +Therefore, if you selected a value for $a$ such that $0 \leq j + 2^n a < 2^n$, the inverse Fourier transform would be as shown below. + +$$ +1/\sqrt{2^n} \sum_{k} \exp (2 \pi i j k / 2^n) \ket{k} \rightarrow \ket{j + 2^n a} +$$ + +です。特に$2^{n-1} \leq j < 0$の場合、右辺は$\ket{j + 2^n}$であり、上の符号付き二進数の標準的な表現法に合致することがわかります。 +In particular, for $2^{n-1} \leq j < 0$, the right side becomes $\ket{j + 2^n}$. As you can see, this matches the standard representation method for signed binary numbers indicated above. + +次に$j$が非整数の場合ですが、このときは逆フーリエ変換の結果が一つの計算基底で表現されず、計算基底の重ね合わせ$\sum_{l} f_{jl} \ket{l}$となります。ここで$f_{jl}$は$j$に最も近い$l$において$|f_{jl}|^2$が最も大きくなるような分布関数です。次のセルで$j$の値を変えて色々プロットして、感覚を掴んでみてください。 +Next, if $j$ is a non-integer, the result of the inverse Fourier transform cannot be expressed with a single computation basis state, instead becoming the computation basis state superposition $\sum_{l} f_{jl} \ket{l}$ . Here, $f_{jl}$ is a distribution function that maximizes the value of $|f_{jl}|^2$ when $l$ is closest to $j$. In the next cell, change the value of $j$ and plot the values to get a better sense of the relationship. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt + +n = 4 +j = 7.8 + +N = 2 ** n + +# Array jk = j*[0, 1, ..., N-1] +jk = np.arange(N) * j +phase_jk = np.exp(2. * np.pi * 1.j * jk / N) + +# Array kl = [[0, 0, ..., 0], [0, 1, ..., N-1], ..., [0, N-1, ..., (N-1)(N-1)]] +kl = np.outer(np.arange(N), np.arange(N)) +phase_minuskl = np.exp(-2. * np.pi * 1.j * kl / N) + +# Inverse Fourier transform +f_jl = (phase_jk @ phase_minuskl) / N + +# Plot Re(f_jl), Im(f_jl), and |f_jl| +plt.plot(np.arange(N), np.real(f_jl), 'o', label='real') +plt.plot(np.arange(N), np.imag(f_jl), 'o', label='imag') +plt.plot(np.arange(N), np.abs(f_jl), 'o', label='norm') +plt.legend() +``` diff --git a/source/en/prerequisites.md b/source/en/prerequisites.md new file mode 100644 index 00000000..2388fd0a --- /dev/null +++ b/source/en/prerequisites.md @@ -0,0 +1,128 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 実習の準備 + +```{contents} 目次 +--- +local: true +--- +``` + ++++ + +## IBM Quantum + +### IBMidを取得し、IBM Quantumにログインする + +IBM Quantumを利用するには、IBMidというアカウントを作り、サービストークンを取得する必要があります。IBM QuantumウェブサイトからIDを取得し、サービスにログインしてください。 +TO use IBM Quantum, you must first create an IBMid account and receive a service token. Acquire an ID from the IBM Quantum website and log into the service. + +(install_token)= +### (ローカル環境)IBM Quantum APIトークンを取得し、Qiskit設定に保存する + +IBM Quantum Lab(IBM Quantumウェブサイト上のJupyter Lab)でプログラムを実行する場合、以下の手続きは不要です。 + +ログインしたらホーム画面のYour API tokenという欄からトークンをコピーできます。 +On the main screen shown after logging in, copy the token displayed in the "Your API token" area. +```{image} figs/ibmq_home.png +:height: 400px +:name: My Account +``` + +アカウントごとに発行されるサービストークンは、ユーザー名+パスワードの代わりとしてPythonプログラム中でIBMQに接続するために使用されます。ローカルディスクに書き込める環境にある場合は、一度トークンを設定ファイルに保存することで、以降の認証を自動化できます。下のコードセルの`__paste_your_token_here__`のところにIBM Quantumからコピーしたトークンを貼り付け、実行してください。 +Service tokens are issued on a per-account basis and are used by the Python program to connect to IBMQ. They play the roles of user IDs and passwords. + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +from qiskit_ibm_provider import IBMProvider + +IBMProvider.save_account('__paste_your_token_here__') +``` + +トークンを保存することで、プログラム中でのIBM Quantumへの認証(IBMProviderの取得)は + +```{code-block} python +from qiskit_ibm_provider import IBMProvider + +provider = IBMProvider() +``` + +のようになります。ちなみにIBM Quantum Labでは最初からトークンが保存されている状態なので、このコードで認証が行なえます。 + +ローカルディスクに書き込める環境でない場合(このワークブックをインタラクティブに使っている場合など)は、Pythonプログラムを実行するたびに(Jupyterのカーネルを再起動するたびに)手動で認証を行う必要があります。 + +```{code-block} python +from qiskit_ibm_provider import IBMProvider + +provider = IBMProvider(token='__paste_your_token_here__') +``` + +## ワークブックの使い方 + +### インタラクティブHTML + +このワークブックの各ページにあるプログラムの書かれたセルは、そのままJupyter Notebookのようにブラウザ上で実行することができます。ページの右上のにカーソルを乗せ、現れるメニューから Live Codeをクリックしてください。ページのタイトルの下にステータス表示が現れるので、readyと表示されるまで待ちます。 +The cells in which programs are written in in this workbook can be executed directly from the browser, like using Jupyter Notebook. Mouse over at the top right of the page and click Live Code on the menu that appears. A status indicator will appear below the page title. Wait until the status is ready. + +```{image} figs/toggle_interactive.jpg +:height: 400px +:name: Turn interactive contents on +``` + +ページがインタラクティブになると、コード・セルにrunおよびrestartというボタンが現れ、直下にセルの出力が表示されるようになります。 +When the page becomes interactive, run and restart buttons will appear in code cells, and the output of running the code will be displayed immediately below them. + +```{image} figs/interactive_cell.jpg +:height: 200px +:name: Interactive code cell +``` + +この状態になったら、入力セルの内容を自由に書き換えて、runボタンをクリックして(もしくはShift + Enterで)Pythonコードを実行することができます。このときいくつか注意すべき点があります。 +In this state, you can directly edit the contents of the cell and click the "run" button (or press Shift + Enter) to run the Python code. There are several important points to note when doing so. + +- restartを押すまでページ全体が一つのプログラムになっているので、定義された変数などはセルをまたいで利用される。 +- しばらく何もしないでページを放置していると、実行サーバーとの接続が切れてしまう。その場合ページを再度読み込んで、改めてインタラクティブコンテンツを起動する必要がある。 +- コードはmybinder.orgという外部サービス上で実行されるので、個人情報等センシティブな内容の送信は極力避ける。
+ (通信は暗号化されていて、mybinder.org中ではそれぞれのユーザーのプログラムは独立のコンテナ中で動くので、情報が外に筒抜けということではないはずですが、念の為。)
+ ただし上で出てきたように、IBM Quantumのサービストークンだけはどうしても送信する必要があります。 + +- The entire page will be treated as one program, so, for examples, the values of variables defined in one cell will be carried over into other cells until "restart" is pressed.The +- If you do not perform any actions on the page for some time, you will lose the connection to the execution server. If this happens, you must reload the page and start up the interactive content again. +•- The code is executed using an external service, mybinder.org, so whenever possible avoid sending any sensitive information such as personal information. +(Transmission is encrypted, and interactive content is executed in a separate container for each user on mybinder.org, so the information is not exposed, but it is best to avoid sending any sensitive information just in case.) +Of course, as explained above, the IBM Quantum service token must be sent in order to use the service. + +### Jupyter Notebook + +インタラクティブHTMLのセキュリティの問題が気になったり、編集したコードを保存したいと考えたりする場合は、ページの元になったノートブックファイルをダウンロードし、自分のローカルの環境で実行することもできます。右上ののメニューの.ipynbをクリックするか、もしくはのメニューのrepositoryからリンクされているgithubレポジトリをクローンしてください。 +If you are concerned about security issues with interactive HTML, or if you would like to save code that you have modified, you can download the notebook file that each page is based on and execute the code in a local environment. From the menu at top right, click .ipynb, or in the menu click on repository to clone the GitHub repository. + +ノートブックをローカルに実行するためには、Pythonバージョン3.8以上が必要です。また、`pip`を使って以下のパッケージをインストールする必要があります。 +Python version 3.8 or above is required to run a notebook locally. You must also use `pip` to install the following packages. + +```{code-block} +pip install qiskit qiskit-ibm-provider qiskit-ibm-runtime matplotlib pylatexenc tabulate +``` diff --git a/source/en/qkc_machine_learning.md b/source/en/qkc_machine_learning.md new file mode 100644 index 00000000..653d02e7 --- /dev/null +++ b/source/en/qkc_machine_learning.md @@ -0,0 +1,493 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + ++++ {"pycharm": {"name": "#%% md\n"}} + +# 【Exercise】Classification of New Physics with Quantum Kernel + ++++ {"pycharm": {"name": "#%% md\n"}} + +We have so far looked at the possibility of using the {doc}`quantum circuit learning` technique to search for new physics, which is a quantum-classical hybrid algorithm. Here in this exercise, we consider the method of utilizing **Quantum Kernels**, an alternative approach for quantum machine learning. In particular, we attempt to apply the **Support Vector Machine**{cite}`quantum_svm` based on quantum kernels to the same problem of new physics search. + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{| #1 \rangle}$ +$\newcommand{\expval}[3]{\langle #1 | #2 | #3 \rangle}$ + ++++ {"pycharm": {"name": "#%% md\n"}} + +(q_kernel)= +## Quantum Kernel + +For quantum circuit learning based on variational quantum algorithm, the following feature map $U_{\text{in}}(x_i)$ was considered. + +$$ +U_{\text{in}}(x_i) = \prod_j R_j^Z(\cos^{-1}(x^2))R_j^Y(\sin^{-1}(x)) +$$ + +Applying the feature map to the initial state $\ket{0}^{\otimes n}$, the quantum state $\ket{\phi(x_i)}=U_{\text{in}}(x_i)\ket{0}^{\otimes n}$ is prepared from the input data. +The quantum kernel is defined as the (square of the absolute value of) inner product of this state $\langle\phi(x_j)|\phi(x_i)\rangle$: + +$$ +K(x_i,x_j):=|\langle\phi(x_j)|\phi(x_i)\rangle|^2=|\langle0^{\otimes n}|U_{\text{in}}^\dagger(x_j)U_{\text{in}}(x_i)|0^{\otimes n}\rangle|^2 +$$ + +The quantum kernel provides a measure of how close the two states of $\ket{\phi(x_i)}$ and $\ket{\phi(x_j)}$, are or how much they are overlapped to each other. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(q_kernel_imp)= +### Estimation of Quantum Kernel + +In order to evaluate quantum kernel, it is necessary to calculate $K(x_i,x_j)=|\langle\phi(x_j)|\phi(x_i)\rangle|^2$ for all the pairs of $\{x_i,x_j\}$ in the training dataset. For the calculation of quantum kernel, one could often hear the term *kernel trick*. In the context of quantum computation, this is generally referred to that the kernel function $K(x_i,x_j)$ is calculated without explicitly using the coordinate values in Hilbert space. One way of doing this is to construct the following circuit: + +```{image} figs/qke_circuit.png +:alt: qke_circuit +:width: 700px +:align: center +``` + +Assume that the input data $x_i$ and $x_j$ are encoded into the circuit and the output state is measured in $Z$ basis. From the definition of quantum kernel, the probability of measuring 0 for all the qubits (i.e, obtaining $0^n$ bitstring after the measurement) provides the $K(x_i,x_j)$ value. By repeating this for all the pairings of input data, the elements of kernel function are determined. Since the kernel is determined from the measurement of $0^n$ bitstring, a statistical uncertainty of ${\cal O}(1/\sqrt{N})$ ($N$ is the number of measurements) is associated with the kernel function. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(svm)= +## Support Vector Machine + +Kernel matrix has been obtained in steps so far. Now the kernel matrix will be incorporated into support vector machine to perform a 2-class classification task. + +### Two-Class Linear Separation Problem + +First, let us look at what the two-class linear separation problem is. Consider the training data $\{(\mathbf{X}_i,y_i)\}\:(i=1,\ldots,N)$ with $N$ samples, where $\mathbf{X}_i \in \mathbb{R}^d$ is an input and $y_i\in\{+1,-1\}$ is the label for the input. A separation problem means that what we aim at is to define a border in the space of input data $\mathbb{R}^d$ and separate the space into the region populated by data with label $+1$ and the region with label $-1$. When this border is a hyperplane, the problem is called linear separation problem. Here the hyperplane corresponds to, for a certain $\mathbf{w}\in\mathbb{R}^d, b \in \mathbb{R}$, + +$$ +\{\mathbf{X}| \mathbf{X} \in \mathbb{R}^d, \: \mathbf{w}\cdot\mathbf{X}+b=0\} +$$ + +A vector $\mathbf{w}$ is orthogonal to this hyperplane. Defining the norm of this vector as $\lVert \mathbf{w} \rVert$, $b/\lVert \mathbf{w} \rVert$ corresponds to the signed distance between the hyperplane and the origin (taken to be positive towards $\mathbf{w}$). + +Since a hyperplane is simple and hence a special set of points, there is a case where the training data cannot be separated by the hyperplane, depending on the data distribution. Whether such separation is possible or not is equivalent to whether $(\mathbf{w},b)$ that satisfies + +```{math} +:label: linear_separation +S_i(\mathbf{w}, b) := y_i(\mathbf{w}\cdot\mathbf{X}_i+b) \geq 1,\:\:\:\forall i=1,\ldots,N +``` +exists or not. This equation can be interpreted as follows: the $\mathbf{w} \cdot \mathbf{X}_i + b$ in parentheses is the signed distance between the data point $X_i$ and hyperplane $(\mathbf{w},b)$, multiplied by $\lVert \mathbf{w} \rVert$. When this quantity is multiplied by $y_i$ and it is larger than 1, this means the data points with $y_i=1(-1)$ are in the positive (negative) region with respect to the hyperplane, and every point in the space is at least $1/\lVert \mathbf{w} \rVert$ distant from the hyperplane. + +The purpose of machine learning is to construct a model based on training data and predict for unseen data with the trained model. For the present separation problem, $(\mathbf{w}, b)$ corresponds to the model, and the label prediction for unseen input $X$ is given by + +```{math} +:label: test_data_label +y = \mathrm{sgn}(\mathbf{w} \cdot \mathbf{X} + b) +``` + +where $\mathrm{sgn}(z)$ is the sign of $z \in \mathbb{R}$. In this setup, we assume that a model which separates the training data the most "strongly" can predict for unseen data the most accurately. "Strongly" separating means that the distance between the hyperplane and all training data points, $1/\lVert \mathbf{w} \rVert$, is large. For linearly separable training data, $(\mathbf{w}, b)$ that satisfies Eq.{eq}`linear_separation` is not unique, and the model with the smallest $\lVert \mathbf{w} \rVert$ is going to be the best one. + +For training data that cannot be separated linearly, we can also think of a problem where a model tries to separate "as much data as possible" in a similar fashion. In this case, the training corresponds to looking for $\mathbf{w}$ and $b$ that make $\lVert \mathbf{w} \rVert$ as small as possible and $\sum_{i} S_i(\mathbf{w}, b)$ as large as possible, and this can be achieved by minimizing the following objective function: + +```{math} +:label: primal_1 +f(\mathbf{w}, b) = \frac{1}{2} \lVert \mathbf{w} \rVert^2 + C \sum_{i=1}^{N} \mathrm{max}\left(0, 1 - S_i(\mathbf{w}, b)\right) +``` + +Here the coefficient $C>0$ is a hyperparameter that controls which of the two purposes is preferred and to what extent it is. The second term ignores the data points that have the $S_i$ value greater than 1 in $\mathrm{max}$ function (sufficiently distant from the hyperplane). The data points that are not ignored, i.e, the data near the separating hyperplane or wrongly separated data with $\{\mathbf{X}_i | S_i < 1\}$, are called "support vector". Which data point will be a support vector depends on the values of $\mathbf{w}$ and $b$, but once the parameters that minimize the function $f$ are determined, only the corresponding support vector is used to predict for unseen input data (more details of how it is used are discussed later). This machine learning model is called support vector machine. + ++++ {"pycharm": {"name": "#%% md\n"}} + +### Dual Formulation + +Next, we consider a "dual formulation" of this optimization problem. A dual form can be obtained by defining a Lagrangian with constraints in an optimization problem and representing the values at stationary points as a function of undetermined multipliers. The introduction of constraints is carried out using the method of Karush-Kuhn-Tucker (KKT) conditions, which is a generalization of the method of Lagrange multipliers. The Lagrange multiplier allows only equality constraints while the method of KKT conditions is generalized to allow inequality constraints. + +Let us first re-write Eq.{eq}`primal_1` by introducing parameters $\xi_i$ instead of using the $\mathrm{max}$ function: + +$$ +\begin{align} +F(\mathbf{w}, b, \{\xi_i\}) & = \frac{1}{2} \lVert \mathbf{w} \rVert^2 + C \sum_{i=1}^{N} \xi_i \\ +\text{with} & \: \xi_i \geq 1 - S_i, \: \xi_i \geq 0 \quad \forall i +\end{align} +$$ + +When the $\mathbf{w}$, $b$ and $\{\xi_i\}$ that minimize $F$ by using the constraints on the second line, please confirm if the function $f$ is also minimized. + +The Lagrangian of this optimization problem is given as follows by introducing non-negative mutlipliers $\{\alpha_i\}$ and $\{\beta_i\}$: + +```{math} +:label: lagrangian +L(\mathbf{w}, b, \{\xi_i\}; \{\alpha_i\}, \{\beta_i\}) = \frac{1}{2} \lVert \mathbf{w} \rVert^2 + C \sum_{i=1}^{N} \xi_i - \sum_{i=1}^{N} \alpha_i \left(\xi_i + S_i(\mathbf{w}, b) - 1\right) - \sum_{i=1}^{N} \beta_i \xi_i +``` + +At stationary points, the following equations hold. + +```{math} +:label: stationarity +\begin{align} +\frac{\partial L}{\partial \mathbf{w}} & = \mathbf{w} - \sum_i \alpha_i y_i \mathbf{X}_i = 0 \\ +\frac{\partial L}{\partial b} & = -\sum_i \alpha_i y_i = 0 \\ +\frac{\partial L}{\partial \xi_i} & = C - \alpha_i - \beta_i = 0 +\end{align} +``` + +Therefore, by substituting these relations into Eq.{eq}`lagrangian`, the dual objection function + +```{math} +:label: dual +\begin{align} +G(\{\alpha_i\}) & = \sum_{i} \alpha_i - \frac{1}{2} \sum_{ij} \alpha_i \alpha_j y_i y_j \mathbf{X}_i \cdot \mathbf{X}_j \\ +\text{with} & \sum_i \alpha_i y_i = 0, \: 0 \leq \alpha_i \leq C \quad \forall i +\end{align} +``` + +can be obtained. Therefore, the dual formulation of the problem is to find $\{\alpha_i\}$ that maximizes the $G$. In addition, the optimized solutions $\mathbf{w}^*$, $b^*$ and $\{\xi^*_i\}$ in the main formulation and the solutions \{\alpha^*_i\}$ in the dual formulation have the following relations (complementarity conditions): + +```{math} +:label: complementarity +\begin{align} +\alpha^*_i (\xi^*_i + S_i(\mathbf{w}^*, b^*) - 1) & = 0 \\ +\beta^*_i \xi^*_i = (C - \alpha^*_i) \xi^*_i & = 0 +\end{align} +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +### Relation with Kernel Matrix + +Even at this point, probably the relation between kernel matrix and the linear separation by support vector machine is not clear at first glance, but there is a hint in the dual formulation. The $\mathbf{X}_i \cdot \mathbf{X}_j$ in Eq.{eq}`dual` is the inner product of input vectors in the input space of $\mathbb{R}^d$. However, since the parameter $\mathbf{w}$ does not appear in the dual formulation, the problem is still valid even if $\mathbf{X}_i$ is taken to be an element in another linear space $V$. In fact, this part of Eq.{eq}`dual` is not even an inner product of vectors. Considering an element $x_i$ in some (not necessarily linear) space $D$, we can think of a function $K$ that represents a "distance" between two elements of $x_i$ and $x_j$: + +$$ +K: \: D \times D \to \mathbb{R} +$$ + +Then, most generally, the support vector machine can be defined as a problem to maximize the following objective function in terms of training data $\{(x_i, y_i) \in D \times \mathbb{R}\} \: (i=1,\ldots,N)$: + +```{math} +:label: dual_kernel +\begin{align} +G(\{\alpha_i\}) & = \sum_{i} \alpha_i - \frac{1}{2} \sum_{ij} \alpha_i \alpha_j y_i y_j K(x_i, x_j) \\ +\text{with} & \sum_i \alpha_i y_i = 0, \: \alpha_i \geq 0 \quad \forall i +\end{align} +``` + +The kernel function defined above corresponds exactly to this distance function $K(x_i, x_j)$. Now it becomes clear how the kernel function is incorporated into the support vector machine. + +Looking further into the complementarity conditions of Eq.{eq}`complementarity`, it turns out that the optimized parameters $\alpha^*_i$, $\xi^*_i$ and $S^*_i$ ($S^*_i := S_i(\mathbf{w}^*, b^*)$) can have only values that satisfy either one of the following three conditions: + +- $\alpha^*_i = C, \xi^*_i = 1 - S^*_i \geq 0$ +- $\alpha^*_i = 0, \xi^*_i = 0$ +- $0 < \alpha^*_i < C, \xi^*_i = 0, S^*_i = 1$ + +In particular, when $S^*_i > 1$, $\alpha^*_i = 0$. This indicates that the summation in Eq.{eq}`dual_kernel` can be taken over all $i$'s with $S^*_i \leq 1$, that is, the points in the support vector. + +Finally, let us look at how the label of unseen data $x$ is predicted when the support vector machine represented in the kernel form is trained (i.e, when the $\{\alpha_i\}$ that maximizes $G$ are found). In the original main formulation of the problem, the label is given by Eq.{eq}`test_data_label`. When substituting the first equation of Eq.{eq}`stationarity` into it, + +$$ +y = \mathrm{sgn}\left(\sum_{i\in \mathrm{s.v.}} \alpha^*_i y_i K(x_i, x) + b^*\right) +$$ + +is obtained. Here $\alpha^*_i$ is the optimized parameter that maximizes $G$, and the summation is taken over $i$'s in the support vector. The optimized parameter $b^*$ can be obtained by solving + +$$ +y_j \left(\sum_{i\in \mathrm{s.v.}} \alpha^*_i y_i K(x_i, x_j) + b^*\right)= 1 +$$ + +for data points $j$ that satisfy $S^*_j = 1$. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(qsvm_imp)= +## Application to New Physics Search + +Let us now move onto the problem we considered {doc}`here ` and see how we can use the quantum support vector machine. + +The preparation of the dataset is the same as before. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +from sklearn.preprocessing import MinMaxScaler +from sklearn.svm import SVC +from sklearn.metrics import accuracy_score + +from qiskit import QuantumCircuit, transpile +from qiskit.circuit import Parameter, ParameterVector +from qiskit.circuit.library import TwoLocal, ZFeatureMap, ZZFeatureMap +from qiskit.primitives import Sampler +from qiskit.quantum_info import SparsePauliOp +from qiskit_aer import AerSimulator +from qiskit_machine_learning.kernels import FidelityQuantumKernel +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Read out variables from input file +df = pd.read_csv("data/SUSY_1K.csv", + names=('isSignal', 'lep1_pt', 'lep1_eta', 'lep1_phi', 'lep2_pt', 'lep2_eta', + 'lep2_phi', 'miss_ene', 'miss_phi', 'MET_rel', 'axial_MET', 'M_R', 'M_TR_2', + 'R', 'MT2', 'S_R', 'M_Delta_R', 'dPhi_r_b', 'cos_theta_r1')) + +# NUmber of input features used in the training +feature_dim = 3 # dimension of each data point + +# Sets of 3, 5 and 7 input features +if feature_dim == 3: + selected_features = ['lep1_pt', 'lep2_pt', 'miss_ene'] +elif feature_dim == 5: + selected_features = ['lep1_pt', 'lep2_pt', 'miss_ene', 'M_TR_2', 'M_Delta_R'] +elif feature_dim == 7: + selected_features = ['lep1_pt', 'lep1_eta', 'lep2_pt', 'lep2_eta', 'miss_ene', 'M_TR_2', 'M_Delta_R'] + +# Number of events in the training and testing samples +train_size = 20 +test_size = 20 + +df_sig = df.loc[df.isSignal==1, selected_features] +df_bkg = df.loc[df.isSignal==0, selected_features] + +# Creation of the samples +df_sig_train = df_sig.values[:train_size] +df_bkg_train = df_bkg.values[:train_size] +df_sig_test = df_sig.values[train_size:train_size + test_size] +df_bkg_test = df_bkg.values[train_size:train_size + test_size] +# The first (last) train_size events are signal (background) events that (do not) contain SUSY particles +train_data = np.concatenate([df_sig_train, df_bkg_train]) +# The first (last) test_size events are signal (background) events that (do not) contain SUSY particles +test_data = np.concatenate([df_sig_test, df_bkg_test]) + +# Label +train_label = np.zeros(train_size * 2, dtype=int) +train_label[:train_size] = 1 +test_label = np.zeros(train_size * 2, dtype=int) +test_label[:test_size] = 1 + +mms = MinMaxScaler((-1, 1)) +norm_train_data = mms.fit_transform(train_data) +norm_test_data = mms.transform(test_data) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(problem1)= +### Exercise 1 + +Select feature map and implement it as a quantum circuit object named `feature_map`. You could use the existing classes such as `ZFeatureMap` and `ZZFeatureMap` as done in {doc}`vqc_machine_learning`, or make an empty `QuantumCircuit` object and write a circuit by hand using `Parameter` and `ParameterVector`. + +You could choose any number of qubits, but the `FidelityQuantumKernel` class used later seems to work better when the number of qubits is equal to the number of input features. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +################## +### EDIT BELOW ### +################## + +# In case of writing a circuit from scratch +input_features = ParameterVector('x', feature_dim) +num_qubits = feature_dim +feature_map = QuantumCircuit(num_qubits) +# ... + +################## +### EDIT ABOVE ### +################## +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(problem2)= +### Exercise 2 + +Create a circuit named with `manual_kernel` to calculate a kernel matrix from the feature map determined above. There is an API (`FidelityQuantumKernel` class) to do this automatically in Qiskit, but here please try to start with an empty `QuantumCircuit` object and write a parameterized circuit with the feature map. + +**Hint 1** + +A QuantumCircuit object ican be added into another QuantumCircuit by doing + +```python +circuit.compose(another_circuit, inplace=True) +``` +If `inplace=True` is omitted, the `compose` method just returns a new circuit object, instead of addint the `circuit` into `another_circuit`. + +**Hint 2** + +The QuantumCircuit class contains a method to return an inverse circuit called `inverse()`. + +**Hint 3** + +Please be careful about the parameter set of `manual_kernel`. If we create a `manual_kernel` from the `feature_map` or a simple copy of the `feature_map`, the `manual_kernel` will contain only parameters used in the `feature_map`. + +A parameter set in a circuit can be replaced with another parameter set by doing, for example, + +```python +current_parameters = circuit.parameters +new_parameters = ParameterVector('new_params', len(current_parameters)) +bind_params = dict(zip(current_parameters, new_parameters)) +new_circuit = circuit.assign_parameters(bind_params, inplace=False) +``` + +In this case, the `new_circuit` is parameterized with `new_parameters`. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +manual_kernel = QuantumCircuit(feature_map.num_qubits) + +################## +### EDIT BELOW ### +################## + +################## +### EDIT ABOVE ### +################## + +manual_kernel.measure_all() +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +Execute the created circuit with simulator to calculate the probability of measuring 0 for all qubits, $|\langle0^{\otimes n}|U_{\text{in}}^\dagger(x_1)U_{\text{in}}(x_0)|0^{\otimes n}\rangle|^2$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +sampler = Sampler() + +first_two_inputs = np.concatenate(norm_train_data[:2]).flatten() + +job = sampler.run(manual_kernel, parameter_values=first_two_inputs, shots=10000) +# quasi_dists[0]がmanual_kernelの測定結果のcountsから推定される確率分布 +fidelity = job.result().quasi_dists[0].get(0, 0.) +print(f'|<φ(x_0)|φ(x_1)>|^2 = {fidelity}') +``` + +Let us do the same thing using the `FidelityQuantumKernel` class. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# FidelityQuantumKernel creates internally an instance of Sample class automatically. +q_kernel = FidelityQuantumKernel(feature_map=feature_map) + +bind_params = dict(zip(feature_map.parameters, norm_train_data[0])) +feature_map_0 = feature_map.bind_parameters(bind_params) +bind_params = dict(zip(feature_map.parameters, norm_train_data[1])) +feature_map_1 = feature_map.bind_parameters(bind_params) + +qc_circuit = q_kernel.fidelity.create_fidelity_circuit(feature_map_0, feature_map_1) +qc_circuit.decompose().decompose().draw('mpl') +``` + ++++ {"pycharm": {"name": "#%% md\n"}, "tags": ["raises-exception", "remove-output"]} + +We can easily visualize the contents of kernel matrix with the `FidelityQuantumKernel` class. Let us make plots of the kernel matrix obtained from the training data alone, and that from the training and testing data. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +matrix_train = q_kernel.evaluate(x_vec=norm_train_data) +matrix_test = q_kernel.evaluate(x_vec=norm_test_data, y_vec=norm_train_data) + +fig, axs = plt.subplots(1, 2, figsize=(10, 5)) +axs[0].imshow(np.asmatrix(matrix_train), interpolation='nearest', origin='upper', cmap='Blues') +axs[0].set_title("training kernel matrix") +axs[1].imshow(np.asmatrix(matrix_test), interpolation='nearest', origin='upper', cmap='Reds') +axs[1].set_title("validation kernel matrix") +plt.show() +``` + ++++ {"pycharm": {"name": "#%% md\n"}, "tags": ["raises-exception", "remove-output"]} + +At the end, we attempt to perform classification with support vector machine implemented in sklearn package. Please check how the classification accuracy varies when changing the dataset size or feature maps. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +qc_svc = SVC(kernel='precomputed') # Default valuye of hyperparameter C is 1 +qc_svc.fit(matrix_train, train_label) + +train_score = qc_svc.score(matrix_train, train_label) +test_score = qc_svc.score(matrix_test, test_label) + +print(f'Precomputed kernel: Classification Train score: {train_score*100}%') +print(f'Precomputed kernel: Classification Test score: {test_score*100}%') +``` + ++++ {"pycharm": {"name": "#%% md\n"}, "tags": ["raises-exception", "remove-output"]} + +**Items to submit** +- Explanation of the selected feature map and the code (Exercise 1). +- Quantum circuit to calculate kernel matrix and the result of $K(x_0, x_1)$ obtained using the circuit (Exercise 2). +- Comparison with results from quantum machine learning using variational quantum circuit in {doc}`this workboo `. + - Can we observe any systematic difference in classification accuracy when comparing the two methods in the same conditions (input features, dataset size, feature map)? Vary the conditions and discuss the observed behavior. + - If one is systematically worse than the other, how can we improve the worse one? When the datasize is small, it is likely that the over-fitting occurs, i.e, the performance for the testing data is worse than that for the training data. Discuss if/how we can improve the classification performance for the testing data while reducing the effect of over-fitting. diff --git a/source/en/references.md b/source/en/references.md new file mode 100644 index 00000000..17af6b7a --- /dev/null +++ b/source/en/references.md @@ -0,0 +1,34 @@ +# 参考文献 + +## Textbooks + +- Nielsen and Chuang {cite}`nielsen_chuang` +- Practical cloud quantum computation in Japanese language {cite}`nakayama` +- Introductory textbook of quantum computation {cite}`sutor` + +## Online Educational Resources + +- [Qiskit textbook](https://qiskit.org/textbook/preface.html) +- [Quantum Native Dojo](https://dojo.qulacs.org/ja/latest/) +- [PennyLane](https://pennylane.ai/qml/) + +## Related papers, etc. + +### Quantum Mechanics + +- CHSH inequality {cite}`chsh` + +### Quantum Computation + +- 実用的入門 {cite}`j2020quantum` + +### Quantum Dynamics Simulation + +- Feynman's lectire {cite}`feynman82` +- Overview of quantum simulation {cite}`simulator_review` +- Quantum simulation with ion-trapped system {cite}`simulator_ion2011` +- Quantum simualtion of fermionic system using superconducting qubit system {cite}`google_fermion` + +### Quantum Chemistry + +### Quantum Machine Learning diff --git a/source/en/shor.md b/source/en/shor.md new file mode 100644 index 00000000..c5b4bf24 --- /dev/null +++ b/source/en/shor.md @@ -0,0 +1,997 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + ++++ {"pycharm": {"name": "#%% md\n"}} + +# Learning Integer Factoring Algorithm + ++++ + +In this exercise, you will be learning about **Shor's algorithm**. You may have heard this name before, as Shor's algorithm{cite}`shor,nielsen_chuang_qft_app` is one of the most famous quantum algorithms. After learning the method called **quantum phase estimation**, on which Shor's algorithm is based, we will then introduce each step of Shor's algorithm, together with actual examples. Lastly, we will use Qiskit to implement Shor's algorithm and exercise integer factoring. + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ +$\newcommand{\modequiv}[3]{#1 \equiv #2 \pmod{#3}}$ +$\newcommand{\modnequiv}[3]{#1 \not\equiv #2 \pmod{#3}}$ + +(shor_introduction)= +## Introduction + +One of the most widely known examples of how the capabilities of quantum computation far surpass classical computation is Shor's algorithm. The problem this algorithm seeks to address is to break down a large positive number into two prime numbers. The problem itself is a simple one. However, there are no known classical computation algorithms that can efficiently performe integer factoring, and as the number in question grows larger, the amount of calculation involved is believed to **grow exponentially**. Using Shor's algorithm is believed to make it possible to solve this problem in **polynomial time** (generally speaking, if an algorithm can solve a problem with a computation time that increases polynomially with respect to the problem size, the algorithm is considered efficient). + +The difficulty involved in performing integer factoring with classical calculation is the basis of the encryption technologies that are currently widely in use. Therefore, if it were possible to realize an exponentially-fast Shor's algorithm using a quantum computer, it could result in the leakage of confidential information. This is why so much attention is being paid to Shor's algorithm. + ++++ + +(qpe)= +## Quantum Phase Estimation + +First, let's learn about **quantum phase estimation** or QPE, on which Shor's algorithm is based. If you understand Shor's algorithm, you will realize that the heart of the algorithm is basically QPE itself. To understand QPE, it is essential that you understand the **quantum Fourier transform** or QFT. For more information about QFT, please refer to task 7 of [this exercise](circuit_from_scratch.ipynb) or to reference material[1]. + +Since the QPE is a very important technique, it is widely used, not only in Shor's algorithm but also in various algorithms as a subroutine. + +The question QPE seeks to address is +"Given a unitary operation $U$ and an eigenvector $\ket{\psi}$ that satisfies $U\ket{\psi}=e^{2\pi i\theta}\ket{\psi}$, what is the phase $\theta$ of the eigenvalue $e^{2\pi i\theta}$?" + ++++ + +(qpe_1qubit)= +### Single-Qubit QPE +First, let us consider a quantum circuit like the one in the figure below. Here, the upper qubit is $\ket{0}$ and the lower qubit is $U$'s eigenvector $\ket{\psi}$. + +```{image} figs/qpe_1qubit.png +:alt: qpe_1qubit +:width: 300px +:align: center +``` + +In this case, the quantum states in each of steps 1 to 3 of the quantum circuit can be expressed as follows. + +- Step 1 : $\frac{1}{\sqrt{2}}(\ket{0}\ket{\psi}+\ket{1}\ket{\psi})$ +- Step 2 : $\frac{1}{\sqrt{2}}(\ket{0}\ket{\psi}+\ket{1} e^{2\pi i\theta}\ket{\psi})$ +- Step 3 : $\frac{1}{2}\left[(1+e^{2\pi i\theta})\ket{0}+(1-e^{2\pi i\theta})\ket{1}\right]\ket{\psi}$ + +If we measure the upper qubit in this state, there is a $|(1+e^{2\pi i\theta})/2|^2$ probability that the value is 0 and a $|(1-e^{2\pi i\theta})/2|^2$ probability that it is 1. In other words, we can determine the value of $\theta$ from these probabilities. However, when the value of $\theta$ is small ($\theta\ll1$), there is an almost 100% probability that the measured value will be 0 and an almost 0% probability that it will be 1. Therefore, in order to measure small discrepancies from 100% or 0%, we will have to perform measurements many times. This does not make for a particularly superior approach. + +```{hint} +This controlled-$U$ gate corresponds to the "oracle" of Shor's algorithm. The inverse quantum Fourier transform comes (QFT) after that, and it is an $H$ gate for one-qubit case ($H=H^\dagger$). In other words, a single-qubit QFT is an $H$ gate itself. +``` + +Returning to the topic at hand, let us see if there any way to determine the phase more accurately, using only a small number of measurements. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(qpe_nqubit)= +### $n$-Qubit QPE +Let us think of a quantum circuit with the upper register expanded to $n$ qubits (as shown in the figure below). + +```{image} figs/qpe_wo_iqft.png +:alt: qpe_wo_iqft +:width: 500px +:align: center +``` + +As a result of this, $U$ is repeatedly applied to the lower registers, but the key is that the $U$ is applied $2^x$ times where $x$ runs from 0 through $n-1$. To understand what this means, let's check that $U^{2^x}\ket{\psi}$ can be written as shown below (this may be obvious). + +$$ +\begin{aligned} +U^{2^x}\ket{\psi}&=U^{2^x-1}U\ket{\psi}\\ +&=U^{2^x-1}e^{2\pi i\theta}\ket{\psi}\\ +&=U^{2^x-2}e^{2\pi i\theta2}\ket{\psi}\\ +&=\cdots\\ +&=e^{2\pi i\theta2^x}\ket{\psi} +\end{aligned} +$$ + +If we trace the quantum states of this quantum circuit in the same fashion using steps 1, 2, ... $n+1$, we find the following. + +- Step 1 : $\frac{1}{\sqrt{2^n}}(\ket{0}+\ket{1})^{\otimes n}\ket{\psi}$ +- Step 2 : $\frac{1}{\sqrt{2^n}}(\ket{0}+e^{2\pi i\theta2^{n-1}}\ket{1})(\ket{0}+\ket{1})^{\otimes n-1}\ket{\psi}$ +- $\cdots$ +- Step $n+1$ : $\frac{1}{\sqrt{2^n}}(\ket{0}+e^{2\pi i\theta2^{n-1}}\ket{1})(\ket{0}+e^{2\pi i\theta2^{n-2}}\ket{1})\cdots(\ket{0}+e^{2\pi i\theta2^0}\ket{1})\ket{\psi}$ + +If we look closely at the state of the $n$-qubit register after step $n+1$, we will see that it is equivalent to the state with QFT where $j$ was replaced with $2^n\theta$. Thus, if we apply an inverse Fourier transform $\rm{QFT}^\dagger$ to this $n$-qubit state, we will be able to obtain the state $\ket{2^n\theta}$! Measuring this state, we can determine $2^n$\theta$ -- that is, phase $\theta$ (multiplied by $2^n$) of the eigenvalue. This is how the QPE works (see figure below). + +(qpe_nqubit_fig)= +```{image} figs/qpe.png +:alt: qpe +:width: 700px +:align: center +``` + +However, generally speaking, there is not guarantee that $2^n \theta$ will be an integer. See the {ref}`supplementary information page ` for information about performing inverse Fourier transformation on non-integer values. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(qpe_imp)= +## Implementation of Example QPE Problem + +Next, let's try to implement QPE using a simple quantum circuit. + +First, it is necessary to prepare an eigenstate $\ket{\psi}$ and a unitary operator $U$ that satisfy $U\ket{\psi}=e^{2\pi i\theta}\ket{\psi}$. Here we consider $S$ gate (phase $\sqrt{Z}$ gate) as $U$. Since $S\ket{1}=e^{i\pi/2}\ket{1}$ with $\ket{1}=\begin{pmatrix}0\\1\end{pmatrix}$, $\ket{1}$ is an eigenvector of $S$ gate and $e^{i\pi/2}$ is its eigenvalue. This means that $\theta=1/4$ for $U=S$ because QPE allows us to estimate phase $\theta$ of the eigenvalue $e^{2\pi i\theta}$. We will confirm this using a quantum circuit. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +# Tested with python 3.8.12, qiskit 0.34.2, numpy 1.22.2 +from fractions import Fraction +import matplotlib.pyplot as plt +import numpy as np + +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile +from qiskit.tools.monitor import job_monitor +from qiskit.visualization import plot_histogram +from qiskit_aer import AerSimulator +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_ibm_provider.accounts import AccountNotFoundError + +# Modules for this workbook +from qc_workbook.utils import operational_backend +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +We create a quantum circuit consisting of a single-qubit register for an eigenvetor $\ket{1}$ and a 3-qubit register for phase estimation. $\ket{1}$ is prepared by applying a Pauli-$X$ to $\ket{0}$, and then the controlled-$S$ gate is applied $2^x$ times for QPE. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +n_meas = 3 + +# Register used to obtain phase +qreg_meas = QuantumRegister(n_meas, name='meas') +# Register used to hold eigenvector +qreg_aux = QuantumRegister(1, name='aux') +# Classical register written by the output of phase estimation +creg_meas = ClassicalRegister(n_meas, name='out') + +# Create quantum circuit from above registers +qc = QuantumCircuit(qreg_meas, qreg_aux, creg_meas) + +# Initialize individudal registers +qc.h(qreg_meas) +qc.x(qreg_aux) + +# This is the phase value that we want to get with QPE +angle = np.pi / 2 + +# Replace (Controlled-S)^x with CP(xπ/2) because S = P(π/2) +for x, ctrl in enumerate(qreg_meas): + qc.cp(angle * (2 ** x), ctrl, qreg_aux[0]) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +Then, we measure qubits after applying an inverse QFT to the register for phase estimation. + +Write an **inverse circuit of QFT** based on {ref}`this workbook `. The `qreg` argument is an object of the measurement register. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +def qft_dagger(qreg): + """Circuit for inverse quantum fourier transform""" + qc = QuantumCircuit(qreg) + + ################## + ### EDIT BELOW ### + ################## + + #qc.? + + ################## + ### EDIT ABOVE ### + ################## + + qc.name = "QFT^dagger" + + return qc + +qc.barrier() +qc.append(qft_dagger(qreg_meas), qargs=qreg_meas) +qc.barrier() +qc.measure(qreg_meas, creg_meas) +qc.draw('mpl') +``` + +**Solution** + +````{toggle} + +Use Inverse QFT in `setup_addition` funton of {ref}`fourier_addition` + +```{code-block} python +def qft_dagger(qreg): + """Circuit for inverse quantum fourier transform""" + qc = QuantumCircuit(qreg) + + ################## + ### EDIT BELOW ### + ################## + + for j in range(qreg.size // 2): + qc.swap(qreg[j], qreg[-1 - j]) + + for itarg in range(qreg.size): + for ictrl in range(itarg): + power = ictrl - itarg - 1 + qc.cp(-2. * np.pi * (2 ** power), ictrl, itarg) + + qc.h(itarg) + + ################## + ### EDIT ABOVE ### + ################## + + qc.name = "QFT^dagger" + return qc +``` + +```` + +```{code-cell} ipython3 +:tags: [remove-input, remove-output] + +## Cell for text + +def qft_dagger(qreg): + qc = QuantumCircuit(qreg) + + for j in range(qreg.size // 2): + qc.swap(qreg[j], qreg[-1 - j]) + + for itarg in range(qreg.size): + for ictrl in range(itarg): + power = ictrl - itarg - 1 + qc.cp(-2. * np.pi * (2 ** power), ictrl, itarg) + + qc.h(itarg) + + qc.name = "IQFT" + return qc + +qreg_meas = QuantumRegister(n_meas, name='meas') +qreg_aux = QuantumRegister(1, name='aux') +creg_meas = ClassicalRegister(n_meas, name='out') + +qc = QuantumCircuit(qreg_meas, qreg_aux, creg_meas) + +qc.h(qreg_meas) +qc.x(qreg_aux) + +angle = np.pi / 2 + +for x, ctrl in enumerate(qreg_meas): + qc.cp(angle * (2 ** x), ctrl, qreg_aux[0]) + +qc.append(qft_dagger(qreg_meas), qargs=qreg_meas) +qc.measure(qreg_meas, creg_meas) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(qpe_imp_simulator)= +### Experiment using Simulator + +The probability distribution of the measured outcome is produced using simulator. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +simulator = AerSimulator() +shots = 2048 +qc_tr = transpile(qc, backend=simulator) +results = simulator.run(qc_tr, shots=shots).result() +answer = results.get_counts() + +def show_distribution(answer): + n = len(answer) + x = [int(key, 2) for key in list(answer.keys())] + y = list(answer.values()) + + fig, ax = plt.subplots() + rect = ax.bar(x,y) + + def autolabel(rects): + for rect in rects: + height = rect.get_height() + ax.annotate(f'{height/sum(y):.3f}', + xy=(rect.get_x() + rect.get_width() / 2, height), + xytext=(0, 0), + textcoords="offset points", ha='center', va='bottom') + autolabel(rect) + plt.ylabel('Probabilities') + plt.show() + +show_distribution(answer) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +Here the answer is 2 in decimal number. Since the measured result should be $\ket{2^n\theta}$, $\theta=2/2^3=1/4$, thus we successfully obtain the correct $\theta$. + +The quantum circuit used here is simple, but is a good staring point to explore the behaviour of QPE circuit. Please examine the followings, for example: +- Here we examined the case of $S=P(\pi/2)$ (except global phase). What happens if we insted use $P(\phi)$ gate with the $\phi$ value varying within $0<\phi<\pi$? +- You will see that the precision of estimated phase value gets worse depending on the choice of $\phi$. How can you improve the precision? +- We used $\ket{1}$ as it is eigenvector of $S$ gate, but what happens if we use a state different from $\ket{1}$? Please examine the case when the state is linearly dependent of the eigenvector. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(qpe_imp_real)= +### Experiment with Quantum Computer + +Finally, we will run the circuit on quantum computer and check results. We can use the least busy machine by using the following syntax. + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Running on real IBM device +instance = 'ibm-q/open/main' + +try: + provider = IBMProvider(instance=instance) +except IBMQAccountCredentialsNotFound: + provider = IBMProvider(token='__paste_your_token_here__', instance=instance) + +backend_list = provider.backends(filters=operational_backend(min_qubits=4)) +backend = least_busy(backend_list) +print(f"least busy backend: {backend.name()}") +``` + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Execute circuit on the least busy backend. Monitor jobs in the queue. +qc_tr = transpile(qc, backend=backend, optimization_level=3) +job = backend.run(qc_tr, shots=shots) +job_monitor(job, interval=2) +``` + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Results +results = job.result() +answer = results.get_counts() +show_distribution(answer) +``` + +(shor_algo)= +## Shor's Algorithm + +Very well, let's move on to the main topic, Shor's algorithm. Shor's algorithm attempts to break down a positive composite number $N$ into a product of two prime numbers $N=qp$. + +First, let's review the method of notation used for integer remainders. Consider the following string of integer values $x$. If they are divided, for example, by 3, then the values of the remainder $y$ will be as shown below. + +|x|0|1|2|3|4|5|6|7|8|9| +|-|-|-|-|-|-|-|-|-|-|-| +|y|0|1|2|0|1|2|0|1|2|0| + +Let us write this as $\modequiv{x}{y}{3}$ (if $k$ is an integer value of 0 or greater, this can also be written as $x=3k+y$). + +Shor's algorithm is composed of multiple steps of computation and they can be written in the form of flowchart below. The steps in black are computed using classical calculation, and those in blue are computed using a quantum computer. You might be wondering why we use quantum calculation only for a part of the algorithm. This is because that blue part is difficult to calcualte classically. that is, the basic idea is to use classical calculation for the steps that can be efficiently processed classically, and quantum calculation for those that cannot be done classically. Later on, it will become clear why classical calculation has difficulty in performing the blue part. + +(shor_algo_fig)= +```{image} figs/shor_flow.png +:alt: shor_flow +:width: 500px +:align: center +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(factoring_example)= +### Example of Integer Factoring + +As a simple example, let us consider the factoring of $N=15$ using this algorithm. + +For example, imagine that we have selected $a=7$ as a coprime number to 15. Dividing $7^x$ by 15, the remainder $y$ is as follows. + +|x|0|1|2|3|4|5|6|$\cdots$| +|-|-|-|-|-|-|-|-|-| +|y|1|7|4|13|1|7|4|$\cdots$| + +As you can see, the smallest (non-trivial) value for $r$ that meets the condition $\modequiv{7^r}{1}{15}$ is 4. Since $r=4$ is an even number, we can define $\modequiv{x}{7^{4/2}}{15}$, that leads to $x=4$. $x+1 = \modnequiv{5}{0}{15}$, so the following is true. + +$$ +\{p,q\}=\{\gcd(5,15), \gcd(3,15)\}=\{5,3\} +$$ + +Thus, we managed to obtain the result $15=5\times3$! + ++++ + +(shor_circuit)= +### Quantum Circuit + +Next, let's look at a quantum circuit to perform integer factoring of $N=15$. It might look like we're jumping right to the answer, but below is the structure of the circuit itself. + +(shor_circuit_fig)= +```{image} figs/shor.png +:alt: shor +:width: 700px +:align: center +``` + +The top 4 qubits comprise the measurement register, and the bottom 4 the work register. Each register has 4 qubits ($n=4$) because they suffice to express 15 (the binary notation of 15 is $1111_2$). All the qubits are initialized to $\ket{0}$, and the state of the measurement (work) register is represented as $\ket{x}$ ($\ket{w}$). $U_f$ is the oracle given below: + +```{image} figs/shor_oracle2.png +:alt: shor_oracle2 +:width: 300px +:align: center +``` + +and it outputs the state $\ket{w\oplus f(x)}$ in the work register (this will be explained in detail later). Let us define the function $f(x)$ as $f(x) = a^x \bmod N$. + +As done above, let us check the quantum states of the circuit through steps 1 through 5. First, in step 1, we generate an equal superposition of computational basis states in the measurement register. Let's write each computational basis state as an integer between 0 and 15. + +- Step 1 :$\frac{1}{\sqrt{2^4}}\left[\sum_{j=0}^{2^4-1}\ket{j}\right]\ket{0}^{\otimes 4} = \frac{1}{4}\left[\ket{0}+\ket{1}+\cdots+\ket{15}\right]\ket{0}^{\otimes 4}$ + +After applying the oracle $U_f$, given the definition of the oracle, the state is as follows. + +- Step 2 : + +$$ +\begin{aligned} +&\frac{1}{4}\left[\ket{0}\ket{0 \oplus (7^0 \bmod 15)}+\ket{1}\ket{0 \oplus (7^1 \bmod 15)}+\cdots+\ket{15}\ket{0 \oplus (7^{15} \bmod 15)}\right]\\ +=&\frac{1}{4}\left[\ket{0}\ket{1}+\ket{1}\ket{7}+\ket{2}\ket{4}+\ket{3}\ket{13}+\ket{4}\ket{1}+\cdots+\ket{15}\ket{13}\right] +\end{aligned} +$$ + +After step 2, we measure the work register. $\ket{w}$ is $\ket{7^x \bmod 15}$, that is, either one of the four states $\ket{1}$, $\ket{7}$, $\ket{4}$ and $\ket{13}$. Let us assume, for example, that the measurement result was 13. In that case, the state of the measurement register would be: + +- Step 3 :$\frac{1}{2}\left[\ket{3}+\ket{7}+\ket{11}+\ket{15}\right]$ + +Next, an inverse QFT $\rm{QFT}^\dagger$ is applied to the measurement register. The inverse QFT converts $\ket{j} \to \frac{1}{\sqrt{N}}\sum_{k=0}^{N-1}e^{\frac{-2\pi ijk}{N}}\ket{k}$. + +- Step 4 : + +$$ +\begin{aligned} +&\frac{1}{2}\mathrm{QFT}^\dagger\left[\ket{3}+\ket{7}+\ket{11}+\ket{15}\right]\\ +=&\frac{1}{2}\frac1{\sqrt{2^4}}\sum_{k=0}^{2^4-1}\left[e^{\frac{-2\pi i\cdot3k}{2^4}}+e^{\frac{-2\pi i\cdot7k}{2^4}}+e^{\frac{-2\pi i\cdot11k}{2^4}}+e^{\frac{-2\pi i\cdot15k}{2^4}}\right]\ket{k}\\ +=&\frac{1}{8}\left[4\ket{0}+4i\ket{4}-4\ket{8}-4i\ket{12}\right] +\end{aligned} +$$ + +Here, the key is that only the states $\ket{0}$, $\ket{4}$, $\ket{8}$ and $\ket{12}$ appear. Here the interference between quantum states is exploited to reduce the amplitudes of incorrect solutions. + +- Step 5 :Last, we measure the measurement bit, and find that 0, 4, 8, and 12 each occur with a 1/4 probability. +- +You may have anticipated, but the signs of repetition is becoming apparent because $7^x \bmod 15$ is calculated in step 2. + ++++ + +(shor_measurement)= +### Analysis of Measurement Results + +Let's think about what these measurement results mean. Given the similarity between the Shor's algorithm {ref}`circuit ` and the $n$-qubit QPE {ref}`circuit `, we can natually hypothesize that both function in the same way (supplementary explanation is provided below). If that is the case, the measurement register should represent $2^4=16$ times the phase $\theta$ of eigenvalue $e^{2\pi i\theta}$. If we get, e.g., 4, from the measurement register, the phase $\theta$ will be $\theta=4/16=0.25$. What does this value mean? + +As a quantum circuit for Shor's algorithm, we have so far used $\ket{w}=\ket{0}^{\otimes n}$ as the initial state and an oracle $U_f$ that acts as $U_f\ket{x}\ket{w}=\ket{x}\ket{w\oplus f(x)}$ $(f(x) = a^x \bmod N)$. To implement this $U_f$, let us consider the following unitary operator $U$. + +```{math} +:label: U_action +U\ket{m} = +\begin{cases} +\ket{am \bmod N)} & 0 \leq m \leq N - 1 \\ +\ket{m} & N \leq m \leq 2^n-1 +\end{cases} +``` + +This unitary satisfies the following relation: + +$$ +U^{x}\ket{1} = U^{x-1} \ket{a \bmod N} = U^{x-2} \ket{a^2 \bmod N} = \cdots = \ket{a^x \bmod N} +$$ + +Therefore, we can use the $U$ to implement $U_f\ket{x}\ket{0}$ where $w=0$. + +$$ +\begin{aligned} +U_f\ket{x}\ket{0}&=\ket{x}\ket{0 \oplus (a^x \bmod N)}\\ +&=\ket{x}\ket{a^x \bmod N}\\ +&=\ket{x} U^x \ket{1} +\end{aligned} +$$ + +Here, we'll consider the state $\ket{\psi_s}$ as defined follows, with $s$ being an integer within $0 \leq s \leq r-1$: + +$$ +\ket{\psi_s} \equiv \frac{1}{\sqrt{r}}\sum_{k=0}^{r-1}e^{-2\pi isk/r}\ket{a^k \bmod N} +$$ + +With this state $\ket{\psi_s}$, we can derive + +$$ +\frac{1}{\sqrt{r}}\sum_{s=0}^{r-1}\ket{\psi_s}=\ket{1}. +$$ + +At the same time, we can see that the $\ket{\psi_s}$ is an eigenvector of the operator $U$ and has an eigenvalue $e^{2\pi is/r}$: + +$$ +U\ket{\psi_s}=e^{2\pi is/r}\ket{\psi_s} +$$ + +In other words, the operation of performing an oracle $U_f$ in Shor's algorithm is equivalent to applying the unitary $U$ $x$ times to $\ket{1}$, which is the superposition of the eigenvectors $\ket{\psi_s}$ of eigenvalue $e^{2\pi is/r}. If you compare this with the {ref}`QPE circuit`, it is obvious that they are essentially doing the same thing. Since the inverse QFT is performed after these operations, the entire operation corresponds exactly to QPE. + +Recall that the phase value determined using QPE is $\theta$ of the eigenvalue $e^{2\pi i\theta}$ for the unitary $U$ and eigenvector $\ket{\psi}$ that satisfy $U\ket{\psi}=e^{2\pi i\theta}\ket{\psi}$. From these, you would see that the phase $\theta$ derived from Shor's algorithm is (an integer multiple of) $s/r$. + ++++ + +(continued_fractions)= +### Continued Fraction Expansion + +Through the above, we understood that the phase obtained by the measurement is $\theta \approx s/r$. In order to derive an order $r$ from these results, we will use **continued-fraction expansion** but the details will be left to other references (P. 230, Box 5.3 of {cite}`nielsen_chuang_qft_app` describes the continued fraction algorithm). This method allows us to determine $s/r$ as the closet fracton to $\theta$. + +For example, we get $r=4$ if $\theta=0.25$ (though we might have $r=8$ at small frequency). If you can get this far, then you can use classical calculation to break it down into two prime numbers (See {ref}`here`). + ++++ + +(modular_exponentiation)= +### Modular Exponentiation + +Let's explore the operation of oracle $U_f\ket{x}\ket{w}=\ket{x}\ket{w\oplus f(x)}$ a bit further. By using the binary representation of $x$: + +$$ +x=(x_{n-1}x_{n-2}\cdots x_0)_2 = 2^{n-1}x_{n-1}+2^{n-2}x_{n-2}+\cdots+2^0x_0, +$$ + +we can express $f(x) = a^x \bmod N$ as follows. + +$$ +\begin{aligned} +f(x) & = a^x \bmod N \\ + & = a^{2^{n-1}x_{n-1}+2^{n-2}x_{n-2}+\cdots+2^0x_0} \bmod N \\ + & = a^{2^{n-1}x_{n-1}}a^{2^{n-2}x_{n-2}}\cdots a^{2^0x_0} \bmod N +\end{aligned} +$$ + +That is, this function can be implemented using unitary operations as shown below. + +```{image} figs/shor_oracle.png +:alt: shor_oracle +:width: 600px +:align: center +``` + +Comparing this circuit with $n$-qubit QPE {ref}`circuit`, you'll immediately see that this circuit implements $U^{2^x}$ operations of the QPE. Applying $a^x \bmod N$, controlled by each qubit of the 1st register, to the contents of the second register (corresponding to the bottom wire in the diagram above) to implement the $U^{2^x}$ operations of QPE is called modular exponentiation. + +(shor_imp)= +## Implementation of Shor's algorithm + +We will now switch to code implementation of Shor's algorithm. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(shor_imp_period)= +### Order Finding + +First, let's look into the algorithm for determining the order (period) of repetitions. + +With a positive integer $N$, let's investigate the behavior of function $f(x) = a^x \bmod N$. In [Shor's algorithm](shor_algo_fig), $a$ is a positive integer that is smaller than $N$ and $a$ and $N$ are coprime. The order $r$ is the smallest non-zero integer that satisfies $\modequiv{a^r}{1}{N}$. +The graph shown below is an example of this function. The arrow between the two points indicates the periodicity. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +N = 35 +a = 3 + +# Calculate the data to be plotted +xvals = np.arange(35) +yvals = [np.mod(a**x, N) for x in xvals] + +# Use matplotlib to perform plotting +fig, ax = plt.subplots() +ax.plot(xvals, yvals, linewidth=1, linestyle='dotted', marker='x') +ax.set(xlabel='$x$', ylabel=f'${a}^x$ mod {N}', + title="Example of Periodic Function in Shor's Algorithm") + +try: # Plot r on the graph + r = yvals[1:].index(1) + 1 +except ValueError: + print('Could not find period, check a < N and have no common factors.') +else: + plt.annotate(text='', xy=(0, 1), xytext=(r, 1), arrowprops={'arrowstyle': '<->'}) + plt.annotate(text=f'$r={r}$', xy=(r / 3, 1.5)) +``` + +(shor_imp_oracle)= +### Oracle Implementation + +Below we aim at factoring $N=15$. As explained above, we implement the oracle $U_f$ by repeating the unitary $U\ket{m}=\ket{am \bmod N}$ $x$ times. + +For this practice task, please implement the function `c_amod15` that executes $C[U^{2^l}] \ket{z} \ket{m}=\ket{z} \ket{a^{z 2^{l}} m \bmod 15} \; (z=0,1)$ below (`c_amod15` returns the entire controlled gate, but here you should write $U$ that is applied to the target register). + +Consider the argument `a` which is an integer smaller than 15 and is coprime to 15. In general, when $a = N-1$, $r=2$ because $\modequiv{a^2}{1}{N}$. Therefore, $a^{r/2} = a$ and $\modequiv{a + 1}{0}{N}$, meaning that such `a` cannot be used for Shor's algorithm. This would require the value of `a` to be less than or equal to 13. + +Such unitary operation will need a complicated circuit if it should work for general values of $a$ and $N${cite}`shor_oracle`, but it can be implemented with a few lines of code if the problem is restricted to $N=15$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +def c_amod15(a, l): + """mod 15-based control gate""" + + if a not in [2, 4, 7, 8, 11, 13]: + raise ValueError("'a' must be 2, 4, 7, 8, 11, or 13") + + U = QuantumCircuit(4) + + ################## + ### EDIT BELOW ### + ################## + + #if a == 2: + # ... + #elif a == 4: + # ... + # ... + + ################## + ### EDIT ABOVE ### + ################## + + # Repeating U 2^l times + U_power = U.repeat(2 ** l) + + # Convert U_power to gate + gate = U_power.to_gate() + gate.name = f"{a}^{2 ** l} mod 15" + + # Convert gate to controlled operation + c_gate = gate.control() + return c_gate +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +**Solution** + +````{toggle} +First we consider the case of `a=2, 4, 8`. Representing $m$ as a binary number: + +```{math} +m=\sum_{j=0}^{3} 2^j m_j \, (m_j=0,1) +``` + +```{math} +:label: ammod15 +am \bmod 15 = \left( \sum_{j=0}^{3} 2^{j+\log_2 a} m_j \right) \bmod 15 +``` + +Note $15 = 2^4 - 1$. In general, for nutaral numbers $n, m$ and the smallest natural number $p$ that satisfies $n-pm < m$, the following relation holds (the proof is simple): + +```{math} +2^n \bmod (2^m - 1) = 2^{n-pm} +``` + +Therefore, $2^{j+\log_2 a} \bmod 15$ takes a value of $1, 2, 4, 8$ once and only once for $j=0, 1, 2, 3$. + +For $m \leq 14$, since the sum of the modulo 15 of individual terms inside the parentheses on the righthand side of Equation {eq}`ammod15` never exceeds 15, + +```{math} +am \bmod 15 = \sum_{j=0}^{3} (2^{j+\log_2 a} \bmod 15) m_j, +``` + +it turns out that we can multiply by $a$ and take a modulo 15 for each bit independently. If we write out the values of $2^{j+\log_2 a} \bmod 15$: + +| | $j=0$ | $j=1$ | $j=2$ | $j=3$ | +|-------|-------|-------|-------|-------| +| $a=2$ | 2 | 4 | 8 | 1 | +| $a=4$ | 4 | 8 | 1 | 2 | +| $a=8$ | 8 | 1 | 2 | 4 | + +This action can be implemented using a cyclic bit shift. For example, `a=2` should be as follows: + +```{math} +\begin{align} +0001 & \rightarrow 0010 \\ +0010 & \rightarrow 0100 \\ +0100 & \rightarrow 1000 \\ +1000 & \rightarrow 0001 +\end{align} +``` + +We can implement this using SWAP gates into a quantum circuit. + +```{code-block} python + ################## + ### EDIT BELOW ### + ################## + + if a == 2: + # Applying SWAP gates from higher qubits in this order + U.swap(3, 2) + U.swap(2, 1) + U.swap(1, 0) + elif a == 4: + # Bit shift by skipping one bit + U.swap(3, 1) + U.swap(2, 0) + elif a == 8: + # Applying from lower qubits + U.swap(1, 0) + U.swap(2, 1) + U.swap(3, 2) + + ################## + ### EDIT ABOVE ### + ################## +``` + +A good thing about using SWAP gates in this way is that Equation {eq}`U_action` is correctly realized because the $U$ does not change state in the register for $m=15$. This is however not very crucial when using this function below, because $\ket{15}$ does not appear in the work register. + +How about `a=7, 11, 13`? Again, we can exploit some uniqueness of the number 15. Noting that $7 = 15 - 8$, $11 = 15 - 4 and $13 = 15 - 2$, + +```{math} +\begin{align} +7m \bmod 15 & = (15 - 8)m \bmod 15 = 15 - (8m \bmod 15) \\ +11m \bmod 15 & = (15 - 4)m \bmod 15 = 15 - (4m \bmod 15) \\ +13m \bmod 15 & = (15 - 2)m \bmod 15 = 15 - (2m \bmod 15), +\end{align} +``` + +we can see that the circuit we want is the one that subtracts the results of `a=2, 4, 8` from 15. Subtracting from 15 for a 4-bit register corresponds to reversing all the bits, that is, applying $X$ gates. Therefore, what we need finally is something like below: + +```{code-block} python + ################## + ### EDIT BELOW ### + ################## + + if a in [2, 13]: + # Applying SWAP gates from higher qubits in this order + U.swap(3, 2) + U.swap(2, 1) + U.swap(1, 0) + elif a in [4, 11]: + # Bit shift by skipping one bit + U.swap(3, 1) + U.swap(2, 0) + elif a in [8, 7]: + # Applying from lower qubits + U.swap(1, 0) + U.swap(2, 1) + U.swap(3, 2) + + if a in [7, 11, 13]: + U.x([0, 1, 2, 3]) + + ################## + ### EDIT ABOVE ### + ################## +``` + +```` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-input, remove-output] +--- +# Cell for text + +def c_amod15(a, l): + U = QuantumCircuit(4, name='U') + + if a in [2, 13]: + U.swap(3, 2) + U.swap(2, 1) + U.swap(1, 0) + elif a in [4, 11]: + U.swap(3, 1) + U.swap(2, 0) + elif a in [8, 7]: + U.swap(1, 0) + U.swap(2, 1) + U.swap(3, 2) + + if a in [7, 11, 13]: + U.x([0, 1, 2, 3]) + + U_power = U.repeat(2 ** l) + + gate = U_power.to_gate() + gate.name = f"{a}^{2 ** l} mod 15" + c_gate = gate.control() + return c_gate +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(shor_imp_circuit)= +### Implementation of Entire Circuit + +Let's use 8 qubits for the measurement register. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Coprime to 15 +a = 7 + +# Number of measurement bits (precision of phase estimation) +n_meas = 8 + +# Register used to obtain phase +qreg_meas = QuantumRegister(n_meas, name='meas') +# Register to hold eigenvector +qreg_aux = QuantumRegister(4, name='aux') +# Classical register written by the output of phase estimation +creg_meas = ClassicalRegister(n_meas, name='out') + +# Create quantum circuit from above registers +qc = QuantumCircuit(qreg_meas, qreg_aux, creg_meas) + +# Initialize individual registers +qc.h(qreg_meas) +qc.x(qreg_aux[0]) + +# Apply controlled-U gate +for l, ctrl in enumerate(qreg_meas): + qc.append(c_amod15(a, l), qargs=([ctrl] + qreg_aux[:])) + +# Apply inverse QFT +qc.append(qft_dagger(qreg_meas), qargs=qreg_meas) + +# Measure the circuit +qc.measure(qreg_meas, creg_meas) +qc.draw('mpl') +``` + +Execute the circuit using simulator and check the results. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +qc = transpile(qc, backend=simulator) +results = simulator.run(qc, shots=2048).result() +answer = results.get_counts() + +show_distribution(answer) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(shor_imp_ana)= +### Analysis of Measured Results +Let's get phase from the output results. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +rows, measured_phases = [], [] +for output in answer: + decimal = int(output, 2) # Converting to decimal number + phase = decimal / (2 ** n_meas) + measured_phases.append(phase) + # Save these values + rows.append(f"{decimal:3d} {decimal:3d}/{2 ** n_meas} = {phase:.3f}") + +# Print the results +print('Register Output Phase') +print('------------------------') + +for row in rows: + print(row) +``` + +From the phase information, you can determine $s$ and $r$ using the continued fraction expansion. You can use the built-in Python `fractions` module to convert fractions into `Fraction` objects. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +rows = [] +for phase in measured_phases: + frac = Fraction(phase).limit_denominator(15) + rows.append(f'{phase:10.3f} {frac.numerator:2d}/{frac.denominator:2d} {frac.denominator:13d}') + +# Print the results +print(' Phase Fraction Guess for r') +print('-------------------------------------') + +for row in rows: + print(row) +``` + +Using the `limit_denominator` method, we obtain the fraction closest to the phase value, for which the denominator is smaller than a specific value (15 in this case). + +From the measurement results, you can see that the two values (64 and 192) provide the correct answer of $r=4$. \ No newline at end of file diff --git a/source/en/spectrum_estimation.md b/source/en/spectrum_estimation.md new file mode 100644 index 00000000..e008042a --- /dev/null +++ b/source/en/spectrum_estimation.md @@ -0,0 +1,689 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【Exercise】Spectral Decomposition with Phase Estimation + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{|#1\rangle}$ + ++++ + +## Estimation of Energy Spectra + +In physics and chemistry, determining energy eigenvalues (spectra) and corresponding eigenstates of a system is an extremely important task, an example of which appears in a {doc}`later exercise`. Deriving the energy spectrum of a system is equivalent to determining the Hamiltonian that governs the system and diagonalizing it. + +However, as we saw in the {doc}`previous exercise`, the number of dimensions in a typical quantum system is extremely large, and we cannot properly perform the inverse matrix calculations that are the key to diagonalizing the Hamiltonian. In the exercise, we found that even in that case, if the form of the Hamiltonian allows us to perform efficient Suzuki-Trotter decomposition, we can simulate time evolution of the system using a quantum computer. However, in this simulation, we did not use energy eigenvalues and eigenstates of the system explicitly. + +In fact, we can numerically determine the energy eigenvalues{cite}`Aspuru-Guzik1704` by combining the simulation of time evolution with the phase estimation method that we discussed in {doc}`shor`. This approach could be even extended to investigate corresponding eigenstates. In this assignment, we will consider Heisenberg model with an external magnetic field and attempt the decomposition of energy spectra using phase estimation technique. + ++++ + +## Reconsideration of Heisenberg Model + +The Hamiltonian of the Heisenberg model, introduced in the previous section, is as follows. + +$$ +H = -J \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j}) \quad (J > 0) +$$ + +This Hamiltonian represents a system composed of particles with spins, lined up in one dimensional space, that interact between adjacent particles. In this system, the interactions will lower the energy when the directions of the spins are aligned. Therefore, the lowest energy will be achived when all the spin directions are aligned. + +In this assignment, we will apply an external magnetic field to the system. When there is an external magnetic field, the energy will be lowered when the spin is aligned with the magnetic field. Therefore, if we apply the external magnetic field along the +$Z$ direction, the Hamiltonian is as follows. + +$$ +H = -J \sum_{j=0}^{n-1} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j} + g \sigma^Z_j) +$$ + +This Hamiltonian has one more difference from the one considered previously. For the previous case, we considered the boundary condition that the spins located at the end of the chain interacted only spins at "inner side" of the chain by taking the sum of the spins from $j=0$ to $n-2$. This time the sum is taken all the way to $n-1$. This represents "periodic boundary condition" (the spins on a circle, not on a straight line) by treating $\sigma^{X,Y,Z}_n$ as equal to $\sigma^{X,Y,Z}_0$. + +Let's look into the eigenvalues and eigenstates of such Hamiltonian for a specific example. We consider the simplest case of $n=2$ and $g=0$, and derive the true answers by exact diagonalization. + +```{code-cell} ipython3 +:tags: [remove-output] + +# First, import all necessary modules +import numpy as np +import matplotlib.pyplot as plt +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile +from qiskit.quantum_info import SparsePauliOp +from qiskit.visualization import plot_histogram +from qiskit_aer import AerSimulator +# Workbook-specific modules +from qc_workbook.show_state import show_state + +print('notebook ready') +``` + +```{code-cell} ipython3 +# Number of spins +n_s = 2 +# Coupling parameter +J = 1. +# External field / J +g = 0. + +# Construct the Hamiltonian matrix +paulis = list() +coeffs = list() + +xx_template = 'I' * (n_s - 2) + 'XX' +yy_template = 'I' * (n_s - 2) + 'YY' +zz_template = 'I' * (n_s - 2) + 'ZZ' + +for j in range(n_s): + paulis.append(xx_template[j:] + xx_template[:j]) + paulis.append(yy_template[j:] + yy_template[:j]) + paulis.append(zz_template[j:] + zz_template[:j]) + coeffs += [-J] * 3 + + if g != 0.: + paulis.append('I' * (n_s - j - 1) + 'Z' + 'I' * j) + coeffs.append(-J * g) + +hamiltonian = SparsePauliOp(paulis, coeffs).to_matrix() + +# Diagonalize and obtain the eigenvalues and vectors +eigvals, eigvectors = np.linalg.eigh(hamiltonian) + +# Print the eigenvectors +for i in range(eigvals.shape[0]): + show_state(eigvectors[:, i], binary=True, state_label=r'\phi_{} (E={}J)'.format(i, eigvals[i])) +``` + +In the last part, the [`show_state` function](https://github.com/UTokyo-ICEPP/qc-workbook/tree/master/source/qc_workbook/show_state.py) was used to show the eigenvalues and eigenvectors. We can see that there are three independent eigenvectors that correspond to the lowest energy state (eigenvalue $-2J$). Therefore, an arbitrary linear combination of these eigenvectors is also the lowest energy state. The excited state (eigenvalue $6J$) is $1/\sqrt{2} (-\ket{01} + \ket{10})$. + ++++ + +## Spectral Decomposition with Phase Estimation + +Let's now begin the main part of this exercise. In the figure shown below at {doc}`shor`, what can you conclude when $U$ is a time evolution operator $U_H(-\tau)$ for a certain time $\tau$ for a Hamiltonian $H$? + +```{image} figs/qpe_wo_iqft.png +:alt: qpe_wo_iqft +:width: 500px +:align: center +``` + +Below, we will refer to the upper register in the figure above (initial state $\ket{0}$) as "readout" register R, and the lower register (initial state $\ket{\psi}$) as "state" register S. The number of bits for the registers R and S are $n_R$ and $n_S$, respectively. Furthermore, note that the bit corresponding to the lowest order in the readout register is written at the bottom of the figure, and this is opposite to the notation used in Qiskit. + +Now, we will break the Hamiltonian $H$ into a constant $\hbar \omega$ (in energy dimension) and the dimensionless Hermitian operator $\Theta$. + +$$ +H = \hbar \omega \Theta. +$$ + +Here, the $\omega$ can be chosen arbitrary. If the $\omega$ is selected to be $x$-times larger, we can simply multiply the $\Theta$ by $1/x$. In practice, as we see later, we will choose tge $\omega$ such that the absolute value of $\Theta$'s eigenvalue is slightly smaller than 1. The formula can thus be rewritten as follows. + +$$ +U_H(-\tau) \ket{\psi} = \exp\left(i\omega \tau \Theta\right) \ket{\psi} +$$ + +If the operator correspondting to the circuit in the figure is denoted by $\Gamma$, we arrive at the following. + +$$ +\Gamma \ket{0}_R \ket{\psi}_S = \frac{1}{\sqrt{2^{n_R}}} \sum_{j=0}^{2^{n_R} - 1} \exp\left(i j \omega \tau \Theta\right) \ket{j}_R \ket{\psi}_S +$$ + +Then, we apply an inverse Fourier transform to this state, as done in the exercise, + +$$ +\text{QFT}^{\dagger}_R \Gamma \ket{0}_R \ket{\psi}_S = \frac{1}{2^{n_R}} \sum_{k=0}^{2^{n_R} - 1} \sum_{j=0}^{2^{n_R} - 1} \exp(i j \omega \tau \Theta) \exp\left(-\frac{2 \pi i j k}{2^{n_R}}\right) \ket{k}_R \ket{\psi}_S. +$$ + +So far we have simply stated that $\tau$ is a certain time, but actually it can take any value. Now let us fix the $\tau$ value to be $\omega \tau = 2 \pi$. This leads to the following. + +$$ +\text{QFT}^{\dagger}_R \Gamma \ket{0}_R \ket{\psi}_S = \frac{1}{2^{n_R}} \sum_{k=0}^{2^{n_R} - 1} \sum_{j=0}^{2^{n_R} - 1} \exp\left[\frac{2 \pi i j}{2^{n_R}} \left(2^{n_R} \Theta - k\right)\right] \ket{k}_R \ket{\psi}_S +$$ + +Therefore, if $\ket{\psi}$ can be written as + +```{math} +:label: spectral_decomposition +\ket{\psi} = \sum_{m=0}^{2^{n_S} - 1} \psi_m \ket{\phi_m} +``` + +using the eigenvectors of $\Theta$ $\{\ket{\phi_m}\}$, then the $\text{QFT}^{\dagger}_R \Gamma \ket{0}_R \ket{\psi}_S$ can be written with the corresponding eigenvalues $\{\theta_m\}$ as + +```{math} +:label: spectrum_estimation_final +\begin{align} +\text{QFT}^{\dagger}_R \Gamma \ket{0}_R \ket{\psi}_S & = \frac{1}{2^{n_R}} \sum_{k=0}^{2^{n_R} - 1} \sum_{j=0}^{2^{n_R} - 1} \sum_{m=0}^{2^{n_S} - 1} \psi_m \exp\left[\frac{2 \pi i j}{2^{n_R}} (\kappa_m - k)\right] \ket{k}_R \ket{\phi_m}_S \\ +& = \sum_{k=0}^{2^{n_R} - 1} \sum_{m=0}^{2^{n_S} - 1} \psi_m f(\kappa_m - k) \ket{k}_R \ket{\phi_m}_S. +\end{align} +``` + +Here, the second equation is written using the function $f(\kappa_m - k)$ defined as $f(\kappa_m - k) := \frac{1}{2^{n_R}} \sum_{j} \exp \left[2 \pi i j (\kappa_m - k) / 2^{n_R}\right]$. + +Finally we are going to measure the state, and then multiply $\theta_m = 2^{-n_R} \kappa_m$, estimated from the measured bitstrings in the R register, by $\hbar \omega$ to determine the energy eigenvalue of $H$. + +You might find it difficult to digest what we actually did because some new *ad-hoc* parameters $\omega$ and $\tau$ were introduced. Let us now look at the problem from different perspective. Eventually, what we did above was the following when a Hamiltonian $H$ was provided: + +1. Normalize $H$ such that the eigenvalue is $\lesssim 1$, or the absolute value is $\lesssim \frac{1}{2}$ if the eigenvalue could be negative (record the normalization constant). +2. Perform phase estimation of $U = \exp(-2 \pi i \Theta)$ with the normalized operator as $\Theta$. +3. Obtain the energy eigenvalue by multiplying the eigenvalues of $\Theta$ obtained from phase estimation by the normalization constant in Step 1. + +By doing above, we determine the eigenvalues of $\Theta$ so that the eigenvalues from the readout register will not cause any {ref}`overflow `. + +It might look contradictory to select a normalization constant so that the eigenvalue can take a specific value in the problem of eigenvalue determination. However, as we touched on in {doc}`dynamics_simulation`, the Hamiltonian expressed in quantum computing can all be decomposed into a linear combination of products of basis state operators ${I, \sigma^X, \sigma^Y, \sigma^Z}$. Since the eigenvalues of products of individual basis state operators are $\pm 1$, if the Hamiltonian $H$ is decomposed into the product of the basis state operators $\sigma_k$ and the energy coefficient $h_k$, as follows: + +$$ +H = \sum_{k} h_k \sigma_k +$$ + +then the absolute value of the eigenvalue of $H$ is at most $\sum_{k} |h_k|$. Therefore, even for a completely unknown Hamiltonian, we can take $\hbar \omega = 2 \sum_{k} |h_k|$ as the normalization constant. If it turns out that the maximum eigenvalue is smaller from the spectral estimation, we can simply adjust the normalization constant and perform the calculation again. + +The same logic can be used to decide the number of bits of the readout register R. Now let us think about an absolute value of the smallest non-zero eigenvalue. The smallest value is equal to or greater than: + +$$ +\mu = \min_{s_k = \pm 1} \left| \sum_{k} s_k h_k \right| +$$ + +In principle, we will have to examine $2^{L}$ combinations (where $L$ is the number of Hamiltonian terms) to determine this value. But, since practical Hamiltonians do not have too many terms, it is likely that this can be handled with a reasonable amount of computation. Let us set the number of bits in register R, $n_R$, to be able to read out $2^{n_R}\mu/(\hbar \omega)$. + +We have seen so far that the normalization constant and the size of the readout register can be determined easily. A problem resides in the state $\ket{\psi}$ which the operator $U$ is applied to. If we want to know the $m$-th excited energy of eigenvalues, $\psi_m \neq 0$ must be true in Equation {eq}`spectral_decomposition`. In special cases we may have an idea of the eigenvector before performing spectral estimation, but it is obvious that for general Hamiltonians we cannot prepare such states for an arbitrary value of $m$. + +On the other hand, for the lowest energy $\hbar \omega \theta_0$, we can evaluate it at relatively good precision by approximating the lowest energy state by using techniques in {doc}`vqe` and setting the obtained state as the input to the S register. Therefore, the above method can, in principle, be used to completely decompose the energy spectra, but in practice it is most commonly used to determine the lowest energy and its eigenvector accurately. + ++++ + +## Exercise 1: Implement Spectrum Estimation and Comparison with Exact Solutions + +Let us now derive the energy spectra of the Hamiltonian for the Heisenberg model using phase estimation. + +We will use Suzuki-Trotter decomposition to calculate $U_H(-\tau)$ on a quantum computer. Refer to {doc}`dynamics_simulation` to implement the rotation gates of $ZZ$, $XX$ and $YY$. + +The next cell defines a function that returns a quantum circuit composed of Suzuki-Trotter steps of the Hamiltonian evolution. The argument `num_steps` specifies the number of Suzuki-Trotter steps. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +def trotter_twopi_heisenberg(state_register, energy_norm, g, num_steps): + """Return a function that implements a single Trotter step for the Heisenberg model. + + The Heisenberg model Hamiltonian is + H = -J * sum_of_sigmas = hbar*ω * Θ + + The returned circuit implements a negative time evolution + U = exp(-i H*(-τ)/hbar) + where τ = 2π / ω, which leads to + U = exp(i 2π Θ). + + Because we employ the Suzuki-Trotter decomposition, the actual circuit corresponds to + U = [exp(i 2π/num_steps Θ)]^num_steps. + + Args: + state_register (QuantumRegister): Register to perform the Suzuki-Trotter simulation. + energy_norm (float): J/(hbar*ω). + g (float): External field strength relative to the coupling constant J. + num_steps (float): Number of steps to divide the time evolution of ωτ=2π. + + Returns: + QuantumCircuit: A quantum circuit implementing the Trotter simulation of the Heisenberg + model. + """ + circuit = QuantumCircuit(state_register, name='ΔU') + + n_spins = state_register.size + step_size = 2. * np.pi / num_steps + + # Implement the circuit corresponding to exp(i*step_size*Θ) below, where Θ is defined by + # Θ = -J/(hbar*ω) * sum_of_sigmas = -energy_norm * sum_of_sigmas + ################## + ### EDIT BELOW ### + ################## + + # circuit.? + + ################## + ### EDIT ABOVE ### + ################## + + circuit = circuit.repeat(num_steps) + circuit.name = 'U' + + return circuit +``` + +In the next cell, the algorithm of spectral estimation is implemented. This function returns a quantum circuit that takes state register, readout register and the time-evolution circuit as arguments and performs phase estimation. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +def spectrum_estimation(state_register, readout_register, u_circuit): + """Perform a spectrum estimation given a circuit containing state and readout registers and a callable implementing + a single Trotter step. + + Args: + state_register (QuantumRegister): State register. + readout_register (QuantumRegister): Readout register. + u_circuit (QuantumCircuit): A circuit implementing U_H(-2π/ω). + + Returns: + QuantumCircuit: A circuit implementing the spectrum estimation of the given Hamiltonian. + """ + circuit = QuantumCircuit(state_register, readout_register, name='Spectrum estimation') + + # Set the R register to an equal superposition + circuit.h(readout_register) + + # Apply controlled-U operations to the circuit + for iq, qubit in enumerate(readout_register): + # Repeat the 2π evolution by 2^iq and convert it to a controlled gate + controlled_u_gate = u_circuit.repeat(2 ** iq).to_gate().control(1) + + # Append the controlled gate specifying the control and target qubits + circuit.append(controlled_u_gate, qargs=([qubit] + state_register[:])) + + circuit.barrier() + + # Inverse QFT + for iq in range(readout_register.size // 2): + circuit.swap(readout_register[iq], readout_register[-1 - iq]) + + dphi = 2. * np.pi / (2 ** readout_register.size) + + for jtarg in range(readout_register.size): + for jctrl in range(jtarg): + power = jctrl - jtarg - 1 + readout_register.size + circuit.cp(-dphi * (2 ** power), readout_register[jctrl], readout_register[jtarg]) + + circuit.h(readout_register[jtarg]) + + return circuit +``` + +In this exercise, we examine the case of $n=3$ and $g=0$ for which the exact solutions were derived above. Since we already know the energy eigenvalues this time, the normalization constant of the Hamiltonian is set $\hbar \omega = 16J$ so that the output state from the readout register becomes simple. In this case, the readout result has sign and the maximum absolute value is $2^{n_R} (6/16)$, therefore the overflow can be avoided by taking $n_R = 1 + 3$. + +In the next cell, the parameters of the simulation and phase estimation are set. + +```{code-cell} ipython3 +## Physics model parameter +g = 0. + +## Spectrum estimation parameters +# Hamiltonian normalization +energy_norm = 1. / 16. # J/(hbar*ω) +# Number of steps per 2pi evolution +# Tune this parameter to find the best balance of simulation accuracy versus circuit depth +num_steps = 6 +# Register sizes +n_state = 2 +n_readout = 4 + +## Registers +state_register = QuantumRegister(n_state, 'state') +readout_register = QuantumRegister(n_readout, 'readout') +``` + +Let us check whether the function is properly defined above. + +```{code-cell} ipython3 +:tags: [remove-output] + +u_circuit = trotter_twopi_heisenberg(state_register, energy_norm, g, num_steps) +u_circuit.draw('mpl') +``` + +```{code-cell} ipython3 +:tags: [remove-output] + +se_circuit = spectrum_estimation(state_register, readout_register, u_circuit) +se_circuit.draw('mpl') +``` + +Let us prepare a function that sets the following state: + +```{math} +:label: two_qubit_init +\frac{1}{2}\ket{00} - \frac{1}{\sqrt{2}}\ket{01} + \frac{1}{2} \ket{11} = \frac{1}{2} \ket{\phi_0} + \frac{1}{2} \ket{\phi_1} + \frac{1}{2} \ket{\phi_2} + \frac{1}{2} \ket{\phi_3} +``` + +as the initial state of the state register. Here the $\ket{\phi_i}$ are four exact solutions of the eigenvectors we initially determined. + +```{code-cell} ipython3 +:tags: [remove-output] + +def make_initial_state(state_register, readout_register): + circuit = QuantumCircuit(state_register, readout_register) + + # Set the initial state of the state vector to (1/2)|00> - (1/sqrt(2))|01> + (1/2)|11> + ################## + ### EDIT BELOW ### + ################## + + #circuit.? + + ################## + ### EDIT ABOVE ### + ################## + + return circuit + + +init_circuit = make_initial_state(state_register, readout_register) +init_circuit.draw('mpl') +``` + +Finally, everything is combined. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-output] +--- +u_circuit = trotter_twopi_heisenberg(state_register, energy_norm, g, num_steps) +se_circuit = spectrum_estimation(state_register, readout_register, u_circuit) + +circuit = make_initial_state(state_register, readout_register) +circuit.compose(se_circuit, inplace=True) +circuit.measure_all() +circuit.draw('mpl') +``` + +Execute the circuit with simulator and get the output histogram. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-output] +--- +# Run the circuit in simulator and plot the histogram +simulator = AerSimulator() +circuit = transpile(circuit, backend=simulator) +job = simulator.run(circuit, shots=10000) +result = job.result() +counts = result.get_counts(circuit) +plot_histogram(counts) +``` + +Since the initial state of the state register is given in Equation {eq}`two_qubit_init`, the final state of the circuit should be: + +$$ +\frac{1}{2} \ket{-2}_{R} \ket{00}_{S} - \frac{1}{2\sqrt{2}} \ket{-2}_{R} \left( \ket{01}_{S} + \ket{10}_{S} \right) + \frac{1}{2} \ket{-2}_{R} \ket{11}_{S} - \frac{1}{2\sqrt{2}} \ket{6}_{R} \left( \ket{01}_{S} - \ket{10}_{S} \right) +$$ + +Is the output histogram consistent with this state? + +**Items to submit**: + +- Completed `make_trotter_step_heisenberg` function +- Completed quantum circuit to initialize the state register +- Histogram of the results of spectrum esimation and its explanation + ++++ + +## Exercise 2: Examine the Behaviour of Non-trivial States + +Next, let's determine all energy spectra of the Heisenberg model with $n=4$ as a function of $g$. We can do an exact diagonalization, as done above, because $n=4$, but we will solely rely on quantum computation here. + +In order to know all the energy eignvalues, we will need to elaborate on the initial states of $S$. But, since we do not have any prior knowledge, we will take a strategy of exhausitve search. That is, we will repeat spectral estimation for each of the computational basis states $\ket{0}$ to $\ket{15}$ as input, and determine the entire spectrum by combining all the results. + +What kind of information can we get if the spectrum estimation is performed for all the computational basis states? In Equation {eq}`spectrum_estimation_final`, if $\ket{\psi} = \ket{l}$ $(l=0,\dots,2^{n_S} - 1)$ and + +```{math} +:label: phim_decomposition +\ket{l} = \sum_{m=0}^{2^{n_S} - 1} c^l_m \ket{\phi_m} +``` + +then, fhe final state of this circuit will be: + +$$ +\sum_{k=0}^{2^{n_R} - 1} \sum_{m=0}^{2^{n_S} - 1} c^l_m f(\kappa_m - k) \ket{k}_R \ket{\phi_m}_S +$$ + +When Eqaation {eq}`phim_decomposition` holds, the following Equation also holds[^unitarity]. + +```{math} +:label: l_decomposition +\ket{\phi_m} = \sum_{l=0}^{2^{n_S} - 1} c^{l*}_m \ket{l} +``` + +Therefore, if we measure the final state of the circuit in the computational basis states of R and S, the resulting probability of obtaining $k, h$, denoted as $P_l(k, h)$, is + +$$ +P_l(k, h) = \left| \sum_{m=0}^{2^{n_S} - 1} c^l_m c^{h*}_m f(\kappa_m - k) \right|^2 +$$ + +となります。$c^l_m$の値がわからなくても、これらの分布から +Let's think how we can obtain: + +$$ +P(k) = \frac{1}{2^{n_S}} \sum_{m=0}^{2^{n_S} - 1} |f(\kappa_m - k)|^2 +$$ + +from these distributions, even if we do not know the value of $c_m^l$. + +Since the distribution of $|f(\kappa_m - k)|$ has a sharp peak near $\kappa_m$, we will be able to observe $m$ peaks (though they could be partially overlapped) by making plots of $P(k)$ with respect to $k$. From these peaks, we can calculate the energy eigenvalues. + +For example, the $P(k)$ distribution for $n=2$, $g=0$, $\hbar \omega = 20J and $n_R=4$ is as follows (Note that unlike exercise 1, $\kappa_m$ is not an integer because $\hbar \omega = 20J$). In this plot, we set $P(k - 2^{n_R}) = P(k)$ and show the range of $-2^{n_R - 1} \leq k < 2^{n_R - 1}$ to visualize the negative eigenvalues. + + +```{image} figs/spectrum_estimation_example.png +:alt: spectrum_estimation_example +:width: 500px +:align: center +``` + +Let's create plots like this by taking $n=4$ and incrementing the $g$ value by 0.1 from 0 to 0.5. + +First, a function that takes computational bases and the $g$ values as arguments and returns the probability distribution of the final state is defined. We will use state vector simulator by default to avoid statistical errors due to finite sampling caused by using an ordinary shot-based simulator. + +[^unitarity]: This is because both $\{\ket{l}\}$ and $\{\ket{\phi_m}\}$ span orthonormal basis states for the state register (transformation matrices are unitary). + +```{code-cell} ipython3 +def get_spectrum_for_comp_basis( + n_state: int, + n_readout: int, + l: int, + energy_norm: float, + g: float, + shots: int = 0 +) -> np.ndarray: + """Compute and return the distribution P_l(k, h) as an ndarray. + + Args: + n_state: Size of the state register. + n_readout: Size of the readout register. + l: Index of the initial-state computational basis in the state register. + energy_norm: Hamiltonian normalization. + g: Parameter g of the Heisenberg model. + shots: Number of shots. If <= 0, statevector simulation will be used. + """ + + # Define the circuit + state_register = QuantumRegister(n_state, 'state') + readout_register = QuantumRegister(n_readout, 'readout') + circuit = QuantumCircuit(state_register, readout_register) + + # Initialize the state register + for iq in range(n_state): + if ((l >> iq) & 1) == 1: + circuit.x(state_register[iq]) + + u_circuit = trotter_twopi_heisenberg(state_register, energy_norm, g, num_steps) + se_circuit = spectrum_estimation(state_register, readout_register, u_circuit) + + circuit.compose(se_circuit, inplace=True) + + # Extract the probability distribution as an array of shape (2 ** n_readout, 2 ** n_state) + if shots <= 0: + circuit.save_statevector() + + simulator = AerSimulator(method='statevector') + circuit = transpile(circuit, backend=simulator) + job = simulator.run(circuit) + result = job.result() + statevector = result.data()['statevector'] + + # Convert the state vector into a probability distribution by taking the norm-squared + probs = np.square(np.abs(statevector)).reshape((2 ** n_readout, 2 ** n_state)) + # Clean up the numerical artifacts + probs = np.where(probs > 1.e-6, probs, np.zeros_like(probs)) + + else: + circuit.measure_all() + + # Run the circuit in simulator and plot the histogram + simulator = AerSimulator() + circuit = transpile(circuit, backend=simulator) + job = simulator.run(circuit, shots=shots) + result = job.result() + counts = result.get_counts(circuit) + + probs = np.zeros((2 ** n_readout, 2 ** n_state), dtype=float) + + for bitstring, count in counts.items(): + readout = int(bitstring[:n_readout], 2) + state = int(bitstring[n_readout:], 2) + + probs[readout, state] = count + + probs /= np.sum(probs) + + # probs[k, h] = P_l(k, h) + return probs +``` + +We decide the number of bits in the readout register here. We take $\hbar \omega = 8(3 + |g|)J$ because the number of spins is 4. When $g=0$, the expected smallest absolute value of the eigenvalues of $\Theta$ is $1/24$. But, in fact the smallest value is expected to be $n=4$ times that value, that is, $1/6$, due to the symmetry of the system. Since we only consider $|g| \ll 1$, the external magnetic field is treated as a perturbation, and $n_R=5$ is chosen so that $2^{n_R} / 6$ is sufficiently greater than 1. + +Now we have decided the circuit parameters. We will then define a function that calls the `get_spectrum_for_comp_basis` function with $g$ as an argument for $2^n$ computational basis states, and the function is executed for $g=0$ (this will take some time). + + +```{code-cell} ipython3 +:tags: [remove-output] + +n_state = 4 +n_readout = 5 +energy_norm = 1. / 24. + +g_values = np.linspace(0., 0.5, 6, endpoint=True) + +spectra = np.empty((g_values.shape[0], 2 ** n_readout), dtype=float) + +def get_full_spectrum(g): + """Compute and return the distribution P(k) for a value of g. + """ + + spectrum = np.zeros(2 ** n_readout, dtype=float) + + for l in range(2 ** n_state): + probs = get_spectrum_for_comp_basis(n_state, n_readout, l, energy_norm, g) + print('Computed spectrum for g = {:.1f} l = {:d}'.format(g, l)) + + ################## + ### EDIT BELOW ### + ################## + + ################## + ### EDIT ABOVE ### + ################## + + return spectrum + +# roll(spectrum, 2^{n_R-1}) => range of k is [-2^{n_R}/2, 2^{n_R}/2 - 1] +spectra[0] = np.roll(get_full_spectrum(0.), 2 ** (n_readout - 1)) +``` + +Let's make a plot of the resulting $P(k)$ distribution by converting $k$ to the energy. + +```{code-cell} ipython3 +:tags: [remove-output] + +plt.plot(np.linspace(-0.5 / energy_norm, 0.5 / energy_norm, 2 ** n_readout), spectra[0], 'o') +plt.xlabel('E/J') +plt.ylabel('P(E)') +``` + +Next, we will execute the same function for $g=0.1$, 0.2, 0.3, 0.4 and 0.5, and make plot for the relation between the energy eigenvalues of the system and $g$ from each spectrum. + +```{code-cell} ipython3 +:tags: [remove-output] + +for i in range(1, g_values.shape[0]): + spectra[i] = np.roll(get_full_spectrum(g_values[i]), 2 ** (n_readout - 1)) +``` + +```{code-cell} ipython3 +:tags: [remove-output] + +energy_eigenvalues = np.empty((g_values.shape[0], 2 ** n_state)) + +# Extract the energy eigenvalues from spectra and fill the array +################## +### EDIT BELOW ### +################## + +#energy_eigenvalues[ig, m] = E_ig_m + +################## +### EDIT ABOVE ### +################## + +plt.plot(g_values, energy_eigenvalues) +``` + +**Items to submit**: + +- Explanation of how to derive $P(k)$ from $P_l(k, h)$ and its implementation into the `get_full_spectrum` function +- Work out how to extract energy eigenvalues from $P(k)$ and the code to implement the method (if you come up with completely different method for extracting the energy eigenvalues, it's fine to submit that as well) +- Plot of the relation between 16 energy eigenvalues and $g$ + + +**HInt**: + +(Regarding the derivation of $P(k)$) If you look at Equations {eq}`phim_decomposition` and {eq}`l_decomposition`, you will see that the following equation holds. + +$$ +\sum_{l=0}^{2^{n_S} - 1} c^l_m c^{l*}_n = \delta_{mn} +$$ + +Here $\delta_{mn}$ is a Kronecker's $\delta$, which is 1 for $m=n$ and 0 otherwise. diff --git a/source/en/vqc_machine_learning.md b/source/en/vqc_machine_learning.md new file mode 100644 index 00000000..8fa4c974 --- /dev/null +++ b/source/en/vqc_machine_learning.md @@ -0,0 +1,752 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +varInspector: + cols: + lenName: 16 + lenType: 16 + lenVar: 40 + kernels_config: + python: + delete_cmd_postfix: '' + delete_cmd_prefix: 'del ' + library: var_list.py + varRefreshCmd: print(var_dic_list()) + r: + delete_cmd_postfix: ') ' + delete_cmd_prefix: rm( + library: var_list.r + varRefreshCmd: 'cat(var_dic_list()) ' + types_to_exclude: [module, function, builtin_function_or_method, instance, _Feature] + window_display: false +--- + +# Search for New Physics with Quantum Machine Learning + ++++ + +In this exercise, we will first learn the basics of **Quantum Machine Learning** (QML), which is a typical application of quantum-classical hybrid algorithm. After that, we will explore, as an example, application of quantum machine learning to **search for new particles in particle physics experiment**. The QML technique that we learn here is called **Quantum Circuit Learning** (QCL) {cite}`quantum_circuit_learning` developed as an extension of Variational Quantum Algorithm. + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{| #1 \rangle}$ +$\newcommand{\expval}[3]{\langle #1 | #2 | #3 \rangle}$ + ++++ + +## Introduction + +In recent years, **Deep Learning** (DL) has gained considerable attention in the field of machine learning (ML). A generic deep learning model uses multiple hidden layers in the architecture of **neural networks** (NN)) and attempts to learn complex relationship between +input and output. The successfully learned DL model is capable of predicting outout for unseen input data. The QML algorithm that we study in this unit is based on a variational quantum circuit that replaces neural network in a ML model. In other words, instead of adjusting trainable parameters for each neuron in neural networks, the QML model learns the input-output relation by adjusting the parameters of variational quantum circuit such as angles of rotation gates. +A strength of quantum computer lies in the fact that it can represent exponentially large Hilbert space using finite number of qubits. If we can leverage this strength, the QML might be able to learn complex, high-dimensional correlations between data, providing a unique strength compared to conventional classical ML. + +The QML model is in general capable of representing wide range of functions using a polynomial number of quantum gates if the circuit is sufficiently deep. However, the QML in quantum-classical hybrid architecture is essentially heuristic and has no mathematical guarantee that it is superior to classical ML in terms of computational complexity (except for specific cases). In particular, in the era of *Noisy Intermediate-Scale Quantum* (NISQ) computer with hardware noise, it is not clear whether the QML has any advantage over classical ML, and therefore it is an active area of research. Since the quantum-classical hybrid QML is suitable for NISQ computer, an early QCL algorithm was implemented into IBM Quantum hardware by IBM team in March 2019, and the result was published in a paper{cite}`quantum_svm`. + ++++ + +## Machine Learneing and Deep Learning + +Machine learning could be (very broadly) described as a series of processes that is provided data and returns predictions from the data. For example, imagine that we have data consisting of two variables, $\mathbf{x}$ and $\mathbf{y}$ (both are vectors made of elements $(x_i, y_i)$ with $i$ as the element index), and consider machine learning for determining the relation between the variables. Let us think of a function $f$ which takes $x_i$ as an input variable. Then, this machine learning problem corresponds to determining the function $f$ from the data so that the output of the function $\tilde{y_i}=f(x_i)$ is as close to $\tilde{y}_i\simeq y_i$ as possible. In general, this function $f$ has parameters other than the input variable $x$, denoted by $\mathbf{w}$ here. Therefore, this ML problem is to determine the function $f=f(x,\mathbf{w}^*)$ and optimized parameter $\mathbf{w}^*$ by adjusting the $\mathbf{w}$ such that $y_i\simeq\tilde{y}_i$. + +One of the most popular methods to approximate the function $f$ is to use artificial neural networks that model the neural structure of the brain. The basic structure of neural networks is shown below. The circles indicate structural units (neurons), and the arrows indicate the flow of information between connected neurons. Neural networks have many different structures, but shown in the figure is the basic one, with the output from neurons in a layer becoming input to neurons in the next layer. In addition to the input layer which accepts inputs $x$ and the output layer that outputs $\tilde{y}$, the intermediate layers called hidden layers are often considered. Such neural network model is collectively referred to as deep neural networks. + +```{image} figs/neural_net.png +:alt: var_circuit +:width: 500px +:align: center +``` + +Let us look at the mathematical model of neural networks. Denoting the $j$-th unit in the $l$-th layer as $u_j^l$, if the $u_j^l$ takes $n$ inputs $o_k^{l-1}$ ($k=1,2,\cdots n$) from the units in the preceding $(l-1)$-th layer, the output from the unit $u_j^l$ is expressed as follows by applying weights $w_k^j$ to the inputs $o_k^{l-1}$: + +$$ +o_j^l=g\left(\sum_{k=1}^n o_k^{l-1}w_k^l\right) +$$ + +This can be shown in the figure below. + +```{image} figs/neuron.png +:alt: var_circuit +:width: 350px +:align: center +``` + +The function $g$ is called activation function, and gives a non-linear output with respect to the input. Sigmoid function or ReLU (Rectified Linear Unit) is often used as activation function. + +To determine the function $f(x,\mathbf{w}^*)$, we need to optimize the parameter $\mathbf{w}$ and this process is called learning. For this reason, another function $L(\mathbf{w})$ to quantify the difference between the output $\tilde{y}$ and the target variable $y$, generally called "loss function" or "cost function", is necessary. + +$$ +L(\mathbf{w}) = \frac{1}{N}\sum_{i=1}^N L(f(x_i,\mathbf{w}),y_i) +$$ + +Here $N$ is the number of $(x_i, y_i)$ data points. We want to determine the parameter $\mathbf{w}^*$ that minimizes the loss function $L(\mathbf{w})$, and this can be done using the method called gradient descent. +In the gradient descent method, one attempts to calculate the partial derivative of the loss function, $\Delta_w L(\mathbf{w})$, for each parameter $w$ and update the parameter so that the loss function "decreases" as follows: + +$$ +w'=w-\epsilon\Delta_w L(\mathbf{w}), +$$ + +where $w$ and $w'$ are parameters before and after being updated, respectively. The $\epsilon\:(>0)$ is a parameter known as a learning rate, and this typically needs to be given by hand. + ++++ + +## Quantum Circuit Learning + +The QML algorithm based on variational quantum circuit generally takes the following steps to construct learning model, implement it into a quantum circuit and execute: ed to perform computation. + +1. Prepare the training data $\{(\mathbf{x}_i, y_i)\}$. The $\mathbf{x}_i$ is the input data vector and $y_i$ is the true value of the input data (e.g, teacher label) ($i$ stands for the index of training data sample). +2. Create a circuit $U_{\text{in}}(\mathbf{x})$ (called **feature map**) to encode the input data $\mathbf{x}$, then producing the input state $\ket{\psi_{\text{in}}(\mathbf{x}_i)} = U_{\text{in}}(\mathbf{x}_i)\ket{0}$, in which the $\mathbf{x}_i$ information is embedded. +3. Generate the output state $\ket{\psi_{\text{out}}(\mathbf{x}i,\boldsymbol{\theta})} = U(\boldsymbol{\theta})\ket{\psi{\text{in}}(\mathbf{x}_i)}$ by applying a parametrized unitary $U(\boldsymbol{\theta})$ (**variational form**) with parameter $\boldsymbol{\theta}$. +4. Measure some **observable** under the output state and get the measurement outcome $O$. For example, consider the expectation value of a Pauli $Z$ operator for the first qubit, $\langle Z_1\rangle = \expval{\psi_{\text{out}}}{Z_1}{\psi_{\text{out}}}$. +5. Introduce some function $F$ and obtain $F(O)$ as the model output $y(\mathbf{x}_i,\boldsymbol{\theta})$. +6. Define a **cost function** $L(\boldsymbol{\theta})$ to quantify the gap between the true value $y_i$ and the output $y(\mathbf{x}_i,\boldsymbol{\theta})$ and calculate it using a classical computer. +7. Update the parameter $\boldsymbol{\theta}$ so that the $L(\boldsymbol{\theta})$ gets smaller. +8. Repeat steps 3 through 7 to minimize the cost function and obtain the optimized parameter $\boldsymbol{\theta^*}$. +9. Obtain the **prediction of the model** as $y(\mathbf{x},\boldsymbol{\theta^*})$ after training. + +```{image} figs/var_circuit.png +:alt: var_circuit +:width: 700px +:align: center +``` + + +Let us implement the quantum machine learning algorithm by following these steps. First, the required libraries are imported. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +from IPython.display import clear_output +from sklearn.preprocessing import MinMaxScaler + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter, ParameterVector +from qiskit.circuit.library import TwoLocal, ZFeatureMap, ZZFeatureMap +from qiskit.primitives import Estimator, Sampler +from qiskit.quantum_info import SparsePauliOp +from qiskit_machine_learning.algorithms.classifiers import VQC +#from qiskit.utils import split_dataset_to_data_and_labels, map_label_to_class_name +from qiskit.algorithms.optimizers import SPSA, COBYLA +from qiskit_ibm_runtime import Session, Sampler as RuntimeSampler +from qiskit_ibm_runtime.accounts import AccountNotFoundError +``` + +## Simple Example + +When an input $\{x_i\}$ and the output $y_i=f(x_i)$ of a known function $f$ are provided as data, we attempt to approximately obtain the function $f$ from the data. For the function $f$, let us consider $f(x)=x^3$. + +### Preparation of Training Data + +First, let us prepare the training data. After randomly selecting `num_x_train` samples of data in the range between $x_{\text{min}}$ and $x_{\text{max}}$, a noise sampled from a normal distribution is added. The `nqubit` is the number of qubits and the `c_depth` is the number of layers in the variational form (explained later). + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +random_seed = 0 +rng = np.random.default_rng(random_seed) + +# Define the number of qubits, the circuit depth, the number of training samples, etc. +nqubit = 3 +nlayer = 5 +x_min = -1. +x_max = 1. +num_x_train = 30 +num_x_validation = 20 + +# Define the function +func_to_learn = lambda x: x ** 3 + +# Training data +x_train = rng.uniform(x_min, x_max, size=num_x_train) +y_train = func_to_learn(x_train) + +# Apply noise with a normal distribution to the function +mag_noise = 0.05 +y_train_noise = y_train + rng.normal(0., mag_noise, size=num_x_train) + +# Testing data +x_validation = rng.uniform(x_min, x_max, size=num_x_validation) +y_validation = func_to_learn(x_validation) + rng.normal(0., mag_noise, size=num_x_validation) +``` + +### Embedding Input Data + +Next, we create a circuit $U_{\text{in}}(x_i)$ to embed the input data $x_i$ into the initial state $\ket{0}^{\otimes n}$ (feature map). First, by following the reference{cite}`quantum_circuit_learning`, the circuit $U_{\text{in}}(x_i)$ is defined using rotation gates around the $Y$ axis, $R_j^Y(\theta)=e^{-i\theta Y_j/2}$, and those around the $Z$ axis, $R_j^Z(\theta)=e^{-i\theta Z_j/2}$: + +$$ +U_{\text{in}}(x_i) = \prod_j R_j^Z(\cos^{-1}(x^2))R_j^Y(\sin^{-1}(x)) +$$ + +By applying the $U_{\text{in}}(x_i)$ to the standard zero state, the input data $x_i$ is encoded into a quantum state $\ket{\psi_{\text{in}}(x_i)}=U_{\text{in}}(x_i)\ket{0}^{\otimes n}$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +u_in = QuantumCircuit(nqubit, name='U_in') +x = Parameter('x') + +for iq in range(nqubit): + # parameter.arcsin() returns arcsin(v) when the parameter is assigned a value v + u_in.ry(x.arcsin(), iq) + # Similarly for arccos + u_in.rz((x * x).arccos(), iq) + +u_in.bind_parameters({x: x_train[0]}).draw('mpl') +``` + +### Tranforming State using Variational Form + +#### Creating Ansatz Circuit $U(\boldsymbol{\theta})$ +Next, we will create the variational quantum circuit $U(\boldsymbol{\theta})$ to be optimized. This is done in three steps as follows. + +1. Place 2-qubit gates to create entanglement +2. Place single-qubit rotation gates +3. Create a variational quantum circuit $U(\boldsymbol{\theta})$ by alternating single- and 2-qubit gates from 1 and 2 + + +#### 2-Qubit Gate +We will use controlled-$Z$ gates ($CZ$) to entangle qubits, increasing the expressibility of the circuit model. + +#### Rotation Gate and $U(\boldsymbol{\theta})$ +Using entangling gates $U_{\text{ent}}$ of $CZ$ and single-qubit rotation gates on $j$-th qubit ($j \:(=1,2,\cdots n)$) in the $l$-th layer, + +$$ +U_{\text{rot}}(\theta_j^l) = R_j^Y(\theta_{j3}^l)R_j^Z(\theta_{j2}^l)R_j^Y(\theta_{j1}^l) +$$ + +the $U(\boldsymbol{\theta})$ is constructed. In this exercise, we use the $U_{\text{rot}}$ first, then the combination of $U_{\text{ent}}$ and U_{\text{rot}}$ $d$ times. Therefore, the $U(\boldsymbol{\theta})$ takes the form of + +$$ +U\left(\{\theta_j^l\}\right) = \prod_{l=1}^d\left(\left(\prod_{j=1}^n U_{\text{rot}}(\theta_j^l)\right) \cdot U_{\text{ent}}\right)\cdot\prod_{j=1}^n U_{\text{rot}}(\theta_j^0) +$$ + +The $U(\boldsymbol{\theta})$ contains $3n(d+1)$ parameters, and they are all randomly initialized within the range of $[0, 2\pi]$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +u_out = QuantumCircuit(nqubit, name='U_out') + +# Parameters with 0 length +theta = ParameterVector('θ', 0) + +# Fundtion to add new element to theta and return the last parameter +def new_theta(): + theta.resize(len(theta) + 1) + return theta[-1] + +for iq in range(nqubit): + u_out.ry(new_theta(), iq) + +for iq in range(nqubit): + u_out.rz(new_theta(), iq) + +for iq in range(nqubit): + u_out.ry(new_theta(), iq) + +for il in range(nlayer): + for iq in range(nqubit): + u_out.cz(iq, (iq + 1) % nqubit) + + for iq in range(nqubit): + u_out.ry(new_theta(), iq) + + for iq in range(nqubit): + u_out.rz(new_theta(), iq) + + for iq in range(nqubit): + u_out.ry(new_theta(), iq) + +print(f'{len(theta)} parameters') + +theta_vals = rng.uniform(0., 2. * np.pi, size=len(theta)) + +u_out.bind_parameters(dict(zip(theta, theta_vals))).draw('mpl') +``` + +### Measurement and Model Output + +As an output of the model (prediction value), we take the expectation value of Pauli $Z$ operator on the first qubit under the state $\ket{\psi_{\text{out}}(\mathbf{x},\boldsymbol{\theta})}=U(\boldsymbol{\theta})\ket{\psi_{\text{in}}(\mathbf{x})}$. +That means y(\mathbf{x},\boldsymbol{\theta}) = \langle Z_0(\mathbf{x},\boldsymbol{\theta}) \rangle = \expval{\psi_{\text{out}}(\mathbf{x},\boldsymbol{\theta})}{Z_0}{\psi_{\text{out}}(\mathbf{x},\boldsymbol{\theta})}$. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +model = QuantumCircuit(nqubit, name='model') + +model.compose(u_in, inplace=True) +model.compose(u_out, inplace=True) + +bind_params = dict(zip(theta, theta_vals)) +bind_params[x] = x_train[0] + +model.bind_parameters(bind_params).draw('mpl') +``` + +```{code-cell} ipython3 +--- +pycharm: + name: '#%% + + ' +--- +# Use Estimator class +estimator = Estimator() + +# Calculate y value from the given parameters and x value +def yvals(param_vals, x_vals=x_train): + circuits = list() + for x_val in x_vals: + circuits.append(model.bind_parameters({x: x_val})) + + # Observable = IIZ (the first qubit from right is 0-th qubit) + observable = SparsePauliOp('I' * (nqubit - 1) + 'Z') + + # shots is defined outside the function + job = estimator.run(circuits, [observable] * len(circuits), [param_vals] * len(circuits), shots=shots) + + return np.array(job.result().values) + +def objective_function(param_vals): + return np.sum(np.square(y_train_noise - yvals(param_vals))) + +def callback_function(param_vals): + # losses is defined outside the function + losses.append(objective_function(param_vals)) + + if len(losses) % 10 == 0: + print(f'COBYLA iteration {len(losses)}: cost={losses[-1]}') +``` + +The mean squared error of the model prediction $y(x_i, \theta)$ and the true values $y_i$ is used as the cost function $L$. + +Let us execute the circuit and check the results. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Maximum number of steps in COBYLA +maxiter = 50 +# Convergence threshold of COBYLA (the smaller the better approximation) +tol = 0.05 +# Number of shots in the backend +shots = 1000 + + +optimizer = COBYLA(maxiter=maxiter, tol=tol, callback=callback_function) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text +import os +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del objective_function +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +initial_params = rng.uniform(0., 2. * np.pi, size=len(theta)) + +losses = list() +min_result = optimizer.minimize(objective_function, initial_params) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + import pickle + + with open('data/qc_machine_learning_xcube.pkl', 'rb') as source: + min_result, losses = pickle.load(source) +``` + +Make a plot of cost functon values. + +```{code-cell} ipython3 +plt.plot(losses) +``` + ++++ {"jupyter": {"outputs_hidden": false}, "pycharm": {"name": "#%%\n"}} + +Check the output of the trained model with optimized parameters and the inputs taken uniformly between x_min and x_max. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +x_list = np.linspace(x_min, x_max, 100) + +y_pred = yvals(min_result.x, x_vals=x_list) + +# Results +plt.plot(x_train, y_train_noise, "o", label='Training Data (w/ Noise)') +plt.plot(x_list, func_to_learn(x_list), label='Original Function') +plt.plot(x_list, np.array(y_pred), label='Predicted Function') +plt.legend(); +``` + +Please check the results. You will see that the original function $f(x)=x^3$ is approximately derived from the training data with noise. + +In order to converge optimization steps quickly, the maximum number of calls (`maxiter`) is set to 50 and the tolerance for the accuracy (`tol`) is set to 0.05 for COBYLA optimizer. Check how the accuracy if the `maxiter` is raised or `tol` is lowered. Note however that if the `maxiter` is taken too large and the `tol` too small, the calculation will take a very long time. + ++++ + +## Application to Search for New Phenomena + +In the next exercise, we will be considering the search for new particles whose existence is predicted by **Supersymmetry** (SUSY), a new physics theory which goes beyond the fundamental theory of particle physics (**Standard Model**). + +The left figure below shows a gluon fusion process to create a Higgs boson $h$, which decays into two SUSY particles $\chi^+\chi^-$. The $\chi^+$ particle further decays, producing a final state consisting of $\ell^+\ell^-\nu\nu\chi^0\chi^0$ at the end. The right figure shows a well-known process in the Standard Model, in which a quark $q$ and an antiquark $\bar{q}$ interact and create a $W$-boson pair which decays into the final state of $\ell^+\ell^-\nu\nu$. + +```{image} figs/susy_bg.png +:alt: susy_bg +:width: 700px +:align: center +``` +(Figures courtesy of reference material{cite}`dl_susy`) + +Comparing the processes in the left and right figures, you can see that the only difference in the final state is whether the $\chi^0\chi^0$ is present or not. The $\chi^0$ particle is expected not to interact with detector, so (broadly speaking) the differences between these two processes lie only in the magnitude of energy that cannot be observed by detector. This generally makes it difficult to discover the left process in large backgrounds of the right process. We will see if we can distinguish between these two processes using quantum machine learning. + ++++ + +### Preparation of Training Data + +The dataset used for the training is the [SUSY data set](https://archive.ics.uci.edu/ml/datasets/SUSY) in the [machine learning repository](https://archive.ics.uci.edu/ml/index.php) provided by a group in the University of California, Irvine (UC Irvine). While we leave the details of the dataset to reference material{cite}`dl_susy`, the dataset includes simulated data of kinematic variables expected when the detector observes a specific SUSY particle production process and background process with similar characteristics to the SUSY signal. + +Selecting the input kinematic variables that are useful for search in machine learning task is important by itself. But here, for the sake of simplicity we use a set of variables that we already know are useful from the past experience. Below, the set of kinematic variables is chosen and the samples for the training and testing are prepared. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Read the variables from the file +df = pd.read_csv("data/SUSY_1K.csv", + names=('isSignal', 'lep1_pt', 'lep1_eta', 'lep1_phi', 'lep2_pt', 'lep2_eta', + 'lep2_phi', 'miss_ene', 'miss_phi', 'MET_rel', 'axial_MET', 'M_R', 'M_TR_2', + 'R', 'MT2', 'S_R', 'M_Delta_R', 'dPhi_r_b', 'cos_theta_r1')) + +# Number of variables to use in learning +feature_dim = 3 # dimension of each data point + +# Set of variables when using 3, 5, or 7 variables +if feature_dim == 3: + selected_features = ['lep1_pt', 'lep2_pt', 'miss_ene'] +elif feature_dim == 5: + selected_features = ['lep1_pt', 'lep2_pt', 'miss_ene', 'M_TR_2', 'M_Delta_R'] +elif feature_dim == 7: + selected_features = ['lep1_pt', 'lep1_eta', 'lep2_pt', 'lep2_eta', 'miss_ene', 'M_TR_2', 'M_Delta_R'] + +# Phenomenon used in learning: "training" samples are used for training, "testing" samples are used for testing +train_size = 20 +test_size = 20 + +df_sig = df.loc[df.isSignal==1, selected_features] +df_bkg = df.loc[df.isSignal==0, selected_features] + +# Generate samples +df_sig_train = df_sig.values[:train_size] +df_bkg_train = df_bkg.values[:train_size] +df_sig_test = df_sig.values[train_size:train_size + test_size] +df_bkg_test = df_bkg.values[train_size:train_size + test_size] +# The first (last) train_size events correspond to signal (background) events that (do not) contain SUSY particles +train_data = np.concatenate([df_sig_train, df_bkg_train]) +# The first (last) test_size events correspond to signal (background) events that (do not) contain SUSY particles +test_data = np.concatenate([df_sig_test, df_bkg_test]) + +# one-hot vector +train_label_one_hot = np.zeros((train_size * 2, 2)) +train_label_one_hot[:train_size, 0] = 1 +train_label_one_hot[train_size:, 1] = 1 + +test_label_one_hot = np.zeros((test_size * 2, 2)) +test_label_one_hot[:test_size, 0] = 1 +test_label_one_hot[test_size:, 1] = 1 + +#datapoints, class_to_label = split_dataset_to_data_and_labels(test_input) +#datapoints_tr, class_to_label_tr = split_dataset_to_data_and_labels(training_input) + +mms = MinMaxScaler((-1, 1)) +norm_train_data = mms.fit_transform(train_data) +norm_test_data = mms.transform(test_data) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +### State Preparation + +Next step is to create the feature map $U_{\text{in}}(\mathbf{x}_i)$. We use the following unitaries as in the reference{cite}`quantum_svm`: + +$$ +U_{\phi_{\{k\}}}(\mathbf{x}_i)=\exp\left(i\phi_{\{k\}}(\mathbf{x}_i)Z_k\right) +$$ + +$$ +U_{\phi_{\{l,m\}}}(\mathbf{x}_i)=\exp\left(i\phi_{\{l,m\}}(\mathbf{x}_i)Z_lZ_m\right) +$$ + +where $k$, $l$ and $m$ are the indices of elements of an input vector $\mathbf{x}_i$. +These feature maps are called Z feature map and ZZ feature map, respectively. Here $\phi_{\{k\}}$ and $\phi{\{l,m\}}$ are defined as $\phi_{\{k\}}(\mathbf{x}_i)=x_i^{(k)}$ and $\phi{\{l,m\}}(\mathbf{x}_i)=(\pi-x_i^{(l)})(\pi-x_i^{(m)})$, respectively, +with $x_i^{k(l,m)}$ being the $k$ ($l$, $m$)-th element of $\mathbf{x}_i$, and are used to encode input data. +The Z feature map embeds each element of the input data directly into qubits (i.e, $\phi_{\{k\}}(\mathbf{x}_i)$ uses one qubit per input). The ZZ feature map often includes Z feature map in practice, therefore the $\phi{\{l,m\}}(\mathbf{x}_i)$ embeds the input data in a cyclic manner by using the same number of qubits as used for the $\phi_{\{k\}}(\mathbf{x}_i)$. +Since the ZZ feature map uses 2-qubit entangling gates, it is generally considered to be difficult to simulate the encoding of the ZZ feature map classically. + +By combining U_\phi(\mathbf{x}_i)$ with Hadamard gates, the Z feature map is given as + +$$ +U_{\text{in}}(\mathbf{x}_i) = U_{\phi}(\mathbf{x}_i)H^{\otimes n},\:\:U_{\phi}(\mathbf{x}_i) = \exp\left(i\sum_{k=1}^nx_i^{(k)}Z_k\right) +$$ + +The ZZ feature map is + +$$ +U_{\text{in}}(\mathbf{x}_i) = U_{\phi}(\mathbf{x}_i)H^{\otimes n},\:\:U_{\phi}(\mathbf{x}_i) = \exp\left(i\sum_{k=1}^n(\pi-x_i^{(k)})(\pi-x_i^{(k\%n+1)})Z_kZ_{k\%n+1}\right)\exp\left(i\sum_{k=1}^nx_i^{(k)}Z_k\right) +$$ + +Repeating the $U_{\phi}(\mathbf{x}_i)H^{\otimes n}$ multiple times will create more complex feature map, as seen above. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +#feature_map = ZFeatureMap(feature_dimension=feature_dim, reps=1) +feature_map = ZZFeatureMap(feature_dimension=feature_dim, reps=1, entanglement='circular') +feature_map.decompose().draw('mpl') +``` + +### State Transformation with Variational Form + +A variational form $U(\boldsymbol{\theta})$ is similar to the circuit used above for a simple example, but it uses the following rotation gates. + +$$ +U_{\text{rot}}(\theta_j^l) = R_j^Z(\theta_{j2}^l)R_j^Y(\theta_{j1}^l) +$$ + +In the example above, we construted the circuit of $U(\boldsymbol{\theta})$を ourselves, but here we will use $U(\boldsymbol{\theta})$ implemented in Qiskit as a standard API. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +ansatz = TwoLocal(num_qubits=feature_dim, rotation_blocks=['ry', 'rz'], entanglement_blocks='cz', entanglement='circular', reps=3) +#ansatz = TwoLocal(num_qubits=feature_dim, rotation_blocks=['ry'], entanglement_blocks='cz', entanglement='circular', reps=3) +ansatz.decompose().draw('mpl') +``` + +### Measurement and Model Output + +The measurement of quantum states, parameter optimization and the definition of the cost function are also largely the same as those used for the example above. Since we will use VQC class in Qiskit API, the program itself is greatly simplified. + +In vhe VQC class, all the steps described above, i.e, creation of feature map and variational form, substituting input data and parameter values, measuring the generated state, calculating the objective function and updating the parameters, are done internally. The measurement is performed using the Sampler class. The function of this class is similar to the Estimator class, but there is a difference: the latter computes the expectation value of an observable, but the former calculates the probability distribution of bit-strings obtained by measuring all the qubits in $Z$ basis. The VQC uses htis distribution for the classification task. What we deal here is two class classification, so the probability of measuring 0 on the first qubit for each input event corresponds to the probability of this event to be signal (model prediction). + + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [remove-output] +--- +# Use Sampler +sampler = Sampler() + +# In case of using real hardware +# instance = 'ibm-q/open/main' + +# try: +# service = QiskitRuntimeService(channel='ibm_quantum', instance=instance) +# except AccountNotFoundError: +# service = QiskitRuntimeService(channel='ibm_quantum', token='__paste_your_token_here__', +# instance=instance) + +# backend_name = 'ibm_washington' +# session = Session(service=service, backend=backend_name) + +# sampler = RuntimeSampler(session=session) + +maxiter = 300 + +optimizer = COBYLA(maxiter=maxiter, disp=True) + +objective_func_vals = [] +# Draw the value of objective function every time when the fit() method is called +def callback_graph(weights, obj_func_eval): + clear_output(wait=True) + objective_func_vals.append(obj_func_eval) + #print('obj_func_eval =',obj_func_eval) + + plt.title("Objective function value against iteration") + plt.xlabel("Iteration") + plt.ylabel("Objective function value") + plt.plot(objective_func_vals) + plt.show() + +vqc = VQC(num_qubits=feature_dim, + feature_map=feature_map, + ansatz=ansatz, + loss="cross_entropy", + optimizer=optimizer, + callback=callback_graph, + sampler=sampler) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del objective_func_vals +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +vqc.fit(norm_train_data, train_label_one_hot) + +# In case of using real hardware (with RuntimeSampler) +# session.close() +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text +with open('data/vqc_machine_learning_susycost.pkl', 'rb') as source: + fig = pickle.load(source) + +with open('data/vqc_machine_learning_susyresult.pkl', 'rb') as source: + vqc._fit_result = pickle.load(source) + +print(''' Return from subroutine COBYLA because the MAXFUN limit has been reached. + + NFVALS = 300 F = 7.527796E-01 MAXCV = 0.000000E+00 + X = 2.769828E+00 2.690659E+00 2.209438E+00 -6.544334E-01 1.791658E+00 + 5.891710E-01 -4.081896E-02 5.725357E-01 1.662590E+00 -5.402978E-01 + 2.029238E+00 1.562530E-01 1.229339E+00 1.596802E+00 2.692169E-01 + -5.828812E-01 1.891610E+00 7.527701E-01 -6.604400E-01 1.587293E+00 + 1.194641E+00 1.505717E-01''') +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +train_score = vqc.score(norm_train_data, train_label_one_hot) +test_score = vqc.score(norm_test_data, test_label_one_hot) + +print(f'--- Classification Train score: {train_score} ---') +print(f'--- Classification Test score: {test_score} ---') +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +What do you think about these results? If you are familiar with machine learning, you would probably say that the results do not look good. The training looks good, that is, the classification between the signal and background is successful, but the performance is worse for the testing sample. This behavior often appears when the *overfitting* occurs in the training. The overfitting typically occurs when the number of trainable parameters is significantly more than the training data size. + +As an exercise, try to increase the number of data samples to 50 or 100 and check the results (the processing time will increase proportionally to the number of data samples). diff --git a/source/en/vqe.md b/source/en/vqe.md new file mode 100644 index 00000000..cad57158 --- /dev/null +++ b/source/en/vqe.md @@ -0,0 +1,793 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# Variational Principle and Variational Quantum Eigensolver + ++++ + +In this exercise, we will learn the basic concepts of variational method and the computation based on variational quantum algorithm. In particular, we will focus on **quantum-classical hybrid** approach of variational quantum algorithm where quantum and classical computations are combined. As a concrete implementation of such hybrid algorithm, we discuss an algorithm called **variational quantum eigensolver**, that enables us to approximately calculate eigenvalues of a physical system. + +```{contents} Contents +--- +local: true +--- +``` + +$\newcommand{\ket}[1]{| #1 \rangle}$ +$\newcommand{\bra}[1]{\langle #1 |}$ +$\newcommand{\braket}[2]{\langle #1 | #2 \rangle}$ +$\newcommand{\expval}[3]{\langle #1 | #2 | #3 \rangle}$ + ++++ + +## Introduction +For physical systems described by a Hermitian matrix, finding the smallest eigenvalue of the matrix is an important technique for many applications. For example, in chemistry calculation the smallest eigenvalue of a Hamiltonian that characterizes the system of, e.g, a molecule is the ground state energy of the molecule. A method called **"Quantum Phase Estimation"** (QPE) can be used to find the smallest eigenvalue (see this {doc}`exercise`), but it is generally known that the QPE requires a deep quantum circuit to solve pratical problems, preventing it from using it on NISQ machines. Therefore, the **Variational Quantum Eigensolver** (VQE) was proposed insteadd to estimate the ground state energy of a molecule with much shallower circuits{cite}`vqe`. + +First, let's formally express the relation that forms the basis of VQE. Given a Hermitian matrix $H$ with an unknown minimum eigenvalue $\lambda_{min}$ associated with the eigenstate $\ket{\psi_{min}}$, VQE allows us to determine an approximated value $\lambda_{\theta}$ of the lowest energy bound $\lambda_{min}$ of the system. + +In other words, it corresponds to determining the smallest $\lambda_{\theta}$ value that satisfies the following: + +$$ +\lambda_{min} \le \lambda_{\theta} \equiv \expval{ \psi(\theta)}{H}{\psi(\theta) } +$$ + +where $\ket{\psi(\theta)}$ is an eigenstate associated with the eigenvalue \lambda_{\theta}$, and $\theta$ is a parameter. The idea is to obtain the state $\ket{\psi(\theta)} \equiv U(\theta)\ket{\psi}$ that approximates $\ket{\psi_{min}}$ by applying a parameterized unitary $U(\theta)$ to a certain initial state $\ket{\psi}$. The optimized value of the parameter $\theta$ is determined by iterating classical calculation such that the expectation value $\expval{\psi(\theta)}{H}{\psi(\theta)}$ is minimized. + ++++ + +## Variational Method in Quantum Mechanics + +### Background + +VQE is based on **variational method** in quantum mechanics. To better understand the variational method, a fundamental mathematical background is provided first. + +An eigenvector $\ket{\psi_i}$ of a matrix $A$ and its eigenvalue $\lambda_i$ satisfies $A \ket{\psi_i} = \lambda_i \ket{\psi_i}$. When the $H$ is an Hermitian ($H = H^{\dagger}$), the eigenvalue of $H$ is a real number ($\lambda_i = \lambda_i^*$) according to the spectral theorem. As any experimentally measured quantity is real number, we usually consider a Hermitian matrix to form the Hamiltonian of a physical system. Moreover, $H$ may be expressed as follows: + +$$ +H = \sum_{i = 1}^{N} \lambda_i \ket{\psi_i} \bra{ \psi_i } +$$ + +where $\lambda_i$ is the eigenvalue associated with eigenvector $\ket{\psi_i}$. Since the expectation value of an observable $H$ for a quantum state $\ket{\psi}$ is given by + +$$ +\langle H \rangle_{\psi} \equiv \expval{ \psi }{ H }{ \psi }, +$$ + +substituting the $H$ above into this equation results in the following. + +$$ +\begin{aligned} +\langle H \rangle_{\psi} = \expval{ \psi }{ H }{ \psi } &= \bra{ \psi } \left(\sum_{i = 1}^{N} \lambda_i \ket{\psi_i} \bra{ \psi_i }\right) \ket{\psi}\\ +&= \sum_{i = 1}^{N} \lambda_i \braket{ \psi }{ \psi_i} \braket{ \psi_i }{ \psi} \\ +&= \sum_{i = 1}^{N} \lambda_i | \braket{ \psi_i }{ \psi} |^2 +\end{aligned} +$$ + +The last equation shows that the expectation value of $H$ in a given state $\ket{\psi}$ can be expressed as a linear combination of (the squared absolute value of) the inner products of the eigenvector $\ket{\psi_i}$ and $\ket{\psi}$, weighted by the eigenvalue $\lambda_i$. Since $| \braket{ \psi_i }{ \psi} |^2 \ge 0$, it is obvious that the following holds: + +$$ +\lambda_{min} \le \langle H \rangle_{\psi} = \expval{ \psi }{ H }{ \psi } = \sum_{i = 1}^{N} \lambda_i | \braket{ \psi_i }{ \psi} |^2 +$$ + +The above equation is known as **variational method** (also referred to as **variational principle**). It shows that if we can take an *appropriate* wavefunction, the smallest eigenvalue can be apprroximately obtained as the lower bound to the expectation value of the Hamiltonian $H$ (though how we can take such wavefunction is unknown at this point). Moreover, the expectation value of the eigenstate $\ket{\psi_{min}}$ is given by $\expval{ \psi_{min}}{H}{\psi_{min}} = \expval{ \psi_{min}}{\lambda_{min}}{\psi_{min}} = \lambda_{min}$. + +### Approximation of Ground States +When the Hamiltonian of a system is described by the Hermitian matrix $H$, the ground state energy of that system is the smallest eigenvalue associated with $H$. Selecting an arbitrary wavefunction $\ket{\psi}$ (called an **ansatz**) as an initial guess of $\ket{\psi_{min}}$, we calculate the expectation value of the Hamiltonian $\langle H \rangle_{\psi}$ under the state. The key to the variational method lies in iteratively performing calculations while updating the wavefunction to make the expectation value smaller, thereby approaching the ground state energy of the Hamiltonian. + ++++ + +(vqa)= +## Variational Quantum Algorithm + +First, we will look at **Variational Quantum Algorithm** (VQA), which the VQE is based on. + ++++ + +### Variational Quantum Circuit +To implement the variational method on a quantum computer, we need a mechanism to update the ansatz. As we know, quantum gates can be used to update quantum states. VQE also uses quantum gates, but does so through the use of a parameterized quantum circuit with a certain structure (called a **variational quantum circuit**). Such a circuit is sometimes called a **variational form**, and the entire circuit is often represented as a unitary operation $U(\theta)$ ($\theta$ is a parameter, often becomes a vector with multiple parameters). + +When a variational form is applied to an initial state $\ket{\psi}$ (such as the standard state $\ket{0}$), it generates an output state $\ket{\psi(\theta)} \equiv U(\theta)\ket{\psi}$. The VQE attemps to optimize parameter $\theta$ for $\ket{\psi(\theta)}$ such that the expectation value $\expval{ \psi(\theta)}{H}{\psi(\theta)}$ gets close to $\lambda_{min}$. This parameter optimization is envisioned as being performed using classical computation. In this sense, VQE is a typical **quantum-classical hybrid algorithm**. + +When constructing variational forms, it is possible to choose variational forms with specific structures based on the domain knowledge of the question to be answered. There are also variational forms that are not domain-specific and can be applied to a wide range of problems (such as $R_X$, $R_Y$, and other rotation gates). Later on, we will look at an assignment in which you will apply VQE to a high energy physics experiment. That assignment implements variational forms that use $R_Y$ and controlled $Z$ gates. + ++++ + +### Simple Variational Form +When constructing a variational form, we have to consider the balance between two competing objectives. If the number of parameters is increased, our $n$-qubit variational form might be able to generate any possible state $\ket{\psi}$ with $2^{n+1}-2$ real degrees of freedom. However, in order to optimize the parameters, we would like the variational form to use as few as possible. Increasing the number of gates that use rotation angles as parameters generally makes the computation more susceptible to noise. From that point of view, one could desire to generate states using as few parameters (and gates) as possible. + +Consider the case where $n=1$. The Qiskit $U$ gate (not to confuse with $U(\theta)$) notation used above!) takes three parameters of $\theta$、$\phi$ and $\lambda$, and represents the following transformation. + +$$ +U(\theta, \phi, \lambda) = \begin{pmatrix}\cos\frac{\theta}{2} & -e^{i\lambda}\sin\frac{\theta}{2} \\ e^{i\phi}\sin\frac{\theta}{2} & e^{i\lambda + i\phi}\cos\frac{\theta}{2} \end{pmatrix} +$$ + +If the initial state of the variational form is taken to be $\ket{0}$, only the first column of the above matrix acts to transform the state, and the two parameters $\theta$ and $\phi$ of the matrix allow us to produce an arbitrary single qubit state. Therefore, this variational form is known to be **universal**. However, this universality brings a subtle issue: when one tries to use this variational form to generate some quantum states and calculate expectation values of a given Hamiltonian, the generated states could be any states other than the eigenstates of the Hamiltonian. In other words, whether the VQE can find efficiently the smallest eigenvalue or not depends crucially on how we can appropriately optimize the parameters while avoiding such vast majority of unwanted quantum states. + ++++ + +### Parameter Optimization +Once you select a parameterized variational form, then you need to optimize the parameters based on the variational method to minimize the expectation value of the target Hamiltonian. The parameter optimization process involves various challenges. For example, quantum hardware has a variety of noises, so there is no guarantee that measuring the energy in this state will return the correct answer. This may result in deviation from the correct objective function value, thus preventing the parameters from being updated properly. Additionally, depending on optimization methods (**"optimizers"**), the number of objective function evaluations generally increases with the number of parameters, making it even more susceptible to noise. An appropriate optimizer must therefore be selected by considering the requirements of the application. + +The most typical optimization strategy is based on **gradient descent**, in which each parameter is updated in the direction yielding the largest decrease in energy. Because the gradient is calculated for each parameter, the greater the number of parameters to optimize the greater the number of objective function evaluations. This allows the algorithm to quickly find a local minimum in the search space. However, this optimization strategy often gets stuck at local minima, preventing the algorithm from reaching the global minimum. The gradient descent is intuitive and easy to understand, but it is considered difficult to perform accurate calculation of gradient descent on a present NISQ computer (the gradient-based optimization is introduced later when implementing VQE). + +As an optimizer suitable for a noisy quantum computer, the *Simultaneous Perturbation Stochastic Approximation optimizer* (**SPSA**){cite}`bhatnagar_optimization` has been proposed. The SPSA attempts to approximate the gradient of the objective function with only two measurements. In addition, the SPSA concurrently changes all the parameters in a random fashion, in contrast to gradient descent where each parameter is changed independently. Because of this, the SPSA is generally recommended as the optimizer when utilizing VQE. + +When evaluating an objective function on a noise-less quantum computer (e.g, when executing on a statevector simulator), a wide variety of optimizers, e.g, those included in Python's SciPy package, may be used. In this exercise, we will use one of the optimizers supported in Qiskit, namely the *Constrained Optimization by Linear Approximation optimizer* (**COBYLA**). The COBYLA performs the evaluation of objective function only once (that is, independently of the number of parameters in a circuit). Therefore, if one wants to minimize the number of evaluations under a noise-free situation, the COBYLA tends to be recommended. However, since the performance of optimizer depends crucially on implementation of the VQE algorithm and execution environment, it is important to study it beforehand and select the most appropriate optimizer for problem in hand. + ++++ + +### Example of Variational Form +We now try to perform parameter optimization using a single-qubit variational form composed of $U$ gate. Since a single-qubit state is determined (up to global phase) by using the expectation values of $\langle X \rangle$, $\langle Y \rangle$ and $\langle Z \rangle$ for the observables $X$, $Y$ and $Z$, respectively, one may optimize $\theta$ and $\phi$ so that the expectation values $\langle X \rangle_{\theta, \phi}$, $\langle Y \rangle_{\theta, \phi}$ and $\langle Z \rangle_{\theta, \phi}$ for $X$, $Y$ and $Z$ under the state $\ket{\psi(\theta, \phi)}$ become equal to the corresponding expectation values $\langle X \rangle_0$, $\langle Y \rangle_0$ and $\langle Z \rangle_0$ under $\ket{\psi_0}$. + +Therefore, the problem is to minimize the following objective function. + +$$ +L(\theta, \phi) = [\langle X \rangle_{\theta, \phi} - \langle X \rangle_0]^2 + [\langle Y \rangle_{\theta, \phi} - \langle Y \rangle_0]^2 + [\langle Z \rangle_{\theta, \phi} - \langle Z \rangle_0]^2 +$$ + +```{image} figs/vqe_u3.png +:alt: vqe_u3 +:width: 400px +:align: center +``` + +[^actually_exact]: Since $U(\theta, \phi, 0)$ is universal for a single qubit, it can in principle be exact, not approximation. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +import numpy as np +import matplotlib.pyplot as plt +from qiskit import QuantumCircuit, transpile +from qiskit.circuit import Parameter, ParameterVector +from qiskit.primitives import BackendEstimator +from qiskit.quantum_info import Statevector, Operator, SparsePauliOp +from qiskit.algorithms.optimizers import SPSA, COBYLA +from qiskit_aer import AerSimulator +``` + +First, the function to randomly generate the target state vector and the function to calculate the expectation values of $X$, $Y$ and $Z$ from the state vector are defined. The state vector is represented using Statevector class in Qiskit, and SparsePauliOp class is used for Pauli operators. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +rng = np.random.default_rng(999999) + +# Function to create a random state vector with nq qubits. +def random_statevector(nq): + # Randomly generate 2^nq complex numbers + data = rng.random(2 ** nq) + 1.j * rng.random(2 ** nq) + # Normalization + data /= np.sqrt(np.sum(np.square(np.abs(data)))) + + return Statevector(data) + +# Example: U(π/3, π/6, 0)|0> +statevector = Statevector(np.array([np.cos(np.pi / 6.), np.exp(1.j * np.pi / 6.) * np.sin(np.pi / 6.)])) +for pauli in ['X', 'Y', 'Z']: + op = SparsePauliOp(pauli) + print(f'<{pauli}> = {statevector.expectation_value(op).real}') +``` + +Next, define a variational form. Here the angles of $U$ gate are parameterized using Parameter objects in Qiskit. The Parameter object can be substituted for real values that will be put later. + +```{code-cell} ipython3 +:tags: [remove-output] + +theta = Parameter('θ') +phi = Parameter('φ') + +ansatz_1q = QuantumCircuit(1) +ansatz_1q.u(theta, phi, 0., 0) +``` + +We use `bind_parameters` method of the circuit to assign values to Parameter object. + +```{code-cell} ipython3 +# Parameter value is unknown +ansatz_1q.draw('mpl') +``` + +```{code-cell} ipython3 +# Assign π/3 and π/6 to theta and phi +ansatz_1q.bind_parameters({theta: np.pi / 3., phi: np.pi / 6.}).draw('mpl') +``` + +Define the circuit to measure the expectation values of $X$, $Y$ and $Z$ under the state of the variational form. + +```{code-cell} ipython3 +circuits = dict() + +# Basis change with H gate for +circuits['X'] = ansatz_1q.copy() +circuits['X'].h(0) +circuits['X'].measure_all() + +# Basis change with Sdg and H gates for +circuits['Y'] = ansatz_1q.copy() +circuits['Y'].sdg(0) +circuits['Y'].h(0) +circuits['Y'].measure_all() + +# No basis change for +circuits['Z'] = ansatz_1q.copy() +circuits['Z'].measure_all() +``` + +Now define the function to execute each circuit with `run()` method of the backend and calculate the expectation values from the results. + +```{code-cell} ipython3 +backend = AerSimulator() + +def circuit_expval(circuit, param_vals): + bound_circuit = circuit.bind_parameters({theta: param_vals[0], phi: param_vals[1]}) + + bound_circuit_tr = transpile(bound_circuit, backend=backend) + # shots is defined outside the function + job = backend.run(bound_circuit_tr, shots=shots) + counts = job.result().get_counts() + + return (counts.get('0', 0) - counts.get('1', 0)) / shots + +# Example: U(π/3, π/6, 0)|0> +shots = 10000 +param_vals = [np.pi / 3., np.pi / 6.] +for pauli in ['X', 'Y', 'Z']: + print(f'<{pauli}> = {circuit_expval(circuits[pauli], param_vals)}') +``` + +The objective function to be minimized is defined here. + +```{code-cell} ipython3 +def objective_function(param_vals): + loss = 0. + for pauli in ['X', 'Y', 'Z']: + # target_state_1q is defined outside the function + op = SparsePauliOp(pauli) + target = target_state_1q.expectation_value(op).real + current = circuit_expval(circuits[pauli], param_vals) + loss += (target - current) ** 2 + + return loss + +# Called every optimization step. Store objective function values in a list. +def callback_function(param_vals): + # losses is defined outside the function + losses.append(objective_function(param_vals)) +``` + +The function to calculate the fidelity $|\langle \psi_0 | \psi(\theta, \phi) \rangle|^2$ between the initial state and the target state after optimization is defined. If the optimization is perfect, this function value is exactly one. + +```{code-cell} ipython3 +def fidelity(ansatz, param_vals, target_state): + # Can get the list of circuit parameters with circuit.parameters + parameters = ansatz.parameters + + param_binding = dict(zip(parameters, param_vals)) + opt_ansatz = ansatz.bind_parameters(param_binding) + + # Statevector can be generated from quantum circuit (the final state obtaind by applying the circuit to |0>) + circuit_state = Statevector(opt_ansatz) + + return np.square(np.abs(target_state.inner(circuit_state))) +``` + +Finally, an instance of the COBYLA optimizer is created and used to run the algorithm. + +```{code-cell} ipython3 +# Maximum number of steps in COBYLA +maxiter = 500 +# Convergence threshold of COBYLA (the smaller the better approximation) +tol = 0.0001 +# Number of shots in the backend +shots = 1000 + +# Intance +optimizer = COBYLA(maxiter=maxiter, tol=tol, callback=callback_function) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +import os +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del optimizer +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# Target state +target_state_1q = random_statevector(1) + +# Choose theta and phi randomly within [0, π) and [0, 2π), respectively +init = [rng.uniform(0., np.pi), rng.uniform(0., 2. * np.pi)] + +# Perform optimization +losses = list() +min_result = optimizer.minimize(objective_function, x0=init) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +import pickle +if os.getenv('JUPYTERBOOK_BUILD') == '1': + with open('data/vqe_results_1q.pkl', 'rb') as source: + min_result, losses = pickle.load(source) +``` + +Make a plot of the objective function values during the optimization process. + +```{code-cell} ipython3 +plt.plot(losses); +``` + +```{raw-cell} +From the returned value `min_result` of `optimizer.minimize()`, we can obtain various information about the optimization process such as the number of calls of objective function and the number of steps required to reach the minimum. In particular, we calculate the fidelity using the optimized parameters from `min_result.x`. +``` + +```{code-cell} ipython3 +fidelity(ansatz_1q, min_result.x, target_state_1q) +``` + +```{raw-cell} +Since the number of shots in finite, the optimized parameters do not exactly coincide with the exact solutions due to statistical uncertainty. Check the level of agreement by changing the number of shots and steps. +``` + +#### Using Estimator + +For variational quantum algorithm including VQE, the optimization loop in which the parameters in variational form are substituted and the expectation valus of observables are calculated appear frequently. Therefore, it is recommended to use Estimator class, in which this process is automated and various error mitigation techniques can be adopted (though not used here). In the exercise below, we will use BackendEstimator to perform calculation using a specific backend. + +```{code-cell} ipython3 +# Create instance of BackendEstimator +estimator = BackendEstimator(backend) + +# Define observable using SparsePauliOp objects +observables = [SparsePauliOp('X'), SparsePauliOp('Y'), SparsePauliOp('Z')] + +param_vals = [np.pi / 3., np.pi / 6.] + +# Pass variational form, observable and parameter values to run() +# Since there are three observables, 3 ansatz_1q and 3 param_values are prepared accordingly +job = estimator.run([ansatz_1q] * 3, observables, [param_vals] * 3, shots=10000) +result = job.result() +result.values +``` + +Define objective function using Estimator. + +```{code-cell} ipython3 +observables_1q = [SparsePauliOp('X'), SparsePauliOp('Y'), SparsePauliOp('Z')] + +def objective_function_estimator(param_vals): + target = np.array(list(target_state_1q.expectation_value(op).real for op in observables_1q)) + + job = estimator.run([ansatz_1q] * len(observables_1q), observables_1q, [param_vals] * len(observables_1q), shots=shots) + current = np.array(job.result().values) + + return np.sum(np.square(target - current)) + +def callback_function_estimator(param_vals): + # losses is defined outside the function + losses.append(objective_function_estimator(param_vals)) +``` + +Optimize the objective function defined above. + +```{code-cell} ipython3 +# Maximum number of steps in COBYLA +maxiter = 500 +# Convergence threshold of COBYLA (the smaller the better approximation) +tol = 0.0001 +# Number of shots in the backend +shots = 1000 + +# Instance +optimizer = COBYLA(maxiter=maxiter, tol=tol, callback=callback_function_estimator) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del optimizer +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +# Target state +target_state_1q = random_statevector(1) + +# Choose theta and phi randomly within [0, π) and [0, 2π), respectively +init = [rng.uniform(0., np.pi), rng.uniform(0., 2. * np.pi)] + +# Perform optimization +losses = list() +min_result = optimizer.minimize(objective_function_estimator, x0=init) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + with open('data/vqe_result_1q_estimator.pkl', 'rb') as source: + min_result = pickle.load(source) +``` + +```{code-cell} ipython3 +fidelity(ansatz_1q, min_result.x, target_state_1q) +``` + +### Introducing Entanglement + +Next, let us extend this problem to 2-qubit problem. Two-qubit pure state has degrees of freedom of 6 real values, but here we consider 15 observables to determine the most generic two-qubit state, with the expectation values of + +$$ +\langle O_1 O_2 \rangle \quad (O_1, O_2 = I, X, Y, Z; O_1 O_2 \neq II). +$$ + +Here $I$ is an identity operator. + +The functions `random_statevector` and `pauli_expval` regarding the target states can be used as they are. First, consider a very simple variational form of $U$ gates attached on 2 qubits each, and define the objective function. + +```{code-cell} ipython3 +:tags: [remove-output] + +# Create 4-element ParameterVector because the number of parameters is 4. +params = ParameterVector('params', 4) + +ansatz_2q = QuantumCircuit(2) +ansatz_2q.u(params[0], params[1], 0., 0) +ansatz_2q.u(params[2], params[3], 0., 1) +``` + +```{code-cell} ipython3 +paulis_1q = ['I', 'X', 'Y', 'Z'] +paulis_2q = list(f'{op1}{op2}' for op1 in paulis_1q for op2 in paulis_1q if (op1, op2) != ('I', 'I')) +observables_2q = list(SparsePauliOp(pauli) for pauli in paulis_2q) + +def objective_function_2q(param_vals): + # target_state_2q is defined outside the function + target = np.array(list(target_state_2q.expectation_value(op).real for op in observables_2q)) + + job = estimator.run([ansatz_2q] * len(observables_2q), observables_2q, [param_vals] * len(observables_2q), shots=shots) + current = np.array(job.result().values) + + return np.sum(np.square(target - current)) + +def callback_function_2q(param_vals): + # losses is defined outside the function + losses.append(objective_function_2q(param_vals)) +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +# Maximum number of steps in COBYLA +maxiter = 500 +# Convergence threshold of COBYLA (the smaller the better approximation) +tol = 0.0001 +# Number of shots in the backend +shots = 1000 + +# Instance +optimizer = COBYLA(maxiter=maxiter, tol=tol, callback=callback_function_2q) + +# Target state +target_state_2q = random_statevector(2) + +# Parameter initial values +init = rng.uniform(0., 2. * np.pi, size=4) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del optimizer +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +tags: [raises-exception, remove-output] +--- +# Perform optimization +losses = list() +min_result = optimizer.minimize(objective_function_2q, x0=init) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + with open('data/vqe_result_2q.pkl', 'rb') as source: + min_result = pickle.load(source) +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +fidelity(ansatz_2q, min_result.x, target_state_2q) +``` + +You'll see that the results are not good compared to the case of single qubit. How can we improve? + ++++ + +**One solution: Introduction of entanglement in the variational form** + +```python +ansatz_2q = QuantumCircuit(2) +ansatz_2q.u(params[0], params[1], 0., 0) +ansatz_2q.u(params[2], params[3], 0., 1) +ansatz_2q.cx(0, 1) +``` + +Let us see what happens. + ++++ + +A generic 2-qubit state is usually entangled, so one could natually expect the accuracy to be improved by introucing 2-qubit gates in the variational form. For example, we can see this most clearly when we want to produce the Bell state ([Confirming the violation of the CHSH inequality](https://utokyo-icepp.github.io/qc-workbook/chsh_inequality.html#id14)). + +If you replace + +```python +target_state_2q = random_statevector(2) +``` + +with + +```python +target_state_2q = Statevector(np.array([1., 0., 0., 1.], dtype=complex) / np.sqrt(2.)) +``` + +and execute, what do you get? You will observe a large difference with and without adding entanglment. + +Play by extending the circuit to 3 qubits, e.g, by targeting the GHZ state ([Creating simple circuit from scratch](https://utokyo-icepp.github.io/qc-workbook/circuit_from_scratch.html#ghz)) + +```python +target_state_3q = Statevector(np.array([1.] + [0.] * 6 + [1.], dtype=complex) / np.sqrt(2.)) +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +(vqe)= +## Variational Quantum Eigensolver + +Now, let us try to implement a simple VQE algorithm. + +(param_shift)= +### Parameter Shift Rule +Before diving into VQE implementation, the optimization technique based on the gradient of objective function, calld **Parameter Shift Rule**, is explainedd. For certain types of quantum circuits, the gradient of objective function is known to be exactly calculablle. The classical optimizer can optimize the parameters using the values of the gradient. + +First, we consider a parameterized unitary $U({\boldsymbol \theta})=\prod_{j=1}^LU_j(\theta_j)$ to derive gradients using parameter shift rule. The $U_j(\theta_j)$ is a unitary with parameter $\theta_j$, and can take the form of $U_j(\theta_j)=\exp(-i\theta_jP_j/2)$ with $\theta_j$ as an angle of Pauli operator $P_j\in\{X,Y,Z\}$. Evolving the initial state $\rho$ by $U({\boldsymbol \theta})$, the expectation value $\langle M({\boldsymbol \theta})\rangle$ for the observable $M$ under the evolved state is + +$$ +\langle M({\boldsymbol \theta})\rangle=\text{Tr}\left[MU({\boldsymbol \theta})\rho U({\boldsymbol \theta})^\dagger\right] = \text{Tr}\left[MU_{L:1}\rho U_{L:1}^\dagger\right] +$$ + +Here $U_{l:m}:=\prod_{j=m}^lU_j(\theta_j)$. The gradient of this expectation value for parameter $\theta_j$ is + +$$ +\frac{\partial}{\partial\theta_j}\langle M({\boldsymbol \theta})\rangle=\text{Tr}\left[M\frac{\partial U_{L:1}}{\partial\theta_j}\rho U_{L:1}^\dagger\right]+\text{Tr}\left[MU_{L:1}\rho\frac{\partial U_{L:1}^\dagger}{\partial\theta_j}\right] +$$ + +Since $P_j^\dagger=P_j$, + +$$ +\begin{aligned} +\frac{\partial U_{L:1}}{\partial\theta_j} &= U_L\ldots U_{j+1}\frac{\partial U_j}{\partial\theta_j}U_{j-1}\ldots U_1=-\frac{i}{2}U_{L:j}P_jU_{j-1:1} \\ +\frac{\partial U_{L:1}^\dagger}{\partial\theta_j} &=\frac{i}{2}U_{j-1:1}^\dagger P_jU_{L:j}^\dagger +\end{aligned} +$$ + +holds. From this, one can get + +$$ +\frac{\partial}{\partial\theta_j}\langle M({\boldsymbol \theta})\rangle=-\frac{i}{2}\text{Tr}\left[MU_{L:j}\left[P_j,U_{j-1:1}\rho U_{j-1:1}^\dagger\right]U_{L:j}^\dagger\right]. +$$ + +Since $P_j$ is a Pauli operator, $U_j(\theta_j)=\exp(-i\theta_jP_j/2)=\cos(\theta_j/2)I-i\sin(\theta_j/2)P_j$ ($I$ is identity), thus $U(\pm\pi/2)=(1/\sqrt{2})(I\mp iP_j)$. Therefore, + +$$ +U_j\left(\frac{\pi}{2}\right)\rho U_j^\dagger\left(\frac{\pi}{2}\right)-U_j\left(-\frac{\pi}{2}\right)\rho U_j^\dagger\left(-\frac{\pi}{2}\right) = \frac12\left(I-iP_j\right)\rho\left(I+iP_j^\dagger\right)-\frac12\left(I+iP_j\right)\rho\left(I-iP_j^\dagger\right) = -i[P_j,\rho]. +$$ + +When applying this result to the above equation of $\partial\langle M({\boldsymbol \theta})\rangle/\partial\theta_j$, + +$$ +\begin{aligned} +\frac{\partial}{\partial\theta_j}\langle M({\boldsymbol \theta})\rangle &=-\frac{i}{2}\text{Tr}\left[MU_{L:j}[P_j,U_{j-1:1}\rho U_{j-1:1}^\dagger]U_{L:j}^\dagger\right] \\ +&= \frac12\text{Tr}\left[MU_{L:j+1}U_j\left(\theta_j+\frac{\pi}{2}\right)U_{j-1:1}\rho U_{j-1:1}^\dagger U_j^\dagger\left(\theta_j+\frac{\pi}{2}\right) U_{L:j+1}^\dagger-MU_{L:j+1}U_j\left(\theta_j-\frac{\pi}{2}\right)U_{j-1:1}\rho U_{j-1:1}^\dagger U_j^\dagger\left(\theta_j-\frac{\pi}{2}\right) U_{L:j+1}^\dagger)\right] \\ +&= \frac12\left[\left\langle M\left({\boldsymbol \theta}+\frac{\pi}{2}{\boldsymbol e}_j\right)\right\rangle - \left\langle M\left({\boldsymbol \theta}-\frac{\pi}{2}{\boldsymbol e}_j\right)\right\rangle\right] +\end{aligned} +$$ + +holds where ${\boldsymbol e}_j$ is a vector with 1 for the $j$-th element and 0 otherwise. + +From this, it turns out that the gradient of the expectation value $\langle M({\boldsymbol \theta})\rangle$ with respect to parameter $\theta_j$ can be obtained as a difference between the two expectation values with the $\theta_j$ values shifted by $\pm\pi/2$. This is the parameter shift rule. + ++++ {"pycharm": {"name": "#%% md\n"}} + +(vqe_imp)= +### VQE Implementation +Moving on to a simple VQE implementation with parameter shift rule. The problem is to determine the parameters of ansatz by minimizing the expectation value of a certain observable with VQE. + +The circuit contains only $R_YR_Z$ gates and the observable is $ZXY$, a tensor product of Pauli $Z$, $X$ and $Y$. + +Actully the parameter shift rule can be implemented in a single line using ParamShiftEstimatorGradient API in Qiskit (if you are interested in gradient calculation, you could write the circuit to calculate the expectation values with each parameter shifted by $\pm\pi/2$ and compare the gradient from the API wtih the difference of the two expectation values). The parameter optimization is performed using Conjugate Descent (CG) and Gradient Descent optimizers, both based on gradient descent, and is compared with COBYLA. + +Finally, the energies obtained using VQE are compared with the true lowest energy from exact diagonalization. + +```{code-cell} ipython3 +from qiskit.algorithms.minimum_eigensolvers import VQE, NumPyMinimumEigensolver +from qiskit.algorithms.optimizers import CG, GradientDescent +from qiskit.algorithms.gradients import ParamShiftEstimatorGradient +``` + +```{code-cell} ipython3 +# Definition of ansatz + +num_qubits = 3 # number of qubits +num_layers = 2 # number of layers + +ansatz = QuantumCircuit(num_qubits) + +# Parameter array with length 0 +theta = ParameterVector('θ') + +# Add one element to the array and return the edded element +def new_theta(): + theta.resize(len(theta) + 1) + return theta[-1] + +for _ in range(num_layers): + for iq in range(num_qubits): + ansatz.ry(new_theta(), iq) + + for iq in range(num_qubits): + ansatz.rz(new_theta(), iq) + + #for iq in range(num_qubits - 1): + # ansatz.cx(iq, iq + 1) + +ansatz.draw('mpl') +``` + +```{code-cell} ipython3 +# Observable +obs = SparsePauliOp('ZXY') + +# Initial values of parameters +init = rng.uniform(0., 2. * np.pi, size=len(theta)) + +# Gradient from parameter shift rule using estimator object +grad = ParamShiftEstimatorGradient(estimator) + +# VQE with Conjugate Gradient methof +optimizer_cg = CG(maxiter=200) +vqe_cg = VQE(estimator, ansatz, optimizer_cg, gradient=grad, initial_point=init) + +# VQE with Gradient Descent method +optimizer_gd = GradientDescent(maxiter=200) +vqe_gd = VQE(estimator, ansatz, optimizer_gd, gradient=grad, initial_point=init) + +# VQE with COBYLA method +optimizer_cobyla = COBYLA(maxiter=300) +vqe_cobyla = VQE(estimator, ansatz, optimizer_cobyla, initial_point=init) + +# Solver with Exact Diagonalization +ee = NumPyMinimumEigensolver() +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +# Cell for text + +if os.getenv('JUPYTERBOOK_BUILD') == '1': + del obs +``` + +```{code-cell} ipython3 +:tags: [raises-exception, remove-output] + +result_vqe_cg = vqe_cg.compute_minimum_eigenvalue(obs) +result_vqe_gd = vqe_gd.compute_minimum_eigenvalue(obs) +result_vqe_cobyla = vqe_cobyla.compute_minimum_eigenvalue(obs) +result_ee = ee.compute_minimum_eigenvalue(obs) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +with open('data/vqe_results.pkl', 'rb') as source: + result_ee, result_vqe_cobyla, result_vqe_cg, result_vqe_gd = pickle.load(source) +``` + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +print('Result:') +print(f' Exact = {result_ee.eigenvalue}') +print(f' VQE(COBYLA) = {result_vqe_cobyla.optimal_value}') +print(f' VQE(CG) = {result_vqe_cg.optimal_value}') +print(f' VQE(GD) = {result_vqe_gd.optimal_value}') +``` + ++++ {"pycharm": {"name": "#%% md\n"}} + +The VQE with COBYLA likely returns a result very close to the exact answer (= -1.0). The VQE with gradient-based optimizers also often works well, but sometime returns very bad answers depending on the initial values of the parameters. + +You could play with different ansatzes, changing the observables and parameters. \ No newline at end of file diff --git a/source/en/vqe_tracking.md b/source/en/vqe_tracking.md new file mode 100644 index 00000000..b8337f1e --- /dev/null +++ b/source/en/vqe_tracking.md @@ -0,0 +1,367 @@ +--- +jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.10.6 +--- + +# 【Exercise】Find Tracks of Charged Particles Produced in High-Energy Physics Experiment + ++++ + +In this assignment, you will think about how to apply the variational quantum eigensolver to physics experiment. Specifically, we focuse on high-energy physics (HEP) experiment and attempt to **reconstruct tracks of charged particles**, which is essential for HEP experiments, using variational quantum eigensolver. + +```{contents} Contents +--- +local: true +--- +``` + ++++ + +## Introduction +In the {doc}`notebook` that introduced **Variational Quantum Eigensolver** (VQE), we learned about the VQE and the basic method to implement a variational quantum circuit. In this section, we consider how we can apply VQE to high-energy physics. + +In HEP experiments, particles with electric charges (such as protons) are accelerated through magnetic field to high energy and collide with each other. The collision produces a numerous number of secondary particles, which are measured using a detector surrounding a collision point. Through these experiments, we invetigate the fundamental properties of the particles and their underlying interaction mechanism. To do this, the detector signals are processed to identify the secondary particles and measure their energies and momenta precisely. In this exercise, you will learn if we can use VQE to **reconstruct tracks of charged particles** (generally called **tracking**) as the important first step to identify the particles. + ++++ + +(hep)= +## High-Energy Physics Experiment +(hep_LHC)= +### Overview of LHC Experiment + +```{image} figs/LHC_ATLAS.png +:alt: LHC_ATLAS +:width: 1000px +:align: center +``` + +The Large Hadron Collider (LHC) is a circular collider that lies at the border of Switzerland and France, operated by European Organization for Nuclear Research (CERN). It is placed inside a tunnel, located about 100 meters underground, with a 27 kilometer circumference. Currently the LHC can accelerate protons up to 6.5 TeV in energy (1 TeV is $10^12$ eV). The accelerated protons collide head-on at the world's highest energy of 13.6 TeV (see the picture at the upper left). The picture at the upper right shows the LHC in the underground tunnel. + +Four experiments, ATLAS, CMS, ALICE, and LHCb, are carried out at the LHC. Of these, the ATLAS and CMS experiments use large general-purpose detectors (the ATLAS detector is shown at the bottom left). In ATLAS and CMS, secondary particles generated by proton collisions are observed by high-precision detectors arranged surrounding the collision point, making it possible to observe various reactions and explore new phenomena. The bottom right figure shows an actual event observed with ATLAS detector, and it is a candidate of Higgs boson which was first observed in 2012 by the ATLAS and CMS experiments. The Higgs boson itself is observed not as a single particle but as a collection of particles produced from the decay of Higgs boson. + ++++ + +(hep_detect)= +### Measurement of Charged Particles + +The detectors used in ATLAS and CMS experiments consist of multiple detectors with differing characteristics, arranged outward in concentric layers. The innermost detector is used to reconstruct and identify charged particles, and is one of the most important detectors in the experiments. The detector itself is made up of roughly 10 layers and sends multiple detector signals when a single charged particle passes through it. For example, as in the lefft figure, a single proton collision produces many secondary particles, and leave the detector signal called "hits" (corresponding to colored points in the figure). From the collection of these hits, a set of hits produced by a charged particle traversing the detector is selected to reconstruct the particle track. The colored lines in the right figure correspond to reconstructed tracks. The reconstruction of charged particle tracks, called tracking, is one of the most important experimental techniques in HEP experiment. + +The charged particle tracking is essentially an optimization problem of detector hits to tracks, and the computational cost often grows exponentially in the number of particles produced in collisions. The LHC is expected to be upgraded to higher beam intensity (number of protons to be accelerated in a single beam) or higher beam energy, and the current reconstruction technique could suffer from increasing computational complexity. Various new techniques are being examined, including quantum computing, to reduce the computational time or resources required for future HEP experiments. + +```{image} figs/tracking.png +:alt: tracking +:width: 1000px +:align: center +``` + ++++ + +(ML_challenge)= +### TrackML challenge + +CERN has plans to upgrade the LHC to "High-Luminosity LHC" or HL-LHC (expected to start operation in 2029), in which the beam intensity is significantly increased. The increased beam intensity will result in 10-fold increase in collision rate and hence produced particle density, making charged particle tracking even more challenging. + +Unfortunately it is hard to imagine that quantum computing can be used in real experiments from 2029. However, some researchers thought that the capability of classical reconstruction algorithm can be enhanced using advanced machine learning techniques and held a competition called TrackML Particle Tracking challenge in 2018. In this competition, the public data composed of detector hits simulated in HL-LHC environment are provided and the participants attempted to apply their own algorithms to these data to compete with each other in speed and accuracy. + ++++ + +(tracking)= +## Exercise: Tracking with VQE + +Our goal in this exercise is to perform charged particle tracking with VQE using this TrackML challenge dataset. Since it is still difficult to solve large-scale tracking problem, we only consider the case of small number of particles produced in collision. + ++++ + +Fisrt, import the necessary libraries. + +```{code-cell} ipython3 +--- +jupyter: + outputs_hidden: false +pycharm: + name: '#%% + + ' +--- +import pprint +import numpy as np +import h5py +import matplotlib.pyplot as plt + +from qiskit import QuantumCircuit +from qiskit.circuit.library import TwoLocal +from qiskit.primitives import BackendEstimator +from qiskit.algorithms.minimum_eigensolvers import VQE, NumPyMinimumEigensolver +from qiskit.algorithms.optimizers import SPSA, COBYLA +from qiskit.algorithms.gradients import ParamShiftEstimatorGradient +from qiskit.quantum_info import SparsePauliOp, Statevector +from qiskit_optimization.applications import OptimizationApplication +from qiskit_aer import AerSimulator +``` + +(hamiltonian_form)= +### Hamiltonian and VQE + +In order to use VQE for optimization, the problem will need to be formulated in the form of Hamiltonian. If the problem is formulated such that the solution corresponds to the lowest energy state of the Hamiltonian, the VQE could solve the problem by finding such state. + ++++ + +(pre_processing)= +#### Preparation + +This assignment will use data from the TrackML challenge, but because original data is difficult to work with, we will use data that has been preprocessed to make it easier to use in quantum calculation, based on {cite}`bapst2019pattern`. + +First, three consecutive layer hits are selected as shown in the figure below (a set of three-layer hits surrounded by a dashed line is called 'segment' here). Think of colored dots as hits. These segments are constructd simply by connecting hits, therefore they do not necessarily originate from real particles. Some of them may arise from connecting hits from different particles or from misidentifying detector noise as hits and grouping them to form segments. However, those "fake" segments can point to any directions while "genuine" segments originating from real particles should point to the center of the detector where collisions occur. +Given this, each segment is assigned *score* depending on how well the direction of the segment is consistent with that from the detector center. As explained later, the score is assigned a smaller value with increasing consistency of the segment pointing to the detector center. The segments obviously identified as fakes are ignored from the first place, and only segments with scores less than certain threshold are considered below. + +```{image} figs/track_segment.png +:alt: track_segment +:width: 350px +:align: center +``` + +Next, all pairings of selected segments are taken. For each pairing, a score is then assigned depending on how the paired segments are **consistent with track originating from a single particle**. This score is designed to have value closer to -1 as the paired segments get closer to forming an identical track. + +For example, if one of three hits in a segment is shared by two segments (such as those two red segments in the figure), it is not considered a track candidate and the score becomes +1. This is because these tracks with branched or merged segments are not consistent with charged particles produced at the center of the detector. + +Furthermore, the segments shown in orange (one of the hits is missing in intermediate layer) and in brown (a zigzag pattern) are not tracks of our interest either, therefore the score is set value larger than -1. The reason why a zigzag pattern is not preferred is that if a charged particle enters perpendicularly into a uniform magnetic field, the trajectory of the particle is bent with a constant curvature on the incident plane due to Lorentz force. + ++++ + +#### QUBO Format + +Under this setup, the next step is whether a given segment is adopted as part of particle tracks or rejected as fake. In a sample of $N$ segments, the adoptation or rejection of $i$-th segment is associated to 1 or 0 of a binary variable $T_i$, and the variable $T_i$ is determined such that the objective function defined as + +$$ +O(b, T) = \sum_{i=1}^N a_{i} T_i + \sum_{i=1}^N \sum_{j