Lumaktaw sa pangunahing nilalaman

Ikumpara ang mga setting ng transpiler

Tinatayang paggamit: wala pang isang minuto sa isang Eagle r3 processor (TANDAAN: Tantiya lamang ito. Maaaring mag-iba ang iyong runtime.)

Background​

Para matiyak ang mas mabilis at mas mahusay na mga resulta, simula 1 Marso 2024, ang mga circuit at observable ay kailangang i-transform upang gumamit lamang ng mga instruksiyong sinusuportahan ng QPU (quantum processing unit) bago isumite sa mga Qiskit Runtime primitive. Tinatawag natin itong mga instruction set architecture (ISA) na circuit at observable. Isang karaniwang paraan para gawin ito ay ang paggamit ng generate_preset_pass_manager na function ng transpiler. Gayunpaman, maaari kang pumili na sumunod sa mas manuwal na proseso.

Halimbawa, maaaring gusto mong i-target ang isang partikular na subset ng mga qubit sa isang partikular na device. Sinusubukan ng walkthrough na ito ang performance ng iba't ibang setting ng transpiler sa pamamagitan ng pagtatapos ng buong proseso ng paglikha, pag-transpile, at pagsumite ng mga circuit.

Mga Kinakailangan​

Bago ka magsimula, tiyaking naka-install ang sumusunod:

  • Qiskit SDK v1.2 o mas bago, na may suporta sa visualization
  • Qiskit Runtime v0.28 o mas bago (pip install qiskit-ibm-runtime)

Setup​

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

Hakbang 1: I-map ang mga klasikal na input sa isang quantum na problema​

Gumawa ng maliit na Circuit para subukan ng transpiler na i-optimize. Sa halimbawang ito, gagawa ng circuit na nagsasagawa ng Grover's algorithm na may oracle na nagmamarka sa estado na 111. Susunod, i-simulate ang ideal na distribusyon (ang inaasahan mong masusukat kung pinatakbo mo ito sa isang perpektong quantum computer nang walang katapusang beses) para sa paghahambing sa ibang pagkakataon.

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

Output of the previous code cell

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

plot_histogram(ideal_distribution)

Output of the previous code cell

Hakbang 2: I-optimize ang problema para sa pagpapatakbo sa quantum hardware​

Susunod, i-transpile ang mga circuit para sa QPU. Ikukumpara mo ang performance ng transpiler na ang optimization_level ay nakatakda sa 0 (pinakamababa) laban sa 3 (pinakamataas). Ang pinakamababang antas ng optimization ay gumagawa lamang ng pinakamaliit na kinakailangan para mapatakbo ang circuit sa device; imi-map nito ang mga circuit qubit sa mga device qubit at nagdadagdag ng mga swap gate para payagan ang lahat ng two-qubit na operasyon. Ang pinakamataas na antas ng optimization ay mas matalino at gumagamit ng maraming paraan para bawasan ang kabuuang bilang ng gate. Dahil ang mga multi-qubit na gate ay may mataas na error rate at ang mga qubit ay nagiging decoherent sa paglipas ng panahon, ang mas maikling mga circuit ay dapat magbigay ng mas magandang resulta.

Ang sumusunod na cell ay nagta-transpile ng qc para sa parehong halaga ng optimization_level, nagpi-print ng bilang ng mga two-qubit na gate, at nagdadagdag ng mga transpiled na circuit sa isang listahan. Ang ilan sa mga algorithm ng transpiler ay randomized, kaya nagtatakda ito ng seed para sa reproducibility.

# 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

Dahil ang mga CNOT ay karaniwang may mataas na error rate, ang circuit na na-transpile gamit ang optimization_level=3 ay dapat na mas mahusay na magsagawa.

Isa pang paraan para mapabuti ang performance ay sa pamamagitan ng dynamic decoupling, sa pamamagitan ng paglalagay ng sequence ng mga gate sa mga idle na qubit. Nagkakansela ito ng ilang hindi gustong interaksyon sa kapaligiran. Ang sumusunod na cell ay nagdadagdag ng dynamic decoupling sa circuit na na-transpile gamit ang optimization_level=3 at idinadagdag ito sa listahan.

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

Output of the previous code cell

Hakbang 3: Isagawa gamit ang mga Qiskit primitive​

Sa puntong ito, mayroon kang listahan ng mga circuit na na-transpile para sa tinukoy na QPU. Susunod, gumawa ng instance ng sampler primitive at magsimula ng batched na trabaho gamit ang context manager (with ...:), na awtomatikong nagbubukas at nagsasara ng batch.

Sa loob ng context manager, i-sample ang mga circuit at itago ang mga resulta sa result.

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

Hakbang 4: I-post-process at ibalik ang resulta sa nais na klasikal na format​

Sa wakas, i-plot ang mga resulta mula sa mga pagpapatakbo ng device laban sa ideal na distribusyon. Makikita mo na ang mga resulta na may optimization_level=3 ay mas malapit sa ideal na distribusyon dahil sa mas mababang bilang ng gate, at ang optimization_level=3 + dd ay mas lalong malapit dahil sa 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",
],
)

Output of the previous code cell

Maaari mo itong kumpirmahin sa pamamagitan ng pagkukuwenta ng Hellinger fidelity sa pagitan ng bawat set ng resulta at ng ideal na distribusyon (mas mataas ay mas mabuti, at ang 1 ay perpektong fidelity).

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