【参考】足し算を実機で行う#
実習の内容の延長です。ここでは並列足し算回路を実機で実行します。
効率化前後の回路の比較#
実習のおさらいをすると、まずもともとの足し算のロジックをそのまま踏襲した回路を作り、それではゲート数が多すぎるので効率化した回路を作成しました。
実は効率化した回路でもまだゲートの数が多すぎて、4ビット+4ビットの計算では答えがスクランブルされてしまいます。回路が小規模になればそれだけ成功確率も上がりますので、\((n_1, n_2)\)の値として(4, 4)以外に(3, 3)、(2, 2)、(1, 1)も同時に試すことにしましょう。
# まずは全てインポート
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumRegister, QuantumCircuit, transpile
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit_ibm_runtime.accounts import AccountNotFoundError
from qc_workbook.optimized_additions import optimized_additions
from qc_workbook.utils import operational_backend, find_best_chain
print('notebook ready')
# 利用できるインスタンスが複数ある場合(Premium accessなど)はここで指定する
# instance = 'hub-x/group-y/project-z'
instance = None
try:
service = QiskitRuntimeService(channel='ibm_quantum', instance=instance)
except AccountNotFoundError:
service = QiskitRuntimeService(channel='ibm_quantum', token='__paste_your_token_here__', instance=instance)
backend = service.least_busy(min_num_qubits=13, filters=operational_backend())
print(f'Using backend {backend.name}')
実習と全く同じsetup_addition
関数と、次のセルで効率化前の回路を返すmake_original_circuit
関数を定義します。
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)までそれぞれオリジナルと効率化した回路の二通りを作り、全てリストにまとめてバックエンドに送ります。
# 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]
# List of circuits
circuits = []
for n1, n2 in [(4, 4), (3, 3), (2, 2), (1, 1)]:
circuits += make_circuits(n1, n2, backend)
# バックエンドで定められた最大のショット数だけ各回路を実行
shots = min(backend.max_shots, 2000)
print(f'Submitting {len(circuits)} circuits to {backend.name}, {shots} shots each')
sampler = Sampler(backend)
job = sampler.run(circuits, shots=shots)
counts_list = [result.data.meas.get_counts() for result in job.result()]
ジョブが返ってきたら、正しい足し算を表しているものの割合を調べてみましょう。
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
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割です。
フェイクバックエンドでは実機のエラーもシミュレートされていますが、エラーのモデリングが甘い部分もあり、\(2^{-n_3}\)よりは遥かに良い成績が得られています。いずれのケースも、回路が短い効率化バージョンの方が正答率が高くなっています。