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:
O Qiskit usa a representação intermediária DAGCircuit (IR) de um circuito em toda a pilha do transpilador, em vez da representação baseada em árvore QuantumCircuit. Um pipeline de transpilador é um objeto PassManager cujo método PassManager.run() recebe um QuantumCircuit e o converte em um DAGCircuite, em seguida, submete o IR a uma sequência de passagens, retornando finalmente a QuantumCircuit de volta. Uma passagem é um AnalysisPassque calcula e armazena as propriedades sobre o circuito no estado PropertySetou um TransformationPassque modifica o IR para atingir um objetivo singular específico. Você pode pensar em um pipeline como sendo dividido em "estágios", em que cada estágio é responsável por uma transformação de alto nível.
O Qiskit expõe um construtor de pipeline de transpilação padrão usando a função generate_preset_pass_manager(). Isso retorna um pipeline configurado corretamente para transpilação completa, em um optimization_level escolhido (entre 0 e 3, inclusive). A menos que você esteja procurando algo altamente especializado, esse é quase certamente o ponto de entrada desejado. Um exemplo de transpilação tem a seguinte aparência:
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, a função generate_preset_pass_manager() invoca um pipeline de transpilação especializado quando a base de destino é composta por portas Clifford+T; consulte clifford_t_pass_manager() a documentaçã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 "gerentes de passagem predefinidos". Todas essas são instâncias de PassManagere, portanto, são usadas passando um QuantumCircuit para o método PassManager.run() método. Mais especificamente, os gerenciadores de passagem predefinidos são instâncias de StagedPassManagerque permitem maior configuração dos estágios individuais de uma transpilação, incluindo ganchos pré e pós-estágio.
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 corresponda às restrições de conectividade do Target. As portas inseridas ainda não precisam corresponder à ISA de destino, portanto, geralmente são apenas instruções swap . Às vezes, esse estágio é omitido quando o estágio layout faz seu trabalho. Consulte Estágio de roteamento para obter mais detalhes.
translation
Converta todas as portas do circuito em portas que correspondam ao ISA do Target. Consulte o estágio 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 afeta quais implementações são usadas para um determinado estágio por padrão, embora isso possa ser substituído passando argumentos <stage>_method="<choice>" explícitos 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 DAGCircuit de maneira determinística caso sua randomização (se houver) tenha sido inicializada, 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 passagens incorporadas sem componentes estocásticos devem respeitar as regras de saída determinística para entrada idêntica. É permitido manter um cache para fins de eficiência, mas, considerando o mesmo conjunto de entradas, os retornos da passagem devem ser os mesmos se a passagem for chamada várias vezes, a menos que algo fora do controle da passagem altere uma de suas entradas no local (por exemplo, o
BasisTranslatorusa oSessionEquivalenceLibrarypor padrão nos gerenciadores de passagem predefinidos, e não é um bug obter resultados diferentes se novas entradas forem adicionadas à biblioteca de equivalência). Uma "saída" é qualquer coisa que a passagem escreva para consumo posterior; pode ser o valorreturnexplícito da passagem, mas também inclui propriedades destinadas ao consumo posterior 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, após a execução de qualquer combinação de passagens do Qiskit integradas — com valores iniciais, se for o caso — com entradas fixas, que a saída exata de todos DAGCircuit() os métodos é determinística. Isso inclui a ordem de saída mesmo de métodos que não fazem 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 estágio 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, o site routing_method="sabre" sempre usará um algoritmo derivado do Sabre). A construção interna exata do PassManager que representa o estágio não é, entretanto; a ordem das passagens pode mudar entre versões menores, ou novas passagens 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 StagedPassManagervocê 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 (usando o passe PadDynamicalDecoupling ) e também adicionar 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 passes em estágios for executado por meio do método run() o gerenciador de passes logical_opt será chamado antes do estágio layout , e o gerenciador de passes scheduling_pm será usado para o estágio scheduling 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.
O estágio init é responsável por otimizações lógicas de alto nível em circuitos abstratos e por reduzir as operações de vários qubits (3+) a uma série de operações de um e dois qubits. Como essa é a execução do primeiro estágio, sua entrada é um circuito totalmente abstrato. O estágio init deve ser capaz de lidar com portas personalizadas definidas pelo usuário e todos os objetos abstratos de alto nível de descrição de circuitos, 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 plugin padrão simplesmente “desdobra” as operações com mais de três qubits, acessando seus campos definition hierárquicos.
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" (consulte
ElidePermutations), em que as operações explícitas de indução de permutação são removidas e, em vez disso, efetuadas como 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.
O estágio de layout deve definir as propriedades layout e original_qubit_indices no arquivo PropertySet.
Todos os plug-ins integrados para o estágio de layout darão prioridade a um layout explícito selecionado usando o argumento initial_layout 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 o estágio de layout é encontrar um layout "perfeito", em que todas as operações respeitem as restrições de conectividade do sistema Target de modo que o estágio de roteamento não seja necessário. Normalmente, isso não é possível para circuitos de entrada arbitrários, mas, quando isso acontece, o VF2Layout pode ser usada para encontrar um layout inicial válido. Se forem encontrados vários layouts perfeitos, uma heurística de pontuação baseada em taxas de erro estimadas é usada para decidir qual deles usar.
Em todos os plug-ins incorporados, passar o argumento generate_preset_pass_manager()initial_layout faz com que o layout fornecido seja usado literalmente, ignorando a lógica de "escolha" individual. 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 para o avaliador de isomorfismo aumenta com o nível de otimização. Para alvos enormes e complexos, não temos a garantia de encontrar layouts perfeitos, mesmo que eles existam, mas a chance aumenta com o nível de otimização. - Se não for possível encontrar um layout perfeito, use
SabreLayoutpara escolher um layout inicial, com o número de tentativas de layout inicial, tentativas de mapa de troca e iterações para frente e para trás aumentando com 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
Usa a passagem DenseLayout para escolher o layout. Essa passagem encontra o subgráfico conectado mais denso do gráfico de conectividade de destino completo, em que "mais denso" significa que os qubits de hardware com o maior número de conexões disponíveis são preferidos. O mapeamento virtual para hardware é concluído com a atribuição dos qubits virtuais de maior grau aos qubits de hardware de maior grau.
Essa é uma heurística relativamente barata para a escolha de um layout inicial, mas normalmente tem uma qualidade de saída muito pior do que os métodos baseados no Sabre. O plug-in de layout padrão usa o mapeamento inicial selecionado por DenseLayout como um de seus layouts iniciais para alimentar o algoritmo do Sabre.
Plugin trivial integrado
Usa a passagem TrivialLayout para escolher o layout. Essa é a atribuição mais simples, em que cada qubit virtual é atribuído ao qubit de hardware com o mesmo índice, de modo que 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
Usa o SabreLayout para escolher um layout inicial, usando o algoritmo de roteamento Sabre modificado do Qiskit como sub-rotina para mapear o circuito candidato para frente e para trás.
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.
O estágio 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 literalmente swap gates no circuito, mesmo que o 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 que tenha uma porta aplicada no circuito.
O estágio de roteamento deve definir as propriedades final_layout e virtual_permutation_layout no arquivo PropertySet se o roteamento tiver ocorrido.
Todos os estágios de roteamento incorporados do Qiskit executarão adicionalmente o VF2PostLayout após o roteamento. Isso pode reatribuir o layout inicial, se for possível encontrar qubits com menos erros. Essa passagem é muito semelhante à classe VF2Layout que o plug-in de layout padrão usa, exceto que em VF2PostLayout podemos garantir que há pelo menos um subgráfico 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 encontrar o número mínimo de trocas a serem inseridas é um problema não polinomial. Isso significa que é proibitivamente caro tentar, portanto, muitos dos algoritmos incorporados do Qiskit são estocásticos, e você poderá ver grandes variações entre diferentes compilações. Se você precisar de reprodutibilidade, certifique-se de definir o argumento seed_transpiler 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
Usar um método padrão escolhido pelo Qiskit para roteamento. A partir do Qiskit 2.0, o algoritmo escolhido é o mesmo do plug-in Sabre integrado, embora, na prática, geralmente o plug-in de estágio de layout padrão integrado execute o algoritmo de roteamento baseado no Sabre, e o estágio de roteamento seja usado 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 a VF2PostLayout faz 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
Usa o algoritmo LookaheadSwap algoritmo para rotear. Essencialmente, trata-se de uma pesquisa de amplitude em primeiro lugar para produzir uma rede de trocas, em que a árvore que está sendo explorada é reduzida a 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 pesquisa, a quantidade de poda por profundidade e a quantidade de trabalho realizado pelo VF2PostLayout para pós-otimizar 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
Usa o algoritmo SabreSwap algoritmo para rotear. Isso usa a versão aprimorada do algoritmo de roteamento Sabre original do Qiskit.
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 afeta quantas sementes estocásticas diferentes são tentadas para o roteamento completo e a quantidade de trabalho realizado pelo VF2PostLayout para pós-otimizar 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
Colete execuções de portas nos mesmos qubits em forma de matriz e, em seguida, resintetize usando a passagem UnitarySynthesis (com o unitary_synthesis_method configurado). Isso é, em grande parte, semelhante ao próprio loop de otimização em altos níveis 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
Usa o algoritmo BasisTranslator para traduzir simbolicamente as portas para a base de destino. Em um nível mais alto, isso começa com o conjunto de portas solicitadas pelo circuito e usa regras de um determinado EquivalenceLibrary (normalmente o SessionEquivalenceLibrary) para avançar em direção ao 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.
Há pouquíssimos requisitos em um plug-in de otimização, a não ser o fato de ele receber circuitos compatíveis com ISA e retornar circuitos compatíveis com ISA. Um plug-in de otimização geralmente contém um loop, como o DoWhileControllere pode incluir o estágio de tradução configurado 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.
A etapa de agendamento deve definir a propriedade node_start_time na seção PropertySet.
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 usando uma estratégia "o mais tarde possível". Isso usa o algoritmo ALAPScheduleAnalysis algoritmo para decidir onde colocar as portas.
Plugin asap integrado
Programe explicitamente todas as operações usando uma estratégia "o mais rápido possível". Isso usa o algoritmo ASAPScheduleAnalysis algoritmo para decidir onde colocar as portas.
Gerenciadores de passes personalizados
Além de modificar os gerenciadores de passagem predefinidos, também é possível construir um gerenciador de passagem para criar um pipeline totalmente personalizado para transformar os circuitos de entrada. Você pode usar a classe StagedPassManager diretamente para fazer isso. Você pode definir nomes de estágios arbitrários e preenchê-los com uma instância PassManager instância. Por exemplo, o código a seguir cria um novo StagedPassManager que tem dois estágios, 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 estágios que você pode colocar em um StagedPassManager. Os estágios não precisam corresponder aos estágios usados pelos pipelines predefinidos do Qiskit.
As funções do gerador Stage podem ser úteis para a construção de instâncias personalizadas StagedPassManager personalizadas. Eles geram gerenciadores de passes que fornecem funcionalidade comum usada em muitos estágios. Por exemplo, generate_embed_passmanager() gera um PassManager para "incorporar" uma inicial selecionada Layout selecionada de uma passagem de layout para o dispositivo de destino especificado.
Escrevendo passagens personalizadas do transpiler
O Qiskit foi projetado para ser ampliado com passes de transpiladores personalizados e especializados.
Há dois tipos de passagem do transpilador: passes de "análise" (AnalysisPass), que leem um circuito e escrevem propriedades globais de análise no site PropertySet; e passagens de "transformação" (TransformationPass), que modificam um circuito DAGCircuit no lugar, ou retornam um novo DAGCircuit. Historicamente, a Qiskit tentou separar fortemente esses dois tipos. No entanto, no Qiskit moderno, é bastante comum incluir tanto a análise quanto as modificações em um único arquivo TransformationPassautônomo, em vez de tentar dividir tudo. Se o seu passe for puramente baseado em análise, ainda será apropriado usar AnalysisPass.
Princípios gerais da autoria de passes
Se você quiser modificar ou criar um novo DAGCircuitvocê deve escrever um arquivo TransformationPass. Se você quiser apenas escrever no site PropertySet e não modificar o DAGCircuitvocê deve escrever um AnalysisPass. Se você quiser fazer as duas coisas, escreva um arquivo TransformationPass. Em ambos os casos, o único método necessário é o BasePass.run(), que é a essência do seu passe. Isso deve aceitar um único argumento (diferente de self), dag: DAGCircuit. Se for um TranspilerPass, ele deverá retornar um DAGCircuit (que pode ser a entrada, se modificado no local), enquanto que se for um AnalysisPass, ele deve retornar None.
Se sua passagem tiver um inicializador, você deverá chamar super().__init__().
Normalmente, seu passe deve aceitar um Target em seu inicializador, que descreve o hardware quântico para o qual você está compilando. A aceitação de restrições "frouxas", como um mapa de acoplamento separado e uma lista de portas de base, é desencorajada Target é mais corretamente descritivo do hardware heterogêneo geral.
Durante a execução de um PassManager quando o método run() da sua passagem for chamado, você poderá acessar o atributo self.property_set para obter o estado PropertySet atual da transpilação. Você deve ler e gravar nesse local. Seu passe deve documentar claramente quais atributos, se houver, no conjunto de propriedades são lidos e gravados.
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(). Por outro lado,topological_op_nodes()por padrão, inclui uma chave de ordenação que faz com que sua ordem não seja afetada pela ordem de remoção/inserção de nós, portanto, é totalmente determinística, desde que o mesmo conjunto de nós com o mesmo fluxo de dados seja especificado, mesmo que tenha sido criado em uma ordem diferente. - A ordem em que as bordas são encontradas em
DAGCircuit.edges(). - A ordem em que as execuções são retornadas de
DAGCircuit.collect_2q_runs()e a ordem exata em que os nós em uma execução são encontrados. - A ordem em que os nós são encontrados em métodos de ordem degenerada, 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 novopRNGa 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 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 de qubit e muito mais, para poder compilar e otimizar com eficiência. Embora a classe BackendV2 define uma interface para consulta e interação com backends, seu escopo é maior do que apenas as necessidades do transpilador, incluindo o gerenciamento do envio de trabalhos e a possível interface com serviços remotos. As informações específicas necessárias para o transpilador são descritas pela Target classe
Por exemplo, para construir um objeto Target simples, é possível adicionar iterativamente 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, RXGatee RYGate nos qubits 1 e 2, CZGate entre os qubits 1 e 2, e os qubits 2 e 0, e Measure em todos os qubits.
Há também estruturas de dados específicas para representar um subconjunto específico de informações do Target. Por exemplo, a classe CouplingMap é usada apenas para representar as restrições de conectividade de um backend como um gráfico direcionado. Um mapa de acoplamento pode ser gerado a partir de um Target usando o método Target.build_coupling_map() método. Essas estruturas de dados normalmente são anteriores à classe Target mas ainda são usadas por alguns passes do transpilador que não funcionam nativamente com uma instância Target ou ao lidar com backends que não estão usando a interface BackendV2 interface.
Por exemplo, se quisermos 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. Para ver a conectividade individual, você pode passar o nome da operação para CouplingMap.build_coupling_map():
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 partes: análise e mapeamento de restrições, seguido de uma passagem de preenchimento. A primeira parte requer a execução de uma passagem 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 usando 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 um agendamento inicial, outras passagens podem ser executadas para levar em conta quaisquer restrições de tempo no backend de destino, como restrições de alinhamento. Normalmente, isso é feito com a passagem ConstrainedReschedule que ajustará o conjunto de agendamento na propriedade definida para as restrições do backend de destino. Quando toda a programação e os ajustes/reprogramação estiverem concluídos, uma passagem de preenchimento, como PadDelay ou PadDynamicalDecoupling é executada para inserir as instruções no circuito, o que conclui a programação.
API do Transpiler
Descrição do hardware
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
StagedPassManager([estágios] ) | Um pipeline de gerenciador de passes criado a partir de estágios individuais. |
PassManager([passes, max_iteração] ) | Gerenciador de um conjunto de Passes e sua programação durante a transpilação. |
PassManagerConfig([initial_layout,...] ) | Configuração do Pass Manager. |
generate_preset_pass_manager([...]) | Gerar uma predefinição PassManager |
Layout e topologia
Layout([input_dict] ) | Dit de duas vias para representar um layout. |
CouplingMap([lista de acoplamento, descrição] ) | Gráfico direcionado que especifica o acoplamento fixo. |
TranspileLayout(initial_layout,...[,...] ) | Atributos de layout para o circuito de saída do transpilador. |
Planejamento
InstructionDurations([instrução_durações, dt] ) | Classe auxiliar para fornecer durações de instruções para agendamento. |
Resumo Passe
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
OptimizationMetric(*valores) | Métrica de otimização considerada durante a transpilação. |