Skip to main content
IBM Quantum Platform

Criar um plugin transpiler

  • O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos o uso dessas versões ou de versões mais recentes.

    qiskit[all]~=2.5.0
    

Criar um plug-in de trans pilação é uma ótima maneira de compartilhar seu código de transpilação com a comunidade Qiskit mais ampla, permitindo que outros usuários se beneficiem da funcionalidade que você desenvolveu. Obrigado por seu interesse em contribuir com a comunidade Qiskit!

Antes de criar um plug-in de transpilador, você precisa decidir que tipo de plug-in é apropriado para a sua situação. Há três tipos de plug-ins de transpiladores:

  • Plug-in de estágio Transpiler. Escolha essa opção se estiver definindo um gerenciador de passagens que possa ser substituído por um dos 6 estágios de um gerenciador de passagens predefinido.
  • Plug-in de síntese unitária. Escolha essa opção se o seu código de transpilação receber como entrada uma matriz unitária (representada como uma matriz Numpy) e gerar uma descrição de um circuito quântico que implemente essa unitária.
  • Plug-in de síntese de alto nível. Escolha essa opção se o seu código de transpilação receber como entrada um "objeto de alto nível", como um operador de Clifford ou uma função linear, e gerar uma descrição de um circuito quântico que implemente esse objeto de alto nível. Os objetos de alto nível são representados por subclasses da classe Operation.

Depois de determinar o tipo de plug-in a ser criado, siga estas etapas para criar o plug-in:

  1. Crie uma subclasse da classe de plug-in abstrata apropriada:
  2. Exponha a classe como um ponto de entrada 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 plug-ins que um único pacote pode definir, mas cada plug-in deve ter um nome exclusivo. O próprio Qiskit SDK inclui vários plug-ins, cujos nomes também são reservados. Os nomes reservados são:

  • Plug-ins de estágio do Transpiler: Consulte esta tabela.
  • Plug-ins de síntese unitária: default, aqc, sk
  • Plug-ins de síntese de alto nível:
Classe de operação
Nome da operação
Nomes reservados
Cliffordclifforddefault, ag, bm, greedy, layers, lnn
LinearFunctionlinear_functiondefault, kms, pmh
PermutationGatepermutationdefault, kms, basic, acg, token_swapper

Nas próximas seções, mostraremos exemplos dessas etapas para os diferentes tipos de plug-ins. Nesses exemplos, presumimos que estamos criando um pacote Python chamado my_qiskit_plugin. Para obter informações sobre a criação de pacotes Python, consulte este tutorial no site Python.


Exemplo: Criar um plugin de etapa transpiler

Neste exemplo, criamos um plug-in de estágio do transpilador para o estágio layout (consulte Estágios do transpilador para obter uma descrição dos 6 estágios do pipeline de transpilação integrado do Qiskit). Nosso plug-in simplesmente executa 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 de destino, como o mapa de acoplamento e as portas de base.

# 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 plug-in adicionando um ponto de entrada em nossos metadados do pacote Python. Aqui, supomos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, ao ser 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 do tipo de arquivo que você escolheu para armazenar os metadados do projeto Python ):

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

Consulte a tabela de estágios do plug-in do transpiler para obter os pontos de entrada e as expectativas de cada estágio do transpiler.

Para verificar se o seu plug-in foi detectado com êxito pelo Qiskit, instale o pacote do plug-in e siga as instruções em Plug-ins do Transpiler para listar os plug-ins instalados e verifique se o seu plug-in aparece na lista:

from qiskit.transpiler.preset_passmanagers.plugin import list_stage_plugins

list_stage_plugins("layout")

Output:

['default', 'dense', 'sabre', 'trivial']

Se o nosso plug-in de exemplo estivesse instalado, o nome my_layout apareceria nessa lista.

Se quiser usar um estágio de transpilador integrado como ponto de partida para seu plug-in de estágio de transpilador, você poderá obter o gerenciador de passes para um estágio de transpilador integrado usando PassManagerStagePluginManager. A célula de código a seguir mostra como fazer isso para obter o estágio de otimização integrada 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 plug-in de síntese unitária que simplesmente usa a passagem de transpilação UnitarySynthesis transpilação integrada para sintetizar uma porta. É claro que seu próprio plug-in fará algo mais interessante do que isso.

A classe UnitarySynthesisPlugin define a interface e o contrato para plug-ins de síntese unitária plug-ins. O método principal é run, que recebe como entrada uma matriz Numpy que armazena uma matriz unitária e retorna um DAGCircuit que representa o circuito sintetizado a partir dessa matriz unitária. Além do método run , há vários métodos de propriedade que precisam ser definidos. Consulte UnitarySynthesisPlugin para obter a documentação de todas as propriedades necessárias.

Vamos criar nossa subclasse 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 run são insuficientes para seus objetivos, abra um problema explicando seus requisitos. As alterações na interface do plug-in, como a inclusão de entradas opcionais adicionais, serão feitas de forma compatível com as versões anteriores, de modo que não exijam alterações nos plug-ins 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 plug-in adicionando um ponto de entrada em nossos metadados do pacote Python. Aqui, supomos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, ao ser 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 plug-in foi detectado com êxito pelo Qiskit, instale o pacote do plug-in e siga as instruções em Plug-ins do Transpiler para listar os plug-ins instalados e verifique se o seu plug-in aparece na lista:

from qiskit.transpiler.passes.synthesis import unitary_synthesis_plugin_names

unitary_synthesis_plugin_names()

Output:

['aqc', 'clifford', 'default', 'gridsynth', 'sk']

Se o nosso plug-in de exemplo estivesse instalado, o nome my_unitary_synthesis apareceria nessa lista.

Para acomodar plug-ins de síntese unitária que expõem várias opções, a interface do plug-in tem uma opção para que os usuários forneçam um dicionário de configuração. Isso será passado para o método run por meio do argumento da palavra-chave options . Se o seu plug-in tiver essas opções de configuração, você deverá documentá-las claramente.


Exemplo: Criar um plug-in de síntese de alto nível

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

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

  • target especifica o backend de destino, permitindo que o plug-in acesse todas as informações específicas do destino, como o mapa de acoplamento, o conjunto de portas suportado e assim por diante
  • coupling_map especifica apenas o mapa de acoplamento e só é usado quando target não é especificado.
  • qubits especifica a lista de qubits sobre os quais o objeto de objeto de alto nível é definido, caso a síntese seja feita no circuito físico. Um valor de None indica que o layout ainda não foi escolhido e que os qubits físicos no alvo ou no mapa de acoplamento em que essa operação está operando ainda não foram determinados.
  • optionsum dicionário de configuração de forma livre para opções específicas do plug-in. Se o seu plug-in tiver essas opções de configuração, você deve documentá-las claramente.

O método run retorna um QuantumCircuit que representa o circuito sintetizado a partir desse objeto de alto nível. Também é permitido retornar None, indicando que o plug-in não consegue sintetizar o objeto de alto nível fornecido. A síntese real de objetos de alto nível é realizada pelo HighLevelSynthesis passagem do transpilador.

Além do método run , há vários métodos de propriedade que precisam ser definidos. Consulte HighLevelSynthesisPlugin para obter a documentação de todas as propriedades necessárias.

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

Esse plug-in sintetiza objetos do tipo Clifford que têm no máximo 3 qubits, usando o método synth_clifford_bm .

Agora, expomos o plug-in adicionando um ponto de entrada em nossos metadados do pacote Python. Aqui, supomos que a classe que definimos está exposta em um módulo chamado my_qiskit_plugin, por exemplo, ao ser 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 consiste em duas partes separadas por um ponto (.):

  • O nome do tipo de operação que o plug-in sintetiza (neste caso, clifford). Observe que essa cadeia de caracteres corresponde ao atributo name da classe Operation, e não ao nome da classe em si.
  • O nome do plug-in (nesse 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 plug-in foi detectado com êxito pelo Qiskit, instale o pacote do plug-in e siga as instruções em Plug-ins do Transpiler para listar os plug-ins instalados e verifique se o seu plug-in aparece na lista:

from qiskit.transpiler.passes.synthesis import (
    high_level_synthesis_plugin_names,
)

high_level_synthesis_plugin_names("clifford")

Output:

['ag', 'bm', 'default', 'greedy', 'layers', 'lnn', 'rb_default']

Se o nosso plug-in de exemplo estivesse instalado, o nome my_clifford_synthesis apareceria nessa lista.

Recomendação
Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.