Přeskočit na hlavní obsah

Kombinování možností zmírnění chyb s primitivem Estimator

Odhadovaná spotřeba: Sedm minut na procesoru Heron r2 (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)

Pozadí

Tento průvodce zkoumá možnosti potlačení a zmírnění chyb dostupné s primitivem Estimator z Qiskit Runtime. 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 příkladů používá 10-qubitový Circuit, aby bylo vizualizace jednodušší, a na konci můžeš pracovní postup škálovat na 50 qubitů.

Jedná se o možnosti potlačení a zmírnění chyb, které použiješ:

  • 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)

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

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)

Output of the previous code cell

Output of the previous code cell

Pro naši pozorovatelnou veličinu zvolíme operátor Pauli ZZ působící na poslední Qubit, ZIIZ I \cdots I.

# 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. Bez bariéry by Transpiler sloučil původní Circuit s jeho inverzem, což by vedlo k transpilovanému Circuitu 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)

Output of the previous code cell

Output of the previous code cell

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
)

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)

Output of the previous code cell

Output of the previous code cell

Transpilovaný Circuit nyní obsahuje pouze ISA instrukce. Jednoqubitová hradla byla rozložena ve smyslu X\sqrt{X} hradel a RzR_z rotací, přičemž CX hradla byla rozložena na ECR hradla a jednoqubitové rotace.

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(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
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:

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)
# 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()

Output of the previous code cell

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.

Škálování experimentu

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)
# 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()

Výstup předchozí buňky kódu

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ýsledky bez potlačení chyb jsou horší. Spuštění většího Circuit zahrnuje provádění více Gate, takže je více příležitostí pro hromadění chyb.
  • Přidání dynamického oddělení mohlo zhoršit výkon. 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 stejně dobrý, nebo téměř stejně dobrý jako výsledek pro 10 qubitů, přestože chybová úsečka je mnohem větší. To demonstruje sílu techniky ZNE!

Závěr

V tomto návodu jsi prozkoumal různé možnosti potlačení chyb dostupné pro primitivum Qiskit Runtime Estimator. Vyvinul jsi pracovní postup s použitím 10qubitového Circuit a poté jsi ho škáloval na 50 qubitů. Možná jsi si všiml, že povolení více možností potlačení a zmírnění chyb ne vždy zlepšuje výkon (konkrétně v tomto případě povolení dynamického oddělení). Většina možností přijímá další konfiguraci, kterou si můžeš vyzkoušet ve své vlastní práci!