Vai al contenuto principale

Migrare alle primitive V2 di Qiskit Runtime

avviso

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.

avviso

Dopo che le primitive V1 non saranno più supportate, import <primitiva> importerà la versione V2 della primitiva specificata.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as 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 precision nel metodo run() che specifica la precisione desiderata delle stime del valore atteso.
  • Sampler V2 ha l'argomento shots nel suo metodo run().
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 data di 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)

# 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

Sampler examples (input and output)

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

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:

  • SamplerV2 e EstimatorV2 ora 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 metodo update() che applica le modifiche all'attributo options.
  • Se non specifichi un valore per un'opzione, le viene assegnato un valore speciale Unset e vengono utilizzati i valori predefiniti del server.
  • Per le primitive V2, l'attributo options è del tipo Python dataclass. Puoi utilizzare il metodo integrato asdict per convertirlo in un dizionario.

Consulta il riferimento API per l'elenco delle opzioni disponibili.

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

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_level on 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_level 0-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 1Level 2
    Measurement twirlingMeasurement twirling
    Readout error mitigationReadout error mitigation
    ZNE
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 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}")

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.

job = estimator.run(...)

# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")

Steps to migrate to Estimator V2

  1. Replace from qiskit_ibm_runtime import Estimator with from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. Rimuovi eventuali istruzioni from qiskit_ibm_runtime import Options, poiché la classe Options non viene utilizzata dalle primitive V2. Puoi invece passare le opzioni come dizionario durante l'inizializzazione della classe EstimatorV2 (ad esempio estimator = Estimator(backend, options={“dynamical_decoupling”: {“enable”: True}})), oppure impostarle dopo l'inizializzazione:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. Review all the supported options and make updates accordingly.

  4. 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 eseguire circuit1 con observable1 e parameter_set1.

  5. 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.

  6. Puoi facoltativamente specificare la precisione desiderata per quel PUB specifico.

  7. Aggiorna il metodo run() di Estimator per passare la lista di PUB. Ad esempio, run([(circuit1, observable1, parameter_set1)]). Puoi facoltativamente specificare una precision qui, che si applicherà a tutti i PUB.

  8. 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.

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

Run multiple experiments in a single job

Use Estimator to determine the expectation values of multiple circuit-observable pairs.

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

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.

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

Use sessions and advanced options

Explore sessions and advanced options to optimize circuit performance on QPUs.

attenzione

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.

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

Steps to migrate to Sampler V2

  1. Replace from qiskit_ibm_runtime import Sampler with from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. Rimuovi eventuali istruzioni from qiskit_ibm_runtime import Options, poiché la classe Options non viene utilizzata dalle primitive V2. Puoi invece passare le opzioni come dizionario durante l'inizializzazione della classe SamplerV2 (ad esempio sampler = Sampler(backend, options={“default_shots”: 1024})), oppure impostarle dopo l'inizializzazione:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. Review all the supported options and make updates accordingly.
  4. 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 eseguire circuit1 con parameter_set1. Puoi facoltativamente specificare gli shot desiderati per quel PUB specifico.
  5. Aggiorna il metodo run() di Sampler per passare la lista di PUB. Ad esempio, run([(circuit1, parameter_set1)]). Puoi facoltativamente specificare shots qui, che si applicherà a tutti i PUB.
  6. 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()}")
nota

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.

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

Run multiple experiments in a single job

Use Sampler to determine the counts or quasi-probability distributions of multiple circuits in one job.

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

Run parameterized circuits

Run several experiments in a single job, leveraging parameter values to increase circuit reusability.

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

Use sessions and advanced options

Explore sessions and advanced options to optimize circuit performance on QPUs.

attenzione

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.

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

Passi successivi

Raccomandazioni