Intrări și ieșiri Sampler
Versiuni de pachete
Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Această pagină oferă o prezentare generală a intrărilor și ieșirilor primitivului Sampler Qiskit Runtime, care execută sarcini de lucru pe resursele de calcul IBM Quantum®. Sampler îți permite să definești eficient sarcini de lucru vectorizate folosind o structură de date cunoscută sub numele de Primitive Unified Bloc (PUB). Acestea sunt utilizate ca intrări pentru metoda run() a primitivului Sampler, care execută sarcina de lucru definită ca un job. Apoi, după ce job-ul s-a finalizat, rezultatele sunt returnate într-un format care depinde atât de PUB-urile utilizate, cât și de opțiunile runtime specificate din primitiv.
Intrări
Fiecare PUB are formatul:
(<circuit unic>, <una sau mai multe valori opționale de parametri>, <shots opționale>),
Pot exista mai multe elemente valori de parametri, fiecare element putând fi fie o matrice, fie un singur parametru, în funcție de circuitul ales. În plus, intrarea trebuie să conțină măsurători.
Pentru primitivul Sampler, un PUB poate conține cel mult trei valori:
- Un singur
QuantumCircuit, care poate conține unul sau mai multe obiecteParameterNotă: Aceste circuite ar trebui să includă și instrucțiuni de măsurare pentru fiecare dintre qubiții de eșantionat. - O colecție de valori de parametri pentru a lega circuitul cu (necesară numai dacă sunt utilizate obiecte
Parametercare trebuie legate la runtime) - (Opțional) un număr de shots pentru măsurarea circuitului
Codul următor demonstrează un set exemplificativ de intrări vectorizate pentru primitivul Sampler și le execută pe un backend IBM® ca un singur obiect 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,
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()
Ieșiri
După ce unul sau mai multe PUB-uri sunt trimise unui QPU pentru execuție și un job se finalizează cu succes, datele sunt returnate ca obiect container PrimitiveResult accesat prin apelarea metodei RuntimeJobV2.result(). PrimitiveResult conține o listă iterabilă de obiecte SamplerPubResult care conțin rezultatele execuției pentru fiecare PUB. Aceste date sunt eșantioane ale ieșirii circuitului.
Fiecare element al acestei liste corespunde unui PUB trimis metodei run() a primitivului (de exemplu, un job trimis cu 20 de PUB-uri va returna un obiect PrimitiveResult care conține o listă de 20 de obiecte SamplerPubResult, câte unul corespunzând fiecărui PUB).
Fiecare obiect SamplerPubResult posedă atât un atribut data, cât și un atribut metadata.
- Atributul
dataeste unDataBinpersonalizat care conține valorile reale ale măsurătorilor, deviatiile standard și altele. Containerele de date sunt obiecte de tip dicționar care conțin câte unBitArrayperClassicalRegisterdin circuit. - Clasa
BitArrayeste un container pentru datele de shot ordonate. Stochează șirurile de biți eșantionate ca octeți într-o matrice bidimensională. Axa din stânga acestei matrice parcurge shot-urile ordonate, în timp ce axa din dreapta parcurge octeții. - Atributul
metadataconține informații despre opțiunile runtime utilizate (explicate mai târziu în secțiunea Metadate rezultate a acestei pagini).
Următoarea este o schiță vizuală a structurii de date 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
├── ...
├── ...
└── ...
Pe scurt, un singur job returnează un obiect PrimitiveResult și conține o listă de unul sau mai multe obiecte SamplerPubResult. Aceste obiecte SamplerPubResult stochează apoi datele de măsurare pentru fiecare PUB trimis job-ului.
Ca prim exemplu, să examinăm următorul circuit de zece qubiți:
# 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:
[[ 0 0]
[ 3 255]
[ 0 0]
...
[ 3 255]
[ 2 255]
[ 3 255]]
Uneori poate fi convenabil să converteți din formatul de octeți din BitArray la șiruri de biți. Metoda get_count returnează un dicționar care mapează șirurile de biți la numărul de ori când au apărut.
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'0000000000': 1649, '1111111111': 1344, '1111111000': 26, '1101111111': 40, '1111110000': 20, '0010000000': 32, '1000000000': 67, '1111110110': 4, '0000011110': 4, '0000000001': 78, '0010100000': 1, '1100000000': 37, '1111111110': 126, '1111110111': 35, '1111011111': 32, '0011111000': 1, '1011110111': 1, '0000011111': 48, '1111000000': 14, '0110000000': 1, '1110111110': 2, '1110011111': 4, '1111100000': 19, '1101111000': 1, '1111111011': 8, '0001011111': 3, '1110000000': 31, '0000000111': 25, '1110000001': 3, '0011111111': 24, '0000100000': 7, '1111111101': 30, '1111101111': 16, '0111111111': 37, '0000011101': 4, '0101111111': 4, '1011111110': 2, '0000000010': 17, '1011111111': 20, '0000100111': 1, '0010000111': 1, '1011010000': 1, '1101101111': 2, '1011110000': 1, '1000000001': 4, '0000001000': 23, '0011111110': 8, '1111111001': 1, '1100111111': 2, '0000011000': 2, '0001111110': 2, '0000111111': 20, '0001111111': 33, '1110111111': 11, '1010000000': 3, '0111011111': 2, '0000000100': 2, '0000000110': 2, '0000001111': 22, '0111101111': 1, '0000010111': 1, '0000000011': 15, '0001000010': 1, '1111111100': 19, '1111101000': 1, '0000001110': 2, '1011110100': 1, '0001000000': 11, '1001111111': 2, '0100000000': 6, '1100000011': 2, '1000001110': 1, '1100001111': 1, '0000010000': 3, '1101111110': 5, '0001111101': 1, '0001110111': 1, '0011000000': 2, '0111101110': 1, '1100000001': 1, '1111000001': 1, '0000000101': 1, '1101110111': 2, '0011111011': 1, '0000111110': 1, '1111101110': 3, '1111001000': 1, '1011111100': 1, '1111110101': 2, '1101001111': 1, '1111011110': 3, '1000011111': 1, '0000001001': 2, '1111010000': 1, '1110100010': 1, '1111110001': 2, '1101110000': 2, '0000010100': 1, '0111111110': 2, '0001000001': 1, '1000010000': 1, '1111011100': 1, '0111111100': 1, '1011101111': 1, '0000111101': 1, '1100011111': 2, '1101100000': 1, '1111011011': 1, '0010011111': 1, '0000110111': 3, '1111100010': 1, '1110111101': 1, '0000111001': 1, '1111100001': 1, '0001111100': 1, '1110011110': 1, '1100000010': 1, '0011110000': 1, '0001100111': 1, '1111010111': 1, '0010000001': 1, '0010000011': 1, '1101000111': 1, '1011111101': 1, '0000001100': 1}
Când un circuit conține mai mult de un registru clasic, rezultatele sunt stocate în obiecte BitArray diferite. Exemplul următor modifică fragmentul anterior împărțind registrul clasic în două registre distincte:
# 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>)
Utilizarea obiectelor BitArray pentru post-procesare performantă
Deoarece matricele oferă în general o performanță mai bună în comparație cu dicționarele, este recomandabil să efectuezi orice post-procesare direct pe obiectele BitArray mai degrabă decât pe dicționarele de numărători. Clasa BitArray oferă o serie de metode pentru efectuarea unor operații comune de post-procesare:
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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 1 248]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 0]
[ 1 248]
[ 0 0]
[ 0 0]
[ 0 0]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.07470703125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.0244140625
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 0 0]
[ 3 240]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 0 0]]
Metadate rezultate
Pe lângă rezultatele execuției, atât obiectele PrimitiveResult, cât și SamplerPubResult conțin un atribut de metadate despre job-ul trimis. Metadatele care conțin informații pentru toate PUB-urile trimise (cum ar fi diferitele opțiuni runtime disponibile) pot fi găsite în PrimitiveResult.metatada, în timp ce metadatele specifice fiecărui PUB se găsesc în SamplerPubResult.metadata.
Metadatele rezultatelor Sampler includ de asemenea informații despre sincronizarea execuției numite interval de execuție.
În câmpul de metadate, implementările de primitive pot returna orice informații despre execuție care sunt relevante pentru ele, și nu există perechi cheie-valoare garantate de primitivul de bază. Astfel, metadatele returnate pot fi diferite în diferite implementări de 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-05-13 14:23:00', stop='2026-05-13 14:23:02', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
Vizualizarea intervalelor de execuție
Rezultatele job-urilor SamplerV2 executate în Qiskit Runtime conțin informații de sincronizare a execuției în metadatele lor.
Aceste informații de sincronizare pot fi utilizate pentru a plasa limite superioare și inferioare de timestamp pentru momentul în care au fost executate anumite shot-uri pe QPU.
Shot-urile sunt grupate în obiecte ExecutionSpan, fiecare indicând un timp de start, un timp de stop și o specificație a shot-urilor colectate în interval.
Un interval de execuție specifică ce date au fost executate în fereastra sa prin furnizarea unei metode ExecutionSpan.mask. Această metodă, dată orice index de Primitive Unified Block (PUB), returnează o mască booleană care este True pentru toate shot-urile executate în fereastra sa. PUB-urile sunt indexate după ordinea în care au fost date apelului Sampler run. Dacă, de exemplu, un PUB are forma (2, 3) și a fost rulat cu patru shot-uri, forma măștii este (2, 3, 4). Consultă pagina API execution_span pentru detalii complete.
Pentru a vizualiza informațiile despre intervalul de execuție, revizuiește metadatele rezultatului returnat de SamplerV2, care vin sub forma unui obiect ExecutionSpans. Acest obiect este un container de tip listă care conține instanțe ale subclaselor ExecutionSpan, cum ar fi SliceSpan.
Exemplu:
# 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)
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', 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)
Intervalele de execuție pot fi filtrate pentru a include informații referitoare la PUB-uri specifice, selectate după indici:
# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', size=24>)])
Vizualizează informații globale despre colecția de intervale de execuție:
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)
Number of execution spans: 1
Start of the first span: 2026-05-13 14:23:20.441518
End of the last span: 2026-05-13 14:23:21.564845
Total duration (s): 1.123327
Extrage și inspectează un anumit interval:
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)
Start of first span: 2026-05-13 14:23:20.441518
End of first span: 2026-05-13 14:23:21.564845
#shots in first span: 24
Este posibil ca ferestrele de timp specificate de intervale de execuție distincte să se suprapună. Aceasta nu se datorează faptului că un QPU efectua mai multe execuții simultan, ci este un artefact al unor procesări clasice care ar putea avea loc concurent cu execuția cuantică. Garanția oferită este că datele referențiate s-au produs cu certitudine în intervalul de execuție raportat, dar nu neapărat că limitele ferestrei de timp sunt cât mai stricte posibil.