Skip to main content
IBM Quantum Platform

Gerar circuitos d SqDRIFT

Com a diagonalização quântica baseada em amostragem ( SQD ), é necessário escolher um ansatz a partir do qual se irão amostrar cadeias de bits. A variante “ SqDRIFT ” utiliza, em vez disso, um conjunto de circuitos de evolução temporal construídos diretamente a partir do hamiltoniano-alvo. Isso é obtido por meio da subamostragem de operadores de evolução temporal menores a partir do hamiltoniano, com base em seus coeficientes, o que é conhecido como método de trotterização de qDRIFT.

Este guia de introdução mostra como gerar um conjunto desses circuitos aleatórios.


1. Configuração do hamiltoniano

Para os fins deste guia, carregue o hamiltoniano da estrutura eletrônica de um N2 e a partir de um arquivo FCIDUMP. Existem outras formas de construir o FermionOperator. Não deixe de consultar a documentação do programa, bem como o qiskit_fermions.operators.library.

[x] PYTHON

>>> from qiskit_fermions.operators.library import FCIDump
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> fcidump = FCIDump.from_file("docs/guides/n2.fcidump")
>>> num_modes = 2 * fcidump.norb
>>> hamil = FermionOperator.from_fcidump(fcidump)

[] C

#include <qiskit_fermions.h>

QfFCIDump* fcidump = qf_fcidump_from_file("docs/guides/n2.fcidump");
QfFermionOperator* hamil = qf_ferm_op_from_fcidump(fcidump);
uint32_t num_modes = 2 * qf_fcidump_norb(fcidump);

2. Termos do hamiltoniano de grupo

Utilize as diversas simetrias presentes no hamiltoniano da estrutura eletrônica, agrupando termos relacionados que tenham coeficientes idênticos. Essa ação altera a distribuição dos coeficientes dos operadores a partir da qual o protocolo de qDRIFT ação realiza a amostragem, mas não afeta suas garantias de convergência. Fundamentalmente, o agrupamento de termos relacionados por simetria leva a uma compensação favorável dos termos de Pauli, resultando em uma profundidade de circuito globalmente menor ao realizar a evolução temporal de um estado sob a ação desses termos.

O qiskit_fermions.operators.terms.grouping módulo oferece funções úteis para agrupar os termos de um operador. Isso é explicado com mais detalhes neste guia.

Cuidado

A implementação do pressupõe group_terms_by_electronic_structure() que os termos do hamiltoniano estejam ordenados normalmente!

[x] PYTHON

>>> from qiskit_fermions.operators.terms.grouping import group_terms_by_electronic_structure
>>> from qiskit_fermions.operators.terms.ordering import canonical_order
>>>
>>> canon = canonical_order(hamil.normal_ordered().simplify(atol=1e-16))
>>> exit_code = group_terms_by_electronic_structure(canon, num_modes, two_body_physicist_order=False)
>>> assert exit_code is None
>>> print(canon.groups)  # the groups attribute now contains some list of group indices
[0, ...]

[] C

QfFermionOperator* normal;
QfExitCode exit = qf_ferm_op_group_terms_by_electronic_structure(normal, num_modes, false);
QfFermionOperator* canon = qf_ferm_op_canonical_order(normal);
Sugestão

O hamiltoniano da estrutura eletrônica completa contém certos termos cuja inclusão em um circuito de evolução temporal não tem impacto sobre as sequências de bits percebidas e, portanto, resulta apenas em um aumento na sobrecarga de amostragem. Portanto, recomenda-se que esses termos sejam filtrados do hamiltoniano neste momento, antes de se construir o portão Evolution na etapa seguinte.

Os termos que se enquadram nessa descrição são aqueles que são diagonais na base de ocupação-número, ou seja, os produtos de operadores numéricos ( aiaia^\dagger_i a_i ). Isso inclui o deslocamento de energia constante, cuja evolução temporal introduz apenas uma fase global no circuito, os operadores numéricos individuais cuja evolução temporal equivale a rotações Z de um único qubit, bem como produtos de ordem superior, como ninjn_i n_j. Nenhum desses termos afeta as sequências de bits amostradas.

A função filter_diagonal_terms() remove esses termos de um operador diretamente:

[x] PYTHON

>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)

[] C

qf_ferm_op_filter_diagonal_terms(canon);

Filtrar aqui, uma única vez, é consideravelmente mais barato do que filtrar repetidamente. QDriftTrotterization é executado uma vez por circuito transpileado; portanto, filtrar o hamiltoniano inicialmente, em vez de fazê-lo a cada chamada, evita repetir o trabalho para cada circuito gerado a partir dele.


3. Preparar o circuito de evolução temporal

Prepare o circuito de evolução temporal do hamiltoniano e o circuito base a partir do qual serão extraídas as amostras. O contém qiskit_fermions.circuit.library todos os componentes necessários para isso, em conformidade com as convenções do Qiskit.

[x] PYTHON

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> time = 1.0  # you can choose a desired scaling factor here
>>> evo_gate = Evolution(num_modes, canon, time)
>>>
>>> circ = FermionicCircuit(num_modes)
>>> circ.append(evo_gate, circ.modes)

[] C

// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions.
Nota

Este exemplo não inicializa os modos fermiónicos com partículas, nem mede seu estado final.


4. Transpilar o circuito com a trotterização do QDrift

O módulo qiskit_fermions.transpiler se integra diretamente ao pipeline de transpilagem do Qiskit, permitindo que o código construído FermionicCircuit acima seja transpilado diretamente para um QuantumCircuit.

Utilize o mapeamento de jordan_wigner() férmions para qubits para converter o hamiltoniano expresso em termos de férmions para que seja expresso, em vez disso, em cadeias de Pauli. Isso pode ser feito diretamente como parte do processo de transpilação, utilizando o plugin de etapa de transpilação EvolutionSynthesis . Use generate_preset_jw_pass_manager() para compilar FermionicStagedPassManager, o que garante que a codificação de Jordan-Wigner seja utilizada de forma consistente em todas as instruções do circuito.

É fundamental adicionar a etapa de transpilação QDriftTrotterization ao optimization estágio do pipeline de transpilação. Isso garante que o circuito não utilize a evolução temporal do hamiltoniano completo, cuja complexidade excederia as capacidades do hardware de computação quântica disponível atualmente.

Em vez disso, ele realiza uma subamostragem de um número fixo de groups termos hamiltonianos para cada circuito, sempre que o circuito é transpilado. Dessa forma, é possível gerar múltiplas randomizações de circuitos, conforme exigido pelo algoritmo de SqDRIFT, executando repetidamente o pipeline de transpilação.

Esta etapa também apresenta os poucos parâmetros que você pode usar para personalizar os circuitos a serem gerados:

  • O número de circuitos a serem gerados: num_sqdrift_randomizations
  • O comprimento de cada circuito em termos de grupos de excitação: num_groups

[x] PYTHON

>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>> from qiskit_fermions.transpiler.passes import QDriftTrotterization
>>>
>>> num_groups = 10
>>> qdrift = QDriftTrotterization(num_groups, rng=19)
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> pm.optimization = FermionicPassManager([qdrift])
>>>
>>> num_sqdrift_randomizations = 10
>>> sqdrift_circuits = [
...     pm.run(circ) for _ in range(num_sqdrift_randomizations)
... ]

[] C

// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
Nota

O exemplo anterior define o valor para seed o gerador de números aleatórios utilizado na etapa de transpilação QDriftTrotterization .


5. Filtrar as excitações triviais

Além dos termos diagonais filtrados na etapa anterior, uma excitação amostrada ainda pode não afetar as cadeias de bits amostradas. Sempre que atua inteiramente dentro de um conjunto de modos cuja ocupação já está fixada (todos ocupados ou todos desocupados), ela não pode transferir uma partícula de um modo para outro; portanto, deixa o estado (e, consequentemente, o resultado final da medição) inalterado. A configuração filter_trivial=True na QDriftTrotterization passagem rejeita tais termos à medida que são selecionados e sorteia um substituto, de modo que cada um dos num_groups slots do circuito resultante contribua com uma excitação não trivial.

Essa filtragem precisa saber quais modos já estão ocupados no início. Portanto, é necessário uma porta InitializeModes que preceda as portas Evolution no circuito; essa é InitializeModes.from_hartree_fock() uma maneira prática de construí-la. Adicione aqui uma simulação para a referência Hartree-Fock do modelo “ N2 ” (sete elétrons alfa e sete elétrons beta em 14 orbitais espaciais) e compare as excitações amostradas com e sem filter_trivial=True:

[x] PYTHON

>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> init = InitializeModes.from_hartree_fock(fcidump.norb, (7, 7))
>>>
>>> hf_circ = FermionicCircuit(num_modes)
>>> hf_circ.append(init, hf_circ.modes)
>>> hf_circ.append(Evolution(num_modes, canon, time), hf_circ.modes)
>>>
>>> num_groups = 5
>>> qdrift_unfiltered = QDriftTrotterization(num_groups, rng=3480)
>>> qdrift_trivial = QDriftTrotterization(
...     num_groups, filter_trivial=True, rng=3480
... )
>>>
>>> for instruction in FermionicPassManager(qdrift_unfiltered).run(hf_circ)._inner.data:
...     if instruction.operation.name == "Evolution":
...         print(sorted(instruction.operation.operator.get_support()))
[2, 4]
[41, 45, 52]
[15, 45, 55]
[41, 52, 53]
[10, 16, 37, 38]
>>>
>>> for instruction in FermionicPassManager(qdrift_trivial).run(hf_circ)._inner.data:
...     if instruction.operation.name == "Evolution":
...         print(sorted(instruction.operation.operator.get_support()))
[0, 1, 6, 7]
[0, 1, 28, 29]
[4, 13, 55]
[13, 20, 40, 41]
[0, 1, 28, 29]

[] C

// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.

Nenhuma das excitações amostradas sem filtragem atinge de forma alguma o conjunto ocupado (0-6 e 28-34), portanto, nenhuma delas pode deslocar uma partícula entre um modo ocupado e um desocupado; todas são triviais e não teriam efeito sobre as sequências de bits amostradas. Com filter_trivial=True, todas as cinco são rejeitadas e substituídas por excitações que, de fato, acoplam um modo ocupado a um modo desocupado. Por exemplo, a primeira excitação aceita move [0, 1, 6, 7] uma partícula entre os modos ocupados 0, 1, e 6 e o modo desocupado 7.

Uma vez que uma excitação é aceita, cada modo em seu suporte torna-se “incerto” e, portanto, passível de desempenhar qualquer um dos papéis em amostras posteriores; assim, os conjuntos de modos ocupados e desocupados continuam crescendo à medida que mais excitações são aceitas. É isso que torna a segunda excitação, [0, 1, 28, 29], aceitável. Todos os seus quatro modos estão entre os originalmente ocupados; portanto, ele não se acopla a nenhum modo originalmente desocupado. Isso só é aceito porque os modos e 0 1 tornaram-se incertos (e, portanto, passaram a ser considerados como o lado “desocupado” do acoplamento) assim que a primeira excitação os atingiu.

Nota

Sem um InitializeModes gate anterior, não filter_trivial=True há informações de ocupação para servir de filtro. Ele emite um UserWarning e deixa a amostragem sem filtragem para esse Evolution gate.


(Opcional) Otimizar a indexação do modo fermiónico

É possível adicionar uma etapa de otimização adicional ao pipeline de transpilação que minimize a distância entre os intervalos de excitação fermiónica, reclassificando os índices dos modos fermiónicos. Essa otimização foi apresentada no artigo “ SqDRIFT ” e é implementada por build_excitation_span_minimization_model(). Ele pode ser facilmente inserido no pipeline do transpiler usando a opção pass RelabelModes:

[x] PYTHON

>>> from pyomo.environ import SolverFactory
>>> from qiskit_fermions.transpiler.passes import RelabelModes
>>>
>>> solver = SolverFactory("appsi_highs")
>>> solver.options["time_limit"] = 10
>>>
>>> qdrift = QDriftTrotterization(5, rng=19)
>>> relabel = RelabelModes(solver=solver)
>>>
>>> pm.optimization = FermionicPassManager([qdrift, relabel])
>>>
>>> relabeled_circ = pm.run(circ)
>>> # if the automatic mode relabeling was successful, the circuit's
>>> # metadata will contain the mode `permutation` information

[] C

// WARNING: This feature is not available via the C API.
Nota

O uso da otimização automática interna ( RelabelModes que utiliza build_excitation_span_minimization_model()) requer a dependência opcional gerenciada por HAS_PYOMO.

Importante

Para realizar a diagonalização correta do subespaço, as sequências de bits amostradas a partir dos circuitos que foram transpilados com a etapa de otimização RelabelModes devem ser submetidas a um pós-processamento com base nas informações permutation contidas nos metadados dos circuitos!


Próximas etapas

Agora que você gerou com sucesso um conjunto de circuitos, pode amostrar sequências de bits a partir deles. Para isso, os circuitos devem ser executados em hardware. Consulte a documentação do Qiskit para obter instruções detalhadas.

Depois que as amostras de sequência de bits forem obtidas, elas poderão ser utilizadas em combinação com os coeficientes hamiltonianos para realizar o pós-processamento SQD, conforme explicado nos tutoriais do complemento SQD.

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.