Creare un plugin per il transpiler
Versioni dei pacchetti
Il codice in questa pagina è stato sviluppato utilizzando i seguenti requisiti. Si consiglia di usare queste versioni o versioni più recenti.
qiskit[all]~=2.3.0
Creare un plugin per il transpiler è un ottimo modo per condividere il tuo codice di transpilazione con la più ampia comunità Qiskit, permettendo ad altri utenti di beneficiare delle funzionalità che hai sviluppato. Grazie per il tuo interesse a contribuire alla comunità Qiskit!
Prima di creare un plugin per il transpiler, devi decidere quale tipo di plugin è più adatto alla tua situazione. Esistono tre tipi di plugin per il transpiler:
- Plugin per le fasi del transpiler. Scegli questo se stai definendo un pass manager che può essere sostituito a una delle 6 fasi di un preset staged pass manager.
- Plugin di sintesi unitaria. Scegli questo se il tuo codice di transpilazione riceve come input una matrice unitaria (rappresentata come array Numpy) e produce come output la descrizione di un circuito quantistico che implementa quella unitaria.
- Plugin di sintesi ad alto livello. Scegli questo se il tuo codice di transpilazione riceve come input un "oggetto ad alto livello", come un operatore di Clifford o una funzione lineare, e produce come output la descrizione di un circuito quantistico che implementa quell'oggetto. Gli oggetti ad alto livello sono rappresentati da sottoclassi della classe Operation.
Una volta stabilito quale tipo di plugin creare, segui questi passaggi:
- Crea una sottoclasse dell'opportuna classe plugin astratta:
- PassManagerStagePlugin per un plugin di fase del transpiler,
- UnitarySynthesisPlugin per un plugin di sintesi unitaria, e
- HighLevelSynthesisPlugin per un plugin di sintesi ad alto livello.
- Esponi la classe come entry point di setuptools nei metadati del pacchetto, tipicamente modificando il file
pyproject.toml,setup.cfgosetup.pydel tuo pacchetto Python.
Non esiste un limite al numero di plugin che un singolo pacchetto può definire, ma ogni plugin deve avere un nome univoco. Lo stesso Qiskit SDK include una serie di plugin i cui nomi sono riservati. I nomi riservati sono:
- Plugin per le fasi del transpiler: vedi questa tabella.
- Plugin di sintesi unitaria:
default,aqc,sk - Plugin di sintesi ad alto livello:
| Classe Operation | Nome dell'operazione | Nomi riservati |
|---|---|---|
| Clifford | clifford | default, ag, bm, greedy, layers, lnn |
| LinearFunction | linear_function | default, kms, pmh |
| PermutationGate | permutation | default, kms, basic, acg, token_swapper |
Nelle sezioni successive mostriamo degli esempi di questi passaggi per i diversi tipi di plugin. In questi esempi assumiamo di stare creando un pacchetto Python chiamato my_qiskit_plugin. Per informazioni su come creare pacchetti Python, puoi consultare questo tutorial sul sito di Python.
Esempio: creare un plugin per una fase del transpiler
In questo esempio creiamo un plugin per la fase layout del transpiler (vedi Fasi del transpiler per una descrizione delle 6 fasi della pipeline di transpilazione integrata in Qiskit).
Il nostro plugin esegue semplicemente VF2Layout per un numero di tentativi che dipende dal livello di ottimizzazione richiesto.
Per prima cosa creiamo una sottoclasse di PassManagerStagePlugin. C'è un unico metodo da implementare, chiamato pass_manager. Questo metodo riceve come input un PassManagerConfig e restituisce il pass manager che stiamo definendo. L'oggetto PassManagerConfig memorizza informazioni sul Backend di destinazione, come la sua coupling map e le 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
Ora esponiamo il plugin aggiungendo un entry point nei metadati del nostro pacchetto Python.
Qui assumiamo che la classe definita sia esposta in un modulo chiamato my_qiskit_plugin, ad esempio importandola nel file __init__.py del modulo my_qiskit_plugin.
Modifichiamo il file pyproject.toml, setup.cfg o setup.py del nostro pacchetto (a seconda del tipo di file scelto per i metadati del progetto 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',
]
}
)
Consulta la tabella delle fasi dei plugin del transpiler per gli entry point e i requisiti di ciascuna fase del transpiler.
Per verificare che il tuo plugin venga rilevato correttamente da Qiskit, installa il pacchetto del plugin e segui le istruzioni in Plugin per il transpiler per elencare i plugin installati, assicurandoti che il tuo plugin compaia nell'elenco:
from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins
list_stage_plugins("layout")
['default', 'dense', 'sabre', 'trivial']
Se il nostro plugin di esempio fosse installato, il nome my_layout apparirebbe in questo elenco.
Se vuoi usare una fase del transpiler integrata come punto di partenza per il tuo plugin, puoi ottenere il pass manager per una fase integrata tramite PassManagerStagePluginManager. La seguente cella di codice mostra come fare per ottenere la fase di ottimizzazione integrata al livello di ottimizzazione 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
)
Esempio: creare un plugin di sintesi unitaria
In questo esempio creeremo un plugin di sintesi unitaria che utilizza semplicemente il pass di transpilazione integrato UnitarySynthesis per sintetizzare un gate. Naturalmente, il tuo plugin farà qualcosa di più interessante.
La classe UnitarySynthesisPlugin definisce l'interfaccia e il contratto per i plugin di sintesi unitaria. Il metodo principale è
run,
che riceve come input un array Numpy contenente una matrice unitaria
e restituisce un DAGCircuit che rappresenta il circuito sintetizzato a partire da quella matrice unitaria.
Oltre al metodo run, occorre definire una serie di metodi property.
Consulta UnitarySynthesisPlugin per la documentazione di tutte le property richieste.
Creiamo la nostra sottoclasse di 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
Se ritieni che gli input disponibili nel metodo run
siano insufficienti per le tue esigenze, ti invitiamo ad aprire una issue spiegando i tuoi requisiti. Le modifiche all'interfaccia del plugin, come l'aggiunta di input opzionali aggiuntivi, verranno effettuate in modo retrocompatibile, così da non richiedere modifiche ai plugin esistenti.
Tutti i metodi con prefisso supports_ sono riservati nelle classi derivate da UnitarySynthesisPlugin come parte dell'interfaccia. Non devi definire metodi supports_* personalizzati in una sottoclasse se non sono definiti nella classe astratta.
Ora esponiamo il plugin aggiungendo un entry point nei metadati del nostro pacchetto Python.
Qui assumiamo che la classe definita sia esposta in un modulo chiamato my_qiskit_plugin, ad esempio importandola nel file __init__.py del modulo my_qiskit_plugin.
Modifichiamo il file pyproject.toml, setup.cfg o setup.py del nostro pacchetto:
- 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',
]
}
)
Come in precedenza, se il tuo progetto usa setup.cfg o setup.py invece di pyproject.toml, consulta la documentazione di setuptools per sapere come adattare queste righe alla tua situazione.
Per verificare che il tuo plugin venga rilevato correttamente da Qiskit, installa il pacchetto del plugin e segui le istruzioni in Plugin per il transpiler per elencare i plugin installati, assicurandoti che il tuo plugin compaia nell'elenco:
from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names
unitary_synthesis_plugin_names()
['aqc', 'clifford', 'default', 'gridsynth', 'sk']
Se il nostro plugin di esempio fosse installato, il nome my_unitary_synthesis apparirebbe in questo elenco.
Per supportare plugin di sintesi unitaria che espongono più opzioni,
l'interfaccia del plugin prevede la possibilità per gli utenti di fornire un dizionario di configurazione libero. Questo dizionario verrà passato al metodo run
tramite l'argomento keyword options. Se il tuo plugin dispone di queste opzioni di configurazione, dovresti documentarle chiaramente.
Esempio: creare un plugin di sintesi ad alto livello
In questo esempio creeremo un plugin di sintesi ad alto livello che utilizza semplicemente la funzione integrata synth_clifford_bm per sintetizzare un operatore di Clifford.
La classe HighLevelSynthesisPlugin definisce l'interfaccia e il contratto per i plugin di sintesi ad alto livello. Il metodo principale è run.
L'argomento posizionale high_level_object è un'Operation che rappresenta l'oggetto "ad alto livello" da sintetizzare. Ad esempio, potrebbe essere una
LinearFunction oppure un
Clifford.
Sono presenti i seguenti argomenti keyword:
targetspecifica il Backend di destinazione, consentendo al plugin di accedere a tutte le informazioni specifiche del target, come la coupling map, il set di gate supportato e così viacoupling_mapspecifica solo la coupling map, ed è utilizzata solo quandotargetnon è specificato.qubitsspecifica la lista di qubit su cui è definito l'oggetto ad alto livello, nel caso in cui la sintesi venga eseguita sul circuito fisico. Un valore diNoneindica che il layout non è ancora stato scelto e che i qubit fisici nel target o nella coupling map su cui opera questa operazione non sono ancora stati determinati.options, un dizionario di configurazione libero per le opzioni specifiche del plugin. Se il tuo plugin dispone di queste opzioni di configurazione, dovresti documentarle chiaramente.
Il metodo run restituisce un QuantumCircuit
che rappresenta il circuito sintetizzato a partire dall'oggetto ad alto livello.
È anche consentito restituire None, indicando che il plugin non è in grado di sintetizzare l'oggetto ad alto livello fornito.
La sintesi effettiva degli oggetti ad alto livello è eseguita dal pass del transpiler
HighLevelSynthesis.
Oltre al metodo run, occorre definire una serie di metodi property.
Consulta HighLevelSynthesisPlugin per la documentazione di tutte le property richieste.
Definiamo la nostra sottoclasse di 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
Questo plugin sintetizza oggetti di tipo Clifford con
al massimo 3 qubit, usando il metodo synth_clifford_bm.
Ora esponiamo il plugin aggiungendo un entry point nei metadati del nostro pacchetto Python.
Qui assumiamo che la classe definita sia esposta in un modulo chiamato my_qiskit_plugin, ad esempio importandola nel file __init__.py del modulo my_qiskit_plugin.
Modifichiamo il file pyproject.toml, setup.cfg o setup.py del nostro pacchetto:
- 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',
]
}
)
Il name è composto da due parti separate da un punto (.):
- Il nome del tipo di Operation che il plugin sintetizza (in questo caso,
clifford). Tieni presente che questa stringa corrisponde all'attributonamedella classe Operation, e non al nome della classe stessa. - Il nome del plugin (in questo caso,
special).
Come in precedenza, se il tuo progetto usa setup.cfg o setup.py invece di pyproject.toml, consulta la documentazione di setuptools per sapere come adattare queste righe alla tua situazione.
Per verificare che il tuo plugin venga rilevato correttamente da Qiskit, installa il pacchetto del plugin e segui le istruzioni in Plugin per il transpiler per elencare i plugin installati, assicurandoti che il tuo plugin compaia nell'elenco:
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']
Se il nostro plugin di esempio fosse installato, il nome my_clifford_synthesis apparirebbe in questo elenco.
- Invia il tuo plugin all'Ecosistema Qiskit!.
- Dai un'occhiata ai tutorial per esempi su come transpilare ed eseguire circuiti quantistici.