Sari la conținutul principal

Compară setările Transpiler-ului

Estimare de utilizare: sub un minut pe un procesor Eagle r3 (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)

Context

Pentru a asigura rezultate mai rapide și mai eficiente, începând cu 1 martie 2024, circuitele și observabilele trebuie transformate pentru a folosi doar instrucțiunile suportate de QPU (unitatea de procesare cuantică) înainte de a fi trimise la primitivele Qiskit Runtime. Le numim circuite și observabile instruction set architecture (ISA). O metodă comună pentru a face acest lucru este utilizarea funcției generate_preset_pass_manager a Transpiler-ului. Totuși, ai putea alege să urmezi un proces mai manual.

De exemplu, ai putea dori să țintești un subset specific de Qubiți pe un anumit dispozitiv. Acest ghid testează performanța diferitelor setări ale Transpiler-ului, parcurgând întregul proces de creare, transpilare și trimitere a circuitelor.

Cerințe

Înainte de a începe, asigură-te că ai instalat următoarele:

  • Qiskit SDK v1.2 sau mai recent, cu suport pentru vizualizare
  • Qiskit Runtime v0.28 sau mai recent (pip install qiskit-ibm-runtime)

Configurare

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import GroverOperator, Diagonal

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

# Qiskit Runtime
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

Pasul 1: Mapează intrările clasice la o problemă cuantică

Creează un Circuit mic pe care Transpiler-ul să încerce să îl optimizeze. Acest exemplu creează un Circuit care execută algoritmul lui Grover cu un oracol care marchează starea 111. Apoi, simulează distribuția ideală (ce te-ai aștepta să măsori dacă ai rula acest Circuit pe un calculator cuantic perfect de un număr infinit de ori) pentru comparație ulterioară.

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_brisbanse'
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))

qc.draw(output="mpl", style="iqp")

Rezultatul celulei de cod anterioare

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Rezultatul celulei de cod anterioare

Pasul 2: Optimizează problema pentru execuția pe hardware cuantic

Apoi, transpilează circuitele pentru QPU. Vei compara performanța Transpiler-ului cu optimization_level setat la 0 (cel mai scăzut) față de 3 (cel mai ridicat). Nivelul de optimizare cel mai scăzut face minimul necesar pentru a rula Circuit-ul pe dispozitiv: mapează Qubiții circuitului la Qubiții dispozitivului și adaugă Gate-uri swap pentru a permite toate operațiile pe doi Qubiți. Nivelul de optimizare cel mai ridicat este mult mai inteligent și folosește numeroase trucuri pentru a reduce numărul total de Gate-uri. Deoarece Gate-urile multi-Qubit au rate de eroare ridicate și Qubiții decoerează în timp, circuitele mai scurte ar trebui să ofere rezultate mai bune.

Celula următoare transpilează qc pentru ambele valori ale optimization_level, afișează numărul de Gate-uri cu doi Qubiți și adaugă circuitele transpilate într-o listă. Unii algoritmi ai Transpiler-ului sunt randomizați, deci se setează un seed pentru reproductibilitate.

# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 14

Deoarece CNOT-urile au de obicei o rată de eroare ridicată, Circuit-ul transpilat cu optimization_level=3 ar trebui să performeze mult mai bine.

O altă modalitate de a îmbunătăți performanța este prin dynamic decoupling, aplicând o secvență de Gate-uri pe Qubiții inactivi. Aceasta anulează unele interacțiuni nedorite cu mediul. Celula următoare adaugă dynamic decoupling la Circuit-ul transpilat cu optimization_level=3 și îl adaugă în listă.

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Rezultatul celulei de cod anterioare

Pasul 3: Execută folosind primitivele Qiskit

În acest moment, ai o listă de circuite transpilate pentru QPU-ul specificat. Apoi, creează o instanță a primitivei Sampler și pornește un job în lot folosind managerul de context (with ...:), care deschide și închide automat lotul.

În interiorul managerului de context, eșantionează circuitele și stochează rezultatele în result.

with Batch(backend=backend):
sampler = Sampler()
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

Pasul 4: Post-procesează și returnează rezultatul în formatul clasic dorit

În final, reprezintă grafic rezultatele de pe dispozitiv față de distribuția ideală. Poți observa că rezultatele cu optimization_level=3 sunt mai aproape de distribuția ideală datorită numărului mai mic de Gate-uri, iar optimization_level=3 + dd este și mai aproape datorită dynamic decoupling.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Rezultatul celulei de cod anterioare

Poți confirma acest lucru calculând fidelitatea Hellinger între fiecare set de rezultate și distribuția ideală (mai mare este mai bine, iar 1 reprezintă fidelitate perfectă).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.848
0.945
0.990