Migrare alle primitive V2 di Qiskit Runtime
Le primitive originali (denominate primitive V1), V1 Sampler e V1 Estimator, sono state deprecate in qiskit-ibm-runtime 0.23.
Il loro supporto è stato rimosso il 15 agosto 2024.
Con la deprecazione delle primitive V1, tutto il codice dovrebbe essere migrato per utilizzare le interfacce V2. Questa guida descrive cosa è cambiato nelle primitive V2 di Qiskit Runtime (disponibili con qiskit-ibm-runtime 0.21.0) e perché, descrive in dettaglio ogni nuova primitiva e fornisce esempi per aiutarti a migrare il codice dall'utilizzo delle primitive legacy alle primitive V2. Gli esempi nella guida utilizzano tutti le primitive di Qiskit Runtime, ma in generale le stesse modifiche si applicano alle altre implementazioni di primitive. Le funzioni esclusive di Qiskit Runtime, come la mitigazione degli errori, rimangono esclusive di Qiskit Runtime.
Per informazioni sulle modifiche alle primitive di riferimento di Qiskit (ora denominate primitive statevector), consulta la sezione qiskit.primitives nella pagina delle modifiche delle funzionalità di Qiskit 1.0. Consulta StatevectorSampler e StatevectorEstimator per le implementazioni di riferimento delle primitive V2.
Panoramica
La versione 2 delle primitive è introdotta con una nuova classe base per Sampler ed Estimator (BaseSamplerV2 e BaseEstimatorV2), insieme a nuovi tipi per i loro input e output.
La nuova interfaccia ti permette di specificare un singolo circuito e più osservabili (se si utilizza Estimator) e insiemi di valori di parametri per quel circuito, in modo che le scansioni sugli insiemi di valori di parametri e sugli osservabili possano essere specificate in modo efficiente. In precedenza, dovevi specificare lo stesso circuito più volte per corrispondere alla dimensione dei dati da combinare. Inoltre, mentre puoi ancora utilizzare resilience_level (se si utilizza Estimator) come controllo semplice, le primitive V2 ti offrono la flessibilità di attivare o disattivare singoli metodi di mitigazione/soppressione degli errori per personalizzarli in base alle tue esigenze.
Per ridurre il tempo totale di esecuzione dei job, le primitive V2 accettano solo circuito e osservabili che utilizzano istruzioni supportate dalla QPU (quantum processing unit) di destinazione. Tali circuito e osservabili sono denominati circuito e osservabili ISA (instruction set architecture). Le primitive V2 non eseguono operazioni di layout, routing e traduzione. Consulta la documentazione sulla transpilazione per istruzioni su come trasformare i circuiti.
Sampler V2 è semplificato per concentrarsi sul suo compito principale di campionare il registro di output dall'esecuzione di circuiti quantistici. Restituisce i campioni, il cui tipo è definito dal programma, senza pesi. I dati di output sono anche separati in base ai nomi dei registri di output definiti dal programma. Questa modifica consente il supporto futuro per i circuiti con flusso di controllo classico.
Consulta il riferimento API di EstimatorV2 e il riferimento API di SamplerV2 per i dettagli completi.
Modifiche principali
Import
Per la compatibilità con le versioni precedenti, devi importare esplicitamente le primitive V2. Specificare import <primitiva>V2 as <primitiva> non è obbligatorio, ma facilita la transizione del codice a V2.
Dopo che le primitive V1 non saranno più supportate, import <primitiva> importerà la versione V2 della primitiva specificata.
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import Sampler
Input and output
Input
Sia SamplerV2 che EstimatorV2 accettano uno o più primitive unified blocs (PUB) come input. Ogni PUB è una tupla che contiene un circuito e i dati trasmessi a quel circuito, che possono essere più osservabili e parametri. Ogni PUB restituisce un risultato.
- Sampler V2 PUB format: (
<circuit>,<parameter values>,<shots>), where<parameter values>and<shots>are optional. - Estimator V2 PUB format: (
<circuit>,<observables>,<parameter values>,<precision>), where<parameter values>and<precision>are optional. Numpy broadcasting rules are used when combining observables and parameter values.
Additionally, the following changes have been made:
- Estimator V2 ha acquisito un argomento
precisionnel metodorun()che specifica la precisione desiderata delle stime del valore atteso. - Sampler V2 ha l'argomento
shotsnel suo metodorun().
Examples
Estimator V2 example that uses precision in run():
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
Sampler V2 example that uses shots in run():
# Sample two circuits at 128 shots each.
sampler.run([circuit1, circuit2], shots=128)
# Sample two circuits at different amounts of shots.
# The "None"s are necessary as placeholders
# for the lack of parameter values in this example.
sampler.run([
(circuit1, None, 123),
(circuit2, None, 456),
])
Output
L’output è ora nel formato PubResult. Un PubResult contiene i dati e i metadati risultanti dall’esecuzione di un singolo PUB.
-
Estimator V2 continua a restituire valori attesi.
-
La porzione
datadi un PubResult di Estimator V2 contiene sia i valori attesi che gli errori standard (stds). V1 restituiva la varianza nei metadati. -
Sampler V2 restituisce misurazioni per singolo shot sotto forma di bitstring, al posto delle distribuzioni di quasi-probabilità dell’interfaccia V1. Le bitstring mostrano i risultati delle misurazioni, preservando l’ordine degli shot in cui sono stati misurati.
-
Sampler V2 ha metodi di comodità come
get_counts()per facilitare la migrazione. -
Gli oggetti risultato di Sampler V2 organizzano i dati in base ai nomi dei registri classici dei circuiti di input, per compatibilità con i circuiti dinamici. Per impostazione predefinita, il nome del registro classico è
meas, come mostrato nell’esempio seguente. Quando definisci il tuo circuito, se crei uno o più registri classici con un nome non predefinito, usa quel nome per ottenere i risultati. Puoi trovare il nome del registro classico eseguendo<circuit_name>.cregs. Ad esempio,qc.cregs.# Define a quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()┌───┐ ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
Esempi di Estimator (input e output)
- 1 circuit, 4 osservabili
- 1 circuit, 4 observables, 2 parameter sets
- 2 circuit, 2 osservabili
# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
Sampler examples (input and output)
- 1 circuit, 3 parameter sets
- 2 circuit, 1 insieme di parametri
- Convert V2 output to V1 format
# Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists
# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
The V1 output format was a dictionary of bitstrings (as an int) as the key and quasi-probabilities as the value for each circuit. The V2 format has the same key (but as a string) and counts as the value. To convert the V2 format to V1, divide the counts by the number of shots, where the number of shots selected is described in the Sampler options guide.
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
Esempio che utilizza registri di output diversi
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)
circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")
Opzioni
Le opzioni vengono specificate in modo diverso nelle primitive V2 nei seguenti modi:
SamplerV2eEstimatorV2ora hanno classi di opzioni separate. Puoi vedere le opzioni disponibili e aggiornare i valori delle opzioni durante o dopo l'inizializzazione della primitiva.- Invece del metodo
set_options(), le opzioni delle primitive V2 dispongono del metodoupdate()che applica le modifiche all'attributooptions. - Se non specifichi un valore per un'opzione, le viene assegnato un valore speciale
Unsete vengono utilizzati i valori predefiniti del server. - Per le primitive V2, l'attributo
optionsè del tipo Pythondataclass. Puoi utilizzare il metodo integratoasdictper convertirlo in un dizionario.
Consulta il riferimento API per l'elenco delle opzioni disponibili.
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})
# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})
# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True
# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)
Error mitigation and suppression
-
Because Sampler V2 returns samples without postprocessing, it does not support resilience levels.
-
Sampler V2 does not support
optimization_level. -
Estimator V2 will drop support for
optimization_levelon or around 30 September 2024. -
Estimator V2 non supporta il livello di resilienza 3. Questo perché il livello 3 in V1 Estimator utilizza la Probabilistic Error Cancellation (PEC), che è dimostrata fornire risultati non distorti al costo di un tempo di elaborazione esponenziale. Il livello 3 è stato rimosso per attirare l'attenzione su questo compromesso. Tuttavia, puoi ancora utilizzare PEC come metodo di mitigazione degli errori specificando l'opzione
pec_mitigation. -
Estimator V2 supporta
resilience_level0-2, come descritto nella tabella seguente. Queste opzioni sono più avanzate rispetto alle loro controparti V1. Puoi anche attivare/disattivare esplicitamente singoli metodi di mitigazione/soppressione degli errori.Level 1 Level 2 Measurement twirling Measurement twirling Readout error mitigation Readout error mitigation ZNE
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend)
# Set resilience_level to 0
estimator.options.resilience_level = 0
# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
Transpilation
Le primitive V2 supportano solo i circuiti conformi all'Instruction Set Architecture (ISA) di un particolare backend. Poiché le primitive non eseguono operazioni di layout, routing e traduzione, le corrispondenti opzioni di transpilazione di V1 non sono supportate.
Job status
Le primitive V2 hanno una nuova classe RuntimeJobV2, che eredita da BasePrimitiveJob. Il metodo status() di questa nuova classe restituisce una stringa invece di un enum JobStatus di Qiskit. Consulta il riferimento API RuntimeJobV2 per i dettagli.
- V2 primitives
- V1 primitives
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
Steps to migrate to Estimator V2
-
Replace
from qiskit_ibm_runtime import Estimatorwithfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Rimuovi eventuali istruzioni
from qiskit_ibm_runtime import Options, poiché la classeOptionsnon viene utilizzata dalle primitive V2. Puoi invece passare le opzioni come dizionario durante l'inizializzazione della classeEstimatorV2(ad esempioestimator = Estimator(backend, options={“dynamical_decoupling”: {“enable”: True}})), oppure impostarle dopo l'inizializzazione:estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
Review all the supported options and make updates accordingly.
-
Raggruppa ogni circuito che vuoi eseguire con gli osservabili e i valori dei parametri che vuoi applicare al circuito in una tupla (un PUB). Ad esempio, usa
(circuit1, observable1, parameter_set1)se vuoi eseguirecircuit1conobservable1eparameter_set1. -
Potrebbe essere necessario ridimensionare gli array di osservabili o set di parametri se vuoi applicare il loro prodotto esterno. Ad esempio, un array di osservabili di forma (4, 1) e un array di set di parametri di forma (1, 6) produrrà un risultato di (4, 6) valori attesi. Consulta le regole di broadcasting di Numpy per maggiori dettagli.
-
Puoi facoltativamente specificare la precisione desiderata per quel PUB specifico.
-
Aggiorna il metodo
run()di Estimator per passare la lista di PUB. Ad esempio,run([(circuit1, observable1, parameter_set1)]). Puoi facoltativamente specificare unaprecisionqui, che si applicherà a tutti i PUB. -
I risultati dei job di Estimator V2 sono raggruppati per PUB. Puoi vedere il valore atteso e l'errore standard per ogni PUB indicizzandolo. Ad esempio:
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Estimator full examples
Run a single experiment
Use Estimator to determine the expectation value of a single circuit-observable pair.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
Run multiple experiments in a single job
Use Estimator to determine the expectation values of multiple circuit-observable pairs.
- Estimator V2
- Estimator (V1)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run(isa_circuits, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
Run parameterized circuits
Usa Estimator per eseguire più esperimenti in un singolo job, sfruttando i valori dei parametri per aumentare il riutilizzo dei circuiti. Nell'esempio seguente, nota che i passaggi 1 e 2 sono gli stessi per V1 e V2.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import EstimatorV2 as Estimator
# Step 3: Execute using Qiskit primitives.
# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))
estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
Use sessions and advanced options
Explore sessions and advanced options to optimize circuit performance on QPUs.
The following code block will return an error for users on the Open Plan because it uses sessions. Workloads on the Open Plan can run only in job mode or batch mode.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator()
estimator.options.resilience_level = 1
job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
job = estimator.run(isa_circuit, isa_observable)
another_job = estimator.run(another_isa_circuit, another_isa_observable)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
Steps to migrate to Sampler V2
- Replace
from qiskit_ibm_runtime import Samplerwithfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Rimuovi eventuali istruzioni
from qiskit_ibm_runtime import Options, poiché la classeOptionsnon viene utilizzata dalle primitive V2. Puoi invece passare le opzioni come dizionario durante l'inizializzazione della classeSamplerV2(ad esempiosampler = Sampler(backend, options={“default_shots”: 1024})), oppure impostarle dopo l'inizializzazione:sampler = Sampler(backend)
sampler.options.default_shots = 1024 - Review all the supported options and make updates accordingly.
- Raggruppa ogni circuito che vuoi eseguire con gli osservabili e i valori dei parametri che vuoi applicare al circuito in una tupla (un PUB). Ad esempio, usa
(circuit1, parameter_set1)se vuoi eseguirecircuit1conparameter_set1. Puoi facoltativamente specificare gli shot desiderati per quel PUB specifico. - Aggiorna il metodo
run()di Sampler per passare la lista di PUB. Ad esempio,run([(circuit1, parameter_set1)]). Puoi facoltativamente specificareshotsqui, che si applicherà a tutti i PUB. - I risultati dei job di Sampler V2 sono raggruppati per PUB. Puoi vedere i dati di output per ogni PUB indicizzandolo. Mentre Sampler V2 restituisce campioni non pesati, la classe risultato ha un metodo di comodità per ottenere i conteggi. Ad esempio:
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
Per ottenere i risultati è necessario il nome del registro classico. Per impostazione predefinita, è denominato meas quando si usa measure_all(). Quando si definisce il circuito, se si creano uno o più registri classici con un nome non predefinito, utilizzare quel nome per ottenere i risultati. È possibile trovare il nome del registro classico eseguendo <circuit_name>.cregs. Ad esempio, qc.cregs.
Sampler full examples
Run a single experiment
Use Sampler to determine the counts or quasi-probability distribution of a single circuit.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Run multiple experiments in a single job
Use Sampler to determine the counts or quasi-probability distributions of multiple circuits in one job.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()
for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
Run parameterized circuits
Run several experiments in a single job, leveraging parameter values to increase circuit reusability.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 5
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Use sessions and advanced options
Explore sessions and advanced options to optimize circuit performance on QPUs.
The following code block will return an error for users on the Open Plan because it uses sessions. Workloads on the Open Plan can run only in job mode or batch mode.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
service = QiskitRuntimeService()
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")
# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
Passi successivi
- Scopri di più su come impostare le opzioni nelle guide Opzioni di Sampler, Opzioni di Estimator e Opzioni dell'Executor.
- Scopri di più sui dettagli di Input e output delle primitive.
- Sperimenta con il tutorial sulla disuguaglianza CHSH.