Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Transpilador

A função generate_boxing_pass_manager() é uma ferramenta flexível e conveniente para construir um qiskit.transpiler.PassManager que pode agrupar as instruções do circuito em caixas anotadas. Isso oferece uma alternativa fácil ao agrupamento e à anotação manual de circuitos, e é útil para automatizar um fluxo de trabalho, testar diferentes estratégias de colocação de circuitos em caixas e assim por diante.

Este guia ilustra como usar generate_boxing_pass_manager() e seus argumentos. Para destacar os efeitos de cada argumento da função, as seções a seguir se referem principalmente ao circuito abaixo.

from qiskit.circuit import Parameter, QuantumCircuit

circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
circuit.rz(Parameter(f"th_{qubit}"), qubit)
circuit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))

circuit.draw("mpl", scale=0.8)

../_images/cf27e1ef03e51b2360c90165a4efeaa793da36083bb674774ca06c8181ef63dd.png

Agrupar operações em caixas​

O argumento enable_gates pode ser definido como True ou False para especificar se as portas de dois qubits devem ser agrupadas em caixas. De forma semelhante, enable_measures especifica se as medições devem ou não ser agrupadas. O trecho a seguir mostra um exemplo em que tanto as portas quanto as medições são agrupadas.

from samplomatic.transpiler import generate_boxing_pass_manager

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/b323dde8a5412e42359aef1831af92ed3702d2251547c663208dc084a2995276.png

Como pode ser visto na figura, o pass manager cria caixas de dois tipos: as que contêm uma única camada de portas de dois qubits e as que contêm uma única camada de medições. Essa separação reflete as práticas padrão em protocolos de aprendizado e mitigação de ruído, que geralmente visam camadas de operações homogêneas. As portas de dois qubits e as medições são colocadas na caixa mais à esquerda que puder acomodá-las, e cada porta de um qubit é colocada na mesma caixa que a porta de dois qubits ou a medição que ela precede.

O código a seguir mostra um exemplo em que enable_gates é definido como False. Como pode ser visto, as portas de dois qubits não são agrupadas em caixas, nem as portas de um qubit que as precedem.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=False,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/3fc8e660b0c7eff1f5aa2e571d064f91b3eb2738c5990727e026a8ffe1101a1c.png

Escolher como anotar as caixas​

Todas as caixas de portas de dois qubits e de medições no circuito retornado possuem anotações de dressing à esquerda. Em particular, todas as caixas que contêm portas de dois qubits são anotadas com um Twirl. Para as caixas de medição, os usuários podem escolher entre Twirl, BasisTranform (com mode definido como "measure") ou ambos. O código a seguir gera um circuito em que todas as caixas recebem twirling e as caixas de medição são anotadas adicionalmente com BasisTranform.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Preparar o circuito para a injeção de ruído​

O inject_noise_targets permite especificar quais caixas devem receber uma anotação InjectNoise. Por exemplo, o código a seguir gera um circuito em que as caixas de portas de dois qubits possuem uma anotação InjectNoise, mas as caixas de medição não.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Se um circuito contém duas ou mais caixas que são equivalentes a menos de portas de um qubit, todas elas são anotadas com uma anotação InjectNoise com o mesmo ref. Assim, o número de refs únicos no circuito retornado é igual ao número de caixas únicas.

O valor de inject_noise_strategy afeta as anotações InjectNoise. Os seguintes valores são possíveis:

  • modifier_ref='': Recomendado quando você não precisa modificar os mapas de ruído antes de amostrar deles.

  • modifier_ref=ref: Recomendado quando todos os mapas de ruído precisam ser escalados uniformemente pelo mesmo fator.

  • Um valor único de modifier_ref: Recomendado quando cada mapa de ruído precisa ser escalado por um fator diferente.

O código a seguir gera um circuito em que as caixas de portas de dois qubits possuem uma anotação InjectNoise com valores únicos de modifier_ref.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Selecionar uma estratégia de twirling​

O boxing pass manager começa escolhendo quais portas emaranhadoras e instruções de medição devem aparecer nas mesmas caixas. Feito isso, as caixas, em geral, não terão a largura total: provavelmente incluirão apenas um subconjunto dos qubits ativos no circuito ou presentes no target do backend. Em configurações típicas do boxing pass manager, como todos os qubits sobre os quais uma instrução de caixa atua recebem twirling (mesmo aqueles sobre os quais nenhuma instrução dentro da caixa atua), ter caixas de largura parcial afeta os efeitos de adaptação de ruído do twirling. Os qubits ociosos não recebem twirling e podem, por exemplo, acumular erros coerentes enquanto a caixa é aplicada. O twirling_strategy oferece opções para estender os qubits pertencentes a cada caixa, modificando assim o número de qubits ociosos:

  • "active" (padrão) não estende as caixas a nenhum qubit ocioso.

  • "active_accum" estende todas as caixas aos qubits sobre os quais uma instrução anterior do circuito já atuou. Isso é útil se você quiser manter os qubits no início do circuito em seu estado fundamental pelo maior tempo possível, não aplicando twirling neles até que se tornem ativos.

  • "active_circuit" estende todas as caixas aos qubits sobre os quais qualquer instrução do circuito atua. Isso é útil se você tem um circuito estruturado com camadas emaranhadoras repetidas e quer minimizar o número de caixas únicas, porque o ruído de cada uma precisa ser aprendido.

  • "all" estende todas as caixas a todos os qubits de todos os registradores quânticos do circuito. Isso é útil como uma extensão de "active_circuit" quando você quer que todos os qubits da QPU sempre recebam twirling.

No exemplo a seguir, cada uma das estratégias é aplicada ao circuito base e, em seguida, os resultados são desenhados.

circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])

circuit.draw("mpl", scale=0.6)

../_images/cdf089891dabe1650f1303c8c747aa8cf03cdde4dfc42c772fe2264bd7a49a1b.png

import matplotlib.pyplot as plt

plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
ax = plt.subplot(2, 2, idx + 1)
pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
transpiled_circuit = pm.run(circuit)
fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
ax.set_title(f"twirling_strategy='{strategy}'")

plt.tight_layout()

../_images/b810ecc4c65822cf60b3d652792a4089c5f931ad1e8320eccdd89bc50a08ba6d.png

Especificar como tratar as barreiras​

Por padrão, as barreiras são removidas do circuito depois que todas as portas emaranhadoras e instruções de medição foram colocadas em caixas conforme instruído, incluindo suas extensões devido à seleção de twirling_strategy, mas antes de as portas de um qubit serem adicionadas à caixa imediatamente à sua direita, se possível.

Por exemplo, o circuito a seguir contém uma barreira:

circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()

circuit_with_barrier.draw("mpl", scale=0.8)

../_images/f2343807597155ab5301205803954b95d29ba0d7370440bd861439fd83d1a193.png

O comportamento padrão impede que as duas portas emaranhadoras acabem na mesma caixa, embora permita que as portas de um qubit sejam absorvidas pela próxima caixa disponível apesar da barreira.

boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

Definir remove_barriers como 'never' preserva as barreiras, de modo que as portas de um qubit ficam restritas.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/6b57e625509dd8ca55fbd96994398b03ac2fe6f76f21535db9bd042d4c68c987.png

Por fim, se as barreiras forem removidas imediatamente, os emaranhadores deixam de ser obrigados a acabar em caixas separadas.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/9a44c59a05a44a3e8f0ce9b7edd3ccc2896c6a68fb00ed110a7e225f52a7ca73.png

Como funciona a estratificação​

O boxing pass manager executa duas passagens principais em sequência:

  1. GroupGatesIntoBoxes — Estratifica as portas emaranhadoras em caixas. As portas de um qubit são ignoradas nesta etapa; apenas as portas de dois qubits são agrupadas.

  2. AbsorbSingleQubitGates — Absorve cada porta de um qubit na caixa elegível mais próxima à sua direita.

Durante a estratificação, GroupGatesIntoBoxes percorre o DAG do circuito em ordem topológica e coloca cada porta de dois qubits no grupo mais antigo cujos qubits ainda não estão ocupados. Três tipos de instruções atuam como delimitadores que encerram o grupo atual para seus qubits, forçando as portas seguintes nesses qubits a um novo grupo:

  • Barreiras

  • Instruções de caixa existentes

  • Measurements

As portas de um qubit não são delimitadores: elas não afetam o agrupamento e são deixadas para AbsorbSingleQubitGates tratar. Atualmente nenhuma análise de comutatividade é realizada; o agrupamento respeita a ordem topológica do circuito como está.

O modo padrão é "after_stratification" porque combina dois benefícios:

  • As barreiras restringem como as portas emaranhadoras são agrupadas (elas atuam como delimitadores durante a estratificação).

  • As barreiras são removidas antes de AbsorbSingleQubitGates ser executado, de modo que as portas de um qubit ainda possam fluir através das antigas posições das barreiras e ser absorvidas pela caixa mais próxima.

Em contraste:

  • "never" mantém as barreiras o tempo todo, o que prende as portas de um qubit entre uma barreira e uma caixa, impedindo sua absorção e deixando rotações sem twirling no circuito.

  • "immediately" remove as barreiras antes da estratificação, de modo que elas não têm efeito no agrupamento.

  • "finally" é útil quando você quer barreiras no circuito de saída (por exemplo, para visualização), mas as portas de um qubit entre uma barreira e uma caixa não são absorvidas.

Box de circuitos ISA com barreiras​

Circuitos com uma estrutura repetida — como simulações hamiltonianas com Trotter, ansatze variacionais e sequências de amplificação de erros — normalmente são construídos de forma programática e depois transpilados para a forma ISA. Adicionar boxes manualmente a um circuito transpilado é trabalhoso, então o fluxo de trabalho natural é o seguinte:

  1. Construa o circuito lógico, inserindo barreiras nas fronteiras de camada que você quer que se tornem fronteiras de box.

  2. Transpile para circuitos ISA. O transpilador do Qiskit preserva as barreiras durante o roteamento e a otimização.

  3. Execute generate_boxing_pass_manager() no circuito ISA.

As barreiras são necessárias quando o fim de uma camada não cobre todos os qubits. Caso contrário, o algoritmo guloso colocaria no mesmo grupo as portas da camada seguinte—nesses qubits “livres”. Como exemplo concreto, considere uma simulação de Heisenberg ZZ em dois passos em um anel de 5 qubits. Cada passo de Trotter tem três camadas de ligações: as ligações pares (0,1), (2,3), as ligações ímpares (1,2), (3,4) e a ligação de fechamento (4,0). Depois da camada de ligações ímpares, os qubits 0 e 4 ficam livres. Sem uma barreira, a ligação de fechamento (4,0) é agrupada de forma gulosa com ligações do próximo passo de Trotter que usam esses qubits livres, misturando portas de passos diferentes no mesmo box.

from qiskit.circuit import Parameter, QuantumCircuit

n = 5 # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)

for step in range(2):
# Layer 1: even bonds (0,1) and (2,3)
for idx in range(0, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
for idx in range(1, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
heisenberg.rzz(dt, n - 1, 0)
if step < 1:
heisenberg.barrier() # separates the two Trotter steps

heisenberg.measure_all()

heisenberg.draw("mpl", scale=0.7)

../_images/172fa55c9f02964fef32fc045a50b86971126be728602b3f1e4b2437df2580b7.png

O código a seguir transpila o circuito para a forma ISA, supondo que o dispositivo subjacente tenha o CX como entrelaçador nativo. Observe que as barreiras sobrevivem à transpilação e continuam visíveis no circuito de saída.

from qiskit.transpiler import generate_preset_pass_manager

preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)

../_images/e9a3fbd6ab1a91051c5cd0273d92ecebc6ae8ebbc370c30bc55daee1fd28bab7.png

Em seguida, aplique o boxing pass manager ao circuito ISA. Cada uma das três camadas de ligações se torna um box separado em cada passo de Trotter.

boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)

../_images/b59e3c047264b3340d9af0f1ca74ad9f7d4c1705d4a5176e441346db9b678213.png

Incorporar o pass manager do Samplomatic aos pass managers predefinidos do Qiskit​

O código abaixo mostra como incorporar generate_boxing_pass_manager() a um pass manager predefinido do Qiskit. O circuito transpilado é ISA e contém boxes.

from qiskit.transpiler import generate_preset_pass_manager

preset_pass_manager = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2]],
optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()

# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager

circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()

transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/fe943b034588a5354a87abcf51b8d805c36f4f2b30a73a39c5886bce9e156b04.png

Esse padrão também se combina naturalmente com o fluxo de trabalho de boxing ISA baseado em barreiras. O boxing pass manager é executado como uma etapa posterior ao escalonamento, depois que as barreiras do circuito lógico original sobreviveram à transpilação, de modo que elas orientam a estratificação como esperado.

preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()

boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)

../_images/4905bf1df63cc83fa581ed006a93beb709be504a4f59fc7f663f4efde62da175.png

Inspecionar e modificar anotações​

Depois do auto-boxing, você pode querer inspecionar as anotações de boxes específicos ou ajustá-las. Por exemplo, para adicionar injeção de ruído, remover anotações ou mudar um grupo de twirling. O Samplomatic fornece um conjunto de funções utilitárias em samplomatic.utils para essa finalidade. Todas elas retornam um novo circuito sem modificar o original.

extend_annotations() acrescenta uma ou mais anotações a cada box do circuito.

from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations

boxed = generate_boxing_pass_manager().run(circuit_with_barrier)

# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

filter_annotations() mantém apenas as anotações dos tipos especificados, removendo as demais. replace_annotations() aplica uma função a cada anotação; essa função retorna uma lista de anotações para substituí-la (retorne [] para remover, uma lista de um elemento para substituir ou uma lista de vários elementos para expandir).

from samplomatic.utils import filter_annotations, replace_annotations

# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)

# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)

A função de uso geral map_annotations() aceita qualquer função que mapeie uma lista de anotações para uma nova lista, dando a você controle total sobre as transformações por box. strip_annotations() é um wrapper de conveniência que remove todas as anotações de cada box.

Para mudanças em massa, como adicionar InjectNoise a todos os boxes de portas, o parâmetro inject_noise_targets de generate_boxing_pass_manager() é a opção mais concisa. Esses utilitários são úteis para edições seletivas por box que os parâmetros do pass manager não cobrem.

Construir um circuito​

Todo pass manager produzido por generate_boxing_pass_manager() retorna circuitos que podem ser transformados com sucesso em um par template/samplex por build(). Por exemplo, o código a seguir chama a função build() em um circuito produzido por um boxing pass manager.

from samplomatic import build

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)

template, samplex = build(transpiled_circuit)

Para garantir que qualquer circuito transpilado possa ser construído com sucesso, os pass managers sabem incluir boxes adicionais quando necessário. Por exemplo, considere o circuito abaixo, que termina com um qubit 0 não medido.

circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))

circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)

../_images/0452779799470d2deb4d32ce19f5aa28dfe8055a064e67c0e8e5684389845b0f.png

Desenhar boxes com vestimenta à esquerda em torno das portas e das medições resultaria em um circuito com portas virtuais não coletadas no qubit 0, e chamar build() nesse circuito resultaria em um erro. Para evitar isso, os pass managers retornados por generate_boxing_pass_manager() podem adicionar boxes com vestimenta à direita para atuar como coletores. Por exemplo, no código a seguir, o qubit 0 é terminado por um box com vestimenta à direita que recolhe a porta virtual não coletada. As portas de um qubit que atuam no qubit 0 também são colocadas dentro do box para minimizar a profundidade do circuito resultante.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/711ee3c014edf6f70ede5dfa849494286e96870b3bd70a2a89f687d83cc5bfc1.png

Em outro exemplo, um box com vestimenta à direita é adicionado para coletar as portas virtuais que, de outra forma, permaneceriam não coletadas por causa das medições fora de boxes.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/d59d0c0af3c47f474a964a455533b99e09a65364454ec04ced7581e6afac73ab.png