Variational Quantum Algorithms module
We provide to the user a module for make easier the execution of variational quantum algorithm. We explain in this notebook how to use these functions. Note that, for the moment, the module only handle local executions.
We first import all needed functions from the vqa
module.
[1]:
from mpqp.execution.vqa import *
<frozen importlib._bootstrap>:228: RuntimeWarning: scipy._lib.messagestream.MessageStream size changed, may indicate binary incompatibility. Expected 56 from C header, got 64 from PyObject
[2]:
from mpqp import QCircuit
from mpqp.gates import *
from sympy import symbols
x, y, z = symbols("x y z")
circuit = QCircuit([Rx(x, 0), Ry(y, 1), Rz(z,0), Rz(z,1), CNOT(0,1)])
print(circuit)
hi
┌───────┐┌───────┐
q_0: ┤ Rx(x) ├┤ Rz(z) ├──■──
├───────┤├───────┤┌─┴─┐
q_1: ┤ Ry(y) ├┤ Rz(z) ├┤ X ├
└───────┘└───────┘└───┘
Once the user defined a circuit depending on variables, using symbols
, he can call the function minimize
for two purposes:
Find the minimum energy of a hamiltonian with respect to the state produced by the circuit (VQE)
Minimize a custom and more complicated cost function defined by the user (general VQA)
Variational Quantum Eigensolver
We define an observable for which we want to find the ground state. Since the circuit has a fixed structure (ansatz) and is parametrized by some angles, finding the minimum energy is equivalent to finding the optimal parameters of the circuit, while supposing the structure has enough expressivity.
[3]:
from mpqp.measures import Observable, ExpectationMeasure
from mpqp.execution import IBMDevice, run
[4]:
import numpy as np
matrix = np.array([[4, 2, 3, 8],
[2, -3, 1, 0],
[3, 1, -1, 5],
[8, 0, 5, 2]])
hamiltonian = Observable(matrix)
We add to the circuit the corresponding ExpectationMeasure
to be minimized by the optimizer.
[5]:
circuit.add(ExpectationMeasure([0, 1], observable=hamiltonian, shots=0))
We then call the minimize
function directly on the function. We precise which Optimizer
(enum) we want to select and on which local device we want to run the circuit and evaluate the expectation value. One can also precise additional optimizer options (see scipy.minimize
documentation). If no initial value of the parameters is given, the parameters are all initialized at 0.
[6]:
minimize(circuit, Optimizer.COBYLA, IBMDevice.AER_SIMULATOR, optimizer_options={"maxiter":200})
[6]:
(-5.062257678262567,
array([-1.74101529e-04, -1.57086122e+00, 1.69523567e+00]))
Minimizing a custom cost function
We can also exit the VQE setup by defining a more custom and complicated cost function to minimize. In that case, the running of the circuit will happen in that function, and it shoud return a float
. It takes as input the parameters of the circuit to optimize.
[7]:
circuit2 = circuit.without_measurements()
def cost_function(params):
r1 = run(circuit2, IBMDevice.AER_SIMULATOR_STATEVECTOR, {x: params[0], y: params[1], z: params[2]})
r2 = run(circuit, IBMDevice.AER_SIMULATOR, {x: params[0], y: params[1], z: params[2]})
return abs(r1.amplitudes[0]) - np.sqrt(r2.expectation_value**3)
Then, we call the minimize
function but this time on the cost function. We again can choose the optimizer and its options, but there is no need to precise any device (since runs already explicited in the cost function). We precise also the number of parameters to optimize, and can eventually initialize the parameters with starting values.
[8]:
minimize(cost_function, Optimizer.COBYLA, nb_params=3, optimizer_options={"maxiter":200})
[8]:
(-36.044727730788836,
array([-1.45462257e+00, 2.50036492e-04, -1.57049530e+00]))
The function returns tuple with the value of the cost function, and the associated solution parameters.