Pular para o conteúdo principal

Exemplos de Executor

Versões dos pacotes

O código desta página foi desenvolvido com os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
samplomatic~=0.18.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic

Os exemplos nesta seção ilustram algumas formas comuns de usar a primitiva Executor. Antes de executar esses exemplos, siga as instruções em Instalar o Qiskit e Início rápido com o Executor.

Antes de começar

Alguns dos exemplos de código nesta página usam samplex, que faz parte do pacote Samplomatic. Portanto, antes de executar esses blocos de código, você deve instalar o Samplomatic, conforme mostrado no bloco de código a seguir. Para mais informações, consulte a documentação do Samplomatic.

pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]

Exemplo: Circuito parametrizado

Este exemplo ilustra como adicionar itens de circuito com parâmetros, bem como como adicionar itens samplex. Consiste nestes passos:

  1. Configurar o circuito: Gerar e transpilar o circuito alvo.
  2. Preparar um samplex: Agrupar gates e medições em caixas anotadas e gerar o par de circuito template e samplex.
  3. Executar: Adicionar um item de circuito e um item samplex a um QuantumProgram e executar ambos em um único job.

Configurar o circuito

Preparar um estado GHZ de três qubits, rotacionar os qubits em torno do eixo de Pauli-Z, e medir os qubits na base computacional.

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Generate the circuit
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
circuit.rz(Parameter("lam"), 2)
circuit.measure_all()

Especificar o backend e transpilar o circuito para usar apenas as instruções suportadas pelo QPU (chamado circuito de arquitetura de conjunto de instruções (ISA)).

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Transpile the circuit to ISA
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3
)
isa_circuit = preset_pass_manager.run(circuit)

Preparar o samplex

Usar a função de conveniência generate_boxing_pass_manager e seus parâmetros de twirling para agrupar gates de dois qubits e medições em caixas e aplicar anotações de twirling.

boxing_pm = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)

boxed_circuit = boxing_pm.run(isa_circuit)

Usar o método build para gerar o circuito template e o samplex.

# Build the template circuit and the samplex
template_circuit, samplex = build(boxed_circuit)

Executar os circuitos

O Executor executa objetos QuantumProgram. Cada QuantumProgram pode conter vários itens. Este exemplo adiciona um item de circuito e um item samplex para execução. Para detalhes completos, consulte Entrada e saída do Executor.

O primeiro passo é inicializar um programa vazio, solicitando 1024 shots para cada configuração de cada item.

# Generate a quantum program
program = QuantumProgram(shots=1024)

Anexar o item de circuito ao QuantumProgram. Este item de circuito consiste em duas partes — o circuito ISA e 10 conjuntos de seus valores de parâmetros.

# Append the circuit and the parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, 3), # 10 sets of parameter values
)

Anexar o item samplex ao QuantumProgram com estes argumentos:

  • O circuito template e o samplex gerados pela função build
  • Dez conjuntos de valores de parâmetros para o circuito original
  • O número de randomizações a realizar
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(
10, 3
), # 10 sets of parameter values
},
shape=(2, 14, 10),
)

Executar o job do Executor

# initialize an Executor with default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()

Recuperar o resultado para cada tarefa.

# Access the results of the classical register of task #0, the CircuitItem
result_0 = result[0]["meas"]

# Access the results of the classical register of task #1, the SamplexItem
result_1 = result[1]["meas"]

Exemplo: Realizar PEC

Este exemplo mostra como usar um item samplex para realizar o cancelamento probabilístico de erros (PEC) para mitigação de erros.

Considere uma versão espelhada de um circuito com dez qubits e duas camadas únicas de gates CX. Estas são as tarefas principais:

O pipeline consiste nestes passos:

  1. Configuração: Gerar o circuito alvo e agrupar suas operações em caixas.
  2. Aprendizado: Aprender o ruído das instruções que queremos mitigar com PEC.
  3. Execução: Executar o circuito em um backend.
  4. Análise: Pós-processar e analisar os resultados.

Para comparação, executaremos este circuito espelhado duas vezes. Uma vez com apenas o Pauli-twirling aplicado, e uma vez com a mitigação PEC aplicada.

nota

O uso para este exemplo é de aproximadamente 10 minutos em um processador Heron r2.

Configurar o circuito

Escolher um backend e preparar um circuito de 10 qubits.

from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.transpiler import generate_preset_pass_manager
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic import build

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Prepare a circuit

num_qubits = 10
num_layers = 10

qubits = list(range(num_qubits))
circuit = QuantumCircuit(num_qubits)

for layer_idx in range(num_layers):
circuit.rx(Parameter(f"theta_{layer_idx}"), qubits)
for i in range(num_qubits // 2):
circuit.cz(qubits[2 * i], qubits[2 * i + 1])

circuit.rx(Parameter(f"phi_{layer_idx}"), qubits)
for i in range(num_qubits // 2 - 1):
circuit.cz(qubits[2 * i] + 1, qubits[2 * i + 1] + 1)

circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Combinar o circuito com seu inverso para criar um circuito espelhado.

mirror_circuit = circuit.compose(circuit.inverse())
mirror_circuit.measure_all()

mirror_circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Definir alguns valores de parâmetros:

import numpy as np

parameter_values = np.random.rand(mirror_circuit.num_parameters)

Usar o pass manager para transpilar o circuito para um circuito ISA.

preset_pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
)

isa_circuit = preset_pass_manager.run(mirror_circuit)

Em seguida, agrupar gates e medições em caixas anotadas. Você pode fazer isso manualmente ou usar a função generate_boxing_pass_manager do Samplomatic para conveniência. O primeiro circuito terá apenas twirling aplicado e portanto precisa apenas da anotação Twirl. O segundo circuito será executado com mitigação PEC completa e precisa das anotações Twirl e InjectNoise.

# Pass manager used to create twirled-annotated boxes.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)

mirror_circuit_twirl = boxing_pm.run(isa_circuit)

# Pass manager used to create a new boxed circuit with
# both Twirl and InjectNoise annotations.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

mirror_circuit_pec = boxing_pm.run(isa_circuit)

Aprender o ruído

Para minimizar o número de experimentos de aprendizado de ruído, identificar as instruções únicas no segundo circuito (o que tem caixas anotadas com InjectNoise). Para definir unicidade, duas instruções de caixa são iguais se ambas as seguintes forem verdadeiras:

  • Seu conteúdo é igual, exceto por gates de um único qubit.
  • Sua anotação Twirl é igual (qualquer outra anotação é desconsiderada).

Isso leva a três instruções únicas, a saber as caixas de gates pares e ímpares, e a caixa de medição final.

from samplomatic.utils import find_unique_box_instructions

unique_box_instructions = find_unique_box_instructions(
mirror_circuit_pec.data
)
assert len(unique_box_instructions) == 3

Inicializar um NoiseLearnerV3, escolher os parâmetros de aprendizado configurando suas opções, e executar um job de aprendizado de ruído.

from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3

learner = NoiseLearnerV3(backend)

learner.options.shots_per_randomization = 128
learner.options.num_randomizations = 32
learner.options.layer_pair_depths = [0, 1, 2, 4, 16, 32]

learner_job = learner.run(unique_box_instructions)

learner_job.job_id()
learner_result = learner_job.result()

Converter result no objeto requerido pelo samplex usando o método result.to_dict.

noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)

Executar os circuitos

Executor executa objetos QuantumProgram. Cada QuantumProgram pode conter vários itens, que são adicionados ao programa. Cada item é uma tarefa para o programa realizar.

Inicializar um programa vazio, solicitando 1000 shots para cada configuração de cada item.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

# Initialize an empty QuantumProgram
program = QuantumProgram(shots=1000)

Em seguida, construir o circuito template e o samplex para mirror_circuit_twirl e adicioná-los ao programa. Também solicitar 900 randomizações do samplex. Isso significa que o samplex gerará 900 conjuntos de parâmetros, e cada conjunto será executado 1000 vezes (o número de shots) no QPU.

Esta é a primeira tarefa do programa (resultado 0).

template_twirl, samplex_twirl = build(mirror_circuit_twirl)

program.append_samplex_item(
template_twirl,
samplex=samplex_twirl,
samplex_arguments={"parameter_values": parameter_values},
shape=(900,),
)

Da mesma forma, adicionar o circuito template e o samplex construídos para mirror_circuit_pec, solicitando 900 randomizações. Esta é a segunda tarefa do programa (resultado 1).

template_pec, samplex_pec = build(mirror_circuit_pec)

program.append_samplex_item(
template_pec,
samplex=samplex_pec,
samplex_arguments={
"parameter_values": parameter_values,
"pauli_lindblad_maps": noise_maps,
"noise_scales": {
ref: -1.0 for ref in noise_maps
}, # Set the scales to -1 for PEC
},
shape=(900,),
)

Importar Executor e enviar um job.

from qiskit_ibm_runtime.executor import Executor

executor = Executor(backend)
executor_job = executor.run(program)

executor_job.job_id()

executor_results = executor_job.result()
executor_results

twirl_result = executor_results[0]

print(f"Twirl result keys:\n {list(twirl_result.keys())}\n")
print(f"Shape of results: {twirl_result['meas'].shape}")

pec_result = executor_results[1]

print(f"PEC result keys:\n {list(pec_result.keys())}\n")
print(f"Shape of results: {pec_result['meas'].shape}")
Twirl result keys:
['meas', 'measurement_flips.meas']

Shape of results: (900, 1000, 10)
PEC result keys:
['meas', 'measurement_flips.meas', 'pauli_signs']

Shape of results: (900, 1000, 10)

Analisar os resultados

Por fim, pós-processar os resultados para estimar os valores esperados de operadores Pauli-Z de um único qubit atuando em cada um dos dez qubits ativos (valor esperado: 1.0).

# Undo measurement twirling
twirl_result_unflipped = (
twirl_result["meas"] ^ twirl_result["measurement_flips.meas"]
)

# Calculate the expectation values of single-qubit Z operators
exp_vals = 1 - 2 * twirl_result_unflipped.mean(axis=1).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.77
Qubit 1 -> 0.76
Qubit 2 -> 0.66
Qubit 3 -> 0.71
Qubit 4 -> 0.69
Qubit 5 -> 0.67
Qubit 6 -> 0.62
Qubit 7 -> 0.59
Qubit 8 -> 0.62
Qubit 9 -> 0.68
# Undo measurement twirling
pec_result_unflipped = (
pec_result["meas"] ^ pec_result["measurement_flips.meas"]
)

# Calculate the signs for PEC mitigation
signs = np.prod((-1) ** pec_result["pauli_signs"], axis=-1)
signs = signs.reshape((signs.shape[0], 1))

# Calculate the expectation values of single-qubit Z operators as required by
# PEC mitigation
exp_vals = 1 - (2 * pec_result_unflipped.mean(axis=1) * signs).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.98
Qubit 1 -> 0.99
Qubit 2 -> 0.96
Qubit 3 -> 0.98
Qubit 4 -> 0.98
Qubit 5 -> 0.98
Qubit 6 -> 0.98
Qubit 7 -> 0.95
Qubit 8 -> 0.95
Qubit 9 -> 0.94

Próximos passos

Recomendações