Exemples d'Executor
Versions des packages
Le code de cette page a été développé avec les exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.
qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.21.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic
Les exemples de cette section illustrent quelques façons courantes d'utiliser la primitive Executor. Avant d'exécuter ces exemples, suis les instructions dans Installer Qiskit et Démarrage rapide avec Executor.
Avant de commencer
Certains des exemples de code sur cette page utilisent samplex, qui fait partie du package Samplomatic. Par conséquent, avant d'exécuter ces blocs de code, tu dois installer Samplomatic, comme indiqué dans le bloc de code suivant. Pour plus d'informations, consulte la documentation Samplomatic.
pip install samplomatic
# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]
Exemple : Circuit paramétré
Cet exemple illustre comment ajouter des éléments de circuit avec des paramètres, ainsi que comment ajouter des éléments samplex. Il comprend ces étapes :
- Configurer le circuit : Générer et transpiler le circuit cible.
- Préparer un samplex : Regrouper les gates et les mesures dans des boîtes annotées et générer la paire circuit template et samplex.
- Exécuter : Ajouter un élément de circuit et un élément samplex à un
QuantumProgramet exécuter les deux dans un seul job.
Configurer le circuit
Préparer un état GHZ à trois qubits, faire pivoter les qubits autour de l'axe de Pauli-Z, et mesurer les qubits dans la base computationnelle.
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()
Spécifier le backend et transpiler le circuit pour n'utiliser que les instructions supportées par le QPU (appelé circuit d'architecture d'ensemble d'instructions (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)
Préparer le samplex
Utiliser la fonction de commodité generate_boxing_pass_manager et ses paramètres de twirling pour regrouper les gates à deux qubits et les mesures dans des boîtes et appliquer des annotations 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)
Utiliser la méthode build pour générer le circuit template et le samplex.
# Build the template circuit and the samplex
template_circuit, samplex = build(boxed_circuit)
Exécuter les circuits
Executor exécute des objets QuantumProgram. Chaque QuantumProgram peut contenir plusieurs éléments. Cet exemple ajoute un élément de circuit et un élément samplex pour l'exécution. Pour tous les détails, voir Entrée et sortie de l'Executor.
La première étape est d'initialiser un programme vide, en demandant 1024 shots pour chaque configuration de chaque élément.
# Generate a quantum program
program = QuantumProgram(shots=1024)
Ajouter l'élément de circuit au QuantumProgram. Cet élément de circuit est composé de deux parties - le circuit ISA et 10 ensembles de ses valeurs de paramètres.
# 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
)
Ajouter l'élément samplex au QuantumProgram avec ces arguments :
- Le circuit template et le samplex générés par la fonction
build - Dix ensembles de valeurs de paramètres pour le circuit original
- Le nombre de randomisations à effectuer
# 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),
)
Exécuter le 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()
Récupérer le résultat pour chaque tâche.
# 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"]
Exemple : Effectuer une PEC
Cet exemple montre comment utiliser un élément samplex pour effectuer la cancellation d'erreurs probabiliste (PEC) pour la mitigation d'erreurs.
Considérons une version miroir d'un circuit avec dix qubits et deux couches uniques de gates CX. Ce sont les tâches principales :
- Exécuter le circuit avec twirling.
- Exécuter le circuit avec la mitigation PEC, comme dans l'article «Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors».
Le pipeline consiste en ces étapes :
- Configuration : Générer le circuit cible et regrouper ses opérations dans des boîtes.
- Apprentissage : Apprendre le bruit des instructions que nous voulons mitiger avec PEC.
- Exécution : Exécuter le circuit sur un backend.
- Analyse : Post-traiter et analyser les résultats.
À titre de comparaison, nous allons exécuter ce circuit miroir deux fois. Une fois avec seulement le Pauli-twirling appliqué, et une fois avec la mitigation PEC appliquée.
L'utilisation pour cet exemple est d'environ 10 minutes sur un processeur Heron r2.
Configurer le circuit
Choisir un backend et préparer un circuit à 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)
Combiner le circuit avec son inverse pour créer un circuit miroir.
mirror_circuit = circuit.compose(circuit.inverse())
mirror_circuit.measure_all()
mirror_circuit.draw("mpl", scale=0.35, fold=100)
Définir quelques valeurs de paramètres :
import numpy as np
parameter_values = np.random.rand(mirror_circuit.num_parameters)
Utiliser le pass manager pour transpiler le circuit en un circuit ISA.
preset_pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
)
isa_circuit = preset_pass_manager.run(mirror_circuit)
Ensuite, regrouper les gates et les mesures dans des boîtes annotées. Tu peux le faire manuellement ou utiliser la fonction generate_boxing_pass_manager de Samplomatic pour plus de commodité. Le premier circuit n'aura que le twirling appliqué et n'a donc besoin que de l'annotation Twirl. Le deuxième circuit sera exécuté avec une mitigation PEC complète et a besoin des annotations Twirl et 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)
Apprendre le bruit
Pour minimiser le nombre d'expériences d'apprentissage du bruit, identifier les instructions uniques dans le deuxième circuit (celui avec les boîtes annotées avec InjectNoise). Pour définir l'unicité, deux instructions de boîte sont égales si les deux conditions suivantes sont vraies :
- Leur contenu est égal, à des gates à un qubit près.
- Leur annotation
Twirlest égale (toute autre annotation est ignorée).
Cela conduit à trois instructions uniques, à savoir les boîtes de gates pairs et impairs, et la boîte de mesure 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
Initialiser un NoiseLearnerV3, choisir les paramètres d'apprentissage en définissant ses options, et exécuter un job d'apprentissage du bruit.
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()
Convertir result en objet requis par le samplex en utilisant la méthode result.to_dict.
noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)
Exécuter les circuits
Executor exécute des objets QuantumProgram. Chaque QuantumProgram peut contenir plusieurs éléments, qui sont ajoutés au programme. Chaque élément est une tâche que le programme doit effectuer.
Initialiser un programme vide, en demandant 1000 shots pour chaque configuration de chaque élément.
from qiskit_ibm_runtime.quantum_program import QuantumProgram
# Initialize an empty QuantumProgram
program = QuantumProgram(shots=1000)
Ensuite, construire le circuit template et le samplex pour mirror_circuit_twirl et les ajouter au programme. Également demander 900 randomisations au samplex. Cela signifie que le samplex générera 900 ensembles de paramètres, et chaque ensemble sera exécuté 1000 fois (le nombre de shots) sur le QPU.
C'est la première tâche du programme (résultat 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,),
)
De même, ajouter le circuit template et le samplex construits pour mirror_circuit_pec, en demandant 900 randomisations. C'est la deuxième tâche du programme (résultat 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,),
)
Importer Executor et soumettre 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)
Analyser les résultats
Enfin, post-traiter les résultats pour estimer les valeurs d'espérance des opérateurs de Pauli-Z à un qubit agissant sur chacun des dix qubits actifs (valeur attendue : 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.71
Qubit 1 -> 0.72
Qubit 2 -> 0.7
Qubit 3 -> 0.68
Qubit 4 -> 0.65
Qubit 5 -> 0.64
Qubit 6 -> 0.62
Qubit 7 -> 0.66
Qubit 8 -> 0.69
Qubit 9 -> 0.75
# 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 -> 1.0
Qubit 2 -> 0.99
Qubit 3 -> 0.97
Qubit 4 -> 0.97
Qubit 5 -> 0.97
Qubit 6 -> 0.96
Qubit 7 -> 0.97
Qubit 8 -> 0.97
Qubit 9 -> 0.98
Prochaines étapes
- Consulte l'aperçu du broadcasting.
- Apprends à utiliser les options d'Executor.
- Comprends le modèle d'exécution dirigée.
- Consulte la documentation Samplomatic.
- Apprends à combiner différentes techniques de mitigation d'erreurs lors de l'utilisation du modèle d'exécution dirigée dans le tutoriel Probabilistic error cancellation with shaded lightcones.