Créer un plugin de transpileur
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
Créer un plugin de transpileur est une excellente façon de partager ton code de transpilation avec la communauté Qiskit au sens large, permettant à d'autres utilisateurs de profiter des fonctionnalités que tu as développées. Merci de ton intérêt pour la contribution à la communauté Qiskit !
Avant de créer un plugin de transpileur, tu dois décider quel type de plugin convient à ta situation. Il existe trois types de plugins de transpileur :
- Plugin d'étape de transpileur. Choisis cette option si tu définis un gestionnaire de passes pouvant remplacer l'une des 6 étapes d'un gestionnaire de passes par étapes prédéfini.
- Plugin de synthèse unitaire. Choisis cette option si ton code de transpilation prend en entrée une matrice unitaire (représentée sous forme de tableau Numpy) et produit en sortie la description d'un circuit quantique implémentant cette unitaire.
- Plugin de synthèse haut niveau. Choisis cette option si ton code de transpilation prend en entrée un « objet haut niveau » tel qu'un opérateur Clifford ou une fonction linéaire, et produit en sortie la description d'un circuit quantique implémentant cet objet. Les objets haut niveau sont représentés par des sous-classes de la classe Operation.
Une fois que tu as déterminé quel type de plugin créer, suis ces étapes pour le créer :
- Crée une sous-classe de la classe abstraite de plugin appropriée :
- PassManagerStagePlugin pour un plugin d'étape de transpileur,
- UnitarySynthesisPlugin pour un plugin de synthèse unitaire, et
- HighLevelSynthesisPlugin pour un plugin de synthèse haut niveau.
- Expose la classe comme point d'entrée setuptools dans les métadonnées du package, généralement en modifiant le fichier
pyproject.toml,setup.cfgousetup.pyde ton package Python.
Il n'y a pas de limite au nombre de plugins qu'un seul package peut définir, mais chaque plugin doit avoir un nom unique. Le SDK Qiskit lui-même inclut un certain nombre de plugins, dont les noms sont également réservés. Les noms réservés sont :
- Plugins d'étape de transpileur : voir ce tableau.
- Plugins de synthèse unitaire :
default,aqc,sk - Plugins de synthèse haut niveau :
| Classe d'opération | Nom de l'opération | Noms réservés |
|---|---|---|
| Clifford | clifford | default, ag, bm, greedy, layers, lnn |
| LinearFunction | linear_function | default, kms, pmh |
| PermutationGate | permutation | default, kms, basic, acg, token_swapper |
Dans les sections suivantes, nous montrons des exemples de ces étapes pour les différents types de plugins. Dans ces exemples, nous supposons que nous créons un package Python appelé my_qiskit_plugin. Pour des informations sur la création de packages Python, tu peux consulter ce tutoriel sur le site Python.
Exemple : Créer un plugin d'étape de transpileur
Dans cet exemple, nous créons un plugin d'étape de transpileur pour l'étape layout (voir Étapes du transpileur pour une description des 6 étapes du pipeline de transpilation intégré de Qiskit).
Notre plugin exécute simplement VF2Layout pour un nombre d'essais qui dépend du niveau d'optimisation demandé.
Tout d'abord, nous créons une sous-classe de PassManagerStagePlugin. Il y a une méthode à implémenter, appelée pass_manager. Cette méthode prend en entrée un PassManagerConfig et retourne le gestionnaire de passes que nous définissons. L'objet PassManagerConfig stocke des informations sur le backend cible, telles que sa carte de couplage et ses portes de base.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
# This import is needed for python versions prior to 3.10
from __future__ import annotations
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import VF2Layout
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.preset_passmanagers.plugin import (
PassManagerStagePlugin,
)
class MyLayoutPlugin(PassManagerStagePlugin):
def pass_manager(
self,
pass_manager_config: PassManagerConfig,
optimization_level: int | None = None,
) -> PassManager:
layout_pm = PassManager(
[
VF2Layout(
coupling_map=pass_manager_config.coupling_map,
properties=pass_manager_config.backend_properties,
max_trials=optimization_level * 10 + 1,
target=pass_manager_config.target,
)
]
)
layout_pm += common.generate_embed_passmanager(
pass_manager_config.coupling_map
)
return layout_pm
Ensuite, nous exposons le plugin en ajoutant un point d'entrée dans les métadonnées de notre package Python.
Ici, nous supposons que la classe définie est exposée dans un module appelé my_qiskit_plugin, par exemple en étant importée dans le fichier __init__.py du module my_qiskit_plugin.
Nous modifions le fichier pyproject.toml, setup.cfg ou setup.py de notre package (selon le type de fichier que tu as choisi pour stocker les métadonnées de ton projet Python) :
- pyproject.toml
- setup.cfg
- setup.py
[project.entry-points."qiskit.transpiler.layout"]
"my_layout" = "my_qiskit_plugin:MyLayoutPlugin"
[options.entry_points]
qiskit.transpiler.layout =
my_layout = my_qiskit_plugin:MyLayoutPlugin
from setuptools import setup
setup(
# ...,
entry_points={
'qiskit.transpiler.layout': [
'my_layout = my_qiskit_plugin:MyLayoutPlugin',
]
}
)
Consulte le tableau des étapes de plugins de transpileur pour les points d'entrée et les attentes pour chaque étape de transpileur.
Pour vérifier que ton plugin est correctement détecté par Qiskit, installe ton package de plugin et suis les instructions dans Plugins de transpileur pour lister les plugins installés, et assure-toi que ton plugin apparaît dans la liste :
from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins
list_stage_plugins("layout")
['default', 'dense', 'sabre', 'trivial']
Si notre plugin d'exemple était installé, le nom my_layout apparaîtrait dans cette liste.
Si tu veux utiliser une étape de transpileur intégrée comme point de départ pour ton plugin d'étape de transpileur, tu peux obtenir le gestionnaire de passes d'une étape de transpileur intégrée à l'aide de PassManagerStagePluginManager. Le code suivant montre comment procéder pour obtenir l'étape d'optimisation intégrée au niveau d'optimisation 3.
from qiskit.transpiler.preset_passmanagers.plugin import (
PassManagerStagePluginManager,
)
# Initialize the plugin manager
plugin_manager = PassManagerStagePluginManager()
# Here we create a pass manager config to use as an example.
# Instead, you should use the pass manager config that you already received as input
# to the pass_manager method of your PassManagerStagePlugin.
pass_manager_config = PassManagerConfig()
# Obtain the desired built-in transpiler stage
optimization = plugin_manager.get_passmanager_stage(
"optimization", "default", pass_manager_config, optimization_level=3
)
Exemple : Créer un plugin de synthèse unitaire
Dans cet exemple, nous allons créer un plugin de synthèse unitaire qui utilise simplement la passe de transpilation intégrée UnitarySynthesis pour synthétiser une porte. Bien entendu, ton propre plugin fera quelque chose de plus intéressant.
La classe UnitarySynthesisPlugin définit l'interface et le contrat pour les plugins de synthèse unitaire. La méthode principale est
run,
qui prend en entrée un tableau Numpy stockant une matrice unitaire
et retourne un DAGCircuit représentant le circuit synthétisé à partir de cette matrice unitaire.
En plus de la méthode run, plusieurs méthodes de propriété doivent être définies.
Voir UnitarySynthesisPlugin pour la documentation de toutes les propriétés requises.
Créons notre sous-classe UnitarySynthesisPlugin :
import numpy as np
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.converters import circuit_to_dag
from qiskit.dagcircuit.dagcircuit import DAGCircuit
from qiskit.quantum_info import Operator
from qiskit.transpiler.passes import UnitarySynthesis
from qiskit.transpiler.passes.synthesis.plugin import UnitarySynthesisPlugin
class MyUnitarySynthesisPlugin(UnitarySynthesisPlugin):
@property
def supports_basis_gates(self):
# Returns True if the plugin can target a list of basis gates
return True
@property
def supports_coupling_map(self):
# Returns True if the plugin can synthesize for a given coupling map
return False
@property
def supports_natural_direction(self):
# Returns True if the plugin supports a toggle for considering
# directionality of 2-qubit gates
return False
@property
def supports_pulse_optimize(self):
# Returns True if the plugin can optimize pulses during synthesis
return False
@property
def supports_gate_lengths(self):
# Returns True if the plugin can accept information about gate lengths
return False
@property
def supports_gate_errors(self):
# Returns True if the plugin can accept information about gate errors
return False
@property
def supports_gate_lengths_by_qubit(self):
# Returns True if the plugin can accept information about gate lengths
# (The format of the input differs from supports_gate_lengths)
return False
@property
def supports_gate_errors_by_qubit(self):
# Returns True if the plugin can accept information about gate errors
# (The format of the input differs from supports_gate_errors)
return False
@property
def min_qubits(self):
# Returns the minimum number of qubits the plugin supports
return None
@property
def max_qubits(self):
# Returns the maximum number of qubits the plugin supports
return None
@property
def supported_bases(self):
# Returns a dictionary of supported bases for synthesis
return None
def run(self, unitary: np.ndarray, **options) -> DAGCircuit:
basis_gates = options["basis_gates"]
synth_pass = UnitarySynthesis(basis_gates, min_qubits=3)
qubits = QuantumRegister(3)
circuit = QuantumCircuit(qubits)
circuit.append(Operator(unitary).to_instruction(), qubits)
dag_circuit = synth_pass.run(circuit_to_dag(circuit))
return dag_circuit
Si tu constates que les entrées disponibles pour la méthode run
sont insuffisantes pour tes besoins, n'hésite pas à ouvrir un ticket en expliquant tes exigences. Les modifications de l'interface du plugin, comme l'ajout d'entrées optionnelles supplémentaires, seront réalisées de manière rétrocompatible afin de ne pas nécessiter de changements de la part des plugins existants.
Toutes les méthodes préfixées par supports_ sont réservées dans une classe dérivée de UnitarySynthesisPlugin dans le cadre de l'interface. Tu ne dois pas définir de méthodes supports_* personnalisées dans une sous-classe qui ne sont pas définies dans la classe abstraite.
Ensuite, nous exposons le plugin en ajoutant un point d'entrée dans les métadonnées de notre package Python.
Ici, nous supposons que la classe définie est exposée dans un module appelé my_qiskit_plugin, par exemple en étant importée dans le fichier __init__.py du module my_qiskit_plugin.
Nous modifions le fichier pyproject.toml, setup.cfg ou setup.py de notre package :
- pyproject.toml
- setup.cfg
- setup.py
[project.entry-points."qiskit.unitary_synthesis"]
"my_unitary_synthesis" = "my_qiskit_plugin:MyUnitarySynthesisPlugin"
[options.entry_points]
qiskit.unitary_synthesis =
my_unitary_synthesis = my_qiskit_plugin:MyUnitarySynthesisPlugin
from setuptools import setup
setup(
# ...,
entry_points={
'qiskit.unitary_synthesis': [
'my_unitary_synthesis = my_qiskit_plugin:MyUnitarySynthesisPlugin',
]
}
)
Comme précédemment, si ton projet utilise setup.cfg ou setup.py au lieu de pyproject.toml, consulte la documentation setuptools pour adapter ces lignes à ta situation.
Pour vérifier que ton plugin est correctement détecté par Qiskit, installe ton package de plugin et suis les instructions dans Plugins de transpileur pour lister les plugins installés, et assure-toi que ton plugin apparaît dans la liste :
from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names
unitary_synthesis_plugin_names()
['aqc', 'clifford', 'default', 'gridsynth', 'sk']
Si notre plugin d'exemple était installé, le nom my_unitary_synthesis apparaîtrait dans cette liste.
Pour prendre en charge les plugins de synthèse unitaire qui exposent plusieurs options,
l'interface de plugin propose une option permettant aux utilisateurs de fournir un
dictionnaire de configuration libre. Celui-ci sera transmis à la méthode run
via l'argument nommé options. Si ton plugin dispose de ces options de configuration, tu dois les documenter clairement.
Exemple : Créer un plugin de synthèse haut niveau
Dans cet exemple, nous allons créer un plugin de synthèse haut niveau qui utilise simplement la fonction intégrée synth_clifford_bm pour synthétiser un opérateur Clifford.
La classe HighLevelSynthesisPlugin définit l'interface et le contrat pour les plugins de synthèse haut niveau. La méthode principale est run.
L'argument positionnel high_level_object est une Operation représentant l'objet « haut niveau » à synthétiser. Par exemple, il peut s'agir d'une
LinearFunction ou d'un
Clifford.
Les arguments nommés suivants sont présents :
targetspécifie le backend cible, permettant au plugin d'accéder à toutes les informations spécifiques à la cible, telles que la carte de couplage, l'ensemble de portes supportées, etc.coupling_mapspécifie uniquement la carte de couplage, et n'est utilisé que lorsquetargetn'est pas spécifié.qubitsspécifie la liste des qubits sur lesquels l'objet haut niveau est défini, dans le cas où la synthèse est effectuée sur le circuit physique. Une valeur deNoneindique que le placement n'a pas encore été choisi et que les qubits physiques dans la cible ou la carte de couplage sur lesquels cette opération s'applique n'ont pas encore été déterminés.options, un dictionnaire de configuration libre pour les options spécifiques au plugin. Si ton plugin dispose de ces options de configuration, tu dois les documenter clairement.
La méthode run retourne un QuantumCircuit
représentant le circuit synthétisé à partir de cet objet haut niveau.
Il est également possible de retourner None, indiquant que le plugin est incapable de synthétiser l'objet haut niveau donné.
La synthèse effective des objets haut niveau est réalisée par la passe de transpilation
HighLevelSynthesis.
En plus de la méthode run, plusieurs méthodes de propriété doivent être définies.
Voir HighLevelSynthesisPlugin pour la documentation de toutes les propriétés requises.
Définissons notre sous-classe HighLevelSynthesisPlugin :
from qiskit.synthesis import synth_clifford_bm
from qiskit.transpiler.passes.synthesis.plugin import HighLevelSynthesisPlugin
class MyCliffordSynthesisPlugin(HighLevelSynthesisPlugin):
def run(
self,
high_level_object,
coupling_map=None,
target=None,
qubits=None,
**options,
) -> QuantumCircuit:
if high_level_object.num_qubits <= 3:
return synth_clifford_bm(high_level_object)
else:
return None
Ce plugin synthétise les objets de type Clifford ayant
au plus 3 qubits, en utilisant la méthode synth_clifford_bm.
Ensuite, nous exposons le plugin en ajoutant un point d'entrée dans les métadonnées de notre package Python.
Ici, nous supposons que la classe définie est exposée dans un module appelé my_qiskit_plugin, par exemple en étant importée dans le fichier __init__.py du module my_qiskit_plugin.
Nous modifions le fichier pyproject.toml, setup.cfg ou setup.py de notre package :
- pyproject.toml
- setup.cfg
- setup.py
[project.entry-points."qiskit.synthesis"]
"clifford.my_clifford_synthesis" = "my_qiskit_plugin:MyCliffordSynthesisPlugin"
[options.entry_points]
qiskit.synthesis =
clifford.my_clifford_synthesis = my_qiskit_plugin:MyCliffordSynthesisPlugin
from setuptools import setup
setup(
# ...,
entry_points={
'qiskit.synthesis': [
'clifford.my_clifford_synthesis = my_qiskit_plugin:MyCliffordSynthesisPlugin',
]
}
)
Le name (nom) est composé de deux parties séparées par un point (.) :
- Le nom du type d'Operation que le plugin synthétise (dans ce cas,
clifford). Note que cette chaîne correspond à l'attributnamede la classe Operation, et non au nom de la classe elle-même. - Le nom du plugin (dans ce cas,
special).
Comme précédemment, si ton projet utilise setup.cfg ou setup.py au lieu de pyproject.toml, consulte la documentation setuptools pour adapter ces lignes à ta situation.
Pour vérifier que ton plugin est correctement détecté par Qiskit, installe ton package de plugin et suis les instructions dans Plugins de transpileur pour lister les plugins installés, et assure-toi que ton plugin apparaît dans la liste :
from qiskit.transpiler.passes.synthesis import (
high_level_synthesis_plugin_names,
)
high_level_synthesis_plugin_names("clifford")
['ag', 'bm', 'default', 'greedy', 'layers', 'lnn', 'rb_default']
Si notre plugin d'exemple était installé, le nom my_clifford_synthesis apparaîtrait dans cette liste.
- Soumets ton plugin à l'écosystème Qiskit !.
- Consulte les tutoriels pour des exemples de transpilation et d'exécution de circuits quantiques.