ข้ามไปยังเนื้อหาหลัก

อินพุตและเอาต์พุตของ Primitives

โมเดลการประมวลผลใหม่ ขณะนี้อยู่ใน beta release

beta release ของโมเดลการประมวลผลใหม่พร้อมให้ใช้งานแล้ว โมเดล directed execution ให้ความยืดหยุ่นมากขึ้นในการปรับแต่ง error mitigation workflow ดูคู่มือ Directed execution model สำหรับข้อมูลเพิ่มเติม

Package versions

The code on this page was developed using the following requirements. We recommend using these versions or newer.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

หน้านี้ให้ภาพรวมของอินพุตและเอาต์พุตของ Qiskit Runtime primitives ที่ประมวลผล workloads บน IBM Quantum® compute resources Primitives เหล่านี้ช่วยให้สามารถกำหนด vectorized workloads ได้อย่างมีประสิทธิภาพโดยใช้โครงสร้างข้อมูลที่เรียกว่า Primitive Unified Bloc (PUB) PUBs เหล่านี้คือหน่วยงานพื้นฐานที่ QPU ต้องใช้ในการประมวลผล workloads และใช้เป็นอินพุตของเมธอด run() สำหรับ Sampler และ Estimator primitives ซึ่งประมวลผล workload ที่กำหนดเป็น job จากนั้น หลังจาก job เสร็จสิ้น ผลลัพธ์จะถูกส่งคืนในรูปแบบที่ขึ้นอยู่กับทั้ง PUBs ที่ใช้และ runtime options ที่ระบุจาก Sampler หรือ Estimator primitives

ภาพรวมของ PUBs

เมื่อเรียกใช้เมธอด run() ของ primitive argument หลักที่ต้องใช้คือ list ของ tuple หนึ่งตัวหรือมากกว่า — หนึ่ง tuple ต่อหนึ่ง circuit ที่รัน แต่ละ tuple นี้ถือเป็น PUB และ elements ที่จำเป็นของแต่ละ tuple ในรายการขึ้นอยู่กับ primitive ที่ใช้ ข้อมูลที่ระบุใน tuples เหล่านี้ยังสามารถจัดเรียงในรูปแบบต่างๆ เพื่อให้มีความยืดหยุ่นใน workload ผ่านการ broadcasting — ซึ่งมีกฎที่อธิบายไว้ในส่วนถัดไป

Estimator PUB

สำหรับ Estimator primitive รูปแบบของ PUB ควรมีค่าไม่เกินสี่ค่า:

  • QuantumCircuit เดียว ซึ่งอาจมี Parameter objects หนึ่งตัวหรือมากกว่า
  • รายการของ observables หนึ่งตัวหรือมากกว่า ซึ่งระบุ expectation values ที่ต้องการประมาณ จัดเรียงเป็น array (เช่น observable เดียวแทนด้วย 0-d array, รายการของ observables เป็น 1-d array และอื่นๆ) ข้อมูลสามารถอยู่ในรูปแบบ ObservablesArrayLike ใดก็ได้ เช่น Pauli, SparsePauliOp, PauliList หรือ str
    หมายเหตุ

    ถ้ามี observables สองตัวที่ commute กันอยู่ใน PUBs ต่างกันแต่มี circuit เดียวกัน จะไม่ถูกประมาณด้วยการวัดเดียวกัน แต่ละ PUB แทน basis สำหรับการวัดที่แตกต่างกัน ดังนั้นจึงต้องมีการวัดแยกกันสำหรับแต่ละ PUB เพื่อให้แน่ใจว่า commuting observables ถูกประมาณด้วยการวัดเดียวกัน ต้องจัดกลุ่มไว้ใน PUB เดียวกัน

  • ชุดของค่า parameter เพื่อ bind กับ circuit สามารถระบุเป็น array-like object เดียวที่ index สุดท้ายอยู่บน Parameter objects ของ circuit หรือละไว้ (หรือเทียบเท่ากับการตั้งค่าเป็น None) ถ้า circuit ไม่มี Parameter objects
  • (ไม่บังคับ) ความแม่นยำเป้าหมายสำหรับ expectation values ที่ต้องการประมาณ

Sampler PUB

สำหรับ Sampler primitive รูปแบบของ PUB tuple มีค่าไม่เกินสามค่า:

  • QuantumCircuit เดียว ซึ่งอาจมี Parameter objects หนึ่งตัวหรือมากกว่า หมายเหตุ: Circuit เหล่านี้ควรมีคำสั่งการวัดสำหรับแต่ละ qubit ที่ต้องการ sample ด้วย
  • ชุดของค่า parameter เพื่อ bind กับ circuit θk\theta_k (จำเป็นเฉพาะเมื่อมี Parameter objects ที่ต้อง bind ตอน runtime)
  • (ไม่บังคับ) จำนวน shots สำหรับวัด circuit

โค้ดต่อไปนี้แสดงตัวอย่างชุดของ vectorized inputs ให้กับ Estimator primitive และรันบน IBM® backend เป็น RuntimeJobV2 object เดียว

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
from qiskit.circuit import (
Parameter,
QuantumCircuit,
ClassicalRegister,
QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray

from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
SamplerV2 as Sampler,
)

import numpy as np

# Instantiate runtime service and get
# the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)

# Transpile the circuit
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout

# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
[
np.linspace(-np.pi, np.pi, 100),
np.linspace(-4 * np.pi, 4 * np.pi, 100),
]
).T

# Define three observables. The inner length-1 lists cause this array of
# observables to have shape (3, 1), rather than shape (3,) if they were
# omitted.
observables = [
[SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
[SparsePauliOp("XX")],
[SparsePauliOp("IY")],
]
# Apply the same layout as the transpiled circuit.
observables = [
[observable.apply_layout(layout) for observable in observable_set]
for observable_set in observables
]

# Estimate the expectation value for all 300 combinations of observables
# and parameter values, where the pub result will have shape (3, 100).
#
# This shape is due to our array of parameter bindings having shape
# (100, 2), combined with our array of observables having shape (3, 1).
estimator_pub = (transpiled_circuit, observables, params)

# Instantiate the new estimator object, then run the transpiled circuit
# using the set of parameters and observables.
estimator = Estimator(mode=backend)
job = estimator.run([estimator_pub])
result = job.result()

กฎการ Broadcasting

PUBs รวม elements จาก arrays หลายตัว (observables และค่า parameter) โดยทำตามกฎการ broadcasting เดียวกับ NumPy ส่วนนี้สรุปกฎเหล่านั้นโดยย่อ สำหรับคำอธิบายโดยละเอียด ดูเอกสาร NumPy broadcasting rules

กฎ:

  • Input arrays ไม่จำเป็นต้องมีจำนวน dimensions เท่ากัน
    • Array ผลลัพธ์จะมีจำนวน dimensions เท่ากับ input array ที่มี dimension ใหญ่ที่สุด
    • ขนาดของแต่ละ dimension คือขนาดที่ใหญ่ที่สุดของ dimension ที่ตรงกัน
    • Dimensions ที่หายไปถือว่ามีขนาดหนึ่ง
  • การเปรียบเทียบ shape เริ่มจาก dimension ขวาสุดและดำเนินต่อไปทางซ้าย
  • Dimensions สองตัวเข้ากันได้ถ้าขนาดเท่ากันหรือถ้าตัวหนึ่งเป็น 1

ตัวอย่างคู่ของ arrays ที่ broadcast ได้:

A1     (1d array):      1
A2 (2d array): 3 x 5
Result (2d array): 3 x 5

A1 (3d array): 11 x 2 x 7
A2 (3d array): 11 x 1 x 7
Result (3d array): 11 x 2 x 7

ตัวอย่างคู่ของ arrays ที่ broadcast ไม่ได้:

A1     (1d array):  5
A2 (1d array): 3

A1 (2d array): 2 x 1
A2 (3d array): 6 x 5 x 4 # This would work if the middle dimension were 2, but it is 5.

EstimatorV2 คืน expectation value estimate หนึ่งค่าสำหรับแต่ละ element ของ shape ที่ broadcast แล้ว

นี่คือตัวอย่างของ patterns ทั่วไปที่แสดงในรูปของ array broadcasting พร้อมกับการแสดงภาพแบบ visual ในรูปถัดไป:

ชุดค่า parameter แทนด้วย n x m arrays และ observable arrays แทนด้วย single-column arrays หนึ่งตัวหรือมากกว่า สำหรับแต่ละตัวอย่างในโค้ดก่อนหน้า ชุดค่า parameter ถูกรวมกับ observable array เพื่อสร้าง expectation value estimates ที่ได้

  • ตัวอย่างที่ 1: (broadcast single observable) มีชุดค่า parameter เป็น array 5x1 และ observables array แบบ 1x1 item เดียวใน observables array ถูกรวมกับแต่ละ item ในชุดค่า parameter เพื่อสร้าง array 5x1 เดียวที่แต่ละ item เป็นการรวมกันของ item ต้นฉบับในชุดค่า parameter กับ item ใน observables array

  • ตัวอย่างที่ 2: (zip) มีชุดค่า parameter 5x1 และ observables array 5x1 ผลลัพธ์คือ array 5x1 ที่แต่ละ item เป็นการรวมกันของ item ที่ n ในชุดค่า parameter กับ item ที่ n ในชุด observables array

  • ตัวอย่างที่ 3: (outer/product) มีชุดค่า parameter 1x6 และ observables array 4x1 การรวมกันของพวกมันให้ผล array 4x6 ที่สร้างโดยการรวมแต่ละ item ในชุดค่า parameter กับ ทุก item ใน observables array ดังนั้นแต่ละค่า parameter จึงกลายเป็นคอลัมน์ทั้งคอลัมน์ในผลลัพธ์

  • ตัวอย่างที่ 4: (Standard nd generalization) มีชุดค่า parameter array 3x6 และ observables array สองตัวแบบ 3x1 พวกมันรวมกันเพื่อสร้าง output arrays 3x6 สองตัวในลักษณะเดียวกับตัวอย่างก่อนหน้า

ภาพนี้แสดง visual representations หลายแบบของ array broadcasting

# Broadcast single observable
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = SparsePauliOp("ZZZ") # shape ()
# >> pub result has shape (5,)

# Zip
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = [
SparsePauliOp(pauli) for pauli in ["III", "XXX", "YYY", "ZZZ", "XYZ"]
] # shape (5,)
# >> pub result has shape (5,)

# Outer/Product
parameter_values = np.random.uniform(size=(1, 6)) # shape (1, 6)
observables = [
[SparsePauliOp(pauli)] for pauli in ["III", "XXX", "YYY", "ZZZ"]
] # shape (4, 1)
# >> pub result has shape (4, 6)

# Standard nd generalization
parameter_values = np.random.uniform(size=(3, 6)) # shape (3, 6)
observables = [
[
[SparsePauliOp(["XII"])],
[SparsePauliOp(["IXI"])],
[SparsePauliOp(["IIX"])],
],
[
[SparsePauliOp(["ZII"])],
[SparsePauliOp(["IZI"])],
[SparsePauliOp(["IIZ"])],
],
] # shape (2, 3, 1)
# >> pub result has shape (2, 3, 6)
SparsePauliOp

แต่ละ SparsePauliOp นับเป็น element เดียวในบริบทนี้ โดยไม่คำนึงถึงจำนวน Paulis ที่มีอยู่ใน SparsePauliOp ดังนั้น สำหรับจุดประสงค์ของกฎ broadcasting เหล่านี้ elements ต่อไปนี้ทั้งหมดมี shape เดียวกัน:

a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()

รายการของ operators ต่อไปนี้ แม้จะเทียบเท่ากันในแง่ของข้อมูลที่มีอยู่ มี shape ต่างกัน:

list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )

title: "อินพุตและเอาต์พุตของ Primitives (ต่อ)" sidebar_label: "อินพุตและเอาต์พุตของ Primitives (ต่อ)" description: "ทำความเข้าใจรูปแบบอินพุตและเอาต์พุต (รวมถึง Primitive Unified Blocs หรือ PUBs) ของ primitives — ส่วนที่ 2" notebook_path: "guides/primitive-input-output.ipynb"

ภาพรวมของ primitive outputs

เมื่อส่ง PUBs หนึ่งตัวหรือมากกว่าไปยัง QPU เพื่อประมวลผลและ job เสร็จสิ้นสำเร็จ ข้อมูลจะถูกส่งคืนในรูปแบบ PrimitiveResult container object ที่เข้าถึงได้โดยการเรียก RuntimeJobV2.result() method PrimitiveResult มี iterable list ของ PubResult objects ที่มีผลลัพธ์การประมวลผลสำหรับแต่ละ PUB ขึ้นอยู่กับ primitive ที่ใช้ ข้อมูลเหล่านี้จะเป็นทั้ง expectation values และ error bars ในกรณีของ Estimator หรือ samples ของ circuit output ในกรณีของ Sampler

แต่ละ element ของ list นี้ตรงกับแต่ละ PUB ที่ส่งให้กับเมธอด run() ของ primitive (เช่น job ที่ส่งพร้อม 20 PUBs จะคืน PrimitiveResult object ที่มีรายการ 20 PubResult หนึ่งตัวตรงกับแต่ละ PUB)

แต่ละ PubResult objects เหล่านี้มีทั้ง data และ metadata attribute data attribute คือ DataBin แบบกำหนดเองที่มีค่าการวัดจริง ค่าเบี่ยงเบนมาตรฐาน และอื่นๆ DataBin นี้มี attributes ต่างกันขึ้นอยู่กับ shape หรือโครงสร้างของ PUB ที่เกี่ยวข้องและ error mitigation options ที่ระบุโดย primitive ที่ใช้ส่ง job (เช่น ZNE หรือ PEC) ในขณะที่ metadata attribute มีข้อมูลเกี่ยวกับ runtime และ error mitigation options ที่ใช้ (อธิบายในส่วนข้อมูล metadata ของผลลัพธ์ ในหน้านี้)

นี่คือโครงร่างภาพของโครงสร้างข้อมูล PrimitiveResult:

└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── evs
│ │ └── List of estimated expectation values in the shape
| | specified by the first pub
│ └── stds
│ └── List of calculated standard deviations in the
| same shape as above
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| ├── evs
| │ └── List of estimated expectation values in the shape
| | specified by the second pub
| └── stds
| └── List of calculated standard deviations in the
| same shape as above
├── ...
├── ...
└── ...

พูดง่ายๆ คือ job เดียวคืน PrimitiveResult object และมีรายการของ PubResult objects หนึ่งตัวหรือมากกว่า PubResult objects เหล่านี้จัดเก็บข้อมูลการวัดสำหรับแต่ละ PUB ที่ส่งให้กับ job

แต่ละ PubResult มีรูปแบบและ attributes ต่างกันตามประเภทของ primitive ที่ใช้สำหรับ job รายละเอียดอธิบายไว้ด้านล่าง

Estimator output

แต่ละ PubResult สำหรับ Estimator primitive มีอย่างน้อย array ของ expectation values (PubResult.data.evs) และค่าเบี่ยงเบนมาตรฐานที่เกี่ยวข้อง (ทั้ง PubResult.data.stds หรือ PubResult.data.ensemble_standard_error ขึ้นอยู่กับ resilience_level ที่ใช้) แต่สามารถมีข้อมูลเพิ่มเติมขึ้นอยู่กับ error mitigation options ที่ระบุ

โค้ดด้านล่างอธิบายรูปแบบ PrimitiveResult (และ PubResult ที่เกี่ยวข้อง) สำหรับ job ที่สร้างด้านบน

print(
f"The result of the submitted job had {len(result)} PUB and has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:\n {result[0].data}\n"
)
print(f"And this DataBin has attributes: {result[0].data.keys()}")
print(
"Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the\n\
number of parameters in the circuit -- combined with our array of observables having shape (3, 1). \n"
)
print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})

The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100))

And this DataBin has attributes: dict_keys(['evs', 'stds', 'ensemble_standard_error'])
Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the
number of parameters in the circuit -- combined with our array of observables having shape (3, 1).

The expectation values measured from this PUB are:
[[ 0.00948597 0.12163221 0.29100944 0.40535344 0.46625814 0.54716103
0.57690846 0.59809047 0.5784682 0.50924868 0.4579837 0.40035644
0.37174056 0.32887613 0.25850853 0.26396412 0.25852429 0.26074166
0.29282485 0.34388535 0.37368314 0.43562138 0.46912323 0.51955146
0.54430185 0.55467261 0.5162183 0.52744696 0.47261781 0.42613541
0.35400013 0.33217125 0.29600426 0.27561903 0.25307754 0.25672088
0.28783701 0.36612701 0.40433263 0.44428286 0.51028376 0.55034507
0.55979913 0.57160124 0.54127534 0.49753533 0.42942659 0.32552331
0.20215918 0.04303087 -0.08115732 -0.18473659 -0.34015892 -0.44489319
-0.49112115 -0.54588034 -0.60601287 -0.55869218 -0.53353861 -0.51628053
-0.44978534 -0.38090252 -0.32481576 -0.28832245 -0.27057547 -0.26542929
-0.27054473 -0.29367389 -0.31531828 -0.38462352 -0.40276794 -0.47168997
-0.48548191 -0.5382924 -0.52716406 -0.53277032 -0.50776933 -0.48512907
-0.44335198 -0.38756463 -0.34438156 -0.29199194 -0.2729216 -0.24602918
-0.23527174 -0.3019153 -0.35159518 -0.38303379 -0.42434541 -0.47743033
-0.54652609 -0.5877912 -0.59175701 -0.57386895 -0.56416812 -0.48022381
-0.3853372 -0.2639702 -0.12030502 0.02081148]
[ 0.00581765 0.0552677 0.15998546 0.20725389 0.25452232 0.34178711
0.39196437 0.47050268 0.50031815 0.527952 0.57231161 0.64066903
0.72429779 0.77011181 0.78174711 0.86610308 0.88646487 0.91337151
0.94245978 0.98100173 0.97372966 1.00936279 1.01881647 1.0544496
1.01954368 1.03699664 0.99845469 1.03845105 1.00936279 1.00354513
0.95409508 0.95264067 0.91264431 0.91846196 0.8355604 0.80283611
0.77956549 0.74102354 0.69520953 0.64575948 0.58976457 0.53231524
0.43996 0.3956004 0.32069812 0.27706572 0.22470684 0.16653032
0.07272066 -0.00218162 -0.05817653 -0.06253977 -0.15853104 -0.25015908
-0.28506499 -0.34251432 -0.44359604 -0.44432324 -0.53158804 -0.60285429
-0.637033 -0.67630215 -0.71266249 -0.76793019 -0.81519862 -0.86464867
-0.90173621 -0.93155168 -0.9337333 -0.98245614 -0.99627307 -1.01518044
-1.01590764 -1.04863194 -1.00499955 -1.02827016 -1.01663485 -1.0108172
-1.02317971 -0.97518407 -0.96500318 -0.94682302 -0.901009 -0.87846559
-0.79556404 -0.84937733 -0.78101991 -0.73811472 -0.65521316 -0.57667485
-0.59921825 -0.49813653 -0.44577766 -0.36505772 -0.33524225 -0.25888556
-0.21161713 -0.12289792 -0.03781474 0.00654486]
[ 0.01315429 0.18799671 0.42203343 0.603453 0.67799397 0.75253494
0.76185256 0.72567827 0.65661825 0.49054535 0.3436558 0.16004385
0.01918334 -0.11235955 -0.26473006 -0.33817484 -0.36941628 -0.39188819
-0.35681008 -0.29323102 -0.22636339 -0.13812003 -0.08057002 -0.01534667
0.06906002 0.07234859 0.03398191 0.01644286 -0.06412716 -0.15127432
-0.24609482 -0.28829816 -0.32063579 -0.3672239 -0.32940532 -0.28939435
-0.20389148 -0.00876953 0.11345574 0.24280625 0.43080296 0.5683749
0.67963826 0.74760208 0.76185256 0.71800493 0.63414634 0.48451631
0.3315977 0.08824335 -0.10413812 -0.30693341 -0.52178679 -0.6396273
-0.69717731 -0.74924637 -0.76842971 -0.67306111 -0.53548918 -0.42970677
-0.26253768 -0.08550288 0.06303097 0.19128528 0.27404768 0.33379008
0.36064675 0.34420389 0.30309674 0.2132091 0.19073719 0.07180049
0.04494382 -0.02795286 -0.04932858 -0.03727049 0.00109619 0.04055906
0.13647575 0.20005481 0.27624007 0.36283913 0.3551658 0.38640723
0.32502055 0.24554673 0.07782954 -0.02795286 -0.19347767 -0.3781858
-0.49383393 -0.67744588 -0.73773637 -0.78268019 -0.793094 -0.70156207
-0.55905728 -0.40504248 -0.20279529 0.0350781 ]]

วิธีที่ Estimator คำนวณ error

นอกจากการประมาณค่าเฉลี่ยของ observables ที่ส่งมาใน input PUBs (field evs ของ DataBin) Estimator ยังพยายามส่งคืนการประมาณ error ที่เกี่ยวข้องกับ expectation values เหล่านั้นด้วย Estimator queries ทั้งหมดจะเติมข้อมูลใน field stds ด้วยปริมาณที่คล้ายกับ standard error of the mean สำหรับแต่ละ expectation value แต่ error mitigation options บางอย่างให้ข้อมูลเพิ่มเติม เช่น ensemble_standard_error

พิจารณา observable เดียว O\mathcal{O} หากไม่มี ZNE สามารถมองแต่ละ shot ของการรัน Estimator ว่าให้ point estimate ของ expectation value O\langle \mathcal{O} \rangle ถ้า pointwise estimates อยู่ใน vector Os ค่าที่คืนใน ensemble_standard_error เทียบเท่ากับสิ่งต่อไปนี้ (ซึ่ง σO\sigma_{\mathcal{O}} คือstandard deviation ของ expectation value estimate และ NshotsN_{shots} คือจำนวน shots):

σONshots,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{shots}} },

ซึ่งถือว่า shots ทั้งหมดเป็นส่วนหนึ่งของ ensemble เดียว ถ้าขอ gate twirling (twirling.enable_gates = True) สามารถจัดเรียง pointwise estimates ของ O\langle \mathcal{O} \rangle เป็นชุดที่มี twirl เดียวกัน เรียกชุด estimates เหล่านี้ว่า O_twirls และมี num_randomizations (จำนวน twirls) ชุด จากนั้น stds คือ standard error of the mean ของ O_twirls เช่น

σONtwirls,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{twirls}} },

ซึ่ง σO\sigma_{\mathcal{O}} คือ standard deviation ของ O_twirls และ NtwirlsN_{twirls} คือจำนวน twirls เมื่อไม่ได้เปิดใช้ twirling stds และ ensemble_standard_error จะเท่ากัน

ถ้าเปิดใช้ ZNE stds ที่อธิบายด้านบนจะกลายเป็น weights ใน non-linear regression กับ extrapolator model สิ่งที่สุดท้ายถูกส่งคืนใน stds ในกรณีนี้คือ uncertainty ของ fit model ที่ประเมินที่ noise factor เป็นศูนย์ เมื่อ fit ไม่ดีหรือมี uncertainty มากใน fit stds ที่รายงานอาจมีค่าสูงมาก เมื่อเปิดใช้ ZNE pub_result.data.evs_noise_factors และ pub_result.data.stds_noise_factors จะถูกเติมข้อมูลด้วย เพื่อให้สามารถทำ extrapolation เองได้

Sampler output

เมื่อ Sampler job เสร็จสิ้นสำเร็จ PrimitiveResult object ที่ส่งคืนมีรายการของ SamplerPubResult หนึ่งตัวต่อ PUB data bins ของ SamplerPubResult objects เหล่านี้เป็น dict-like objects ที่มี BitArray หนึ่งตัวต่อ ClassicalRegister ใน circuit

คลาส BitArray เป็น container สำหรับข้อมูล shot แบบเรียงลำดับ โดยละเอียดคือจัดเก็บ bitstrings ที่ sample มาเป็น bytes ภายใน array สองมิติ แกนซ้ายสุดของ array นี้วิ่งผ่าน ordered shots ส่วนแกนขวาสุดวิ่งผ่าน bytes

เป็นตัวอย่างแรก มาดู ten-qubit circuit ต่อไปนี้:

# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure_all()

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")

# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
Databin: DataBin(meas=BitArray(<shape=(), num_shots=4096, num_bits=10>))

BitArray: BitArray(<shape=(), num_shots=4096, num_bits=10>)

The shape of register `meas` is (4096, 2).

The bytes in register `alpha`, shot by shot:
[[ 3 254]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]

บางครั้งการแปลงจากรูปแบบ bytes ใน BitArray เป็น bitstrings อาจสะดวกกว่า เมธอด get_count คืน dictionary ที่ mapping bitstrings ไปยังจำนวนครั้งที่เกิดขึ้น

# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'1111111110': 199, '0000000000': 1337, '1111111111': 1052, '1111111000': 33, '1110000000': 65, '1100100000': 2, '1100000000': 25, '0010001110': 1, '0000000011': 30, '1111111011': 58, '1111111010': 25, '0000000110': 7, '0010000001': 11, '0000000001': 179, '1110111110': 6, '1111110000': 33, '1111101111': 49, '1110111111': 40, '0000111010': 2, '0100000000': 35, '0000000010': 51, '0000100000': 31, '0110000000': 7, '0000001111': 22, '1111111100': 24, '1011111110': 5, '0001111111': 58, '0000111111': 24, '1111101110': 10, '0000010001': 5, '0000001001': 2, '0011111111': 38, '0000001000': 11, '1111100000': 34, '0111111111': 45, '0000000100': 18, '0000000101': 2, '1011111111': 11, '1110000001': 13, '1101111000': 1, '0010000000': 52, '0000010000': 17, '0000011111': 15, '1110100001': 1, '0111111110': 9, '0000000111': 19, '1101111111': 15, '1111110111': 17, '0011111110': 5, '0001101110': 1, '0111111011': 6, '0100001000': 2, '0010001111': 1, '1111011000': 1, '0000111110': 4, '0011110010': 1, '1110111100': 2, '1111000000': 8, '1111111101': 27, '0000011110': 6, '0001000000': 5, '1111010000': 3, '0000011011': 4, '0001111110': 9, '1111011110': 6, '1110001111': 2, '0100000001': 7, '1110111011': 3, '1111101101': 2, '1101111110': 5, '1110000010': 7, '0111111000': 1, '1110111000': 1, '0000100001': 2, '1110100000': 6, '1000000001': 2, '0001011111': 1, '0000010111': 1, '1011111100': 1, '0111110000': 5, '0110111111': 2, '0010000010': 1, '0001111100': 4, '0011111001': 2, '1111110011': 1, '1110000011': 5, '0000001011': 8, '0100000010': 3, '1111011111': 13, '0010111000': 2, '0100111110': 1, '1111101000': 2, '1110110000': 2, '1100000001': 1, '0001110000': 3, '1011101111': 2, '1111000001': 2, '1111110001': 8, '1111110110': 4, '1100000010': 3, '0011000000': 2, '1110011111': 3, '0011101111': 3, '0010010000': 2, '0000100010': 1, '1100001110': 1, '0001111011': 4, '1010000000': 3, '0000001110': 5, '0000001010': 2, '0011111011': 4, '0100100000': 2, '1111110100': 1, '1111100011': 3, '0000110110': 1, '0001111101': 2, '1111100001': 2, '1000000000': 5, '0010000011': 3, '0010011111': 3, '0100001111': 1, '0100000111': 1, '1011101110': 1, '0011110111': 1, '1100000111': 1, '1100111111': 3, '0001111010': 1, '1101111011': 1, '0111111100': 2, '0100000110': 2, '0100000011': 2, '0001101111': 3, '0001000001': 1, '1111110010': 1, '0010100000': 1, '0011100000': 4, '1010001111': 1, '0101111111': 2, '1111101001': 1, '1110111101': 1, '0000011101': 1, '1110001000': 2, '0001111001': 1, '0101000000': 1, '1111111001': 5, '0001110111': 2, '0000111001': 1, '0100001011': 1, '0000010011': 1, '1011110111': 1, '0011110001': 1, '0000001100': 2, '0111010111': 1, '0001101011': 1, '1110010000': 2, '1110000100': 1, '0010111111': 3, '0111011100': 1, '1010001000': 1, '0000101110': 1, '0011111100': 2, '0000111100': 2, '1110011110': 1, '0011111000': 2, '0110100000': 1, '1001101111': 1, '1011000000': 1, '1101000000': 1, '1110001011': 1, '1110110111': 1, '0110111110': 1, '0011011111': 1, '0111100000': 1, '0000110111': 1, '0000010010': 2, '1111101100': 2, '1111011101': 1, '1101100000': 1, '0010111110': 1, '1101101110': 1, '1111001111': 1, '1101111100': 1, '1011111010': 1, '0001100000': 1, '1101110111': 1, '1100001011': 1}

เมื่อ circuit มีมากกว่าหนึ่ง classical register ผลลัพธ์จะถูกจัดเก็บใน BitArray objects ต่างกัน ตัวอย่างต่อไปนี้ปรับ snippet ก่อนหน้าโดยแบ่ง classical register เป็นสอง registers ที่แตกต่างกัน:

# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)

การใช้ประโยชน์จาก BitArray objects เพื่อการ post-processing ที่มีประสิทธิภาพ

เนื่องจาก arrays โดยทั่วไปให้ประสิทธิภาพที่ดีกว่าเมื่อเทียบกับ dictionaries จึงแนะนำให้ทำ post-processing ใดๆ บน BitArray objects โดยตรงแทนที่จะทำบน dictionaries ของ counts คลาส BitArray มีเมธอดหลายอย่างสำหรับทำ post-processing operations ทั่วไป:

print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")

print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")

# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")

# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")

# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)

# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")

# concatenate the bitstrings in `alpha` and `beta` to "merge" the results of the two
# registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 135]
[ 0 247]
[ 1 247]
...
[ 0 0]
[ 1 224]
[ 1 255]]

The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[7]
[7]
...
[0]
[0]
[7]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 135]
[ 0 247]
[ 1 247]
[ 1 128]
[ 1 255]]

Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.068359375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.06396484375

The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 1 15]
[ 1 239]
[ 3 239]
...
[ 0 0]
[ 3 192]
[ 3 255]]

ข้อมูล metadata ของผลลัพธ์

นอกจากผลลัพธ์การประมวลผล ทั้ง PrimitiveResult และ PubResult objects ยังมี metadata attribute เกี่ยวกับ job ที่ส่งไป metadata ที่มีข้อมูลสำหรับ PUBs ทั้งหมดที่ส่ง (เช่น runtime options ต่างๆ ที่มีให้) สามารถพบได้ใน PrimitiveResult.metatada ส่วน metadata เฉพาะสำหรับแต่ละ PUB อยู่ใน PubResult.metadata

หมายเหตุ

ใน metadata field การ implement primitives สามารถคืนข้อมูลใดๆ เกี่ยวกับการประมวลผลที่เกี่ยวข้อง และไม่มี key-value pairs ที่รับประกันโดย base primitive ดังนั้น metadata ที่ส่งคืนอาจแตกต่างกันใน primitive implementations ต่างกัน

# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")

print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-01-15 08:07:33', stop='2026-01-15 08:07:36', size=4096>)])},
'version' : 2,

The metadata of the PubResult result is:
'circuit_metadata' : {},

สำหรับ Sampler jobs ยังสามารถตรวจสอบ result metadata เพื่อเข้าใจว่าข้อมูลบางส่วนถูกรันเมื่อไหร่ ซึ่งเรียกว่า execution span

Source: IBM Quantum docs — updated 27 เม.ย. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of 11 มี.ค. 2569