Circuit cutting pentru reducerea adâncimii
Estimare de utilizare: opt minute pe un procesor Eagle (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)
Background
Acest tutorial demonstrează cum să construiești un Qiskit pattern pentru tăierea porților dintr-un Circuit cuantic în vederea reducerii adâncimii circuitului. Pentru o discuție mai aprofundată despre circuit cutting, vizitează documentația addon-ului Qiskit pentru circuit cutting.
Cerințe
Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:
- Qiskit SDK v2.0 sau mai recent, cu suport de vizualizare
- Qiskit Runtime v0.22 sau mai recent (
pip install qiskit-ibm-runtime) - Addon-ul Qiskit pentru circuit cutting v0.9.0 sau mai recent (
pip install qiskit-addon-cutting)
Configurare
# 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
Pasul 1: Maparea intrărilor clasice la o problemă cuantică
Vom implementa pattern-ul nostru Qiskit folosind cei patru pași descriși în documentație. În acest caz, vom simula valorile de așteptare pe un Circuit de o anumită adâncime prin tăierea porților, ceea ce generează porți swap și execută subexperimente pe circuite mai puțin adânci. Circuit cutting este relevant pentru Pasul 2 (optimizarea circuitului pentru execuție cuantică prin descompunerea porților îndepărtate) și Pasul 4 (post-procesare pentru reconstruirea valorilor de așteptare pe circuitul original). În primul pas, vom genera un Circuit din biblioteca de circuite Qiskit și vom defini câteva observabile.
- Intrare: Parametri clasici pentru a defini un Circuit
- Ieșire: Circuit abstract și observabile
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")
Pasul 2: Optimizarea problemei pentru execuția pe hardware cuantic
- Intrare: Circuit abstract și observabile
- Ieșire: Circuit țintă și observabile produse prin tăierea porților îndepărtate pentru a reduce adâncimea circuitului transpilat
Alegem un layout inițial care necesită două swap-uri pentru a executa porțile dintre Qubiții 3 și 0 și alte două swap-uri pentru a returna Qubiții la pozițiile lor inițiale. Alegem optimization_level=3, care este cel mai înalt nivel de optimizare disponibil cu un pass manager preset.
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
Găsirea și tăierea porților îndepărtate: Vom înlocui porțile îndepărtate (porțile care conectează Qubiți non-locali, 0 și 3) cu obiecte TwoQubitQPDGate prin specificarea indicilor lor. cut_gates va înlocui porțile la indicii specificați cu obiecte TwoQubitQPDGate și va returna, de asemenea, o listă de instanțe QPDBasis — câte una pentru fiecare descompunere de poartă. Obiectul QPDBasis conține informații despre cum să descompunem porțile tăiate în operații cu un singur Qubit.
# 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)
Generarea subexperimentelor care vor fi rulate pe Backend: generate_cutting_experiments acceptă un Circuit care conține instanțe TwoQubitQPDGate și observabile sub formă de PauliList.
Pentru a simula valoarea de așteptare a circuitului de dimensiune completă, multe subexperimente sunt generate din distribuția de quasiprobabilitate comună a porților descompuse și sunt executate pe unul sau mai multe Backend-uri. Numărul de eșantioane preluate din distribuție este controlat de num_samples, iar un coeficient combinat este dat pentru fiecare eșantion unic. Pentru mai multe informații despre modul în care sunt calculați coeficienții, consultă materialul explicativ.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
Pentru comparație, observăm că subexperimentele QPD vor fi mai puțin adânci după tăierea porților îndepărtate: Iată un exemplu de subexperiment ales arbitrar, generat din circuitul QPD. Adâncimea sa a fost redusă cu mai mult de jumătate. Multe dintre aceste subexperimente probabilistice trebuie generate și evaluate pentru a reconstrui o valoare de așteptare a circuitului mai adânc.
# 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
Pe de altă parte, tăierea implică necesitatea unui eșantionaj suplimentar. Aici tăiem trei porți CNOT, rezultând un overhead de eșantionare de . Pentru mai multe informații despre overhead-ul de eșantionare generat de circuit cutting, consultă documentația Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Pasul 3: Execuția folosind primitivele Qiskit
Execută circuitele țintă ("subexperimentele") cu Primitiva Sampler.
- Intrare: Circuite țintă
- Ieșire: Distribuții de quasiprobabilitate
# 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
Pasul 4: Post-procesare și returnarea rezultatului în formatul clasic dorit
Folosește rezultatele subexperimentelor, subobservabilele și coeficienții de eșantionare pentru a reconstrui valoarea de așteptare a circuitului original.
Intrare: Distribuții de quasiprobabilitate Ieșire: Valori de așteptare reconstruite
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
Sondaj despre tutorial
Te rog să completezi acest scurt sondaj pentru a oferi feedback despre acest tutorial. Opiniile tale ne vor ajuta să îmbunătățim conținutul și experiența utilizatorilor.
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.