Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Construire une expérience d'apprentissage

Ce tutoriel montre comment construire une expérience d'apprentissage standard à partir de zéro, et l'utiliser pour apprendre un modèle à partir de données bruitées simulées.

  1. Définir un ensemble de portes sur un anneau de qubits

  2. Choisir un modèle de Pauli-Lindblad 2-local

  3. Construire l'expérience d'apprentissage

  4. Générer les circuits

  5. Configurer la simulation locale

  6. Exécuter le programme

  7. Analyser les données

Exécution sur du matériel réel

Les circuits ci-dessous sont simulés localement, donc ce pas à pas ne nécessite aucun identifiant IBM Quantum®. Pour l'exécuter plutôt sur un appareil réel :

  • Étape 1 : remplace FakeMarrakesh par un Backend réel.

  • Étape 5 : passe ce Backend comme mode de l'executor et supprime les options du simulateur.

1. Définir un ensemble de portes sur un anneau de qubits​

Construis un QiskitGateSet à partir d'une Target de Backend et d'un sous-ensemble de ses qubits — ici un anneau de douze qubits de FakeMarrakesh — puis ajoute-lui une porte : une couche de six portes CZ couvrant cet anneau. Par défaut, l'ensemble de portes est initialisé avec une porte de préparation P et une porte de mesure M.

from qiskit_ibm_runtime.fake_provider import FakeMarrakesh

from qiskit_noise_learning.gate_sets import QiskitGateSet

backend = FakeMarrakesh()

qubit_subset = [*range(25, 30), *range(37, 39), *range(45, 50)]
cz_pairs = [(25, 26), (27, 28), (29, 38), (37, 45), (46, 47), (48, 49)]

gate_set = QiskitGateSet(backend.num_qubits, target=backend.target, qubit_subset=qubit_subset)

with gate_set.build_new_gate("cz_gate", latex_str=r"\mathrm{CZ}") as builder:
for pair in cz_pairs:
builder.circuit.cz(*pair)

list(gate_set)
['M', 'P', 'cz_gate']
Exécution sur du matériel réel
from qiskit_ibm_runtime import QiskitRuntimeService

backend = QiskitRuntimeService().backend("ibm_marrakesh")

Appelle draw() pour voir la porte sur la topologie de l'appareil : l'anneau de qubits de l'ensemble de portes, et quelles paires d'entre eux la porte intrique.

gate_set.draw()

2. Choisir un modèle de Pauli-Lindblad 2-local​

Décide quels générateurs de Pauli-Lindblad le bruit peut avoir avec un PauliLindbladModel. Construis cet ensemble de générateurs à partir de la connectivité de l'ensemble de portes avec k_local() — ici tous les Paulis 2-locaux sur les paires de qubits connectés de la porte unitaire, et des Paulis à un seul qubit pour la préparation et la mesure.

from qiskit_noise_learning.models import PauliLindbladModel

model = PauliLindbladModel.k_local(gate_set, gate_k={"cz_gate": 2, "M": 1, "P": 1})

Affiche le nombre de paramètres inconnus du modèle de bruit de chaque porte.

{name: len(generators) for name, generators in model.generators.items()}
{'M': 12, 'P': 12, 'cz_gate': 144}

3. Construire l'expérience d'apprentissage​

Assemble une Experiment en composant des étapes de construction. Chaque étape lit ce que les étapes précédentes ont écrit, donc l'ordre compte :

from qiskit_noise_learning.experiment_builder import (
BindFragmentDepths,
CompleteSequences,
EvenDepthVanillaPaths,
Experiment,
GenerateInstructionSequences,
IdentifyRelations,
MergeInstructionSequences,
SPAMPaths,
VanillaInstructionSequences,
)

experiment_builder = (
# Add standard vanilla learning paths and instruction sequences, and identify relations
EvenDepthVanillaPaths()
+ VanillaInstructionSequences()
+ IdentifyRelations()
# Add paths for learning SPAM, then generate and merge instruction sequences for measuring them
+ SPAMPaths()
+ GenerateInstructionSequences()
+ MergeInstructionSequences()
# Finalize by completing the instruction sequences and setting experiment depths
+ CompleteSequences()
+ BindFragmentDepths([2, 16, 32, 64])
)

experiment = experiment_builder.run(
Experiment(fidelity_model=model, shots=20, randomizations=50)
)

print(f"Number of paths: {len(experiment.paths)}")
print(f"Number of instruction sequences: {len(experiment.instruction_sequences)}")
Number of paths: 156
Number of instruction sequences: 37

Observe le rang de la matrice de conception :

print(f"Design matrix rank: {experiment.design_matrix.rank}")
Design matrix rank: 120

4. Générer les circuits​

Compile l'expérience avec un ExecutorCircuitGenerator, ce qui génère un QuantumProgram — un circuit modèle paramétré par profondeur de fragment.

from qiskit_noise_learning.circuit_generator import ExecutorCircuitGenerator

circuit_generator = ExecutorCircuitGenerator(gate_set)
quantum_program = circuit_generator.generate(experiment)

print(f"Number of template circuits: {len(quantum_program.items)}")
Number of template circuits: 5

Dessine un circuit modèle pour l'inspecter.

quantum_program.items[0].circuit.draw("mpl", idle_wires=False, fold=False)

../_images/4088c7e98baeb5b56925c6c1f276159582c5f7040fbba76de438b919210bcc5f.png

5. Configurer la simulation locale​

Pour définir le modèle de bruit de la simulation, nous utilisons les générateurs déjà présents dans le modèle avec des taux aléatoires pour construire une PauliLindbladMap.

import numpy as np
from qiskit.quantum_info import PauliLindbladMap

rng = np.random.default_rng(1234)

true_maps = {
name: PauliLindbladMap.from_terms(
[
PauliLindbladMap.GeneratorTerm(rate, generator)
for generator, rate in zip(generators, rng.uniform(2e-4, 1.5e-3, len(generators)))
]
)
for name, generators in model.generators.items()
if name != "P"
}
{name: noise_map.num_qubits for name, noise_map in true_maps.items()}
{'M': 156, 'cz_gate': 156}

Ces maps ont la largeur de l'appareil, puisque le modèle est exprimé dans l'indexation des qubits de backend, mais le simulateur veut que chaque map ait la largeur de la porte à laquelle elle s'applique, avec des indices de Pauli parcourant les qubits de la porte dans l'ordre physique croissant. Réduis-les avec keep_qubits(), qui trace tout ce qui est hors de l'anneau :

noise_dict = {
name: noise_map.keep_qubits(qubit_subset) for name, noise_map in true_maps.items()
}
{name: noise_map.num_qubits for name, noise_map in noise_dict.items()}
{'M': 12, 'cz_gate': 12}

Le simulateur nomme le bruit d'une couche d'après l'instruction de boîte à partir de laquelle la couche a été construite, que to_instruction() renvoie pour toute porte de l'ensemble de portes. Associe chaque boîte à sa map, utilise la méthode de simulation stabilizer et définis seed_simulator pour rendre les données simulées reproductibles :

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor

executor = Executor(
mode=AerSimulator(method="stabilizer"),
options={
"simulator": {
"layer_noise_model": [
(gate_set[name].to_instruction(), noise_map)
for name, noise_map in noise_dict.items()
],
"seed_simulator": 42,
}
},
)
Exécution sur du matériel réel
executor = Executor(mode=backend)

6. Exécuter le programme​

Exécute le programme, puis formate les résultats avec collect(), pour produire un Fit — le conteneur dans lequel les étapes d'analyse lisent et écrivent.

Cette étape est la même que l'executor pointe vers un simulateur ou vers un appareil réel.

job = executor.run(quantum_program)
fit = circuit_generator.collect(job.result())

7. Analyser les données​

Construis un pipeline d'analyse autour de l'ajustement du modèle par moindres carrés non négatifs.

from qiskit_noise_learning.analysis import (
ComputeObservables, # computes observables from raw data
CurveFitObservables, # performs exponential fitting
LeastSquaresSolve, # solves model with (nonnegative) least squares
)

analyzer = ComputeObservables() + CurveFitObservables() + LeastSquaresSolve()

fit = analyzer.run(fit)
/opt/hostedtoolcache/Python/3.15.1/x64/lib/python3.15/site-packages/qiskit_noise_learning/analysis/model_solve.py:250: UserWarning: 7 of 156 row(s) of the linear system have a non-positive or non-finite uncertainty, so they carry no usable statistical weight. Positions in LinearSystemData.row_labels: 144, 145, 148, 149, 151, 152, 154.
system = LinearSystemData.from_fit(fit)

Trace les moyennes d'observables mesurées face à leurs exponentielles ajustées, un sous-graphique par paire CZ, pour voir si les décroissances ajustées sont bien exponentielles.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
exponential_fit=True,
)

Remplace exponential_fit par model_prediction pour comparer les mêmes données aux décroissances impliquées par les taux issus de la résolution par moindres carrés non négatifs.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
model_prediction=True,
)