Aller au contenu principal

Utilitaires pour les addons Qiskit

Versions des packages

Le code de cette page a été développé avec les prérequis suivants. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-addon-utils~=0.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-utils qiskit-ibm-runtime

Le package d'utilitaires pour les addons Qiskit est un ensemble de fonctionnalités qui viennent compléter les workflows faisant appel à un ou plusieurs addons Qiskit. Il contient par exemple des fonctions pour créer des hamiltoniens, générer des circuits d'évolution temporelle de Trotter, et découper ou recombiner des circuits quantiques.

Installation

Il existe deux façons d'installer les utilitaires pour les addons Qiskit : via PyPI ou en compilant depuis les sources. Il est recommandé d'installer ces packages dans un environnement virtuel afin d'isoler les dépendances.

Installer depuis PyPI

La façon la plus simple d'installer le package d'utilitaires pour les addons Qiskit est via PyPI.

pip install 'qiskit-addon-utils'

Installer depuis les sources

Clique ici pour savoir comment installer ce package manuellement.

Si tu souhaites contribuer à ce package ou l'installer manuellement, commence par cloner le dépôt :

git clone git@github.com:Qiskit/qiskit-addon-utils.git

puis installe le package via pip. Si tu prévois d'exécuter les tutoriels inclus dans le dépôt, installe également les dépendances notebook. Si tu prévois de contribuer au développement, installe les dépendances dev.

pip install tox jupyterlab -e '.[notebook-dependencies,dev]'

Premiers pas avec les utilitaires

Le package qiskit-addon-utils comprend plusieurs modules, notamment un pour la génération de problèmes liés à la simulation de systèmes quantiques, un pour la coloration de graphes permettant de placer les portes dans un circuit quantique de manière plus efficace, et un pour le découpage de circuits, utile notamment pour la rétropropagation d'opérateurs. Les sections suivantes résument chaque module. La documentation de l'API du package contient également des informations utiles.

Génération de problèmes

Le contenu du module qiskit_addon_utils.problem_generators comprend :

  • Une fonction generate_xyz_hamiltonian(), qui génère une représentation SparsePauliOp d'un modèle XYZ de type Ising tenant compte de la connectivité :

H=(j,k)E(JxXjXk+JyYjYk+JzZjZk)+jV(hxXj+hyYj+hzZj)H = \sum_{(j,k)\in E} \left(J_x X_jX_k + J_yY_jY_k + J_zZ_jZ_k\right) + \sum_{j\in V} \left(h_x X_j + h_y Y_j + h_z Z_j\right)

  • Une fonction generate_time_evolution_circuit(), qui construit un circuit modélisant l'évolution temporelle d'un opérateur donné.
  • Trois objets PauliOrderStrategy permettant d'énumérer différents ordres de chaînes de Pauli. Cela est particulièrement utile en combinaison avec la coloration de graphes, et ces objets peuvent être passés en argument à generate_xyz_hamiltonian() et à generate_time_evolution_circuit().

Coloration de graphes

Le module qiskit_addon_utils.coloring sert à colorier les arêtes d'une carte de couplage et à exploiter ce coloriage pour placer les portes dans un circuit quantique de façon plus efficace. L'objectif de cette carte de couplage à arêtes colorées est de trouver un ensemble de couleurs tel qu'aucune paire d'arêtes de la même couleur ne partage un nœud commun. Sur un QPU, cela signifie que les portes situées sur des arêtes de même couleur (connexions entre qubits) peuvent être exécutées simultanément, ce qui accélère l'exécution du circuit.

Pour illustrer cela rapidement, tu peux utiliser la fonction auto_color_edges() pour générer un coloriage d'arêtes pour un circuit naïf qui applique une CZGate sur chaque connexion de qubit. L'extrait de code ci-dessous utilise la carte de couplage du backend FakeSherbrooke, crée ce circuit naïf, puis utilise auto_color_edges() pour générer un circuit équivalent plus efficace.

from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
from qiskit import QuantumCircuit
from qiskit_addon_utils.coloring import auto_color_edges
from qiskit_addon_utils.slicing import combine_slices, slice_by_depth
from collections import defaultdict

backend = FakeSherbrooke()
coupling_map = backend.coupling_map

# Create naive circuit
circuit = QuantumCircuit(backend.num_qubits)
for edge in coupling_map.graph.edge_list():
circuit.cz(edge[0], edge[1])

# Color the edges of the coupling map
coloring = auto_color_edges(coupling_map)
circuit_with_coloring = QuantumCircuit(backend.num_qubits)

# Make a reverse coloring dict in order to make the circuit
color_to_edge = defaultdict(list)
for edge, color in coloring.items():
color_to_edge[color].append(edge)

# Place edges in order of color
for edges in color_to_edge.values():
for edge in edges:
circuit_with_coloring.cz(edge[0], edge[1])

print(f"The circuit without using edge coloring has depth: {circuit.depth()}")
print(
f"The circuit using edge coloring has depth: {circuit_with_coloring.depth()}"
)
The circuit without using edge coloring has depth: 37
The circuit using edge coloring has depth: 3

Découpage

Enfin, le module qiskit-addon-utils.slicing contient des fonctions et des passes du transpiler pour travailler avec des « tranches » de circuit, c'est-à-dire des partitions temporelles d'un QuantumCircuit couvrant tous les qubits. Ces tranches sont principalement utilisées pour la rétropropagation d'opérateurs. Les quatre principales façons de découper un circuit sont : par type de porte, par profondeur, par coloriage, ou par instructions Barrier. Ces fonctions de découpage retournent une liste d'objets QuantumCircuit. Les tranches peuvent également être recombinées avec la fonction combine_slices(). Consulte la référence de l'API du module pour plus d'informations.

Voici quelques exemples illustrant comment créer ces tranches à partir du circuit suivant :

import numpy as np
from qiskit import QuantumCircuit

num_qubits = 9
qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
qubits_1 = [i for i in range(num_qubits) if i % 2 == 0]
qubits_2 = [i for i in range(num_qubits) if i % 2 == 1]
qc.cx(qubits_1[:-1], qubits_2)
qc.cx(qubits_2, qubits_1[1:])
qc.cx(qubits_1[-1], qubits_1[0])
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))
qc.draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Lorsqu'il n'y a pas de structure évidente à exploiter dans un circuit pour la rétropropagation d'opérateurs, tu peux partitionner le circuit en tranches d'une profondeur donnée.

# Slice circuit into partitions of depth 1
slices = slice_by_depth(qc, 1)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Dans certains cas, comme lors de l'exécution de circuits de Trotter pour modéliser la dynamique d'un système quantique, il peut être avantageux de découper par type de porte.

from qiskit_addon_utils.slicing import slice_by_gate_types

slices = slice_by_gate_types(qc)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Si ton workflow est conçu pour exploiter la connectivité physique des qubits sur le QPU cible, tu peux créer des tranches basées sur le coloriage des arêtes. L'extrait de code ci-dessous attribue un coloriage à trois couleurs aux arêtes du circuit et découpe le circuit en fonction de ce coloriage. (Remarque : cela n'affecte que les portes non locales. Les portes à un seul qubit seront découpées par type de porte.)

from qiskit_addon_utils.slicing import slice_by_coloring

# Assign a color to each set of connected qubits
coloring = {}
for i in range(num_qubits - 1):
coloring[(i, i + 1)] = i % 3
coloring[(num_qubits - 1, 0)] = 2

# Create a circuit with operations added in order of color
qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
edges = [
edge for color in range(3) for edge in coloring if coloring[edge] == color
]
for edge in edges:
qc.cx(edge[0], edge[1])
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))

# Create slices by edge color
slices = slice_by_coloring(qc, coloring=coloring)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Si tu as une stratégie de découpage personnalisée, tu peux placer des barrières dans le circuit pour indiquer les points de découpage, puis utiliser la fonction slice_by_barriers.

qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
qc.barrier()
qubits_1 = [i for i in range(num_qubits) if i % 2 == 0]
qubits_2 = [i for i in range(num_qubits) if i % 2 == 1]
qc.cx(qubits_1[:-1], qubits_2)
qc.cx(qubits_2, qubits_1[1:])
qc.cx(qubits_1[-1], qubits_1[0])
qc.barrier()
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))
qc.draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Une fois les barrières en place, tu peux examiner chaque tranche individuellement.

from qiskit_addon_utils.slicing import slice_by_barriers

slices = slice_by_barriers(qc)
slices[0].draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

slices[1].draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

slices[2].draw("mpl", scale=0.6)

Résultat de la cellule de code précédente

Prochaines étapes

Recommandations