Pular para o conteúdo principal

Migrar para os primitivos V2 do Qiskit Runtime

aviso

Os primitivos originais (chamados de primitivos V1), V1 Sampler e V1 Estimator, foram descontinuados no qiskit-ibm-runtime 0.23. O suporte a eles foi removido em 15 de agosto de 2024.

Com a descontinuação dos primitivos V1, todo o código deve ser migrado para usar as interfaces V2. Este guia descreve o que mudou nos primitivos V2 do Qiskit Runtime (disponíveis a partir do qiskit-ibm-runtime 0.21.0) e o porquê, descreve cada novo primitivo em detalhes e fornece exemplos para ajudar você a migrar o código dos primitivos legados para os primitivos V2. Os exemplos deste guia usam os primitivos do Qiskit Runtime, mas, em geral, as mesmas mudanças se aplicam às outras implementações de primitivos. As funções exclusivas do Qiskit Runtime, como a mitigação de erros, continuam sendo exclusivas do Qiskit Runtime.

Para informações sobre as mudanças nos primitivos de referência do Qiskit (agora chamados de primitivos statevector), consulte a seção qiskit.primitives na página de mudanças de recursos do Qiskit 1.0. Veja StatevectorSampler e StatevectorEstimator para implementações de referência de primitivos V2.

Visão geral

A versão 2 dos primitivos é introduzida com uma nova classe base para Sampler e Estimator (BaseSamplerV2 e BaseEstimatorV2), juntamente com novos tipos para suas entradas e saídas.

A nova interface permite especificar um único Circuit e múltiplos observáveis (ao usar o Estimator) e conjuntos de valores de parâmetros para esse Circuit, de forma que varreduras sobre conjuntos de valores de parâmetros e observáveis possam ser especificadas de forma eficiente. Anteriormente, era necessário especificar o mesmo Circuit várias vezes para corresponder ao tamanho dos dados a serem combinados. Além disso, embora ainda seja possível usar resilience_level (ao usar o Estimator) como o controle simples, os primitivos V2 oferecem a flexibilidade de ativar ou desativar métodos individuais de mitigação/supressão de erros para personalizá-los conforme suas necessidades.

Para reduzir o tempo total de execução do job, os primitivos V2 aceitam apenas Circuits e observáveis que usam instruções suportadas pelo QPU (unidade de processamento quântico) alvo. Esses Circuits e observáveis são chamados de Circuits e observáveis ISA (instruction set architecture). Os primitivos V2 não realizam operações de layout, roteamento e tradução. Consulte a documentação de transpilação para instruções sobre como transformar Circuits.

O Sampler V2 foi simplificado para focar em sua tarefa principal de amostrar o registrador de saída a partir da execução de Circuits quânticos. Ele retorna as amostras, cujo tipo é definido pelo programa, sem pesos. Os dados de saída também são separados pelos nomes dos registradores de saída definidos pelo programa. Essa mudança viabiliza o suporte futuro a Circuits com fluxo de controle clássico.

Consulte a referência da API EstimatorV2 e a referência da API SamplerV2 para detalhes completos.

Principais mudanças

Importação

Por compatibilidade retroativa, você deve importar explicitamente os primitivos V2. Especificar import <primitive>V2 as <primitive> não é obrigatório, mas facilita a transição do código para V2.

aviso

Após os primitivos V1 não serem mais suportados, import <primitive> importará a versão V2 do primitivo especificado.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler

Entrada e saída

Entrada

Tanto o SamplerV2 quanto o EstimatorV2 recebem um ou mais primitive unified blocs (PUBs) como entrada. Cada PUB é uma tupla que contém um Circuit e os dados transmitidos a esse Circuit, que podem ser múltiplos observáveis e parâmetros. Cada PUB retorna um resultado.

  • Formato PUB do Sampler V2: (<circuit>, <parameter values>, <shots>), onde <parameter values> e <shots> são opcionais.
  • Formato PUB do Estimator V2: (<circuit>, <observables>, <parameter values>, <precision>), onde <parameter values> e <precision> são opcionais. As regras de broadcasting do Numpy são usadas ao combinar observáveis e valores de parâmetros.

Além disso, as seguintes mudanças foram feitas:

  • O Estimator V2 ganhou um argumento precision no método run() que especifica a precisão alvo das estimativas de valor esperado.
  • O Sampler V2 possui o argumento shots em seu método run().
Exemplos

Exemplo do Estimator V2 que usa precisão no run():

# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)

Exemplo do Sampler V2 que usa shots no 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),
])

Saída

A saída agora está no formato PubResult. Um PubResult contém os dados e metadados resultantes da execução de um único PUB.

  • O Estimator V2 continua retornando valores esperados.

  • A parte data de um PubResult do Estimator V2 contém tanto valores esperados quanto erros padrão (stds). A V1 retornava a variância nos metadados.

  • O Sampler V2 retorna medições por shot na forma de bitstrings, em vez das distribuições de quase-probabilidade da interface V1. As bitstrings mostram os resultados das medições, preservando a ordem dos shots em que foram medidos.

  • O Sampler V2 possui métodos de conveniência como get_counts() para auxiliar na migração.

  • Os objetos de resultado do Sampler V2 organizam os dados em termos dos nomes dos registradores clássicos dos Circuits de entrada, para compatibilidade com Circuits dinâmicos. Por padrão, o nome do registrador clássico é meas, como mostrado no exemplo a seguir. Ao definir seu Circuit, se você criar um ou mais registradores clássicos com um nome não padrão, use esse nome para obter os resultados. Você pode encontrar o nome do registrador clássico executando <circuit_name>.cregs. Por exemplo, 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

Exemplos de Estimator (entrada e saída)

# 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

Exemplos de Sampler (entrada e saída)

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

Exemplo que usa diferentes registradores de saída

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

Opções

As opções são especificadas de forma diferente nos primitivos V2 nestas formas:

  • SamplerV2 e EstimatorV2 agora têm classes de opções separadas. Você pode ver as opções disponíveis e atualizar os valores das opções durante ou após a inicialização do primitivo.
  • Em vez do método set_options(), as opções dos primitivos V2 possuem o método update() que aplica mudanças ao atributo options.
  • Se você não especificar um valor para uma opção, ela receberá um valor especial de Unset e os padrões do servidor serão usados.
  • Para os primitivos V2, o atributo options é do tipo Python dataclass. Você pode usar o método integrado asdict para convertê-lo em um dicionário.

Consulte a referência da API para a lista de opções disponíveis.

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

Mitigação e supressão de erros

  • Como o Sampler V2 retorna amostras sem pós-processamento, ele não suporta níveis de resiliência.

  • O Sampler V2 não suporta optimization_level.

  • O Estimator V2 deixará de suportar optimization_level em ou por volta de 30 de setembro de 2024.

  • O Estimator V2 não suporta nível de resiliência 3. Isso ocorre porque o nível de resiliência 3 no Estimator V1 usa Probabilistic Error Cancellation (PEC), que é comprovadamente capaz de fornecer resultados não tendenciosos ao custo de tempo de processamento exponencial. O nível 3 foi removido para chamar atenção a essa troca. No entanto, você ainda pode usar PEC como método de mitigação de erros especificando a opção pec_mitigation.

  • O Estimator V2 suporta resilience_level de 0 a 2, conforme descrito na tabela a seguir. Essas opções são mais avançadas do que suas contrapartes V1. Você também pode ativar/desativar explicitamente métodos individuais de mitigação/supressão de erros.

    Nível 1Nível 2
    Measurement twirlingMeasurement twirling
    Mitigação de erro de leituraMitigação de erro de leitura
    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}")

Transpilação

Os primitivos V2 suportam apenas Circuits que aderem à Instruction Set Architecture (ISA) de um Backend específico. Como os primitivos não realizam operações de layout, roteamento e tradução, as opções de transpilação correspondentes da V1 não são suportadas.

Status do job

Os primitivos V2 possuem uma nova classe RuntimeJobV2, que herda de BasePrimitiveJob. O método status() desta nova classe retorna uma string em vez de um enum JobStatus do Qiskit. Consulte a referência da API RuntimeJobV2 para detalhes.

job = estimator.run(...)

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

Passos para migrar para o Estimator V2

  1. Substitua from qiskit_ibm_runtime import Estimator por from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. Remova quaisquer declarações from qiskit_ibm_runtime import Options, pois a classe Options não é usada pelos primitivos V2. Você pode, em vez disso, passar opções como um dicionário ao inicializar a classe EstimatorV2 (por exemplo, estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), ou defini-las após a inicialização:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. Revise todas as opções suportadas e faça as atualizações necessárias.

  4. Agrupe cada Circuit que você deseja executar com os observáveis e valores de parâmetros que deseja aplicar ao Circuit em uma tupla (um PUB). Por exemplo, use (circuit1, observable1, parameter_set1) se quiser executar circuit1 com observable1 e parameter_set1.

  5. Pode ser necessário remodelar seus arrays de observáveis ou conjuntos de parâmetros se quiser aplicar o produto externo deles. Por exemplo, um array de observáveis de forma (4, 1) e um array de conjuntos de parâmetros de forma (1, 6) resultará em (4, 6) valores esperados. Consulte as regras de broadcasting do Numpy para mais detalhes.

  6. Você pode opcionalmente especificar a precisão que deseja para esse PUB específico.

  7. Atualize o método run() do estimator para passar a lista de PUBs. Por exemplo, run([(circuit1, observable1, parameter_set1)]). Você pode opcionalmente especificar precision aqui, que se aplicaria a todos os PUBs.

  8. Os resultados de jobs do Estimator V2 são agrupados por PUBs. Você pode ver o valor esperado e o erro padrão de cada PUB indexando-o. Por exemplo:

pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")

Exemplos completos de Estimator

Executar um único experimento

Use o Estimator para determinar o valor esperado de um par Circuit-observável único.

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

Executar múltiplos experimentos em um único job

Use o Estimator para determinar os valores esperados de múltiplos pares Circuit-observável.

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

Executar Circuits parametrizados

Use o Estimator para executar múltiplos experimentos em um único job, aproveitando valores de parâmetros para aumentar a reutilização do Circuit. No exemplo a seguir, observe que os passos 1 e 2 são os mesmos para 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}")

Usar sessões e opções avançadas

Explore sessões e opções avançadas para otimizar o desempenho do Circuit nos QPUs.

cuidado

O bloco de código a seguir retornará um erro para usuários no Plano Aberto porque usa sessões. As cargas de trabalho no Plano Aberto só podem ser executadas no modo job ou no modo batch.

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

Passos para migrar para o Sampler V2

  1. Substitua from qiskit_ibm_runtime import Sampler por from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. Remova quaisquer declarações from qiskit_ibm_runtime import Options, pois a classe Options não é usada pelos primitivos V2. Você pode, em vez disso, passar opções como um dicionário ao inicializar a classe SamplerV2 (por exemplo, sampler = Sampler(backend, options={"default_shots": 1024})), ou defini-las após a inicialização:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. Revise todas as opções suportadas e faça as atualizações necessárias.
  4. Agrupe cada Circuit que você deseja executar com os observáveis e valores de parâmetros que deseja aplicar ao Circuit em uma tupla (um PUB). Por exemplo, use (circuit1, parameter_set1) se quiser executar circuit1 com parameter_set1. Você pode opcionalmente especificar os shots que deseja para esse PUB específico.
  5. Atualize o método run() do sampler para passar a lista de PUBs. Por exemplo, run([(circuit1, parameter_set1)]). Você pode opcionalmente especificar shots aqui, que se aplicaria a todos os PUBs.
  6. Os resultados de jobs do Sampler V2 são agrupados por PUBs. Você pode ver os dados de saída de cada PUB indexando-o. Embora o Sampler V2 retorne amostras sem peso, a classe de resultado tem um método de conveniência para obter contagens. Por exemplo:
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

Você precisa do nome do registrador clássico para obter os resultados. Por padrão, ele é chamado meas quando você usa measure_all(). Ao definir seu Circuit, se você criar um ou mais registradores clássicos com um nome não padrão, use esse nome para obter os resultados. Você pode encontrar o nome do registrador clássico executando <circuit_name>.cregs. Por exemplo, qc.cregs.

Exemplos completos de Sampler

Executar um único experimento

Use o Sampler para determinar as contagens ou a distribuição de quase-probabilidade de um único 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()

Executar múltiplos experimentos em um único job

Use o Sampler para determinar as contagens ou distribuições de quase-probabilidade de múltiplos Circuits em um 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()}")

Executar Circuits parametrizados

Execute vários experimentos em um único job, aproveitando valores de parâmetros para aumentar a reutilização do Circuit.

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

Usar sessões e opções avançadas

Explore sessões e opções avançadas para otimizar o desempenho do Circuit nos QPUs.

cuidado

O bloco de código a seguir retornará um erro para usuários no Plano Aberto porque usa sessões. As cargas de trabalho no Plano Aberto só podem ser executadas no modo job ou no modo batch.

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

Próximos passos

Recomendações