Przejdź do głównej treści

Tworzenie wtyczki transpilera

Wersje pakietów

Kod na tej stronie został opracowany z użyciem poniższych wymagań. Zalecamy korzystanie z tych lub nowszych wersji.

qiskit[all]~=2.3.0

Tworzenie wtyczki transpilera to świetny sposób na udostępnienie swojego kodu transpilacji szerszej społeczności Qiskit, dzięki czemu inni użytkownicy mogą czerpać korzyści z opracowanej przez ciebie funkcjonalności. Dziękujemy za zainteresowanie wkładem w społeczność Qiskit!

Zanim stworzysz wtyczkę transpilera, musisz zdecydować, jaki jej rodzaj jest odpowiedni dla twojej sytuacji. Istnieją trzy rodzaje wtyczek transpilera:

  • Wtyczka etapu transpilera. Wybierz tę opcję, jeśli definiujesz menedżer przejść, który może zastąpić jeden z 6 etapów wstępnie skonfigurowanego etapowego menedżera przejść.
  • Wtyczka syntezy unitarnej. Wybierz tę opcję, jeśli twój kod transpilacji przyjmuje jako wejście macierz unitarną (reprezentowaną jako tablica Numpy) i zwraca opis Circuit kwantowego realizującego tę macierz unitarną.
  • Wtyczka syntezy wysokiego poziomu. Wybierz tę opcję, jeśli twój kod transpilacji przyjmuje jako wejście „obiekt wysokiego poziomu", taki jak operator Clifforda lub funkcja liniowa, i zwraca opis Circuit kwantowego realizującego ten obiekt wysokiego poziomu. Obiekty wysokiego poziomu są reprezentowane przez podklasy klasy Operation.

Po ustaleniu, jaki rodzaj wtyczki chcesz stworzyć, wykonaj poniższe kroki:

  1. Utwórz podklasę odpowiedniej abstrakcyjnej klasy wtyczki:
  2. Udostępnij klasę jako punkt wejścia setuptools w metadanych pakietu — zazwyczaj przez edycję pliku pyproject.toml, setup.cfg lub setup.py swojego pakietu Python.

Nie ma limitu liczby wtyczek, które może definiować jeden pakiet, jednak każda wtyczka musi mieć unikalną nazwę. Sam Qiskit SDK zawiera wiele wtyczek, których nazwy są zarezerwowane. Zarezerwowane nazwy to:

  • Wtyczki etapów transpilera: patrz ta tabela.
  • Wtyczki syntezy unitarnej: default, aqc, sk
  • Wtyczki syntezy wysokiego poziomu:
Klasa operacjiNazwa operacjiZarezerwowane nazwy
Cliffordclifforddefault, ag, bm, greedy, layers, lnn
LinearFunctionlinear_functiondefault, kms, pmh
PermutationGatepermutationdefault, kms, basic, acg, token_swapper

W kolejnych sekcjach pokazujemy przykłady tych kroków dla różnych typów wtyczek. W tych przykładach zakładamy, że tworzymy pakiet Python o nazwie my_qiskit_plugin. Informacje na temat tworzenia pakietów Python znajdziesz w tym samouczku na stronie Python.

Przykład: tworzenie wtyczki etapu transpilera

W tym przykładzie tworzymy wtyczkę etapu transpilera dla etapu layout (opis 6 etapów wbudowanego potoku transpilacji Qiskit znajdziesz w artykule Etapy transpilera). Nasza wtyczka uruchamia po prostu VF2Layout z liczbą prób zależną od żądanego poziomu optymalizacji.

Najpierw tworzymy podklasę PassManagerStagePlugin. Musimy zaimplementować jedną metodę o nazwie pass_manager. Metoda ta przyjmuje jako wejście obiekt PassManagerConfig i zwraca definiowany przez nas menedżer przejść. Obiekt PassManagerConfig przechowuje informacje o docelowym Backend, takie jak mapa sprzężeń i bramki bazowe.

# 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

Teraz udostępniamy wtyczkę, dodając punkt wejścia w metadanych naszego pakietu Python. Zakładamy tutaj, że zdefiniowana przez nas klasa jest dostępna w module o nazwie my_qiskit_plugin, na przykład poprzez import w pliku __init__.py modułu my_qiskit_plugin. Edytujemy plik pyproject.toml, setup.cfg lub setup.py naszego pakietu (w zależności od tego, jaki rodzaj pliku wybrałeś do przechowywania metadanych projektu Python):

[project.entry-points."qiskit.transpiler.layout"]
"my_layout" = "my_qiskit_plugin:MyLayoutPlugin"

Zapoznaj się z tabelą etapów wtyczek transpilera, aby zobaczyć punkty wejścia i wymagania dla każdego etapu transpilera.

Aby sprawdzić, czy Qiskit wykrywa twoją wtyczkę poprawnie, zainstaluj pakiet wtyczki i postępuj zgodnie z instrukcjami w artykule Wtyczki transpilera dotyczącymi listowania zainstalowanych wtyczek — upewnij się, że twoja wtyczka pojawia się na liście:

from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins

list_stage_plugins("layout")
['default', 'dense', 'sabre', 'trivial']

Gdyby nasza przykładowa wtyczka była zainstalowana, nazwa my_layout pojawiłaby się na tej liście.

Jeśli chcesz użyć wbudowanego etapu transpilera jako punktu wyjścia dla swojej wtyczki etapu transpilera, możesz uzyskać menedżer przejść dla wbudowanego etapu transpilera za pomocą PassManagerStagePluginManager. Poniższy blok kodu pokazuje, jak uzyskać wbudowany etap optymalizacji dla poziomu optymalizacji 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
)

Przykład: tworzenie pluginu syntezy unitarnej

W tym przykładzie stworzymy plugin syntezy unitarnej, który po prostu używa wbudowanego przejścia transpilacji UnitarySynthesis do syntezy Gate'a. Twój własny plugin będzie oczywiście robił coś ciekawszego.

Klasa UnitarySynthesisPlugin definiuje interfejs i kontrakt dla pluginów syntezy unitarnej. Główną metodą jest run, która przyjmuje jako wejście tablicę Numpy przechowującą macierz unitarną i zwraca DAGCircuit reprezentujący Circuit zsyntetyzowany z tej macierzy unitarnej. Oprócz metody run należy zdefiniować szereg metod właściwości. Pełną dokumentację wszystkich wymaganych właściwości znajdziesz w UnitarySynthesisPlugin.

Stwórzmy naszą podklasę 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

Jeśli okaże się, że dane wejściowe dostępne w metodzie run są niewystarczające do twoich celów, otwórz zgłoszenie opisując swoje wymagania. Zmiany w interfejsie pluginu, takie jak dodanie opcjonalnych danych wejściowych, będą wprowadzane w sposób zapewniający wsteczną kompatybilność, tak aby nie wymagały zmian w istniejących pluginach.

Uwaga

Wszystkie metody z przedrostkiem supports_ są zarezerwowane w klasie pochodnej UnitarySynthesisPlugin jako część interfejsu. Nie należy definiować żadnych własnych metod supports_* w podklasie, które nie są zdefiniowane w klasie abstrakcyjnej.

Teraz udostępniamy plugin, dodając punkt wejścia w metadanych naszego pakietu Pythona. Zakładamy, że zdefiniowana klasa jest dostępna w module o nazwie my_qiskit_plugin, na przykład przez zaimportowanie jej w pliku __init__.py modułu my_qiskit_plugin. Edytujemy plik pyproject.toml, setup.cfg lub setup.py naszego pakietu:

[project.entry-points."qiskit.unitary_synthesis"]
"my_unitary_synthesis" = "my_qiskit_plugin:MyUnitarySynthesisPlugin"

Podobnie jak poprzednio, jeśli twój projekt używa setup.cfg lub setup.py zamiast pyproject.toml, zapoznaj się z dokumentacją setuptools, aby dowiedzieć się, jak dostosować te linie do swojej sytuacji.

Aby sprawdzić, czy twój plugin jest poprawnie wykrywany przez Qiskit, zainstaluj pakiet pluginu i postępuj zgodnie z instrukcjami zawartymi w Pluginy Transpilatora dotyczącymi listowania zainstalowanych pluginów, a następnie upewnij się, że twój plugin pojawia się na liście:

from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names

unitary_synthesis_plugin_names()
['aqc', 'clifford', 'default', 'gridsynth', 'sk']

Gdyby nasz przykładowy plugin był zainstalowany, nazwa my_unitary_synthesis pojawiłaby się na tej liście.

Aby obsłużyć pluginy syntezy unitarnej udostępniające wiele opcji, interfejs pluginu przewiduje możliwość przekazania przez użytkownika słownika konfiguracyjnego w dowolnym formacie. Zostanie on przekazany do metody run za pomocą argumentu kluczowego options. Jeśli twój plugin posiada takie opcje konfiguracyjne, powinieneś je wyraźnie udokumentować.

Przykład: Tworzenie wtyczki syntezy wysokiego poziomu

W tym przykładzie stworzymy wtyczkę syntezy wysokiego poziomu, która po prostu używa wbudowanej funkcji synth_clifford_bm do syntezy operatora Clifforda.

Klasa HighLevelSynthesisPlugin definiuje interfejs i kontrakt dla wtyczek syntezy wysokiego poziomu. Główną metodą jest run. Argument pozycyjny high_level_object to Operation reprezentujący obiekt „wysokiego poziomu", który ma zostać zsyntetyzowany. Może to być na przykład LinearFunction lub Clifford. Dostępne są następujące argumenty kluczowe:

  • target wskazuje docelowy Backend, umożliwiając wtyczce dostęp do wszystkich informacji specyficznych dla danego celu, takich jak mapa sprzężeń, obsługiwany zestaw Gate'ów i inne.
  • coupling_map wskazuje wyłącznie mapę sprzężeń i jest używany tylko wtedy, gdy target nie jest podany.
  • qubits wskazuje listę Qubitów, na których zdefiniowany jest obiekt wysokiego poziomu — w przypadku gdy synteza jest wykonywana na fizycznym Circuit. Wartość None oznacza, że układ nie został jeszcze wybrany i fizyczne Qubity w docelowym Backend lub mapie sprzężeń, na których operuje ta operacja, nie zostały jeszcze ustalone.
  • options — słownik konfiguracyjny dowolnej postaci przeznaczony dla opcji specyficznych dla wtyczki. Jeśli twoja wtyczka obsługuje takie opcje, powinnaś je wyraźnie udokumentować.

Metoda run zwraca QuantumCircuit reprezentujący Circuit zsyntetyzowany z danego obiektu wysokiego poziomu. Dozwolone jest również zwrócenie None, co oznacza, że wtyczka nie jest w stanie zsyntetyzować podanego obiektu wysokiego poziomu. Faktyczna synteza obiektów wysokiego poziomu jest wykonywana przez przebieg Transpilatora HighLevelSynthesis.

Oprócz metody run należy zdefiniować szereg metod właściwości. Dokumentację wszystkich wymaganych właściwości znajdziesz w HighLevelSynthesisPlugin.

Zdefiniujmy naszą podklasę 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

Ta wtyczka syntetyzuje obiekty typu Clifford posiadające co najwyżej 3 Qubity, używając metody synth_clifford_bm.

Teraz udostępniamy wtyczkę, dodając punkt wejścia w metadanych naszego pakietu Pythona. Zakładamy tutaj, że zdefiniowana przez nas klasa jest eksponowana w module o nazwie my_qiskit_plugin, na przykład przez zaimportowanie jej w pliku __init__.py modułu my_qiskit_plugin. Edytujemy plik pyproject.toml, setup.cfg lub setup.py naszego pakietu:

[project.entry-points."qiskit.synthesis"]
"clifford.my_clifford_synthesis" = "my_qiskit_plugin:MyCliffordSynthesisPlugin"

name składa się z dwóch części oddzielonych kropką (.):

  • Nazwa typu Operation, który wtyczka syntetyzuje (w tym przypadku clifford). Pamiętaj, że ten ciąg znaków odpowiada atrybutowi name klasy Operation, a nie nazwie samej klasy.
  • Nazwa wtyczki (w tym przypadku special).

Tak jak poprzednio, jeśli twój projekt używa setup.cfg lub setup.py zamiast pyproject.toml, zajrzyj do dokumentacji setuptools, aby dowiedzieć się, jak dostosować te linie do swojej sytuacji.

Aby sprawdzić, czy twoja wtyczka jest poprawnie wykrywana przez Qiskit, zainstaluj pakiet z wtyczką i postępuj zgodnie z instrukcjami na stronie Wtyczki Transpilatora dotyczącymi listowania zainstalowanych wtyczek, upewniając się, że twoja wtyczka pojawia się na liście:

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']

Gdyby nasza przykładowa wtyczka była zainstalowana, nazwa my_clifford_synthesis pojawiłaby się na tej liście.

Rekomendacja