Pular para o conteúdo principal

Criar um plugin de transpilador

Versões dos pacotes

O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou mais recentes.

qiskit[all]~=2.3.0

Criar um plugin de transpilador é uma ótima forma de compartilhar seu código de transpilação com a comunidade Qiskit, permitindo que outros usuários se beneficiem da funcionalidade que você desenvolveu. Obrigado pelo seu interesse em contribuir com a comunidade Qiskit!

Antes de criar um plugin de transpilador, você precisa decidir qual tipo de plugin é adequado para sua situação. Existem três tipos de plugins de transpilador:

  • Plugin de estágio do transpilador. Escolha este se você estiver definindo um gerenciador de passes que pode substituir um dos 6 estágios de um gerenciador de passes staged predefinido.
  • Plugin de síntese unitária. Escolha este se o seu código de transpilação recebe como entrada uma matriz unitária (representada como um array Numpy) e produz como saída uma descrição de um Circuit quântico que implementa esse unitário.
  • Plugin de síntese de alto nível. Escolha este se o seu código de transpilação recebe como entrada um "objeto de alto nível", como um operador Clifford ou uma função linear, e produz como saída uma descrição de um Circuit quântico que implementa esse objeto de alto nível. Objetos de alto nível são representados por subclasses da classe Operation.

Depois de determinar qual tipo de plugin criar, siga estes passos para criar o plugin:

  1. Crie uma subclasse da classe abstrata de plugin adequada:
  2. Exponha a classe como um entry point do setuptools nos metadados do pacote, geralmente editando o arquivo pyproject.toml, setup.cfg ou setup.py do seu pacote Python.

Não há limite para o número de plugins que um único pacote pode definir, mas cada plugin deve ter um nome único. O próprio SDK do Qiskit inclui uma série de plugins, cujos nomes também são reservados. Os nomes reservados são:

  • Plugins de estágio do transpilador: Veja esta tabela.
  • Plugins de síntese unitária: default, aqc, sk
  • Plugins de síntese de alto nível:
Classe de operaçãoNome da operaçãoNomes reservados
Cliffordclifforddefault, ag, bm, greedy, layers, lnn
LinearFunctionlinear_functiondefault, kms, pmh
PermutationGatepermutationdefault, kms, basic, acg, token_swapper

Nas próximas seções, mostramos exemplos desses passos para os diferentes tipos de plugins. Nestes exemplos, assumimos que estamos criando um pacote Python chamado my_qiskit_plugin. Para informações sobre como criar pacotes Python, você pode conferir este tutorial no site do Python.

Exemplo: Criar um plugin de estágio do transpilador

Neste exemplo, criamos um plugin de estágio do transpilador para o estágio layout (veja Estágios do transpilador para uma descrição dos 6 estágios do pipeline de transpilação integrado do Qiskit). Nosso plugin simplesmente executa o VF2Layout por um número de tentativas que depende do nível de otimização solicitado.

Primeiro, criamos uma subclasse de PassManagerStagePlugin. Há um método que precisamos implementar, chamado pass_manager. Esse método recebe como entrada um PassManagerConfig e retorna o gerenciador de passes que estamos definindo. O objeto PassManagerConfig armazena informações sobre o backend alvo, como seu mapa de acoplamento e gates 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

Agora, expomos o plugin adicionando um entry point nos metadados do nosso pacote Python. Aqui, assumimos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, sendo importada no arquivo __init__.py do módulo my_qiskit_plugin. Editamos o arquivo pyproject.toml, setup.cfg ou setup.py do nosso pacote (dependendo de qual tipo de arquivo você escolheu para armazenar os metadados do seu projeto Python):

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

Veja a tabela de estágios de plugins do transpilador para os entry points e expectativas de cada estágio do transpilador.

Para verificar se o seu plugin é detectado com sucesso pelo Qiskit, instale o seu pacote de plugin e siga as instruções em Plugins do transpilador para listar os plugins instalados, e certifique-se de que o seu plugin aparece na lista:

from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins

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

Se o nosso plugin de exemplo estivesse instalado, o nome my_layout apareceria nessa lista.

Se você quiser usar um estágio de transpilador integrado como ponto de partida para o seu plugin de estágio do transpilador, você pode obter o gerenciador de passes de um estágio de transpilador integrado usando PassManagerStagePluginManager. O seguinte bloco de código mostra como fazer isso para obter o estágio de otimização integrado para o nível de otimização 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
)

Exemplo: Criar um plugin de síntese unitária

Neste exemplo, criaremos um plugin de síntese unitária que simplesmente usa o passe de transpilação integrado UnitarySynthesis para sintetizar um gate. Claro, o seu próprio plugin fará algo mais interessante do que isso.

A classe UnitarySynthesisPlugin define a interface e o contrato para plugins de síntese unitária. O método principal é run, que recebe como entrada um array Numpy armazenando uma matriz unitária e retorna um DAGCircuit representando o Circuit sintetizado a partir dessa matriz unitária. Além do método run, há uma série de métodos de propriedade que precisam ser definidos. Veja UnitarySynthesisPlugin para documentação de todas as propriedades obrigatórias.

Vamos criar nossa subclasse de 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 você achar que as entradas disponíveis para o método run são insuficientes para seus propósitos, abra uma issue explicando seus requisitos. Alterações na interface do plugin, como adicionar entradas opcionais adicionais, serão feitas de forma compatível com versões anteriores, de modo que não exijam mudanças nos plugins existentes.

Nota

Todos os métodos prefixados com supports_ são reservados em uma classe derivada de UnitarySynthesisPlugin como parte da interface. Você não deve definir nenhum método supports_* personalizado em uma subclasse que não esteja definido na classe abstrata.

Agora, expomos o plugin adicionando um entry point nos metadados do nosso pacote Python. Aqui, assumimos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, sendo importada no arquivo __init__.py do módulo my_qiskit_plugin. Editamos o arquivo pyproject.toml, setup.cfg ou setup.py do nosso pacote:

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

Como antes, se o seu projeto usar setup.cfg ou setup.py em vez de pyproject.toml, consulte a documentação do setuptools para saber como adaptar essas linhas à sua situação.

Para verificar se o seu plugin é detectado com sucesso pelo Qiskit, instale o seu pacote de plugin e siga as instruções em Plugins do transpilador para listar os plugins instalados, e certifique-se de que o seu plugin aparece na lista:

from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names

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

Se o nosso plugin de exemplo estivesse instalado, o nome my_unitary_synthesis apareceria nessa lista.

Para acomodar plugins de síntese unitária que expõem múltiplas opções, a interface do plugin tem uma opção para que os usuários forneçam um dicionário de configuração de forma livre. Isso será passado ao método run via o argumento de palavra-chave options. Se o seu plugin tiver essas opções de configuração, você deve documentá-las claramente.

Exemplo: Criar um plugin de síntese de alto nível

Neste exemplo, criaremos um plugin de síntese de alto nível que simplesmente usa a função integrada synth_clifford_bm para sintetizar um operador Clifford.

A classe HighLevelSynthesisPlugin define a interface e o contrato para plugins de síntese de alto nível. O método principal é run. O argumento posicional high_level_object é uma Operation que representa o objeto de "alto nível" a ser sintetizado. Por exemplo, pode ser uma LinearFunction ou um Clifford. Os seguintes argumentos de palavra-chave estão presentes:

  • target especifica o backend alvo, permitindo ao plugin acessar todas as informações específicas do alvo, como o mapa de acoplamento, o conjunto de gates suportados e assim por diante
  • coupling_map especifica apenas o mapa de acoplamento e é usado somente quando target não é especificado.
  • qubits especifica a lista de Qubits sobre os quais o objeto de alto nível é definido, caso a síntese seja feita no Circuit físico. Um valor None indica que o layout ainda não foi escolhido e os Qubits físicos no alvo ou mapa de acoplamento em que esta operação está atuando ainda não foram determinados.
  • options, um dicionário de configuração de forma livre para opções específicas do plugin. Se o seu plugin tiver essas opções de configuração, você deve documentá-las claramente.

O método run retorna um QuantumCircuit representando o Circuit sintetizado a partir desse objeto de alto nível. Também é permitido retornar None, indicando que o plugin não consegue sintetizar o objeto de alto nível fornecido. A síntese real de objetos de alto nível é realizada pelo passe de transpilador HighLevelSynthesis.

Além do método run, há uma série de métodos de propriedade que precisam ser definidos. Veja HighLevelSynthesisPlugin para documentação de todas as propriedades obrigatórias.

Vamos definir nossa subclasse de 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

Este plugin sintetiza objetos do tipo Clifford que têm no máximo 3 Qubits, usando o método synth_clifford_bm.

Agora, expomos o plugin adicionando um entry point nos metadados do nosso pacote Python. Aqui, assumimos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, sendo importada no arquivo __init__.py do módulo my_qiskit_plugin. Editamos o arquivo pyproject.toml, setup.cfg ou setup.py do nosso pacote:

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

O name é composto de duas partes separadas por um ponto (.):

  • O nome do tipo de Operation que o plugin sintetiza (neste caso, clifford). Observe que essa string corresponde ao atributo name da classe Operation, e não ao nome da própria classe.
  • O nome do plugin (neste caso, special).

Como antes, se o seu projeto usar setup.cfg ou setup.py em vez de pyproject.toml, consulte a documentação do setuptools para saber como adaptar essas linhas à sua situação.

Para verificar se o seu plugin é detectado com sucesso pelo Qiskit, instale o seu pacote de plugin e siga as instruções em Plugins do transpilador para listar os plugins instalados, e certifique-se de que o seu plugin aparece na lista:

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 o nosso plugin de exemplo estivesse instalado, o nome my_clifford_synthesis apareceria nessa lista.

Recomendação