Skip to main content
IBM Quantum Platform

Exemples d'exécuteurs testamentaires

  • Le code présenté sur cette page a été développé en respectant les exigences suivantes. Nous vous recommandons d'utiliser ces versions ou des versions plus récentes.

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

Les exemples présentés dans cette section illustrent certaines façons courantes d'utiliser la primitive Executor. Avant d'exécuter ces exemples, suivez les instructions fournies dans le guide de démarrage rapide « Installer Qiskit et Executor ».


Avant de commencer

Certains des exemples de code présentés sur cette page utilisent samplex, qui fait partie du package Samplomatic. Par conséquent, avant d'exécuter ces blocs de code, vous devez installer Samplomatic, comme indiqué dans le bloc de code suivant. Pour plus d'informations, consultez la documentation de Samplomatic.

pip install samplomatic

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

Exemple : circuit paramétré

Cet exemple montre comment ajouter des éléments de circuit avec des paramètres, ainsi que des éléments Samplex. Elle comprend les étapes suivantes :

  1. Configurer le circuit : générer et transcompiler le circuit cible.
  2. Préparez un samplex : regroupez les portes et les mesures dans des zones annotées, puis générez le modèle de circuit et la paire de samplex.
  3. Exécuter : ajouter un élément de circuit et un élément Samplex à un QuantumProgram et exécuter les deux dans une seule tâche.

Mettre en place le circuit

Préparez un état GHZ à trois qubits, faites pivoter les qubits autour de l'axe de Pauli-Z, puis mesurez les qubits dans la base de calcul.

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écifiez le backend et transcompilez le circuit afin qu'il n'utilise que les instructions prises en charge par le QPU (ce que l'on appelle un circuit à architecture de jeu 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éparez le samplex

Utilisez la generate_boxing_pass_manager fonction pratique et ses paramètres de rotation pour regrouper les portes à deux qubits et les mesures dans des encadrés, puis appliquez des annotations de rotation.

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)

Utilisez la build méthode pour générer le circuit type et le samplex.

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

Effectuez les circuits

Executor exécute QuantumProgram des objets. Chacun QuantumProgram peut contenir plusieurs éléments. Cet exemple ajoute un élément de circuit et un élément Samplex à exécuter. Pour plus de détails, consultez la section « Entrées et sorties de l'exécuteur ».

La première étape consiste à initialiser un programme vide, en demandant 1024 des images pour chaque configuration de chaque élément.

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

QuantumProgramAjouter l'élément de circuit à la liste. Cet élément de circuit se compose de deux parties : le circuit ISA et 10 jeux de 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
)

QuantumProgram Ajoutez l'élément samplex à avec ces arguments :

  • Le circuit modèle et le samplex générés par la build fonction
  • Dix jeux de valeurs de paramètres pour le circuit d'origine
  • 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 la tâche 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 : Exécuter PEC

Cet exemple montre comment utiliser un élément Samplex pour effectuer une annulation probabiliste des erreurs ( PEC ) afin d'atténuer les erreurs.

Considérons une version symétrique d'un circuit comportant dix qubits et deux couches distinctes de portes CX. Voici les principales tâches :

Le processus comprend les étapes suivantes :

  1. Configuration : générez le circuit cible et regroupez ses opérations dans des encadrés.
  2. Apprendre : Identifier le bruit des instructions que nous souhaitons atténuer à l'aide du PEC.
  3. Exécuter : lancer le circuit sur un serveur.
  4. Analyser : Traiter et analyser les résultats.

À titre de comparaison, nous allons exécuter ce circuit en miroir deux fois. Une fois avec uniquement l'effet de Pauli appliqué, et une fois avec l'atténuation PEC appliquée.

Note

L'exécution de cet exemple prend environ 10 minutes sur un processeur Heron r2.

Mettre en place le circuit

Choisissez un backend et préparez un circuit 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

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

Output:

Output of the previous code cell

Définissez certaines valeurs de paramètres :

import numpy as np

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

Utilisez le gestionnaire de passes pour transcompiler le circuit afin d'en faire un circuit ISA.

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

isa_circuit = preset_pass_manager.run(mirror_circuit)

Ensuite, regroupez les portes et les mesures dans des encadrés annotés. Vous pouvez le faire manuellement ou, pour plus de commodité, utiliser la generate_boxing_pass_manager fonction de Samplomatic. Le premier circuit ne comportera qu'une rotation et ne nécessite donc que l'annotation Twirl . Le deuxième circuit sera exécuté avec une atténuation PEC complète et nécessite les Twirl annotations 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)

Apprenez à reconnaître ce bruit

Afin de réduire au minimum le nombre d'expériences d'apprentissage du bruit, identifiez les instructions uniques présentes dans le deuxième circuit (celui dont les cases sont annotées d'un InjectNoise). Pour définir l'unicité, deux instructions de boîte sont considérées comme égales si les deux conditions suivantes sont remplies :

  • Leur contenu est identique, jusqu'aux portes à un seul qubit.
  • Leur Twirl annotation est identique (toutes les autres annotations sont ignorées).

Cela donne lieu à trois instructions distinctes, à savoir les cases « porte paire » et « porte impaire », ainsi que la case 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

NoiseLearnerV3Lancez une session, sélectionnez les paramètres d'apprentissage en configurant les options correspondantes, puis exécutez une tâche 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()

Convertissez result l'objet en celui requis par le samplex à l'aide de la result.to_dict méthode.

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

Effectuez les circuits

Executor exécute QuantumProgram des objets. Chacune QuantumProgram peut contenir plusieurs éléments, qui sont ajoutés au programme. Chaque élément correspond à une tâche que le programme doit effectuer.

Lancez un programme vide qui demande 1000 des captures d'écran pour chaque configuration de chaque élément.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

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

Ensuite, créez le circuit modèle et le samplex pour mirror_circuit_twirl et ajoutez-les au programme. Demandez 900 également des échantillons aléatoires à partir du samplex. Cela signifie que le samplex générera 900 des ensembles de paramètres, et que chaque ensemble sera exécuté 1000 un certain nombre de fois (le nombre de passes) dans le QPU.

Il s'agit de 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, ajoutez le circuit modèle et le samplex créés pour mirror_circuit_pec, en demandant 900 des randomisations. Il s'agit de 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 envoyer un travail.

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)

Analyser les résultats

Enfin, on effectue un post-traitement des résultats afin d'estimer les valeurs attendues 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)}")

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

Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.