トランスパイラー
この関 generate_boxing_pass_manager() 数は、回路の指示を注釈付きのボックスにグループ化できる qiskit.transpiler.PassManager ものを構築するための、柔軟で便利なツールです。 これにより、回路を手作業でグループ化したり注釈を付けたりする代わりに、簡単な代替手段が得られ、ワークフローの自動化や、さまざまな回路のグループ化手法の検証などに役立ちます。
このガイドでは、および generate_boxing_pass_manager() その引数の使用方法について説明します。 各関数の引数の効果を明確にするため、以下のセクションでは主に下図の回路を参照します。
from qiskit.circuit import Parameter, QuantumCircuit
circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
circuit.rz(Parameter(f"th_{qubit}"), qubit)
circuit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))
circuit.draw("mpl", scale=0.8)
操作をグループごとにまとめる
この引数を または True に設定 enable_gates することで、2量子ビットゲートをボックスにグループ化するかどうかを指定 False できます。 同様に、測定値をグループ化するかどうかを指定 enable_measures します。 以下のスニペットは、ゲートと測定の両方がグループ化されている例を示しています。
from samplomatic.transpiler import generate_boxing_pass_manager
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
図からわかるように、パスマネージャーは2種類のボックスを作成します。1つは2量子ビットゲートの単層を含むもので、もう1つは測定の単層を含むものです。 この分離は、ノイズ学習および低減プロトコルにおける標準的な手法を反映したものであり、通常、同種の演算からなる層を対象としています。 2量子ビットゲートと測定は、それらを収容できる最も左側のボックスに配置され、すべての1量子ビットゲートは、その前にある2量子ビットゲートまたは測定と同じボックスに配置される。
以下のコードは、 が に設定されている enable_gates 例を示しています False。 ご覧の通り、2量子ビットゲートはボックスにまとめられておらず、その前にある1量子ビットゲートも同様です。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=False,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
ボックスの注釈の付け方を選択してください
返された回路内のすべての2量子ビットゲートおよび測定ボックスには、左向き表記の注釈が付いています。 特に、2量子ビットゲートを含むすべてのボックスには、. が付記されています Twirl。 測定ボックスについては、ユーザーは Twirl、、 BasisTranform ( を に設定 mode``"measure" )、またはその両方から選択できます。 以下のコードは、すべてのボックスがツイール処理され、さらに測定ボックスに が注釈として付加された回路を生成します BasisTranform。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)ノイズ注入のための回路を準備する
この機能を使用すると、どのボックスに注釈 InjectNoise を付けるかを指定 inject_noise_targets できます。 たとえば、次のコードは、2量子ビットゲートのボックスには注釈 InjectNoise が付いているが、測定のボックスには付いていない回路を生成します。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)ある回路に、単一量子ビットゲートに関して等価なボックスが2つ以上含まれている場合、それらのすべてに、同じ を持つ注釈 InjectNoise が付けられます ref。 したがって、返される回路に含まれる「 refs」の個数は、異なるボックスの個数と等しくなる。
の値は、の InjectNoise 注釈に影響 inject_noise_strategy を与えます。 以下の値をとります。
modifier_ref='': ノイズマップからサンプリングを行う前に、そのマップを変更する必要がない場合に推奨されます。modifier_ref=ref: すべてのノイズマップを同じ倍率で均一に拡大・縮小する必要がある場合に推奨されます。- : の固有の値
modifier_ref:各ノイズマップをそれぞれ異なる倍率でスケーリングする必要がある場合に推奨されます。
以下のコードは、2量子ビットゲートの各ボックスに一意の値 modifier_ref を持つ注釈 InjectNoise が付けられた回路を生成します。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)回転戦略を選択してください
ボクシング・パス・マネージャーは、まず、どのエンタングルメント・ゲートと測定指示を同じボックスに表示するかを決定することから始めます。 これが完了すると、ボックスは一般的に全幅にはならず、回路内でアクティブな、あるいはバックエンドのターゲットに存在する量子ビットの一部のみが含まれることになるでしょう。 ボクシング・パス・マネージャーの一般的な構成では、ボックス命令が作用するすべての量子ビット(ボックス内の命令が作用しない量子ビットであっても)がツイリングされるため、部分幅のボックスを使用すると、ツイリングによるノイズ調整効果に影響を及ぼす。 アイドル状態の量子ビットは回転させられないため、例えば、ボックスが適用されている間にコヒーレントエラーが蓄積される可能性がある。 この機能により、各ボックスが保有する量子ビットを拡張するオプションが提供 twirling_strategy され、それによってアイドル状態の量子ビットの数を変更することができます:
"active"(デフォルト) では、ボックスはアイドル状態の量子ビットには拡張されません。"active_accum"回路内の先行する命令によってすでに操作が行われた量子ビットに対して、すべてのボックスを拡張する。 これは、回路の開始時点でクビットを、活性状態になるまでスピンを回転させずに、できるだけ長く基底状態に保ちたい場合に役立ちます。"active_circuit"回路内のいずれかの命令によって操作される量子ビットすべてに、すべてのボックスを拡張する。 これは、絡み合い層が繰り返し現れる構造化された回路があり、各ボックスごとにノイズの学習が必要となるため、固有のボックスの数を最小限に抑えたい場合に役立ちます。"all"回路内のすべての量子レジスタにあるすべての量子ビットに対して、すべてのボックスを拡張する。 これは、QPU内のすべての量子ビットを常にツイール状態にしておきたい場合"active_circuit"における拡張として有用です。
次の例では、各戦略をベース回路に適用し、その結果を描画しています。
circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])
circuit.draw("mpl", scale=0.6)
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
ax = plt.subplot(2, 2, idx + 1)
pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
transpiled_circuit = pm.run(circuit)
fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
ax.set_title(f"twirling_strategy='{strategy}'")
plt.tight_layout()
障壁の扱い方を指定する
デフォルトでは、すべてのエンタングルメントゲートおよび測定命令が指示通りにボックス化された後(選択 twirling_strategy による拡張部分を含む)、可能であれば、そのすぐ右側のボックスに単一量子ビットゲートが追加される前に、回路からバリアが除去されます。
たとえば、次の回路にはバリアが含まれています:
circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()
circuit_with_barrier.draw("mpl", scale=0.8)
デフォルトの動作では、2つのエンタングルメントゲートが同じボックスに入ってしまうことは防がれますが、単一クビットゲートについては、障壁があっても次に利用可能なボックスに吸収されるようになっています。
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
を に remove_barriers 設定すると、障壁が維持 'never' されるため、単一量子ビットゲートに制約が課される。
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
最後に、障壁が直ちに取り除かれれば、絡み合った粒子はもはや別々の箱に入ることを余儀なくされなくなる。
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
層別化の仕組み
ボクシング・パス・マネージャーは、2つの主要なパスを順番に実行します:
GroupGatesIntoBoxes— 絡み合ったゲートをボックスごとに分類する。 この段階では、1クビットゲートは考慮せず、2クビットゲートのみをグループ化します。AbsorbSingleQubitGates— 各シングルクビットゲートを、その右側にある最も近い該当するボックスに吸収する。
層化処理中、回路DAGを位相順序に従って走査 GroupGatesIntoBoxes し、各2量子ビットゲートを、その量子ビットがまだ占有されていない最も早いグループに配置する。 3種類の命令は区切り役として機能し、それぞれの量子ビットの現在のグループをフラッシュして、それらの量子ビットに対する後続のゲートを新しいグループに強制的に移します:
- 障壁
- 既存の箱に記載されている説明
- 計測値
単一量子ビットゲートは区切り子ではありません。これらはグループ化に影響を与えず、その AbsorbSingleQubitGates 処理は に委ねられます。 現在、可換性の解析は行われていません。グループ分けは、回路の位相順序をそのまま反映しています。
なぜ、推奨されるデフォルト設定 remove_barriers="after_stratification" は
デフォルトのモードは、次の2つの利点を兼ね備えている "after_stratification" ためです:
- 障壁は、エンタングルメントゲートのグループ分けを制限します(これらは層別化の際、区切り役として機能します)。
- 障壁が取り除かれると、電子が 以前
AbsorbSingleQubitGates流れ出すため、単一量子ビットゲートは依然として以前の障壁の位置を通り抜け、最も近いボックスに吸収される。
対照的に:
"never"回路全体に障壁を維持することで、単一量子ビットゲートを障壁とボックスの間に閉じ込め、その吸収を防ぎ、回路内にねじれのない回転を残す。"immediately"層別化の前に障壁を取り除くため、グループ分けには影響を与えません。"finally"これは、出力回路にバリアを設けたい場合(例えば、可視化のためなど)に有用ですが、バリアとボックスの間の単一量子ビットゲートは吸収されません。
バリア付きボックスISA回路
トロッター化ハミルトニアンシミュレーション、変分アンサッツ、誤差増幅シーケンスなど、構造が反復される回路は、通常、プログラムで構築された後、ISA形式にトランスパイルされます。 トランスパイルされた回路に手動でボックスを追加するのは面倒なので、自然なワークフローは次のようになります:
- 論理回路を構築し、ボックスの境界にしたい層の境界にバリアを挿入してください。
- ISA回路に変換する。 Qiskitのトランスパイラーは、ルーティングや最適化の過程においてもバリアを維持します。
- ISAサーキット
generate_boxing_pass_manager()でレースを行う。
ある層の端がすべての量子ビットを覆っていない場合は、バリアが必要となります。 そうしないと、貪欲アルゴリズムは、次の層のゲート――つまり、それらの「空き」量子ビット上のゲート――を同じグループに配置してしまうことになる。 具体的な例として、5量子ビットのリング上で行われる2段階のハイゼンベルクZZシミュレーションを考えてみましょう。 トロッターの各ステップには、3つの結合層があります。すなわち、偶数結合 (0,1), (2,3)、奇数結合 (1,2), (3,4)、およびラップ結合です (4,0)。 奇数結合層の後、量子ビット0と4はどちらも自由な状態になる。 バリアがない場合、ラップ結合は、それらの空き量子ビットを使用する次のトロッターステップの結合と「貪欲なグループ化」 (4,0) が行われ、同じボックス内の異なるステップの混合ゲートが混在することになります。
from qiskit.circuit import Parameter, QuantumCircuit
n = 5 # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)
for step in range(2):
# Layer 1: even bonds (0,1) and (2,3)
for idx in range(0, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
for idx in range(1, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
heisenberg.rzz(dt, n - 1, 0)
if step < 1:
heisenberg.barrier() # separates the two Trotter steps
heisenberg.measure_all()
heisenberg.draw("mpl", scale=0.7)
以下のコードは、対象デバイスがCXをネイティブのエンタングラとして持つことを前提として、回路をISA形式にトランスパイルします。 なお、バリアはトランスパイレーション後も残存し、出力回路でも引き続き表示されることに注意してください。
from qiskit.transpiler import generate_preset_pass_manager
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)
次に、ISA回路にボクシング・パスマネージャーを適用します。 3つの結合層は、トロッター法の一連のステップにおいて、それぞれ独立したボックスとなります。
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
SamplomaticのパスマネージャーをQiskitのプリセットパスマネージャーに組み込む
以下のコードは、Qiskitのプリセット・パス・マネージャー generate_boxing_pass_manager() に を組み込む方法を示しています。 トランスパイルされた回路はISAであり、ボックスが含まれています。
from qiskit.transpiler import generate_preset_pass_manager
preset_pass_manager = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2]],
optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()
# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()
transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
このパターンは、バリアベースのISAボクシングワークフローとも自然に組み合わされます。 ボクシング・パス・マネージャーは、元の論理回路のバリアがトランスピレーションを経て存続した後、スケジューリング後のステップとして実行されるため、期待どおりに階層化を誘導します。
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()
boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)
注釈の確認と修正
オートボクシングを行った後、特定のボックスの注釈を確認したり、調整したりしたい場合があるかもしれません。 たとえば、ノイズの注入を追加したり、注釈を削除したり、ツイリンググループを変更したりする場合などです。 Samplomatic では、この目的 samplomatic.utils のために一連のユーティリティ関数が提供されています。 これらはすべて、元の回路を変更することなく、 新しい回路を返します。
extend_annotations() 回路内のすべてのボックスに、1つ以上の注釈を追加します。
from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations
boxed = generate_boxing_pass_manager().run(circuit_with_barrier)
# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)
filter_annotations() 指定されたタイプの注釈のみを残し、それ以外は削除します。 replace_annotations() 各アノテーションに対して呼び出し可能オブジェクトを適用します。この呼び出し可能オブジェクトは、その場所に代入するアノテーションのリストを返します(削除 [] する場合は return、置換する場合は要素が1つのリスト、展開する場合は要素が複数あるリストを返します)。
from samplomatic.utils import filter_annotations, replace_annotations
# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)
# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)この汎用関 map_annotations() 数は、注釈のリストを新しいリストにマッピングする任意の呼び出し可能オブジェクトを受け入れるため、ボックスごとの変換を完全に制御することができます。 strip_annotations() すべてのボックスからすべてのアノテーションを削除する、便利なラッパーです。
すべてのゲートボックス InjectNoise に を追加するといった一括変更を行う場合、 の パラメータ inject_noise_targets が最も簡潔な選択肢 generate_boxing_pass_manager() となります。 これらのユーティリティは、パスマネージャーのパラメータでは対応できない、ボックスごとの選択的な編集を行う際に役立ちます。
回路を作る
によって生成されるすべてのパスマネージャーは、によってテンプレート/サンプルックスペアに正常に変換可能な回路を生成 generate_boxing_pass_manager() します build()。 たとえば、次のコードは、ボクシング・パス・マネージャーによって生成された回路に対して、関 build() 数を呼び出します。
from samplomatic import build
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
template, samplex = build(transpiled_circuit)トランスパイルされた回路を確実にビルドできるようにするため、パスマネージャーは、必要に応じて追加のボックスを組み込む方法を把握しています。 例えば、以下の回路を考えてみましょう。この回路の末端には、測定されていない量子ビットがあります 0。
circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))
circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)
ゲートと測定値の周囲に左向きボックスを描くと、クビット上に未収集の仮想ゲートを持つ回路となり 0、この回路 build() に対して を呼び出すとエラーが発生します。 これを回避するために、によって返されるパスマネージャーは、コレクターとして機能する右揃えのボックスを追加することができます generate_boxing_pass_manager() 。 たとえば、次のコードでは、qubit は、未収集の仮想ゲートを拾い上げる右向きのボックスで終了 0 しています。 クビット に作用する単一クビットゲートも、結果として得られる回路の深さを最小限に抑えるために、このボックス内に配置 0 される。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)
別の例としては、アンボックス化された測定値のために収集されずに残ってしまう仮想ゲートを収集するために、ライトドレッシングされたボックスが追加されます。
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)