Transpiler
Funcția generate_boxing_pass_manager() este un instrument flexibil și convenabil pentru a construi un qiskit.transpiler.PassManager care poate grupa instrucțiunile circuitului în casete adnotate. Aceasta oferă o alternativă simplă la gruparea și adnotarea manuală a circuitelor și este utilă pentru automatizarea unui flux de lucru, testarea diferitelor strategii de împărțire a circuitelor în casete și așa mai departe.
Acest ghid ilustrează cum se folosește generate_boxing_pass_manager() și argumentele sale. Pentru a evidenția efectele fiecărui argument al funcției, secțiunile următoare se referă în principal la circuitul de mai jos.
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)
Grupează operațiile în casete
Argumentul enable_gates poate fi setat la True sau False pentru a specifica dacă porțile cu doi qubiți trebuie grupate în casete. În mod similar, enable_measures specifică dacă măsurătorile trebuie grupate sau nu. Fragmentul următor arată un exemplu în care atât porțile, cât și măsurătorile sunt grupate.
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)

După cum se poate vedea în figură, pass manager-ul creează casete de două tipuri: cele care conțin un singur strat de porți cu doi qubiți și cele care conțin un singur strat de măsurători. Această separare reflectă practicile standard din protocoalele de învățare și atenuare a zgomotului, care vizează de obicei straturi de operații omogene. Porțile cu doi qubiți și măsurătorile sunt plasate în caseta cea mai din stânga care le poate găzdui, iar fiecare poartă cu un singur qubit este plasată în aceeași casetă cu poarta cu doi qubiți sau cu măsurătoarea pe care o precedă.
Următorul cod arată un exemplu în care enable_gates este setat la False. După cum se poate vedea, porțile cu doi qubiți nu sunt grupate în casete, și nici porțile cu un singur qubit care le precedă.
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)
Alege cum să adnotezi casetele
Toate casetele cu porți cu doi qubiți și cele de măsurători din circuitul returnat dețin adnotări îmbrăcate la stânga. În special, toate casetele care conțin porți cu doi qubiți sunt adnotate cu un Twirl. Pentru casetele de măsurători, utilizatorii pot alege între Twirl, BasisTranform (cu mode setat la "measure") sau ambele. Următorul cod generează un circuit în care toate casetele sunt supuse twirling-ului, iar casetele de măsurători sunt adnotate suplimentar cu BasisTranform.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Pregătește circuitul pentru injectarea zgomotului
inject_noise_targets îți permite să specifici care casete ar trebui să primească o adnotare InjectNoise. De exemplu, următorul cod generează un circuit în care casetele cu porți cu doi qubiți dețin o adnotare InjectNoise, dar casetele de măsurători nu.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
Dacă un circuit conține două sau mai multe casete echivalente până la porți cu un singur qubit, toate sunt adnotate cu o adnotare InjectNoise cu același ref. Astfel, numărul de ref unice din circuitul returnat este egal cu numărul de casete unice.
Valoarea pentru inject_noise_strategy influențează adnotările InjectNoise. Sunt posibile următoarele valori:
-
modifier_ref='': Recomandat când nu trebuie să modifici hărțile de zgomot înainte de a eșantiona din ele. -
modifier_ref=ref: Recomandat când toate hărțile de zgomot trebuie scalate uniform cu același factor. -
O valoare unică pentru
modifier_ref: Recomandat când fiecare hartă de zgomot trebuie scalată cu un factor diferit.
Următorul cod generează un circuit în care casetele cu porți cu doi qubiți dețin o adnotare InjectNoise cu valori unice 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)
Selectează o strategie de twirling
Pass manager-ul de împărțire în casete începe prin a alege care porți de încâlcire și instrucțiuni de măsurare ar trebui să apară în aceleași casete. Odată făcut acest lucru, casetele nu vor avea, în general, lățime completă; vor include probabil doar un subset al qubiților activi în circuit sau prezenți în ținta Backend-ului. În configurațiile tipice ale pass manager-ului de împărțire în casete, deoarece toți qubiții asupra cărora acționează o instrucțiune de tip casetă sunt supuși twirling-ului (chiar și cei asupra cărora nu acționează nicio instrucțiune din interiorul casetei), casetele cu lățime parțială afectează efectele de adaptare a zgomotului ale twirling-ului. Qubiții inactivi nu sunt supuși twirling-ului și pot, de exemplu, acumula erori coerente cât timp caseta este aplicată. twirling_strategy oferă opțiuni pentru extinderea qubiților deținuți de fiecare casetă, modificând astfel numărul de qubiți inactivi:
-
"active"(implicit) nu extinde casetele la niciun qubit inactiv. -
"active_accum"extinde toate casetele la acei qubiți asupra cărora a acționat deja o instrucțiune anterioară din circuit. Este util dacă vrei să lași qubiții de la începutul circuitului în starea lor fundamentală cât mai mult timp, nesupunându-i twirling-ului până când devin activi. -
"active_circuit"extinde toate casetele la acei qubiți asupra cărora acționează orice instrucțiune din circuit. Este util dacă ai un circuit structurat cu straturi de încâlcire repetate și vrei să minimizezi numărul de casete unice, deoarece pentru fiecare trebuie învățat zgomotul. -
"all"extinde toate casetele la toți qubiții din toate registrele cuantice ale circuitului. Este util ca extensie a lui"active_circuit"când vrei ca toți qubiții din QPU să fie întotdeauna supuși twirling-ului.
În exemplul următor, fiecare dintre strategii este aplicată circuitului de bază, apoi rezultatele sunt desenate.
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()

Specifică cum sunt tratate barierele
Implicit, barierele sunt eliminate din circuit după ce toate porțile de încâlcire și instrucțiunile de măsurare au fost încasetate conform instrucțiunilor, inclusiv extensiile lor datorate selecției twirling_strategy, dar înainte ca porțile cu un singur qubit să fie adăugate în caseta din imediata lor dreapta, dacă este posibil.
De exemplu, următorul circuit conține o 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)
Comportamentul implicit împiedică ajungerea celor două porți de încâlcire în aceeași casetă, deși permite absorbirea porților cu un singur qubit în următoarea casetă disponibilă, în ciuda barierei.
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
Setarea remove_barriers la 'never' păstrează barierele, astfel încât porțile cu un singur qubit sunt constrânse.
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)
În final, dacă barierele sunt eliminate imediat, porțile de încâlcire nu mai sunt constrânse să ajungă în casete separate.
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)
Cum funcționează stratificarea
Pass manager-ul de împărțire în casete rulează în secvență două pase cheie:
-
GroupGatesIntoBoxes— Stratifică porțile de încâlcire în casete. Porțile cu un singur qubit sunt ignorate în această etapă; sunt grupate doar porțile cu doi qubiți. -
AbsorbSingleQubitGates— Absoarbe fiecare poartă cu un singur qubit în cea mai apropiată casetă eligibilă din dreapta ei.
În timpul stratificării, GroupGatesIntoBoxes parcurge DAG-ul circuitului în ordine topologică și plasează fiecare poartă cu doi qubiți în cel mai timpuriu grup ai cărui qubiți nu sunt încă ocupați. Trei tipuri de instrucțiuni acționează ca delimitatori care închid grupul curent pentru qubiții lor, forțând porțile ulterioare de pe acei qubiți într-un grup nou:
-
Bariere
-
Instrucțiuni de tip casetă existente
-
Measurements
Porțile cu un singur qubit nu sunt delimitatori — nu au niciun efect asupra grupării și sunt lăsate în seama AbsorbSingleQubitGates. În prezent nu se efectuează nicio analiză de comutativitate; gruparea respectă ordinea topologică a circuitului așa cum este.
De ce remove_barriers="after_stratification" este valoarea implicită recomandată
Modul implicit este "after_stratification" deoarece combină două avantaje:
-
Barierele constrâng modul în care sunt grupate porțile de încâlcire (acționează ca delimitatori în timpul stratificării).
-
Barierele sunt eliminate înainte ca
AbsorbSingleQubitGatessă ruleze, astfel încât porțile cu un singur qubit pot trece în continuare peste fostele poziții ale barierelor și pot fi absorbite în cea mai apropiată casetă.
Prin contrast:
-
"never"păstrează barierele pe tot parcursul, ceea ce blochează porțile cu un singur qubit între o barieră și o casetă, împiedicându-le absorbirea și lăsând rotații fără twirling în circuit. -
"immediately"elimină barierele înainte de stratificare, astfel încât ele nu au niciun efect asupra grupării. -
"finally"este util când vrei bariere în circuitul de ieșire (de exemplu, pentru vizualizare), dar porțile cu un singur qubit dintre o barieră și o casetă nu sunt absorbite.
Încadrează în box-uri circuitele ISA cu bariere
Circuitele cu o structură repetitivă — cum ar fi simulările Hamiltonienelor de tip Trotter, ansatz-urile variaționale și secvențele de amplificare a erorilor — sunt de obicei construite programatic și apoi transpilate în formă ISA. Adăugarea manuală de box-uri într-un circuit transpilat este greoaie, așa că fluxul de lucru firesc este următorul:
-
Construiește circuitul logic, inserând bariere la limitele straturilor care vrei să devină limite de box.
-
Transpilează în circuite ISA. Transpiler-ul Qiskit păstrează barierele pe parcursul rutării și al optimizării.
-
Rulează
generate_boxing_pass_manager()pe circuitul ISA.
Barierele sunt necesare atunci când sfârșitul unui strat nu acoperă toți qubiții. Altfel, algoritmul greedy ar plasa în același grup porțile din stratul următor — de pe acei qubiți „liberi”. Ca exemplu concret, ia în considerare o simulare Heisenberg ZZ în doi pași pe un inel de 5 qubiți. Fiecare pas Trotter are trei straturi de legături: legăturile pare (0,1), (2,3), legăturile impare (1,2), (3,4) și legătura de închidere (4,0). După stratul legăturilor impare, qubiții 0 și 4 sunt amândoi liberi. Fără o barieră, legătura de închidere (4,0) este grupată greedy cu legăturile din pasul Trotter următor care folosesc acei qubiți liberi, amestecând în același box porți din pași diferiți.
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)
Codul următor transpilează circuitul în formă ISA, presupunând că dispozitivul de bază are CX ca poartă de încâlcire nativă. Observă că barierele supraviețuiesc transpilării și rămân vizibile în circuitul rezultat.
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)
Apoi, aplică pass manager-ul de boxing pe circuitul ISA. Fiecare dintre cele trei straturi de legături devine un box separat în fiecare pas Trotter.
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
Încorporează pass manager-ul Samplomatic în pass manager-ele presetate Qiskit
Codul de mai jos arată cum să încorporezi generate_boxing_pass_manager() într-un pass manager presetat Qiskit. Circuitul transpilat este ISA și conține box-uri.
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)
Acest tipar se combină în mod natural și cu fluxul de lucru de boxing ISA bazat pe bariere. Pass manager-ul de boxing rulează ca pas post-planificare, după ce barierele din circuitul logic original au supraviețuit transpilării, astfel încât ghidează stratificarea așa cum te aștepți.
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)
Inspectează și modifică adnotările
După auto-boxing, poate vrei să inspectezi adnotările de pe anumite box-uri sau să le ajustezi. De exemplu, pentru a adăuga injectare de zgomot, a elimina adnotări sau a schimba un grup de twirling. Samplomatic oferă un set de funcții utilitare în samplomatic.utils în acest scop. Toate returnează un circuit nou, fără a modifica originalul.
extend_annotations() adaugă una sau mai multe adnotări la fiecare box din circuit.
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() păstrează doar adnotările de tipurile specificate, eliminându-le pe celelalte. replace_annotations() aplică un callable fiecărei adnotări; callable-ul returnează o listă de adnotări care să le înlocuiască (returnează [] pentru a elimina, o listă cu un singur element pentru a înlocui sau o listă cu mai multe elemente pentru a extinde).
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]
)
Funcția generală map_annotations() acceptă orice callable care transformă o listă de adnotări într-o listă nouă, oferindu-ți control deplin asupra transformărilor per box. strip_annotations() este un wrapper de conveniență care elimină toate adnotările de la fiecare box.
Pentru modificări în masă, cum ar fi adăugarea InjectNoise la toate box-urile de porți, parametrul inject_noise_targets al generate_boxing_pass_manager() este cea mai concisă opțiune. Aceste utilitare sunt utile pentru editări selective per box, pe care parametrii pass manager-ului nu le acoperă.
Construiește un circuit
Fiecare pass manager produs de generate_boxing_pass_manager() returnează circuite care pot fi transformate cu succes într-o pereche template/samplex de către build(). De exemplu, codul următor apelează funcția build() pe un circuit produs de un pass manager de boxing.
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)
Pentru a se asigura că orice circuit transpilat poate fi construit cu succes, pass manager-ele știu să includă box-uri suplimentare atunci când este nevoie. De exemplu, ia în considerare circuitul de mai jos, care se termină cu un qubit 0 nemăsurat.
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)
Desenarea unor box-uri left-dressed în jurul porților și măsurătorilor ar produce un circuit cu porți virtuale necolectate pe qubitul 0, iar apelarea build() pe acest circuit ar duce la o eroare. Pentru a evita acest lucru, pass manager-ele returnate de generate_boxing_pass_manager() pot adăuga box-uri right-dressed care să acționeze drept colectoare. De exemplu, în codul următor, qubitul 0 este încheiat de un box right-dressed care preia poarta virtuală necolectată. Porțile cu un singur qubit care acționează asupra qubitului 0 sunt plasate și ele în interiorul box-ului, pentru a minimiza adâncimea circuitului rezultat.
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)
Într-un alt exemplu, se adaugă un box right-dressed pentru a colecta porțile virtuale care altfel ar rămâne necolectate din cauza măsurătorilor neîncadrate în box-uri.
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)