Přeskočit na hlavní obsah

Krájení Circuit pro snížení hloubky

Odhad využití: osm minut na procesoru Eagle (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)

Pozadí

Tento tutoriál ukazuje, jak sestavit Qiskit pattern pro krájení Gate v kvantovém Circuit za účelem snížení hloubky Circuit. Podrobnější diskusi o circuit cutting najdeš v dokumentaci doplňku Qiskit pro circuit cutting.

Požadavky

Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno následující:

  • Qiskit SDK v2.0 nebo novější, s podporou vizualizace
  • Qiskit Runtime v0.22 nebo novější (pip install qiskit-ibm-runtime)
  • Doplněk Qiskit pro circuit cutting v0.9.0 nebo novější (pip install qiskit-addon-cutting)

Nastavení

# 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: Mapování klasických vstupů na kvantový problém

Náš Qiskit pattern implementujeme pomocí čtyř kroků popsaných v dokumentaci. V tomto případě budeme simulovat střední hodnoty na Circuit určité hloubky tím, že nakrájíme Gate, čímž vzniknou swap gate, a spustíme dílčí experimenty na mělčích Circuit. Krájení Gate je relevantní pro kroky 2 (optimalizace Circuit pro kvantové spuštění rozkladem vzdálených Gate) a 4 (post-processing pro rekonstrukci středních hodnot na původním Circuit). V prvním kroku vygenerujeme Circuit z knihovny Qiskit a definujeme některé observovatelné.

  • Vstup: Klasické parametry pro definici Circuit
  • Výstup: Abstraktní Circuit a observovatelné
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")

Output of the previous code cell

Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru

  • Vstup: Abstraktní Circuit a observovatelné
  • Výstup: Cílový Circuit a observovatelné vzniklé krájením vzdálených Gate za účelem snížení hloubky transpilovánného Circuit

Zvolíme počáteční rozvržení, které vyžaduje dva swapy pro provedení Gate mezi Qubit 3 a 0, a další dva swapy pro návrat Qubit do jejich počátečních pozic. Zvolíme optimization_level=3, což je nejvyšší úroveň optimalizace dostupná s přednastavením pass manageru.

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)

Coupling map showing the qubits that will need to be swapped

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

Output of the previous code cell

Nalezení a krájení vzdálených Gate: Vzdálené Gate (Gate spojující nelokální Qubit 0 a 3) nahradíme objekty TwoQubitQPDGate zadáním jejich indexů. cut_gates nahradí Gate na zadaných indexech objekty TwoQubitQPDGate a také vrátí seznam instancí QPDBasis — jeden pro každý rozklad Gate. Objekt QPDBasis obsahuje informace o tom, jak rozložit nakrájené Gate na jednoqubitové operace.

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

Output of the previous code cell

Generování dílčích experimentů pro spuštění na Backend: generate_cutting_experiments přijímá Circuit obsahující instance TwoQubitQPDGate a observovatelné jako PauliList.

Aby bylo možné simulovat střední hodnotu Circuit plné velikosti, generuje se z kvazipravděpodobnostního rozdělení rozložených Gate mnoho dílčích experimentů, které se pak spouštějí na jednom nebo více Backend. Počet vzorků odebraných z rozdělení je řízen parametrem num_samples a pro každý jedinečný vzorek je uveden jeden kombinovaný koeficient. Více informací o výpočtu koeficientů najdeš v vysvětlujícím materiálu.

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)

Pro srovnání vidíme, že dílčí experimenty QPD budou po nakrájení vzdálených Gate mělčí: Zde je příklad libovolně zvoleného dílčího experimentu generovaného z QPD Circuit. Jeho hloubka se snížila o více než polovinu. Aby bylo možné rekonstruovat střední hodnotu hlubšího Circuit, musí být vygenerováno a vyhodnoceno mnoho těchto pravděpodobnostních dílčích experimentů.

# 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

Output of the previous code cell

Na druhou stranu krájení vede k potřebě extra vzorkování. Zde nakrájíme tři CNOT Gate, čímž vznikne vzorkovací overhead 939^3. Více o vzorkovacím overheadu způsobeném circuit cutting najdeš v dokumentaci Circuit Knitting Toolbox.

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

Krok 3: Spuštění pomocí primitiv Qiskit

Spusť cílové Circuit ("dílčí experimenty") pomocí primitivy Sampler.

  • Vstup: Cílové Circuit
  • Výstup: Kvazipravděpodobnostní rozdělení
# 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: Post-processing a vrácení výsledku v požadovaném klasickém formátu

Pomocí výsledků dílčích experimentů, dílčích observovatelných a vzorkovacích koeficientů rekonstruuj střední hodnotu původního Circuit.

Vstup: Kvazipravděpodobnostní rozdělení Výstup: Rekonstruované střední hodnoty

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

Průzkum k tutoriálu

Prosím, vyplň tento krátký průzkum a poskytni zpětnou vazbu k tomuto tutoriálu. Tvoje poznatky nám pomohou zlepšit naši nabídku obsahu a uživatelský zážitek.

Odkaz na průzkum

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.