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?

Trabalhar com circuitos bosônicos

Um BosonicCircuit expressa uma computação diretamente em modos bosônicos. Ele não carrega truncamento, codificação nem número de qubits: isso entra depois, durante a transpilação, e é assim que um mesmo circuito pode ser convertido sob várias codificações e vários truncamentos sem ser reconstruído.

Ele expõe deliberadamente uma API reduzida em comparação com o QuantumCircuit do Qiskit, de modo que portas de qubits não possam ser aplicadas a um modo bosônico, o que não seria uma operação bem definida. Este guia cobre as operações que são expostas para montar circuitos maiores a partir de menores.

Por que usar circuitos bosônicos​

Considere o fluxo de trabalho tradicional para colocar a evolução temporal de um Hamiltoniano bosônico em uma arquitetura baseada em qubits:

  1. Defina o Hamiltoniano bosônico, por exemplo H=−∑ijbi†bjH = -\sum_{ij} b^\dagger_i b_j.

  2. Escolha um truncamento e uma codificação, e mapeie o Hamiltoniano para um operador de qubits com eles.

  3. Implemente a evolução temporal no espaço de qubits, por Trotterização ou outro método.

O segundo passo vem cedo e toma duas decisões que são caras de desfazer. O truncamento fixa quantos níveis cada modo mantém, e a codificação fixa quantos qubits isso custa, então ambos ficam fixados no operador de qubits antes que qualquer outra coisa tenha sido feita com o circuito. Para olhar a mesma física sob outro corte ou outra codificação, você mapeia o Hamiltoniano de novo e constrói o circuito de novo. O que o mapeamento descarta, além disso, é a estrutura bosônica: quais termos andam juntos e o fato de que os termos de um par de salto são adjuntos um do outro.

Um BosonicCircuit move essas decisões para o fim:

  1. Defina o Hamiltoniano bosônico.

  2. Expresse a evolução temporal diretamente em modos bosônicos, com portas bosônicas.

  3. Escolha o truncamento e a codificação como parte da transpilação.

O circuito descreve o que computar, e a transpilação decide como colocá-lo em qubits. O mesmo circuito é então convertido em cada corte e sob cada codificação, e o agrupamento do operador continua lá para os passos que podem usá-lo. As próximas seções mostram como esse circuito é montado; Transpilar circuitos bosônicos mostra ele sendo convertido.

Indexação genérica de modos​

O módulo operators e o circuito compartilham uma convenção para modos. Um modo é apenas um índice que rotula um grau de liberdade bosônico, e não carrega significado próprio. Você pode, portanto, evoluir qualquer operador que implemente o protocolo OperatorTrait, seja o que ele representar, e o circuito nunca precisa saber o que o modo 2 significa. O mesmo vale para o tamanho de um modo: um circuito não tem corte de ocupação, então nada aqui diz quantos níveis um modo tem.

Acrescente portas​

As portas são acrescentadas aos modos. Somente instâncias de BosonicGate são aceitas:

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)

Qualquer outra coisa é recusada, e o erro nomeia o tipo ofensor:

>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>

Componha circuitos​

compose() coloca as instruções de um circuito sobre outro. Elas são incorporadas, não embrulhadas em uma única porta, então o resultado é um circuito comum que você pode continuar decompondo e transpilando.

>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A composed BosonicCircuit.

Por padrão, um novo circuito é retornado e os dois operandos permanecem intocados. Passe inplace=True para modificar o primeiro em vez disso, e front=True para colocar o operando antes das instruções existentes:

>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True

Um circuito mais estreito pode ser colocado sobre um subconjunto escolhido dos modos:

>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A narrow-composed BosonicCircuit.

Somente outro BosonicCircuit é aceito como operando, porque o próprio compose do Qiskit incorporaria portas de qubits e contornaria a verificação que append impõe.

Repita um circuito​

repeat() repete um circuito de ponta a ponta. As repetições são achatadas: o resultado contém as instruções reps vezes em sequência, em vez de uma porta que embrulha o circuito. Portanto, as contagens de operações crescem com reps, e decompose atua sobre o corpo repetido em vez de remover uma repetição.

>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit.

Esta é a forma, no nível do circuito, de fazer a Trotterização: evolua por um tempo curto e repita. Veja Sintetizar uma porta Evolution no espaço bosônico para a alternativa de escolher uma fórmula de produto na própria porta, e por que as duas são coisas diferentes que por acaso compartilham a palavra “repetições”.

Separe os passos com barreiras​

Uma barreira marca um ponto de sincronização através do qual os passos de otimização não fundem portas. barrier() coloca uma em todos os modos, ou nos modos que você nomear:

>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A BosonicCircuit with barriers.

repeat(..., insert_barriers=True) coloca uma entre repetições consecutivas, e nenhuma depois da última:

>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A repeated BosonicCircuit with barriers.

Barreiras e a codificação​

Uma codificação precisa de vários qubits por modo, então o registrador de modos e o registrador de qubits têm tamanhos diferentes, e um layout relaciona registradores inteiros. Um modo, portanto, não tem qubits próprios aos quais restringir uma barreira. Durante a síntese de bóson para qubit, uma barreira é mapeada para todos os qubits de cada registrador que ela tocava, o que é correto sob qualquer layout:

>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

A QuantumCircuit showing the expansion of barrier to cover the entire qubit register.

Leia o resultado de um circuito bosônico​

Um BosonicCircuit não tem measure nem measure_all. O que um bit medido significa depende da codificação. Sob uma codificação unária, a ocupação de um modo se espalha por vários qubits e precisa ser lida a partir de um padrão de bits, e sob uma compacta ela é um número binário, então uma medição que nomeasse um modo teria que conhecer a codificação, o que um circuito bosônico deliberadamente não conhece. Meça, em vez disso, o QuantumCircuit transpilado e decodifique as strings de bits com LevelEncoding.level_to_bits() e os deslocamentos do LevelSpec com o qual você fez a conversão. Simular a dinâmica de Bose-Hubbard com a escolha de uma codificação faz isso para um modelo completo.

>>> hasattr(BosonicCircuit, "measure_all")
False

Próximos passos​