เปรียบเทียบ process fidelity ของ QFT+M ด้วย Orbit ซึ่งเป็น Qiskit Function โดย Quantum Elements
ประมาณการใช้งาน: 2 นาทีบน Heron r3 processor (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น เวลารันจริงของคุณอาจแตกต่างไป) โดยค่าเริ่มต้น tutorial นี้จะส่ง Orbit function job สามงานเข้าไปใน IBM Quantum Compute Service batch mode workload เดียว โดยมี 300 PUB ต่อ job รวมเป็น 900 PUB และ 921,600 shot ทั้งหมด
คำเตือน: Dynamic circuit ในปัจจุบันเป็นฟีเจอร์ทดลอง และมีข้อจำกัดใน Quantum Compute [3] ที่อาจทำให้ job ล้มเหลวได้ ตัวอย่างเช่น error 6073 บ่งชี้ว่า job เกินขีดจำกัดหน่วยความจำของฮาร์ดแวร์ควบคุมแบบคลาสสิก [4] notebook นี้ลดความเสี่ยงนั้นโดยการแบ่งขนาด Circuit ออกเป็นสาม Quantum Compute job ใน batch เดียว [5] การเปรียบเทียบขนาดคงที่แต่ละแบบจะอยู่ใน job เดียวกัน ในขณะที่ขนาดใหญ่และเล็กจะถูกจับคู่กันเพื่อปรับสมดุล workload ของฮาร์ดแวร์ควบคุมแบบคลาสสิกของ job
ผลลัพธ์การเรียนรู้
-
เตรียม product state ที่ใช้โดยตัวประมาณค่า sampled process-fidelity ใน Figure 2a ของ Ref. [1]
-
สร้างการทำงานแบบ unitary และ dynamic ที่เทียบเท่ากันของ quantum Fourier transform ตามด้วยการวัด (QFT+M)
-
เลือกคิวบิตทางกายภาพสำหรับ dynamic circuit โดยใช้ข้อมูลการ calibration และการเชื่อมต่อในปัจจุบัน
-
เปรียบเทียบค่าประมาณ process-fidelity ของ QFT+M แบบ unitary ดั้งเดิม, dynamic ดั้งเดิม, และ dynamic ที่ปรับปรุงด้วย Orbit เมื่อขนาด Circuit เพิ่มขึ้น
-
ใช้ API การทำ transpilation แบบคล่องตัวของ Orbit ด้วย
mode="raw"และtranspilation_mode="validate" -
ส่ง Orbit workload หลายรายการผ่าน batch mode API โดยยังคงการเปรียบเทียบสามกลยุทธ์ในขนาดคงที่แต่ละแบบไว้ใน job เดียว
-
ตรวจสอบ metadata ของ Orbit เพื่อยืนยันว่ามีการใช้ dynamical decoupling (DD) และ measurement-error mitigation (MEM) หรือไม่
ความเป็นมา
Figure 2a ของ Ref. [1] เปรียบเทียบ process fidelity ของ channel QFT+M ในอุดมคติกับการทำงานแบบ unitary และ dynamic ที่มี noise สำหรับ label ใน computational basis ที่สุ่มมา benchmark นี้เตรียม นำการทำงาน QFT+M ที่มี noise มาใช้ และประมาณความน่าจะเป็น ของการได้ผลลัพธ์ในอุดมคติที่สอดคล้องกัน state ของ inverse-QFT เหล่านี้แยกออกจากกันได้และสามารถเตรียมได้อย่างมีประสิทธิภาพด้วย Hadamard gate และการหมุนเฟสแบบเสมือน
สำหรับ label ที่สุ่มมาอย่างอิสระ ตัว notebook นี้ใช้ตัวประมาณค่าที่ไม่เอนเอียงซึ่งได้มาจาก Ref. [1]:
โครงสร้างแบบ dynamic แทนที่ controlled-phase gate ของ unitary QFT+M ด้วยการวัดกลาง Circuit และการหมุนเฟสที่มีเงื่อนไขแบบคลาสสิก [1] ด้วยการเลื่อนการวัดออกไป Circuit ทั้งสองมีการกระจายผลลัพธ์ในอุดมคติเดียวกัน รูปแบบ dynamic ขจัดข้อกำหนดของ gate สองคิวบิตแบบ all-to-all และใช้การวัดกลาง Circuit แบบ พร้อม feedforward แทน โดยไม่มีข้อจำกัดด้านการเชื่อมต่อ การวัดและ feedforward ยังทำให้เกิดช่วงว่างที่นานบนคิวบิตที่ยังไม่ได้ถูกวัด ทำให้ DD มีความสำคัญเป็นพิเศษ
ความสัมพันธ์กับ Figure 2a notebook นี้ทำตามโพรโทคอล sampled process-fidelity ของบทความ แต่เป็นการดัดแปลง tutorial ที่เน้น Orbit มากกว่าจะเป็นการทำซ้ำ ตัวอย่างเช่น ในขณะที่ Figure 2a ใช้ ibm_kyiv ที่ 2000 shot เราใช้อุปกรณ์รุ่นใหม่ ibm_aachen ที่มีจำนวน shot น้อยกว่าคือ 1024 เพื่อประหยัดเวลาใช้งาน QPU
ตัวอย่างผลลัพธ์
กราฟแบบ static ด้านล่างแสดงเส้นโค้ง process-fidelity เฉลี่ยจากสาม development job ที่รันติดต่อกันบน ibm_aachen ด้วยกระบวนการที่อธิบายไว้ด้านล่าง ดังที่แสดงในที่นี้ Orbit สามารถเพิ่มคุณภาพของ dynamic circuit ได้อย่างมาก; dynamic QFT มีคุณภาพเทียบเท่ากับ benchmark ที่เผยแพร่ และแสดงให้เห็นการปรับปรุงเมื่อเทียบกับ unitary QFT แบบมาตรฐาน ดังที่เราจะเห็น ผลลัพธ์เหล่านี้มาจากการเลือกคิวบิตที่ดีแบบอัตโนมัติ, การแทรก dynamic decoupling แบบอัตโนมัติ (ไม่ได้ปรับแต่งด้วยมือสำหรับปัญหานี้), และการลด measurement error อย่าลืมเปรียบเทียบผลลัพธ์เหล่านี้กับผลลัพธ์ของคุณในตอนท้ายเพื่อความสนุก โดยเฉพาะถ้าคุณเลือก backend อื่น
หมายเหตุ: ผลลัพธ์เหล่านี้เป็นภาพตัวอย่างของการรันที่สำเร็จก่อนหน้านี้ด้วย Orbit ไม่ใช่การรับประกันประสิทธิภาพ ผลลัพธ์ด้านล่างควรมีลักษณะคล้ายกันในเชิงคุณภาพ แต่รายละเอียดขึ้นอยู่กับอุปกรณ์ที่เลือกและคุณสมบัติของมัน โดยเฉพาะ error จากการวัดและการรอ (idling) ในขณะรัน
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-catalog qiskit-ibm-runtime
ข้อกำหนด
ติดตั้งแพ็กเกจต่อไปนี้เวอร์ชันล่าสุดก่อนรัน tutorial นี้:
-
numpy -
matplotlib -
qiskit -
qiskit-ibm-runtime -
qiskit-ibm-catalog
pip install qiskit qiskit-ibm-runtime qiskit-ibm-catalog numpy matplotlib
การตั้งค่า
ยืนยันตัวตนด้วย IBM Quantum® Platform โหลด ibm_aachen และโหลด Quantum Elements Orbit จาก Qiskit Functions Catalog การ sweep ค่าเริ่มต้นจะประเมิน 15 ขนาด Circuit, 20 bitstring ที่สุ่มมาต่อขนาด, และสามกลยุทธ์ NUM_BATCH_JOBS=3 แบ่งขนาดออกเป็นสาม job ใน batch เดียว ลด N_VALUES หรือ M หรือเพิ่ม NUM_BATCH_JOBS หาก dynamic-circuit job แต่ละงานยังคงถึงขีดจำกัดหน่วยความจำของฮาร์ดแวร์ควบคุมแบบคลาสสิกของ backend ลด SHOTS เมื่อเป้าหมายคือการลดการใช้งานการรันมากกว่าจำนวนหรือความซับซ้อนของ Circuit
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 Batch, QiskitRuntimeService
IBM_BACKEND_NAME = "ibm_aachen"
N_VALUES = [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40]
M = 20
SHOTS = 1024
RNG_SEED = 12345
OPTIMIZATION_LEVEL = 0
NUM_BATCH_JOBS = 3
STRATEGY_LABELS = ("unitary/raw", "dynamic/raw", "dynamic/orbit")
def balanced_n_groups(
n_values: list[int], num_jobs: int = 3
) -> list[list[int]]:
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_JOBS 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_JOBS)
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}."
)
{
"backend": backend.name,
"num_qubits": backend.num_qubits,
"n_values": N_VALUES,
"m": M,
"shots": SHOTS,
"num_function_jobs": NUM_BATCH_JOBS,
"n_groups": N_GROUPS,
"pubs_per_job": [
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,
}
qiskit_runtime_service._discover_account:WARNING:2026-07-21 15:57:39,310: Loading account with the given token. A saved account will not be used.
{'backend': 'ibm_aachen',
'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': 3,
'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]],
'pubs_per_job': [300, 300, 300],
'total_pubs': 900,
'total_shots': 921600}
สร้าง Circuit ของ QFT+M
สำหรับจำนวนเต็ม ที่สุ่มมาแต่ละตัว bit_inv_qft เตรียม product state ด้วย Hadamard ตามด้วยการหมุนเฟส จากนั้น notebook จะต่อท้ายด้วย unitary QFT มาตรฐาน หรือ semiclassical dynamic QFT+M ที่เทียบเท่ากัน
การทำงานทั้งสองแบบละเว้น final swap network ลำดับการแสดงผลของ classical bit ใน Qiskit จึงทำให้ bitstring ที่คาดว่าจะวัดได้เป็นลำดับย้อนกลับของการแทนค่าไบนารีที่เติมศูนย์ของ ซึ่งเข้ารหัสโดย format(x, f"0{n}b")[::-1]
def bit_inv_qft(circuit: QuantumCircuit, x: int, conv: str = "LSB") -> None:
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:
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:
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:
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:
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 format(int(x), f"0{n_qubits}b")[::-1]
def process_fidelity_from_success_probabilities(
success_probabilities: list[float],
) -> float:
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))
)
เลือกคิวบิตทางกายภาพสำหรับ dynamic circuit
การทำงานแบบ dynamic ไม่ต้องการ gate สองคิวบิต ดังนั้นคิวบิตทางกายภาพของมันจึงไม่จำเป็นต้องรวมกันเป็น subgraph ที่เชื่อมต่อกัน สำหรับแต่ละขนาด Circuit ตัวคัดเลือกจะจัดอันดับคิวบิตของ backend ปัจจุบันโดยใช้คะแนนที่ถ่วงน้ำหนัก 80% ต่อ readout error ที่ต่ำกว่า และ 10% ต่อ และ ที่สูงกว่าอย่างละส่วน โดยจะเลือกคิวบิตที่มีคะแนนสูงและไม่มีการเชื่อมต่อโดยตรงระหว่างกันก่อนเมื่อเป็นไปได้ ซึ่งสามารถลดการสัมผัส crosstalk ระหว่างเพื่อนบ้านใกล้เคียง แล้วจึงเติมตำแหน่งที่เหลือตามคะแนน
รูปแบบ dynamic/raw และ dynamic/orbit ใช้ layout ที่เลือกเหมือนกันทุกประการสำหรับขนาดที่กำหนด ทำให้การเปรียบเทียบระหว่างกันควบคุมด้วย layout ในขณะที่ Circuit unitary/raw ถูก map และ route โดย transpiler แทน เนื่องจากต้องการการเชื่อมต่อแบบสองคิวบิต การเลือกที่ใช้การ calibration แบบ runtime นี้เจาะจงสำหรับ tutorial นี้เท่านั้น ไม่ใช่ layout ibm_kyiv แบบ 40 คิวบิตคงที่ที่ใช้ในการทดลอง Figure 2a ของบทความ
def value_from_property(raw):
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:
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:
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]]:
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]]:
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:
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]:
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]:
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': 0, 't1': 0.0002514242577986401, 't2': 0.00037559012475638467, 'measurement_error': 0.0028076171875, 'score': 1.0}, {'qubit': 20, 't1': 0.0002526252407383437, 't2': 0.00038493543771861573, 'measurement_error': 0.00390625, 'score': 1.0}, {'qubit': 25, 't1': 0.0002712841332005567, 't2': 0.00025793268824583597, 'measurement_error': 0.0040283203125, 'score': 1.0}]
Worst 3 qubits (according to our scoring):
[{'qubit': 146, 't1': 7.619772882181663e-05, 't2': 0.00014166983578724752, 'measurement_error': 0.0802001953125, 'score': 0.05893378230453208}, {'qubit': 51, 't1': 0.00014200221819602618, 't2': 1.930870507157441e-06, 'measurement_error': 0.054443359375, 'score': 0.04600110909801309}, {'qubit': 35, 't1': 7.116087485472031e-05, 't2': 9.463696242928626e-05, 'measurement_error': 0.14501953125, 'score': 0.03289891864200328}]
เตรียม PUB สำหรับ benchmark
สำหรับคู่ (N, x) ทุกคู่ notebook จะ transpile Circuit แบบตรรกะก่อน และสร้าง Sampler PUB หนึ่งรายการสำหรับแต่ละกลยุทธ์:
-
unitary/raw: unitary QFT+M บน layout ที่ transpiler เลือก พร้อม routing ตามความจำเป็น และไม่มี DD หรือ MEM จาก Orbit -
dynamic/raw: dynamic QFT+M บนคิวบิตทางกายภาพที่เลือกจากการ calibration โดยไม่มี DD หรือ MEM จาก Orbit -
dynamic/orbit: dynamic circuit ที่ transpile เหมือนกันบนคิวบิตทางกายภาพเดียวกัน โดยเปิดใช้งาน DD และ MEM ของ Orbit
PUB ที่ปรับปรุงด้วย Orbit ใช้ transpilation_mode="validate" เนื่องจากมี mapping ถูกเลือกไว้แล้ว Orbit ตรวจสอบความถูกต้องของ Circuit ทางกายภาพที่ให้มาแทนที่จะ remap มัน แล้วจึงใช้ pipeline DD และ MEM ของมัน เนื่องจากมีเพียงเส้นโค้ง dynamic ที่ปรับปรุงแล้วเท่านั้นที่ขอ MEM จึงไม่ควรตีความว่าเป็นการเปรียบเทียบ DD-กับ-ไม่มี-DD แบบแยกเดี่ยว
PUB, ตัวเลือกต่อ PUB, และบันทึกผลลัพธ์จะถูกเก็บตามดัชนี batch-job สำหรับ คงที่แต่ละค่า PUB ของ unitary/raw, dynamic/raw, และ dynamic/orbit จะถูกเก็บไว้ด้วยกันใน job เดียวกัน ตัวช่วยจัดกลุ่มจะจับคู่ขนาด Circuit ใหญ่และเล็ก สลับลำดับของมัน และปรับสมดุลผลรวมของ ระหว่างสาม job เพื่อเป็นตัวแทนอย่างง่ายของ workload ของฮาร์ดแวร์ควบคุมแบบคลาสสิก
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_by_job = [[] for _ in N_GROUPS]
pub_options_by_job = [[] for _ in N_GROUPS]
pub_records_by_job = [[] for _ in N_GROUPS]
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 job_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_by_job[job_index].append((circuit, None, SHOTS))
pub_options_by_job[job_index].append(options)
pub_records_by_job[job_index].append(
{
"job_index": job_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(),
}
)
{
"num_function_jobs": 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_job": {
job_index: len(pubs) for job_index, pubs in enumerate(pubs_by_job)
},
"expected_executions_per_job": {
job_index: len(pubs) * SHOTS
for job_index, pubs in enumerate(pubs_by_job)
},
"first_pub_record_by_job": {
job_index: records[0]
for job_index, records in enumerate(pub_records_by_job)
},
"largest_dynamic_qubit_set": layout_summary[str(max(N_VALUES))][
"dynamic_qubits"
],
}
{'num_function_jobs': 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_job': {0: 300, 1: 300, 2: 300},
'expected_executions_per_job': {0: 307200, 1: 307200, 2: 307200},
'first_pub_record_by_job': {0: {'job_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},
1: {'job_index': 1,
'n_qubits': 35,
'target_decimal': 26888951661,
'target_bitstring': '10110110111010110010110101000010011',
'label': 'unitary/raw',
'pub_options': {'mode': 'raw'},
'dynamic_qubits': None,
'transpiled_depth': 3614,
'transpiled_size': 21204},
2: {'job_index': 2,
'n_qubits': 30,
'target_decimal': 620442965,
'target_bitstring': '101010101010110011011111001001',
'label': 'unitary/raw',
'pub_options': {'mode': 'raw'},
'dynamic_qubits': None,
'transpiled_depth': 2835,
'transpiled_size': 15078}},
'largest_dynamic_qubit_set': [0,
20,
25,
27,
33,
59,
74,
80,
95,
144,
151,
155,
79,
90,
60,
68,
114,
107,
13,
126,
133,
103,
3,
87,
53,
41,
130,
5,
98,
135,
153,
15,
116,
45,
7,
48,
136,
11,
147,
77]}
รัน benchmark
สร้าง batch หนึ่งชุด จากนั้นส่ง Orbit function job สามงานเข้าไปในนั้น
job ถูกแบ่งตามกลุ่มจำนวนคิวบิต เพื่อให้สามารถเปรียบเทียบทั้งสามกลยุทธ์สำหรับ คงที่ได้ กล่าวคือ PUB ทั้ง 60 รายการสำหรับ คงที่ — อินพุตที่สุ่มมา 20 รายการคูณด้วยสามกลยุทธ์ — จึงรันใน job เดียวกัน และสามารถเปรียบเทียบกันได้อย่างเป็นธรรมที่สุดเท่าที่จะทำได้ (มิฉะนั้น หากรันใน job ต่างกัน อุปกรณ์อาจ drift ไปในขณะที่อยู่ในคิว) กลุ่มเริ่มต้นรวม Circuit ขนาดใหญ่และเล็กเข้าด้วยกัน และมี 300 PUB ต่อกลุ่ม ซึ่งลดโอกาสที่ job หนึ่งจะสะสม dynamic program ที่ใหญ่ที่สุดทั้งหมด ในขณะที่ยังคงรักษาการเปรียบเทียบภายใน job ไว้
runtime_batch = Batch(backend=backend)
jobs = []
try:
for job_index, pubs in enumerate(pubs_by_job):
jobs.append(
quantum_elements_orbit.run(
primitive="sampler",
pubs=pubs,
backend_name=backend.name,
options={
"pub_options": pub_options_by_job[job_index],
"save_backend_info": True,
},
)
)
except Exception:
runtime_batch.close()
raise
{
"runtime_batch_id": runtime_batch.session_id,
"jobs": {
job_index: {
"backend": backend.name,
"function_job_id": job.job_id,
"status": job.status(),
"n_values": N_GROUPS[job_index],
"num_pubs": len(pubs_by_job[job_index]),
}
for job_index, job in enumerate(jobs)
},
}
{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',
'jobs': {0: {'backend': 'ibm_aachen',
'function_job_id': '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',
'status': 'QUEUED',
'n_values': [40, 2, 7, 15, 10],
'num_pubs': 300},
1: {'backend': 'ibm_aachen',
'function_job_id': '4f046fd4-80e4-460b-87c7-e7252691f764',
'status': 'QUEUED',
'n_values': [35, 3, 6, 20, 9],
'num_pubs': 300},
2: {'backend': 'ibm_aachen',
'function_job_id': '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be',
'status': 'QUEUED',
'n_values': [30, 4, 5, 25, 8],
'num_pubs': 300}}}
ดึงผลลัพธ์และคำนวณ process fidelity
ดึงและตรวจสอบผลลัพธ์ของแต่ละกลุ่มคิวบิตอย่างเป็นอิสระ จากนั้นรวมสตรีมของ job ทั้งสามผ่านบันทึกที่จัดดัชนีตาม job batch จะถูกเก็บไว้แบบเปิดในขณะที่ผลลัพธ์ function ทั้งหมดถูกร้องขอ และปิดใน block finally หลังจากที่พยายามรัน job ทุกงานแล้ว สำหรับแต่ละ PUB คือความน่าจะเป็นที่กำหนดให้กับ bitstring ที่คาดว่าจะได้ extract_counts อ่าน count ที่ส่งกลับมาให้ผู้เรียก; สำหรับ dynamic/orbit นี่คือ count ที่ปรับด้วย MEM แล้วเมื่อการลด error สำเร็จ extract_raw_counts ยังกู้คืน count ที่ไม่ได้ลด error ที่บันทึกไว้ใน metadata ของ Orbit ด้วย โค้ดจัดกลุ่มค่า 20 ค่าสำหรับแต่ละคู่ (N, label) และใช้ตัวประมาณค่าที่นำเสนอไว้ข้างต้น
ดังนั้น dictionary process_fidelity ที่พล็อตจึงใช้ count ดิบสำหรับ unitary/raw และ dynamic/raw แต่ใช้ count ที่ปรับด้วย MEM สำหรับ dynamic/orbit dictionary raw_process_fidelity แบบขนานจะเก็บการคำนวณที่ไม่ได้ลด error ไว้สำหรับทุกกลยุทธ์ และมีประโยชน์เมื่อต้องการแยกผลของ MEM ออกจากส่วนที่เหลือของ pipeline Orbit MEM แก้ไข output histogram ที่ส่งกลับมา; มันไม่สามารถเปลี่ยนผลการวัดกลาง Circuit ย้อนหลังที่ถูกใช้ไปแล้วโดย real-time feedforward ได้
def extract_counts(pub_result) -> dict[str, int]:
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]:
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:
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)
results_by_job = {}
job_failures = []
try:
for job_index, job in enumerate(jobs):
try:
job_result = job.result()
except Exception as exc:
job_logs = getattr(job, "logs", lambda: "")()
if job_logs:
print(f"Logs for job {job_index} ({job.job_id}):\n{job_logs}")
job_failures.append(
f"job {job_index} ({job.job_id}) failed: {type(exc).__name__}: {exc}"
)
continue
expected_results = len(pub_records_by_job[job_index])
if len(job_result) != expected_results:
job_failures.append(
f"job {job_index} ({job.job_id}) returned {len(job_result)} PUB results; "
f"expected {expected_results}"
)
continue
results_by_job[job_index] = job_result
finally:
runtime_batch.close()
if job_failures:
raise RuntimeError(
"One or more batched Orbit jobs failed:\n" + "\n".join(job_failures)
)
grouped_success = defaultdict(list)
grouped_raw_success = defaultdict(list)
pub_summaries = []
for job_index, job_result in sorted(results_by_job.items()):
records = pub_records_by_job[job_index]
for record, pub_result in zip(records, job_result, strict=True):
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": jobs[job_index].job_id,
"runtime_batch_id": runtime_batch.session_id,
"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)
{
"runtime_batch_id": runtime_batch.session_id,
"function_job_ids": {
job_index: job.job_id for job_index, job in enumerate(jobs)
},
"n_groups": {
job_index: group for job_index, group in enumerate(N_GROUPS)
},
"process_fidelity": process_fidelity,
"mean_success_probability": mean_success_probability,
}
{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',
'function_job_ids': {0: '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',
1: '4f046fd4-80e4-460b-87c7-e7252691f764',
2: '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be'},
'n_groups': {0: [40, 2, 7, 15, 10],
1: [35, 3, 6, 20, 9],
2: [30, 4, 5, 25, 8]},
'process_fidelity': {'2': {'dynamic/orbit': 0.9870551835473073,
'dynamic/raw': 0.9912537998030566,
'unitary/raw': 0.9884650767434809},
'3': {'dynamic/orbit': 0.9662998634131841,
'dynamic/raw': 0.9699631603283018,
'unitary/raw': 0.9388637172865901},
'4': {'dynamic/orbit': 0.9271266520750502,
'dynamic/raw': 0.7334377020091254,
'unitary/raw': 0.9010122207121433},
'5': {'dynamic/orbit': 0.8883501513887149,
'dynamic/raw': 0.6577660260669806,
'unitary/raw': 0.7806443417987445},
'6': {'dynamic/orbit': 0.8524225652033044,
'dynamic/raw': 0.4444025126308521,
'unitary/raw': 0.7167426842521228},
'7': {'dynamic/orbit': 0.832962085697061,
'dynamic/raw': 0.2253787798698553,
'unitary/raw': 0.5746335601063436},
'8': {'dynamic/orbit': 0.7881895956180588,
'dynamic/raw': 0.16909516699831612,
'unitary/raw': 0.5408263851227074},
'9': {'dynamic/orbit': 0.7422635627368794,
'dynamic/raw': 0.0242474245097341,
'unitary/raw': 0.4855953298367578},
'10': {'dynamic/orbit': 0.7002274273149545,
'dynamic/raw': 0.033718865729016285,
'unitary/raw': 0.3607634828181049},
'15': {'dynamic/orbit': 0.4694995355699914,
'dynamic/raw': 7.70970394736842e-05,
'unitary/raw': 0.054582117352985286},
'20': {'dynamic/orbit': 0.24118032284867608,
'dynamic/raw': 4.235164736271502e-22,
'unitary/raw': 0.0},
'25': {'dynamic/orbit': 0.027122712989729438,
'dynamic/raw': 0.0,
'unitary/raw': 0.0},
'30': {'dynamic/orbit': 0.0003581886014704875,
'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.987060546875,
'dynamic/raw': 0.991259765625,
'unitary/raw': 0.9884765625},
'3': {'dynamic/orbit': 0.96630859375,
'dynamic/raw': 0.969970703125,
'unitary/raw': 0.939013671875},
'4': {'dynamic/orbit': 0.9271484375,
'dynamic/raw': 0.7337890625,
'unitary/raw': 0.901318359375},
'5': {'dynamic/orbit': 0.88837890625,
'dynamic/raw': 0.657861328125,
'unitary/raw': 0.78115234375},
'6': {'dynamic/orbit': 0.85244140625,
'dynamic/raw': 0.44453125,
'unitary/raw': 0.71728515625},
'7': {'dynamic/orbit': 0.8330078125,
'dynamic/raw': 0.22568359375,
'unitary/raw': 0.575390625},
'8': {'dynamic/orbit': 0.788232421875,
'dynamic/raw': 0.169189453125,
'unitary/raw': 0.541796875},
'9': {'dynamic/orbit': 0.742333984375,
'dynamic/raw': 0.0244140625,
'unitary/raw': 0.487353515625},
'10': {'dynamic/orbit': 0.70029296875,
'dynamic/raw': 0.033935546875,
'unitary/raw': 0.363037109375},
'15': {'dynamic/orbit': 0.4697265625,
'dynamic/raw': 0.00029296875,
'unitary/raw': 0.055615234375},
'20': {'dynamic/orbit': 0.241357421875,
'dynamic/raw': 4.8828125e-05,
'unitary/raw': 0.0},
'25': {'dynamic/orbit': 0.041015625, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},
'30': {'dynamic/orbit': 0.0013671875,
'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}}}
ตรวจสอบ metadata DD ของ Orbit
สรุปด้านล่างตรวจสอบ metadata ของ PUB dynamic/orbit แทนที่จะสันนิษฐานว่ามีการแทรก DD ที่ร้องขอแล้ว ตรวจสอบสถานะ, ลำดับ DD แบบ dynamic ที่รายงาน, คำเตือน, และจำนวนช่องว่างที่ถูกเติมและลำดับที่ถูกเพิ่ม การแทรกที่สำเร็จควรให้ค่าที่ไม่เป็นศูนย์สำหรับ PUB อย่างน้อยบางส่วน แต่ค่าที่แน่นอนขึ้นอยู่กับ Circuit ที่ถูกจัดตารางเวลาแล้ว ข้อจำกัดด้านเวลาของ backend และขนาด Circuit metadata นี้อธิบายลำดับที่ Orbit นำไปใช้; ไม่ควรระบุว่าเป็นโพรโทคอล FC-DD ของบทความ เว้นแต่รายงานจะระบุความเท่าเทียมกันนั้นอย่างชัดเจน
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.']}]}
พล็อตเส้นโค้ง process-fidelity
กราฟแสดงค่าประมาณจุดของ process-fidelity ของ QFT+M ที่ sample แล้ว เทียบกับจำนวนคิวบิตสำหรับสามกลยุทธ์ dynamic/raw และ dynamic/orbit ใช้ layout ทางกายภาพร่วมกันในแต่ละขนาด; unitary/raw ใช้ layout และ routing ของ transpiler
ต่างจาก Figure 2a กราฟนี้ไม่แสดงเส้นโค้ง unitary-with-DD หรือแถบความไม่แน่นอน และเส้นโค้งดิบของมันไม่ได้ถูกลด readout error ควรอ่านกราฟนี้ในฐานะการเปรียบเทียบการสเกลแบบ Figure 2a ของเวิร์กโฟลว์ Orbit นี้มากกว่า ไม่ใช่การทำซ้ำโดยตรงของเส้นโค้งที่เผยแพร่
from datetime import datetime
from zoneinfo import ZoneInfo
closed_at = runtime_batch.details()["closed_at"] # "2026-07-22T00:08:54.89Z"
closed_dt = datetime.fromisoformat(closed_at.replace("Z", "+00:00"))
closed_local = closed_dt.astimezone(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+M with Orbit",
"dynamic/raw": "Dynamic QFT+M",
"unitary/raw": "Unitary QFT+M",
}
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 = globals().get("finished_local", closed_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()

เอกสารอ้างอิง
ขั้นตอนถัดไป
-
ดูเอกสาร คู่มือ Orbit และ เอกสารอ้างอิง API
-
ลอง backend อื่น, layout ทางเลือก, หรือทดลองลำดับ dynamical decoupling แบบ orbit-enabled ทางเลือกโดยการปรับตัวเลือก
dd_strategyโปรดจำไว้ว่าเนื่องจากลักษณะทดลองของ dynamic circuit คุณต้องระมัดระวังโหมดความล้มเหลวของ job ที่อาจเกิดขึ้น (ดู [3] และ [4]) หากคุณพบค่า stretch ที่เป็นลบ [3] ให้ลองใช้ลำดับ DD ที่เล็กกว่า (พัลส์น้อยกว่า) หากคุณพบ [4] ให้เพิ่มNUM_BATCH_JOBS, ลดM, หรือลดค่าที่ใหญ่ที่สุดในN_VALUES