Skip to main content
IBM Quantum Platform

Debug e tracciamento

La funzione build() trasforma un circuito annotato in un circuito modello e in una coppia samplex. In un circuito complesso può risultare difficile stabilire quale casella del circuito originale corrisponda a quali barriere nel modello, oppure a quali nodi nel DAG di Samplex.

Samplomatic mette a disposizione due strumenti di tracciamento complementari:

  • Etichette delle barriere : ogni barriera nel circuito del modello è contrassegnata da un'etichetta che ne indica l'origine. Questi sono sempre presenti, indipendentemente dal fatto che debug=True sia impostato o meno.
  • Informazioni di tracciamento sui nodi samplex : quando build() viene chiamato con debug=True, ogni nodo samplex contiene un oggetto TraceInfo che lo ricollega alla casella o alle caselle che lo hanno generato.

Entrambi gli strumenti risultano particolarmente utili quando i riquadri contengono un’annotazione Tag , che associa una stringa ref al riquadro. In alternativa, sono allegati anche i riferimenti InjectNoise .


Caselle dei tag

Un'annotazione Tag associa una stringa ref a un riquadro. Il simbolo compare ref quindi nelle etichette delle barriere e nelle informazioni di tracciamento sui nodi samplex.

Il seguente circuito di esempio presenta tre caselle: due caselle CX etichettate su coppie di qubit disgiunte e una casella di misurazione "right-dressed" non etichettata. Anche la seconda casella CX presenta un’annotazione InjectNoise : sia il suo tag ref che il suo rumore ref appariranno nelle etichette delle barriere. Le due caselle CX coprono qubit diversi, il che consente all’ottimizzatore Samplex di unire i loro nodi di propagazione in nodi condivisi. Ciò è indicato nella sezione delle informazioni di tracciamento.

from qiskit.circuit import QuantumCircuit

from samplomatic import InjectNoise, Tag, Twirl, build

circuit = QuantumCircuit(4, 4)

with circuit.box([Twirl(), Tag("cx_ab")]):
    circuit.cx(0, 1)

with circuit.box([Twirl(), InjectNoise("cx_noise"), Tag("cx_cd")]):
    circuit.cx(2, 3)

with circuit.box([Twirl(), Tag("meas_box")]):
    circuit.measure(range(4), range(4))

circuit.draw("mpl")
../_images/f6b6fe7b60a39ba897814008443fa79b66213005462ab09f49ffc89aab1f3668.png

Etichette di barriera nel circuito del modello

La chiamata build() al circuito sopra riportato genera un modello le cui barriere recano etichette identificative. Ogni etichetta ha la forma {side}{scope}@{key=value&...}:

  • Lato : L = bordo con allineamento a sinistra, M = bordo del contenuto del riquadro interno, R = bordo con allineamento a destra.
  • Ambito : un indice intero (o un elenco separato da trattini bassi nel caso di riquadri annidati) che distingue più riquadri allo stesso livello di annidamento.
  • Annotazioni : un elenco di coppie key=value , @precedute dal prefisso "-" e &separate da "-", ricavate dalle annotazioni della casella. La chiave tag proviene da un'annotazione Tag e inject_noise proviene da un'annotazione InjectNoise .

Le barriere provenienti da caselle senza etichetta riportano solo il lato e l'estensione (ad esempio, L2), senza alcun suffisso @ .

template, samplex = build(circuit)
template.draw("mpl", fold=100)
../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

Le etichette delle barriere possono anche essere estratte a livello di programmazione:

barrier_labels = [instr.operation.label for instr in template if instr.operation.name == "barrier"]
barrier_labels
['L0@tag=cx_ab',
 'M0@tag=cx_ab',
 'R0@tag=cx_ab',
 'L1@inject_noise=cx_noise&tag=cx_cd',
 'M1@inject_noise=cx_noise&tag=cx_cd',
 'R1@inject_noise=cx_noise&tag=cx_cd',
 'L2@tag=meas_box',
 'M2@tag=meas_box',
 'R2@tag=meas_box']

Informazioni sulle tracce nei nodi Samplex

Il passaggio debug=True a build() aggiunge informazioni di tracciamento a ogni nodo samplex. L'attributo trace_info di ciascun nodo è un TraceInfo oggetto il cui dizionario trace_refs associa le chiavi delle annotazioni (ad esempio, "tag" e "inject_noise") a insiemi di stringhe di riferimento. I nodi privi di una corrispondente annotazione in un riquadro hanno trace_info=None.

Quando draw() si chiama questa funzione su un samplex compilato in modalità debug, passando il mouse su un nodo qualsiasi del grafico interattivo, ne viene visualizzata la descrizione all’interno trace_refs del tooltip che appare al passaggio del mouse.

template, samplex = build(circuit, debug=True)
samplex.draw()

È inoltre possibile esaminare le informazioni di tracciamento a livello di programmazione. Si noti che alcuni nodi contengono riferimenti sia da 'cx_ab' che da 'cx_cd'. L'ottimizzatore Samplex ha unito i nodi di propagazione paralleli poiché i due riquadri coprono qubit disgiunti e condividono un predecessore comune proveniente dall'emissione del riquadro di misurazione "right-dressed".

for node in samplex.graph.nodes():
    if node.trace_info is not None:
        tags = node.trace_info.trace_refs.get("tag", set())
        merged = " ← merged" if len(tags) > 1 else ""
        print(f"{type(node).__name__:40s}  tags={tags}{merged}")
TwirlSamplingNode                         tags={'meas_box'}
InjectNoiseNode                           tags={'cx_cd'}
CollectZ2ToOutputNode                     tags={'cx_cd'}
TwirlSamplingNode                         tags={'cx_cd'}
CombineRegistersNode                      tags={'cx_cd'}
CollectTemplateValues                     tags={'cx_cd'}
TwirlSamplingNode                         tags={'cx_ab'}
CombineRegistersNode                      tags={'cx_ab', 'cx_cd'} ← merged
CollectTemplateValues                     tags={'cx_ab', 'cx_cd'} ← merged

Per individuare tutti i nodi provenienti da una casella specifica, applicare un filtro in base alla chiave "tag" :

tag_ref = "cx_ab"
matching_nodes = [
    node
    for node in samplex.graph.nodes()
    if node.trace_info is not None and tag_ref in node.trace_info.trace_refs.get("tag", set())
]

print(f"Nodes originating from box '{tag_ref}':")
for node in matching_nodes:
    print(f"  {type(node).__name__}")
Nodes originating from box 'cx_ab':
  TwirlSamplingNode
  CombineRegistersNode
  CollectTemplateValues

Il DAG di Samplex

Il samplex restituito da build() è un grafo aciclico orientato (DAG) in cui gli archi indicano le dipendenze tra i registri. In altre parole, un arco dal nodo A al nodo B implica che B debba attendere che A abbia effettuato le operazioni sui registri virtuali condivisi prima che B possa a sua volta agire. Non si tratta di un ordinamento temporale come il DAG di un circuito quantistico. Il grafico, invece, si snoda dai nodi incaricati di generare le randomizzazioni ai nodi incaricati di sintetizzarle come output.

Esistono tre tipi di nodi, ciascuno con uno stile visivo distinto nel grafico interattivo:

Forma
Colore
Tipo
Ruolo
StellaRossoSamplingNodeCrea nuovi registri virtuali a partire da una distribuzione o da un input
CerchioVerdeEvaluationNodeTrasforma, combina o propaga i registri virtuali
PapillonBlu / ViolaCollectionNodeLegge i registri e scrive sulle uscite sample()

L'esecuzione si svolge in tre fasi:

  1. Tutti i nodi di campionamento vengono eseguiti per primi (in parallelo).
  2. Successivamente vengono eseguiti i nodi di valutazione, in ordine topologico (in parallelo all’interno di ciascuna generazione).
  3. I nodi di raccolta vengono eseguiti per ultimi (in parallelo).

Il layout di Graphviz riflette questa situazione. I nodi di campionamento compaiono in alto, quelli di raccolta in basso.


Suggerimenti al passaggio del mouse

Ogni nodo della visualizzazione interattiva mostra un'informativa quando ci si passa sopra con il mouse. Il testo di aiuto contiene le seguenti informazioni:

  • Nome della classe del nodo e relativo indice intero nel grafo.
  • Manifesti di registrazione, che registrano i nodi che l'istanza crea, da cui legge, su cui scrive e che rimuove. Questi schemi illustrano come avvengono i flussi di dati tra i nodi.
  • Dettagli specifici per ciascun nodo, tra cui il tipo di distribuzione per i nodi di campionamento, l'operando per i nodi di moltiplicazione, gli indici dei parametri del modello per i nodi di raccolta e così via.
  • Riferimenti di tracciamento (solo quando debug=True), ovvero le chiavi di annotazione e le stringhe di riferimento che collegano il nodo alle caselle da cui proviene.

Cliccare sul grafico e poi passare il mouse sui singoli nodi è il modo più veloce per capire a cosa serve un determinato nodo senza leggere il codice sorgente.


Sintesi di Samplex

Prima di esaminare la visualizzazione, print(samplex) fornisce un breve riassunto testuale relativo al numero di nodi, agli input richiesti e agli output previsti.

template, samplex = build(circuit)
print(samplex)
Samplex(<17 nodes>)
  Inputs:
  - 'pauli_lindblad_maps.cx_noise' <PauliLindbladMap>: A PauliLindblad map acting on 2
      qubits, with 'num_terms_cx_noise' terms.

  Outputs:
    * 'measurement_flips.c' <bool['num_randomizations', 1, 4]>: Bit-flip corrections for
        measurement twirling.
    * 'parameter_values' <float32['num_randomizations', 24]>: Parameter values valid for an
        associated template circuit.
    * 'pauli_signs' <bool['num_randomizations', 1]>: Signs from sampled Pauli Lindblad
        maps, where boolean values represent the parity of the number of non-trivial factors in the
        sampled error that arise from negative rates. In other words, in order to implement basic
        PEC, the sign used to correct expectation values should be ``(-1)**bool_value``. The order
        matches the iteration order of boxes in the original circuit with noise injection
        annotations.

Controllare i registri

Il passaggio keep_registers=True a sample() conserva gli oggetti VirtualRegister intermedi che sono ancora attivi al termine del campionamento e li memorizza in outputs.metadata["registers"]. Ogni registro è un array di tipo " 2D " (di forma (num_subsystems, num_randomizations), con eventuali dimensioni aggiuntive relative alla forma del gate) composto da elementi di gruppi virtuali.

Ciò è utile per verificare che le porte virtuali siano state combinate correttamente, oppure per esaminare i campioni grezzi di Pauli o unitari prima che vengano sintetizzati in angoli di rotazione.

from qiskit.quantum_info import PauliLindbladMap

outputs = samplex.sample(
    {"pauli_lindblad_maps.cx_noise": PauliLindbladMap.identity(2)},
    num_randomizations=3,
    keep_registers=True,
)
for name, reg in outputs.metadata["registers"].items():
    print(f"{name}: type={reg.TYPE.value}, shape={reg.virtual_gates.shape}")
lhs_0: type=pauli, shape=(4, 3)
rhs_0: type=pauli, shape=(4, 3)
lhs_3: type=pauli, shape=(2, 3)
rhs_3: type=pauli, shape=(2, 3)
inject_noise_1: type=pauli, shape=(2, 3)
sign_1: type=z2, shape=(1, 3)
lhs_7: type=pauli, shape=(2, 3)
rhs_7: type=pauli, shape=(2, 3)
meas_prop_6: type=pauli, shape=(4, 3)
collect_10: type=u2, shape=(2, 3, 2, 2)
meas_prop_z2a_6: type=z2, shape=(4, 3)
collect_9: type=u2, shape=(4, 3, 2, 2)
collect_5: type=u2, shape=(2, 3, 2, 2)

È possibile visualizzare il contenuto dello stato finale di un determinato registro. Nell'esempio seguente, il registro è di tipo PauliRegister. Per ulteriori dettagli sul formato di archiviazione, consultare la sezione API documentation(/apidocs/index)__.

print("lhs_0:", outputs.metadata["registers"]["lhs_0"])
outputs.metadata["registers"]["lhs_0"].virtual_gates
lhs_0: PauliRegister(<4, 3>)
array([[0, 0, 1],
       [3, 1, 1],
       [3, 0, 0],
       [1, 0, 0]], dtype=uint8)

Come i nodi di Samplex vengono associati ai parametri dei modelli

L'array outputs["parameter_values"] restituito da sample() ha forma (num_randomizations, N), dove N corrisponde a len(template.parameters). Ogni colonna corrisponde a un parametro del circuito modello — la i-esima colonna inserisce template.parameters[i].

I nodi di raccolta CollectTemplateValues costituiscono il collegamento tra i registri virtuali e i parametri dei modelli. Ciascuno di questi nodi contiene informazioni sull'indice che indicano in quali colonne dell'array di output vengono scritti i dati. Ciò consente di individuare quali sottosistemi di gate virtuali controllano quali parametri dei modelli.

Consulta la guida “ {Samplex inputs and outputs}samplex-io ” per scoprire come associare i valori dei parametri campionati al circuito del modello ed eseguire esperimenti.

from samplomatic.samplex.nodes import CollectTemplateValues

template, samplex = build(circuit, debug=True)
for node in samplex.graph.nodes():
    if isinstance(node, CollectTemplateValues):
        tags = node.trace_info.trace_refs.get("tag", set()) if node.trace_info else set()
        print(f"tags={tags}  →  template param indices: {node.template_idxs.tolist()}")
tags={'cx_cd'}  →  template param indices: [[6, 7, 8], [9, 10, 11]]
tags={'cx_ab', 'cx_cd'}  →  template param indices: [[12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23]]
tags=set()  →  template param indices: [[0, 1, 2], [3, 4, 5]]

Etichettatura automatica

Quando si utilizza generate_boxing_pass_manager(), il parametro add_tags aggiunge automaticamente le annotazioni Tag a tutte le caselle. Sono disponibili tre modalità:

  • "unique_instance": Assegna numeri sequenziali ( ref``t0 , t1, e così via) alle caselle nell'ordine del circuito. A ogni casella viene assegnato un identificativo univoco, indipendentemente ref dalla sua struttura.
  • "unique_box": Calcola un hash strutturale del contenuto di ogni casella e assegna lo stesso hash a ref tutte le caselle strutturalmente equivalenti. Questo è utile per raggruppare i riquadri in base al tipo anziché alla posizione.
  • "noise_ref": Copia l'annotazione InjectNoise da ref ciascuna casella e contrassegna solo le caselle che ne contengono una. Ciò risulta utile quando esistono già riferimenti significativi al rumore.

L'esempio seguente applica "unique_instance" e "unique_box" a un circuito le cui due caselle CX sono strutturalmente equivalenti. Con "unique_instance", ciascuno ottiene un ref, mentre con "unique_box", ne condividono uno.

from samplomatic.transpiler import generate_boxing_pass_manager

base_circuit = QuantumCircuit(3)
base_circuit.cx(0, 1)
base_circuit.cx(1, 2)
base_circuit.measure_all()

# unique_instance: every box gets a distinct tag ref
pm = generate_boxing_pass_manager(add_tags="unique_instance")
boxed = pm.run(base_circuit)
template, _ = build(boxed)

print("unique_instance barrier labels:")
for instr in template:
    if instr.operation.name == "barrier" and instr.operation.label:
        print(f"  {instr.operation.label}")
unique_instance barrier labels:
  L0@tag=t0
  M0@tag=t0
  R0@tag=t0
  L1@tag=t1
  M1@tag=t1
  R1@tag=t1
  L2@tag=t2
  M2@tag=t2
  R2@tag=t2
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.