Ein Transpiler-Plugin erstellen
Paketversionen
Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.
qiskit[all]~=2.3.0
Die Erstellung eines Transpiler-Plugins ist eine hervorragende Möglichkeit, deinen Transpilationscode mit der breiteren Qiskit-Community zu teilen, sodass andere Benutzer von der von dir entwickelten Funktionalität profitieren können. Vielen Dank für dein Interesse, zur Qiskit-Community beizutragen!
Bevor du ein Transpiler-Plugin erstellst, musst du entscheiden, welche Art von Plugin für deine Situation geeignet ist. Es gibt drei Arten von Transpiler-Plugins:
- Transpiler-Stage-Plugin. Wähle dies, wenn du einen Pass-Manager definierst, der eine der 6 Stufen eines voreingestellten gestuften Pass-Managers ersetzen kann.
- Unitary-Synthesis-Plugin. Wähle dies, wenn dein Transpilationscode als Eingabe eine unitäre Matrix (dargestellt als Numpy-Array) erhält und eine Beschreibung eines Quantenschaltkreises ausgibt, der diese unitäre Matrix implementiert.
- High-Level-Synthesis-Plugin. Wähle dies, wenn dein Transpilationscode als Eingabe ein "High-Level-Objekt" wie einen Clifford-Operator oder eine lineare Funktion erhält und eine Beschreibung eines Quantenschaltkreises ausgibt, der dieses High-Level-Objekt implementiert. High-Level-Objekte werden durch Unterklassen der Operation-Klasse dargestellt.
Sobald du bestimmt hast, welche Art von Plugin du erstellen möchtest, folge diesen Schritten zur Erstellung des Plugins:
- Erstelle eine Unterklasse der entsprechenden abstrakten Plugin-Klasse:
- PassManagerStagePlugin für ein Transpiler-Stage-Plugin,
- UnitarySynthesisPlugin für ein Unitary-Synthesis-Plugin, und
- HighLevelSynthesisPlugin für ein High-Level-Synthesis-Plugin.
- Mache die Klasse als setuptools entry point in den Paket-Metadaten verfügbar, typischerweise durch Bearbeiten der Datei
pyproject.toml,setup.cfgodersetup.pyfür dein Python-Paket.
Es gibt keine Begrenzung für die Anzahl der Plugins, die ein einzelnes Paket definieren kann, aber jedes Plugin muss einen eindeutigen Namen haben. Das Qiskit SDK selbst enthält eine Reihe von Plugins, deren Namen ebenfalls reserviert sind. Die reservierten Namen sind:
- Transpiler-Stage-Plugins: Siehe diese Tabelle.
- Unitary-Synthesis-Plugins:
default,aqc,sk - High-Level-Synthesis-Plugins:
| Operation class | Operation name | Reserved names |
|---|---|---|
| Clifford | clifford | default, ag, bm, greedy, layers, lnn |
| LinearFunction | linear_function | default, kms, pmh |
| PermutationGate | permutation | default, kms, basic, acg, token_swapper |
In den nächsten Abschnitten zeigen wir Beispiele für diese Schritte für die verschiedenen Plugin-Typen. In diesen Beispielen gehen wir davon aus, dass wir ein Python-Paket namens my_qiskit_plugin erstellen. Informationen zur Erstellung von Python-Paketen findest du in diesem Tutorial auf der Python-Website.
Beispiel: Ein Transpiler-Stage-Plugin erstellen
In diesem Beispiel erstellen wir ein Transpiler-Stage-Plugin für die layout-Stufe (siehe Transpiler-Stufen für eine Beschreibung der 6 Stufen der in Qiskit integrierten Transpilations-Pipeline).
Unser Plugin führt einfach VF2Layout für eine Anzahl von Versuchen aus, die vom angeforderten Optimierungslevel abhängt.
Zuerst erstellen wir eine Unterklasse von PassManagerStagePlugin. Es gibt eine Methode, die wir implementieren müssen, genannt pass_manager. Diese Methode erhält als Eingabe ein PassManagerConfig und gibt den Pass-Manager zurück, den wir definieren. Das PassManagerConfig-Objekt speichert Informationen über das Ziel-Backend, wie dessen Coupling Map und Basis-Gates.
# 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
Nun machen wir das Plugin verfügbar, indem wir einen Entry-Point in unseren Python-Paket-Metadaten hinzufügen.
Hier gehen wir davon aus, dass die von uns definierte Klasse in einem Modul namens my_qiskit_plugin verfügbar gemacht wird, beispielsweise durch Import in der __init__.py-Datei des my_qiskit_plugin-Moduls.
Wir bearbeiten die Datei pyproject.toml, setup.cfg oder setup.py unseres Pakets (je nachdem, welche Art von Datei du zur Speicherung deiner Python-Projekt-Metadaten gewählt hast):
- 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',
]
}
)
Siehe die Tabelle der Transpiler-Plugin-Stufen für die Entry-Points und Erwartungen für jede Transpiler-Stufe.
Um zu überprüfen, ob dein Plugin erfolgreich von Qiskit erkannt wird, installiere dein Plugin-Paket und folge den Anweisungen unter Transpiler-Plugins zum Auflisten installierter Plugins, und stelle sicher, dass dein Plugin in der Liste erscheint:
from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins
list_stage_plugins("layout")
['default', 'dense', 'sabre', 'trivial']
Wenn unser Beispiel-Plugin installiert wäre, würde der Name my_layout in dieser Liste erscheinen.
Wenn du eine integrierte Transpiler-Stufe als Ausgangspunkt für dein Transpiler-Stage-Plugin verwenden möchtest, kannst du den Pass-Manager für eine integrierte Transpiler-Stufe mit PassManagerStagePluginManager erhalten. Die folgende Codezelle zeigt, wie dies durchgeführt wird, um die integrierte Optimierungsstufe für Optimierungslevel 3 zu erhalten.
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
)
Beispiel: Ein Unitary-Synthesis-Plugin erstellen
In diesem Beispiel erstellen wir ein Unitary-Synthesis-Plugin, das einfach den integrierten UnitarySynthesis Transpilationspass verwendet, um ein Gate zu synthetisieren. Natürlich wird dein eigenes Plugin etwas Interessanteres tun als das.
Die UnitarySynthesisPlugin-Klasse definiert die Schnittstelle und den Vertrag für Unitary-Synthesis-Plugins. Die primäre Methode ist
run,
die als Eingabe ein Numpy-Array erhält, das eine unitäre Matrix speichert,
und einen DAGCircuit zurückgibt, der den aus dieser unitären Matrix synthetisierten Schaltkreis darstellt.
Zusätzlich zur run-Methode gibt es eine Reihe von Property-Methoden, die definiert werden müssen.
Siehe UnitarySynthesisPlugin für die Dokumentation aller erforderlichen Properties.
Erstellen wir unsere UnitarySynthesisPlugin-Unterklasse:
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
Wenn du feststellst, dass die für die run-Methode verfügbaren Eingaben für deine Zwecke nicht ausreichen, öffne bitte ein Issue, in dem du deine Anforderungen erläuterst. Änderungen an der Plugin-Schnittstelle, wie das Hinzufügen zusätzlicher optionaler Eingaben, werden auf rückwärtskompatible Weise vorgenommen, sodass sie keine Änderungen an bestehenden Plugins erfordern.
Alle Methoden mit dem Präfix supports_ sind auf einer von UnitarySynthesisPlugin abgeleiteten Klasse als Teil der Schnittstelle reserviert. Du solltest keine benutzerdefinierten supports_*-Methoden auf einer Unterklasse definieren, die nicht in der abstrakten Klasse definiert sind.
Nun machen wir das Plugin verfügbar, indem wir einen Entry-Point in unseren Python-Paket-Metadaten hinzufügen.
Hier gehen wir davon aus, dass die von uns definierte Klasse in einem Modul namens my_qiskit_plugin verfügbar gemacht wird, beispielsweise durch Import in der __init__.py-Datei des my_qiskit_plugin-Moduls.
Wir bearbeiten die Datei pyproject.toml, setup.cfg oder setup.py unseres Pakets:
- 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',
]
}
)
Wie zuvor, wenn dein Projekt setup.cfg oder setup.py anstelle von pyproject.toml verwendet, sieh die setuptools-Dokumentation, um zu erfahren, wie du diese Zeilen für deine Situation anpasst.
Um zu überprüfen, ob dein Plugin erfolgreich von Qiskit erkannt wird, installiere dein Plugin-Paket und folge den Anweisungen unter Transpiler-Plugins zum Auflisten installierter Plugins, und stelle sicher, dass dein Plugin in der Liste erscheint:
from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names
unitary_synthesis_plugin_names()
['aqc', 'clifford', 'default', 'gridsynth', 'sk']
Wenn unser Beispiel-Plugin installiert wäre, würde der Name my_unitary_synthesis in dieser Liste erscheinen.
Um Unitary-Synthesis-Plugins zu unterstützen, die mehrere Optionen bereitstellen,
verfügt die Plugin-Schnittstelle über eine Option, mit der Benutzer ein frei konfigurierbares
Configuration-Dictionary bereitstellen können. Dieses wird über das Schlüsselwortargument options an die run-Methode übergeben. Wenn dein Plugin diese Konfigurationsoptionen hat, solltest du diese klar dokumentieren.
Beispiel: Ein High-Level-Synthesis-Plugin erstellen
In diesem Beispiel erstellen wir ein High-Level-Synthesis-Plugin, das einfach die integrierte synth_clifford_bm-Funktion verwendet, um einen Clifford-Operator zu synthetisieren.
Die HighLevelSynthesisPlugin-Klasse definiert die Schnittstelle und den Vertrag für High-Level-Synthesis-Plugins. Die primäre Methode ist run.
Das Positionsargument high_level_object ist eine Operation, die das zu synthetisierende "High-Level"-Objekt darstellt. Es könnte beispielsweise eine
LinearFunction oder ein
Clifford sein.
Die folgenden Schlüsselwortargumente sind vorhanden:
targetspezifiziert das Ziel-Backend und ermöglicht dem Plugin den Zugriff auf alle zielspezifischen Informationen, wie die Coupling Map, den unterstützten Gate-Satz usw.coupling_mapspezifiziert nur die Coupling Map und wird nur verwendet, wenntargetnicht spezifiziert ist.qubitsspezifiziert die Liste der Qubits, über die das High-Level-Objekt definiert ist, falls die Synthese auf dem physischen Schaltkreis durchgeführt wird. Ein Wert vonNonezeigt an, dass das Layout noch nicht gewählt wurde und die physischen Qubits im Target oder der Coupling Map, auf denen diese Operation ausgeführt wird, noch nicht bestimmt wurden.options, ein frei konfigurierbares Configuration-Dictionary für plugin-spezifische Optionen. Wenn dein Plugin diese Konfigurationsoptionen hat, solltest du diese klar dokumentieren.
Die run-Methode gibt einen QuantumCircuit zurück,
der den aus diesem High-Level-Objekt synthetisierten Schaltkreis darstellt.
Es ist auch erlaubt, None zurückzugeben, was anzeigt, dass das Plugin das gegebene High-Level-Objekt nicht synthetisieren kann.
Die tatsächliche Synthese von High-Level-Objekten wird vom
HighLevelSynthesis
Transpiler-Pass durchgeführt.
Zusätzlich zur run-Methode gibt es eine Reihe von Property-Methoden, die definiert werden müssen.
Siehe HighLevelSynthesisPlugin für die Dokumentation aller erforderlichen Properties.
Definieren wir unsere HighLevelSynthesisPlugin-Unterklasse:
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
Dieses Plugin synthetisiert Objekte vom Typ Clifford, die
höchstens 3 Qubits haben, unter Verwendung der synth_clifford_bm-Methode.
Nun machen wir das Plugin verfügbar, indem wir einen Entry-Point in unseren Python-Paket-Metadaten hinzufügen.
Hier gehen wir davon aus, dass die von uns definierte Klasse in einem Modul namens my_qiskit_plugin verfügbar gemacht wird, beispielsweise durch Import in der __init__.py-Datei des my_qiskit_plugin-Moduls.
Wir bearbeiten die Datei pyproject.toml, setup.cfg oder setup.py unseres Pakets:
- 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',
]
}
)
Der name besteht aus zwei durch einen Punkt (.) getrennten Teilen:
- Der Name des Typs der Operation, die das Plugin synthetisiert (in diesem Fall
clifford). Beachte, dass dieser String demname-Attribut der Operation-Klasse entspricht und nicht dem Namen der Klasse selbst. - Der Name des Plugins (in diesem Fall
special).
Wie zuvor, wenn dein Projekt setup.cfg oder setup.py anstelle von pyproject.toml verwendet, sieh die setuptools-Dokumentation, um zu erfahren, wie du diese Zeilen für deine Situation anpasst.
Um zu überprüfen, ob dein Plugin erfolgreich von Qiskit erkannt wird, installiere dein Plugin-Paket und folge den Anweisungen unter Transpiler-Plugins zum Auflisten installierter Plugins, und stelle sicher, dass dein Plugin in der Liste erscheint:
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']
Wenn unser Beispiel-Plugin installiert wäre, würde der Name my_clifford_synthesis in dieser Liste erscheinen.
- Reiche dein Plugin beim Qiskit Ecosystem ein!.
- Schau dir die Tutorials für Beispiele zur Transpilation und Ausführung von Quantenschaltkreisen an.