Skip to main content
IBM Quantum Platform

샘플러의 입력 및 출력

  • 이 페이지의 코드는 다음 요구 사항을 바탕으로 개발되었습니다. 이 버전 이상을 사용하시기를 권장합니다.

    qiskit[all]~=2.5.2
    qiskit-ibm-runtime~=0.47.0
    

이 페이지에서는 IBM Quantum® 컴퓨트 서비스에서 워크로드를 실행하는 qiskit-ibm-runtime ‘Sampler’ 프리미티브의 입력 및 출력에 대한 개요를 제공합니다. Sampler를 사용하면 ‘Primitive Unified Bloc( PUB ) ’이라는 데이터 구조를 활용하여 벡터화된 워크로드를 효율적으로 정의할 수 있습니다. 이들은 Sampler 프리미티브의 run() 메서드에 입력으로 사용되며, 이 메서드는 정의된 워크로드를 작업으로 실행합니다. 그런 다음, 작업이 완료되면 결과는 사용된 PUB와 프리미티브에서 지정된 런타임 옵션 모두에 따라 달라지는 형식으로 반환됩니다.


입력

각 PUB 파일은 다음과 같은 형식을 갖습니다:

(<single circuit>, <one or more optional parameter value>, <optional shots>),

항목은 parameter values 여러 개일 수 있으며, 선택한 회로에 따라 각 항목은 배열이거나 단일 매개변수일 수 있습니다. 또한, 입력값에는 측정값이 포함되어야 합니다.

Sampler 프리미티브의 경우, PUB에는 최대 세 개의 값을 포함할 수 있습니다:

  • 하나 이상의 Parameter 객체를 포함할 수 있는 QuantumCircuit단일 참고: 이러한 회로에는 샘플링 대상인 각 큐비트에 대한 측정 지침도 포함되어야 합니다.
  • θk\theta_k 에 회로를 바인딩하기 위한 매개변수 값 모음 (실행 시점에 바인딩해야 하는 객체가 Parameter 있는 경우에만 필요함)
  • (선택 사항) 회로를 측정할 샷 수

다음 코드는 프라이머리 Sampler (primitive)에 대한 벡터화된 입력 예시를 보여주고, 이를 IBM® 백엔드에서 단일 RuntimeJobV2 객체로 실행합니다.

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,
    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)
circuit.measure_all()

# 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

sampler_pub = (transpiled_circuit, params)

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

출력

하나 이상의 PUB가 실행을 위해 QPU로 전송되고 작업이 성공적으로 완료되면, 데이터는 RuntimeJobV2.result() 메서드를 호출하여 액세스할 수 있는 PrimitiveResult 컨테이너 객체로 반환됩니다. 이 객체에는 각 SamplerPubResultPUB 에 대한 PrimitiveResult 실행 결과를 포함하는 객체들의 반복 가능한 목록이 포함되어 있습니다. 이 데이터는 회로 출력의 샘플입니다.

이 목록의 각 요소는 프라이머리(primitive)의 run() 메서드에 제출된 작업( PUB )에 해당합니다(예를 들어, 20개의 PUB로 제출된 작업은 각 작업( PUB )에 하나씩 대응하는 20개의 SamplerPubResult 객체 목록을 포함하는 객체를 PrimitiveResult 반환합니다).

SamplerPubResult 객체는 속성과 data 속성을 metadata 모두 가지고 있습니다.

  • data 속성은 실제 측정값, 표준 편차 등을 포함하는 사용자 DataBin 정의된 데이터입니다. 데이터 빈은 회로 내의 각 ClassicalRegister 노드마다 BitArray 하나씩 포함하는 딕셔너리 형태의 객체입니다.
  • BitArray 클래스는 순서대로 정렬된 샷 데이터를 담는 컨테이너입니다. 샘플링된 비트열을 2차원 배열 내에 바이트 단위로 저장합니다. 이 배열의 가장 왼쪽 축은 순서대로 배열된 샷을, 가장 오른쪽 축은 바이트를 나타냅니다.
  • metadata 속성에는 사용된 런타임 옵션에 대한 정보가 포함되어 있습니다(이 페이지의 ‘결과 메타데이터 ’ 섹션에서 나중에 설명합니다).

다음은 해당 PrimitiveResult 데이터 구조의 시각적 개요입니다:

    └── PrimitiveResult
        ├── SamplerPubResult[0]
        │   ├── metadata
        │   └── data  ## In the form of a DataBin object
        │       ├── NAME_OF_CLASSICAL_REGISTER
        │       │   └── BitArray of count data (default is 'meas')
        |       |
        │       └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
        │           └── BitArray of count data (exists only if more than one
        |                 ClassicalRegister was specified in the circuit)
        ├── SamplerPubResult[1]
        |   ├── metadata
        |   └── data  ## In the form of a DataBin object
        |       └── NAME_OF_CLASSICAL_REGISTER
        |           └── BitArray of count data for second pub
        ├── ...
        ├── ...
        └── ...

간단히 말해, 하나의 작업은 객체를 PrimitiveResult 반환하며 하나 이상의 SamplerPubResult 객체로 구성된 목록을 포함합니다. 그런 다음 이 SamplerPubResult 객체들은 해당 작업에 제출된 각 PUB 에 대한 측정 데이터를 저장합니다.

첫 번째 예로, 다음의 10큐비트 회로를 살펴보겠습니다:

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

Output:

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 247]
 [  0   0]
 [  3 223]
 ...
 [  0 255]
 [  0   0]
 [  0   0]]

때로는 바이트 형식을 BitArray 비트열로 변환하는 것이 편리할 수 있습니다. 이 get_count 메서드는 비트열과 해당 비트열이 나타난 횟수를 대응시키는 사전(dictionary)을 반환합니다.

# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")

Output:

Counts: {'1111110111': 28, '0000000000': 1680, '1111011111': 35, '1111111111': 1467, '1110000000': 28, '0000000010': 15, '1111111011': 15, '1110111111': 7, '1000000000': 52, '1111101111': 16, '1111110000': 22, '0000000101': 3, '0001111101': 1, '0000000111': 17, '0000000001': 66, '1111111110': 119, '0000001000': 15, '0011111111': 24, '1111111101': 38, '0000001111': 32, '1100000000': 39, '1101111111': 51, '0000011111': 34, '1110000010': 2, '0010000000': 25, '0111111111': 21, '0001111111': 34, '1011111111': 16, '1000011111': 3, '0000001101': 1, '1111100000': 22, '1111111100': 22, '1111111000': 8, '0010001011': 1, '0011000000': 1, '0000000011': 16, '0011111100': 2, '1011110001': 1, '1101111100': 1, '1101111101': 1, '0000000110': 2, '0000000100': 2, '0001111110': 6, '0000001110': 4, '0011110111': 2, '1101111110': 3, '0111111011': 1, '0110000000': 1, '0000001011': 2, '1111110110': 3, '0111111000': 1, '1010000000': 2, '0001110111': 1, '1111101000': 1, '0010011110': 1, '1111100111': 1, '0111110111': 1, '0100000000': 5, '1101110111': 1, '0000001001': 1, '0010000001': 1, '1111000001': 1, '0000010000': 3, '1111101110': 1, '1111000000': 8, '0001011111': 3, '1000000001': 1, '1111010111': 2, '0010011111': 1, '1011111110': 1, '1101101111': 1, '1111011110': 2, '1111111010': 1, '1111010000': 1, '1101000000': 1, '1100001111': 1, '1100011111': 1, '0000111111': 4, '0010111111': 1, '1000000110': 1, '1110111110': 1, '1101011111': 2, '0000011000': 1, '1101110000': 2, '1011111011': 1, '1000111111': 1, '1011000000': 1, '1110000001': 2, '0101111111': 2, '1111110101': 1, '0010000011': 1, '0000011110': 1, '0000011101': 1, '0111000000': 1, '0001110000': 1, '0001000000': 4, '1101111000': 1, '1011111000': 1, '0111110000': 1, '1101110110': 1, '0011011111': 1, '0000100000': 2, '1111101100': 1, '1100001000': 1, '1010000011': 1, '0011111110': 1, '1010001110': 1, '1000001000': 1, '1000000011': 1, '1111101011': 1}

회로에 하나 이상의 고전적 레지스터가 포함되어 있으면, 결과는 서로 다른 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}")

Output:

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")

Output:

The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
 [0]
 [1]
 ...
 [0]
 [1]
 [0]]

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

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]
 [0]
 [7]
 ...
 [0]
 [7]
 [0]]

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

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

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


결과 메타데이터

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

샘플러 결과 메타데이터에는 ‘실행 기간 ’이라고 하는 실행 시간 정보도 포함됩니다.

Note

메타데이터 필드에서 기본 구현체는 자신과 관련된 실행 정보를 자유롭게 반환할 수 있으며, 기본 기본형에서 보장하는 키-값 쌍은 존재하지 않습니다. 따라서 메타데이터의 반환 결과는 각 기본 구현에 따라 다를 수 있습니다.

# 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},")

Output:

The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 07:46:47', stop='2026-09-01 07:46:49', size=4096>)])},
'version' : 2,

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

실행 기간 보기

IBM Quantum Compute Service에서 실행된 SamplerV2 작업의 결과에는 메타데이터에 실행 시간 정보가 포함되어 있습니다. 이 타이밍 정보를 활용하면 특정 샷이 QPU에서 실행된 시점에 대한 타임스탬프의 상한과 하한을 설정할 수 있습니다. 샷은 ExecutionSpan ‘오브젝트’로 묶이며, 각 오브젝트는 시작 시간, 종료 시간, 그리고 해당 기간 동안 수집된 샷에 대한 세부 정보를 나타냅니다.

실행 스팬은 ExecutionSpan.mask 메서드를 제공함으로써 해당 기간 동안 어떤 데이터가 실행되었는지 지정합니다. 이 메서드는 주어진 기본 통합 블록( PUB ) 인덱스에 대해, 해당 창 동안 실행된 모든 True 샷에 대해 참(true)인 부울 마스크를 반환합니다. PUB은 샘플러 실행 호출에 전달된 순서대로 인덱싱됩니다. 예를 들어, PUB 의 모양이 (2, 3) 이고 4번의 샷으로 실행되었다면, 마스크의 모양은 입니다 (2, 3, 4). 자세한 내용은 execution_span API 페이지를 참조하십시오.

실행 기간 정보를 확인하려면, 객체 ExecutionSpans 형태로 SamplerV2반환되는 결과의 메타데이터를 검토하십시오. 이 객체는 와 같은 하위 ExecutionSpan 클래스의 인스턴스를 SliceSpan포함하는 리스트와 유사한 컨테이너입니다.

예:

# Define two circuits, each with one parameter with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()


pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)

params = np.random.uniform(size=(2, 3)).T

sampler_pub = (transpiled_circuit, params)

# Instantiate the new Estimator object, then run the transpiled circuit
# using the set of parameters and observables.

job = sampler.run([sampler_pub], shots=4)

result = job.result()
spans = job.result().metadata["execution"]["execution_spans"]
print(spans)

Output:

ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 08:11:01', stop='2026-09-01 08:11:02', size=24>)])
from qiskit.primitives import BitArray

# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)

# Decide whether the 0th shot of parameter set (1, 2) occurred in this span.
in_this_span = mask[2, 1, 0]

# Create a new bit array containing only the PUB-1 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)

실행 범위를 필터링하여 인덱스를 기준으로 선택한 특정 PUB에 대한 정보를 포함할 수 있습니다:

# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])

Output:

ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 08:11:01', stop='2026-09-01 08:11:02', size=24>)])

실행 스팬 컬렉션에 대한 전체 정보를 확인합니다:

print("Number of execution spans:", len(spans))
print("  Start of the first span:", spans.start)
print("     End of the last span:", spans.stop)
print("       Total duration (s):", spans.duration)

Output:

Number of execution spans: 1
  Start of the first span: 2026-09-01 08:11:01.798871
     End of the last span: 2026-09-01 08:11:02.894134
       Total duration (s): 1.095263

특정 스팬을 추출하여 확인합니다:

spans.sort()
print(" Start of first span:", spans[0].start)
print("   End of first span:", spans[0].stop)
print("#shots in first span:", spans[0].size)

Output:

 Start of first span: 2026-09-01 08:11:01.798871
   End of first span: 2026-09-01 08:11:02.894134
#shots in first span: 24
Note

서로 다른 실행 기간으로 지정된 시간 창들이 겹칠 수 있습니다. 이는 QPU가 한 번에 여러 작업을 수행했기 때문이 아니라, 양자 실행과 동시에 발생할 수 있는 특정 고전적 처리 과정에서 비롯된 현상입니다. 이 보장은 참조된 데이터가 보고된 실행 기간 내에 확실히 발생했다는 점을 보장하는 것이지, 시간 창(time window)의 범위가 가능한 한 좁다는 것을 보장하는 것은 아닙니다.

이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.