Transpiler
Funkcja generate_boxing_pass_manager() jest elastycznym i wygodnym narzędziem do budowania qiskit.transpiler.PassManager, który potrafi grupować instrukcje obwodu w adnotowane boxy. Daje to łatwą alternatywę dla ręcznego grupowania i adnotowania obwodów i jest przydatne do automatyzacji przepływu pracy, testowania różnych strategii dzielenia obwodu na boxy i tak dalej.
Ten przewodnik ilustruje, jak używać generate_boxing_pass_manager() i jego argumentów. Aby podkreślić skutki każdego z argumentów funkcji, kolejne sekcje odnoszą się głównie do poniższego obwodu.
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)
Grupowanie operacji w boxy
Argument enable_gates można ustawić na True lub False, aby określić, czy bramki dwukubitowe mają być grupowane w bloki. Podobnie enable_measures określa, czy pomiary mają być grupowane. Poniższy fragment pokazuje przykład, w którym grupowane są zarówno bramki, jak i pomiary.
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 widać na rysunku, pass manager tworzy boxy dwóch typów: te zawierające pojedynczą warstwę bramek dwukubitowych oraz te zawierające pojedynczą warstwę pomiarów. Ten podział odzwierciedla standardowe praktyki w protokołach uczenia i łagodzenia szumu, które zwykle dotyczą warstw jednorodnych operacji. Bramki dwukubitowe i pomiary są umieszczane w skrajnie lewym boxie, który może je pomieścić, a każda bramka jednokubitowa trafia do tego samego boxa co bramka dwukubitowa lub pomiar, które po niej następują.
Poniższy kod pokazuje przykład, w którym enable_gates ustawiono na False. Jak widać, bramki dwukubitowe nie są grupowane w boxy, podobnie jak poprzedzające je bramki jednokubitowe.
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)
Wybór sposobu adnotowania boxów
Wszystkie boxy z bramkami dwukubitowymi i pomiarami w zwróconym obwodzie mają adnotacje lewostronnego dressingu. W szczególności wszystkie boxy zawierające bramki dwukubitowe są adnotowane za pomocą Twirl. Dla boxów pomiarowych użytkownicy mogą wybrać Twirl, BasisTranform (z mode ustawionym na "measure") lub oba. Poniższy kod generuje obwód, w którym wszystkie boxy podlegają twirlingowi, a boxy pomiarowe są dodatkowo adnotowane za pomocą BasisTranform.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Przygotowanie obwodu do wstrzykiwania szumu
inject_noise_targets pozwala określić, które boxy mają otrzymać adnotację InjectNoise. Na przykład poniższy kod generuje obwód, w którym boxy z bramkami dwukubitowymi mają adnotację InjectNoise, a boxy pomiarowe jej nie mają.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Jeśli obwód zawiera dwa lub więcej boxów, które są równoważne z dokładnością do bramek jednokubitowych, wszystkie z nich są adnotowane adnotacją InjectNoise z tym samym ref. Zatem liczba unikalnych ref w zwróconym obwodzie jest równa liczbie unikalnych boxów.
Wartość inject_noise_strategy wpływa na adnotacje InjectNoise. Możliwe są następujące wartości:
-
modifier_ref='': Zalecane, gdy nie musisz modyfikować map szumu przed próbkowaniem z nich. -
modifier_ref=ref: Zalecane, gdy wszystkie mapy szumu trzeba przeskalować jednolicie tym samym czynnikiem. -
Unikalna wartość
modifier_ref: Zalecane, gdy każdą mapę szumu trzeba przeskalować innym czynnikiem.
Poniższy kod generuje obwód, w którym boxy z bramkami dwukubitowymi mają adnotację InjectNoise z unikalnymi wartościami 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)
Wybór strategii twirlingu
Boxing pass manager zaczyna od wyboru, które bramki splatające i instrukcje pomiaru mają znaleźć się w tych samych boxach. Po tym boxy na ogół nie mają pełnej szerokości — będą zawierać tylko podzbiór kubitów aktywnych w obwodzie lub obecnych w docelowym backendzie. W typowych konfiguracjach boxing pass managera, ponieważ twirlingowi podlegają wszystkie kubity, na których działa instrukcja boxa (nawet te, na których nie działa żadna instrukcja wewnątrz boxa), boxy częściowej szerokości wpływają na efekty twirlingu związane z dopasowaniem szumu. Kubity bezczynne nie podlegają twirlingowi i mogą na przykład gromadzić błędy koherentne podczas stosowania boxa. twirling_strategy udostępnia opcje rozszerzania kubitów należących do każdego boxa, modyfikując w ten sposób liczbę kubitów bezczynnych:
-
"active"(domyślnie) nie rozszerza boxów o żadne kubity bezczynne. -
"active_accum"rozszerza wszystkie boxy o te kubity, na których działała już wcześniejsza instrukcja w obwodzie. Jest to przydatne, jeśli chcesz jak najdłużej zostawiać kubity na początku obwodu w stanie podstawowym, nie wykonując ich twirlingu, dopóki nie staną się aktywne. -
"active_circuit"rozszerza wszystkie boxy o te kubity, na których działa jakakolwiek instrukcja w obwodzie. Jest to przydatne, jeśli masz obwód o strukturze z powtarzającymi się warstwami splatającymi i chcesz zminimalizować liczbę unikalnych boxów, ponieważ dla każdego trzeba nauczyć się jego szumu. -
"all"rozszerza wszystkie boxy o wszystkie kubity we wszystkich rejestrach kwantowych obwodu. Jest to przydatne jako rozszerzenie"active_circuit", gdy chcesz, aby wszystkie kubity w QPU zawsze podlegały twirlingowi.
W poniższym przykładzie każda ze strategii jest stosowana do obwodu bazowego, a następnie wyniki są rysowane.
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()

Określanie sposobu traktowania barier
Domyślnie bariery są usuwane z obwodu po tym, jak wszystkie bramki splatające i instrukcje pomiaru zostały umieszczone w boxach zgodnie z instrukcjami, wraz z ich rozszerzeniami wynikającymi z wyboru twirling_strategy, ale przed dodaniem bramek jednokubitowych do boxa bezpośrednio po ich prawej stronie, jeśli to możliwe.
Na przykład poniższy obwód zawiera barierę:
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)
Zachowanie domyślne uniemożliwia umieszczenie obu bramek splatających w tym samym boxie, chociaż pozwala bramkom jednokubitowym zostać wchłoniętym do następnego dostępnego boxa mimo bariery.
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
Ustawienie remove_barriers na 'never' zachowuje bariery, tak że bramki jednokubitowe są ograniczone.
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)
Wreszcie, jeśli bariery zostaną usunięte natychmiast, bramki splatające nie są już ograniczone do trafiania do osobnych boxów.
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 działa stratyfikacja
Boxing pass manager uruchamia kolejno dwa kluczowe przebiegi:
-
GroupGatesIntoBoxes— Stratyfikuje bramki splatające w boxy. Bramki jednokubitowe są na tym etapie ignorowane; grupowane są tylko bramki dwukubitowe. -
AbsorbSingleQubitGates— Wchłania każdą bramkę jednokubitową do najbliższego kwalifikującego się boxa po jej prawej stronie.
Podczas stratyfikacji GroupGatesIntoBoxes przechodzi DAG obwodu w porządku topologicznym i umieszcza każdą bramkę dwukubitową w najwcześniejszej grupie, której kubity nie są jeszcze zajęte. Trzy rodzaje instrukcji działają jako ograniczniki, które zamykają bieżącą grupę dla swoich kubitów, wymuszając umieszczenie kolejnych bramek na tych kubitach w nowej grupie:
-
Bariery
-
Istniejące instrukcje box
-
Measurements
Bramki jednokubitowe nie są ogranicznikami — nie wpływają na grupowanie i są pozostawiane AbsorbSingleQubitGates do obsłużenia. Obecnie nie przeprowadza się analizy komutatywności; grupowanie respektuje porządek topologiczny obwodu w niezmienionej postaci.
Dlaczego remove_barriers="after_stratification" jest zalecanym ustawieniem domyślnym
Tryb domyślny to "after_stratification", ponieważ łączy dwie korzyści:
-
Bariery ograniczają sposób grupowania bramek splatających (działają jako ograniczniki podczas stratyfikacji).
-
Bariery są usuwane przed uruchomieniem
AbsorbSingleQubitGates, więc bramki jednokubitowe nadal mogą przepływać przez dawne pozycje barier i być wchłaniane do najbliższego boxa.
Dla porównania:
-
"never"zachowuje bariery przez cały czas, co więzi bramki jednokubitowe między barierą a boxem, uniemożliwiając ich wchłonięcie i pozostawiając w obwodzie obroty bez twirlingu. -
"immediately"usuwa bariery przed stratyfikacją, więc nie mają one wpływu na grupowanie. -
"finally"jest przydatne, gdy chcesz mieć bariery w obwodzie wyjściowym (na przykład do wizualizacji), ale bramki jednokubitowe między barierą a boxem nie są wchłaniane.
Obramowuj obwody ISA w boxy za pomocą barier
Obwody o powtarzalnej strukturze — takie jak symulacje Hamiltonianów metodą Trottera, ansatze wariacyjne i sekwencje wzmacniania błędów — zwykle buduje się programowo, a następnie transpiluje do postaci ISA. Ręczne dodawanie boxów do transpilowanego obwodu jest uciążliwe, dlatego naturalny przebieg pracy wygląda następująco:
-
Zbuduj obwód logiczny, wstawiając bariery na granicach warstw, które mają stać się granicami boxów.
-
Transpiluj do obwodów ISA. Transpiler Qiskit zachowuje bariery podczas routingu i optymalizacji.
-
Uruchom
generate_boxing_pass_manager()na obwodzie ISA.
Bariery są potrzebne, gdy koniec jednej warstwy nie obejmuje wszystkich kubitów. Algorytm zachłanny w przeciwnym razie umieściłby bramki z następnej warstwy — działające na tych „wolnych” kubitach — w tej samej grupie. Jako konkretny przykład weźmy dwukrokową symulację Heisenberga ZZ na pierścieniu 5 kubitów. Każdy krok Trottera ma trzy warstwy wiązań: wiązania parzyste (0,1), (2,3), wiązania nieparzyste (1,2), (3,4) oraz wiązanie zamykające (4,0). Po warstwie wiązań nieparzystych kubity 0 i 4 są wolne. Bez bariery wiązanie zamykające (4,0) zostaje zachłannie zgrupowane z wiązaniami z następnego kroku Trottera, które używają tych wolnych kubitów, co miesza bramki z różnych kroków w jednym boxie.
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)
Poniższy kod transpiluje obwód do postaci ISA, zakładając, że urządzenie docelowe ma CX jako natywny element splątujący. Zwróć uwagę, że bariery przetrwały transpilację i pozostają widoczne w obwodzie wyjściowym.
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)
Następnie zastosuj boxing pass manager do obwodu ISA. Każda z trzech warstw wiązań staje się osobnym boxem w każdym kroku Trottera.
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
Włączanie pass managera Samplomatic do predefiniowanych pass managerów Qiskit
Poniższy kod pokazuje, jak włączyć generate_boxing_pass_manager() do predefiniowanego pass managera Qiskit. Transpilowany obwód jest w postaci ISA i zawiera 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)
Ten wzorzec naturalnie łączy się również z przepływem pracy obramowywania ISA opartym na barierach. Boxing pass manager działa jako krok po szeregowaniu, gdy bariery z oryginalnego obwodu logicznego przetrwały transpilację, więc kierują stratyfikacją zgodnie z oczekiwaniami.
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)
Sprawdzanie i modyfikowanie adnotacji
Po automatycznym obramowaniu w boxy możesz chcieć sprawdzić adnotacje na wybranych boxach lub je zmienić. Na przykład, aby dodać wstrzykiwanie szumu, usunąć adnotacje albo zmienić grupę twirlingu. Samplomatic udostępnia w tym celu zestaw funkcji pomocniczych w samplomatic.utils. Wszystkie zwracają nowy obwód, nie modyfikując oryginału.
extend_annotations() dołącza jedną lub więcej adnotacji do każdego boxa w obwodzie.
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() zachowuje tylko adnotacje wskazanych typów, usuwając pozostałe. replace_annotations() stosuje funkcję wywoływalną do każdej adnotacji; funkcja ta zwraca listę adnotacji, które mają zostać podstawione w jej miejsce (zwróć [], aby usunąć, listę jednoelementową, aby zastąpić, lub listę wieloelementową, aby rozszerzyć).
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]
)
Uniwersalna funkcja map_annotations() przyjmuje dowolną funkcję wywoływalną, która odwzorowuje listę adnotacji na nową listę, dając pełną kontrolę nad przekształceniami poszczególnych boxów. strip_annotations() to wygodna nakładka, która usuwa wszystkie adnotacje z każdego boxa.
Przy zmianach zbiorczych, takich jak dodanie InjectNoise do wszystkich boxów z bramkami, najbardziej zwięzłą opcją jest parametr inject_noise_targets funkcji generate_boxing_pass_manager(). Te narzędzia przydają się przy selektywnych edycjach pojedynczych boxów, których parametry pass managera nie obejmują.
Budowanie obwodu
Każdy pass manager utworzony przez generate_boxing_pass_manager() zwraca obwody, które można pomyślnie zamienić na parę szablon/samplex za pomocą build(). Na przykład poniższy kod wywołuje funkcję build() na obwodzie utworzonym przez boxing pass manager.
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 zapewnić, że każdy transpilowany obwód da się pomyślnie zbudować, pass managery potrafią w razie potrzeby dodawać dodatkowe boxy. Rozważ na przykład poniższy obwód, który kończy się niezmierzonym kubitem 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)
Narysowanie boxów lewostronnie ubranych wokół bramek i pomiarów dałoby obwód z niezebranymi bramkami wirtualnymi na kubicie 0, a wywołanie build() na tym obwodzie zakończyłoby się błędem. Aby tego uniknąć, pass managery zwracane przez generate_boxing_pass_manager() mogą dodawać boxy prawostronnie ubrane, które pełnią rolę zbieraczy. Na przykład w poniższym kodzie kubit 0 kończy się boxem prawostronnie ubranym, który zbiera niezebraną bramkę wirtualną. Bramki jednokubitowe działające na kubicie 0 są również umieszczone wewnątrz boxa, aby zminimalizować głębokość powstałego obwodu.
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)
W innym przykładzie dodawany jest box prawostronnie ubrany, który zbiera bramki wirtualne, które w przeciwnym razie pozostałyby niezebrane z powodu pomiarów poza boxami.
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)