Zom Hauptinhalt springe

Fählerminderungsoptione met däm Estimator-Primitiv kombiniere

Zitschätzung för de Nutzungsduur: Sivve Minute op enem Heron-r2-Prozessor (HINWEIS: Dat es nur e Schätzung. Üch tatssächliche Lauffzick kann variire.)

Hinterjrund

Disse Durchgang onnersöch de Fählerdämpfungs- un Fählerminderungsoptione, die met däm Estimator-Primitiv vun Qiskit Runtime verfüjbar sin. Ehr wäd enne Circuit un enne Observable opbaue un dann Jöbs met däm Estimator-Primitiv met verschiedene Kombinatione vun Fählerminderungsenstallunge enreiche. Donoh wäd Ehr de Erjebnisse afbildde, öm de Ußwirkunge vun de verschiedene Enstallunge ze beobachte. De meiste Beispiell nuze enne 10-Qubit-Circuit, öm de Visualisierunge leichter ze maache, un am Äng künnt Ehr dä Arbeitsablauf op 50 Qubits hochskaliere.

Dat sin de Fählerdämpfungs- un Fählerminderungsoptione, die Ehr verwende wäd:

  • Dynamisches Entkoppele
  • Messfählerminderung
  • Gate-Twirling
  • Rauschfreie Extrapolation (ZNE)

Vörutsäzzunge

Vör däm Aafange vun dässem Durchgang sörjt dobetür, dat Ehr Folgendes installiert hatt:

Enrichtung

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

Schratt 1: Klassische Enjabe op e Quanteproblem afbildde

Dä Durchgang jeiht dovun uß, dat dat klassische Problem ald op Quante afjebildet wude. Fangt aan, enne Circuit un enne Observable op ze baue, die jemesse wääde soll. Obwohl de hee verwandte Technike för vill verschiedene Arte vun Circuits jelze, nöz dä Durchgang dä Eenfachheit halver dä efficient_su2-Circuit uß der Qiskit-Circuit-Bibliothek.

efficient_su2 es e parametrisierter Quantecircuit, dä eso bestallt es, dat hä op Quantehardware met bejränzter Qubit-Verbinnung effizient ußföhrbar es, ävver jliechwohl expressiv jenuch es, öm Probleme en Anwendungsbereichen wie Optimierung un Chemie ze löße. Hä weed jebaut, indem Schichte vun parametrisierten Einzel-Qubit-Gates met einer Schicht afwexselt wääde, die e festes Muster vun Zwei-Qubit-Gates för en jewählte Anzahl vun Wiederholunge enthält. Dat Muster vun de Zwei-Qubit-Gates kann vum Benutzer bes bestimmt wääde. Hee künnt Ehr dat enjebute pairwise-Muster nuze, weil et de Circuit-Tiefe minimiert, indem de Zwei-Qubit-Gates mööjlichs dicht jepackt wääde. Diss Muster kann met nur linearer Qubit-Verbinnung ußjeföhrt wääde.

n_qubits = 10
reps = 1

circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)

circuit.decompose().draw("mpl", scale=0.7)

Usjabe vum vorherige Codeblock

Usjabe vum vorherige Codeblock

För onnze Observable nemme mer dä Pauli-ZZ-Operator, dä op dä letzze Qubit wirkt: 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
)

An däm Punkt künndet Ehr direkt wigger maache un Üch Circuit ußföhre un de Observable mässe. Ävver Ehr wellt uch de Usjabe vum Quantejerät met dä richtige Antwort verjliche — also met däm theoretische Wärt vun der Observable, als wär dä Circuit oohne Fähler ußjeföhrt wodeJe. För kleine Quantecircuits kann mer diese Wärt berechne, indem mer dä Circuit op enem klassischen Computer simuliert, ävver dat es nit möjlich för jrößere Circuits em Utility-Maßstab. Ehr künnt dat Problem met der "Spijelcircuit"-Technik (uch als "Berechne-Entberechne" bekannt) ömjäng, die nötzlich es, öm de Leistung vun Quantejärät ze benchmarke.

Spijelcircuit

Bei dä Spijelcircuit-Technik hängt mer dä Circuit met singe inverse Circuit aanenander, dä jebault weed, indem jede Gate vum Circuit en der umjekehrte Reihenfoljg invertiert weed. Dä resultierend Circuit implementiert dä Identitätsoperator, dä trivialerweise simuliert wääde kann. Weil de Struktur vum ursprünjlichen Circuit em Spijelcircuit erhalte blitt, jitt et de Usföhrung vum Spijelcircuits trotzdem Uffschluss darövver, wie dat Quantejärät beim ursprünjliche Circuit abschnide würd.

Dä folgende Codeblock wiest dä Circuit zufällige Parameter zo un baut dann dä Spijelcircuit metteiles dä unitary_overlap-Klasse. Vör däm Spieje vum Circuit hängt dran enne Barrier-Anweisung aan, öm ze verhinnere, dat dä Transpiler de beide Teile vum Circuit op beide Sitte vun dä Barrier ussemmefasst. Oohne die Barrier würd dä Transpiler dä ursprünjliche Circuit met sime Inversen verjliche, wat zo enem transpilierten Circuit oohne jenachte Gates führe würd.

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

Usjabe vum vorherige Codeblock

Usjabe vum vorherige Codeblock

Schratt 2: Problem för de Quantehardware-Usföhrung optimiere

Ehr müsst Üch Circuit optimiere, ehr Ehr en op Hardware ußföhre. Diss Prozess ömfasst enige Schratte:

  • En Qubit-Layout ußwähle, dat de virtuellen Qubits vun Üch Circuit op physikalische Qubits op der Hardware afbildet.
  • Swap-Gates enjefüje, falls nüüdich, öm Interaktione zweschenne Qubits, die nit verbonge sin, ze routte.
  • De Gates en Üch Circuit en ISA-Anweisunge (Instruction Set Architecture) üvvesäzze, die direkt op der Hardware ußjeföhrt wääde künne.
  • Circuit-Optimierunge dörchföhre, öm de Circuit-Tiefe un die Gate-Anzahl ze minimiere.

Dä en Qiskit enjebute Transpiler kann all diese Schratte för Üch dörchföhre. Weil diss Beispiel enne hardwareeffizienter Circuit nöz, soll dä Transpiler en Qubit-Layout wähle künne, dä keine Swap-Gates för de Routinh vun Interaktione nüüdich hät.

Ehr müsst dat Hardwarejärät ußwähle, dät Ehr verwende wellt, ehr Ehr Üch Circuit optimiert. Dä folgende Codeblock fräägt noh däm am wennichste besette Järät met mindestenns 127 Qubits.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

Ehr künnt Üch Circuit för Üch jewählte Backend transpiliere, indem Ehr enne Pass-Manager erstellt un dann dä Pass-Manager op dä Circuit ußföhrt. E eenfache Wäch, enne Pass-Manager ze erstelle, es de Funktion generate_preset_pass_manager ze verwende. Luur der op Met Pass-Managern transpiliere för e meeh detaillieerter Erklärung vum Transpiliere met Pass-Managern.

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)

Usjabe vum vorherige Codeblock

Usjabe vum vorherige Codeblock

Dä transpilierte Circuit enthält jetzt nur noch ISA-Anweisunge. De Einzel-Qubit-Gates sin en Bezoch op X\sqrt{X}-Gates un RzR_z-Rotatione zerläät wodeJe, un die CX-Gates sin en ECR-Gates un Einzel-Qubit-Rotatione zerläät wodeJe.

Dä Transpilierungsprozess hät de virtuellen Qubits vum Circuit op physikalische Qubits op der Hardware afjebildet. De Information övver dat Qubit-Layout es em layout-Attribut vum transpilierten Circuit jespeichert. De Observable wur uch en Bezug op de virtuellen Qubits definiert, esu dat Ehr disses Layout op de Observable aanwände müsst, watt Ehr met dä apply_layout-Methode vun SparsePauliOp maache künnt.

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

Schratt 3: Met Qiskit-Primitiven ußföhre

Jetz sin Ehr parat, Üch Circuit met däm Estimator-Primitiv ußzeföhre.

Hee wäd Ehr fönnef separate Jöbs enreiche, aanjefange oohne Fählerdämpfung udder -minderung, un nohenenanner veschiedene Fählerdämpfungs- un Fählerminderungsoptione vun Qiskit Runtime aktiviere. För Informationen övver de Optione luurt Ehr op die folgende Sigge:

Weil disse Jöbs unabhängich vunnenander ußjeföhrt wääde künne, künnt Ehr dä Batch-Modus verwende, öm Qiskit Runtime de Möjlichkeit ze gäve, dä Zeitplan ehr Usföhrung ze optimiere.

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)

Schratt 4: Erjebnisse nohbearbeide un em jewünschte klassische Format retourniere

Jetzt künnt Ehr de Daate analysiere. Hee wäd Ehr de Joberjebnisse abhole, de jemessene Erwartungswerte erußhole un de Werte afbildde, inkl. Fählerbalkens vun einer Standardabweichung.

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

Usjabe vum vorherige Codeblock

En diesem kleine Maßstab es et schwer, dä Effekt vun de meiste Fählerminderungstechnike ze sinn, ävver die rauschfreie Extrapolation jitt tatsächlich enne merkliche Vödeil. Ähm ävver, datt diss Verbesserung nit omsunst kütt, weil dat ZNE-Ergebnis uch enne jrößere Fählerbalken hät.

Dat Experiment hochskaliere

Beim Entwickle vun enem Experiment es et nötzlich, met enem kleine Circuit aanzefahge, öm Visualisierunge un Simulatione leichter ze maache. Jetzt, woh Ehr Üch Arbeitsablauf op enem 10-Qubit-Circuit entwickelt un jetestet hatt, künnt Ehr en op 50 Qubits hochskaliere. Dä folgende Codeblock widderholt all Schratte uß dässem Durchgang, ävver wendt se jetzt op enne 50-Qubit-Circuit aan.

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

Usjabe vum vorherige Codeblock

Wann Ehr de 50-Qubit-Erjebnisse met de 10-Qubit-Erjebnisse vun vorher verjlicht, künnt Ehr Folgendes bemärke (Üch Erjebnisse künne övver Läuf variire):

  • De Erjebnisse oohne Fählerminderung sin schlechter. Dä jrößere Circuit ußzeföhre bedüt, dat mieh Gates ußjeföhrt wääde, esu dat et mieh Möjlichkeite jitt, dat Fähler sich ansammele.
  • De Zusatz vum dynamischen Entkoppele hät de Leistung mööjlicherweis verschlechtert. Dat es nit überraschend, weil dä Circuit sehr dicht es. Dynamisches Entkoppele es en eeeschte Linie nözzlich, wann et jroße Lücke em Circuit jitt, während dä Qubits leerlaufe oohne dat Gates op se aanjewendet wääde. Wann disse Lücke nit vorhande sin, es dynamisches Entkoppele nit effektiv un kann de Leistung sogar dursch Fähler en de dynamischen Entkoppelungspulse selvs verschlechtern. Dä 10-Qubit-Circuit wär mööjlicherweis zo klein jewäse, öm dissen Effekt ze beobachte.
  • Met rauschfreier Extrapolation es dat Ergebnis esu joot udder fast esu joot wie dat 10-Qubit-Ergebnis, obwohl dä Fählerbalken vill jrößer es. Dat demonstriert de Leistungsfähigkeit vun dä ZNE-Technik!

Zusammefassunk

En dässem Durchgang hatt Ehr verschiedene Fählerminderungsoptione onnersöch, die för dä Qiskit Runtime Estimator-Primitiv verfüjbar sin. Ehr hatt enne Arbeitsablauf met enem 10-Qubit-Circuit entwickelt un dän dann op 50 Qubits hochskaliert. Ehr künntet beobachtet han, dat dat Aktiviere vun mieh Fählerdämpfungs- un Fählerminderungsoptione de Leistung nit emmer verbessert (övverall jesäht, dat Aktiviere vum dynamischen Entkoppele en dässem Fall). De meiste Optione akzeptiere zusäzzliche Konfiguration, die Ehr en Üch eijene Arbeit ußprobbiere künnt!