Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Depuração e rastreamento

A função build() transforma um circuito anotado em um par de circuito modelo e samplex. Em um circuito complexo, pode ser difícil saber qual caixa do circuito original corresponde a quais barreiras no modelo, ou a quais nós no DAG do samplex.

O Samplomatic oferece duas ferramentas de rastreamento complementares:

  • Rótulos de barreira: toda barreira no circuito modelo carrega um rótulo que codifica sua origem. Eles estão sempre presentes, independentemente de debug=True estar definido.

  • Informações de rastreamento nos nós do samplex: quando build() é chamada com debug=True, todo nó do samplex carrega um objeto TraceInfo que o liga de volta à caixa ou às caixas que o produziram.

As duas ferramentas são mais úteis quando as caixas carregam uma anotação Tag, que anexa uma string ref à caixa. Alternativamente, referências de InjectNoise também são anexadas.

Marque caixas com tags​

Uma anotação Tag anexa uma string ref a uma caixa. O ref então aparece nos rótulos de barreira e nas informações de rastreamento dos nós do samplex.

O circuito de exemplo a seguir tem três caixas: duas caixas CX com tag em pares de Qubits disjuntos e uma caixa de medição sem tag, com dressing à direita. A segunda caixa CX também carrega uma anotação InjectNoise — tanto a referência tag quanto a ref de ruído dela aparecerão nos rótulos das barreiras. As duas caixas CX cobrem Qubits diferentes, o que permite que o otimizador de samplex una seus nós de propagação em nós compartilhados. Isso é mostrado na seção de informações de trace.

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

Rótulos de barreira no circuito modelo​

Chamar build() no circuito acima produz um modelo cujas barreiras carregam rótulos identificadores. Cada rótulo tem a forma {side}{scope}@{key=value&...}:

  • Lado: L = fronteira de dressing à esquerda, M = fronteira do conteúdo interno da caixa, R = fronteira de dressing à direita.

  • Escopo: um índice inteiro (ou uma lista separada por sublinhados para caixas aninhadas) que distingue várias caixas no mesmo nível de aninhamento.

  • Anotações: uma lista de pares key=value prefixada por @ e separada por &, derivada das anotações da caixa. A chave tag vem de uma anotação Tag e inject_noise vem de uma anotação InjectNoise.

Barreiras de caixas sem tag carregam apenas o lado e o escopo (por exemplo, L2), sem o sufixo @.

template, samplex = build(circuit)
template.draw("mpl", fold=100)

../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

Os rótulos de barreira também podem ser extraídos de forma programática:

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

Informações de rastreamento nos nós do samplex​

Passar debug=True para build() anexa informações de rastreamento a todo nó do samplex. O atributo trace_info de cada nó é um objeto TraceInfo cujo dicionário trace_refs mapeia chaves de anotação (por exemplo, "tag" e "inject_noise") para conjuntos de strings de referência. Nós sem uma anotação de caixa correspondente têm trace_info=None.

Quando draw() é chamada em um samplex construído em modo de depuração, passar o mouse sobre qualquer nó no grafo interativo revela seus trace_refs na dica de ferramenta.

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

As informações de rastreamento também podem ser inspecionadas de forma programática. Note que alguns nós carregam referências tanto de 'cx_ab' quanto de 'cx_cd'. O otimizador do samplex mesclou seus nós de propagação paralelos porque as duas caixas cobrem qubits disjuntos e compartilham um predecessor comum a partir da emissão da caixa de medição com dressing à direita.

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

Para encontrar todos os nós que se originam de uma caixa específica, filtre pela chave "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

O DAG do samplex​

O samplex retornado por build() é um grafo acíclico direcionado (DAG) em que as arestas denotam dependência de registrador. Ou seja, uma aresta do nó A para o nó B significa que B precisa esperar que A tenha atuado nos registradores virtuais compartilhados antes de poder atuar. Isso não é uma ordenação temporal como o DAG de um circuito quântico. Em vez disso, o grafo flui dos nós responsáveis por gerar randomizações para os nós responsáveis por sintetizá-las como saídas.

Há três tipos de nó, cada um com um estilo visual distinto no gráfico interativo:

FormaCorTipoFunção
EstrelaVermelhoSamplingNodeInstancia novos registradores virtuais a partir de uma distribuição ou entrada
CírculoVerdeEvaluationNodeTransforma, combina ou propaga registradores virtuais
Gravata-borboletaAzul / RoxoCollectionNodeLê registradores e escreve nas saídas de sample()

A execução ocorre em três fases:

  1. Todos os nós de amostragem rodam primeiro (em paralelo).

  2. Os nós de avaliação rodam em seguida, em ordem topológica (em paralelo dentro de cada geração).

  3. Os nós de coleta rodam por último (em paralelo).

O layout do graphviz reflete isso. Os nós de amostragem aparecem no topo e os nós de coleta na parte inferior.

Dicas de ferramenta ao passar o mouse​

Todo nó na visualização interativa mostra uma dica de ferramenta quando você passa o mouse sobre ele. A dica contém as seguintes informações:

  • Nome da classe do nó e seu índice inteiro no grafo.

  • Manifestos de registradores, que mostram quais registradores o nó instancia, lê, escreve e remove. Eles indicam como os dados fluem entre os nós.

  • Detalhes específicos do nó, incluindo o tipo de distribuição para nós de amostragem, o operando para nós de multiplicação, os índices de parâmetros do modelo para nós de coleta, e assim por diante.

  • Trace refs (somente com debug=True), que são as chaves de anotação e as strings de referência que ligam o nó de volta às caixas de origem.

Clicar no gráfico e depois passar o mouse sobre nós individuais é a maneira mais rápida de entender o que um nó faz sem ler o código-fonte.

Resumo do samplex​

Antes de examinar a visualização, print(samplex) dá um resumo rápido em texto da contagem de nós, das entradas necessárias e das saídas prometidas.

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.

Inspecione os registradores​

Passar keep_registers=True para sample() mantém os objetos intermediários VirtualRegister que estão vivos no final da amostragem e os armazena em outputs.metadata["registers"]. Cada registrador é um array 2D (formato (num_subsystems, num_randomizations), com possíveis dimensões finais de formato de porta) de elementos de grupo virtuais.

Isso é útil para verificar se as portas virtuais foram combinadas corretamente, ou para inspecionar as amostras brutas de Pauli ou unitárias antes de serem sintetizadas em ângulos de rotação.

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)

Você pode ver o conteúdo do estado final de um registrador específico. No exemplo a seguir, o registrador é do tipo PauliRegister. Veja a API documentation(/apidocs/index)__ para detalhes sobre o formato de armazenamento.

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)

Como os nós do samplex se mapeiam para os parâmetros do modelo​

O array outputs["parameter_values"] retornado por sample() tem formato (num_randomizations, N), em que N é igual a len(template.parameters). Cada coluna corresponde a um parâmetro do circuito modelo — a i-ésima coluna preenche template.parameters[i].

Os nós de coleta CollectTemplateValues são o elo entre registradores virtuais e parâmetros do modelo. Cada um desses nós guarda informações de índice que registram em quais colunas do array de saída ele escreve. Isso torna possível rastrear quais subsistemas de portas virtuais controlam quais parâmetros do modelo.

Veja o guia {Samplex inputs and outputs}samplex-io para aprender como vincular os valores de parâmetros amostrados ao circuito modelo e executar experimentos.

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

Marcação automática​

Ao usar generate_boxing_pass_manager(), o parâmetro add_tags adiciona automaticamente anotações Tag a todas as caixas. Há três modos disponíveis:

  • "unique_instance": Atribui ref sequenciais (t0, t1 e assim por diante) às caixas na ordem do circuito. Cada caixa recebe um ref distinto, independentemente de sua estrutura.

  • "unique_box": Calcula um hash estrutural do conteúdo de cada caixa e atribui o mesmo ref a todas as caixas estruturalmente equivalentes. Isso é útil para agrupar caixas por tipo em vez de por posição.

  • "noise_ref": Copia o ref da anotação InjectNoise de cada caixa e marca apenas as caixas que têm uma. Isso é útil quando já existem refs de ruído significativos.

O exemplo a seguir aplica "unique_instance" e "unique_box" a um circuito cujas duas caixas CX são estruturalmente equivalentes. Com "unique_instance", cada uma recebe um ref distinto, enquanto com "unique_box" elas compartilham um só.

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