Kombinování možností zmírnění chyb s primitivem Estimator
Odhadovaná spotřeba: 7 minut na procesoru Heron r2 (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)
Výsledky učení
Doporučujeme, aby se uživatelé před absolvováním tohoto tutoriálu seznámili s následujícími tématy:
- Základy dynamického oddělování, zmírnění chyb měření, rotace hradel a extrapolace nulového šumu, jak je popsáno v tomto průvodci.
Požadavky
Po absolvování tohoto tutoriálu by uživatelé měli rozumět:
- Tomu, jak jsou výše zmíněné techniky zmírnění chyb selektivně implementovány na hardwaru.
- Jak se porovnávají z hlediska jejich schopnosti zmírňovat šum hardwaru.
Pozadí
Tento tutoriál zkoumá možnosti potlačení a zmírnění chyb dostupné s primitivem Estimator z Qiskit Runtime. Tento tutoriál ukazuje, jak implementovat každou z následujících metod samostatně:
- Dynamické oddělování (Dynamical decoupling)
- Zmírnění chyb měření (Measurement error mitigation)
- Rotace hradel (Gate twirling)
- Extrapolace nulového šumu (Zero-noise extrapolation, ZNE)
Upozorňujeme, že alternativou k individuální implementaci těchto technik je jejich implementace pomocí úrovně odolnosti, kde resilience_level nabývá hodnot 0, 1, 2:
- 0 : Žádné zmírnění není implementováno.
- 1 : Zmírnění chyb měření je implementováno.
- 2 : Rotace hradel, zmírnění chyb měření a ZNE jsou implementovány.
V tomto tutoriálu sestavíš Circuit a pozorovatelnou veličinu a odešleš úlohy pomocí primitivu Estimator s různými kombinacemi nastavení zmírnění chyb. Poté vykresleš výsledky, abys pozoroval(a) účinky různých nastavení. Většina tutoriálu používá 10-qubitový Circuit, aby byla vizualizace jednodušší, a na konci škáluješ pracovní postup na 50 qubitů.
Požadavky
Před zahájením tohoto průvodce se ujisti, že máš nainstalováno následující:
- Qiskit SDK v2.1 nebo novější, s podporou vizualizace
- Qiskit Runtime v0.40 nebo novější (
pip install qiskit-ibm-runtime)
Nastavení
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator
Příklad na malém simulátoru
Tento krok přeskočíme, protože zmírnění chyb za běhu není na simulátorech podporováno.
Příklad na hardwaru
Krok 1: Mapování klasických vstupů na kvantový problém
Tento průvodce předpokládá, že klasický problém již byl namapován na kvantový. Začni sestavením Circuitu a pozorovatelné veličiny, kterou chceš měřit. Přestože zde použité techniky se vztahují na mnoho různých typů Circuitů, pro jednoduchost tento průvodce používá Circuit efficient_su2 obsažený v knihovně Qiskit circuit library.
efficient_su2 je parametrizovaný kvantový Circuit navržený tak, aby byl efektivně spustitelný na kvantovém hardwaru s omezenou konektivitou qubitů, a přitom byl dostatečně expresivní k řešení problémů v aplikačních oblastech, jako je optimalizace a chemie. Je sestaven střídáním vrstev parametrizovaných jednoqubitových hradel s vrstvou obsahující pevný vzor dvouqubitových hradel pro zvolený počet opakování. Vzor dvouqubitových hradel může uživatel specifikovat. Zde můžeš použít vestavěný vzor pairwise, protože minimalizuje hloubku Circuitu tím, že dvouqubitová hradla husto uspořádá. Tento vzor lze spustit pouze s lineární konektivitou qubitů.
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)

Jako naši pozorovatelnou veličinu zvolíme operátor Pauli působící na poslední qubit, . Upozorňujeme, že skutečnost, že poslední qubit odpovídá prvnímu prvku tohoto řetězce, je způsobena tím, že Qiskit používá little-endian notaci.
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
V tuto chvíli bys mohl(a) přistoupit ke spuštění Circuitu a měření pozorovatelné veličiny. Chceš však také porovnat výstup kvantového zařízení se správnou odpovědí — tedy teoretickou hodnotou pozorovatelné veličiny, pokud by byl Circuit spuštěn bez chyb. U malých kvantových Circuitů lze tuto hodnotu vypočítat simulací Circuitu na klasickém počítači, ale u větších Circuitů na úrovni utility to není možné. Tento problém lze obejít technikou „zrcadlového Circuitu" (také znám jako „compute-uncompute"), která je užitečná pro hodnocení výkonu kvantových zařízení.
Zrcadlový Circuit
V technice zrcadlového Circuitu konkatenuj Circuit s jeho inverzním Circuitem, který vznikne inverzí každého hradla Circuitu v obráceném pořadí. Výsledný Circuit implementuje operátor identity, který lze triviálně simulovat. Protože struktura původního Circuitu je zachována ve zrcadlovém Circuitu, spuštění zrcadlového Circuitu stále dává představu o tom, jak by kvantové zařízení zvládlo původní Circuit.
Následující buňka kódu přiřadí náhodné parametry tvému Circuitu a poté sestaví zrcadlový Circuit pomocí třídy unitary_overlap. Před zrcadlením Circuitu k němu připoj instrukci barrier, aby Transpiler nemohl sloučit obě části Circuitu na obou stranách bariéry a výsledkem nebyl transpilovaný Circuit bez jakýchkoliv hradel.
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)

Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru
Před spuštěním Circuitu na hardwaru je nutné jej optimalizovat. Tento proces zahrnuje několik kroků:
- Výběr rozvržení qubitů, které mapuje virtuální Qubity tvého Circuitu na fyzické Qubity na hardwaru.
- Vložení swap hradel podle potřeby pro směrování interakcí mezi Qubity, které nejsou propojeny.
- Překlad hradel ve tvém Circuitu na instrukce Instruction Set Architecture (ISA), které lze přímo spustit na hardwaru.
- Provádění optimalizací Circuitu za účelem minimalizace hloubky Circuitu a počtu hradel.
Transpiler integrovaný v Qiskit může za tebe provést všechny tyto kroky. Protože tento příklad používá hardwarově efektivní Circuit, Transpiler by měl být schopen zvolit rozvržení qubitů, které nevyžaduje vkládání swap hradel pro směrování interakcí.
Před optimalizací Circuitu je třeba zvolit hardwarové zařízení. Následující buňka kódu požaduje nejméně vytížené zařízení s alespoň 127 qubity.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
<IBMBackend('ibm_fez')>
Circuit pro zvolený Backend lze transpilovat vytvořením pass manageru a jeho spuštěním na Circuitu. Snadný způsob, jak vytvořit pass manager, je použít funkci generate_preset_pass_manager. Podrobnější vysvětlení transpilace pomocí pass managerů najdeš v části Transpilace s pass managery.
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)

Transpilovaný Circuit nyní obsahuje pouze ISA instrukce. Všechna hradla byla rozložena ve smyslu hradel a rotací a CZ hradel.
Proces transpilace namapoval virtuální Qubity Circuitu na fyzické Qubity na hardwaru. Informace o rozvržení qubitů je uložena v atributu layout transpilovaného Circuitu. Pozorovatelná veličina byla také definována ve smyslu virtuálních Qubitů, takže je třeba na ni toto rozvržení aplikovat, což lze provést metodou apply_layout třídy SparsePauliOp.
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Krok 3: Spuštění pomocí primitiv Qiskit
Nyní jsi připraven(a) spustit svůj Circuit pomocí primitivu Estimator.
Odešleš pět samostatných úloh, počínaje bez potlačení nebo zmírnění chyb, a postupně budeš povolovat různé možnosti potlačení a zmírnění chyb dostupné v Qiskit Runtime. Pro informace o možnostech se podívej na následující stránky:
- Přehled všech možností
- Dynamické oddělování
- Odolnost, včetně zmírnění chyb měření a extrapolace nulového šumu (ZNE)
- Rotace (Twirling)
Protože tyto úlohy lze spouštět nezávisle na sobě, můžeš použít dávkový režim a umožnit tak Qiskit Runtime optimalizovat načasování jejich spuštění.
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
Krok 4: Následné zpracování a vrácení výsledku v požadovaném klasickém formátu
Nakonec můžeš analyzovat data. Načteš výsledky úloh, extrahuješ z nich naměřené střední hodnoty a vykreslíš hodnoty včetně chybových úseček jedné směrodatné odchylky.
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
V tomto malém měřítku je obtížné pozorovat účinek většiny technik zmírnění chyb, ale extrapolace nulového šumu (ZNE) přináší znatelné zlepšení. Všimni si však, že toto zlepšení není zadarmo, protože výsledek ZNE má také větší chybovou úsečku.
Příklad na rozsáhlém hardwaru
Při vývoji experimentu je užitečné začít s malým Circuit, aby byla vizualizace a simulace jednodušší. Teď, když jsi vyvinul a otestoval náš pracovní postup na 10qubitovém Circuit, ho můžeš škálovat na 50 qubitů. Následující buňka kódu opakuje všechny kroky tohoto návodu, ale tentokrát je aplikuje na 50qubitový Circuit.
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
Když porovnáš výsledky pro 50 qubitů s výsledky pro 10 qubitů z dřívějška, možná si všimneš následujícího (tvé výsledky se mohou mezi spuštěními lišit):
- Všechny experimenty dávají výsledky blíže ideální hodnotě a všechny chybové úsečky jsou menší.
- Přidání dynamického oddělení mohlo zhoršit výkon ve srovnání s případem bez zmírnění. To není překvapivé, protože Circuit je velmi hustý. Dynamické oddělení je primárně užitečné tehdy, když jsou v Circuit velké mezery, během nichž qubity čekají nečinně bez aplikovaných Gate. Pokud tyto mezery nejsou přítomny, dynamické oddělení není účinné a může ve skutečnosti zhoršit výkon kvůli chybám v samotných pulzech dynamického oddělení. 10qubitový Circuit mohl být příliš malý na to, abychom tento efekt pozorovali.
- Při extrapolaci nulového šumu je výsledek velmi blízko ideální hodnotě. To demonstruje sílu ZNE.
Další kroky
Pokud tě tato práce zaujala, možná tě budou zajímat i následující materiály o některých dalších technikách zmírnění a potlačení chyb, které v tomto tutoriálu nebyly zmíněny: