Cięcie obwodów w celu redukcji głębokości
Szacowany czas użycia: osiem minut na procesorze Eagle (UWAGA: To jest tylko szacunek. Twój czas wykonania może się różnić.)
Tło
Ten samouczek pokazuje, jak zbudować Qiskit pattern do cięcia bramek w obwodzie kwantowym w celu redukcji głębokości obwodu. Aby uzyskać bardziej szczegółowe informacje na temat cięcia obwodów, odwiedź dokumentację dodatku Qiskit do cięcia obwodów.
Wymagania
Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane następujące elementy:
- Qiskit SDK v2.0 lub nowszy, z obsługą wizualizacji
- Qiskit Runtime v0.22 lub nowszy (
pip install qiskit-ibm-runtime) - Dodatek Qiskit do cięcia obwodów v0.9.0 lub nowszy (
pip install qiskit-addon-cutting)
Konfiguracja
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
Krok 1: Mapowanie klasycznych danych wejściowych na problem kwantowy
Zaimplementujemy nasz wzorzec Qiskit przy użyciu czterech kroków opisanych w dokumentacji. W tym przypadku będziemy symulować wartości oczekiwane na obwodzie o określonej głębokości, tnąc bramki, co powoduje powstanie bramek swap, i wykonując podeksperymenty na płytszych obwodach. Cięcie bramek jest istotne dla Kroku 2 (optymalizacja obwodu do wykonania kwantowego poprzez rozkładanie odległych bramek) i Kroku 4 (przetwarzanie końcowe w celu rekonstrukcji wartości oczekiwanych na oryginalnym obwodzie). W pierwszym kroku wygenerujemy obwód z biblioteki obwodów Qiskit i zdefiniujemy pewne obserwable.
- Wejście: Klasyczne parametry do zdefiniowania obwodu
- Wyjście: Abstrakcyjny obwód i obserwable
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")
Krok 2: Optymalizacja problemu do wykonania na sprzęcie kwantowym
- Wejście: Abstrakcyjny obwód i obserwable
- Wyjście: Docelowy obwód i obserwable uzyskane przez cięcie odległych bramek w celu redukcji głębokości po transpilacji
Wybieramy układ początkowy, który wymaga dwóch swapów do wykonania bramek między Qubitami 3 i 0 oraz kolejnych dwóch swapów, aby przywrócić Qubity do ich początkowych pozycji. Wybieramy optimization_level=3, czyli najwyższy poziom optymalizacji dostępny w predefiniowanym menedżerze przejść.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=circuit.num_qubits, simulator=False
)
pm = generate_preset_pass_manager(
optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)

print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)
Transpiled circuit depth: 103
Znajdź i przeciąj odległe bramki: Zastąpimy odległe bramki (bramki łączące nielokalne Qubity 0 i 3) obiektami TwoQubitQPDGate poprzez określenie ich indeksów. cut_gates zastąpi bramki pod podanymi indeksami obiektami TwoQubitQPDGate i zwróci również listę instancji QPDBasis — po jednej dla każdego rozkładu bramki. Obiekt QPDBasis zawiera informacje o tym, jak rozkładać cięte bramki na operacje jednoQubitowe.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)
Wygeneruj podeksperymenty do uruchomienia na Backend: generate_cutting_experiments przyjmuje obwód zawierający instancje TwoQubitQPDGate i obserwable jako PauliList.
Aby zasymulować wartość oczekiwaną pełnowymiarowego obwodu, z rozkładu kwaziprawnopodobieństwa rozkładanych bramek generowane jest wiele podeksperymentów, które następnie są wykonywane na jednym lub kilku Backend. Liczba próbek pobieranych z rozkładu jest kontrolowana przez num_samples, a jeden łączny współczynnik jest podawany dla każdej unikalnej próbki. Aby uzyskać więcej informacji na temat sposobu obliczania współczynników, zapoznaj się z materiałami objaśniającymi.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
Dla porównania, widzimy, że podeksperymenty QPD będą płytsze po cięciu odległych bramek: Oto przykład dowolnie wybranego podeksperymentu wygenerowanego z obwodu QPD. Jego głębokość została zredukowana o ponad połowę. Wiele z tych probabilistycznych podeksperymentów musi zostać wygenerowanych i ocenionych, aby zrekonstruować wartość oczekiwaną głębszego obwodu.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])
print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
"mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
Z drugiej strony, cięcie skutkuje koniecznością dodatkowego próbkowania. Tutaj tniemy trzy bramki CNOT, co powoduje narzut próbkowania wynoszący . Aby uzyskać więcej informacji na temat narzutu próbkowania wynikającego z cięcia obwodów, zapoznaj się z dokumentacją Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Krok 3: Wykonanie przy użyciu prymitywów Qiskit
Wykonaj docelowe obwody ("podeksperymenty") za pomocą prymitywu Sampler.
- Wejście: Docelowe obwody
- Wyjście: Rozkłady kwaziprawnopodobieństwa
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
print(job.job_id())
czypg1r6rr3g008mgp6g
Krok 4: Przetwarzanie końcowe i zwrócenie wyniku w pożądanym formacie klasycznym
Użyj wyników podeksperymentów, podobiektów obserwowalnych i współczynników próbkowania, aby zrekonstruować wartość oczekiwaną oryginalnego obwodu.
Wejście: Rozkłady kwaziprawnopodobieństwa Wyjście: Zrekonstruowane wartości oczekiwane
reconstructed_expvals = reconstruct_expectation_values(
results,
coefficients,
observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)
Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
Statevector(circuit).expectation_value(SparsePauliOp(observable))
for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)
Ideal expectation value
1.2283177520039992
Ankieta dotycząca samouczka
Wypełnij tę krótką ankietę, aby przekazać opinię na temat tego samouczka. Twoje spostrzeżenia pomogą nam ulepszyć nasze materiały i doświadczenia użytkownika.
Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.