Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Benchmark wierności procesu QFT za pomocą Orbit, funkcji Qiskit od Quantum Elements

Szacowany czas użycia: 2 minuty na procesorze Heron r3. (UWAGA: To jest tylko szacunek. Twój rzeczywisty czas wykonania może się różnić.) Domyślnie ten samouczek przesyła jedno zadanie funkcji Orbit przy użyciu działającego po stronie serwera Runtime Batch usługi IBM Quantum Compute Service z trzema zadaniami podrzędnymi, po 300 PUB-ów na zadanie podrzędne, co daje łącznie 921 600 strzałów.

Ostrzeżenie: Obwody dynamiczne są obecnie funkcją eksperymentalną i podlegają ograniczeniom w Quantum Compute [3], które mogą powodować niepowodzenia zadań. Na przykład błąd 6073 wskazuje, że zadanie przekroczyło limit pamięci sprzętu klasycznego sterowania [4]. Ten notatnik zmniejsza to ryzyko, dzieląc rozmiary obwodów na trzy zadania podrzędne Runtime Batch [5]. Każde porównanie o stałym rozmiarze pozostaje w jednym zadaniu podrzędnym, podczas gdy duże i małe rozmiary są parowane, aby zrównoważyć obciążenie sterowania klasycznego między zadaniami.

Efekty uczenia się​

  • Przygotować stany produktowe QFT†∣x⟩\mathrm{QFT}^\dagger|x\rangle używane przez próbkowany estymator wierności procesu na rysunku 2a w Ref. [1].

  • Zbudować równoważne implementacje unitarną i dynamiczną kwantowej transformaty Fouriera z pomiarem.

  • Wybrać kubity fizyczne dla obwodów dynamicznych, korzystając z aktualnych danych kalibracji i łączności.

  • Porównać oszacowania wierności procesu QFT dla surowej implementacji unitarnej, surowej dynamicznej i dynamicznej wzbogaconej o Orbit, wraz ze wzrostem rozmiaru obwodu.

  • Przesłać jedno obciążenie Orbit za pomocą API batch mode, zachowując każde porównanie trzech strategii o stałym rozmiarze w jednym zadaniu podrzędnym.

  • Sprawdzić metadane Orbit, aby potwierdzić, czy zastosowano dynamiczne odsprzęganie (DD) i łagodzenie błędów pomiaru (MEM).

Tło​

Rysunek 2a w Ref. [1] porównuje wierność procesu idealnego kanału QFT+M z zaszumionymi implementacjami unitarną i dynamiczną. Dla próbkowanej etykiety xx w bazie obliczeniowej benchmark przygotowuje QFT†∣x⟩\mathrm{QFT}^\dagger|x\rangle, stosuje zaszumioną implementację QFT+M i szacuje prawdopodobieństwo pxp_x uzyskania odpowiadającego idealnego wyniku. Te stany odwrotnej QFT są separowalne i mogą być efektywnie przygotowane za pomocą bramek Hadamarda i wirtualnych obrotów fazowych.

Dla mm niezależnie próbkowanych etykiet notatnik używa nieobciążonego estymatora wyprowadzonego w Ref. [1]:

F^proc=mm−1(1m∑ℓ=1mpxℓ)2−1m(m−1)∑ℓ=1mpxℓ.\widehat{\mathcal{F}}_{\mathrm{proc}} = \frac{m}{m-1}\left(\frac{1}{m}\sum_{\ell=1}^{m}\sqrt{p_{x_\ell}}\right)^2 - \frac{1}{m(m-1)}\sum_{\ell=1}^{m}p_{x_\ell}.

Konstrukcja dynamiczna zastępuje bramki kontrolowanej fazy unitarnego QFT+M pomiarami w środku obwodu i klasycznie warunkowanymi obrotami fazy [1]. Dzięki odroczonemu pomiarowi oba obwody mają ten sam idealny rozkład wyjściowy. Implementacja unitarna wymaga dwukubitowych bramek każdy-z-każdym i z powodu ograniczeń lokalnej łączności generuje narzut bramek SWAP rzędu O(n2)O(n^2). Natomiast obwód dynamiczny używa wyłącznie bramek jednokubitowych i O(n)O(n) pomiarów w środku obwodu, które wprowadzają długie przestoje w środku. Pomijając całkowicie DD, podstawowa rywalizacja toczy się więc między O(n2)O(n^2) zaszumionych bramek unitarnych a O(n)O(n) zaszumionych pomiarów w środku obwodu. Jeśli przestoje podczas tych pomiarów mają dostatecznie niski błąd, obwód dynamiczny przewyższy implementacje unitarne, a poniżej pokazujemy, że można to osiągnąć za pomocą Orbit.

Związek z rysunkiem 2a. Ten notatnik podąża za protokołem próbkowanej wierności procesu z artykułu, ale jest adaptacją samouczka skoncentrowaną na Orbit, a nie reprodukcją. Na przykład, podczas gdy rysunek 2a używał ibm_kyiv z 2000 strzałami, my używamy nowoczesnego urządzenia ibm_aachen z mniejszą liczbą 1024 strzałów, aby oszczędzić czas QPU.

Przykładowe wyniki​

Statyczny wykres poniżej pokazuje średnie krzywe wierności procesu z trzech kolejnych zadań rozwojowych uruchomionych na ibm_aachen z opisanym poniżej procesem. Jak pokazano tutaj, Orbit może znacznie zwiększyć jakość obwodów dynamicznych; dynamiczna QFT dorównuje jakości opublikowanego benchmarku i wykazuje poprawę względem standardowej unitarnej QFT. Jak zobaczymy, wyniki te wynikają z automatycznego dobrego wyboru kubitów, automatycznego wstawiania dynamicznego odsprzęgania (nieoptymalizowanego ręcznie pod ten problem) oraz łagodzenia błędów pomiaru. Dla zabawy porównaj te wyniki ze swoimi wynikami na końcu, zwłaszcza jeśli wybierzesz inny backend.

Uwaga: Te wyniki są ilustracyjnym zrzutem udanego wcześniejszego przebiegu z Orbit, a nie gwarancją wydajności. Poniższe wyniki powinny wyglądać jakościowo podobnie, ale szczegóły zależą od wybranego urządzenia i jego właściwości, zwłaszcza błędów pomiaru i bezczynności, w czasie wykonania. Wierność procesu QFT na 'ibm_aachen'

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_catalog": "qiskit-ibm-catalog", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")

Wymagania​

Przed uruchomieniem tego samouczka zainstaluj najnowsze wersje następujących pakietów:

  • numpy

  • matplotlib

  • qiskit

  • qiskit-ibm-runtime

  • qiskit-ibm-catalog

pip install qiskit qiskit-ibm-runtime qiskit-ibm-catalog numpy matplotlib

Konfiguracja​

Uwierzytelnij się w IBM Quantum® Platform, załaduj ibm_aachen i załaduj Quantum Elements Orbit z Qiskit Functions Catalog.

N_VALUES to lista rozmiarów obwodów: każda wartość to liczba logicznych qubitów w jednym obwodzie QFT. M to liczba niezależnie próbkowanych etykiet bazowych xx ocenianych dla każdego rozmiaru; w estymatorze wierności jest to mm z powyższego równania. Dla każdej pary (N, x) notatnik przesyła po jednym PUB-ie dla każdej z trzech strategii, więc ustalony rozmiar wnosi 3 * M PUB-ów i 3 * M * SHOTS zliczeń obwodu.

Domyślny skan parametrów obejmuje zatem 15 rozmiarów obwodów, 20 próbkowanych ciągów bitów na rozmiar i trzy strategie. NUM_BATCH_PARTITIONS=3 określa trzy uporządkowane partycje Runtime Batch, a BATCH_MAX_PUBS_PER_JOB jest przekazywane przez orbit.run(...) jako konfiguracja JSON. Duże N_VALUES tworzą bardziej złożone obwody dynamiczne, podczas gdy większe M umieszcza więcej PUB-ów w każdym zadaniu podrzędnym; każde z nich może zwiększyć obciążenie pamięci sterowania klasycznego backendu. Jeśli pojedyncze zadanie podrzędne z obwodem dynamicznym osiągnie ten limit, zmniejsz N_VALUES lub M albo zwiększ NUM_BATCH_PARTITIONS. Zmniejsz SHOTS, gdy celem jest obniżenie zużycia wykonania, a nie liczby lub złożoności obwodów.

OPTIMIZATION_LEVEL=0 prosi Transpiler Qiskit o wykonanie wymaganego tłumaczenia i routingu z minimalną optymalizacją. Używamy go, aby zachować jawnie wybrane układy obwodów dynamicznych i ułatwić interpretację surowych porównań. Poziomy 1–3 są poprawnymi eksperymentami, ale mogą przepisywać lub upraszczać obwody i zmieniać wybrane rozmieszczenie; oczekiwane korzyści są tu niewielkie, ponieważ obwód dynamiczny nie ma bramek dwukubitowych ani ograniczeń łączności. Jeśli zmienisz poziom, uruchom ponownie benchmark i traktuj wyniki jako oddzielne porównanie.

import warnings
from collections import Counter, defaultdict

import matplotlib.pyplot as plt
import numpy as np
from qiskit import (
ClassicalRegister,
QuantumCircuit,
QuantumRegister,
transpile,
)
from qiskit.circuit import IfElseOp
from qiskit.synthesis.qft import synth_qft_full
from qiskit_ibm_catalog import QiskitFunctionsCatalog
from qiskit_ibm_runtime import QiskitRuntimeService

IBM_BACKEND_NAME = "ibm_marrakesh" # Change to your preferred backend

# N_VALUES contains circuit sizes in logical qubits; M is samples per size.
N_VALUES = [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40]
M = 20
# Each sample is run with this many shots for each of the three strategies.
SHOTS = 1024
# Transpilation options.
OPTIMIZATION_LEVEL = 0 # Level 0 preserves the explicitly selected layouts and dynamic structure.
RNG_SEED = 12345 # Reproducible transpilation seed.
# Batch partitioning options.
NUM_BATCH_PARTITIONS = 3
STRATEGY_LABELS = ("unitary/raw", "dynamic/raw", "dynamic/orbit")

def balanced_n_groups(
n_values: list[int], num_jobs: int = 3
) -> list[list[int]]:
"""Pair circuit sizes into balanced Runtime Batch partitions."""
values = sorted(n_values)
if len(set(values)) != len(values):
raise ValueError("N_VALUES must not contain duplicates")
if not 1 <= num_jobs <= len(values):
raise ValueError(
"NUM_BATCH_PARTITIONS must be between 1 and len(N_VALUES)"
)

max_group_size = (len(values) + num_jobs - 1) // num_jobs
groups = [[] for _ in range(num_jobs)]
loads = [0] * num_jobs
pair_counts = [0] * num_jobs
remaining = values.copy()

while len(remaining) >= 2:
candidates = [
i
for i, group in enumerate(groups)
if len(group) + 2 <= max_group_size
]
if not candidates:
break
smallest = remaining.pop(0)
largest = remaining.pop()
job_index = min(
candidates, key=lambda i: (loads[i], len(groups[i]), i)
)
pair = (
[largest, smallest]
if pair_counts[job_index] % 2 == 0
else [smallest, largest]
)
groups[job_index].extend(pair)
loads[job_index] += smallest + largest
pair_counts[job_index] += 1

while remaining:
value = remaining.pop()
candidates = [
i for i, group in enumerate(groups) if len(group) < max_group_size
]
job_index = min(
candidates, key=lambda i: (loads[i], len(groups[i]), i)
)
groups[job_index].append(value)
loads[job_index] += value

return groups

N_GROUPS = balanced_n_groups(N_VALUES, NUM_BATCH_PARTITIONS)

service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.backend(IBM_BACKEND_NAME)
if "if_else" not in backend.target.operation_names:
backend.target.add_instruction(IfElseOp, name="if_else")

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
quantum_elements_orbit = catalog.load("quantum-elements/orbit")
if quantum_elements_orbit is None:
raise RuntimeError(
"Quantum Elements Orbit is not enabled for this IBM Quantum instance."
)

required_qubits = max(N_VALUES)
if backend.num_qubits < required_qubits:
raise ValueError(
f"Backend {backend.name} has {backend.num_qubits} qubits, "
f"but this benchmark needs at least {required_qubits}."
)

{
"orbit version": quantum_elements_orbit.version,
"backend": backend.name,
"num_qubits": backend.num_qubits,
"n_values": N_VALUES,
"m": M,
"shots": SHOTS,
"num_function_jobs": 1,
"num_runtime_batch_partitions": NUM_BATCH_PARTITIONS,
"n_groups": N_GROUPS,
"pubs_per_partition": [
len(group) * M * len(STRATEGY_LABELS) for group in N_GROUPS
],
"total_pubs": len(N_VALUES) * M * len(STRATEGY_LABELS),
"total_shots": len(N_VALUES) * M * len(STRATEGY_LABELS) * SHOTS,
}
{'orbit version': '0.3.23',
'backend': 'ibm_marrakesh',
'num_qubits': 156,
'n_values': [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40],
'm': 20,
'shots': 1024,
'num_function_jobs': 1,
'num_runtime_batch_partitions': 3,
'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]],
'pubs_per_partition': [300, 300, 300],
'total_pubs': 900,
'total_shots': 921600}

Zbuduj obwody QFT​

Dla każdej próbkowanej liczby całkowitej xx bit_inv_qft przygotowuje stan produktowy QFT†∣x⟩\mathrm{QFT}^\dagger|x\rangle za pomocą Hadamardów, po których następują obroty fazowe. Notatnik następnie dodaje albo standardową unitarną QFT, albo jej semiklasyczny dynamiczny odpowiednik QFT.

Obie implementacje pomijają końcową sieć zamiany (swap network). Kolejność wyświetlania bitów klasycznych w Qiskit sprawia zatem, że oczekiwany zmierzony ciąg jest odwrotnością zerowo-dopełnionej binarnej reprezentacji xx, kodowanej przez format(x, f"0{n}b")[::-1].

def bit_inv_qft(circuit: QuantumCircuit, x: int, conv: str = "LSB") -> None:
"""Prepare QFT†|x⟩ as a product state on `circuit`."""
num_qubits = circuit.num_qubits
circuit.h(range(num_qubits))
for j in range(num_qubits):
phase = (
2 * np.pi * x / 2 ** (num_qubits - j)
if conv == "LSB"
else 2 * np.pi * x / 2 ** (j + 1)
)
circuit.p(-phase, j)

def build_unitary_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:
"""Build a unitary QFT circuit for sampled basis label `x`."""
if not 0 <= x < 2**num_qubits:
raise ValueError(
f"x={x} is outside the {num_qubits}-qubit basis range"
)
qreg = QuantumRegister(num_qubits, "q")
creg = ClassicalRegister(num_qubits, "c")
circuit = QuantumCircuit(qreg, creg, name=f"unitary_qft_{num_qubits}q")
bit_inv_qft(circuit, x)
circuit.append(
synth_qft_full(num_qubits, do_swaps=False), range(num_qubits)
)
circuit.measure(range(num_qubits), range(num_qubits))
return circuit

def _warn_if_precision_loss(max_num_entanglements: int) -> None:
"""Warn when the smallest QFT rotation may lose float precision."""
if max_num_entanglements > -np.finfo(float).minexp:
warnings.warn(
"precision loss in QFT."
f" The rotation needed to represent {max_num_entanglements} entanglements"
" is smaller than the smallest normal floating-point number.",
category=RuntimeWarning,
stacklevel=4,
)

def synth_dynamic_qft(
circuit: QuantumCircuit, *, do_swaps: bool = False
) -> QuantumCircuit:
"""Append semiclassical dynamic QFT operations to `circuit`."""
num_qubits = circuit.num_qubits
creg = circuit.cregs[0]
_warn_if_precision_loss(num_qubits - 1)

for j in reversed(range(num_qubits)):
circuit.h(j)
circuit.measure([j], [j])

if j > 0:
with circuit.if_test((creg[j], 1)):
for k in reversed(range(j)):
circuit.p(np.pi * (2.0 ** (k - j)), k)

if do_swaps:
for i in range(num_qubits // 2):
circuit.swap(i, num_qubits - i - 1)
return circuit

def build_dynamic_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:
"""Build a dynamic QFT circuit for sampled basis label `x`."""
if not 0 <= x < 2**num_qubits:
raise ValueError(
f"x={x} is outside the {num_qubits}-qubit basis range"
)
qreg = QuantumRegister(num_qubits, "q")
creg = ClassicalRegister(num_qubits, "c")
circuit = QuantumCircuit(qreg, creg, name=f"dynamic_qft_{num_qubits}q")
bit_inv_qft(circuit, x)
synth_dynamic_qft(circuit, do_swaps=False)
return circuit

def target_output_bitstring(x: int, n_qubits: int) -> str:
"""Return Qiskit's expected measured bitstring for basis label `x`."""
return format(int(x), f"0{n_qubits}b")[::-1]

def process_fidelity_from_success_probabilities(
success_probabilities: list[float],
) -> float:
"""Estimate process fidelity from sampled success probabilities."""
m = len(success_probabilities)
if m <= 1:
raise ValueError(
"m must be larger than 1 for the process-fidelity estimator"
)
succ = np.asarray(success_probabilities, dtype=float)
return float(
(m / (m - 1)) * (np.mean(np.sqrt(succ)) ** 2)
- np.sum(succ) / (m * (m - 1))
)

Wybierz fizyczne kubity dla obwodu dynamicznego​

Implementacja dynamiczna nie wymaga bramek dwukubitowych, więc jej kubity fizyczne nie muszą tworzyć połączonego podgrafu. Dla każdego rozmiaru obwodu selektor ranguje aktualne kubity backendu na podstawie wyniku, w którym niski błąd odczytu ma wagę 80%, a wysokie T1T_1 i T2T_2 po 10%. Najpierw wybiera kubity o wysokim wyniku bez bezpośrednich sprzężeń między nimi, gdy to możliwe, co może zmniejszyć narażenie na przesłuchy między sąsiadami, a następnie wypełnia pozostałe pozycje według wyniku.

Warianty dynamic/raw i dynamic/orbit używają dokładnie tego samego wybranego układu dla danego rozmiaru, dzięki czemu ich porównanie jest kontrolowane pod względem układu. Obwód unitary/raw jest natomiast mapowany i trasowany przez transpiler, ponieważ wymaga łączności dwukubitowej. Ten wybór oparty na kalibracji w czasie wykonania jest specyficzny dla tego samouczka; nie jest to stały 40-kubitowy układ ibm_kyiv użyty w eksperymentach z rysunku 2a artykułu.

def value_from_property(raw):
"""Extract a numeric value from a backend property record."""
if raw is None:
return None
if isinstance(raw, tuple):
return raw[0]
return getattr(raw, "value", raw)

def qubit_property_value(properties, qubit: int, *names: str) -> float | None:
"""Read the first available backend property for one qubit."""
for name in names:
try:
value = value_from_property(
properties.qubit_property(qubit, name)
)
except Exception:
value = None
if value is not None:
return float(value)
return None

def measurement_error(properties, qubit: int) -> float | None:
"""Return readout error, deriving it from asymmetric errors if needed."""
readout = qubit_property_value(properties, qubit, "readout_error")
if readout is not None:
return readout
p01 = qubit_property_value(properties, qubit, "prob_meas0_prep1")
p10 = qubit_property_value(properties, qubit, "prob_meas1_prep0")
if p01 is not None and p10 is not None:
return 0.5 * (p01 + p10)
return None

def coupling_edges(backend) -> list[tuple[int, int]]:
"""Return directed coupling-map edges as integer pairs."""
coupling_map = getattr(backend, "coupling_map", None)
if coupling_map is not None:
try:
return [(int(a), int(b)) for a, b in coupling_map.get_edges()]
except Exception:
pass
built = backend.target.build_coupling_map()
return [(int(a), int(b)) for a, b in built.get_edges()]

def neighbor_map(backend) -> dict[int, set[int]]:
"""Build an undirected neighboring-qubit map from backend connectivity."""
neighbors = {qubit: set() for qubit in range(backend.num_qubits)}
for a, b in coupling_edges(backend):
neighbors[a].add(b)
neighbors[b].add(a)
return neighbors

def anchored_score(
value: float | None, *, good: float, bad: float, higher_is_better: bool
) -> float:
"""Normalize a calibration value to a [0, 1] quality score."""
if value is None:
return 0.0
if higher_is_better:
low, high = sorted((bad, good))
score = (value - low) / (high - low)
else:
low, high = sorted((good, bad))
score = (high - value) / (high - low)
return float(min(1.0, max(0.0, score)))

def qubit_metrics(backend) -> list[dict]:
"""Collect and rank calibration metrics for all backend qubits."""
properties = backend.properties()
rows = []
for qubit in range(backend.num_qubits):
t1 = qubit_property_value(properties, qubit, "T1", "t1")
t2 = qubit_property_value(properties, qubit, "T2", "t2")
meas_error = measurement_error(properties, qubit)
measurement_score = anchored_score(
meas_error, good=0.005, bad=0.05, higher_is_better=False
)
t1_score = anchored_score(
t1, good=0.00025, bad=0.00005, higher_is_better=True
)
t2_score = anchored_score(
t2, good=0.00025, bad=0.00005, higher_is_better=True
)
rows.append(
{
"qubit": qubit,
"t1": t1,
"t2": t2,
"measurement_error": meas_error,
"score": 0.8 * measurement_score
+ 0.1 * t1_score
+ 0.1 * t2_score,
}
)
return sorted(rows, key=lambda row: row["score"], reverse=True)

def select_dynamic_qubits(backend, n_qubits: int) -> list[int]:
"""Choose high-scoring, preferably nonadjacent qubits for dynamic circuits."""
ranked = qubit_metrics(backend)
neighbors = neighbor_map(backend)
selected = []
blocked = set()
for row in ranked:
qubit = row["qubit"]
if qubit in blocked:
continue
selected.append(qubit)
blocked.add(qubit)
blocked.update(neighbors.get(qubit, set()))
if len(selected) == n_qubits:
return selected

for row in ranked:
qubit = row["qubit"]
if qubit not in selected:
selected.append(qubit)
if len(selected) == n_qubits:
return selected
raise RuntimeError(f"Could not select {n_qubits} physical qubits")

print("The top 3 qubits (according to our scoring): ")
print(qubit_metrics(backend)[0:3])
print("Worst 3 qubits (according to our scoring): ")
print(qubit_metrics(backend)[-3:])
The top 3 qubits (according to our scoring):
[{'qubit': 35, 't1': 0.0004024167194138543, 't2': 0.0003811132194981602, 'measurement_error': 0.003662109375, 'score': 1.0}, {'qubit': 92, 't1': 0.0003281277199162346, 't2': 0.0002414989126012369, 'measurement_error': 0.00390625, 'score': 0.9957494563006185}, {'qubit': 5, 't1': 0.0002919284700896914, 't2': 0.0002184196315970577, 'measurement_error': 0.00537109375, 'score': 0.9776125935763066}]
Worst 3 qubits (according to our scoring):
[{'qubit': 76, 't1': 7.65424382656864e-05, 't2': 3.438638139929193e-05, 'measurement_error': 0.05859375, 'score': 0.013271219132843196}, {'qubit': 26, 't1': 7.000219049917298e-06, 't2': 7.849466281257113e-06, 'measurement_error': 0.1009521484375, 'score': 0.0}, {'qubit': 82, 't1': 7.260327905277343e-06, 't2': 6.440762177221646e-06, 'measurement_error': 0.5048828125, 'score': 0.0}]

Przygotuj benchmarkowe PUB-y​

Dla każdej pary (N, x) notatnik najpierw transpiluje obwody logiczne, a następnie tworzy jeden PUB Sampler dla każdej strategii:

  • unitary/raw: unitarna QFT na układzie wybranym przez transpiler, z trasowaniem w razie potrzeby i bez DD ani MEM od Orbit.

  • dynamic/raw: dynamiczna QFT na kubitach fizycznych wybranych na podstawie kalibracji, bez DD ani MEM od Orbit.

  • dynamic/orbit: ten sam transpilowany obwód dynamiczny na tych samych kubitach fizycznych, z włączonymi DD i MEM od Orbit.

PUB wzbogacony o Orbit używa transpilation_mode="validate", ponieważ jego mapowanie zostało już wybrane. Orbit waliduje dostarczony obwód fizyczny zamiast go remapować, a następnie stosuje swój potok DD i MEM. Ponieważ tylko wzbogacona krzywa dynamiczna żąda MEM, nie należy tego interpretować jako izolowanego porównania DD kontra brak DD.

PUB-y, opcje per PUB oraz rekordy wyników są spłaszczane w tej samej kolejności. Dla każdego ustalonego NN PUB-y unitary/raw, dynamic/raw i dynamic/orbit są utrzymywane razem w jednej partycji Runtime Batch. Pomocnik grupujący paruje duże i małe rozmiary obwodów, naprzemiennie zmienia ich kolejność i równoważy sumę NN w trzech partycjach jako prosty wskaźnik obciążenia sterowania klasycznego.

strategy_options = {
"unitary/raw": {"mode": "raw"},
"dynamic/raw": {"mode": "raw"},
"dynamic/orbit": {"mode": "orbit", "transpilation_mode": "validate"},
}
rng = np.random.default_rng(RNG_SEED)
pubs = []
pub_options = []
pub_records = []
layout_summary = {}

target_decimals_by_n = {
n_qubits: [int(x) for x in rng.integers(0, 2**n_qubits, size=M)]
for n_qubits in N_VALUES
}

for partition_index, n_group in enumerate(N_GROUPS):
for n_qubits in n_group:
dynamic_qubits = select_dynamic_qubits(backend, n_qubits)
layout_summary[str(n_qubits)] = {"dynamic_qubits": dynamic_qubits}

for x in target_decimals_by_n[n_qubits]:
target_bitstring = target_output_bitstring(x, n_qubits)
unitary_logical = build_unitary_qft_circuit(n_qubits, x)
dynamic_logical = build_dynamic_qft_circuit(n_qubits, x)

unitary_transpiled = transpile(
unitary_logical,
backend=backend,
optimization_level=OPTIMIZATION_LEVEL,
seed_transpiler=RNG_SEED,
)
dynamic_transpiled = transpile(
dynamic_logical,
backend=backend,
optimization_level=OPTIMIZATION_LEVEL,
seed_transpiler=RNG_SEED,
initial_layout=dynamic_qubits,
)

circuits_by_label = {
"unitary/raw": unitary_transpiled,
"dynamic/raw": dynamic_transpiled,
"dynamic/orbit": dynamic_transpiled,
}
for label in STRATEGY_LABELS:
circuit = circuits_by_label[label]
options = dict(strategy_options[label])
pubs.append((circuit, None, SHOTS))
pub_options.append(options)
pub_records.append(
{
"partition_index": partition_index,
"n_qubits": n_qubits,
"target_decimal": x,
"target_bitstring": target_bitstring,
"label": label,
"pub_options": options,
"dynamic_qubits": (
dynamic_qubits
if label.startswith("dynamic/")
else None
),
"transpiled_depth": circuit.depth(),
"transpiled_size": circuit.size(),
}
)

BATCH_MAX_PUBS_PER_JOB = max(
len(group) * M * len(STRATEGY_LABELS) for group in N_GROUPS
)

{
"num_function_jobs": 1,
"num_runtime_batch_partitions": len(N_GROUPS),
"n_groups": {
job_index: group for job_index, group in enumerate(N_GROUPS)
},
"n_load_per_job": {
job_index: sum(group) for job_index, group in enumerate(N_GROUPS)
},
"pubs_per_partition": {
partition_index: len(group) * M * len(STRATEGY_LABELS)
for partition_index, group in enumerate(N_GROUPS)
},
"expected_executions_per_partition": {
partition_index: len(group) * M * len(STRATEGY_LABELS) * SHOTS
for partition_index, group in enumerate(N_GROUPS)
},
"first_pub_record": pub_records[0],
"largest_dynamic_qubit_set": layout_summary[str(max(N_VALUES))][
"dynamic_qubits"
],
}
{'num_function_jobs': 1,
'num_runtime_batch_partitions': 3,
'n_groups': {0: [40, 2, 7, 15, 10],
1: [35, 3, 6, 20, 9],
2: [30, 4, 5, 25, 8]},
'n_load_per_job': {0: 74, 1: 73, 2: 72},
'pubs_per_partition': {0: 300, 1: 300, 2: 300},
'expected_executions_per_partition': {0: 307200, 1: 307200, 2: 307200},
'first_pub_record': {'partition_index': 0,
'n_qubits': 40,
'target_decimal': 853235401719,
'target_bitstring': '1110111111000000110100110001010101100011',
'label': 'unitary/raw',
'pub_options': {'mode': 'raw'},
'dynamic_qubits': None,
'transpiled_depth': 4778,
'transpiled_size': 28259},
'largest_dynamic_qubit_set': [35,
92,
5,
141,
75,
147,
1,
152,
12,
10,
128,
107,
21,
8,
15,
98,
33,
134,
154,
53,
78,
109,
112,
149,
120,
55,
3,
132,
85,
49,
115,
101,
95,
64,
136,
71,
90,
56,
18,
44]}

Uruchom benchmark​

Przekaż serializowalną do JSON konfigurację Batch przez orbit.run(...). Orbit tworzy uwierzytelniony Runtime Batch wewnątrz Funkcji, przesyła uporządkowane partycje PUB-ów, zamyka Batch po przesłaniu i zwraca wywołującemu jeden scalony wynik. Lokalny Batch utworzony przez wywołującego nie może kontrolować asynchronicznego zadania Qiskit Function.

Uporządkowana lista PUB-ów jest dzielona na grupy według liczby kubitów, tak aby wszystkie trzy strategie dla ustalonego NN można było porównać uczciwie: wszystkie 60 PUB-ów dla ustalonego NN — 20 próbkowanych wejść razy trzy strategie — wykonuje się w tym samym zadaniu podrzędnym Runtime Batch. Domyślne grupy łączą duże i małe obwody i zawierają po 300 PUB-ów każda, zmniejszając szansę, że jedno zadanie podrzędne zgromadzi wszystkie największe programy dynamiczne, przy jednoczesnym zachowaniu porównań wewnątrz zadania.

job = quantum_elements_orbit.run(
primitive="sampler",
pubs=pubs,
backend_name=backend.name,
options={
"pub_options": pub_options,
"batch": {
"max_pubs_per_job": BATCH_MAX_PUBS_PER_JOB,
"max_time": "2h",
},
"save_backend_info": True,
},
)

{
"backend": backend.name,
"function_job_id": job.job_id,
"status": job.status(),
"num_pubs": len(pubs),
"max_pubs_per_runtime_job": BATCH_MAX_PUBS_PER_JOB,
"n_groups": N_GROUPS,
}
{'backend': 'ibm_marrakesh',
'function_job_id': '4b953302-afc3-4889-9ecf-e63d78b4e252',
'status': 'QUEUED',
'num_pubs': 900,
'max_pubs_per_runtime_job': 300,
'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]]}

Pobierz wyniki i oblicz wierność procesu​

Pobierz pojedynczy scalony wynik zwrócony przez Orbit Function. Orbit zapisuje w metadanych wyniku identyfikator Runtime Batch, identyfikatory podrzędnych zadań Runtime oraz zakresy PUB-ów; kolejność PUB-ów jest zachowywana w zadaniach podrzędnych. Dla każdego PUB-a pxp_x to prawdopodobieństwo przypisane oczekiwanemu ciągowi bitów. extract_counts odczytuje zliczenia zwrócone wywołującemu; dla dynamic/orbit są to zliczenia skorygowane przez MEM, gdy łagodzenie się powiedzie. extract_raw_counts odzyskuje również odpowiadające niezłagodzone zliczenia zapisane w metadanych Orbit. Kod grupuje 20 wartości pxp_x dla każdej pary (N, label) i stosuje estymator wprowadzony powyżej.

Wykreślony słownik process_fidelity używa zatem surowych zliczeń dla unitary/raw i dynamic/raw, ale zliczeń skorygowanych przez MEM dla dynamic/orbit. Równoległy słownik raw_process_fidelity zachowuje niezłagodzone obliczenie dla każdej strategii i jest przydatny przy oddzielaniu efektu MEM od reszty potoku Orbit. MEM koryguje zwrócony histogram wyjściowy; nie może wstecznie zmienić wyniku pomiaru w trakcie obwodu, który został już użyty przez sprzężenie w przód (feedforward) w czasie rzeczywistym.

def extract_counts(pub_result) -> dict[str, int]:
"""Extract the first nonempty classical-register counts mapping."""
data = getattr(pub_result, "data", None)
if data is None:
raise TypeError("pub_result.data is missing")

for name in dir(data):
if name.startswith("_"):
continue
register = getattr(data, name)
get_counts = getattr(register, "get_counts", None)
if callable(get_counts):
counts = get_counts()
if counts:
return counts

raise TypeError(
"No classical register with get_counts() found in pub_result.data"
)

def extract_raw_counts(pub_result) -> dict[str, int]:
"""Extract unmitigated counts from Orbit metadata when available."""
orbit_metadata = pub_result.metadata.get("quantum_elements_orbit", {})
mem_report = orbit_metadata.get("measurementErrorMitigation", {})
return mem_report.get("rawCounts") or extract_counts(pub_result)

def probability_for_bitstring(
counts: dict[str, int], bitstring: str, n_qubits: int
) -> float:
"""Return observed probability of an expected bitstring."""
total = sum(counts.values())
if total <= 0:
return 0.0
normalized = Counter()
for measured, count in counts.items():
key = measured.replace(" ", "")[-n_qubits:].zfill(n_qubits)
normalized[key] += count
return float(normalized.get(bitstring, 0) / total)

try:
job_result = job.result()
except Exception as exc:
job_logs = getattr(job, "logs", lambda: "")()
if job_logs:
print(f"Logs for Function job {job.job_id}:\n{job_logs}")
raise RuntimeError(
f"Batched Orbit Function job {job.job_id} failed: "
f"{type(exc).__name__}: {exc}"
) from exc

if len(job_result) != len(pub_records):
raise RuntimeError(
f"Function job {job.job_id} returned {len(job_result)} PUB results; "
f"expected {len(pub_records)}"
)

batch_report = job_result.metadata.get("quantum_elements_orbit", {}).get(
"batch", {}
)
batch_partitions = batch_report.get("partitions", [])
partition_by_pub = {}
for partition in batch_partitions:
start = int(partition["pubStart"])
stop = start + int(partition["pubCount"])
for pub_index in range(start, stop):
partition_by_pub[pub_index] = partition

if len(partition_by_pub) != len(pub_records):
raise RuntimeError(
"Orbit Batch metadata does not cover every returned PUB."
)

grouped_success = defaultdict(list)
grouped_raw_success = defaultdict(list)
pub_summaries = []

for pub_index, (record, pub_result) in enumerate(
zip(pub_records, job_result, strict=True)
):
partition = partition_by_pub[pub_index]
label = record["label"]
n_qubits = record["n_qubits"]
counts = extract_counts(pub_result)
raw_counts = extract_raw_counts(pub_result)
success = probability_for_bitstring(
counts, record["target_bitstring"], n_qubits
)
raw_success = probability_for_bitstring(
raw_counts, record["target_bitstring"], n_qubits
)
key = (n_qubits, label)
grouped_success[key].append(success)
grouped_raw_success[key].append(raw_success)

orbit_report = pub_result.metadata.get("quantum_elements_orbit", {})
mem_report = orbit_report.get("measurementErrorMitigation", {})
pub_summaries.append(
{
**record,
"function_job_id": job.job_id,
"runtime_batch_id": batch_report.get("id"),
"runtime_job_id": partition.get("jobId"),
"runtime_batch_partition": partition.get("index"),
"success_probability": success,
"raw_success_probability": raw_success,
"orbit_mode": orbit_report.get("mode"),
"transpilation_mode": orbit_report.get("transpilationMode"),
"physical_layout": orbit_report.get("physicalLayout"),
"dd_status": orbit_report.get("status", "not_applied"),
"num_sequences_added": orbit_report.get("numSequencesAdded", 0),
"num_gaps_filled": orbit_report.get("numGapsFilled", 0),
"dynamic_dd_seq": orbit_report.get("dynamicDdSeq"),
"mem_status": mem_report.get("status", "not_requested"),
"warnings": orbit_report.get("warnings", [])
+ mem_report.get("warnings", []),
}
)

process_fidelity = defaultdict(dict)
raw_process_fidelity = defaultdict(dict)
mean_success_probability = defaultdict(dict)
raw_mean_success_probability = defaultdict(dict)

for (n_qubits, label), probabilities in sorted(grouped_success.items()):
n_key = str(n_qubits)
process_fidelity[n_key][label] = (
process_fidelity_from_success_probabilities(probabilities)
)
mean_success_probability[n_key][label] = float(np.mean(probabilities))

for (n_qubits, label), probabilities in sorted(grouped_raw_success.items()):
n_key = str(n_qubits)
raw_process_fidelity[n_key][label] = (
process_fidelity_from_success_probabilities(probabilities)
)
raw_mean_success_probability[n_key][label] = float(np.mean(probabilities))

process_fidelity = dict(process_fidelity)
raw_process_fidelity = dict(raw_process_fidelity)
mean_success_probability = dict(mean_success_probability)
raw_mean_success_probability = dict(raw_mean_success_probability)

{
"function_job_id": job.job_id,
"runtime_batch_id": batch_report.get("id"),
"runtime_job_ids": batch_report.get("jobIds", []),
"runtime_batch_partitions": batch_partitions,
"n_groups": N_GROUPS,
"process_fidelity": process_fidelity,
"mean_success_probability": mean_success_probability,
}
{'function_job_id': '4b953302-afc3-4889-9ecf-e63d78b4e252',
'runtime_batch_id': '096da6a9-cbea-4940-bd6f-3bdebfad436e',
'runtime_job_ids': ['dabhtfh6e67c73a1dspg',
'dabhtkp6e67c73a1dt0g',
'dabhtple36ac739ff7ag'],
'runtime_batch_partitions': [{'index': 0,
'pubStart': 0,
'pubCount': 300,
'jobId': 'dabhtfh6e67c73a1dspg'},
{'index': 1,
'pubStart': 300,
'pubCount': 300,
'jobId': 'dabhtkp6e67c73a1dt0g'},
{'index': 2,
'pubStart': 600,
'pubCount': 300,
'jobId': 'dabhtple36ac739ff7ag'}],
'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]],
'process_fidelity': {'2': {'dynamic/orbit': 0.96795267841156,
'dynamic/raw': 0.982106196462969,
'unitary/raw': 0.9813114213091467},
'3': {'dynamic/orbit': 0.9260299657995851,
'dynamic/raw': 0.9561294394338111,
'unitary/raw': 0.9500873287318629},
'4': {'dynamic/orbit': 0.8830473125154273,
'dynamic/raw': 0.8338948172317725,
'unitary/raw': 0.8723284938083211},
'5': {'dynamic/orbit': 0.8345263398914793,
'dynamic/raw': 0.7202370952637628,
'unitary/raw': 0.7886522480980407},
'6': {'dynamic/orbit': 0.7555427381229081,
'dynamic/raw': 0.5132161092086612,
'unitary/raw': 0.7212899564100468},
'7': {'dynamic/orbit': 0.7050351887334156,
'dynamic/raw': 0.3785120988393675,
'unitary/raw': 0.6045265978953258},
'8': {'dynamic/orbit': 0.6646106162043616,
'dynamic/raw': 0.16869967802023164,
'unitary/raw': 0.5043677114922691},
'9': {'dynamic/orbit': 0.5230999959600392,
'dynamic/raw': 0.03629305030182246,
'unitary/raw': 0.4292104907804543},
'10': {'dynamic/orbit': 0.5578720776016518,
'dynamic/raw': 0.041261905412736485,
'unitary/raw': 0.3509234542669733},
'15': {'dynamic/orbit': 0.2580377878661623,
'dynamic/raw': 3.083881578947369e-05,
'unitary/raw': 0.09345722346055228},
'20': {'dynamic/orbit': 0.05567530378670723,
'dynamic/raw': 0.0,
'unitary/raw': 0.00010793585526315788},
'25': {'dynamic/orbit': 0.003482218097213064,
'dynamic/raw': 0.0,
'unitary/raw': 0.0},
'30': {'dynamic/orbit': 7.268778589499868e-06,
'dynamic/raw': 0.0,
'unitary/raw': 0.0},
'35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},
'40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}},
'mean_success_probability': {'2': {'dynamic/orbit': 0.96796875,
'dynamic/raw': 0.98212890625,
'unitary/raw': 0.98134765625},
'3': {'dynamic/orbit': 0.92607421875,
'dynamic/raw': 0.95615234375,
'unitary/raw': 0.950146484375},
'4': {'dynamic/orbit': 0.88310546875,
'dynamic/raw': 0.833935546875,
'unitary/raw': 0.872705078125},
'5': {'dynamic/orbit': 0.8345703125,
'dynamic/raw': 0.7203125,
'unitary/raw': 0.78896484375},
'6': {'dynamic/orbit': 0.7556640625,
'dynamic/raw': 0.51337890625,
'unitary/raw': 0.721826171875},
'7': {'dynamic/orbit': 0.70517578125,
'dynamic/raw': 0.3787109375,
'unitary/raw': 0.605419921875},
'8': {'dynamic/orbit': 0.66484375,
'dynamic/raw': 0.16884765625,
'unitary/raw': 0.50576171875},
'9': {'dynamic/orbit': 0.5232421875,
'dynamic/raw': 0.0365234375,
'unitary/raw': 0.43203125},
'10': {'dynamic/orbit': 0.5580078125,
'dynamic/raw': 0.04150390625,
'unitary/raw': 0.353466796875},
'15': {'dynamic/orbit': 0.258349609375,
'dynamic/raw': 0.0001953125,
'unitary/raw': 0.09453125},
'20': {'dynamic/orbit': 0.057568359375,
'dynamic/raw': 0.0,
'unitary/raw': 0.000341796875},
'25': {'dynamic/orbit': 0.006396484375,
'dynamic/raw': 0.0,
'unitary/raw': 0.0},
'30': {'dynamic/orbit': 0.000146484375,
'dynamic/raw': 0.0,
'unitary/raw': 0.0},
'35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},
'40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}}}

Sprawdź metadane Orbit DD​

Poniższe podsumowanie sprawdza metadane PUB dynamic/orbit zamiast zakładać, że żądany DD został wstawiony. Sprawdź status, zgłoszoną dynamiczną sekwencję DD, ostrzeżenia oraz liczby wypełnionych luk i dodanych sekwencji. Udane wstawienie powinno dać niezerowe liczby przynajmniej dla niektórych PUB, ale dokładne wartości zależą od zaplanowanego obwodu, ograniczeń czasowych backendu oraz rozmiaru obwodu. Te metadane opisują sekwencję zastosowaną przez Orbit; nie należy jej oznaczać jako protokołu FC-DD z artykułu, chyba że raport wyraźnie ustala tę równoważność.

dd_summary = defaultdict(lambda: Counter())
sequence_totals = defaultdict(int)
warning_examples = []

for summary in pub_summaries:
if summary["label"] != "dynamic/orbit":
continue
n_key = str(summary["n_qubits"])
dd_summary[n_key][summary["dd_status"]] += 1
sequence_totals[n_key] += int(summary.get("num_sequences_added") or 0)
if summary.get("warnings") and len(warning_examples) < 5:
warning_examples.append(
{
"n_qubits": summary["n_qubits"],
"target_decimal": summary["target_decimal"],
"warnings": summary["warnings"],
}
)

{
"dynamic_orbit_dd_status_counts": {
key: dict(value) for key, value in dd_summary.items()
},
"dynamic_orbit_sequences_added": dict(sequence_totals),
"warning_examples": warning_examples,
}
{'dynamic_orbit_dd_status_counts': {'40': {'dd_inserted': 20},
'2': {'dd_inserted': 20},
'7': {'dd_inserted': 20},
'15': {'dd_inserted': 20},
'10': {'dd_inserted': 20},
'35': {'dd_inserted': 20},
'3': {'dd_inserted': 20},
'6': {'dd_inserted': 20},
'20': {'dd_inserted': 20},
'9': {'dd_inserted': 20},
'30': {'dd_inserted': 20},
'4': {'dd_inserted': 20},
'5': {'dd_inserted': 20},
'25': {'dd_inserted': 20},
'8': {'dd_inserted': 20}},
'dynamic_orbit_sequences_added': {'40': 31200,
'2': 40,
'7': 840,
'15': 4200,
'10': 1800,
'35': 23800,
'3': 120,
'6': 600,
'20': 7600,
'9': 1440,
'30': 17400,
'4': 240,
'5': 400,
'25': 12000,
'8': 1120},
'warning_examples': [{'n_qubits': 40,
'target_decimal': 853235401719,
'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',
'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},
{'n_qubits': 40,
'target_decimal': 954673909846,
'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',
'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},
{'n_qubits': 40,
'target_decimal': 524641045908,
'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',
'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},
{'n_qubits': 40,
'target_decimal': 185651043478,
'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',
'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},
{'n_qubits': 40,
'target_decimal': 587114273567,
'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',
'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']}]}

Wykreśl krzywe wierności procesu​

Wykres pokazuje punktowe oszacowanie wierności procesu QFT z próbkowania w funkcji liczby qubitów dla trzech strategii. dynamic/raw i dynamic/orbit mają wspólny układ fizyczny dla każdego rozmiaru; unitary/raw używa układu i trasowania wyznaczonego przez transpiler.

W przeciwieństwie do rysunku 2a ten wykres nie pokazuje krzywej unitarnej z DD ani pasm niepewności, a jego surowe krzywe nie są poddane łagodzeniu błędów odczytu. Najlepiej odczytywać go jako porównanie skalowania w stylu rysunku 2a dla tego przepływu pracy Orbit, a nie jako bezpośrednie odtworzenie opublikowanych krzywych.

from datetime import datetime
from zoneinfo import ZoneInfo

finished_local = datetime.now(ZoneInfo("America/Los_Angeles"))
labels = ["dynamic/orbit", "dynamic/raw", "unitary/raw"]
colors = {
"dynamic/orbit": "#26735b",
"dynamic/raw": "#9b1c31",
"unitary/raw": "#6e6e6e",
}
pretty_labels = {
"dynamic/orbit": "Dynamic QFT with Orbit",
"dynamic/raw": "Dynamic QFT",
"unitary/raw": "Unitary QFT",
}

series = []
for label in labels:
values = [process_fidelity[str(n)][label] for n in N_VALUES]
log_values = [value if value > 0.0 else float("nan") for value in values]
series.append((label, values, log_values))

nonzero_values = [
value
for _, _, log_values in series
for value in log_values
if value > 0.0
]
if not nonzero_values:
raise RuntimeError(
"No nonzero process-fidelity values found for log inset"
)
log_floor = min(nonzero_values) / 2

fig, ax = plt.subplots(figsize=(9.8, 5.6))
for label, values, _ in series:
ax.plot(
N_VALUES,
values,
marker="o",
linewidth=2.0,
markersize=5,
color=colors[label],
label=pretty_labels[label],
)

ax.set_xlabel("N qubits")
ax.set_ylabel("Process fidelity")
finished_time_for_title = finished_local
ax.set_title(
f"Dynamic QFT Orbit results on {IBM_BACKEND_NAME}\n"
f"Job finished {finished_time_for_title:%Y-%m-%d %H:%M %Z}"
)
ax.set_xticks(N_VALUES)
ax.set_ylim(bottom=0)
ax.grid(axis="both", alpha=0.25)
ax.legend(loc="upper right")

inset = ax.inset_axes([0.53, 0.31, 0.44, 0.43])
for label, _, log_values in series:
inset.plot(
N_VALUES,
log_values,
marker="o",
linewidth=2.0,
markersize=5,
color=colors[label],
)
inset.set_yscale("log")
inset.set_ylim(bottom=log_floor)
inset.set_xlim(min(N_VALUES), max(N_VALUES))
inset.set_title("Log scale; zeros omitted", fontsize=9)
inset.grid(axis="both", alpha=0.25)
inset.tick_params(axis="both", labelsize=8)
inset.patch.set_alpha(0.96)

fig.tight_layout()
plt.show()

Output of the previous code cell

Odnośniki​

  1. E. Bäumer et al., "Quantum Fourier Transform Using Dynamic Circuits," arXiv:2403.09514; Physical Review Letters 133, 150602 (2024)

  2. Wprowadzenie do Qiskit Functions

  3. Ograniczenia Quantum Compute dla zmiennych stretch

  4. Kody błędów IBM Quantum: 6073

  5. Uruchamianie zadań w trybie wsadowym

Kolejne kroki​

  • Zobacz dokumentację przewodnika Orbit i dokumentację API.

  • Wypróbuj inny backend, alternatywny układ lub eksperymentuj z alternatywną sekwencją dynamicznego odsprzęgania dostępną w Orbit, zmieniając opcję dd_strategy. Pamiętaj, że ze względu na eksperymentalny charakter obwodów dynamicznych musisz uważać na możliwe tryby awarii zadania (zobacz [3] i [4]). Jeśli napotkasz ujemne wartości stretch [3], spróbuj mniejszej (z mniejszą liczbą impulsów) sekwencji DD. Jeśli napotkasz [4], zwiększ NUM_BATCH_PARTITIONS, zmniejsz M lub zmniejsz największe wartości w N_VALUES, z których wszystkie są zdefiniowane w sekcji Konfiguracja na początku strony.