Portuguese
Idiomas
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

Nota

Esta página foi gerada, a partir do tutorials/algorithms/02_vqe_advanced.ipynb.

Opções avançadas de VQE

No primeiro tutorial de algoritmos, você aprendeu como configurar um algoritmo básico VQE. Agora, você verá como fornecer parâmetros de configuração mais avançados para explorar toda a gama de recursos dos algoritmos variacionais do Qiskit: VQE <https://qiskit.org/documentation/stubs/qiskit.algorithms.minimum_eigensolvers.VQE.html> __, QAOA e VQD entre outros. Em particular, este tutorial explicará como configurar um callback para monitorar a convergência e o uso de ponto inicials e gradientes personalizados.

Callback

Os métodos de callback podem ser usados para monitorar o progresso da otimização à medida que o algoritmo é executado e converge para o mínimo. O callback é invocado para cada avaliação funcional pelo otimizador e fornece o valor atual do otimizador, contagem de avaliação, parâmetros atuais do otimizador, etc. Observe que, dependendo do otimizador específico, pode não ser cada iteração (etapa) do otimizador, por exemplo se o otimizador estiver chamando a função de custo para calcular um gradiente baseado em diferença finita, isso será visível por meio do callback.

Esta seção demonstra como alavancar callbacks em VQE para traçar o caminho de convergência para a energia do estado fundamental com um conjunto selecionado de otimizadores.

Primeiro, você precisa de um operador qubit para VQE. Para este exemplo, você pode usar o mesmo operador usado na introdução de algoritmos, que foi originalmente calculado pelo Qiskit Nature para uma molécula de H2.

[1]:
from qiskit.quantum_info import SparsePauliOp

H2_op = SparsePauliOp.from_list(
    [
        ("II", -1.052373245772859),
        ("IZ", 0.39793742484318045),
        ("ZI", -0.39793742484318045),
        ("ZZ", -0.01128010425623538),
        ("XX", 0.18093119978423156),
    ]
)

O próximo passo é instanciar o Estimator de escolha para a avaliação dos valores esperados dentro do VQE. Para simplificar, você pode selecionar o qiskit.primitives.Estimator fornecido com a instalação padrão do Qiskit Terra.

[2]:
from qiskit.primitives import Estimator

estimator = Estimator()

Agora você pode comparar um conjunto de otimizadores através do callback VQE. A energia mínima do hamiltoniano H2 pode ser encontrada facilmente, então o número máximo de iterações (maxiter) não precisa ser muito grande. Você pode mais uma vez usar TwoLocal como a função de onda de teste selecionada (ou seja, ansatz).

[3]:
import numpy as np
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.utils import algorithm_globals

# we will iterate over these different optimizers
optimizers = [COBYLA(maxiter=80), L_BFGS_B(maxiter=60), SLSQP(maxiter=60)]
converge_counts = np.empty([len(optimizers)], dtype=object)
converge_vals = np.empty([len(optimizers)], dtype=object)

for i, optimizer in enumerate(optimizers):
    print("\rOptimizer: {}        ".format(type(optimizer).__name__), end="")
    algorithm_globals.random_seed = 50
    ansatz = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")

    counts = []
    values = []

    def store_intermediate_result(eval_count, parameters, mean, std):
        counts.append(eval_count)
        values.append(mean)

    vqe = VQE(estimator, ansatz, optimizer, callback=store_intermediate_result)
    result = vqe.compute_minimum_eigenvalue(operator=H2_op)
    converge_counts[i] = np.asarray(counts)
    converge_vals[i] = np.asarray(values)

print("\rOptimization complete      ");
Optimization complete

Agora, a partir dos dados do callback que você armazenou, você pode plotar o valor de energia em cada chamada de função objetivo que cada otimizador faz. Um optimizador que usa um método de diferenças finitas para calcular o gradiente tem aquele gráfico de passos característico onde para um número de avaliações calcula o valor dos pontos próximos para estabelecer um gradiente (os pontos próximos têm valores muito semelhantes cuja diferença não pode ser vista na escala do gráfico aqui).

[4]:
import pylab

pylab.rcParams["figure.figsize"] = (12, 8)
for i, optimizer in enumerate(optimizers):
    pylab.plot(converge_counts[i], converge_vals[i], label=type(optimizer).__name__)
pylab.xlabel("Eval count")
pylab.ylabel("Energy")
pylab.title("Energy convergence for various optimizers")
pylab.legend(loc="upper right");
../../_images/tutorials_algorithms_02_vqe_advanced_options_10_0.png

Finalmente, uma vez que o problema acima ainda é facilmente tratável classicamente, você pode usar NumPyMinimumEigensolver para calcular um valor de referência para a solução.

[5]:
from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver
from qiskit.opflow import PauliSumOp

numpy_solver = NumPyMinimumEigensolver()
result = numpy_solver.compute_minimum_eigenvalue(operator=PauliSumOp(H2_op))
ref_value = result.eigenvalue.real
print(f"Reference value: {ref_value:.5f}")
Reference value: -1.85728

Agora você pode plotar a diferença entre a solução VQE e este valor de referência exato conforme o algoritmo converge para a energia mínima.

[6]:
pylab.rcParams["figure.figsize"] = (12, 8)
for i, optimizer in enumerate(optimizers):
    pylab.plot(
        converge_counts[i],
        abs(ref_value - converge_vals[i]),
        label=type(optimizer).__name__,
    )
pylab.xlabel("Eval count")
pylab.ylabel("Energy difference from solution reference value")
pylab.title("Energy convergence for various optimizers")
pylab.yscale("log")
pylab.legend(loc="upper right");
../../_images/tutorials_algorithms_02_vqe_advanced_options_14_0.png

Gradientes

Nos algoritmos variacionais do Qiskit, se o otimizador fornecido usar uma técnica baseada em gradiente, o método de gradiente padrão será o de diferenças finitas. No entanto, essas classes incluem uma opção para passar gradientes personalizados por meio do parâmetro gradient, que pode ser qualquer um dos métodos fornecidos no framework gradient, que suporta totalmente o uso de primitivas. Esta seção mostra como usar gradientes personalizados no fluxo de trabalho do VQE.

O primeiro passo é inicializar a primitiva correspondente e o gradiente primitivo:

[7]:
from qiskit.algorithms.gradients import FiniteDiffEstimatorGradient

estimator = Estimator()
gradient = FiniteDiffEstimatorGradient(estimator, epsilon=0.01)

Agora, você pode inspecionar uma execução SLSQP usando o FiniteDiffEstimatorGradient acima:

[8]:
algorithm_globals.random_seed = 50
ansatz = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")

optimizer = SLSQP(maxiter=100)

counts = []
values = []


def store_intermediate_result(eval_count, parameters, mean, std):
    counts.append(eval_count)
    values.append(mean)


vqe = VQE(
    estimator, ansatz, optimizer, callback=store_intermediate_result, gradient=gradient
)

result = vqe.compute_minimum_eigenvalue(operator=H2_op)
print(f"Value using Gradient: {result.eigenvalue.real:.5f}")
Value using Gradient: -1.85728
[9]:
pylab.rcParams["figure.figsize"] = (12, 8)
pylab.plot(counts, values, label=type(optimizer).__name__)
pylab.xlabel("Eval count")
pylab.ylabel("Energy")
pylab.title("Energy convergence using Gradient")
pylab.legend(loc="upper right");
../../_images/tutorials_algorithms_02_vqe_advanced_options_19_0.png

Ponto inicial

Por padrão, a otimização começa em um ponto aleatório dentro dos limites definidos pelo ansatz. A opção initial_point permite substituir este ponto com uma lista personalizada de valores que correspondem ao número de parâmetros ansatz.

Você pode se perguntar… Por que definir um ponto inicial personalizado? Bem, essa opção pode ser útil se você tiver um palpite para um ponto de partida razoável para o problema ou tiver informações de um experimento anterior.

Para demonstrar esse recurso, vejamos os resultados de nossa execução VQE anterior:

[10]:
print(result)
cost_function_evals = result.cost_function_evals
{   'aux_operators_evaluated': None,
    'cost_function_evals': 9,
    'eigenvalue': -1.8572750175655812,
    'optimal_circuit': <qiskit.circuit.library.n_local.two_local.TwoLocal object at 0x13ef7dd20>,
    'optimal_parameters': {   ParameterVectorElement(θ[0]): 4.296519450348719,
                              ParameterVectorElement(θ[3]): 6.092947832767056,
                              ParameterVectorElement(θ[1]): 4.426962358395531,
                              ParameterVectorElement(θ[7]): 0.36021017470898664,
                              ParameterVectorElement(θ[4]): -2.598326651673288,
                              ParameterVectorElement(θ[5]): 1.5683250498282322,
                              ParameterVectorElement(θ[2]): 0.5470777607659094,
                              ParameterVectorElement(θ[6]): -4.717616147449751},
    'optimal_point': array([ 4.29651945,  4.42696236,  0.54707776,  6.09294783, -2.59832665,
        1.56832505, -4.71761615,  0.36021017]),
    'optimal_value': -1.8572750175655812,
    'optimizer_evals': None,
    'optimizer_result': <qiskit.algorithms.optimizers.optimizer.OptimizerResult object at 0x13010b6a0>,
    'optimizer_time': 0.3502693176269531}

Agora, você pode pegar o optimal_point do resultado acima e usá-lo como o initial_point para um cálculo de acompanhamento.

Observação: initial_point agora é um argumento apenas de palavra-chave da classe VQE (ou seja, deve ser definido seguindo a sintaxe keyword=value).

[11]:
initial_pt = result.optimal_point

estimator1 = Estimator()
gradient1 = FiniteDiffEstimatorGradient(estimator, epsilon=0.01)
ansatz1 = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")
optimizer1 = SLSQP(maxiter=1000)

vqe1 = VQE(
    estimator1, ansatz1, optimizer1, gradient=gradient1, initial_point=initial_pt
)
result1 = vqe1.compute_minimum_eigenvalue(operator=H2_op)
print(result1)

cost_function_evals1 = result1.cost_function_evals
print()
{   'aux_operators_evaluated': None,
    'cost_function_evals': 1,
    'eigenvalue': -1.8572750175655812,
    'optimal_circuit': <qiskit.circuit.library.n_local.two_local.TwoLocal object at 0x1411b9780>,
    'optimal_parameters': {   ParameterVectorElement(θ[0]): 4.296519450348719,
                              ParameterVectorElement(θ[1]): 4.426962358395531,
                              ParameterVectorElement(θ[4]): -2.598326651673288,
                              ParameterVectorElement(θ[5]): 1.5683250498282322,
                              ParameterVectorElement(θ[3]): 6.092947832767056,
                              ParameterVectorElement(θ[2]): 0.5470777607659094,
                              ParameterVectorElement(θ[6]): -4.717616147449751,
                              ParameterVectorElement(θ[7]): 0.36021017470898664},
    'optimal_point': array([ 4.29651945,  4.42696236,  0.54707776,  6.09294783, -2.59832665,
        1.56832505, -4.71761615,  0.36021017]),
    'optimal_value': -1.8572750175655812,
    'optimizer_evals': None,
    'optimizer_result': <qiskit.algorithms.optimizers.optimizer.OptimizerResult object at 0x1411e3f10>,
    'optimizer_time': 0.05097508430480957}

[12]:
print(
    f"cost_function_evals is {cost_function_evals1} with initial point versus {cost_function_evals} without it."
)
cost_function_evals is 1 with initial point versus 9 without it.

Olhando para cost_function_evals você pode notar como o ponto inicial ajudou o algoritmo a convergir mais rápido (em apenas 1 iteração, pois já fornecemos a solução ótima).

Isso pode ser particularmente útil nos casos em que temos dois problemas intimamente relacionados e a solução de um deles pode ser usada para adivinhar a do outro. Um exemplo pode ser traçar perfis de dissociação em química, onde alteramos as distâncias interatômicas de uma molécula e calculamos o autovalor mínimo para cada distância. Quando as mudanças na distância forem pequenas, esperamos que a solução ainda esteja próxima da anterior. Assim, uma técnica famosa é simplesmente usar o ponto ótimo de uma solução como ponto de partida para a próxima etapa. Também existem técnicas mais complexas, nas quais podemos aplicar a extrapolação para calcular uma posição inicial com base na(s) solução(ões) anterior(es) em vez de usar diretamente a solução anterior.

[13]:
import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.2
qiskit-aer0.11.1
qiskit-ignis0.7.1
qiskit-ibmq-provider0.19.2
qiskit0.39.2
System information
Python version3.10.2
Python compilerClang 13.0.0 (clang-1300.0.29.30)
Python buildv3.10.2:a58ebcc701, Jan 13 2022 14:50:16
OSDarwin
CPUs8
Memory (Gb)64.0
Fri Nov 18 01:08:34 2022 CET

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.