Nota
Esta página foi gerada, a partir do tutorials/algorithms/04_vqe_advanced.ipynb.
Execute interativamente no IBM Quantum lab.
Uso avançado do VQE¶
Existem vários parâmetros para configurar e usar recursos mais avançados do VQE. Este tutorial vai cobrir parâmetros como initial_point
, expectation
e gradient
.
Ele também irá abranger o uso avançado do simulador como a utilização do Aer com o método de Estado de Produto Matricial.
[1]:
from qiskit import BasicAer
from qiskit.aqua.operators import X, Z, I
from qiskit.aqua import QuantumInstance, aqua_globals
from qiskit.aqua.algorithms import VQE
from qiskit.aqua.components.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
Aqui usaremos o mesmo operador usado nos outros tutoriais dos algoritmos VQE.
[2]:
H2_op = (-1.052373245772859 * I ^ I) + \
(0.39793742484318045 * I ^ Z) + \
(-0.39793742484318045 * Z ^ I) + \
(-0.01128010425623538 * Z ^ Z) + \
(0.18093119978423156 * X ^ X)
Ponto inicial¶
O parâmetro initial_point
permite que a otimização comece no ponto determinado, onde o ponto é uma lista de parâmetros que configurarão a forma variacional. Por padrão o ponto inicial é None
o que significa que o VQE escolherá um. A escolha neste caso é: se a forma variacional fornecida tiver um ponto preferencial, com base no estado inicial fornecido a ela, então este será escolhido, caso contrário, será escolhido um ponto inicial aleatório que se encaixe com quaisquer limites que a forma variacional tenha. Se um ponto inicial for fornecido, ele terá prioridade e será usado - note no entanto que ele deve corresponder em comprimento ao número de parâmetros no circuito da forma variacional.
Por que usar um ponto inicial? Uma razão seria se você tiver conseguido adivinhar um ponto de partida razoável para o problema ou talvez tenha informações de um experimento prévio.
Para demonstrar o uso vamos simplesmente repetir o primeiro exemplo funcional do tutorial de introdução a algoritmos para obter o ponto ideal de uma solução.
[3]:
from qiskit.aqua import aqua_globals
seed = 50
aqua_globals.random_seed = seed
qi = QuantumInstance(BasicAer.get_backend('statevector_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi)
result = vqe.run()
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
{ 'cost_function_evals': 72,
'eigenstate': array([-9.55448660e-05+2.12037105e-17j, 9.93766273e-01+2.25293943e-16j,
-1.11483565e-01+1.52657541e-16j, -1.77521351e-05+3.71607315e-17j]),
'eigenvalue': (-1.857275017559769+0j),
'optimal_parameters': { Parameter(θ[0]): 4.296520551468743,
Parameter(θ[1]): 4.426962086704216,
Parameter(θ[2]): 0.5470753710293924,
Parameter(θ[3]): 6.09294789784282,
Parameter(θ[4]): -2.598325857134344,
Parameter(θ[5]): 1.5683261371389359,
Parameter(θ[6]): -4.717618235040379,
Parameter(θ[7]): 0.3602072316165878},
'optimal_point': array([ 4.29652055, 4.42696209, 0.54707537, 6.0929479 , -2.59832586,
1.56832614, -4.71761824, 0.36020723]),
'optimal_value': -1.857275017559769,
'optimizer_evals': 72,
'optimizer_time': 1.2399418354034424}
Agora podemos tomar o optimal_point
do resultado acima e usá-lo como o initial_point
aqui.
[4]:
initial_pt = result.optimal_point
aqua_globals.random_seed = seed
qi = QuantumInstance(BasicAer.get_backend('statevector_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, initial_point=initial_pt, quantum_instance=qi)
result1 = vqe.run()
pp.pprint(result1)
{ 'cost_function_evals': 10,
'eigenstate': array([-9.55448660e-05+2.12037105e-17j, 9.93766273e-01+2.25293943e-16j,
-1.11483565e-01+1.52657541e-16j, -1.77521351e-05+3.71607315e-17j]),
'eigenvalue': (-1.857275017559769+0j),
'optimal_parameters': { Parameter(θ[1]): 4.426962086704216,
Parameter(θ[0]): 4.296520551468743,
Parameter(θ[4]): -2.598325857134344,
Parameter(θ[6]): -4.717618235040379,
Parameter(θ[3]): 6.09294789784282,
Parameter(θ[2]): 0.5470753710293924,
Parameter(θ[5]): 1.5683261371389359,
Parameter(θ[7]): 0.3602072316165878},
'optimal_point': array([ 4.29652055, 4.42696209, 0.54707537, 6.0929479 , -2.59832586,
1.56832614, -4.71761824, 0.36020723]),
'optimal_value': -1.857275017559769,
'optimizer_evals': 10,
'optimizer_time': 0.27449989318847656}
Aqui vemos que o resultado foi obtido muito mais rapidamente já que optimizer_evals
é 10 contra 72 de quando se iniciou a partir de um valor aleatório quando o ponto inicial não foi fornecido (padrão de None).
Isso torna-se útil por exemplo quando a solução para um problema pode ser utilizada para adivinhar a solução de um problema muito semelhante. A química é um bom exemplo, onde mudamos distância(s) interatômica(s) da molécula para traçar um perfil de dissociação. Quando as variações da distância são pequenas, esperamos que a solução ainda esteja nas proximidades da anterior. Uma técnica é simplesmente usar o ponto ideal de uma solução como ponto de partida para o próximo passo. Agora, técnicas mais complexas que fazem alguma extrapolação para calcular uma posição inicial com base em soluções prévias ao invés de usar diretamente a solução anterior são possíveis. O tutorial sampling_potential_energy_surfaces do Qiskit Chemistry mostra o bootstraping e a extrapolação.
Expectativas¶
A energia do operador Hamiltoniano no qual o VQE está trabalhando é o valor esperado quando avaliado com a forma variacional parametrizada. Para calcular o valor esperado o VQE usa uma instância de um objeto expectation. Tal instância pode ser fornecida através do parâmetro expectation
, ou no caso padrão em que ela tem um valor de None
, o VQE usará a ExpectationFactory para criar por si próprio uma instância adequada com base no backend fornecido.
Para a maioria dos casos deixar o VQE criar uma instância adequada é suficiente. No entanto o qasm_simulator do Qiskit Aer suporta uma instrução instantânea (snapshot) que pode ser usada em conjunto com o cálculo da esperança do operador. Se for usada o resultado é ideal, isto é, como o simulador de statevector, e não tem shot-noise. Uma vez que as pessoas normalmente escolhem o qasm_simulator para ter shot-noise (ruído de amostragem), e ser mais parecido com o resultado de um dispositivo real, o VQE tem uma flag include_custom
que é passada para a ExpectationFactory. Quando estiver usando o simulador qasm do Aer, e está estiver definida como True
, a fábrica retornará uma AerPauliExpectation
que usa a instrução instantânea, já quando estiver definida como False
, padrão, é retornada a PauliExpectation
habitual.
O exemplo a seguir mostra include_custom=True
onde o resultado corresponde ao simulador de statevector. Na verdade, pode ser melhor/mais rápido fazer isso do que usar o statevector_simulator diretamente. Isto porque, no último caso, quando o Hamiltoniano é uma soma de Paulis, ele tem de ser convertido para a forma matricial, e isso é evitado com o uso da instrução snapshot quando include_custom é True.
[5]:
from qiskit import Aer
aqua_globals.random_seed = seed
qi = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi, include_custom=True)
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 72,
'eigenstate': {'01': 1008, '10': 16},
'eigenvalue': (-1.8572750175597519+0j),
'optimal_parameters': { Parameter(θ[0]): 4.296520463599476,
Parameter(θ[1]): 4.426962139199476,
Parameter(θ[2]): 0.5470754235069875,
Parameter(θ[3]): 6.092947836794945,
Parameter(θ[4]): -2.5983258956331645,
Parameter(θ[5]): 1.568326000491598,
Parameter(θ[6]): -4.717618128585369,
Parameter(θ[7]): 0.3602072910298268},
'optimal_point': array([ 4.29652046, 4.42696214, 0.54707542, 6.09294784, -2.5983259 ,
1.568326 , -4.71761813, 0.36020729]),
'optimal_value': -1.8572750175597519,
'optimizer_evals': 72,
'optimizer_time': 1.2052154541015625}
Caso você tenha dúvidas, aqui é o qasm_simulator novamente, mas include_custom foi deixado como padrão, que é False. A otimização terminou abruptamente, presumivelmente devido ao shot-noise (ruído de disparo) confundindo o otimizador SLSQP. O valor ideal também pode ser visto como errado, isto é, -1,098 contra o valor correto -1,857.
[6]:
aqua_globals.random_seed = seed
qi = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi)
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 10,
'eigenstate': {'00': 620, '01': 244, '10': 159, '11': 1},
'eigenvalue': (-1.0987888676631705+0j),
'optimal_parameters': { Parameter(θ[5]): 1.8462931831829383,
Parameter(θ[6]): -5.466043598406607,
Parameter(θ[7]): 0.6984088030463615,
Parameter(θ[2]): 0.6019852007557844,
Parameter(θ[1]): 4.19301252102391,
Parameter(θ[0]): 3.611860069224077,
Parameter(θ[4]): -3.3070470445355764,
Parameter(θ[3]): 5.949536809130025},
'optimal_point': array([ 3.61186007, 4.19301252, 0.6019852 , 5.94953681, -3.30704704,
1.84629318, -5.4660436 , 0.6984088 ]),
'optimal_value': -1.0987888676631705,
'optimizer_evals': 10,
'optimizer_time': 0.5309410095214844}
Mudar o otimizador para SPSA, que é projetado para trabalhar em ambientes ruidosos, nos proporciona um resultado melhor. No entanto não é tão preciso pois o ruído afetou o resultado.
[7]:
from qiskit.aqua.components.optimizers import SPSA
aqua_globals.random_seed = seed
qi = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SPSA(maxiter=100)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi)
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 241,
'eigenstate': {'01': 1007, '10': 17},
'eigenvalue': (-1.8623464125433034+0j),
'optimal_parameters': { Parameter(θ[3]): 7.149874669811867,
Parameter(θ[7]): -1.8648388048829714,
Parameter(θ[2]): 2.146860065695897,
Parameter(θ[1]): 2.0047037904408738,
Parameter(θ[5]): 0.026059065222908795,
Parameter(θ[6]): -4.4426207121597745,
Parameter(θ[4]): -4.6685058886658455,
Parameter(θ[0]): 4.731406884288576},
'optimal_point': array([ 4.73140688, 2.00470379, 2.14686007, 7.14987467, -4.66850589,
0.02605907, -4.44262071, -1.8648388 ]),
'optimal_value': -1.8623464125433034,
'optimizer_time': 11.047632932662964}
Como mencionado acima, um objeto de esperança pode ser explicitamente dado (então o ExpectationFactory
interno e include_custom nunca são usados/necessários). Abaixo nós criamos uma AerPauliExpectation
e passamos ela para o VQE. Podemos ver que os resultados correspondem com os acima onde definimos include_custom como True e deixamos o VQE criar seu próprio objeto de esperança.
[8]:
from qiskit.aqua.operators import AerPauliExpectation
aqua_globals.random_seed = seed
qi = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi,
expectation=AerPauliExpectation())
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 72,
'eigenstate': {'01': 1008, '10': 16},
'eigenvalue': (-1.8572750175597519+0j),
'optimal_parameters': { Parameter(θ[5]): 1.568326000491598,
Parameter(θ[6]): -4.717618128585369,
Parameter(θ[4]): -2.5983258956331645,
Parameter(θ[2]): 0.5470754235069875,
Parameter(θ[3]): 6.092947836794945,
Parameter(θ[7]): 0.3602072910298268,
Parameter(θ[0]): 4.296520463599476,
Parameter(θ[1]): 4.426962139199476},
'optimal_point': array([ 4.29652046, 4.42696214, 0.54707542, 6.09294784, -2.5983259 ,
1.568326 , -4.71761813, 0.36020729]),
'optimal_value': -1.8572750175597519,
'optimizer_evals': 72,
'optimizer_time': 1.3454556465148926}
By default, the PauliExpectation
object, that would have be chosen when include_custom is False (or when using BasicAer qasm_simulator, or a real device) groups Paulis into commuting sets. This is efficient as it runs less circuits to compute the expectation. However, if for some reason you wanted to run a circuit for each Pauli then then grouping can be turned off when constructing the PauliExpectation. You need to explicitly pass in such an expectation instance to VQE to have it work this
way though as shown below.
[9]:
from qiskit.aqua.operators import PauliExpectation
aqua_globals.random_seed = seed
qi = QuantumInstance(Aer.get_backend('qasm_simulator'), seed_transpiler=seed, seed_simulator=seed)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SPSA(maxiter=100)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi,
expectation=PauliExpectation(group_paulis=False))
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 241,
'eigenstate': {'01': 1007, '10': 17},
'eigenvalue': (-1.8667499370593512+0j),
'optimal_parameters': { Parameter(θ[1]): 2.1216349904032947,
Parameter(θ[7]): -1.9698662442728732,
Parameter(θ[5]): 0.2634941452871489,
Parameter(θ[6]): -4.360567678129973,
Parameter(θ[0]): 4.718047005792739,
Parameter(θ[4]): -4.733789920734712,
Parameter(θ[2]): 2.1876557567510497,
Parameter(θ[3]): 7.317481602611872},
'optimal_point': array([ 4.71804701, 2.12163499, 2.18765576, 7.3174816 , -4.73378992,
0.26349415, -4.36056768, -1.96986624]),
'optimal_value': -1.8667499370593512,
'optimizer_time': 20.964618682861328}
Gradiente¶
Os otimizadores que utilizam uma técnica baseada no gradiente podem ser fornecidos como um gradiente definido pelo usuário que será usado em vez de seu cálculo de gradiente padrão, o que geralmente é feito por uma simples diferença finita. Os gradientes são passados indiretamente ao otimizador através de seu parâmetro gradient
.
Como o uso de um gradient
fornecido pelo usuário foi mostrado no tutorial Monitorando a Convergência do VQE vou simplesmente referi-los lá. O tutorial Gradients framework tem muito mais sobre os gradientes em si também.
Instância Quântica e simulação avançada¶
While you may be familiar with passing a QuantumInstancen created from a statevector_simulator
a qasm_simulator
or real device backend, it is possible to use the advanced simulation modes of Aer too when applicable. For instance we can easily use the Aer Matrix Product State method, that has the potential to scale to larger numbers of qubits.
[10]:
aqua_globals.random_seed = seed
from qiskit.providers.aer import QasmSimulator
quantum_instance = QuantumInstance(QasmSimulator(method='matrix_product_state'), shots=1)
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')
slsqp = SLSQP(maxiter=1000)
vqe = VQE(operator=H2_op, var_form=ansatz, optimizer=slsqp, quantum_instance=qi, include_custom=True)
result = vqe.run()
pp.pprint(result)
{ 'cost_function_evals': 72,
'eigenstate': {'01': 1008, '10': 16},
'eigenvalue': (-1.8572750175597519+0j),
'optimal_parameters': { Parameter(θ[0]): 4.296520463599476,
Parameter(θ[6]): -4.717618128585369,
Parameter(θ[7]): 0.3602072910298268,
Parameter(θ[4]): -2.5983258956331645,
Parameter(θ[5]): 1.568326000491598,
Parameter(θ[3]): 6.092947836794945,
Parameter(θ[2]): 0.5470754235069875,
Parameter(θ[1]): 4.426962139199476},
'optimal_point': array([ 4.29652046, 4.42696214, 0.54707542, 6.09294784, -2.5983259 ,
1.568326 , -4.71761813, 0.36020729]),
'optimal_value': -1.8572750175597519,
'optimizer_evals': 72,
'optimizer_time': 1.2383882999420166}
[11]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
Qiskit | 0.23.1 |
Terra | 0.16.1 |
Aer | 0.7.1 |
Ignis | 0.5.1 |
Aqua | 0.8.1 |
IBM Q Provider | 0.11.1 |
System information | |
Python | 3.6.1 |Continuum Analytics, Inc.| (default, May 11 2017, 13:09:58) [GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] |
OS | Linux |
CPUs | 1 |
Memory (Gb) | 5.827335357666016 |
Tue Nov 17 15:48:57 2020 EST |
This code is a part of Qiskit
© Copyright IBM 2017, 2020.
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.
[ ]: