Transpilátor
Funkce generate_boxing_pass_manager() je flexibilní a pohodlný nástroj pro sestavení qiskit.transpiler.PassManager, který dokáže seskupit instrukce obvodu do anotovaných boxů. To poskytuje snadnou alternativu k ručnímu seskupování a anotování obvodů a je užitečné pro automatizaci pracovního postupu, testování různých strategií rozdělování obvodů do boxů a podobně.
Tento průvodce ukazuje, jak používat generate_boxing_pass_manager() a jeho argumenty. Aby vyniklo působení jednotlivých argumentů funkce, následující části se většinou odkazují na obvod níže.
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)
Seskupení operací do boxů
Argument enable_gates lze nastavit na True nebo False, aby se určilo, zda mají být dvouqubitová hradla seskupena do boxů. Podobně enable_measures určuje, zda se mají seskupovat měření. Následující úryvek ukazuje příklad, kde jsou seskupena hradla i měření.
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)

Jak je vidět na obrázku, pass manager vytváří boxy dvou typů: ty, které obsahují jedinou vrstvu dvouqubitových hradel, a ty, které obsahují jedinou vrstvu měření. Toto oddělení odráží standardní postupy v protokolech učení a zmírňování šumu, které obvykle cílí na vrstvy homogenních operací. Dvouqubitová hradla a měření se umísťují do nejlevějšího boxu, který je pojme, a každé jednoqubitové hradlo se umístí do stejného boxu jako dvouqubitové hradlo nebo měření, které následuje po něm.
Následující kód ukazuje příklad, kde je enable_gates nastaveno na False. Jak je vidět, dvouqubitová hradla nejsou seskupena do boxů, stejně tak jednoqubitová hradla, která jim předcházejí.
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)
Volba způsobu anotace boxů
Všechna dvouqubitová hradla a boxy měření ve vráceném obvodu vlastní anotace s dressingem vlevo. Všechny boxy obsahující dvouqubitová hradla jsou konkrétně anotovány anotací Twirl. U boxů měření mohou uživatelé zvolit Twirl, BasisTranform (s mode nastaveným na "measure"), nebo obojí. Následující kód vygeneruje obvod, kde jsou všechny boxy twirlovány a boxy měření jsou navíc anotovány anotací BasisTranform.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Příprava obvodu na injekci šumu
inject_noise_targets umožňuje určit, které boxy mají dostat anotaci InjectNoise. Následující kód například vygeneruje obvod, kde boxy dvouqubitových hradel vlastní anotaci InjectNoise, ale boxy měření ne.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Pokud obvod obsahuje dva nebo více boxů, které jsou ekvivalentní až na jednoqubitová hradla, jsou všechny anotovány anotací InjectNoise se stejným ref. Počet unikátních ref ve vráceném obvodu je tedy roven počtu unikátních boxů.
Hodnota inject_noise_strategy ovlivňuje anotace InjectNoise. Možné jsou následující hodnoty:
-
modifier_ref='': Doporučeno, pokud nemusíš před vzorkováním upravovat mapy šumu. -
modifier_ref=ref: Doporučeno, pokud je třeba všechny mapy šumu škálovat rovnoměrně stejným faktorem. -
Unikátní hodnota
modifier_ref: Doporučeno, pokud je třeba každou mapu šumu škálovat jiným faktorem.
Následující kód vygeneruje obvod, kde boxy dvouqubitových hradel vlastní anotaci InjectNoise s unikátními hodnotami modifier_ref.
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)
Výběr twirlovací strategie
Boxing pass manager začíná výběrem, které provázající hradla a instrukce měření se mají objevit ve stejných boxech. Jakmile je to hotovo, boxy obecně nebudou mít plnou šířku – budou pravděpodobně zahrnovat jen podmnožinu qubitů, které jsou v obvodu aktivní nebo přítomné v cíli backendu. V typických konfiguracích boxing pass manageru, protože se twirlují všechny qubity, na které instrukce boxu působí (i ty, na které nepůsobí žádná instrukce uvnitř boxu), ovlivňuje použití boxů s částečnou šířkou efekty twirlingu na úpravu šumu. Nečinné qubity se netwirlují a mohou například během aplikace boxu hromadit koherentní chyby. twirling_strategy poskytuje možnosti rozšíření qubitů vlastněných každým boxem, a tím změny počtu nečinných qubitů:
-
"active"(výchozí) nerozšiřuje boxy na žádné nečinné qubity. -
"active_accum"rozšíří všechny boxy na ty qubity, na které už působila předchozí instrukce v obvodu. To je užitečné, pokud chceš nechat qubity na začátku obvodu co nejdéle v základním stavu tím, že je netwirluješ, dokud se nestanou aktivními. -
"active_circuit"rozšíří všechny boxy na ty qubity, na které působí jakákoli instrukce v obvodu. To je užitečné, pokud máš strukturovaný obvod s opakovanými provázajícími vrstvami a chceš minimalizovat počet unikátních boxů, protože u každého je třeba naučit jeho šum. -
"all"rozšíří všechny boxy na všechny qubity ve všech kvantových registrech obvodu. To je užitečné jako rozšíření"active_circuit", kde chceš, aby byly všechny qubity v QPU vždy twirlovány.
V následujícím příkladu se každá ze strategií aplikuje na základní obvod a poté se vykreslí výsledky.
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()

Určení způsobu zacházení s bariérami
Ve výchozím nastavení se bariéry z obvodu odstraní poté, co byla všechna provázající hradla a instrukce měření rozdělena do boxů podle pokynů, včetně jejich rozšíření vlivem volby twirling_strategy, ale před přidáním jednoqubitových hradel do boxu bezprostředně napravo od nich, je-li to možné.
Například následující obvod obsahuje bariéru:
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)
Výchozí chování brání tomu, aby obě provázající hradla skončila ve stejném boxu, i když umožňuje, aby byla jednoqubitová hradla navzdory bariéře pohlcena do dalšího dostupného boxu.
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
Nastavení remove_barriers na 'never' zachová bariéry, takže jednoqubitová hradla jsou omezena.
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)
Konečně, pokud se bariéry odstraní okamžitě, provázající hradla už nejsou omezena tak, aby skončila v oddělených boxech.
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)
Jak funguje rozvrstvení
Boxing pass manager spouští postupně dva klíčové průchody:
-
GroupGatesIntoBoxes— Rozvrství provázající hradla do boxů. Jednoqubitová hradla se v této fázi ignorují; seskupují se pouze dvouqubitová hradla. -
AbsorbSingleQubitGates— Pohltí každé jednoqubitové hradlo do nejbližšího vhodného boxu napravo od něj.
Během rozvrstvování GroupGatesIntoBoxes prochází DAG obvodu v topologickém pořadí a každé dvouqubitové hradlo umístí do nejranější skupiny, jejíž qubity ještě nejsou obsazeny. Tři druhy instrukcí fungují jako oddělovače, které pro své qubity uzavřou aktuální skupinu a donutí následující hradla na těchto qubitech přejít do nové skupiny:
-
Bariéry
-
Existující instrukce box
-
Measurements
Jednoqubitová hradla nejsou oddělovače — nemají vliv na seskupování a zpracuje je AbsorbSingleQubitGates. V současnosti se neprovádí žádná analýza komutativity; seskupování respektuje topologické pořadí obvodu tak, jak je.
Proč je remove_barriers="after_stratification" doporučená výchozí hodnota
Výchozí režim je "after_stratification", protože kombinuje dvě výhody:
-
Bariéry omezují, jak se provázající hradla seskupují (během rozvrstvování fungují jako oddělovače).
-
Bariéry se odstraní před spuštěním
AbsorbSingleQubitGates, takže jednoqubitová hradla mohou stále proudit přes bývalé pozice bariér a být pohlcena do nejbližšího boxu.
Naproti tomu:
-
"never"zachová bariéry po celou dobu, což uvězní jednoqubitová hradla mezi bariérou a boxem, zabrání jejich pohlcení a v obvodu zanechá netwirlované rotace. -
"immediately"odstraní bariéry před rozvrstvením, takže nemají na seskupování žádný vliv. -
"finally"je užitečné, když chceš mít bariéry ve výstupním obvodu (například pro vizualizaci), ale jednoqubitová hradla mezi bariérou a boxem se nepohltí.
Obalení ISA obvodů do boxů pomocí barrier
Obvody s opakující se strukturou — například Trotterizované simulace Hamiltoniánu, variační ansatze a sekvence zesilování chyb — se obvykle vytvářejí programově a poté se transpilují do ISA podoby. Ruční přidávání boxů do transpilovaného obvodu je těžkopádné, takže přirozený postup je následující:
-
Sestav logický obvod a na hranice vrstev, které se mají stát hranicemi boxů, vlož barriery.
-
Transpiluj do ISA obvodů. Transpilátor Qiskit zachovává barriery při směrování i optimalizaci.
-
Spusť
generate_boxing_pass_manager()na ISA obvodu.
Barriery jsou potřeba, když konec jedné vrstvy nepokrývá všechny qubity. Greedy algoritmus by jinak zařadil hradla z další vrstvy — na těchto „volných“ qubitech — do téže skupiny. Jako konkrétní příklad uvažuj dvoukrokovou Heisenbergovu ZZ simulaci na kruhu o 5 qubitech. Každý Trotterův krok má tři vrstvy vazeb: sudé vazby (0,1), (2,3), liché vazby (1,2), (3,4) a uzavírací vazbu (4,0). Po vrstvě lichých vazeb jsou qubity 0 a 4 volné. Bez barriery se uzavírací vazba (4,0) greedy seskupí s vazbami z dalšího Trotterova kroku, které používají tyto volné qubity, a v jednom boxu se tak smíchají hradla z různých kroků.
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)
Následující kód transpiluje obvod do ISA podoby za předpokladu, že podkladové zařízení má jako nativní provázávací hradlo CX. Všimni si, že barriery transpilaci přežijí a zůstanou viditelné ve výstupním obvodu.
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)
Dále aplikuj boxing pass manager na ISA obvod. Každá ze tří vrstev vazeb se v každém Trotterově kroku stane samostatným boxem.
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
Začlenění pass manageru Samplomatic do přednastavených pass managerů Qiskit
Kód níže ukazuje, jak začlenit generate_boxing_pass_manager() do přednastaveného pass manageru Qiskit. Transpilovaný obvod je ISA a obsahuje boxy.
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)
Tento vzor se přirozeně kombinuje i s postupem ISA boxingu založeným na barrierách. Boxing pass manager běží jako krok po plánování, poté co barriery z původního logického obvodu přežily transpilaci, takže řídí stratifikaci podle očekávání.
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)
Kontrola a úprava anotací
Po automatickém boxingu můžeš chtít zkontrolovat anotace na konkrétních boxech nebo je upravit. Například pro přidání injekce šumu, odebrání anotací nebo změnu twirlingové grupy. Samplomatic k tomuto účelu nabízí sadu užitečných funkcí v samplomatic.utils. Všechny vracejí nový obvod, aniž by měnily původní.
extend_annotations() připojí jednu nebo více anotací ke každému boxu v obvodu.
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() ponechá pouze anotace zadaných typů a ostatní odstraní. replace_annotations() aplikuje na každou anotaci callable; ten vrací seznam anotací, které se mají dosadit na její místo (vrať [] pro odstranění, jednoprvkový seznam pro nahrazení nebo víceprvkový seznam pro rozšíření).
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]
)
Univerzální funkce map_annotations() přijímá libovolný callable, který zobrazí seznam anotací na nový seznam, a dává ti tak plnou kontrolu nad transformacemi jednotlivých boxů. strip_annotations() je pohodlný wrapper, který odstraní všechny anotace z každého boxu.
Pro hromadné změny, například přidání InjectNoise do všech boxů s hradly, je nejstručnější možností parametr inject_noise_targets funkce generate_boxing_pass_manager(). Tyto utility jsou užitečné pro selektivní úpravy jednotlivých boxů, které parametry pass manageru nepokrývají.
Sestavení obvodu
Každý pass manager vytvořený pomocí generate_boxing_pass_manager() vrací obvody, které lze funkcí build() úspěšně převést na dvojici šablona/samplex. Například následující kód volá funkci build() na obvodu vytvořeném boxing pass managerem.
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)
Aby bylo možné úspěšně sestavit jakýkoli transpilovaný obvod, pass managery umějí v případě potřeby přidat další boxy. Uvažuj například obvod níže, který končí neměřeným qubitem 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)
Obalení hradel a měření boxy s levým oblečením (left-dressed) by vedlo k obvodu s nesebranými virtuálními hradly na qubitu 0 a volání build() na tomto obvodu by skončilo chybou. Aby se tomu předešlo, mohou pass managery vrácené funkcí generate_boxing_pass_manager() přidat boxy s pravým oblečením (right-dressed), které fungují jako sběrače. Například v následujícím kódu je qubit 0 zakončen boxem s pravým oblečením, který zachytí nesebrané virtuální hradlo. Jednoqubitová hradla působící na qubit 0 jsou rovněž umístěna uvnitř boxu, aby se minimalizovala hloubka výsledného obvodu.
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)
V dalším příkladu se přidá box s pravým oblečením, který sebere virtuální hradla, jež by jinak zůstala nesebraná kvůli neobaleným měřením.
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)