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 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.
- Die in Schritt 2 erhaltenen Teilexperimente mithilfe eines
- 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 — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
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)

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-Argument für partition_problem hat den Typ PauliList. Koeffizienten und Phasen der Observable-Terme werden bei der Zerlegung des Problems und der Ausführung der Teilexperimente ignoriert. Sie können bei der Rekonstruktion des Erwartungswertes erneut 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)

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

Den Sampling-Overhead für die gewählten Schnitte berechnen
Hier schneiden wir zwei CNOT-Gates, was zu einem Sampling-Overhead von 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 vollständig großen Circuits 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 eindeutige 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