Transpiler
qiskit.transpiler
Visão geral
Se você já estiver familiarizado com os conceitos de transpilação/compilação de circuitos, talvez queira pular para a próxima etapa:
- Descrições dos gerenciadores de passes pré-configurados. Isso inclui descrições dos estágios e de todos os plug-ins internos disponíveis.
- Documentação sobre a criação de uma passagem de transpilador personalizada.
A transpilação é o processo de reescrever um determinado circuito de entrada para corresponder à topologia de um dispositivo quântico específico e/ou para otimizar o circuito para execução em sistemas quânticos.
A maioria dos circuitos precisa passar por uma série de transformações que os tornam compatíveis com um determinado dispositivo de destino e os otimizam para reduzir os efeitos do ruído nos resultados resultantes. A reescrita de circuitos quânticos para atender às restrições de hardware e a otimização do desempenho podem estar longe de ser triviais. O fluxo da lógica na cadeia de ferramentas de reescrita não precisa ser linear e, muitas vezes, pode ter sub-loops iterativos, ramificações condicionais e outros comportamentos complexos. Dito isso, o fluxo de compilação padrão segue a estrutura apresentada a seguir:
QuantumCircuitO Qiskit utiliza a representação intermediária (IR) baseada DAGCircuit em grafos de um circuito em toda a pilha do transpilador, em vez da representação baseada em árvores. Um pipeline de transpilador é um PassManager objeto cujo PassManager.run() método recebe um QuantumCircuit e o converte em um DAGCircuit, submete o IR a uma sequência de passagens e, por fim, retorna um QuantumCircuit . Uma passagem pode ser uma AnalysisPass, que calcula e armazena propriedades do circuito no PropertySet, ou uma TransformationPass, que modifica o IR para atingir um objetivo específico. Você pode pensar em um pipeline como se ele fosse dividido em “etapas”, em que cada etapa é responsável por uma transformação de alto nível.
O Qiskit disponibiliza um gerador padrão de pipeline de transpilação que utiliza a função generate_preset_pass_manager(). Isso retorna um pipeline devidamente configurado para a transpilagem completa, em um nível escolhido optimization_level (entre 0 e 3, inclusive). A menos que você esteja procurando algo altamente especializado, este é, quase com certeza, o ponto de partida que você procura. Um exemplo de transpilação fica assim:
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Any abstract circuit you want:
abstract = QuantumCircuit(2)
abstract.h(0)
abstract.cx(0, 1)
# Any method you like to retrieve the backend you want to run on:
backend = QiskitRuntimeService().backend("some-backend")
# Create the pass manager for the transpilation ...
pm = generate_preset_pass_manager(backend=backend)
# ... and use it (as many times as you like).
physical = pm.run(abstract)Nas primeiras experiências voltadas para a tolerância a falhas, as funções generate_preset_pass_manager() e transpile() ativam um pipeline de transpilação especializado quando a base de destino é composta por portas Clifford+T; consulte generate_preset_clifford_t_pass_manager() para obter a documentação. Recomenda-se utilizar esta última opção para uma configuração detalhada dos pipelines do Clifford+. Por exemplo, a precisão da síntese do não pode ser definida por meio de "unitary_synthesis_method" em generate_preset_pass_manager() , mas só pode ser definida globalmente por meio do "approximation_degree". No entanto, generate_preset_clifford_t_pass_manager() revela "rz_synthesis_config" essa questão.
Por exemplo:
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import QFTGate
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import get_clifford_gate_names
# Any abstract circuit you want:
abstract = QuantumCircuit(4)
abstract.append(QFTGate(4), [0, 1, 2, 3])
# Use all Clifford+T basis gates
basis_gates = get_clifford_gate_names() + ["t", "tdg"]
# Create and run the pass manager
pm = generate_preset_pass_manager(basis_gates=basis_gates)
transpiled = pm.run(abstract)Para a maioria dos casos de uso, isso é tudo o que você precisa. No entanto, toda a infraestrutura do transpilador do Qiskit é altamente extensível e configurável. O restante desta página detalha como aproveitar os recursos de baixo nível da pilha do transpilador.
Gerenciadores de senhas predefinidos
A função generate_preset_pass_manager() cria os “gerenciadores de passagem predefinidos”. Todos esses são exemplos de PassManager, portanto são utilizados passando um QuantumCircuit para o PassManager.run() método. Mais especificamente, os gerenciadores de passagens predefinidos são instâncias de StagedPassManager, o que permite uma maior configuração das etapas individuais de uma transpilagem, incluindo ganchos pré e pós-etapa.
Um gerenciador de passagens predefinidas tem até seis estágios nomeados. Eles estão resumidos, em ordem de execução, abaixo, com informações mais detalhadas nas subseções a seguir.
init
Otimizações de circuitos abstratos e redução de operações de vários qubits para operações de um e dois qubits. Consulte Estágio de inicialização para obter mais detalhes.
layout
Escolha um mapeamento inicial de qubits virtuais para qubits físicos, incluindo a expansão do circuito para conter ancilas explícitas. Às vezes, esse estágio é substituído pelo routing. Consulte o estágio de Layout para obter mais detalhes.
routing
Insira portas no circuito para garantir que ele atenda às restrições de conectividade do Target. Os portões inseridos ainda não precisam corresponder à ISA de destino; portanto, muitas vezes são apenas swap instruções. Essa etapa às vezes é omitida, quando a layout etapa cumpre sua função. Consulte a seção “Etapa de roteamento” para obter mais detalhes.
translation
Converta todas as portas do circuito para que correspondam à ISA do Target. Consulte a seção “Etapa de tradução” para obter mais detalhes.
optimization
Otimizações de baixo nível e com reconhecimento de hardware. Diferentemente das otimizações abstratas do estágio init , esse estágio atua em um circuito físico. Consulte o estágio de otimização para obter mais detalhes.
scheduling
Inserir Delay instruções para tornar explícito o tempo de relógio de parede de um circuito. Isso também pode incluir técnicas de redução de erros on-line com reconhecimento de hardware, como o desacoplamento dinâmico, que depende do conhecimento das temporizações do relógio de parede. Consulte Estágio de agendamento para obter mais detalhes.
Os pipelines predefinidos do transpiler também podem ser configurados em alto nível, definindo um optimization_level. É um número inteiro de 0 a 3, inclusive, que indica o esforço relativo a ser exercido na tentativa de otimizar o circuito para o hardware. O nível 0 desativa todas as otimizações desnecessárias; somente as transformações necessárias para tornar o circuito executável são usadas. Por outro lado, o nível 3 permite uma gama completa de técnicas de otimização, algumas das quais podem ser muito caras em termos de tempo de compilação. De modo semelhante aos compiladores clássicos, nem sempre é garantido que o nível de otimização 3 produza os melhores resultados. O Qiskit tem como padrão o nível de otimização 2, como uma compensação entre o tempo de compilação e a quantidade esperada de otimização.
O nível de otimização determina quais implementações são usadas por padrão em uma determinada etapa, embora isso possa ser substituído passando argumentos explícitos <stage>_method="<choice>" para generate_preset_pass_manager().
Reprodutibilidade dos pipelines predefinidos
A compilação quântica frequentemente envolve a resolução de problemas cuja complexidade é reconhecidamente não polinomial e, portanto, impossíveis de otimizar globalmente. Nesses casos, os algoritmos estocásticos e heurísticos costumam ser mais adequados. Isso leva, no entanto, a problemas de reprodutibilidade.
Os gerenciadores de passes predefinidos quase sempre incluem passes estocásticos e baseados em heurística. Se você precisar garantir a reprodutibilidade de uma compilação, passe um número inteiro conhecido para o argumento seed_transpiler das funções do gerador.
Todos os plug-ins integrados ao Qiskit devem gerar suas análises e modificar o código DAGCircuit de maneira determinística caso sua randomização (se houver) seja inicializada com uma semente, de modo que a compilação possa ser repetida posteriormente. Existem limites para isso:
- Todas as passagens incorporadas com componentes estocásticos devem fornecer uma maneira de semear a randomização e, se forem semeadas, devem respeitar as regras de saída determinística.
- Todas as funções integradas sem componentes estocásticos devem respeitar as regras de saída determinística para entradas idênticas. É permitido manter um cache para fins de eficiência, mas, dado o mesmo conjunto de entradas, os resultados da passagem devem ser os mesmos se ela for chamada várias vezes, a menos que algo fora do controle da passagem altere uma de suas entradas no próprio local (por exemplo, o
BasisTranslatorusa oSessionEquivalenceLibrarypor padrão nos gerenciadores de passagens predefinidos, e não é um bug obter resultados diferentes se novas entradas forem adicionadas à biblioteca de equivalências). Uma “saída” é qualquer coisa que a etapa gera para uso posterior; isso pode ser o valor explícitoreturnda etapa, mas também inclui propriedades destinadas a serem utilizadas posteriormente noPropertySet. - A saída de uma passagem deve ser determinística em uma determinada máquina para uma determinada versão do Qiskit e um ambiente congelado, independentemente de quantos threads estejam disponíveis para a passagem. Muitas passagens integradas do Qiskit usam simultaneidade de threads e não têm permissão para ter um comportamento diferente com base no número de threads.
- Não é necessário que o resultado de uma passagem para uma semente fixa seja igual se alguma parte do ambiente subjacente do Python mudar (como a atualização de um pacote dependente) ou se as bibliotecas matemáticas do sistema mudarem (como a disponibilidade de uma implementação diferente do BLAS).
- Não é necessário que a saída de uma passagem para uma semente fixa seja igual entre os sistemas operacionais (embora, normalmente, seja a implementação da biblioteca matemática do sistema a causa principal das diferenças relacionadas ao sistema operacional).
- Não é necessário que a saída de uma passagem para uma semente fixa seja a mesma entre duas máquinas que tenham diferentes instruções de CPU disponíveis; espera-se que diferentes implementações de núcleos matemáticos possam produzir um comportamento diferente se diferentes instruções de CPU estiverem disponíveis, como instruções de multiplicação e adição fundidas com características de arredondamento diferentes de duas instruções separadas de multiplicação e adição de ponto flutuante.
- A saída de uma passagem para uma semente fixa deve ser a mesma, independentemente do número de threads que ela pode usar, a menos que o usuário opte especificamente por esse comportamento. Por exemplo, nos gerenciadores de passagens predefinidos, os métodos de layout e roteamento do Sabre precisam executar o mesmo número de tentativas por padrão, independentemente de haver um único thread permitido ou até mesmo mais threads do que tentativas, embora esse comportamento possa ser explicitamente substituído pela configuração da variável de ambiente
QISKIT_SABRE_ALL_THREADSpara que se torne sensível à contagem de threads. - Todas as regras acima se aplicam mesmo entre sessões separadas do intérprete Python, mesmo quando
PYTHONHASHSEEDnão tiver sido explicitamente definido.
Em geral, um usuário do DAGCircuit deve poder presumir que, após a execução de qualquer combinação de passos do Qiskit integrados — com valores iniciais, se for o caso — com entradas fixas, a saída exata de todos DAGCircuit() os métodos seja determinística. Isso inclui a ordem de saída até mesmo de métodos que não oferecem nenhuma garantia quanto à ordem; embora não se possa confiar na semântica e na ordem exata, é possível confiar no determinismo dessa ordem para entradas fixas.
Os autores de passagens de transpiladores devem consultar Randomness and determinism (Aleatoriedade e determinismo ) para uma discussão sobre como tornar uma passagem de transpilador determinística.
Escolhendo implementações de estágio predefinidas
O Qiskit inclui várias implementações dos estágios acima, e outros podem ser instalados como "plugins" separados. Para controlar qual implementação de um estágio é usada, passe seu nome para o argumento da palavra-chave <stage>_method das duas funções, como translation_method="translator". Para saber mais sobre a implementação desses plug-ins externos para um estágio, consulte qiskit.transpiler.preset_passmanagers.plugin.
Por exemplo, para gerar um gerenciador de passagem predefinido no nível de otimização 1 que use explicitamente o método trivial para layout com o método sabre para roteamento, faríamos isso:
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.providers.fake_provider import GenericBackendV2
# Whatever backend you like:
backend = GenericBackendV2(num_qubits=5)
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial",
routing_method="sabre",
)O conjunto integrado de plug-ins disponíveis para cada etapa faz parte da API pública do Qiskit e está sujeito a todas as garantias de estabilidade. Isso inclui os efeitos lógicos de alto nível desse método (por exemplo, routing_method="sabre" ele sempre utilizará um algoritmo derivado do Sabre). A estrutura interna exata do mecanismo PassManager que representa a etapa não é, no entanto, fixa; a ordem das etapas pode mudar entre versões secundárias, ou novas etapas podem ser introduzidas.
Para qualquer estágio que tenha um, o método denominado "default" é o mais sujeito a alterações. Normalmente, o Qiskit só faz alterações algorítmicas completas no método padrão em um limite de versão principal, mas pode reequilibrar a heurística e adicionar novas passagens aos métodos padrão entre versões secundárias.
Como a saída de generate_preset_pass_manager() é um StagedPassManager, você também pode modificar o gerenciador de passagens após sua criação para fornecer uma implementação de estágio totalmente personalizada. Por exemplo, se você quisesse executar uma etapa de programação personalizada usando o desacoplamento dinâmico (por meio do PadDynamicalDecoupling pass) e também adicionar uma otimização lógica inicial antes do roteamento, você faria algo como o seguinte (com base no exemplo anterior):
import numpy as np
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.circuit import library as lib
from qiskit.transpiler import PassManager, generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
InverseCancellation,
PadDynamicalDecoupling,
)
backend = GenericBackendV2(num_qubits=5)
dd_sequence = [lib.XGate(), lib.XGate()]
scheduling_pm = PassManager(
[
ALAPScheduleAnalysis(target=backend.target),
PadDynamicalDecoupling(target=backend.target, dd_sequence=dd_sequence),
]
)
inverse_gate_list = [
lib.CXGate(),
lib.HGate(),
(lib.RXGate(np.pi / 4), lib.RXGate(-np.pi / 4)),
(lib.PhaseGate(np.pi / 4), lib.PhaseGate(-np.pi / 4)),
(lib.TGate(), lib.TdgGate()),
]
logical_opt = PassManager([InverseCancellation(inverse_gate_list)])
pass_manager = generate_preset_pass_manager(optimization_level=0)
# Add pre-layout stage to run extra logical optimization
pass_manager.pre_layout = logical_opt
# Set scheduling stage to custom pass manager
pass_manager.scheduling = scheduling_pmAgora, quando o gerenciador de passagens do estágio for executado por meio do run() método, o scheduling_pm``logical_opt gerenciador de passagens será chamado antes do layout estágio, e será utilizado para esse scheduling estágio em vez do padrão.
Se você estiver construindo estágios personalizados para os gerenciadores de passagem predefinidos, poderá achar úteis algumas das funções auxiliares de baixo nível em qiskit.transpiler.preset_passmanagers úteis.
Fase de inicialização
Explicação de nível mais alto para o usuário sobre o estágio de inicialização no guia IBM Quantum.
Essa init etapa é responsável por otimizações lógicas de alto nível em circuitos abstratos e por decompor operações com múltiplos qubits (3+) em uma série de operações de um e dois qubits. Como esta é a primeira execução do estágio, sua entrada é um circuito totalmente abstrato. O init estágio deve ser capaz de lidar com portas personalizadas definidas pelo usuário e com todos os objetos abstratos de alto nível para descrição de circuitos, tais como AnnotatedOperation.
A saída do estágio init é um circuito abstrato que contém apenas operações de um e dois qubits.
Ao escrever plug-ins de estágio, o ponto de entrada para init é qiskit.transpiler.init. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Desenrolamento integrado de operações multiqubit e otimizações abstratas. |
Plugin default integrado
No nível de otimização 0, não é realizada nenhuma otimização abstrata. O plug-in padrão simplesmente “desdobra” as operações com mais de três qubits, acessando seus campos hierárquicos definition .
Nos níveis de otimização 1 e superiores, o plug-in padrão também faz o cancelamento simples de portas inversas adjacentes, como duas portas cx consecutivas.
Nos níveis de otimização 2 e 3, o plug-in padrão permite uma variedade muito maior de otimizações abstratas. Isso inclui:
- “Elisão de permutação virtual” (ver
ElidePermutations), em que as operações explícitas que induzem permutações são removidas e, em vez disso, realizadas por meio do remapeamento de qubits virtuais. - Análise da estrutura de comutação do IR para encontrar pares de portas que possam ser canceladas.
- Divisão numérica de operações de dois qubits que podem ser expressas como uma série de operações separáveis de um qubit.
- Remoção de operações imperceptíveis, como rotações Pauli de ângulo mínimo e operações diagonais imediatamente anteriores às medições.
Fase de layout
Explicação do estágio do layout
Explicação de nível superior para o usuário sobre o estágio do layout no guia IBM Quantum.
O estágio de layout é responsável por fazer um mapeamento inicial entre os qubits virtuais do circuito de entrada e os qubits de hardware do alvo. Isso inclui a expansão do circuito de entrada com ancillas explícitas para que ele tenha tantos qubits quanto o alvo e a reescrita de todas as operações em termos de qubits de hardware. Você também pode ver esse problema chamado de problema de "posicionamento" em outros kits de ferramentas ou na literatura.
PropertySetA etapa de layout deve definir as propriedades layout e original_qubit_indices no. do pipeline.
Todos os plug-ins integrados para a etapa de layout darão prioridade a um layout explícito selecionado por meio do initial_layout argumento para generate_preset_pass_manager() ou transpile().
Em qualquer ponto de um circuito, podemos identificar um mapeamento entre os qubits "virtuais" atualmente ativos do circuito de entrada para os qubits de hardware do backend. Um qubit de hardware só pode representar um único qubit virtual em um determinado ponto, mas o mapeamento pode variar ao longo do circuito. Em princípio, alguns qubits virtuais podem não ser mapeados em todos os pontos da execução do circuito, se o tempo de vida de um estado de qubit virtual puder ser reduzido, embora os pipelines integrados do Qiskit não usem isso atualmente.
O estágio de layout não é responsável por garantir que a conectividade do alvo seja respeitada em todo o circuito, nem que todas as operações sejam válidas para execução direta no alvo; essas são responsabilidades dos estágios de roteamento e tradução, respectivamente.
A escolha do layout inicial é um dos fatores mais importantes que afetam a qualidade do circuito de saída. O estágio de layout costuma ser o mais caro do ponto de vista computacional nos pipelines padrão; o plug-in padrão para layout tenta até mesmo vários algoritmos diferentes (descritos em mais detalhes em Plug-in padrão incorporado ).
A situação ideal para a etapa de layout é encontrar um layout “perfeito”, em que todas as operações respeitem as restrições de conectividade do circuito Target , de modo que a etapa de roteamento não seja necessária. Normalmente, isso não é possível para circuitos de entrada arbitrários, mas, quando é, essa VF2Layout etapa pode ser usada para encontrar um layout inicial válido. Caso sejam encontrados vários layouts perfeitos, utiliza-se uma heurística de pontuação baseada em taxas de erro estimadas para decidir qual deles será utilizado.
Em todos os plug-ins integrados, passar o generate_preset_pass_manager() argumento initial_layout faz com que o layout especificado seja usado tal como está, ignorando a lógica individual de “escolha”. Todos os plug-ins integrados também lidam com a incorporação do circuito em toda a largura do dispositivo, incluindo a atribuição de ancillas.
Se você escrever seu próprio plug-in de layout, talvez ache o generate_embed_passmanager() útil para automatizar o estágio de "incorporação" do aplicativo de layout.
Ao escrever plug-ins de estágio, o ponto de entrada para layout é qiskit.transpiler.layout. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Nos níveis mais altos de otimização, tenta encontrar um layout perfeito e, em seguida, tenta uma passagem combinada de layout e roteamento baseada no Sabre. |
| denso | Encontra o subgráfico mais denso (em termos de graus de ligação de qubit) do backend para usar como os qubits iniciais. |
| trivial | Mapeia o qubit virtual 0 para o qubit físico 0, e assim por diante. |
| sabre | Usa o algoritmo de layout Sabre aprimorado do Qiskit. |
Em todos os níveis de otimização, o método de layout padrão é default, embora a estrutura desse estágio mude drasticamente de acordo com o nível.
Plugin default integrado
Um amálgama de várias técnicas de layout diferentes.
No nível de otimização 0, o layout trivial é escolhido.
Em níveis de otimização acima de 0, há um processo de duas etapas:
- Primeiro, use
VF2Layoutpara tentar encontrar um layout “perfeito”. O número máximo de chamadas ao avaliador de isomorfismo aumenta com o nível de otimização. No caso de alvos enormes e complexos, não há garantia de que encontraremos disposições perfeitas, mesmo que elas existam, mas a probabilidade aumenta com o nível de otimização. - Se não for possível encontrar um layout perfeito, use
SabreLayoutpara escolher um layout inicial, de modo que o número de tentativas de layout inicial, de troca de mapa e de iterações para frente e para trás aumente conforme o nível de otimização.
Além disso, o nível de otimização 1 também tenta o layout trivial antes da versão VF2-based, para compatibilidade histórica com versões anteriores.
Plugin dense integrado
Utiliza o DenseLayout pass para selecionar o layout. Esta etapa identifica o subgrafo conectado mais denso do grafo de conectividade completo de destino, sendo que “mais denso” significa que são preferidos os qubits de hardware com o maior número de conexões disponíveis. O mapeamento do virtual para o hardware é concluído pela atribuição dos qubits virtuais de grau mais alto aos qubits de hardware de grau mais alto.
Essa é uma heurística relativamente barata para escolher um layout inicial, mas geralmente apresenta uma qualidade de saída muito inferior à dos métodos baseados no Sabre. O plug-in de layout padrão utiliza o mapeamento inicial selecionado por DenseLayout como um de seus layouts iniciais para inicializar o algoritmo Sabre.
Plugin trivial integrado
Utiliza o TrivialLayout pass para selecionar o layout. Essa é a atribuição mais simples, na qual cada qubit virtual é atribuído ao qubit de hardware com o mesmo índice; assim, o qubit virtual 0 é mapeado para o qubit de hardware 0, e assim por diante.
Esse método é mais útil para experimentos de caracterização de hardware, em que o circuito "abstrato" de entrada já está em toda a largura do dispositivo, suas operações correspondem a operações físicas e o transpilador está sendo invocado apenas para formalizar a criação de um circuito físico QuantumCircuit.
Plugin sabre integrado
Utiliza o SabreLayout para escolher um layout inicial, empregando o algoritmo de roteamento Sabre modificado do Qiskit como sub-rotina para mapear o circuito candidato tanto na direção direta quanto na reversa.
Em resumo, o componente de layout do algoritmo Sabre original escolhe um layout inicial arbitrariamente e, em seguida, tenta "melhorá-lo" executando o roteamento no circuito, invertendo o circuito e executando o roteamento no circuito invertido com a atribuição "final" anterior de virtual para hardware como o estado inicial. O nível de otimização configurado decide quantas iterações desse vai-e-vem devem ser feitas e quantos layouts iniciais aleatórios diferentes devem ser tentados.
A principal diferença em relação ao estágio padrão em níveis de otimização diferentes de 0 é que esse plug-in executa apenas o algoritmo baseado no Sabre. Ele não tenta encontrar um layout perfeito, nem tenta o layout trivial.
Etapa de roteamento
Explicação do estágio de roteamento
Explicação de nível superior para o usuário sobre o estágio de roteamento no guia IBM Quantum.
O estágio de roteamento garante que o gráfico de conectividade virtual do circuito seja compatível com o gráfico de conectividade de hardware do destino. Em termos mais simples, o estágio de roteamento garante que todas as portas de dois qubits no circuito sejam mapeadas para qubits de hardware que tenham uma operação de dois qubits definida na ISA de destino. Você também pode ver esse problema ser chamado de problema de "mapeamento" ou "mapeamento de swap" em outros kits de ferramentas ou na literatura.
Os algoritmos de roteamento geralmente fazem isso inserindo portas swap no circuito e modificando o mapeamento virtual para hardware dos qubits no decorrer da execução do circuito.
A etapa de roteamento não precisa garantir que todas as portas do circuito sejam válidas para a ISA de destino. Por exemplo, um plug-in de roteamento pode deixar portas literais swap no circuito, mesmo que o circuito Target não contenha SwapGate. No entanto, deve haver pelo menos uma porta de dois qubits definida no circuito Target para qualquer par de qubits de hardware ao qual seja aplicada uma porta no circuito.
A etapa de roteamento deve definir as final_layout propriedades e virtual_permutation_layout no PropertySet caso tenha ocorrido roteamento.
Todas as etapas de roteamento integradas do Qiskit também executarão a etapa VF2PostLayout “pass” após o roteamento. Isso pode redefinir o layout inicial, caso seja possível encontrar qubits com menor taxa de erro. Essa passagem é muito semelhante à VF2Layout classe que o plug-in de layout padrão utiliza, exceto que, neste VF2PostLayout caso, podemos garantir que há pelo menos um subgrafo induzido isomórfico da topologia de destino que corresponde à topologia do circuito.
Os plug-ins de roteamento integrados do Qiskit geralmente assumem que todos os pares de qubits com um link de dois qubits definido têm um conjunto universal de portas definidas para esses dois qubits. O hardware não precisa necessariamente respeitar isso (por exemplo, se a única porta de dois qubits definida for swap, então operações de entrelaçamento como cx não poderão ser realizadas), mas o Qiskit ainda não considera essa possibilidade.
Sabe-se que determinar o número mínimo de trocas a serem inseridas é um problema não polinomial. Isso significa que é proibitivamente caro tentar, por isso muitos dos algoritmos integrados ao Qiskit são estocásticos, e você pode observar grandes variações entre diferentes compilações. Se você precisar de reprodutibilidade, certifique-se de definir o seed_transpiler argumento de generate_preset_pass_manager() ou transpile().
Ao escrever plug-ins de estágio, o ponto de entrada para routing é qiskit.transpiler.routing. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Usar um método de roteamento padrão escolhido pelo Qiskit. |
| sabre | Padrão. Usa o algoritmo de roteamento Sabre modificado do Qiskit para trocar o mapa. |
| Nenhum | Desativar o roteamento. Gera um erro se o roteamento for necessário. |
| configuração básica | Inserção de swap inteligente para rotear uma única operação de cada vez. |
| lookahead | Pesquisa Breadth-first com poda heurística para encontrar trocas que tornem as portas executáveis. |
Plugin default integrado
Use o método padrão de roteamento escolhido pelo Qiskit. A partir do Qiskit 2.0, o algoritmo escolhido é o mesmo do plug-in Sabre integrado; porém, na prática, geralmente o plug-in padrão integrado da etapa de layout executa o algoritmo de roteamento baseado no Sabre, e a etapa de roteamento será usada apenas para executar VF2PostLayout.
Plugin none integrado
Um plug-in fictício usado para desativar totalmente o roteamento. Ocasionalmente, isso pode ser útil para experimentos de configuração de hardware ou em determinados casos especiais de compilação parcial.
Plugin basic integrado
Usa o algoritmo de inserção de swap BasisSwap . Isso é conceitualmente muito simples; para cada operação em ordem topológica, insira as trocas de caminho mais curtas necessárias para tornar a conexão executável no dispositivo.
O nível de otimização afeta apenas a quantidade de trabalho que VF2PostLayout a etapa realiza para tentar melhorar o layout inicial após o roteamento.
Em geral, esse método tem uma qualidade de saída ruim.
Plugin lookahead integrado
Utiliza o LookaheadSwap algoritmo para o roteamento. Trata-se, essencialmente, de uma busca em largura para gerar uma rede de trocas, na qual a árvore que está sendo explorada é podada até restar um pequeno número de trocas candidatas em cada profundidade.
Esse algoritmo é semelhante à heurística basic do plug-in "sabre", exceto pelo fato de que ele também considera os seguintes efeitos de cada troca em uma pequena profundidade.
O nível de otimização afeta a profundidade da busca, a extensão da poda por profundidade e a quantidade de trabalho realizada para VF2PostLayout otimizar posteriormente o layout inicial.
Na prática, o plug-in "sabre" é executado várias ordens de magnitude mais rapidamente e produz resultados melhores.
Plugin sabre integrado
Utiliza o SabreSwap algoritmo para o roteamento. Isso utiliza a versão aprimorada do Qiskit do algoritmo de roteamento Sabre original.
Esse algoritmo de roteamento é executado com paralelismo de threads para considerar várias possibilidades diferentes de roteamento, escolhendo aquela que minimiza o número de trocas inseridas.
O nível de otimização influencia o número de sementes estocásticas diferentes que são testadas para o roteamento completo, bem como a quantidade de trabalho realizada para VF2PostLayout otimizar posteriormente o layout inicial.
Esse é quase invariavelmente o plug-in interno de melhor desempenho e o que o Qiskit usa por padrão em todos os casos em que o roteamento é necessário.
Fase de tradução
Explicação da etapa de tradução
Explicação de alto nível para o usuário sobre o estágio de tradução no guia IBM Quantum.
O estágio de tradução é responsável por reescrever todas as portas do circuito em portas compatíveis com a ISA de destino. Por exemplo, se um cx for solicitado nos qubits 0 e 1 do hardware, mas o ISA contiver apenas uma operação cz nesses qubits, o estágio de tradução deverá encontrar uma maneira de representar a porta cx usando o cz e as portas de um qubit disponíveis.
O estágio de tradução é chamado antes de entrar no estágio de otimização. Os plug-ins de otimização (incluindo os plug-ins integrados do Qiskit) também podem usar o estágio de tradução como um estágio de "correção" após o loop de otimização, se o loop de otimização retornar um circuito que inclua portas não ISA. Essa última situação é bastante comum; o loop de otimização pode estar preocupado apenas em minimizar propriedades como "número de portas de dois qubits" e deixará sua saída em termos de portas localmente equivalentes, que o estágio de tradução pode reescrever facilmente sem afetar as propriedades de otimização de destino. Isso permite uma separação mais fácil das preocupações entre os dois estágios. Alguns plug-ins de otimização podem ser mais rigorosos em seus resultados e, portanto, esse acompanhamento do estágio de tradução pode não ser mais necessário.
Ao escrever plug-ins de estágio, o ponto de entrada para translation é qiskit.transpiler.translation. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Usar um método de tradução padrão escolhido pelo Qiskit. |
| tradutor | Tradução simbólica de portas para a base de destino usando equivalências conhecidas. |
| síntese | Colete cada execução de portas de um e dois qubits em uma representação de matriz e resintetize a partir daí. |
Plugin default integrado
Usar um método padrão escolhido pelo Qiskit para tradução. A partir do Qiskit 2.0, é o mesmo que o plug-in do tradutor integrado, mas o algoritmo escolhido pode mudar durante a série 2.x, seja para todos os destinos ou somente para determinadas classes de destino.
Plugin synthesis integrado
unitary_synthesis_methodReúna as sequências de portas nos mesmos qubits na forma de matriz e, em seguida, ressintetize usando a UnitarySynthesis passagem (com a configuração definida). Isso é, em grande parte, semelhante ao próprio ciclo de otimização em níveis elevados de otimização.
A coleta em matrizes é normalmente mais cara do que as traduções sem matrizes, mas, em princípio, a qualidade das traduções pode ser melhor. Na prática, isso exige um algoritmo de síntese adaptado ao ISA de destino, o que torna esse método menos geral do que outros métodos. Ele pode produzir resultados de maior qualidade ao direcionar ISAs simples que correspondem às rotinas de síntese já existentes no Qiskit.
Se esse método for usado, talvez você não precise do loop de otimização.
O nível de otimização não tem efeito sobre esse plug-in.
Plugin translator integrado
Utiliza o BasisTranslator algoritmo para traduzir simbolicamente as portas para a base de destino. Em linhas gerais, esse processo parte do conjunto de portas solicitadas pelo circuito e utiliza regras de um determinado EquivalenceLibrary (normalmente o SessionEquivalenceLibrary) para avançar em direção à ISA.
Esse é o método de tradução padrão.
O nível de otimização não tem efeito sobre esse plug-in.
Fase de otimização
Explicação do estágio de otimização
Explicação de alto nível para o usuário sobre o estágio de otimização no guia IBM Quantum.
O estágio de otimização é para otimizações de baixo nível com reconhecimento de hardware. Ao contrário do estágio de inicialização, a entrada para esse estágio é um circuito que já é compatível com ISA, portanto, um plug-in de otimização de baixo nível pode ser adaptado para uma ISA específica.
Existem muito poucos requisitos para um plug-in de otimização, além de ele aceitar circuitos compatíveis com a ISA e retornar circuitos compatíveis com a ISA. Um plug-in de otimização geralmente contém um loop, como o DoWhileController, e pode incluir a etapa de tradução configurada como um pipeline de correção.
Os plug-ins de otimização integrados do Qiskit são gerais e se aplicam bem à maioria dos ISAs do mundo real para dispositivos sem correção de erros. Os plug-ins integrados são menos adequados para ISAs que não têm uma porta de um único qubit continuamente parametrizada.
Ao escrever plug-ins de estágio, o ponto de entrada para optimization é qiskit.transpiler.optimization. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Um conjunto padrão de passes de otimização. Isso varia significativamente entre os níveis de otimização. |
Plugin default integrado
Isso varia significativamente dependendo do nível de otimização.
Os detalhes deste pipeline estão sujeitos a alterações entre as versões do Qiskit. Os princípios gerais são descritos a seguir.
No nível de otimização 0, o estágio está vazio.
No nível de otimização 1, o estágio faz a ressíntese baseada em matriz de execuções de portas de um único qubit e o cancelamento inverso simbólico muito simples de portas de dois qubits, se elas aparecerem consecutivamente. Isso é executado em um loop até que o tamanho e a profundidade do circuito sejam fixados.
No nível de otimização 2, além das otimizações do nível 1, o loop contém análise de comutação de conjuntos de portas para ampliar a gama de portas que podem ser consideradas para cancelamento. Antes do loop, as execuções de portas de um e dois qubits passam por uma única ressíntese baseada em matriz.
No nível de otimização 3, a ressíntese baseada em matriz de dois qubits é executada dentro do loop de otimização. A condição do loop de otimização também tenta várias execuções e escolhe o ponto mínimo no caso de saída flutuante; isso é necessário porque a ressíntese baseada em matriz é relativamente instável em termos de portas concretas.
O nível 3 de otimização é normalmente muito caro para circuitos grandes.
Fase de programação
Uma explicação em nível de guia dos conceitos de agendamento.
O estágio de programação, se solicitado, é responsável por inserir instruções explícitas Delay instruções para tornar explícitos os períodos ociosos dos qubits. Os plug-ins podem, opcionalmente, optar por fazer transformações sensíveis ao tempo de parede, como a inserção de sequências de desacoplamento dinâmico.
A entrada para o estágio de programação é um circuito compatível com ISA. A saída do estágio de programação também deve ser um circuito compatível com ISA, com instruções explícitas Delay instruções explícitas que satisfaçam as informações de tempo do hardware, se apropriado.
PropertySetA etapa de agendamento deve definir a node_start_time propriedade no pipeline.
Ao escrever plug-ins de estágio, o ponto de entrada para scheduling é qiskit.transpiler.scheduling. Os plug-ins integrados são:
Método | Resumo |
|---|---|
| padrão | Tentativa de satisfazer as restrições de alinhamento de tempo sem programar de outra forma. |
| alap | Programe o circuito, preferindo que as operações ocorram o mais tarde possível. |
| O QUANTO ANTES | Agendar o circuito, preferindo que as operações sejam realizadas o mais rápido possível. |
Plugin default integrado
Não faça nada, a menos que o circuito já contenha instruções com tempos explícitos. Se houver operações com temporização explícita no circuito, insira um preenchimento adicional para garantir que essas temporizações satisfaçam o alinhamento e outras restrições de hardware.
Plugin alap integrado
Programe explicitamente todas as operações utilizando uma estratégia do tipo “o mais tarde possível”. Isso utiliza o ALAPScheduleAnalysis algoritmo para decidir onde posicionar as portas.
Plugin asap integrado
Programe explicitamente todas as operações utilizando uma estratégia do tipo “o mais rápido possível”. Isso utiliza o ASAPScheduleAnalysis algoritmo para decidir onde posicionar as portas.
Gerenciadores de passes personalizados
Além de modificar gerenciadores de passagem predefinidos, também é possível criar um gerenciador de passagem para construir um pipeline totalmente personalizado para a transformação de circuitos de entrada. Você pode usar a StagedPassManager classe diretamente para fazer isso. Você pode definir nomes de etapas arbitrários e preenchê-los com uma PassManager instância. Por exemplo, o código a seguir cria um novo StagedPassManager que possui duas etapas, init e translation.
from qiskit.transpiler.passes import (
UnitarySynthesis,
Collect2qBlocks,
ConsolidateBlocks,
UnitarySynthesis,
Unroll3qOrMore,
)
from qiskit.transpiler import PassManager, StagedPassManager
basis_gates = ["rx", "ry", "rxx"]
init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()])
translate = PassManager(
[
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates),
]
)
staged_pm = StagedPassManager(
stages=["init", "translation"], init=init, translation=translate
)Não há limite para o número de etapas que você pode incluir em um StagedPassManager. As etapas não precisam corresponder às etapas utilizadas pelos pipelines predefinidos do Qiskit.
As funções do gerador Stage podem ser úteis para a construção de instâncias personalizadas StagedPassManager . Eles geram gerenciadores de passos que oferecem funcionalidades comuns utilizadas em várias etapas. Por exemplo, generate_embed_passmanager() gera um PassManager para “incorporar” um inicial Layout selecionado de uma passagem de layout ao dispositivo de destino especificado.
Escrevendo passagens personalizadas do transpiler
O Qiskit foi projetado para ser ampliado com passes de transpiladores personalizados e especializados.
Existem dois tipos de passagem do transpiler: as passagens de “análise” (AnalysisPass), que leem um circuito e gravam propriedades de análise globais no PropertySet; e as passagens de “transformação” (TransformationPass), que modificam um DAGCircuit no próprio local ou retornam um novo DAGCircuit. Historicamente, o Qiskit tentou separar claramente esses dois tipos. TransformationPassNo Qiskit moderno, no entanto, é bastante comum incluir tanto a análise quanto as modificações em um único arquivo independente, em vez de tentar separar tudo. Se a sua análise for baseada exclusivamente em dados, ainda assim é adequado usar AnalysisPass.
Princípios gerais da autoria de passes
TransformationPassDAGCircuitSe você quiser modificar ou criar um novo, é preciso escrever um. Se você quiser apenas gravar no PropertySet e não modificar o DAGCircuit, deve escrever um AnalysisPass. Se você quiser fazer as duas coisas, escreva um TransformationPass. Em ambos os casos, o único método obrigatório é BasePass.run(), que constitui a parte principal da sua passagem. Isso deve aceitar um único argumento (que não seja self), dag: DAGCircuit. Se a TranspilerPass, deve retornar um DAGCircuit (que pode ser a entrada, caso tenha sido modificada no próprio local), enquanto que, se a AnalysisPass, deve retornar None.
Se sua passagem tiver um inicializador, você deverá chamar super().__init__().
Normalmente, seu pass deve aceitar um Target em seu inicializador, que descreve o hardware quântico para o qual você está compilando. Não é recomendável aceitar restrições “vagas”, como um mapa de acoplamento separado e uma lista de portas de base; a abordagem Target descreve de forma mais correta o hardware heterogêneo em geral.
Durante a execução de um PassManager pipeline, quando o run() método da sua etapa for chamado, você poderá acessar o atributo self.property_set para obter o estado atual PropertySet da transpilagem. Você deve ler e gravar nesse arquivo diretamente. Seu pass deve documentar claramente quais atributos, se houver, do conjunto de propriedades ele lê e grava.
Aleatoriedade e determinismo
A compilação quântica geralmente envolve a solução de problemas que são difíceis de otimizar globalmente. Nesses casos, os algoritmos estocásticos e heurísticos costumam ser mais adequados. No entanto, isso leva a problemas de reprodutibilidade.
Não há nenhum requisito formal para que uma passagem personalizada seja determinística sob o mesmo conjunto exato de regras que as passagens integradas do Qiskit devem seguir. No entanto, recomendamos enfaticamente que você siga essas regras em seus próprios passes; a ciência prospera com a reprodutibilidade, e a depuração é um pesadelo quando não é possível reproduzir o comportamento observado anteriormente.
Ao escrever uma passagem de transpilador, você pode confiar que os exemplos a seguir (representativos e não exaustivos) são determinísticos, embora a semântica exata da ordenação possa não estar totalmente especificada, e as passagens não devem depender de nenhuma ordem específica:
- Os nós de ordem são encontrados em
DAGCircuit.op_nodes(). Em contrapartida,topological_op_nodes()por padrão, inclui uma chave de ordenação que faz com que sua ordem não seja afetada de forma alguma pela ordem de remoção/inserção dos nós; portanto, é totalmente determinístico, desde que seja especificado o mesmo conjunto de nós com o mesmo fluxo de dados, mesmo que tenha sido construído em uma ordem diferente. - As arestas de ordem são encontradas em
DAGCircuit.edges(). - A ordem em que as execuções são retornadas e a ordem exata em
DAGCircuit.collect_2q_runs()que os nós de uma execução são encontrados. - A ordem em que os nós são encontrados em métodos com ordem degenerada, tais como
predecessors(),bfs_successors(), e assim por diante.
Em geral, o requisito é que todas as mesmas modificações no circuito sejam feitas, exatamente na mesma ordem. Por exemplo, se for necessário adicionar, contrair ou remover nós, a ordem dessas modificações deve ser feita em uma ordem determinística, e as substituições devem ser especificadas de forma determinística.
Algumas dicas para garantir isso incluem:
-
Tenha muito cuidado ao iterar sobre contêineres baseados em hash. Iteração sobre Python 's
setnão é determinística devido à randomização de hash-seed. No Rust, a iteração sobre os contêineres baseados em hash da biblioteca padrão, incluindo os equivalentes dohashbrowncom seus hashers padrão, não é determinística.NotaIteração sobre Python 's
dicté determinística e garantida em ordem de inserção se não houver remoções, e em ordem arbitrária, mas ainda determinística, se houver remoções determinísticas.Em Python, se você precisar criar um
sete depois iterar sobre ele, considere usar umdictcom todos os valores sendoNonecomo um substituto. Usar umsetpuramente para teste de associação não é um problema.No Rust, use
indexmape seus structsIndexMapeIndexSetcomo substitutos deHashMapeHashSet, respectivamente; eles têm propriedades de iteração determinística semelhantes às de Pythondict. -
Se o seu pass tiver componentes estocásticos, certifique-se de aceitar uma
seedentrada e torne sua saída pura caso ela seja fornecida como um número inteiro. Normalmente, isso significa armazenar o seed e instanciar um novo pRNG a partir desse seed no início de cada chamada paraBasePass.run(). -
Se estiver usando o paralelismo com threads, tome cuidado para que sua saída não dependa da ordem em que os threads fazem seu trabalho ou retornam seus resultados parciais. Por exemplo, se estiver distribuindo o trabalho em um pool de threads e coletando os resultados no final, certifique-se de que a saída esteja organizada em uma ordem correspondente à entrada. Em Python, funções como
concurrent.futures.ThreadPoolExecutor.map()garantem isso. Da mesma forma, no Rust, os iteradores paralelos dorayoncoletarão sua saída na mesma ordem da entrada.Cuidado com o fato de que _reductions_ paralelas, como "aplicar uma função a cada item neste iterador e escolher aquele que minimiza alguma métrica", normalmente são altamente suscetíveis ao não determinismo de threads, no caso de degenerescências na métrica. Por exemplo, se dois itens no iterador produzirem resultados não iguais que, no entanto, tenham a mesma chave de comparação, o escolhido em um ambiente com threads não é determinístico. Para evitar isso, aplique um desempate determinístico para eliminar a degenerescência, por exemplo, enumerando a entrada e usando o número de sequência como chave de desempate, de modo que, se dois itens tiverem a mesma pontuação, o que corresponder a uma entrada anterior será escolhido de forma confiável.
Representando computadores quânticos
Para poder compilar um código QuantumCircuit para um backend específico, o transpilador precisa de uma representação especializada desse backend, incluindo suas restrições, conjunto de instruções, propriedades dos qubits e outros aspectos, a fim de poder compilar e otimizar de forma eficaz. Embora a BackendV2 classe defina uma interface para consultar e interagir com back-ends, seu escopo vai além das necessidades do transpiler, incluindo o gerenciamento do envio de tarefas e, possivelmente, a interação com serviços remotos. As informações específicas necessárias ao transpiler são descritas pela Target classe
Por exemplo, para construir um objeto simples Target , pode-se adicionar, de forma iterativa, descrições das instruções que ele suporta:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
print(target)Target
Number of qubits: 3
Instructions:
cx
(0, 1):
Duration: 5e-07 sec.
Error Rate: 0.0001
u
(0,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(1,):
Duration: 6e-08 sec.
Error Rate: 2e-05
rz
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
ry
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
rx
(1,):
Duration: 5e-08 sec.
Error Rate: 1e-05
(2,):
Duration: 6e-08 sec.
Error Rate: 2e-05
cz
(1, 2):
Duration: 5e-07 sec.
Error Rate: 0.0001
(2, 0):
Duration: 5e-07 sec.
Error Rate: 0.0001
measure
(0,):
Duration: 5e-05 sec.
Error Rate: 0.001
(1,):
Duration: 6e-05 sec.
Error Rate: 0.002
(2,):
Duration: 5e-07 sec.
Error Rate: 0.2Isso Target representa um backend de 3 qubits que suporta CXGate entre os qubits 0 e 1, UGate nos qubits 0 e 1, RZGate, RXGate, e RYGate nos qubits 1 e 2, CZGate entre os qubits 1 e 2 e entre os qubits 2 e 0, e Measure em todos os qubits.
Existem também estruturas de dados específicas para representar um subconjunto específico de informações do Target. Por exemplo, a CouplingMap classe é usada exclusivamente para representar as restrições de conectividade de um backend como um grafo direcionado. É possível gerar um mapa de acoplamento a partir de um Target usando o Target.build_coupling_map() método. Essas estruturas de dados geralmente são anteriores à Target classe, mas ainda são utilizadas por algumas etapas do transpiler que ainda não funcionam nativamente com uma Target instância da classe ou ao lidar com backends que não utilizam a interface mais recente BackendV2 .
Por exemplo, se quiséssemos visualizar o CouplingMap para o exemplo de 3 qubits Target acima:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map().draw()Isso mostra a conectividade global do, Target que é a combinação dos qubits suportados para CXGate e CZGate. CouplingMap.build_coupling_map()Para verificar a conectividade de cada uma, você pode passar o nome da operação para:
from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map('cx').draw()from qiskit.circuit import Parameter, Measure
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import UGate, RZGate, RXGate, RYGate, CXGate, CZGate
target = Target(num_qubits=3)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
UGate(Parameter('theta'), Parameter('phi'), Parameter('lam')),
{
(0,): InstructionProperties(error=.00001, duration=5e-8),
(1,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RZGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RYGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
RXGate(Parameter('theta')),
{
(1,): InstructionProperties(error=.00001, duration=5e-8),
(2,): InstructionProperties(error=.00002, duration=6e-8)
}
)
target.add_instruction(
CZGate(),
{
(1, 2): InstructionProperties(error=.0001, duration=5e-7),
(2, 0): InstructionProperties(error=.0001, duration=5e-7)
}
)
target.add_instruction(
Measure(),
{
(0,): InstructionProperties(error=.001, duration=5e-5),
(1,): InstructionProperties(error=.002, duration=6e-5),
(2,): InstructionProperties(error=.2, duration=5e-7)
}
)
target.build_coupling_map('cz').draw()Programação de circuitos
Como configurar os estágios de agendamento dos gerenciadores de passes predefinidos.
Depois que o circuito tiver sido traduzido para a base de destino, mapeado para o dispositivo e otimizado, uma fase de programação pode ser aplicada para contabilizar, opcionalmente, todo o tempo ocioso no circuito. Em um nível mais alto, o agendamento pode ser considerado como a inserção de atrasos no circuito para contabilizar o tempo ocioso nos qubits entre a execução das instruções. Por exemplo, se começarmos com um circuito como o seguinte:
podemos então chamar transpile() com scheduling_method definido:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
backend = GenericBackendV2(5)
ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0,range(1,5))
circ = transpile(ghz, backend, scheduling_method="asap")
circ.draw(output='mpl')
Você pode ver aqui que o transpilador inseriu Delay para contabilizar o tempo ocioso em cada qubit. Para ter uma ideia melhor do tempo do circuito, também podemos examiná-lo com a função timeline.draw() :
A programação de um circuito envolve duas etapas: análise e mapeamento de restrições, seguidas por uma etapa de preenchimento. A primeira parte requer a execução de uma etapa de análise de agendamento, como ALAPSchedulingAnalysis ou ASAPSchedulingAnalysis , que analisa o circuito e registra a hora de início de cada instrução no circuito utilizando um algoritmo de agendamento (“o mais tarde possível” para ALAPSchedulingAnalysis e “o mais cedo possível” para ASAPSchedulingAnalysis) no conjunto de propriedades. Depois que o circuito tiver uma programação inicial, é possível executar etapas adicionais para levar em conta quaisquer restrições de tempo no backend de destino, como restrições de alinhamento. Isso geralmente é feito com a opção ConstrainedReschedule pass, que ajustará a programação definida no conjunto de propriedades às restrições do backend de destino. Assim que todo o processo de programação e os ajustes/reprogramações forem concluídos, é executada uma etapa de preenchimento, como PadDelay ou PadDynamicalDecoupling , para inserir as instruções no circuito, o que conclui a programação.
API do Transpiler
Descrição do hardware
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
Target( [descrição, num_qubits, dt,...] ) | A intenção do objeto Target é informar o compilador do Qiskit sobre as restrições de um backend específico para que o compilador possa compilar um circuito de entrada em algo que funcione e seja otimizado para um dispositivo. |
InstructionProperties( [duração, erro] ) | Uma representação das propriedades de uma implementação de porta. |
WrapAngleRegistry() | Registro da função Angle Wrapping |
Definição de Gerenciador de Senhas
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
StagedPassManager( [etapas] ) | Um pipeline de gerenciador de passes criado a partir de estágios individuais. |
PassManager( [passes, max_iteration] ) | Gerenciador de um conjunto de Passes e sua programação durante a transpilação. |
PassManagerConfig( [layout_inicial,...] ) | Configuração do Pass Manager. |
PassManagerCliffordTConfig( [layout_inicial,...] ) | Configuração do Pass Manager para a transpilagem do Clifford+T. |
generate_preset_pass_manager([...]) | Gerar uma predefinição PassManager |
generate_preset_clifford_t_pass_manager([...]) | StagedPassManagerGerar uma predefinição Clifford+T. |
generate_preset_pbc_pass_manager([...]) | StagedPassManagerGerar uma predefinição PBC. |
Layout e topologia
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
Layout( [input_dict] ) | Dit de duas vias para representar um layout. |
CouplingMap( [lista de acoplamentos, descrição] ) | Gráfico direcionado que especifica o acoplamento fixo. |
TranspileLayout(layout_inicial,...[,...] ) | Atributos de layout para o circuito de saída do transpilador. |
Planejamento
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
InstructionDurations( [duração das aulas, dt] ) | Classe auxiliar para fornecer durações de instruções para agendamento. |
Resumo Passe
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
TransformationPass(*args, **kwargs) | Uma passagem de transformação: altere o DAG, não o conjunto de propriedades. |
AnalysisPass(*args, **kwargs) | Uma passagem de análise: alterar o conjunto de propriedades, não o DAG. |
Exceções
TranspilerError
exception qiskit.transpiler.TranspilerError(*message)
Bases: TranspilerAccessError
Exceções levantadas durante a transpilação.
Defina a mensagem de erro.
TranspilerAccessError
exception qiskit.transpiler.TranspilerAccessError(*message)
Bases: PassManagerError
DEPRECATED: Exceção de erro de acesso nas passagens do transpilador.
Defina a mensagem de erro.
CouplingError
exception qiskit.transpiler.CouplingError(*msg)
Bases: QiskitError
Classe base para erros gerados pelo objeto do gráfico de acoplamento.
Defina a mensagem de erro.
LayoutError
exception qiskit.transpiler.LayoutError(*msg)
Bases: QiskitError
Erros gerados pelo objeto de layout.
Defina a mensagem de erro.
CircuitTooWideForTarget
exception qiskit.transpiler.CircuitTooWideForTarget(*message)
Bases: TranspilerError
Erro gerado se o circuito for muito largo para o alvo.
Defina a mensagem de erro.
InvalidLayoutError
exception qiskit.transpiler.InvalidLayoutError(*message)
Bases: TranspilerError
Erro gerado quando um layout fornecido pelo usuário é inválido.
Defina a mensagem de erro.
Métrica de otimização
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
OptimizationMetric(*valores) | Métrica de otimização considerada durante a transpilação. |