Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

Gate Cutting zur Reduzierung der Circuit-Breite

In diesem Notebook arbeiten wir die Schritte eines Qiskit-Musters durch und verwenden dabei Circuit Cutting, um die Anzahl der Qubits in einem Circuit zu reduzieren. Wir schneiden Gates, damit wir den Erwartungswert eines Vier-Qubit-Circuits allein mithilfe von Zwei-Qubit-Experimenten rekonstruieren können.

Dies sind die Schritte, die wir unternehmen werden:

  • Schritt 1: Problem auf Quantencircuits und Operatoren abbilden:

    • Den Hamiltonian auf einen Quantencircuit abbilden.
  • Schritt 2: Für Zielhardware optimieren [Verwendet das Cutting-Addon]:

    • Den Circuit und das Observable schneiden.
    • Die Teilexperimente für die Hardware transpilieren.

  • Schritt 3: Auf Zielhardware ausführen:

    • Die in Schritt 2 erhaltenen Teilexperimente mithilfe eines Sampler-Primitives ausführen.
  • Schritt 4: Ergebnisse nachverarbeiten [Verwendet das Cutting-Addon]:

    • Die Ergebnisse aus Schritt 3 kombinieren, um den Erwartungswert des betreffenden Observables zu rekonstruieren.

Schritt 1: Abbilden​

Einen Circuit zum Schneiden erstellen​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_cutting": "qiskit-addon-cutting", "qiskit_aer": "qiskit-aer", "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")
from qiskit.circuit.library import efficient_su2

qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)

qc.draw("mpl", scale=0.8)

Quantencircuit-Diagramm

Ein Observable festlegen​

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])

Schritt 2: Optimieren​

Den Circuit und das Observable gemäß einer festgelegten Qubit-Partitionierung trennen​

Jedes Label in partition_labels entspricht dem circuit-Qubit am selben Index. Qubits mit demselben Partitions-Label werden gruppiert, und nicht-lokale Gates, die mehr als eine Partition überspannen, werden geschnitten.

Hinweis: Das observables-Kwarg von partition_problem ist vom Typ PauliList. Koeffizienten und Phasen der Observablen-Terme werden bei der Zerlegung des Problems und der Ausführung der Teilexperimente ignoriert. Sie können bei der Rekonstruktion des Erwartungswerts wieder angewendet werden.

from qiskit_addon_cutting import partition_problem

partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

Das zerlegte Problem visualisieren​

subobservables
{'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']),
'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}
subcircuits["A"].draw("mpl", scale=0.8)

Quantencircuit-Diagramm

subcircuits["B"].draw("mpl", scale=0.8)

Quantencircuit-Diagramm

Den Sampling-Overhead für die gewählten Schnitte berechnen​

Hier schneiden wir zwei CNOT-Gates, was zu einem Sampling-Overhead von 929^2 führt.

Weitere Informationen zum Sampling-Overhead beim Circuit Cutting findest du im Erklärungsmaterial.

import numpy as np

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 81.0

Die Teilexperimente generieren, die auf dem Backend ausgeführt werden sollen​

generate_cutting_experiments akzeptiert circuits/observables-Argumente als Dictionaries, die Qubit-Partitions-Labels den entsprechenden subcircuit/subobservables zuordnen.

Um den Erwartungswert des Circuits in voller Größe zu simulieren, werden viele Teilexperimente aus der gemeinsamen Quasiwahrscheinlichkeitsverteilung der zerlegten Gates generiert und anschließend auf einem oder mehreren Backends ausgeführt. Die Anzahl der aus der Verteilung entnommenen Stichproben wird durch num_samples gesteuert, und für jede unterschiedliche Stichprobe wird ein kombinierter Koeffizient angegeben. Weitere Informationen zur Berechnung der Koeffizienten findest du im Erklärungsmaterial.

from qiskit_addon_cutting import generate_cutting_experiments

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)

Ein Backend auswählen​

Hier verwenden wir ein Fake-Backend, was dazu führt, dass Qiskit Runtime im lokalen Modus ausgeführt wird (d. h. auf einem lokalen Simulator).

from qiskit_ibm_runtime.fake_provider import FakeManilaV2

backend = FakeManilaV2()

Die Teilexperimente für das Backend vorbereiten​

Wir müssen die Circuits mit unserem Backend als Ziel transpilieren, bevor wir sie an Qiskit Runtime übermitteln.

from qiskit.transpiler import generate_preset_pass_manager

# Transpile the subexperiments to ISA circuits
pass_manager = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

Schritt 3: Ausführen​

Die Teilexperimente mit dem Qiskit Runtime Sampler-Primitive ausführen​

from qiskit_ibm_runtime import SamplerV2, Batch

# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

Schritt 4: Nachverarbeiten​

Den Erwartungswert rekonstruieren​

Erwartungswerte für jeden Observable-Term rekonstruieren und kombinieren, um den Erwartungswert für das ursprüngliche Observable zu rekonstruieren.

from qiskit_addon_cutting import reconstruct_expectation_values

# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)

# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

Den rekonstruierten Erwartungswert mit dem exakten Erwartungswert aus dem ursprünglichen Circuit und Observable vergleichen​

from qiskit_aer.primitives import EstimatorV2

estimator = EstimatorV2()
exact_expval = estimator.run([(qc, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.6991539
Exact expectation value: 0.56254612
Error in estimation: 0.13660778
Relative error in estimation: 0.24283836