주 콘텐츠로 건너뛰기

프리미티브 입력 및 출력

새로운 실행 모델 — 현재 베타 출시 중

새로운 실행 모델의 베타 버전이 제공됩니다. 지향형 실행 모델은 오류 완화 워크플로우를 맞춤 설정할 때 더 많은 유연성을 제공합니다. 자세한 내용은 지향형 실행 모델 가이드를 참조하세요.

패키지 버전

이 페이지의 코드는 아래 요구 사항을 사용하여 개발되었습니다. 해당 버전 이상을 사용하시기 권장합니다.

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

이 페이지는 IBM Quantum® 컴퓨팅 리소스에서 워크로드를 실행하는 Qiskit Runtime 프리미티브의 입력 및 출력에 대한 개요를 제공합니다. 이 프리미티브는 **Primitive Unified Bloc(PUB)**이라는 데이터 구조를 사용하여 벡터화된 워크로드를 효율적으로 정의할 수 있는 기능을 제공합니다. 이러한 PUB는 QPU가 워크로드를 실행하기 위한 기본 작업 단위입니다. PUB는 Sampler 및 Estimator 프리미티브의 run() 메서드에 입력으로 사용되며, 정의된 워크로드를 작업(job)으로 실행합니다. 그런 다음 작업이 완료되면 결과는 사용된 PUB와 Sampler 또는 Estimator 프리미티브에서 지정한 런타임 옵션 모두에 따라 달라지는 형식으로 반환됩니다.

PUB 개요

프리미티브의 run() 메서드를 호출할 때 필요한 주요 인수는 하나 이상의 튜플로 구성된 list입니다. 각 튜플은 프리미티브가 실행하는 Circuit에 해당합니다. 이 튜플 각각이 PUB이며, 목록의 각 튜플에 필요한 요소는 사용하는 프리미티브에 따라 다릅니다. 이 튜플에 제공되는 데이터는 브로드캐스팅을 통해 워크로드의 유연성을 제공하기 위해 다양한 형태로 배열될 수도 있습니다. 브로드캐스팅 규칙은 이후 섹션에서 설명합니다.

Estimator PUB

Estimator 프리미티브의 경우 PUB 형식에는 최대 네 가지 값이 포함되어야 합니다.

  • 하나 이상의 Parameter 객체를 포함할 수 있는 단일 QuantumCircuit
  • 추정할 기댓값을 지정하는 하나 이상의 관측값(observable) 목록. 배열로 배치됩니다(예: 단일 관측값은 0차원 배열, 관측값 목록은 1차원 배열 등). 데이터는 Pauli, SparsePauliOp, PauliList, 또는 str과 같은 ObservablesArrayLike 형식 중 하나일 수 있습니다.
    참고

    동일한 Circuit을 가진 서로 다른 PUB에 두 개의 가환(commuting) 관측값이 있는 경우, 동일한 측정을 사용하여 추정되지 않습니다. 각 PUB는 측정의 서로 다른 기저(basis)를 나타내므로, 각 PUB에 대해 별도의 측정이 필요합니다. 가환 관측값이 동일한 측정을 사용하여 추정되도록 하려면, 동일한 PUB 내에 그룹화해야 합니다.

  • Circuit에 바인딩할 파라미터 값 컬렉션. 마지막 인덱스가 Circuit의 Parameter 객체에 대한 단일 배열형 객체로 지정하거나, Circuit에 Parameter 객체가 없는 경우 생략(또는 동등하게 None으로 설정)할 수 있습니다.
  • (선택 사항) 추정할 기댓값의 목표 정밀도

Sampler PUB

Sampler 프리미티브의 경우 PUB 튜플 형식에는 최대 세 가지 값이 포함됩니다.

  • 하나 이상의 Parameter 객체를 포함할 수 있는 단일 QuantumCircuit 참고: 이 Circuit에는 샘플링할 각 Qubit에 대한 측정 명령도 포함되어야 합니다.
  • Circuit에 바인딩할 파라미터 값 컬렉션 θk\theta_k (런타임에 바인딩해야 하는 Parameter 객체가 사용된 경우에만 필요)
  • (선택 사항) Circuit을 측정할 샷(shot) 수

다음 코드는 Estimator 프리미티브에 대한 벡터화된 입력 예시를 보여주며, 이를 IBM® Backend에서 단일 RuntimeJobV2 객체로 실행합니다.

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

브로드캐스팅 규칙

PUB는 NumPy와 동일한 브로드캐스팅 규칙에 따라 여러 배열(관측값 및 파라미터 값)의 요소를 집계합니다. 이 섹션에서는 해당 규칙을 간략하게 요약합니다. 자세한 설명은 NumPy 브로드캐스팅 규칙 문서를 참조하세요.

규칙:

  • 입력 배열의 차원 수가 동일할 필요는 없습니다.
    • 결과 배열은 가장 큰 차원을 가진 입력 배열과 동일한 차원 수를 갖습니다.
    • 각 차원의 크기는 해당 차원의 가장 큰 크기입니다.
    • 누락된 차원은 크기가 1인 것으로 가정합니다.
  • 형태 비교는 가장 오른쪽 차원에서 시작하여 왼쪽으로 진행합니다.
  • 두 차원의 크기가 같거나 그 중 하나가 1이면 호환됩니다.

브로드캐스팅되는 배열 쌍의 예:

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

브로드캐스팅되지 않는 배열 쌍의 예:

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는 브로드캐스팅된 형태의 각 요소에 대해 하나의 기댓값 추정치를 반환합니다.

다음은 배열 브로드캐스팅으로 표현된 일반적인 패턴의 몇 가지 예입니다. 아래 그림에서 이에 대한 시각적 표현을 확인할 수 있습니다.

파라미터 값 세트는 n x m 배열로, 관측값 배열은 하나 이상의 단일 열 배열로 표현됩니다. 이전 코드의 각 예시에서 파라미터 값 세트는 관측값 배열과 결합하여 결과 기댓값 추정치를 생성합니다.

  • 예시 1: (단일 관측값 브로드캐스팅) 파라미터 값 세트는 5x1 배열이고 관측값 배열은 1x1 배열입니다. 관측값 배열의 항목 하나가 파라미터 값 세트의 각 항목과 결합되어, 원래 파라미터 값 세트의 항목과 관측값 배열의 항목의 조합으로 이루어진 단일 5x1 배열이 생성됩니다.

  • 예시 2: (zip) 5x1 파라미터 값 세트와 5x1 관측값 배열이 있습니다. 출력은 파라미터 값 세트의 n번째 항목과 관측값 배열의 n번째 항목이 결합된 5x1 배열입니다.

  • 예시 3: (외적/곱) 1x6 파라미터 값 세트와 4x1 관측값 배열이 있습니다. 이들의 조합은 파라미터 값 세트의 각 항목이 관측값 배열의 모든 항목과 결합되어 생성된 4x6 배열이며, 따라서 각 파라미터 값은 출력에서 전체 열이 됩니다.

  • 예시 4: (표준 n차원 일반화) 3x6 파라미터 값 세트 배열과 두 개의 3x1 관측값 배열이 있습니다. 이들은 이전 예시와 유사한 방식으로 결합하여 두 개의 3x6 출력 배열을 만들어냅니다.

이 이미지는 배열 브로드캐스팅의 여러 시각적 표��현을 보여줍니다

# 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

SparsePauliOpSparsePauliOp에 포함된 Pauli의 수에 관계없이 이 맥락에서 단일 요소로 계산됩니다. 따라서 이 브로드캐스팅 규칙의 목적상 다음 요소들은 모두 동일한 형태를 가집니다.

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

다음 연산자 목록들은 포함된 정보 측면에서 동등하지만 형태가 다릅니다.

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

프리미티브 출력 개요

하나 이상의 PUB이 실행을 위해 QPU에 전송되고 작업이 성공적으로 완료되면, 데이터는 RuntimeJobV2.result() 메서드를 호출하여 접근하는 PrimitiveResult 컨테이너 객체로 반환됩니다. PrimitiveResult에는 각 PUB의 실행 결과를 담은 PubResult 객체의 이터러블 목록이 포함됩니다. 사용된 프리미티브에 따라, 이 데이터는 Estimator의 경우 기댓값과 오차 막대이거나, Sampler의 경우 Circuit 출력의 샘플입니다.

이 목록의 각 요소는 프리미티브의 run() 메서드에 제출된 각 PUB에 해당합니다(예: 20개의 PUB으로 제출된 작업은 각 PUB에 해당하는 20개의 PubResult 목록을 포함하는 PrimitiveResult 객체를 반환합니다).

PubResult 객체는 data 속성과 metadata 속성을 모두 가집니다. data 속성은 실제 측정값, 표준 편차 등을 포함하는 사용자 정의 DataBin입니다. 이 DataBin은 연관된 PUB의 형태나 구조, 그리고 작업 제출에 사용된 프리미티브가 지정한 오류 완화 옵션(예: ZNE 또는 PEC)에 따라 다양한 속성을 가집니다. 한편 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
├── ...
├── ...
└── ...

간단히 말하면, 단일 작업은 PrimitiveResult 객체를 반환하며 하나 이상의 PubResult 객체 목록을 포함합니다. 이 PubResult 객체들은 작업에 제출된 각 PUB의 측정 데이터를 저장합니다.

PubResult는 작업에 사용된 프리미티브 유형에 따라 서로 다른 형식과 속성을 가집니다. 자세한 내용은 아래에 설명되어 있습니다.

Estimator 출력

Estimator 프리미티브의 각 PubResult에는 최소한 기댓값 배열(PubResult.data.evs)과 관련 표준 편차(사용된 resilience_level에 따라 PubResult.data.stds 또는 PubResult.data.ensemble_standard_error)가 포함되며, 지정된 오류 완화 옵션에 따라 더 많은 데이터가 포함될 수 있습니다.

아래 코드 스니펫은 위에서 생성된 작업에 대한 PrimitiveResult(및 관련 PubResult) 형식을 설명합니다.

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가 오류를 계산하는 방법

입력 PUB에 전달된 관측 가능량의 평균 추정값(DataBinevs 필드) 외에도, Estimator는 해당 기댓값과 관련된 오류의 추정값도 제공하려 합니다. 모든 Estimator 쿼리는 각 기댓값에 대한 평균의 표준 오차와 유사한 양으로 stds 필드를 채우지만, 일부 오류 완화 옵션은 ensemble_standard_error와 같은 추가 정보를 생성합니다.

단일 관측 가능량 O\mathcal{O}를 고려해 봅시다. ZNE가 없는 경우, Estimator 실행의 각 샷은 기댓값 O\langle \mathcal{O} \rangle의 점 추정값을 제공한다고 생각할 수 있습니다. 점별 추정값이 벡터 Os에 있다면, ensemble_standard_error에 반환되는 값은 다음과 동일합니다(σO\sigma_{\mathcal{O}}기댓값의 표준 편차 추정값이고 NshotsN_{shots}는 샷 수입니다):

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

이는 모든 샷을 단일 앙상블의 일부로 취급합니다. Gate twirling(twirling.enable_gates = True)을 요청한 경우, O\langle \mathcal{O} \rangle의 점별 추정값을 공통 트윌을 공유하는 집합으로 정렬할 수 있습니다. 이 추정값 집합을 O_twirls라고 부르며, 이러한 집합이 num_randomizations(트윌 수)만큼 있습니다. 그러면 stds는 다음과 같이 O_twirls의 평균의 표준 오차입니다:

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

여기서 σO\sigma_{\mathcal{O}}O_twirls의 표준 편차이고 NtwirlsN_{twirls}는 트윌 수입니다. twirling을 활성화하지 않으면 stdsensemble_standard_error는 동일합니다.

ZNE를 활성화하면, 위에서 설명한 stds는 외삽기 모델에 대한 비선형 회귀의 가중치가 됩니다. 이 경우 stds에 최종적으로 반환되는 것은 잡음 인자가 0일 때 평가된 적합 모델의 불확실성입니다. 적합이 불량하거나 적합의 불확실성이 클 경우, 보고된 stds가 매우 커질 수 있습니다. ZNE가 활성화되면 pub_result.data.evs_noise_factorspub_result.data.stds_noise_factors도 채워지므로, 직접 외삽을 수행할 수 있습니다.

Sampler 출력

Sampler 작업이 성공적으로 완료되면, 반환되는 PrimitiveResult 객체에는 PUB별로 하나씩 SamplerPubResult 목록이 포함됩니다. 이 SamplerPubResult 객체의 데이터 빈(data bin)은 Circuit의 각 ClassicalRegister마다 하나의 BitArray를 포함하는 딕셔너리 유사 객체입니다.

BitArray 클래스는 순서가 있는 샷(shot) 데이터를 담는 컨테이너입니다. 더 자세히 설명하면, 샘플링된 비트 문자열을 2차원 배열 내에 바이트 형태로 저장합니다. 이 배열의 가장 왼쪽 축은 순서가 있는 샷에 걸쳐 있으며, 가장 오른쪽 축은 바이트에 걸쳐 있습니다.

첫 번째 예시로, 다음 10-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]]

BitArray의 바이트 형식에서 비트 문자열로 변환하면 편리한 경우가 있습니다. get_count 메서드는 비트 문자열을 해당 비트 문자열이 등장한 횟수에 매핑하는 딕셔너리를 반환합니다.

# 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에 둘 이상의 고전 레지스터가 포함된 경우, 결과는 서로 다른 BitArray 객체에 저장됩니다. 다음 예시는 이전 코드를 수정하여 고전 레지스터를 두 개의 별도 레지스터로 분리합니다.

# 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 객체 활용

일반적으로 배열은 딕셔너리에 비해 더 나은 성능을 제공하므로, 카운트 딕셔너리보다 BitArray 객체에서 직접 후처리를 수행하는 것이 좋습니다. BitArray 클래스는 일반적인 후처리 작업을 수행하기 위한 다양한 메서드를 제공합니다.

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]]

결과 메타데이터

실행 결과 외에도, PrimitiveResultPubResult 객체 모두 제출된 작업에 대한 메타데이터 속성을 포함합니다. 제출된 모든 PUB에 대한 정보(예: 다양한 런타임 옵션)를 담고 있는 메타데이터는 PrimitiveResult.metatada에서 확인할 수 있으며, 각 PUB에 특화된 메타데이터는 PubResult.metadata에서 확인할 수 있습니다.

참고

메타데이터 필드에서 primitive 구현체는 실행과 관련된 모든 정보를 반환할 수 있으며, 기본 primitive에 의해 보장되는 키-값 쌍은 없습니다. 따라서 반환되는 메타데이터는 primitive 구현체에 따라 다를 수 있습니다.

# 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 작업의 경우, 결과 메타데이터를 검토하여 특정 데이터가 언제 실행되었는지 확인할 수도 있습니다. 이를 실행 스팬(execution span)이라고 합니다.