Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Jak interpretować wizualizacje cieniowanego stożka światła

Pakiet oferuje dwa sposoby przyjrzenia się cieniowanemu stożkowi światła:

  • draw_shaded_lightcone nakłada ograniczenia na diagram obwodu. Sprawdza się najlepiej dla obwodów na jednowymiarowym łańcuchu kubitów, gdzie diagram obwodu i fizyczny układ się zgadzają.

  • animate_shaded_lightcone rzutuje ograniczenia na mapę połączeń QPU i przechodzi przez obwód warstwa po warstwie. Sprawdza się najlepiej, gdy łączność kubitów jest czymś więcej niż linią 1D, gdzie diagram obwodu przestaje pokazywać, które kubity są sąsiadami.

Obie wizualizacje pokazują te same liczby. Ten przewodnik wyjaśnia, co te liczby znaczą, i omawia szczegóły, które łatwo źle odczytać.

Konfiguracja​

Użyj tego samego trotteryzowanego obwodu Isinga i obserwabla co w przewodniku szybkiego startu.

# 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​

Każdy zaszumiony blok jest zacieniowany według ograniczenia błędów, które tam występują: jasny oznacza duże ograniczenie, a blok zanikający w tle oznacza, że jego błędy mają niewielki wpływ na obserwabl. Te wyblakłe bloki są kandydatami do usunięcia ze złagodzonego modelu szumu.

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

Ważne uwagi​

1. Każda liczba jest sumą po członach błędu o wspólnym nośniku. Bez filtra ograniczenia każdego członu błędu działającego na ten sam kubit lub na tę samą parę kubitów są sumowane w jedną wartość: wszystkie trzy typy Pauliego na kubicie i wszystkie dziewięć na parze. Ma to dwie konsekwencje. Wartość może przekraczać 2.0, mimo że ograniczenie żadnego pojedynczego członu nie może (blok oznaczony 4.5 to kilka członów sumujących się, a nie naruszone ograniczenie). Z rysunku nie da się odczytać, czy duża wartość pochodzi z jednego typu Pauliego, czy z kilku porównywalnych; następna sekcja pokazuje, jak je rozdzielić.

2. Skala kolorów zaczyna się od 2.0 i rośnie wraz z danymi. Skala biegnie od 0 do wpisu "max_bound" w metadanych nałożonego obwodu, który jest większą z wartości: 2.0 i największej pokazanej wartości. Niefiltrowane sumy często przekraczają 2.0, więc skala rozciąga się, by je objąć, a ten sam kolor może oznaczać różne wartości na różnych rysunkach. Gdy wszystkie wartości są małe, skala pozostaje przy 2.0 i wszystko wygląda jednolicie ciemno (to skala odzwierciedla bezwzględny rozmiar, a nie błąd renderowania).

Rozdzielanie typów Pauliego​

Użyj pauli_filter, aby zobaczyć jeden typ naraz. Liczba całkowita wybiera wagę Pauliego; łańcuch znaków wybiera konkretny operator Pauliego, dopasowywany do każdego członu szumu zredukowanego do jego własnego nośnika bez identyczności. Zatem "X" wybiera każdy jednokubitowy błąd XX, niezależnie od tego, na którym kubicie działa, a "XX" wybiera błędy wagi 2 (etykieta pełnej szerokości, jak "XII", nie pasuje do niczego).

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}

Wiersz niefiltrowany jest sumą trzech typów Pauliego pod nim. Filtrowanie to jedyny sposób, by stwierdzić, czy duża wartość niefiltrowana pochodzi z jednego dominującego typu błędu, czy z kilku porównywalnych. Ma to znaczenie, ponieważ łagodzenie działa na człon, a nie na miejsce.

Przekazanie tego samego filtra do draw_shaded_lightcone zapisuje go w tytule rysunku, więc przefiltrowanego rysunku nie da się pomylić z niefiltrowanym:

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

Output of the previous code cell

Ten rysunek wygląda zupełnie inaczej niż niefiltrowany powyżej, i to nie tylko dlatego, że pokazuje mniej wartości. Każdy symbol zastępczy na tych rysunkach oznacza jeden nośnik: pojedynczy kubit lub parę kubitów. Bez filtra blok pokazuje symbol zastępczy na każdym kubicie i na każdej parze sąsiednich kubitów, więc rozciąga się na kilka kolumn. Przy "X" pozostają tylko nośniki jednokubitowe, a każdy blok kurczy się do jednej kolumny.

Żaden z tych rysunków nie pokazuje jednak tego, co PEC faktycznie stosuje. Symbole zastępcze to sposób wyświetlania granic, a nie bramki przeciwszumowe. Podczas eksperymentu każdy blok, którego składniki błędu są łagodzone, jest traktowany jako pojedyncza warstwa bramek dwukubitowych, a próbkowane dla jego składników błędu przeciwszumowe operatory Pauliego są składane w jeden operator Pauliego, który jest stosowany do całej tej warstwy.

animate_shaded_lightcone​

Na urządzeniu, którego łączność nie jest linią 1D, diagram obwodu nie pokazuje już, które kubity sąsiadują ze sobą, więc przestrzenny zasięg stożka świetlnego staje się nieczytelny. animate_shaded_lightcone zamiast tego rysuje mapę połączeń QPU i animuje na niej granice, jeden zaszumiony blok na klatkę.

Wymaga obiektu CouplingMap, który układa na siatce, osadzając w niej graf urządzenia, dzięki czemu obsługuje zarówno łączność heavy-hex, jak i kwadratową.

Uwaga: W tej wyrenderowanej dokumentacji animacja jest statyczna. Suwak i przycisk odtwarzania działają tylko wtedy, gdy notebook jest uruchomiony interaktywnie, na przykład w 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

Odczytywanie animacji​

Każdy kubit jest wykresem kołowym, jeden wycinek na typ Pauliego. Znacznik kubitu jest podzielony na trzy wycinki odpowiadające błędom XX, YY i ZZ, a każdy z nich jest zacieniowany według własnej granicy. To jest filtrowanie z poprzedniej sekcji wbudowane w rysunek (nie potrzeba pauli_filter, aby rozróżnić typy).

Krawędzie niosą granice błędów dwukubitowych. Linia między dwoma połączonymi kubitami jest zacieniowana według granicy błędów o wadze 2 działających na tę parę.

Jasnoszary oznacza brak danych, a nie małą granicę. Wycinek lub krawędź są rysowane na jasnoszaro, gdy model szumu bloku nie ma dla nich składnika błędu, na przykład dla kubitów, na które blok nie działa. Rysuje się je na szaro, a nie na ciemno, aby nie można ich było pomylić z granicą równą zero. Inaczej jest z blokami poza limitem max_num_boxes: mają one trywialną granicę 2.0, więc są rysowane na jasnym końcu skali.

Suwak przechodzi po blokach, a nie równomiernie w czasie. Na każdy zaszumiony blok przypada jedna klatka, w kolejności obwodu (lub w odwrotnej z reverse=True, co śledzi stożek świetlny wstecz od obserwabli). Oglądanie w odwrotnej kolejności jest zwykle bardziej intuicyjne: stożek rośnie na zewnątrz od mierzonych kubitów. Każda klatka jest opisana identyfikatorem swojego bloku, tym samym kluczem, pod którym słownik granic przechowuje ten blok, więc suwaka można też użyć do przeskoczenia do konkretnego bloku i sprawdzenia jego wartości.

Uwaga: Układ kubitów jest odtwarzany z grafu mapy połączeń przez osadzenie go w kwadratowej siatce. Mapa połączeń, której nie da się osadzić (bo jest niespójna lub zbyt gęsto połączona jak na siatkę), zgłasza ValueError zamiast rysować mylący obraz.

Którego użyć​

draw_shaded_lightconeanimate_shaded_lightcone
geometriadiagram obwodumapa połączeń QPU
najlepsze dlałańcuchów 1Ddowolnej łączności
typy Pauliegosumowane, chyba że przefiltrowanezawsze rozdzielone, jako wycinki wykresu kołowego
czaswszystkie warstwy narazjedna warstwa na klatkę
wynikrysunek matplotlibrysunek plotly

Dla łańcucha 1D statyczny rysunek łatwiej ogarnąć na pierwszy rzut oka i można go odtworzyć w artykule. Poza 1D animacja jest jedynym z tych dwóch narzędzi, które wiernie pokazuje strukturę przestrzenną.