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?

Como este pacote se relaciona com o ffsim

Importante

Os conceitos deste guia estão disponíveis apenas na API Python.

O ffsim e este pacote dividem o trabalho em vez de se sobrepor, ao longo de uma única linha:

O ffsim é responsável porO qiskit-fermions é responsável por
Os operadores de ansatz de alto nível (UCJOpSpinBalanced, UCCSDOpRestrictedReal e seus similares), construídos a partir de amplitudes de coupled-cluster ou de um vetor de parâmetrosO circuito fermiônico (FermionicCircuit) em que esses operadores se transformam, e sua transpilação para um circuito de Qubits sob qualquer codificação de férmion para Qubit
Simulação rápida em um espaço compacto de número fixo de partículasO framework de mapeadores e as estruturas de dados de operadores a partir dos quais o circuito é construído

A consequência prática vale nos dois sentidos. A matemática do ansatz vive no ffsim, então UCJ e UCC recebem um operador do ffsim diretamente como entrada. Por outro lado, as portas Qiskit próprias do ffsim são específicas da transformação de Jordan-Wigner, então reduzir um de seus operadores de ansatz por meio de uma codificação diferente é o que trazê-lo para cá oferece a você.

A simulação é onde os dois se encontram, e este guia explica como. O ffsim é o backend de simulação deste pacote, acessado por meio de seus próprios protocolos em vez de um wrapper: o guia do SKQD chama ffsim.apply_unitary(), ffsim.linear_operator() e ffsim.sample_state_vector() diretamente sobre objetos FermionicCircuite objetos FermionOperator, sem etapa de conversão.

Conectando-se à interface de simulação do ffsim​

O ffsim é um simulador de alto desempenho para circuitos quânticos fermiônicos que explora a conservação do número de partículas e do spin-Z para representar vetores de estado de forma mais compacta que um statevector genérico de Qubits de dimensão 2n2^n. Ele define protocolos que qualquer objeto pode implementar para participar de sua maquinaria de simulação (veja qiskit_fermions.protocols para os protocolos próprios deste pacote, que seguem o mesmo design):

Este pacote implementa essa interface: toda porta fermiônica em qiskit_fermions.circuit.library fornece _apply_unitary_, e FermionOperator fornece _linear_operator_ e _trace_. Isso é o que torna os operadores e circuitos deste pacote compatíveis com o ffsim, de modo que suas ferramentas funcionam neles nativamente, sem etapa de conversão. ffsim.apply_unitary() pode simular um FermionicCircuit de ponta a ponta, ffsim.linear_operator() pode diagonalizar um FermionOperator, e utilitários de amostragem como ffsim.sample_state_vector() (usado no guia do SKQD para transformar um statevector simulado em contagens de medição) funcionam sem modificação.

O que _linear_operator_ retorna é um LinearOperator comum do SciPy, então scipy.sparse.linalg.eigsh() e companhia também funcionam nele.

>>> import ffsim
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> norb, nelec = 2, (1, 1)
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(1)], 0.5),
... ([cre(1), ann(0)], 0.5),
... ])
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(Evolution(2 * norb, hamiltonian, time=1.0), circuit.modes)
>>>
>>> reference = ffsim.hartree_fock_state(norb, nelec)
>>> state = ffsim.apply_unitary(reference, circuit, norb=norb, nelec=nelec) # native ffsim call

Acoplar-se por meio dos protocolos do ffsim é o que proporciona isso: seu ecossistema de utilitários de simulação e amostragem, em desenvolvimento ativo, se aplica sem alterações aos circuitos e operadores deste pacote, e usuários que já trabalham com o ffsim podem misturar as portas e operadores deste pacote sem aprender outra API de simulação.

O ffsim é uma dependência opcional​

O ffsim é declarado como um extra opcional (pip install "qiskit-fermions[ffsim]", ou transitivamente por [all]), protegido em tempo de execução por HAS_FFSIM (como mostrado acima). Instalá-lo desbloqueia a simulação: ffsim.SupportsLinearOperator e ffsim.SupportsTrace são implementados convertendo o FermionOperator deste pacote em um ffsim.FermionOperator, então sem o ffsim eles levantam MissingOptionalLibraryError. Tudo o que não simula (construir operadores, mapeá-los e transpilar os circuitos resultantes) não precisa de nada disso.

A simulação fermiônica vive em um setor de número fixo de partículas​

Tanto _apply_unitary_ quanto _linear_operator_ recebem um argumento nelec e representam o vetor de estado sobre a base de determinantes de número fixo de partículas para esse setor (norb, nelec), espelhando a configuração do espaço FCI do ffsim (e, transitivamente, do PySCF), em vez do espaço de dimensão 2num_modes2^{\text{num\_modes}} completo que um simulador de Qubits geral usaria. Este é um espaço muito menor (seu tamanho é um produto de coeficientes binomiais em vez de uma potência de dois), mas vem com uma restrição rígida. Apenas operadores e portas que preservam o número de partículas (e, no caso com spin, o número de partículas de cada espécie de spin individualmente) podem ser representados nele. Um operador cuja ação moveria amplitude para um número de partículas diferente não tem onde colocá-la.

O ffsim resolve isso rejeitando tal operador de imediato: convertê-lo em um operador linear levanta um ValueError que nomeia qual lei de conservação falha. Esse é o padrão correto, porque aplicar exp⁡(−itH)\exp(-i t H) para um Hamiltoniano HH com um termo que não conserva partículas transformaria, de outra forma, um suposto unitário em um mapa não unitário e sem significado físico.

  • Evolution repassa essa rejeição a partir do operador que ele evolui.

  • OrbitalRotation verifica se sua matriz de rotação (embutida) é diagonal em blocos na divisão alfa/beta no caso com spin, e lança um ValueError se ela misturar setores de spin.

>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> non_conserving = FermionOperator.from_terms([([cre(0), cre(1)], 1.0)]) # creates 2 particles
>>> gate = Evolution(2 * norb, non_conserving, time=1.0)
>>>
>>> try:
... gate._apply_unitary_(reference, norb, nelec, copy=True)
... except ValueError as exc:
... print("rejected:", exc)
rejected: The given FermionOperator could not be converted to a LinearOperator because it does not conserve particle number and the z component of spin. Conserves particle number: False Conserves spin z: False

Portanto, a simulação que não preserva o número de partículas está fora de alcance no espaço fermiônico, pela própria construção da representação de setor fixo, e esse é o preço do espaço FCI compacto que torna a simulação fermiônica tratável logo de início. Se o seu algoritmo precisar de operadores que violam o número de partículas (por exemplo, um canal de erro nativo de qubits, ou um operador construído para um Hamiltoniano mapeado que não conserva o número de partículas termo a termo), transpile primeiro para qubits e simule o QuantumCircuit resultante com um simulador em nível de qubits. Qualquer rota de transpilação serve para isso; os guias de circuito fermiônico e de transpilação entram em mais detalhes, mas generate_preset_jw_pass_manager() é uma escolha padrão razoável.

A convenção de spin em blocos para sistemas com spin​

nelec tem o tipo int | tuple[int, int], e é o seu tipo, não um sinalizador separado, que seleciona um dos layouts de modos suportados:

  • Um int seleciona a interpretação sem spin. Os norb modos são tratados diretamente como orbitais sem spin, e nelec é o número total de partículas.

  • Um par (n_alpha, n_beta) seleciona a interpretação com spin de 2 * norb modos sob uma convenção de spin em blocos fixa. Os modos 0 .. norb são os orbitais alfa (spin para cima) e os modos norb .. 2 * norb são os orbitais beta (spin para baixo). Cada espécie de spin é conservada independentemente; um termo somente alfa pode mover um elétron entre modos alfa, mas nunca para um modo beta, e vice-versa.

Esse despacho acontece em todo ponto de entrada do protocolo ffsim neste pacote (construtores de portas, implementações de _apply_unitary_placed_ e a compilação de setores do kernel nativo em Rust), e é a convenção usada em todos os guias LUCJ e SKQD (por exemplo, InitializeModes.from_hartree_fock() preenche as ocupações alfa nos modos 0 .. n_alpha e as ocupações beta nos modos norb .. norb + n_beta). Vale contrastar isso com o módulo qiskit_fermions.operators e com o FermionicCircuit em geral, que usam índices de modos genéricos sem semântica de spin inerente (veja o guia de circuito fermiônico). O significado de spin em blocos só é imposto quando um nelec com spin é fornecido a uma chamada de simulação, e não está embutido na própria representação do operador ou do circuito.

>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> norb, nelec = 3, (2, 1)
>>> init = InitializeModes.from_hartree_fock(norb, nelec)
>>> print([bool(occ) for occ in init.occupation]) # alpha modes 0,1; beta mode 3 (= norb + 0)
[True, True, False, True, False, False]

Próximos passos​