Skip to main content
IBM Quantum Platform

深さ削減のための回路切断

使用時間の目安イーグルプロセッサーで8分(注:あくまでも目安です。 ランタイムは異なるかもしれない)。


背景

このチュートリアルでは、量子回路のゲートを切断して回路の深さを減らすための Qiskit pattern 。 回路切断に関するより詳細な議論については、 回路切断Qiskitアドオンのドキュメントをご覧ください。


要件

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

  • Qiskit SDK v2.0 またはそれ以降、 可視化サポート付き
  • Qiskit Runtime v0.22 またはそれ以降 (pip install qiskit-ibm-runtime)
  • 回路切断 Qiskit アドオン v0.9.0 以降 (pip install qiskit-addon-cutting)

セットアップ

import numpy as np

from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_addon_cutting import (
    cut_gates,
    generate_cutting_experiments,
    reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2

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

ドキュメントに概説されている4つのステップを使用して、Qiskitパターンを実装します。 この場合、スワップ・ゲートになるゲートをカットし、より浅い回路でサブ実験を実行することで、ある深さの回路で期待値をシミュレートする。 ゲート切断は、ステップ2(離れたゲートを分解して量子実行のために回路を最適化する)とステップ4(元の回路の期待値を再構成する後処理)に関連している。 最初のステップでは、Qiskit回路ライブラリから回路を生成し、いくつかのobservablesを定義する。

  • インプット回路を定義するための古典的なパラメータ
  • 出力抽象回路と観測値
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")

Output:

Output of the previous code cell

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

  • 入力抽象回路と観測値
  • 出力:出力:トランスパイルド回路の深さを減らすために遠くのゲートをカットすることによって生成されるターゲット回路と観測値

初期レイアウトは、量子ビット3と0の間のゲートを実行するために2回のスワップを必要とし、量子ビットを初期位置に戻すためにさらに2回のスワップを必要とするものを選んだ。 私たちは、プリセット・パス・マネージャーで利用可能な最高レベルの最適化である optimization_level=3

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

pm = generate_preset_pass_manager(
    optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)
スワップが必要な量子ビットを示すカップリングマップ
print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)

Output:

Transpiled circuit depth: 103
Output of the previous code cell

遠隔ゲートを検索して切り取る: 非局所量子ビット(0と3)を接続する遠隔ゲートを、そのインデックスを指定 TwoQubitQPDGate することでオブジェクトに置き換える。 cut_gates 指定されたインデ TwoQubitQPDGate ックスのゲートをオブジェクトで置き換え、さらに各ゲート分解に対応 QPDBasis するインスタンスのリストを返します。 オブジェクト QPDBasis は、カットゲートを単一量子ビット操作に分解する方法に関する情報を含みます。

# Find the indices of the distant gates
cut_indices = [
    i
    for i, instruction in enumerate(circuit.data)
    if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]

# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)

qpd_circuit.draw("mpl", scale=0.8)

Output:

Output of the previous code cell

バックエンドで実行するサブ実験を生成するgenerate_cutting_experiments は、 TwoQubitQPDGate インスタンスと観測値を含む回路を PauliList として受け取る。

フルサイズの回路の期待値をシミュレートするために、分解されたゲートの結合準確率分布から多数のサブ実験が生成され、1つ以上のバックエンドで実行される。 分布から取られたサンプルの数は num_samples で制御され、各ユニークなサンプルに対して1つの結合係数が与えられます。 係数の計算方法の詳細については、 説明資料を参照のこと。

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
    circuits=qpd_circuit, observables=observables, num_samples=np.inf
)

比較のため、遠くのゲートをカットするとQPDサブ実験が浅くなることがわかる :以下は、QPD回路から生成された、任意に選択されたサブ実験の例である。 その深さは半分以下になった。 より深い回路の期待値を再構築するためには、このような確率的なサブ実験の多くを生成し、評価しなければならない。

# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])

print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
    f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
    "mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)

Output:

Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
Output of the previous code cell

一方、カットすることで余計なサンプリングが必要になる。 ここでは、3つのCNOTゲートをカットした結果、サンプリング・オーバーヘッドが 939^3。回路カットで発生するサンプリング・オーバーヘッドについては、 Circuit Knitting Toolboxのドキュメントを参照。

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")

Output:

Sampling overhead: 729.0

ステップ3: Qiskit primitivesを使用して実行する

Samplerプリミティブを使用して、対象の回路(「サブ実験」)を実行します。

  • 入力ターゲット回路
  • アウトプット準確率分布
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)

# Set up the IBM Quantum Sampler primitive.  For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)

# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
print(job.job_id())

Output:

czypg1r6rr3g008mgp6g

ステップ4:後処理を行い、結果を希望の古典形式で返す

サブ実験結果、サブ観測値、サンプリング係数を用いて、元の回路の期待値を再構築する。

入力準確率分布 出力再構成された期待値

reconstructed_expvals = reconstruct_expectation_values(
    results,
    coefficients,
    observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)

Output:

Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
    Statevector(circuit).expectation_value(SparsePauliOp(observable))
    for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)

Output:

Ideal expectation value
1.2283177520039992

チュートリアル調査

このチュートリアルに関するご意見・ご感想をお寄せください。 あなたの洞察は、私たちのコンテンツの提供とユーザーエクスペリエンスを向上させるのに役立ちます。

アンケートへのリンク

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。