เปรียบเทียบ process fidelity ของ QFT ด้วย Orbit ซึ่งเป็น Qiskit Function โดย Quantum Elements
ประมาณการใช้งาน: 2 นาทีบน Heron r3 processor (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น เวลารันจริงของคุณอาจแตกต่างไป) โดยค่าเริ่มต้น tutorial นี้จะส่ง Orbit Function job หนึ่งงานโดยใช้ IBM Quantum Compute Service Runtime Batch ฝั่งเซิร์ฟเวอร์ ซึ่งมี child job สามงาน งานละ 300 PUB และรวม 921,600 shot ทั้งหมด
คำเตือน: Dynamic circuit ในปัจจุบันเป็นฟีเจอร์ทดลอง และมีข้อจำกัดใน Quantum Compute [3] ที่อาจทำให้ job ล้มเหลวได้ ตัวอย่างเช่น error 6073 บ่งชี้ว่า job เกินขีดจำกัดหน่วยความจำของฮาร์ดแวร์ควบคุมแบบคลาสสิก [4] notebook นี้ลดความเสี่ยงนั้นโดยการแบ่งขนาด Circuit ออกเป็นสาม Runtime Batch child job [5] การเปรียบเทียบขนาดคงที่แต่ละแบบจะอยู่ใน child job เดียว ในขณะที่ขนาดใหญ่และเล็กจะถูกจับคู่กันเพื่อปรับสมดุล workload ของฮาร์ดแวร์ควบคุมแบบคลาสสิกของ job
ผลลัพธ์การเรียนรู้
-
เตรียม product state ที่ใช้โดยตัวประมาณค่า sampled process-fidelity ใน Figure 2a ของ Ref. [1]
-
สร้างการทำงานแบบ unitary และ dynamic ที่เทียบเท่ากันของ quantum Fourier transform ตามด้วยการวัด
-
เลือกคิวบิตทางกายภาพสำหรับ dynamic circuit โดยใช้ข้อมูลการ calibration และการเชื่อมต่อในปัจจุบัน
-
เปรียบเทียบค่าประมาณ process-fidelity ของ QFT แบบ unitary ดั้งเดิม, dynamic ดั้งเดิม, และ dynamic ที่ปรับปรุงด้วย Orbit เมื่อขนาด Circuit เพิ่มขึ้น
-
ส่ง Orbit workload หนึ่งรายการผ่าน batch mode API โดยยังคงการเปรียบเทียบสามกลยุทธ์ในขนาดคงที่แต่ละแบบไว้ใน child 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]:
การสร้างแบบไดนามิกแทนที่ controlled-phase gate ของ QFT+M แบบ unitary ด้วยการวัดกลาง Circuit และการหมุนเฟสที่มีเงื่อนไขแบบคลาสสิก [1] ด้วย deferred measurement ทำให้ทั้งสอง Circuit มีการแจกแจงเอาต์พุตในอุดมคติเหมือนกัน การทำแบบ unitary ต้องใช้ two-qubit gate แบบ all-to-all และเนื่องจากข้อจำกัดด้านการเชื่อมต่อแบบท้องถิ่น จึงเกิดโอเวอร์เฮด SWAP gate ขนาด ในทางตรงกันข้าม Circuit แบบไดนามิกใช้เพียง single qubit gate และการวัดกลาง Circuit จำนวน ครั้ง ซึ่งทำให้เกิดช่วง idle ยาวตรงกลาง หากไม่พิจารณา DD เลย การแข่งขันพื้นฐานจึงอยู่ระหว่าง unitary gate ที่มีสัญญาณรบกวนจำนวน กับการวัดกลาง Circuit ที่มีสัญญาณรบกวนจำนวน หากช่วง idle ระหว่างการวัดกลาง Circuit เหล่านี้มีข้อผิดพลาดต่ำพอ Circuit แบบไดนามิกจะทำงานได้ดีกว่าการทำแบบ unitary และเราจะแสดงด้านล่างว่าสิ่งนี้ทำได้โดยใช้ Orbit
ความสัมพันธ์กับ 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 — 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")
ข้อกำหนด
ติดตั้งแพ็กเกจต่อไปนี้เวอร์ชันล่าสุดก่อนรัน 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
N_VALUES คือรายการขนาดของ Circuit โดยแต่ละค่าคือจำนวนคิวบิตเชิงตรรกะใน QFT Circuit หนึ่งตัว M คือจำนวน basis label ที่สุ่มอย่างอิสระซึ่งประเมินในแต่ละขนาด ในตัวประมาณ fidelity ค่านี้คือ ในสมการข้างต้น สำหรับทุกคู่ (N, x) notebook จะส่ง PUB หนึ่งรายการสำหรับแต่ละกลยุทธ์ทั้งสามแบบ ดังนั้นแต่ละขนาดคงที่จึงมี 3 * M PUB และ 3 * M * SHOTS shot ของ Circuit
ดังนั้นการกวาดตามค่าเริ่มต้นจะประเมิน Circuit 15 ขนาด, bitstring ที่สุ่ม 20 ตัวต่อขนาด และสามกลยุทธ์ NUM_BATCH_PARTITIONS=3 กำหนดพาร์ทิชัน Runtime Batch แบบเรียงลำดับสามพาร์ทิชัน และ BATCH_MAX_PUBS_PER_JOB ถูกส่งผ่าน orbit.run(...) เป็นการตั้งค่า JSON N_VALUES ที่ใหญ่จะสร้าง Circuit แบบไดนามิกที่ซับซ้อนกว่า ส่วน M ที่ใหญ่ขึ้นจะใส่ PUB ในแต่ละ child job มากขึ้น ทั้งสองอย่างอาจเพิ่มแรงกดดันต่อหน่วยความจำควบคุมแบบคลาสสิกของ Backend หาก child job ของ dynamic circuit ใดถึงขีดจำกัดนั้น ให้ลด N_VALUES หรือ M หรือเพิ่ม NUM_BATCH_PARTITIONS ลด SHOTS เมื่อเป้าหมายคือลดการใช้งานการรัน ไม่ใช่ลดจำนวนหรือความซับซ้อนของ Circuit
OPTIMIZATION_LEVEL=0 บอกให้ Transpiler ของ Qiskit ทำการแปลและ routing ที่จำเป็นโดยใช้การปรับให้เหมาะสมน้อยที่สุด เราใช้ค่านี้เพื่อรักษา layout ของ dynamic circuit ที่เลือกไว้อย่างชัดเจน และทำให้การเปรียบเทียบแบบดิบตีความได้ง่ายขึ้น ระดับ 1–3 ก็เป็นการทดลองที่ใช้ได้ แต่อาจเขียนใหม่หรือทำให้ Circuit ง่ายลง และอาจเปลี่ยนตำแหน่งที่เลือกไว้ ซึ่งคาดว่าให้ประโยชน์น้อยในกรณีนี้ เพราะ dynamic circuit ไม่มี two-qubit gate หรือข้อจำกัดด้านการเชื่อมต่อ หากคุณเปลี่ยนระดับ ให้รัน benchmark ใหม่และถือว่าผลลัพธ์เป็นการเปรียบเทียบแยกต่างหาก
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}
สร้าง Circuit ของ QFT
สำหรับจำนวนเต็ม ที่สุ่มมาแต่ละตัว bit_inv_qft เตรียม product state ด้วย Hadamard ตามด้วยการหมุนเฟส จากนั้น notebook จะต่อท้ายด้วย unitary QFT มาตรฐาน หรือ semiclassical dynamic QFT ที่เทียบเท่ากัน
การทำงานทั้งสองแบบละเว้น 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:
"""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))
)
เลือกคิวบิตทางกายภาพสำหรับ 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):
"""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}]
เตรียม PUB สำหรับ benchmark
สำหรับคู่ (N, x) ทุกคู่ notebook จะ transpile Circuit แบบตรรกะก่อน และสร้าง Sampler PUB หนึ่งรายการสำหรับแต่ละกลยุทธ์:
-
unitary/raw: unitary QFT บน layout ที่ transpiler เลือก พร้อม routing ตามความจำเป็น และไม่มี DD หรือ MEM จาก Orbit -
dynamic/raw: dynamic QFT บนคิวบิตทางกายภาพที่เลือกจากการ 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 และบันทึกผลลัพธ์จะถูกทำให้เรียบตามลำดับเดียวกัน สำหรับ คงที่แต่ละค่า PUB ของ unitary/raw, dynamic/raw และ dynamic/orbit จะถูกเก็บไว้ด้วยกันในพาร์ทิชัน Runtime Batch เดียว ตัวช่วยจัดกลุ่มจะจับคู่ขนาด Circuit ใหญ่และเล็ก สลับลำดับของมัน และปรับสมดุลผลรวมของ ระหว่างสามพาร์ทิชัน เพื่อเป็นตัวแทนอย่างง่ายของ 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 = []
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]}
รัน benchmark
ส่งการตั้งค่า Batch ที่แปลงเป็น JSON ได้ผ่าน orbit.run(...) Orbit จะสร้าง Runtime Batch ที่ยืนยันตัวตนแล้วภายใน Function ส่งพาร์ทิชัน PUB แบบเรียงลำดับ ปิด Batch หลังส่งเสร็จ และส่งผลลัพธ์ที่รวมแล้วหนึ่งชุดกลับไปยังผู้เรียก Batch ในเครื่องที่ผู้เรียกสร้างขึ้นไม่สามารถควบคุม Qiskit Function job แบบอะซิงโครนัสได้
รายการ PUB แบบเรียงลำดับถูกแบ่งพาร์ทิชันตามกลุ่มจำนวน qubit เพื่อให้เปรียบเทียบทั้งสามกลยุทธ์สำหรับ คงที่ได้อย่างเป็นธรรม PUB ทั้ง 60 รายการสำหรับ คงที่ — อินพุตที่สุ่มมา 20 รายการคูณด้วยสามกลยุทธ์ — จะรันใน Runtime Batch child job เดียวกัน กลุ่มเริ่มต้นรวม Circuit ขนาดใหญ่และเล็กเข้าด้วยกัน และมี 300 PUB ต่อกลุ่ม ซึ่งลดโอกาสที่ child job หนึ่งจะสะสม dynamic program ที่ใหญ่ที่สุดทั้งหมด ในขณะที่ยังคงรักษาการเปรียบเทียบภายใน job ไว้
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]]}
ดึงผลลัพธ์และคำนวณ process fidelity
ดึงผลลัพธ์ที่รวมแล้วหนึ่งชุดซึ่ง Orbit Function ส่งกลับมา Orbit บันทึก Runtime Batch ID, ID ของ Runtime job ลูก และช่วงของ PUB ไว้ใน metadata ของผลลัพธ์ ลำดับของ PUB จะถูกรักษาไว้ตลอดทุก child 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]:
"""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}}}
ตรวจสอบ 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 ที่ 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
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()

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