Skip to main content
IBM Quantum Platform

Exemplos de executores

  • O código desta página foi desenvolvido com base nos seguintes requisitos. Recomendamos o uso dessas versões ou versões mais recentes.

    qiskit[all]~=2.4.0
    qiskit-ibm-runtime~=0.46.1
    samplomatic~=0.18.0
    

Os exemplos desta seção ilustram algumas formas comuns de utilizar a primitiva Executor. Antes de executar estes exemplos, siga as instruções do guia rápidoInstalar o Qiskit e o Executor”.


Antes de iniciar

Alguns dos exemplos de código nesta página utilizam samplex, que faz parte do pacote Samplomatic. Portanto, antes de executar esses blocos de código, é necessário 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 mostra como adicionar itens de circuito com parâmetros, bem como itens Samplex. Consiste nas seguintes etapas:

  1. Configurar o circuito: Gerar e compilar o circuito de destino.
  2. Prepare um samplex: agrupe portas e medições em caixas anotadas e gere o modelo de circuito e o par de samplex.
  3. Executar: Adicione um item de circuito e um item Samplex a um QuantumProgram e execute ambos em um único trabalho.

Monte o circuito

Prepare um estado GHZ de três qubits, gire os qubits em torno do eixo de Pauli-Z e meça 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()

Especifique o backend e compile o circuito para que ele utilize apenas instruções compatíveis com a QPU (conhecido como circuito de arquitetura de conjunto de instruções, ou 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)

Prepare o samplex

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

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)

Use o build método para gerar o circuito modelo e o samplex.

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

Execute os circuitos

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

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

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

Adicione o item do circuito ao QuantumProgram. Este item de circuito é composto por duas partes: o circuito ISA e 10 conjuntos de 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
)

Adicione o item samplex ao QuantumProgram com estes argumentos:

  • O circuito modelo e o samplex gerado pela build função
  • Dez conjuntos de valores de parâmetros para o circuito original
  • O número de randomizações a serem realizadas
# 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 a tarefa 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()

Recupere o resultado de 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: Executar o PEC

Este exemplo mostra como usar um item Samplex para realizar o cancelamento probabilístico de erros ( PEC ) com o objetivo de mitigar erros.

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

O fluxo de trabalho consiste nas seguintes etapas:

  1. Configuração: Gere o circuito de destino e agrupe suas operações em caixas.
  2. Aprenda: Identifique o ruído das instruções que queremos atenuar com o PEC.
  3. Executar: Executar o circuito em um backend.
  4. Analisar: Realizar o pós-processamento e analisar os resultados.

Para fins de comparação, vamos executar esse circuito espelhado duas vezes. Uma vez apenas com o efeito de Pauli aplicado e outra com a mitigação PEC aplicada.

Note

A duração deste exemplo é de aproximadamente 10 minutos em um processador Heron r2.

Monte o circuito

Escolha um backend e prepare 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:

Output of the previous code cell

Combine o circuito com seu inverso para criar um circuito espelho.

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

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

Output:

Output of the previous code cell

Defina alguns valores de parâmetros:

import numpy as np

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

Use o gerenciador de passagens para compilar o circuito, transformando-o em 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, agrupe os portões e as medições em caixas anotadas. Você pode fazer isso manualmente ou usar a generate_boxing_pass_manager função do Samplomatic para facilitar. O primeiro circuito terá apenas o efeito giratório aplicado e, portanto, precisa apenas da Twirl anotação. A segunda volta será realizada com mitigação PEC total e requer as Twirl anotações 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)

Aprenda a identificar o ruído

Para minimizar o número de experimentos de aprendizado com ruído, identifique as instruções únicas no segundo circuito (aquele com caixas marcadas com InjectNoise). Ao definir a exclusividade, duas instruções de caixa são consideradas iguais se ambas as condições a seguir forem verdadeiras:

  • O conteúdo é idêntico, até as portas de um único qubit.
  • A anotação deles Twirl é igual (todas as outras anotações são desconsideradas).

Isso resulta em três instruções distintas: as caixas de portas ímpares e pares 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

Inicialize um NoiseLearnerV3, selecione os parâmetros de aprendizado definindo suas opções e execute um trabalho 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()

Converta result para o objeto exigido pelo samplex utilizando o result.to_dict método.

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

Execute os circuitos

Executor executa QuantumProgram objetos. Cada um deles QuantumProgram pode conter vários itens, que são anexados ao programa. Cada item é uma tarefa a ser executada pelo programa.

Inicie um programa vazio, solicitando 1000 imagens 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, crie o circuito modelo e o samplex para mirror_circuit_twirl e acrescente-os ao programa. Solicite 900 também randomizações do Samplex. Isso significa que o samplex irá gerar 900 conjuntos de parâmetros, e cada conjunto será executado 1000 várias vezes (o número de iterações) na 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, acrescente o circuito modelo e o samplex criados 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 trabalho.

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

Output:

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, processe os resultados para estimar os valores esperados dos operadores de Pauli-Z de um único qubit que atuam sobre 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)}")

Output:

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

Output:

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óximas etapas

Recomendações
Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.