Přeskočit na hlavní obsah

Porovnání nastavení transpileru

Odhadovaná doba použití: méně než jedna minuta na procesoru Eagle r3 (POZNÁMKA: Jde pouze o odhad. Skutečná doba běhu se může lišit.)

Pozadí

Aby byly výsledky rychlejší a efektivnější, musí být od 1. března 2024 Circuit a observables transformovány tak, aby používaly pouze instrukce podporované QPU (quantum processing unit), než jsou odeslány do primitiv Qiskit Runtime. Takové Circuit a observables nazýváme instruction set architecture (ISA). Jedním z běžných způsobů, jak toho dosáhnout, je použít funkci generate_preset_pass_manager z Transpileru. Můžeš se však rozhodnout postupovat i ručně.

Například možná budeš chtít cílit na konkrétní podmnožinu Qubitů na konkrétním zařízení. Tento návod testuje výkonnost různých nastavení Transpileru tím, že projde celým procesem vytváření, transpilování a odesílání Circuit.

Požadavky

Než začneš, ujisti se, že máš nainstalováno následující:

  • Qiskit SDK v1.2 nebo novější, s podporou vizualizace
  • Qiskit Runtime v0.28 nebo novější (pip install qiskit-ibm-runtime)

Nastavení

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

Krok 1: Mapování klasických vstupů na kvantový problém

Vytvoř malý Circuit, na němž se Transpiler pokusí provést optimalizaci. Tento příklad vytváří Circuit, který provádí Groverův algoritmus s orákulem, které označuje stav 111. Poté simuluj ideální distribuci (co bys očekával/a naměřit, kdybys toto spustil/a na dokonalém kvantovém počítači nekonečněkrát) pro pozdější porovnání.

# 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

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

Dále transpiluj Circuit pro QPU. Porovnáš výkonnost Transpileru s optimization_level nastaveným na 0 (nejnižší) oproti 3 (nejvyšší). Nejnižší úroveň optimalizace dělá naprosté minimum potřebné pro spuštění Circuit na zařízení – mapuje Qubity Circuit na Qubity zařízení a přidává swap Gate, aby bylo možné provádět všechny dvoQubitové operace. Nejvyšší úroveň optimalizace je mnohem chytřejší a využívá řadu triků ke snížení celkového počtu Gate. Protože vícQubitové Gate mají vysokou míru chyb a Qubity se dekoherují v čase, kratší Circuit by měly přinést lepší výsledky.

Následující buňka transpiluje qc pro obě hodnoty optimization_level, vypíše počet dvoQubitových Gate a přidá transpilované Circuit do seznamu. Některé algoritmy Transpileru jsou náhodné, proto se nastavuje seed pro reprodukovatelnost.

# 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

Protože CNOT Gate mívají obvykle vysokou míru chyb, Circuit transpilovaný s optimization_level=3 by měl dosahovat výrazně lepších výsledků.

Dalším způsobem, jak zlepšit výkon, je dynamické oddělování (dynamic decoupling), při němž se aplikuje sekvence Gate na nečinné Qubity. Tím se potlačují některé nežádoucí interakce s prostředím. Následující buňka přidá dynamické oddělování do Circuit transpilovaného s optimization_level=3 a přidá ho do seznamu.

# 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

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

V tomto okamžiku máš seznam Circuit transpilovaných pro zadané QPU. Dále vytvoř instanci primitiva Sampler a spusť dávkovou úlohu pomocí context manageru (with ...:), který automaticky otevírá a zavírá dávku.

V rámci context manageru vzorkuj Circuit a ulož výsledky do result.

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

Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu

Nakonec vykresli výsledky ze spuštění na zařízení oproti ideální distribuci. Výsledky s optimization_level=3 jsou blíže ideální distribuci díky nižšímu počtu Gate, a optimization_level=3 + dd je ještě blíže díky dynamickému oddělování.

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

To si můžeš ověřit výpočtem Hellingerovy věrnosti mezi každou sadou výsledků a ideální distribucí (vyšší je lepší, přičemž 1 znamená dokonalou věrnost).

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