メインコンテンツへスキップ

期待値推定のためのワイヤカッティング

使用量の目安:Heronプロセッサで22秒(注意:これはあくまで目安です。実行時間は異なる場合があります。)

学習成果

このチュートリアルを通じて、ユーザーは以下を理解できるようになります:

  • qiskit-addon-cutting を使用して大きな回路を小さなサブ回路に分割し、ノイズの影響を軽減する方法

前提条件

このチュートリアルを始める前に、以下のトピックに慣れておくことをお勧めします:

  • このワークフローで使用される Sampler プリミティブの使い方

背景

回路ニッティング(Circuit-knitting)は、回路をより少ないゲートやキュービットで構成される複数の小さなサブ回路に分割するさまざまな手法を包括する総称です。各サブ回路は独立して実行でき、最終的な結果は各サブ回路の結果に対する古典的な後処理によって得られます。この手法はCircuit Cutting Qiskitアドオンで利用可能であり、技術の詳細な説明はドキュメントに記載されています。また、その他の入門資料も提供されています。

このチュートリアルでは、ワイヤカッティングと呼ばれる手法を扱います。これは回路をワイヤに沿って分割する方法です[1], [2]。古典回路では、分割点における結果が決定論的に求められ、0か1のいずれかであるため、分割は単純です。しかし、カット点におけるキュービットの状態は、一般的に混合状態です。そのため、各サブ回路は異なる基底(通常はパウリ基底[3], [4]のようなトモグラフィ的に完全な基底のセット)で複数回測定し、対応する固有状態で準備する必要があります。以下の図(出典:[7])は、4キュービットGHZ状態を3つのサブ回路にワイヤカッティングする例を示しています。ここで MjM_j は基底のセット(通常はパウリ X、Y、Z)を表し、PiP_i は固有状態のセット(通常は 0|0\rangle1|1\rangle+|+\rangle+i|+i\rangle)を表します。

wc-1.png wc-2.png

各サブ回路はキュービット数やゲート数が少ないため、ノイズの影響を受けにくいと期待されます。このチュートリアルでは、この手法を使用してシステム内のノイズを効果的に抑制できる例を示します。

必要条件

このチュートリアルを始める前に、以下がインストールされていることをご確認ください:

  • Qiskit SDK v2.0以降(visualizationサポート付き)
  • Qiskit Runtime v0.22以降(pip install qiskit-ibm-runtime
  • Circuit Cutting Qiskitアドオン v0.10.0以降(pip install qiskit-addon-cutting
  • Qiskit addon utils 0.3以降(pip install qiskit-addon-utils
  • Qiskit Aer(pip install qiskit-aer

セットアップ

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch

小規模シミュレータの例

このチュートリアルでは、Qiskitパターン を実装して、1次元(1D)多体局在(MBL)回路をシミュレートします。MBL回路はハードウェア効率の良い回路であり、2つのパラメータ θ\thetaϕ\vec{\phi} によってパラメータ化されています。θ\theta00 に設定し、すべてのキュービットの初期状態を 0|0\rangle に準備した場合、Zi\langle Z_i \rangle の理想的な期待値は ϕ\vec{\phi} の値に関係なく、すべてのキュービットサイト ii において +1+1 となります。この回路の詳細については、こちらの論文を参照してください。

ノイズのないシミュレータでは、回路カッティングの有無に関わらず得られる期待値は同じになることに注意してください。

ステップ 1:古典入力を量子問題にマッピングする

1D MBL回路の構築

まず、1D MBL回路を構築するための関数を示します。

class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)

class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)

theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)

for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)

Output of the previous code cell

すべてのキュービットの ZZ の平均期待値 O=1niZiO = \frac{1}{n} \sum_i Z_iθ=0\theta = 0 の場合)を計算します。Zi=1\langle Z_i \rangle = 1 \forall ii の理想的な期待値より、OO の理想的な期待値も 11 となります。パラメータ ϕ\phi はランダムに選択されます。

np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

回路を分割するには、所望の位置に CutWire を挿入してアノテーションを行う必要があります。このチュートリアルでは均等な分割を選択します。MBL回路は use_cut=True を設定することで、元の回路のキュービット数を nn として n2\frac{n}{2} キュービット後に適切にアノテーションを挿入するよう設計されています。また、ランダムに生成されたパラメータを回路に割り当てます。

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

Output of the previous code cell

ステップ 2:量子ハードウェア実行のための問題の最適化

回路を小さなサブ回路にカットする

次に、qiskit-addon-cutting を使用して回路を2つの小さなサブ回路に分割します。qiskit-addon-cutting は仮想 Move ゲートを追加してワイヤカット位置を分割し、キュービット数を適切に調整します。ここで、この仮想ゲートを含む回路を作成します。ワイヤカットが1つのため、関連するキュービット数は1つ増加します。

mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)

Output of the previous code cell

オブザーバブルの構築と拡張

前述のとおり、オブザーバブルは各キュービットの ZZ の平均となります。ただし、仮想 Move ゲートを挿入することで、回路内の実効的なキュービット数が増加します。この変化に対応するため、オブザーバブルも適切に拡張する必要があります。仮想 Move ゲートのために追加されるキュービットには、オブザーバブルは常に恒等演算子(II)として作用することに注意してください。

observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

ここで2つのサブ回路を可視化します:

subcircuits[0].draw("mpl", fold=-1)

Output of the previous code cell

subcircuits[1].draw("mpl", fold=-1)

Output of the previous code cell

Move 操作を使用してオブザーバブルを拡張するには PauliList データ構造が必要です。元の回路の期待値を再構成するには、SparsePauliOp 形式のオブザーバブルが必要です。

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

前述のとおり、各カットでは上流の回路をパウリ基底で測定し、下流の回路をその基底の固有状態で準備する必要があります。generate_cutting_experiments 関数は、これらの必要な回路と再構成に必要な各回路の係数をすべて生成します。詳細はこちらの論文をご参照ください。

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

バックエンドへの回路のトランスパイル

最初のシミュレーションのみの例では、バックエンドの基底ゲートセットに回路をトランスパイルします:

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)

print(backend)
<IBMBackend('ibm_fez')>

ステップ 3:Qiskitプリミティブを使用した実行

各サブ実験を実行します:

pm_basis = generate_preset_pass_manager(
optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
label: pm_basis.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
sampler = SamplerV2(mode=AerSimulator())
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in basis_subexperiments.items()
}

ステップ 4:後処理と所望の古典形式での結果の返却

各サブ実験の実行結果を取得し、カットされていない回路の期待値を再構成します:

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
np.float64(0.9953821063041687)
methods = [
"Uncut",
"Wire cut",
]
values = [
1,
reconstructed_expval,
] # since the ideal expectation value in noiseless simulation is +1

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

大規模ハードウェアの例

次に、60キュービットのMBL回路でワイヤカッティングを実演します。カットあり・なし両方の回路をIBM Quantum®ハードウェアで実行します:

num_qubits = 60
depth = 2

# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)

# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)

# Define observable and expand to account for the wire cut
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
sampler.options.environment.job_tags = ["TUT_WC"]
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
results = {label: job.result() for label, job in jobs.items()}

# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real

# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])

uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)

# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()

Output of the previous code cell

uncut_expval
0.9202473958333336

次のステップ

おすすめ

この研究に興味を持たれた方には、以下の資料もお勧めです:

参考文献

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.

[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.

[7] Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y