Backend baseado na camada Quimb
qiskit_addon_mpf.backends.quimb_layers
Um backend de evolução de tempo em camadas usando quimb.
Esse backend só estará disponível se as dependências opcionais tiverem sido instaladas:
pip install "qiskit-addon-mpf[quimb]"LayerwiseEvolver | Um caso especial do TEBDEvolver baseado em modelos de evolução em camadas. |
LayerModel | Um modelo para representar uma camada de interações de evolução temporal. |
Método subjacente
Esse módulo fornece um backend de evolução temporal semelhante ao baseado em TEBD fornecido pelo módulo quimb_tebd módulo. A principal diferença é que esse módulo oferece ao usuário total flexibilidade para definir suas fórmulas de produto, não o limitando às opções incorporadas na quimb biblioteca.
Em sua essência, o algoritmo fornecido por esse módulo ainda é um algoritmo TEBD [1]. No entanto, em vez de impor as atualizações alternadas para os vínculos pares e ímpares do estado de evolução temporal (consulte também quimb_tebd.TEBDEvolver.sweep()), essa implementação terceiriza a responsabilidade de atualizar os vínculos de forma alternada para a definição de várias camadas de evolução temporal.
É melhor explicar isso com um exemplo. Vamos supor que temos um Hamiltoniano genérico atuando em uma cadeia unidimensional de locais.
Abaixo, somos muito cuidadosos com a ordem dos termos Pauli do Hamiltoniano, pois isso afeta diretamente a estrutura do circuito de evolução temporal mais tarde.
>>> from qiskit.quantum_info import SparsePauliOp
>>> hamil = SparsePauliOp.from_sparse_list(
... [("ZZ", (i, i+1), 1.0) for i in range(1, 9, 2)] +
... [("Z", (i,), 0.5) for i in range(10)] +
... [("ZZ", (i, i+1), 1.0) for i in range(0, 9, 2)],
... num_qubits=10,
... )Vamos agora inspecionar o circuito de evolução temporal desse Hamiltoniano usando uma fórmula de Suzuki-Trotter de segunda ordem.
>>> from qiskit.synthesis import SuzukiTrotter
>>> from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
>>> circuit = generate_time_evolution_circuit(hamil, time=1.0, synthesis=SuzukiTrotter(order=2))
>>> circuit.draw("mpl")
<Figure size 956...x869... with 1 Axes>
No circuito acima, podemos identificar claramente sua estrutura em camadas. Podemos enfatizar isso ainda mais, dividindo o circuito em várias camadas, conforme mostrado abaixo (recombinamos o layers em um único circuito com barreiras entre eles para facilitar a visualização).
>>> from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
>>> layers = slice_by_gate_types(circuit)
>>> combine_slices(layers, include_barriers=True).draw("mpl")
<Figure size 1374...x869... with 1 Axes>
A assimetria das camadas centrais é resultado da implementação da fórmula de Qiskit SuzukiTrotter fórmula de Qiskit. Em sua forma de segunda ordem, ele combina as duas evoluções de meio período do termo final no Hamiltoniano em uma única evolução de duas vezes o comprimento. Poderíamos transpilar esse circuito para recolher todas essas portas subequentes nas duas camadas centrais (como na última), mas, para simplificar o exemplo, não faremos isso aqui.
Não é possível instruir o algoritmo TEBD do Quimb a simular a estrutura exata do circuito mostrado acima. O motivo disso é uma limitação em sua interface, pois ele só aceita o Hamiltoniano completo a ser fornecido, que é então evoluído no tempo usando fórmulas de Trotter predefinidas. No entanto, ao fazer isso, ele não trata a ordem dos termos de Pauli em um Hamiltoniano com qualquer importância (como fazemos aqui).
Se alguém quiser calcular os coeficientes MPF dinâmicos de uma evolução temporal empregando uma estrutura de fórmula de produto diferente das implementadas no Quimb (como o exemplo acima), poderá usar o algoritmo de evolução temporal fornecido por esse módulo. Em vez de usar um único Hamiltoniano monolítico cuja evolução temporal deve ser modelada, o LayerwiseEvolver aceita uma lista de objetos LayerModel cada um descrevendo uma camada individual da fórmula do produto. Isso proporciona ao usuário total flexibilidade na definição da decomposição de Trotter até o nível mais granular.
No entanto, é preciso ter cuidado para garantir que a propriedade do TEBD de atualizar títulos pares e ímpares de forma alternada ainda seja garantida. Felizmente, para circuitos quânticos que consistem em, no máximo, portas de dois qubits, essa propriedade é satisfeita por construção.
Exemplo de código
Nesta seção, vamos nos basear no exemplo acima e mostrar como pegar uma fórmula Trotter personalizada e usá-la para construir uma fórmula LayerwiseEvolver que pode ser usada para substituir o quimb_tebd.TEBDEvolver no fluxo de trabalho descrito em quimb_tebd.
O fluxo de trabalho geral do uso desse módulo é o mesmo do módulo quimb_tebd portanto, não deixe de ler essas instruções também.
Em termos simples, devemos converter cada um dos circuitos layers (veja acima) em uma LayerModel instância. Para isso, podemos usar seu método from_quantum_circuit() método.
>>> from qiskit_addon_mpf.backends.quimb_layers import LayerModel
>>> model0 = LayerModel.from_quantum_circuit(layers[0])
>>> layer_models = [model0]No código acima, você pode ver como a conversão é simples para camadas que contêm apenas portas de dois qubits atuando em qubits mutuamente exclusivos (que as camadas de profundidade 1 garantem).
No entanto, devemos ter mais cuidado com as camadas que incluem portas de um único qubit. A razão para isso é que o algoritmo TEBD subjacente ao LayerwiseEvolver deve atualizar os vínculos pares e ímpares de forma alternada. E como as portas de um único qubit não são aplicadas em um local, mas, em vez disso, são divididas ao meio e aplicadas às ligações em ambos os lados, uma camada de portas de um único qubit atuando em todos os qubits quebraria essa suposição.
Para contornar esse problema, podemos pegar qualquer camada que consista apenas em portas de um único qubit e aplicar duas vezes (uma nas ligações pares e outra nas ligações ímpares).
>>> model1a = LayerModel.from_quantum_circuit(layers[1], keep_only_odd=True)
>>> model1b = LayerModel.from_quantum_circuit(layers[1], keep_only_odd=False)
>>> layer_models.extend([model1a, model1b])Agora que sabemos como tratar as camadas que consistem em portas de dois qubits e de um único qubit, podemos transformar as camadas restantes.
>>> for layer in layers[2:]:
... num_qubits = len(layer.data[0].qubits)
... if num_qubits == 2:
... layer_models.append(LayerModel.from_quantum_circuit(layer))
... else:
... layer_models.append(
... LayerModel.from_quantum_circuit(layer, keep_only_odd=True)
... )
... layer_models.append(
... LayerModel.from_quantum_circuit(layer, keep_only_odd=False)
... )
>>> assert len(layer_models) == 8No final, temos 8 LayerModelum para cada uma das 4 camadas de dois qubits e dois para cada uma das 2 camadas de um único qubit.
Por fim, podemos definir nosso ApproxEvolverFactory para ser usado na função setup_dynamic_lse() função.
>>> from functools import partial
>>> from qiskit_addon_mpf.backends.quimb_layers import LayerwiseEvolver
>>> approx_evolver_factory = partial(
... LayerwiseEvolver,
... layers=layer_models,
... split_opts={"max_bond": 10, "cutoff": 1e-5},
... )Deve-se observar que, nesse fluxo de trabalho, ainda não fixamos a etapa de tempo usada pela fórmula de Trotter. Também configuramos apenas uma única repetição da fórmula de Trotter, pois o restante será feito pelo algoritmo interno DynamicMPF interno, executado durante setup_dynamic_lse().
Obviamente, você também pode usar isso para especificar um arquivo ExactEvolverFactory. Mas você também pode misturar e combinar um quimb_layers.LayerwiseEvolver com um quimb_tebd.TEBDEvolver.
Recursos
[1]: https://en.wikipedia.org/wiki/Time-evolving _block_decimation