Migrar para os primitivos V2 do Qiskit Runtime
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.
Após os primitivos V1 não serem mais suportados, import <primitive> importará a versão V2 do primitivo especificado.
- 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
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
precisionno métodorun()que especifica a precisão alvo das estimativas de valor esperado. - O Sampler V2 possui o argumento
shotsem seu métodorun().
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
datade 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)
- 1 circuit, 4 observables
- 1 circuit, 4 observables, 2 parameter sets
- 2 circuits, 2 observables
# 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)
Exemplos de Sampler (entrada e saída)
- 1 circuit, 3 parameter sets
- 2 circuits, 1 parameter set
- 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)
O formato de saída V1 era um dicionário de bitstrings (como inteiro) como chave e quase-probabilidades como valor para cada Circuit. O formato V2 usa a mesma chave (mas como string) e contagens como valor. Para converter o formato V2 para V1, divida as contagens pelo número de shots, onde o número de shots selecionado é descrito no guia Especificar opções.
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()} )
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:
SamplerV2eEstimatorV2agora 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étodoupdate()que aplica mudanças ao atributooptions. - Se você não especificar um valor para uma opção, ela receberá um valor especial de
Unsete os padrões do servidor serão usados. - Para os primitivos V2, o atributo
optionsé do tipo Pythondataclass. Você pode usar o método integradoasdictpara convertê-lo em um dicionário.
Consulte a referência da API para a lista de opções disponíveis.
- 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)
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_levelem 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_levelde 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 1 Nível 2 Measurement twirling Measurement twirling Mitigação de erro de leitura Mitigação de erro de leitura 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
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.
- 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}")
Passos para migrar para o Estimator V2
-
Substitua
from qiskit_ibm_runtime import Estimatorporfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Remova quaisquer declarações
from qiskit_ibm_runtime import Options, pois a classeOptionsnão é usada pelos primitivos V2. Você pode, em vez disso, passar opções como um dicionário ao inicializar a classeEstimatorV2(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 -
Revise todas as opções suportadas e faça as atualizações necessárias.
-
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 executarcircuit1comobservable1eparameter_set1. -
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.
-
Você pode opcionalmente especificar a precisão que deseja para esse PUB específico.
-
Atualize o método
run()do estimator para passar a lista de PUBs. Por exemplo,run([(circuit1, observable1, parameter_set1)]). Você pode opcionalmente especificarprecisionaqui, que se aplicaria a todos os PUBs. -
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.
- 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}")
Executar múltiplos experimentos em um único job
Use o Estimator para determinar os valores esperados de múltiplos pares Circuit-observável.
- 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}")
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.
- 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
Usar sessões e opções avançadas
Explore sessões e opções avançadas para otimizar o desempenho do Circuit nos QPUs.
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.
- 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}")
Passos para migrar para o Sampler V2
- Substitua
from qiskit_ibm_runtime import Samplerporfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Remova quaisquer declarações
from qiskit_ibm_runtime import Options, pois a classeOptionsnão é usada pelos primitivos V2. Você pode, em vez disso, passar opções como um dicionário ao inicializar a classeSamplerV2(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 - Revise todas as opções suportadas e faça as atualizações necessárias.
- 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 executarcircuit1comparameter_set1. Você pode opcionalmente especificar os shots que deseja para esse PUB específico. - Atualize o método
run()do sampler para passar a lista de PUBs. Por exemplo,run([(circuit1, parameter_set1)]). Você pode opcionalmente especificarshotsaqui, que se aplicaria a todos os PUBs. - 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()}")
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.
- 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}")
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.
- 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}")
Executar Circuits parametrizados
Execute vários experimentos em um único job, aproveitando valores de parâmetros para aumentar a reutilização do Circuit.
- 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}")
Usar sessões e opções avançadas
Explore sessões e opções avançadas para otimizar o desempenho do Circuit nos QPUs.
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.
- 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}")
Próximos passos
- Saiba mais sobre como definir opções no guia Especificar opções.
- Saiba mais detalhes sobre Entradas e saídas de primitivos.
- Experimente o tutorial Desigualdade CHSH.