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?

Como interpretar as visualizações de shaded lightcone

O pacote oferece duas formas de observar um shaded lightcone:

  • draw_shaded_lightcone sobrepõe os limites ao diagrama do circuito. É melhor para circuitos em uma cadeia 1D de qubits, em que o diagrama do circuito e o layout físico coincidem.

  • animate_shaded_lightcone projeta os limites no mapa de acoplamento da QPU e percorre o circuito camada por camada. É melhor quando a conectividade dos qubits é mais do que uma linha 1D, em que um diagrama de circuito deixa de transmitir quais qubits são vizinhos.

As duas visualizações mostram os mesmos números subjacentes. Este guia explica o que esses números significam e os detalhes que são fáceis de interpretar mal.

Configuração​

Use o mesmo circuito de Ising com Trotter e o mesmo observável do guia de início rápido.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
merge_bounds,
)
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit

num_qubits = 10
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])

boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)

forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
backward_bounds = compute_backward_bounds(boxed_circuit, noise_model_paulis)

rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}
merged_bounds = merge_bounds(
boxed_circuit, forward_bounds, backward_bounds, noise_rates
)
print(f"observable: Z on qubit {num_qubits // 2}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
observable: Z on qubit 5

draw_shaded_lightcone​

Cada caixa ruidosa é sombreada pelo limite dos erros que ocorrem ali: brilhante significa um limite grande, e uma caixa que se apaga no fundo significa que seus erros têm pouco efeito no observável. Essas caixas apagadas são as candidatas a serem removidas do modelo de ruído mitigado.

from qiskit_addon_slc.visualization import draw_shaded_lightcone

draw_shaded_lightcone(
boxed_circuit, merged_bounds, noise_model_paulis, fold=-1, scale=0.5
)

Output of the previous code cell

Notas importantes​

1. Cada número é uma soma sobre os termos de erro que compartilham um suporte. Sem filtro, os limites de todo termo de erro que atua no mesmo qubit, ou no mesmo par de qubits, são somados em um único valor: todos os três tipos de Pauli em um qubit e todos os nove em um par. Isso tem duas consequências. Um valor pode exceder 2.0, mesmo que o limite de nenhum termo isolado possa (uma caixa rotulada 4.5 são vários termos somados, não um limite violado). A figura não consegue dizer se um valor grande vem de um único tipo de Pauli ou de vários comparáveis; a próxima seção mostra como separá-los.

2. A escala de cores começa em 2.0 e cresce com os dados. A escala vai de 0 até a entrada "max_bound" dos metadados do circuito sobreposto, que é o maior entre 2.0 e o maior valor mostrado. Somas sem filtro costumam exceder 2.0, então a escala se estica para acomodá-las, e a mesma cor pode representar valores diferentes em figuras diferentes. Quando todos os valores são pequenos, a escala permanece em 2.0 e tudo parece uniformemente escuro (isso é a escala refletindo o tamanho absoluto, não uma falha de renderização).

Separando os tipos de Pauli​

Use pauli_filter para ver um tipo por vez. Um inteiro seleciona um peso de Pauli; uma string seleciona um Pauli específico, comparado com cada termo de ruído reduzido ao seu próprio suporte não identidade. Assim, "X" seleciona todo erro XX de um único qubit, não importa em qual qubit ele atue, e "XX" seleciona os de peso 2 (um rótulo de largura total como "XII" não corresponde a nada).

from qiskit_addon_slc.visualization import accumulate_filtered_bounds

box_id = next(iter(merged_bounds))
print(f"single-qubit supports of box {box_id!r}:\n")
print(f"{'filter':>8} bounds per qubit")
for pauli_filter in (None, 1, "X", "Y", "Z"):
filtered = accumulate_filtered_bounds(
boxed_circuit, merged_bounds, noise_model_paulis, pauli_filter
)
per_qubit = {
support[0]: round(float(bound), 3)
for support, bound in sorted(filtered[box_id].items())
if len(support) == 1
}
print(f"{pauli_filter!s:>8} {per_qubit}")
single-qubit supports of box 'm0':

filter bounds per qubit
None {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
1 {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
X {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.153, 4: 0.577, 5: 2.0, 6: 0.702, 7: 0.056, 8: 0.01, 9: 0.0}
Y {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.148, 4: 0.607, 5: 2.0, 6: 0.631, 7: 0.057, 8: 0.01, 9: 0.0}
Z {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.011, 4: 0.172, 5: 1.111, 6: 0.172, 7: 0.011, 8: 0.0, 9: 0.0}

A linha sem filtro é a soma dos três tipos de Pauli abaixo dela. Filtrar é a única forma de saber se um valor grande sem filtro vem de um tipo de erro dominante ou de vários comparáveis. Isso importa, porque a mitigação atua por termo, não por sítio.

Passar o mesmo filtro para draw_shaded_lightcone o registra no título da figura, de modo que uma figura filtrada não possa ser confundida com uma sem filtro:

draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter="X",
fold=-1,
scale=0.5,
)

Output of the previous code cell

Esta figura parece bem diferente da sem filtro acima, e não só porque mostra menos valores. Cada marcador nessas figuras representa um suporte: um único qubit ou um par de qubits. Sem filtro, uma caixa mostra um marcador em cada qubit e em cada par de qubits vizinhos, então se espalha por várias colunas. Com "X", só restam os suportes de um único qubit, e cada caixa encolhe para uma única coluna.

Nenhuma das duas imagens mostra o que o PEC realmente aplica, porém. Os placeholders são uma forma de exibir limites, não portas anti-ruído. Durante o experimento, cada caixa cujos termos de erro são mitigados é tratada como uma única camada de portas de dois qubits, e os Paulis anti-ruído amostrados para seus termos de erro são compostos em um único Pauli que é aplicado a essa camada como um todo.

animate_shaded_lightcone​

Em um dispositivo cuja conectividade não é uma linha 1D, um diagrama de circuito não mostra mais quais qubits são vizinhos, então a extensão espacial do cone de luz se torna impossível de ler. animate_shaded_lightcone, em vez disso, desenha o mapa de acoplamento da QPU e anima os limites sobre ele, uma caixa ruidosa por quadro.

Ela precisa de um CouplingMap, que ela organiza em uma grade incorporando o grafo do dispositivo, então funciona tanto com conectividade heavy-hex quanto quadrada.

Observação: Nesta documentação renderizada, a animação é estática. O controle deslizante e o botão de reprodução só funcionam quando o notebook é executado de forma interativa, por exemplo no Jupyter.

from qiskit import transpile
from qiskit.transpiler import CouplingMap
from qiskit_addon_slc.visualization import animate_shaded_lightcone

# A small square-lattice device stands in for a real backend here.
coupling_map = CouplingMap.from_grid(3, 4)

# Lay the chain onto a connected path of physical qubits, snaking through the grid, so the
# transpiler inserts no SWAPs: a SWAP-laden circuit is far more expensive to bound and harder to
# read.
initial_layout = [0, 1, 2, 3, 7, 6, 5, 4, 8, 9]
device_circuit = transpile(
circuit,
coupling_map=coupling_map,
initial_layout=initial_layout,
optimization_level=0,
)
boxed_device_circuit = boxing_pass.run(device_circuit)
device_noise_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_device_circuit),
coupling_map=coupling_map,
circuit=boxed_device_circuit,
)
# The same observable, on the physical qubit the middle of the chain was laid onto.
device_observable = Pauli("I" * device_circuit.num_qubits).compose(
"Z", [initial_layout[num_qubits // 2]]
)
device_bounds = compute_forward_bounds(
boxed_device_circuit, device_noise_paulis, device_observable
)

animate_shaded_lightcone(boxed_device_circuit, device_bounds, coupling_map)

Output of the previous code cell

Lendo a animação​

Cada qubit é um gráfico de pizza, uma fatia por tipo de Pauli. O marcador de um qubit é dividido em três fatias para seus erros XX, YY e ZZ, cada uma sombreada pelo seu próprio limite. Esta é a filtragem da seção anterior incorporada à figura (não é preciso pauli_filter para distinguir os tipos).

As arestas carregam os limites de erro de dois qubits. A linha entre dois qubits acoplados é sombreada pelo limite dos erros de peso 2 que atuam sobre esse par.

Cinza claro significa ausência de dados, não um limite pequeno. Uma fatia ou aresta é desenhada em cinza claro quando o modelo de ruído da caixa não tem termo de erro para ela, por exemplo em qubits sobre os quais a caixa não atua. Ela é desenhada em cinza, e não em escuro, para que não seja confundida com um limite igual a zero. As caixas além de um limite max_num_boxes são diferentes: elas carregam o limite trivial de 2.0, então são desenhadas na extremidade brilhante da escala.

O controle deslizante avança por caixas, não uniformemente pelo tempo. Há um quadro por caixa ruidosa, na ordem do circuito (ou em ordem inversa com reverse=True, que traça o cone de luz para trás a partir do observável). Assistir em ordem inversa costuma ser a direção mais intuitiva: o cone cresce para fora a partir dos qubits medidos. Cada quadro é rotulado com o identificador de sua caixa, a mesma chave sob a qual o dicionário de limites armazena essa caixa, então o controle deslizante também pode ser usado para saltar para uma caixa específica e consultar seus números.

Observação: O layout dos qubits é recuperado do grafo do mapa de acoplamento incorporando-o em uma rede quadrada. Um mapa de acoplamento que não pode ser incorporado (porque é desconexo ou conectado de forma densa demais para uma grade) gera um ValueError em vez de desenhar uma imagem enganosa.

Qual usar​

draw_shaded_lightconeanimate_shaded_lightcone
geometriao diagrama do circuitoo mapa de acoplamento da QPU
melhor paracadeias 1Dqualquer conectividade
tipos de Paulisomados, a menos que filtradossempre separados, como fatias de pizza
tempotodas as camadas de uma vezuma camada por quadro
saídauma figura matplotlibuma figura plotly

Para uma cadeia 1D, a figura estática é mais fácil de assimilar de relance e de reproduzir em um artigo. Além de 1D, a animação é a única das duas que mostra fielmente a estrutura espacial.