Vai al contenuto principale

Esempi di Executor

Versioni dei pacchetti

Il codice di questa pagina è stato sviluppato con i seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.

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

Gli esempi in questa sezione illustrano alcune modi comuni di utilizzare la primitiva Executor. Prima di eseguire questi esempi, segui le istruzioni in Installare Qiskit e Avvio rapido con Executor.

Prima di iniziare

Alcuni degli esempi di codice su questa pagina utilizzano samplex, che fa parte del pacchetto Samplomatic. Pertanto, prima di eseguire quei blocchi di codice, devi installare Samplomatic, come mostrato nel blocco di codice seguente. Per ulteriori informazioni, consulta la documentazione di Samplomatic.

pip install samplomatic

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

Esempio: Circuito parametrizzato

Questo esempio illustra come aggiungere elementi del circuito con parametri e come aggiungere elementi samplex. Consiste in questi passaggi:

  1. Configurare il circuito: Generare e transpilare il circuito target.
  2. Preparare un samplex: Raggruppare i gate e le misurazioni in box annotati e generare la coppia circuito template e samplex.
  3. Eseguire: Aggiungere un elemento di circuito e un elemento samplex a un QuantumProgram ed eseguire entrambi in un singolo job.

Configurare il circuito

Preparare uno stato GHZ a tre qubit, ruotare i qubit intorno all'asse di Pauli-Z e misurare i qubit nella base computazionale.

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

Specificare il backend e transpilare il circuito per utilizzare solo le istruzioni supportate dal QPU (chiamato circuito di architettura del set di istruzioni (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)

Preparare il samplex

Usare la funzione di convenienza generate_boxing_pass_manager e i suoi parametri di twirling per raggruppare i gate a due qubit e le misurazioni in box e applicare annotazioni di 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)

Usare il metodo build per generare il circuito template e il samplex.

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

Eseguire i circuiti

Executor esegue oggetti QuantumProgram. Ogni QuantumProgram può contenere diversi elementi. Questo esempio aggiunge un elemento di circuito e un elemento samplex per l'esecuzione. Per tutti i dettagli, vedere Input e output di Executor.

Il primo passo è inizializzare un programma vuoto, richiedendo 1024 shots per ogni configurazione di ogni elemento.

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

Aggiungere l'elemento di circuito al QuantumProgram. Questo elemento di circuito è composto da due parti: il circuito ISA e 10 set di valori dei suoi parametri.

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

Aggiungere l'elemento samplex al QuantumProgram con questi argomenti:

  • Il circuito template e il samplex generati dalla funzione build
  • Dieci set di valori dei parametri per il circuito originale
  • Il numero di randomizzazioni da eseguire
# 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),
)

Eseguire il job Executor

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

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

# Retrieve the result
result = job.result()

Recuperare il risultato per ogni task.

# 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"]

Esempio: Eseguire la PEC

Questo esempio mostra come usare un elemento samplex per eseguire la cancellazione probabilistica degli errori (PEC) per la mitigazione degli errori.

Considera una versione specchiata di un circuito con dieci qubit e due strati unici di gate CX. Questi sono i compiti principali:

Il pipeline consiste in questi passaggi:

  1. Configurazione: Generare il circuito target e raggruppare le sue operazioni in box.
  2. Apprendimento: Apprendere il rumore delle istruzioni che vogliamo mitigare con PEC.
  3. Esecuzione: Eseguire il circuito su un backend.
  4. Analisi: Post-elaborare e analizzare i risultati.

A scopo di confronto, eseguiremo questo circuito specchiato due volte. Una volta con solo il Pauli-twirling applicato, e una volta con la mitigazione PEC applicata.

nota

L'utilizzo per questo esempio è di circa 10 minuti su un processore Heron r2.

Configurare il circuito

Scegliere un backend e preparare un circuito a 10 qubit.

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

Combinare il circuito con il suo inverso per creare un circuito specchiato.

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

Impostare alcuni valori di parametri:

import numpy as np

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

Usare il pass manager per transpilare il circuito in un circuito ISA.

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

isa_circuit = preset_pass_manager.run(mirror_circuit)

Quindi, raggruppare i gate e le misurazioni in box annotati. Puoi farlo manualmente o usare la funzione generate_boxing_pass_manager di Samplomatic per comodità. Il primo circuito avrà solo il twirling applicato e quindi ha bisogno solo dell'annotazione Twirl. Il secondo circuito verrà eseguito con la mitigazione PEC completa e ha bisogno sia delle annotazioni Twirl che 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)

Apprendere il rumore

Per minimizzare il numero di esperimenti di apprendimento del rumore, identificare le istruzioni uniche nel secondo circuito (quello con box annotati con InjectNoise). Per definire l'unicità, due istruzioni di box sono uguali se entrambe le seguenti sono vere:

  • Il loro contenuto è uguale, a meno di gate a un qubit.
  • La loro annotazione Twirl è uguale (qualsiasi altra annotazione è ignorata).

Questo porta a tre istruzioni uniche, ovvero i box di gate pari e dispari, e il box di misurazione finale.

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

Inizializzare un NoiseLearnerV3, scegliere i parametri di apprendimento impostando le sue opzioni, ed eseguire un job di apprendimento del rumore.

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

Convertire result nell'oggetto richiesto dal samplex usando il metodo result.to_dict.

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

Eseguire i circuiti

Executor esegue oggetti QuantumProgram. Ogni QuantumProgram può contenere diversi elementi, che vengono aggiunti al programma. Ogni elemento è un task che il programma deve eseguire.

Inizializzare un programma vuoto, richiedendo 1000 shots per ogni configurazione di ogni elemento.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

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

Quindi, costruire il circuito template e il samplex per mirror_circuit_twirl e aggiungerli al programma. Richiedere anche 900 randomizzazioni dal samplex. Ciò significa che il samplex genererà 900 set di parametri, e ogni set verrà eseguito 1000 volte (il numero di shots) sul QPU.

Questo è il primo task del programma (risultato 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,),
)

Allo stesso modo, aggiungere il circuito template e il samplex costruiti per mirror_circuit_pec, richiedendo 900 randomizzazioni. Questo è il secondo task del programma (risultato 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,),
)

Importare Executor e inviare un 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)

Analizzare i risultati

Infine, post-elaborare i risultati per stimare i valori di aspettativa degli operatori Pauli-Z a un qubit che agiscono su ciascuno dei dieci qubit attivi (valore atteso: 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

Prossimi passi

Raccomandazioni