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í
-
Jak se na hardwaru selektivně implementuje dynamické oddělování, zmírnění chyb měření, twirling hradel a extrapolace nulového šumu.
-
Jak se porovnávají z hlediska jejich schopnosti zmírňovat šum hardwaru.
Požadavky
- Základy dynamického oddělování, zmírnění chyb měření, twirlingu hradel a extrapolace nulového šumu, jak je popsáno v tomto průvodci.
Pozadí
Tento tutoriál zkoumá možnosti potlačení a zmírnění chyb dostupné s primitivem Estimator IBM Quantum. 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)
-
Twirling 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 : Twirling 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 — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_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 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 v Qiskit Runtime 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 obvodu a pozorovatelné veličiny, kterou chceš měřit. Přestože zde použité techniky se vztahují na mnoho různých typů obvodů, pro jednoduchost tento průvodce používá obvod efficient_su2 obsažený v knihovně Qiskit circuit library.
efficient_su2 je parametrizovaný kvantový obvod 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 obvodu tím, že dvouqubitová hradla uspořádá co nejhustěji. Ke spuštění tohoto vzoru stačí pouhá lineární konektivita 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í obvodu 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 obvod spuštěn bez chyb. U malých kvantových obvodů lze tuto hodnotu vypočítat simulací obvodu na klasickém počítači, ale u větších obvodů na úrovni utility to není možné. Tento problém lze obejít technikou „zrcadlového obvodu" (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 obvodu se obvod spojí se svým inverzním obvodem, který vznikne inverzí každého hradla obvodu v obráceném pořadí. Výsledný obvod implementuje operátor identity, který lze triviálně simulovat. Protože struktura původního obvodu je zachována ve zrcadlovém obvodu, spuštění zrcadlového obvodu stále dává představu o tom, jak by kvantové zařízení zvládlo původní obvod.
Následující buňka kódu přiřadí náhodné parametry tvému obvodu a poté sestaví zrcadlový obvod pomocí třídy unitary_overlap. Před zrcadlením obvodu k němu připoj instrukci barrier, aby Transpiler nemohl sloučit obě části obvodu na obou stranách bariéry a výsledkem nebyl transpilovaný obvod 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 obvodu 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 obvodu 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 obvodu na instrukce Instruction Set Architecture (ISA), které lze přímo spustit na hardwaru.
-
Provádění optimalizací obvodu za účelem minimalizace hloubky obvodu 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í obvodu 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')>
Obvod pro zvolený Backend lze transpilovat vytvořením pass manageru a jeho spuštěním na obvodu. 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 na hradla , rotace a hradla CZ.
Proces transpilace namapoval virtuální Qubity obvodu na fyzické Qubity na hardwaru. Informace o rozvržení qubitů je uložena v atributu layout transpilovaného obvodu. Pozorovatelná veličina byla také definována pomocí 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 IBM Quantum Compute Service. Pro informace o možnostech se podívej na následující stránky:
Protože tyto úlohy lze spouštět nezávisle na sobě, můžeš použít batch mode a umožnit tak Quantum Compute 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 obvodem, aby byla vizualizace a simulace jednodušší. Teď, když jsi vyvinul a otestoval náš pracovní postup na 10-qubitovém obvodu, 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 50-qubitový obvod.
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ělování 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ělování 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ělování není účinné a může ve skutečnosti zhoršit výkon kvůli chybám v samotných pulzech dynamického oddělování. 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: