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:
-
Defina o Hamiltoniano bosônico, por exemplo .
-
Escolha um truncamento e uma codificação, e mapeie o Hamiltoniano para um operador de qubits com eles.
-
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:
-
Defina o Hamiltoniano bosônico.
-
Expresse a evolução temporal diretamente em modos bosônicos, com portas bosônicas.
-
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>
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>
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>
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>
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>
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>
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
-
Transpilar circuitos bosônicos explica como um circuito bosônico se torna um circuito de qubits, etapa por etapa.
-
Rotacionar modos bosônicos aborda a porta que mistura modos e conserva o número de bósons.
-
Sintetizar uma porta Evolution no espaço bosônico mostra como aplicar a Trotterização a uma evolução no espaço bosônico.
-
Princípios de design das representações de operadores explica os operadores que as portas carregam.