Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Transpiler

La funzione generate_boxing_pass_manager() è uno strumento flessibile e pratico per costruire un qiskit.transpiler.PassManager in grado di raggruppare le istruzioni del circuito in box annotati. Offre un'alternativa semplice al raggruppamento e all'annotazione manuali dei circuiti ed è utile per automatizzare un flusso di lavoro, testare diverse strategie di suddivisione dei circuiti in box e così via.

Questa guida illustra come usare generate_boxing_pass_manager() e i suoi argomenti. Per evidenziare gli effetti di ciascun argomento della funzione, le sezioni seguenti fanno riferimento principalmente al circuito qui sotto.

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)

../_images/cf27e1ef03e51b2360c90165a4efeaa793da36083bb674774ca06c8181ef63dd.png

Raggruppare le operazioni in box​

L'argomento enable_gates può essere impostato su True o False per specificare se i gate a due qubit debbano essere raggruppati in box. Analogamente, enable_measures specifica se le misure debbano essere raggruppate o meno. Il frammento seguente mostra un esempio in cui sia i gate sia le misure vengono raggruppati.

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)

../_images/b323dde8a5412e42359aef1831af92ed3702d2251547c663208dc084a2995276.png

Come si può vedere nella figura, il pass manager crea box di due tipi: quelli che contengono un singolo livello di gate a due qubit e quelli che contengono un singolo livello di misure. Questa separazione riflette le pratiche standard nei protocolli di apprendimento e mitigazione del rumore, che di solito si rivolgono a livelli di operazioni omogenee. I gate a due qubit e le misure vengono collocati nel box più a sinistra che possa accoglierli, e ogni gate a singolo qubit viene collocato nello stesso box del gate a due qubit o della misura che segue.

Il codice seguente mostra un esempio in cui enable_gates è impostato su False. Come si può vedere, i gate a due qubit non vengono raggruppati in box, e nemmeno i gate a singolo qubit che li precedono.

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)

../_images/3fc8e660b0c7eff1f5aa2e571d064f91b3eb2738c5990727e026a8ffe1101a1c.png

Scegliere come annotare i box​

Tutti i box dei gate a due qubit e delle misure nel circuito restituito possiedono annotazioni left-dressed. In particolare, tutti i box che contengono gate a due qubit sono annotati con un Twirl. Per i box di misura, gli utenti possono scegliere tra Twirl, BasisTranform (con mode impostato su "measure") o entrambi. Il codice seguente genera un circuito in cui tutti i box hanno il twirling e i box di misura sono inoltre annotati con BasisTranform.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Preparare il circuito per l'iniezione del rumore​

inject_noise_targets ti permette di specificare quali box devono ricevere un'annotazione InjectNoise. Ad esempio, il codice seguente genera un circuito in cui i box dei gate a due qubit possiedono un'annotazione InjectNoise ma i box di misura no.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Se un circuito contiene due o più box equivalenti a meno di gate a singolo qubit, tutti vengono annotati con un'annotazione InjectNoise con lo stesso ref. Quindi il numero di ref univoci nel circuito restituito è uguale al numero di box univoci.

Il valore di inject_noise_strategy influisce sulle annotazioni InjectNoise. Sono possibili i valori seguenti:

  • modifier_ref='': consigliato quando non occorre modificare le mappe di rumore prima di campionarle.

  • modifier_ref=ref: consigliato quando tutte le mappe di rumore devono essere scalate in modo uniforme dello stesso fattore.

  • Un valore univoco di modifier_ref: consigliato quando ogni mappa di rumore deve essere scalata di un fattore diverso.

Il codice seguente genera un circuito in cui i box dei gate a due qubit possiedono un'annotazione InjectNoise con valori modifier_ref univoci.

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)

Selezionare una strategia di twirling​

Il boxing pass manager inizia scegliendo quali gate entangling e istruzioni di misura debbano comparire negli stessi box. Una volta fatto ciò, in generale i box non avranno larghezza piena: probabilmente includeranno solo un sottoinsieme dei qubit attivi nel circuito o presenti nel target del backend. Nelle configurazioni tipiche del boxing pass manager, poiché tutti i qubit su cui agisce un'istruzione box vengono sottoposti a twirling (anche quelli su cui non agisce alcuna istruzione all'interno del box), avere box a larghezza parziale influisce sugli effetti di noise tailoring del twirling. I qubit inattivi non vengono sottoposti a twirling e possono, ad esempio, accumulare errori coerenti mentre il box viene applicato. twirling_strategy offre opzioni per estendere i qubit posseduti da ciascun box, modificando così il numero di qubit inattivi:

  • "active" (predefinito) non estende i box ad alcun qubit inattivo.

  • "active_accum" estende tutti i box ai qubit su cui ha già agito un'istruzione precedente nel circuito. È utile se vuoi lasciare i qubit all'inizio del circuito nel loro stato fondamentale il più a lungo possibile, non applicando il twirling finché non diventano attivi.

  • "active_circuit" estende tutti i box ai qubit su cui agisce una qualsiasi istruzione nel circuito. È utile se hai un circuito strutturato con livelli entangling ripetuti e vuoi minimizzare il numero di box univoci, perché per ciascuno occorre apprendere il rumore.

  • "all" estende tutti i box a tutti i qubit di tutti i registri quantistici del circuito. È utile come estensione di "active_circuit" quando vuoi che tutti i qubit della QPU siano sempre sottoposti a twirling.

Nell'esempio seguente, ciascuna strategia viene applicata al circuito di base e poi vengono disegnati i risultati.

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)

../_images/cdf089891dabe1650f1303c8c747aa8cf03cdde4dfc42c772fe2264bd7a49a1b.png

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()

../_images/b810ecc4c65822cf60b3d652792a4089c5f931ad1e8320eccdd89bc50a08ba6d.png

Specificare come trattare le barrier​

Per impostazione predefinita, le barrier vengono rimosse dal circuito dopo che tutti i gate entangling e le istruzioni di misura sono stati inseriti nei box come indicato, comprese le loro estensioni dovute alla selezione di twirling_strategy, ma prima che i gate a singolo qubit vengano aggiunti al box immediatamente alla loro destra, se possibile.

Ad esempio, il circuito seguente contiene una barrier:

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)

../_images/f2343807597155ab5301205803954b95d29ba0d7370440bd861439fd83d1a193.png

Il comportamento predefinito impedisce che i due gate entangling finiscano nello stesso box, ma lascia che i gate a singolo qubit vengano assorbiti nel successivo box disponibile nonostante la barrier.

boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

Impostare remove_barriers su 'never' preserva le barrier in modo che i gate a singolo qubit siano vincolati.

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)

../_images/6b57e625509dd8ca55fbd96994398b03ac2fe6f76f21535db9bd042d4c68c987.png

Infine, se le barrier vengono rimosse immediatamente, gli entangler non sono più vincolati a finire in box separati.

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)

../_images/9a44c59a05a44a3e8f0ce9b7edd3ccc2896c6a68fb00ed110a7e225f52a7ca73.png

Come funziona la stratificazione​

Il boxing pass manager esegue in sequenza due pass chiave:

  1. GroupGatesIntoBoxes — stratifica i gate entangling in box. I gate a singolo qubit vengono ignorati in questa fase; vengono raggruppati solo i gate a due qubit.

  2. AbsorbSingleQubitGates — assorbe ogni gate a singolo qubit nel box idoneo più vicino alla sua destra.

Durante la stratificazione, GroupGatesIntoBoxes attraversa il DAG del circuito in ordine topologico e colloca ogni gate a due qubit nel primo gruppo i cui qubit non sono ancora occupati. Tre tipi di istruzioni agiscono come delimitatori che chiudono il gruppo corrente per i loro qubit, costringendo i gate successivi su quei qubit a entrare in un nuovo gruppo:

  • Barriere

  • Istruzioni box esistenti

  • Measurements

I gate a singolo qubit non sono delimitatori: non hanno effetto sul raggruppamento e vengono lasciati a AbsorbSingleQubitGates. Attualmente non viene eseguita alcuna analisi di commutatività; il raggruppamento rispetta l'ordine topologico del circuito così com'è.

La modalità predefinita è "after_stratification" perché combina due vantaggi:

  • Le barrier vincolano il modo in cui i gate entangling vengono raggruppati (agiscono come delimitatori durante la stratificazione).

  • Le barrier vengono rimosse prima dell'esecuzione di AbsorbSingleQubitGates, quindi i gate a singolo qubit possono comunque attraversare le ex posizioni delle barrier ed essere assorbiti nel box più vicino.

Al contrario:

  • "never" mantiene le barrier per tutto il tempo, il che intrappola i gate a singolo qubit tra una barrier e un box, impedendone l'assorbimento e lasciando rotazioni senza twirling nel circuito.

  • "immediately" rimuove le barrier prima della stratificazione, quindi non hanno alcun effetto sul raggruppamento.

  • "finally" è utile quando vuoi le barrier nel circuito di output (ad esempio per la visualizzazione), ma i gate a singolo qubit tra una barrier e un box non vengono assorbiti.

Box di circuiti ISA con barriere​

I circuiti con una struttura ripetuta — come le simulazioni hamiltoniane con Trotterizzazione, gli ansatz variazionali e le sequenze di amplificazione degli errori — vengono tipicamente costruiti in modo programmatico e poi sottoposti a transpilazione in forma ISA. Aggiungere manualmente dei box a un circuito transpilato è scomodo, quindi il flusso di lavoro naturale è il seguente:

  1. Costruisci il circuito logico, inserendo barriere ai confini dei layer che vuoi diventino confini dei box.

  2. Esegui la transpilazione in circuiti ISA. Il Transpiler di Qiskit preserva le barriere durante il routing e l'ottimizzazione.

  3. Esegui generate_boxing_pass_manager() sul circuito ISA.

Le barriere sono necessarie quando la fine di un layer non copre tutti i qubit. Altrimenti l'algoritmo greedy collocherebbe nello stesso gruppo i gate del layer successivo—sui qubit "liberi". Come esempio concreto, considera una simulazione di Heisenberg ZZ in due passi su un anello di 5 qubit. Ogni passo di Trotter ha tre layer di legami: i legami pari (0,1), (2,3), i legami dispari (1,2), (3,4) e il legame di chiusura (4,0). Dopo il layer dei legami dispari, i qubit 0 e 4 sono entrambi liberi. Senza una barriera, il legame di chiusura (4,0) viene raggruppato in modo greedy con i legami del passo di Trotter successivo che usano quei qubit liberi, mescolando nello stesso box gate di passi diversi.

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)

../_images/172fa55c9f02964fef32fc045a50b86971126be728602b3f1e4b2437df2580b7.png

Il codice seguente esegue la transpilazione del circuito in forma ISA, supponendo che il dispositivo sottostante abbia CX come entangler nativo. Nota che le barriere sopravvivono alla transpilazione e restano visibili nel circuito di output.

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)

../_images/e9a3fbd6ab1a91051c5cd0273d92ecebc6ae8ebbc370c30bc55daee1fd28bab7.png

Poi applica il boxing pass manager al circuito ISA. Ciascuno dei tre layer di legami diventa un box separato in ogni passo di Trotter.

boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)

../_images/b59e3c047264b3340d9af0f1ca74ad9f7d4c1705d4a5176e441346db9b678213.png

Incorporare il pass manager di Samplomatic nei pass manager preset di Qiskit​

Il codice seguente mostra come incorporare generate_boxing_pass_manager() in un pass manager preset di Qiskit. Il circuito transpilato è ISA e contiene dei box.

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)

../_images/fe943b034588a5354a87abcf51b8d805c36f4f2b30a73a39c5886bce9e156b04.png

Questo schema si combina naturalmente anche con il flusso di lavoro di boxing ISA basato sulle barriere. Il boxing pass manager viene eseguito come passo successivo allo scheduling, dopo che le barriere del circuito logico originale sono sopravvissute alla transpilazione, così guidano la stratificazione come previsto.

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)

../_images/4905bf1df63cc83fa581ed006a93beb709be504a4f59fc7f663f4efde62da175.png

Ispezionare e modificare le annotazioni​

Dopo l'auto-boxing, potresti voler ispezionare le annotazioni di specifici box o modificarle. Ad esempio, per aggiungere iniezione di rumore, rimuovere annotazioni o cambiare un gruppo di twirling. Samplomatic fornisce a questo scopo un insieme di funzioni di utilità in samplomatic.utils. Tutte restituiscono un nuovo circuito senza modificare l'originale.

extend_annotations() aggiunge una o più annotazioni a ogni box del circuito.

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)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

filter_annotations() mantiene solo le annotazioni dei tipi specificati, rimuovendo le altre. replace_annotations() applica un callable a ciascuna annotazione; il callable restituisce una lista di annotazioni da sostituire al suo posto (restituisci [] per rimuovere, una lista con un solo elemento per sostituire, o una lista con più elementi per espandere).

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]
)

La funzione generica map_annotations() accetta qualsiasi callable che trasformi una lista di annotazioni in una nuova lista, dandoti il pieno controllo sulle trasformazioni per singolo box. strip_annotations() è un wrapper di comodo che rimuove tutte le annotazioni da ogni box.

Per modifiche in blocco, come aggiungere InjectNoise a tutti i box di gate, il parametro inject_noise_targets di generate_boxing_pass_manager() è l'opzione più concisa. Queste utilità sono utili per modifiche selettive per singolo box che i parametri del pass manager non coprono.

Costruire un circuito​

Ogni pass manager prodotto da generate_boxing_pass_manager() restituisce circuiti che possono essere trasformati con successo in una coppia template/samplex da build(). Ad esempio, il codice seguente chiama la funzione build() su un circuito prodotto da un 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)

Per garantire che qualsiasi circuito transpilato possa essere costruito con successo, i pass manager sanno includere box aggiuntivi quando servono. Ad esempio, considera il circuito seguente, che termina con un qubit 0 non misurato.

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)

../_images/0452779799470d2deb4d32ce19f5aa28dfe8055a064e67c0e8e5684389845b0f.png

Disegnare box left-dressed attorno ai gate e alle misure produrrebbe un circuito con gate virtuali non raccolti sul qubit 0, e chiamare build() su questo circuito causerebbe un errore. Per evitarlo, i pass manager restituiti da generate_boxing_pass_manager() possono aggiungere box right-dressed che fungono da collettori. Ad esempio, nel codice seguente il qubit 0 termina con un box right-dressed che raccoglie il gate virtuale non raccolto. Anche i gate a singolo qubit che agiscono sul qubit 0 vengono collocati dentro il box per minimizzare la profondità del circuito risultante.

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)

../_images/711ee3c014edf6f70ede5dfa849494286e96870b3bd70a2a89f687d83cc5bfc1.png

In un altro esempio, viene aggiunto un box right-dressed per raccogliere i gate virtuali che altrimenti resterebbero non raccolti a causa delle misure non incluse in box.

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)

../_images/d59d0c0af3c47f474a964a455533b99e09a65364454ec04ced7581e6afac73ab.png