Trabalhe com circuitos fermiônicos
Os conceitos deste guia estão disponíveis atualmente apenas na API Python. Uma funcionalidade equivalente será disponibilizada na API C em uma versão futura.
Este guia explica como usar o FermionicCircuit para implementar algoritmos quânticos diretamente no espaço fermiônico, em vez de mapear para Qubits primeiro.
Por que usar circuitos fermiônicos
Considere o fluxo de trabalho tradicional para implementar a evolução temporal de um Hamiltoniano fermiônico em uma arquitetura baseada em Qubits:
-
Defina o Hamiltoniano fermiônico (por exemplo, ).
-
Mapeie-o para um operador de Qubits (por exemplo, usando o mapeamento de Jordan-Wigner).
-
Implemente a evolução temporal no espaço de Qubits (usando Trotterização ou outros métodos).
Essa abordagem tem uma limitação fundamental: o mapeamento precoce para Qubits descarta o conhecimento do problema sobre a estrutura fermiônica. As passagens de otimização padrão não conseguem explorar simetrias fermiônicas nem reordenações permitidas de termos, resultando em circuitos subótimos, com profundidade e contagem de portas desnecessárias.
O FermionicCircuit possibilita um fluxo de trabalho melhor:
-
Defina o Hamiltoniano fermiônico (por exemplo, ).
-
Implemente a evolução temporal diretamente no espaço fermiônico usando portas fermiônicas.
-
Mapeie para o espaço de Qubits como parte do processo de transpilação.
Essa abordagem resolve a limitação ao manter o conhecimento do problema no espaço fermiônico, onde as passagens de otimização podem explorar a estrutura fermiônica e as relações de comutação para reduzir a profundidade do circuito. O circuito fermiônico descreve qual computação executar, enquanto a transpilação cuida de como mapeá-la para Qubits, oferecendo uma separação de responsabilidades mais limpa.
Indexação genérica de modos
Tanto o módulo operators quanto o FermionicCircuit usam indexação genérica baseada em modos, que não faz suposições sobre a natureza dos modos. Um modo é simplesmente um índice abstrato que rotula um grau de liberdade fermiônico, sem semântica inerente. Essa escolha de design espelha o módulo operators e garante máxima flexibilidade. Por exemplo, você pode implementar a evolução temporal de qualquer operador que implemente o protocolo OperatorTrait, independentemente de sua representação matemática.
Construa um circuito fermiônico
O exemplo abaixo implementa a evolução temporal descrita na seção anterior. Ele constrói um FermionicCircuit especificando o número de modos fermiônicos e depois adiciona portas fermiônicas de qiskit_fermions.circuit.library para implementar a evolução temporal.
O exemplo também demonstra como incorporar conhecimento do domínio em um operador usando o agrupamento de termos do operador. Ao atribuir índices de grupo aos termos do Hamiltoniano, você preserva informações estruturais que as passagens de otimização podem explorar em toda a pilha de transpilação.
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Create a circuit with 4 fermionic modes
>>> circuit = FermionicCircuit(4)
>>>
>>> # Define a simple fermionic Hamiltonian
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(2)], 0.5),
... ([cre(2), ann(0)], 0.5),
... ([cre(1), ann(3)], 0.5),
... ([cre(3), ann(1)], 0.5),
... ])
>>> # Add some problem structure by grouping our Hamiltonian terms
>>> hamiltonian.groups = [0, 0, 1, 1]
>>>
>>> # Add an evolution gate to implement exp(-i * t * H)
>>> evolution = Evolution(4, hamiltonian, time=1.0)
>>> circuit.append(evolution, circuit.register)
Observe como o agrupamento de termos do operador é preservado mesmo em operações simples como a decomposição. Isso demonstra como a informação estrutural flui pela pilha de circuitos. Para entender a transpilação completa para Qubits, consulte o guia Transpilar circuitos fermiônicos.
Mantenha as portas separadas com uma barreira
As passagens de otimização fundem portas vizinhas sempre que possível. Por exemplo, MergeOrbitalRotations combina uma sequência de portas OrbitalRotation consecutivas em uma só, e MergeSlaterDeterminantPreparation incorpora um InitializeModes na rotação que vem depois dele.
Use FermionicCircuit.barrier() quando quiser impedir isso. Uma barreira não tem efeito unitário, portanto não altera o estado que seu circuito prepara; ela apenas marca um ponto através do qual as passagens não fundem portas. Chame-a sem argumentos para abranger todos os modos, ou passe os modos aos quais restringi-la:
>>> from qiskit_fermions.circuit.library import OrbitalRotation
>>> import numpy as np
>>>
>>> rotations = FermionicCircuit(4)
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
>>> rotations.barrier()
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
As barreiras sobrevivem à transpilação de férmion para Qubit, então também restringem a otimização em nível de Qubits que vem em seguida. O guia Transpilar circuitos fermiônicos cobre essa etapa.
Repita um circuito
Muitos algoritmos aplicam o mesmo bloco de portas muitas vezes. A evolução temporal é o caso comum: um passo de Trotter é construído uma vez e depois repetido até atingir o tempo desejado. Use repeat() para construir o circuito repetido e compose() para juntá-lo a um prefixo de preparação de estado.
O exemplo abaixo constrói um passo de Trotter de segunda ordem para um modelo de Fermi-Hubbard. O passo é simétrico: ele divide o termo de salto em duas portas OrbitalRotation de meia duração que cercam a interação local.
>>> import scipy.linalg
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>>
>>> norb, reps, time = 2, 3, 1.0
>>> num_modes = 2 * norb
>>> dt = time / reps
>>>
>>> hopping = np.zeros((norb, norb))
>>> hopping[0, 1] = hopping[1, 0] = -1.0
>>> interaction = FermionOperator.from_dict(
... {(cre(p), ann(p), cre(p + norb), ann(p + norb)): 2.0 for p in range(norb)}
... )
>>>
>>> def rotation(duration):
... propagator = scipy.linalg.expm(-1j * duration * hopping)
... # the same rotation acts on the alpha and beta halves of the register
... return scipy.linalg.block_diag(propagator, propagator)
>>>
>>> trotter_step = FermionicCircuit(num_modes)
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
>>> trotter_step.append(
... Evolution(num_modes, interaction, dt, atomic=True), trotter_step.modes
... )
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
Repetir o passo e compô-lo sobre uma referência de Hartree-Fock dá a evolução completa:
>>> evolution = FermionicCircuit(num_modes)
>>> evolution.append(InitializeModes([True, False, True, False]), evolution.modes)
>>> evolution.compose(trotter_step.repeat(reps), inplace=True)
>>> evolution.count_ops()["OrbitalRotation"]
6
As repetições são achatadas no circuito retornado, em vez de embrulhadas em uma única porta, então count_ops() conta duas rotações por passo. Esse achatamento é o que torna visível ao transpilador a redundância nas fronteiras dos passos: a rotação final de meia duração de um passo e a inicial do próximo são adjacentes, então MergeOrbitalRotations as combina em uma única rotação.
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4
O resultado custa uma rotação por passo mais uma, que é o que você obtém ao fundir esses meios passos manualmente. Escrever o passo em sua forma simétrica natural e repeti-lo produz o mesmo circuito, então a fusão fica a cargo da etapa de otimização.
Quando precisar manter as repetições separadas, passe insert_barriers=True para colocar uma barrier() entre elas. As passagens de otimização não fundem portas através de uma barreira, então toda rotação sobrevive:
>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6
O argumento reps de FermionicSuzukiTrotter é um conceito diferente. Ele subdivide uma única porta Evolution em fatores mais numerosos e mais curtos, enquanto repeat() duplica circuitos inteiros. Repetir um circuito que contém uma única Evolution apenas multiplica seu tempo total de evolução.
Meça modos fermiônicos
measure_all() lê cada modo em um bit clássico próprio, associando o modo i ao bit i. Use measure() com seu próprio ClassicalRegister para ler apenas alguns deles.
>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4
Passar inplace=False retorna uma cópia com medição e deixa o original intacto, o que importa porque uma medição não é unitária: um circuito que contém uma não pode mais ser aplicado a um vetor de estado, então mantenha um circuito sem medição à mão se também quiser simulá-lo.
Medir aqui, em vez de no QuantumCircuit transpilado, é o que mantém as bitstrings amostradas fáceis de ler. Uma medição fermiônica nomeia o modo que ela lê, então toda etapa que move modos de lugar (RelabelModes no nível fermiônico, layout e roteamento no nível de Qubits) leva a medição junto, e a bitstring continua indexada pelo modo sobre o qual você perguntou.
Ao contrário de measure_all(), nenhuma barrier() é inserida antes das medições. Chame barrier() você mesmo antes, se quiser uma ali.
Se um bit medido significa o que você espera depende da codificação de férmion para Qubit, que é escolhida durante a transpilação, e não aqui. Sob uma codificação na base de ocupação, como Jordan-Wigner, cada modo é carregado por um Qubit e o bit é a ocupação daquele modo; veja FermionicMeasure.
Transpile circuitos fermiônicos
Para implementar o algoritmo quântico representado pelo seu FermionicCircuit, ele precisa ser transpilado para um QuantumCircuit. Você pode aprender como fazer isso no guia Transpilar circuitos fermiônicos.
Próximos passos
-
Conheça as portas fermiônicas disponíveis na documentação de
qiskit_fermions.circuit.library. -
Explore o guia de explicação dos operadores para entender como construir Hamiltonianos fermiônicos que você pode usar com circuitos fermiônicos.
-
Consulte o guia de introdução ao SQDRIFT para um exemplo prático de ponta a ponta.
-
Leia o guia da relação com o ffsim para entender como um
FermionicCircuitpode ser simulado diretamente e como esses índices de modo genéricos assumem semântica de spin apenas no momento da simulação.

