Skip to main content
IBM Quantum Platform

Ingressi e uscite del campionatore

  • Il codice presente in questa pagina è stato sviluppato sulla base dei seguenti requisiti. Si consiglia di utilizzare queste versioni o quelle più recenti.

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

Questa pagina offre una panoramica degli input e degli output della primitiva Sampler qiskit-ibm-runtime , che esegue carichi di lavoro sul servizio di elaborazione IBM Quantum®. Sampler consente di definire in modo efficiente i carichi di lavoro vettorializzati utilizzando una struttura dati nota come Primitive Unified Bloc ( PUB ). Vengono utilizzati come input per il metodo run() della primitiva Sampler, che esegue il carico di lavoro definito come un’attività. Una volta completata l'operazione, i risultati vengono restituiti in un formato che dipende sia dai PUB utilizzati sia dalle opzioni di esecuzione specificate nella primitiva.


Input

Ogni file « PUB » ha il seguente formato:

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

Possono esserci più parameter values elementi e ciascuno di essi può essere un array o un singolo parametro, a seconda del circuito scelto. Inoltre, l'input deve contenere delle misure.

Per la primitiva Sampler, un oggetto di tipo « PUB » può contenere al massimo tre valori:

  • Un singolo circuito QuantumCircuit, che può contenere uno o più Parameter oggetti Nota: questi circuiti dovrebbero includere anche le istruzioni di misurazione per ciascuno dei qubit da campionare.
  • Una raccolta di valori dei parametri per l'assemblaggio del circuito con θk\theta_k (necessaria solo se vengono utilizzati Parameter oggetti che devono essere assemblati in fase di esecuzione)
  • (Facoltativo) un numero di misurazioni per misurare il circuito

Il codice seguente mostra un esempio di insieme di input vettorializzati per la Sampler primitiva e li esegue su un backend IBM® e come un unico RuntimeJobV2 oggetto.

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

Output

Dopo che uno o più PUB sono stati inviati a una QPU per l'esecuzione e un processo è stato completato con successo, i dati vengono restituiti sotto forma di un oggetto PrimitiveResult contenitore a cui si accede chiamando il RuntimeJobV2.result() metodo. L'oggetto PrimitiveResult contiene un elenco iterabile di SamplerPubResult oggetti che contengono i risultati dell'esecuzione per ciascun PUB. Questi dati sono campioni dell'uscita del circuito.

Ogni elemento di questo elenco corrisponde a un oggetto PUB inviato al metodo della run() primitiva (ad esempio, un lavoro inviato con 20 oggetti PUB restituirà un PrimitiveResult oggetto che contiene un elenco di 20 SamplerPubResult oggetti, uno per ogni oggetto PUB).

Ogni SamplerPubResult oggetto possiede sia un attributo data che un metadata attributo.

  • L'attributo data è un campo personalizzato DataBin che contiene i valori effettivi delle misurazioni, le deviazioni standard e così via. I contenitori di dati sono oggetti simili a dizionari che ne contengono uno BitArray per ClassicalRegister ogni elemento del circuito.
  • La BitArray classe è un contenitore per i dati relativi alle riprese ordinati. Memorizza le stringhe di bit campionate come byte all'interno di un array bidimensionale. L'asse più a sinistra di questa matrice copre le riprese ordinate, mentre quello più a destra copre i byte.
  • L'attributo metadata contiene informazioni sulle opzioni di esecuzione utilizzate (come spiegato più avanti nella sezione "Metadati dei risultati" di questa pagina).

Di seguito è riportata una rappresentazione grafica della struttura PrimitiveResult dei dati:

    └── 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
        ├── ...
        ├── ...
        └── ...

In parole povere, un singolo job restituisce un PrimitiveResult oggetto e contiene un elenco di uno o più SamplerPubResult oggetti. Questi SamplerPubResult oggetti memorizzano quindi i dati di misurazione relativi a ciascun « PUB » inviato al processo.

Come primo esempio, consideriamo il seguente circuito a dieci qubit:

# 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 255]
 [  0   0]
 [  0   1]
 ...
 [  3   0]
 [  0   0]
 [  3 254]]

A volte può essere utile convertire i dati dal formato byte in stringhe BitArray di bit. Il get_count metodo restituisce un dizionario che associa le stringhe di bit al numero di volte in cui sono apparse.

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

Output:

Counts: {'1111111111': 1346, '0000000000': 1754, '0000000001': 55, '1000000000': 56, '1111111110': 92, '0111111111': 23, '1011111111': 15, '0001111111': 23, '1111011011': 1, '1111111101': 45, '1111111011': 108, '1111110111': 32, '0100000000': 10, '0000000111': 21, '0011111111': 21, '1111110000': 26, '1101111111': 47, '1111011111': 23, '1111111010': 6, '1100000000': 45, '1111100000': 32, '1110000000': 21, '1111101111': 13, '0010000000': 14, '0000000011': 19, '0000000101': 2, '0000001110': 2, '0000100000': 4, '0000001111': 20, '1111111100': 22, '0000010000': 5, '1101110111': 4, '1011111101': 1, '0000000010': 15, '0000001000': 12, '1111110110': 7, '1111000000': 3, '0010000001': 1, '0111011111': 3, '1001111111': 3, '1101111011': 3, '0000011111': 16, '0000011110': 3, '0001111011': 1, '1011111011': 3, '1111110011': 4, '1111101011': 2, '0000000100': 6, '1110111111': 12, '1111111000': 17, '0000111111': 5, '0001111101': 2, '1101100000': 2, '1101110001': 1, '1000001111': 2, '1111101110': 1, '1110111101': 1, '1101111101': 2, '1110000100': 1, '0100011111': 1, '1110000010': 1, '0011111110': 2, '0111111110': 1, '1111110010': 1, '0111110111': 1, '0000000110': 1, '0101111111': 1, '1101011111': 1, '1111001111': 1, '1110011111': 1, '0011111000': 2, '1101111110': 3, '1110111110': 1, '0110000000': 2, '1110000111': 1, '0000010111': 3, '0001000000': 3, '0111101111': 1, '0000011100': 1, '1000000001': 1, '1111011010': 1, '0000001010': 1, '1111100111': 2, '1111100011': 2, '0000001101': 1, '0111001111': 1, '1111111001': 1, '1101111000': 1, '0111110000': 1, '1111000111': 1, '1010000000': 1, '0011110000': 1, '1100000001': 1, '1011001101': 1, '0000001100': 1, '1100111111': 1, '1110111011': 1, '1111011101': 1, '1000011111': 1, '1101111001': 1, '0101101111': 1, '0000011011': 1, '0000111011': 1, '0111111100': 1, '1011100000': 1, '0011111011': 1, '0000010010': 1, '1001111011': 1}

Quando un circuito contiene più di un registro classico, i risultati vengono memorizzati in oggetti BitArray diversi. L'esempio seguente modifica il frammento precedente suddividendo il registro classico in due registri distinti:

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

Utilizza BitArray gli oggetti per una post-elaborazione efficiente

Poiché gli array offrono generalmente prestazioni migliori rispetto ai dizionari, è consigliabile eseguire qualsiasi elaborazione successiva direttamente sugli BitArray oggetti piuttosto che sui dizionari dei conteggi. La BitArray classe offre una serie di metodi per eseguire alcune operazioni comuni di post-elaborazione:

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

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[  0   0]
 [  0   0]
 [  1 255]
 ...
 [  1 255]
 [  0   0]
 [  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:
[[0]
 [0]
 [7]
 ...
 [7]
 [0]
 [7]]

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

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

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


Metadati dei risultati

Oltre ai risultati dell'esecuzione, entrambi gli PrimitiveResult oggetti SamplerPubResult e contengono un attributo di metadati relativo al processo inviato. I metadati contenenti le informazioni relative a tutti i PUB inviati (come le varie opzioni di esecuzione disponibili) sono disponibili nel file PrimitiveResult.metatada, mentre i metadati specifici per ciascun PUB si trovano nel file SamplerPubResult.metadata.

I metadati dei risultati del Sampler includono anche informazioni sui tempi di esecuzione denominate «durata dell'esecuzione ».

Note

Nel campo dei metadati, le implementazioni delle primitive possono restituire qualsiasi informazione relativa all'esecuzione che ritengano pertinente, e non esistono coppie chiave-valore garantite dalla primitiva di base. Pertanto, i metadati restituiti potrebbero variare a seconda delle diverse implementazioni delle 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},")

Output:

The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:10', stop='2026-08-01 08:21:13', size=4096>)])},
'version' : 2,

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

Visualizza gli intervalli di esecuzione

I risultati delle operazioni SamplerV2 eseguite nel servizio di elaborazione di IBM Quantum contengono, nei propri metadati, informazioni sui tempi di esecuzione. Queste informazioni temporali possono essere utilizzate per stabilire i limiti superiore e inferiore relativi al momento in cui determinate operazioni sono state eseguite sulla QPU. Le riprese sono raggruppate in ExecutionSpan oggetti, ciascuno dei quali indica un’ora di inizio, un’ora di fine e una specifica delle riprese raccolte in quel lasso di tempo.

Un intervallo di esecuzione specifica quali dati sono stati elaborati durante la sua finestra, fornendo un ExecutionSpan.mask metodo. Questo metodo, dato un indice di un blocco unificato primitivo ( PUB ), restituisce una maschera booleana che è True vera per tutti gli scatti eseguiti durante la sua finestra. I PUB vengono indicizzati in base all'ordine in cui sono stati passati alla chiamata di esecuzione del Sampler. Se, ad esempio, un’immagine « PUB » ha forma (2, 3) ed è stata elaborata con quattro scatti, allora la forma della maschera è (2, 3, 4). Per ulteriori dettagli, consultare la pagina dell'API execution_span.

Per visualizzare le informazioni relative alla durata dell'esecuzione, consultare i metadati del risultato restituito da SamplerV2, che si presenta sotto forma di un ExecutionSpans oggetto. Questo oggetto è un contenitore simile a una lista che contiene istanze di sottoclassi di ExecutionSpan, come ad esempio SliceSpan.

Esempio:

# 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-08-01 08:21:37', stop='2026-08-01 08:21:38', 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)

È possibile filtrare gli intervalli di esecuzione per includere le informazioni relative a specifici PUB, selezionati in base ai loro indici:

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

Output:

ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', size=24>)])

Visualizza le informazioni generali relative all'insieme degli intervalli di esecuzione:

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-08-01 08:21:37.606114
     End of the last span: 2026-08-01 08:21:38.960352
       Total duration (s): 1.354238

Estrai e controlla un determinato intervallo:

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-08-01 08:21:37.606114
   End of first span: 2026-08-01 08:21:38.960352
#shots in first span: 24
Note

È possibile che le finestre temporali specificate da intervalli di esecuzione distinti si sovrappongano. Ciò non è dovuto al fatto che una QPU stesse eseguendo più operazioni contemporaneamente, bensì è un artefatto di determinati processi classici che potrebbero verificarsi in concomitanza con l'esecuzione quantistica. Si garantisce che i dati indicati si siano effettivamente verificati nell'intervallo di esecuzione riportato, ma non necessariamente che i limiti della finestra temporale siano il più ristretti possibile.

Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.