Tequila Tutorial:¶
Handling Optimizers, Initializing parametrized circuits¶
# Import everything we need here
import tequila as tq
numpy = tq.numpy
In the following example we will optimize a simple two qubit VQE with an Ansatz that is parametrized by two parameters. The Hamiltonian is also very simple and defined in the box below.
The tutorial is not a guideline to optimization strategies but only intends to show tequilas functionality
c = 0.5
H = tq.paulis.X(1) + c*tq.paulis.Z(1) - tq.paulis.Z(0)
matrix = H.to_matrix()
e, v = numpy.linalg.eigh(matrix)
e
And here we initialize our Ansatz circuit which is parametrized by a and b.
We show to ways to initialize Variables: Variable a is initialized with a convenience string based initialization Variable b is initialized as Variable which allows you to conveniently transforming it when initializing gates. Here we do a simple rescaling as an example Note that you don’t need strings to name variables but can use any hashable non-numeric type (meaning anything that is not interpreted as number).
For example: tq.Variable(name=(0,1,2,3)) will also work
The Ansatz has no specific meaning and it is more to show different ways how to deal with variables. See for instance that you can scale them and use the same variable in multiple gates. (See the other tutorials for more examples)
a = tq.Variable(name="a")
b = tq.Variable(name="b")
c = tq.Variable(name="c")
a = 2.0*a*tq.numpy.pi # optimize a in units of 2pi
b = 2.0*b*tq.numpy.pi # optimize b in units of 2pi
c = 2.0*c*tq.numpy.pi # optimize c in units of 2pi
U = tq.gates.Ry(target=0, angle=a)
U += tq.gates.Ry(target=1, control=0, angle=b)
U += tq.gates.X(target=1)
U += tq.gates.Ry(target=1, control=0, angle=-b)
U += tq.gates.Ry(target=0,angle=c)
print(U)
# nice output (depends on which backends you have installed)
tq.draw(U)
In the next box we form an objective out of our Hamiltonian and our Ansatz and pass it down to the optimizer. In the following boxes we show how the results of the optimizer can be plotted
See further below for some small exercises and additional information
First lets see how objectives are created and simulated
O = tq.Objective.ExpectationValue(U=U, H=H)
variables = {"a":0.25, "b":0.25, "c":0.25}
energy = tq.simulate(O, variables=variables)
wfn = tq.simulate(U, variables=variables)
evaluate_squared = tq.simulate(O**2, variables=variables)
print("energy : {}".format(energy))
print("wfn : {}".format(wfn))
print("squared: {}".format(evaluate_squared))
Now the objective can be optimized
initial_values = {'a':0.3, 'b':0.3, 'c':0.3}
O = tq.Objective.ExpectationValue(U=U, H=H)
result = tq.minimize(objective=O, method="bfgs", initial_values=initial_values, tol=1.e-3, method_options={"gtol":1.e-3})
# final energy
result.energy
Plot out the History: Note, that we choose bad initial points since they are close to the maximum
result.history.plot('energies')
result.history.plot(property='angles', key=["a", "b"])
# Convenience in the history plot
result.history.plot(property='angles', key="a")
result.history.plot(property=['angles', 'gradients'], key=["c"])
FAQ¶
1: How can I extract the parameters from a given circuit?¶
Call the ‘extract_parameters’ attribute and get back a list of all Variables in the circuit
angles = U.extract_variables()
angles
2: How can I do measurement based simulation?¶
Pass down the ‘samples’ keyword to simulate finite samples See the later exercises to play around with sample number and optimization methods.
Feel free to play around with the number of samples Don’t excpect miracles from the optimizer, you might need to hit return a few times or increase the number of samples.Note that stochastic gradients are not yet supported Note also that we did not set the initial parameters, so we will start with all parameters set to 0 which is a stationary point in this example (full wavefunction simulation would get stuck, see also the exercise below).
Sampling based simulation needs improvement. Don’t expect too much, but feel free to contribute
Lets only optimize veriable b and set the other to the correct value to also show how that works
O = tq.Objective.ExpectationValue(U=U, H=H)
initial_values["a"] = 0.3
initial_values["c"] = 0.3
initial_values["b"] = 0.3
result = tq.minimize(objective=O, variables=["b"], initial_values = initial_values, tol=1.e-3, samples=100, method="bfgs")
result.history.plot('energies')
print("result = ", result.energy)
3: Which Simulator was used and how can I choose the simulator?¶
You can pass down the simulator to the optimizer by the simulator keyword (see below) If no specific simulator was chosen by you that means the simulator is automatically picked. Which simulator is picked depends on what simulators you have installed and if you demanded a full wavefunction to be simulated or not.
You can check which simulators you have installed with the following
print(tq.show_available_simulators())
Here is how you would initialize a simulator and pass it down the optimizer. The if statement is just to prevent your Ipython kernel from crashing when you have not installed the simulator Feel free to change it to something you have installed
if 'qiskit' in tq.INSTALLED_BACKENDS: # failsafe to only execute cell when qiskit is actually there
O = tq.Objective.ExpectationValue(U=U, H=H)
result = tq.minimize(objective=O, method="bfgs",
initial_values=initial_values,
backend="qiskit")
result.history.plot()
result.history.plot('angles')
4: Can I use numerical evaluation of gradients¶
use_gradient = False
or
use_gradient = "2-point"
.use_gradient = '3-point' or 'cs'
are also possible for scipy
methods which support them.eps
which defines the stepsizes
for the ‘2-point’ method.result = tq.minimize(objective=O,
method="bfgs",
initial_values=initial_values,
use_gradient='2-point',
method_options = {'eps':1.e-3})
5: Can I use Hessian based optimizations, and can I evaluate Hessians numerically?¶
Yes you can, by just picking those methods (like for example ‘dogleg’ or ‘newton-cg’). For all ‘trust-*’ methods you can also pick different options (see again scipy documentation)
use_hessian
instead of use_gradient
.use_gradient
and
use_hessian
will work (usually you need to have the gradients
analytically) and that most scipy methods do not support numerical
evaluation of Hessians. Also do not confuse that with methods which
use an approximation of the Hessian. Again: Check the scipy
documentation for more informationHere comes a small example where (note that the methods above will not converge to the minimum for this initial_values)
tq.optimizer_scipy.OptimizerSciPy.hessian_based_methods
options = {
"initial_tr_radius":0.05,
"max_tr_radius":0.1
}
result = tq.optimizer_scipy.minimize(objective=O,
initial_values={"a":0.25, "b":0.25, "c":0.25},
method = "trust-exact",
method_options = options)
Exercises¶
See farther down for solutions
Exercise 1¶
You can pass down initial_values to the optimizer in the same format as you can do it with the circuit (see above). Figure out how to do that by checking out the documentation of the ‘minimize’ function.
Exercise 2¶
Figure out which method the optimizer above used and how to use a different optimization method.
# hints
tq.show_available_optimizers()
Exercise 3¶
If you initialize both parameters to 0.0 you will directly hit a stationary point which causes the optimizer stop. Find out how you can impose bounds on the variables in order to prevent the optimizer from hitting that point.
Again: Check the documentation of the ‘minimize’ function.
Note: Not all optimization methods of SciPy support bounds on the variables
Note: It is not enough to just restrict the point 0.0
Solutions¶
Exercise 1 & 2¶
O = tq.Objective.ExpectationValue(U=U, H=H)
result = tq.minimize(objective=O, method='Nelder-Mead', maxiter=100, initial_values={'a':0.1, 'b':0.1})
result.history.plot('energies')
result.history.plot('angles')
Exercise 3¶
# this will get stuck (similar for other gradient based optimizers)
zeroes = {'a':0.0, 'b':0.0}
shift = 1.0
O = tq.Objective.ExpectationValue(U=U, H=H)
result = tq.minimize(objective=O, method='TNC', initial_values=zeroes)
result.history.plot()
# bounding the variables to keep it away from the stationary point which occurs at 0 and is periodic in 2pi
# using negative values since that converges faster
zeroes = {'a':0.0, 'b':0.0}
shift = 1.0
bounds = {'a':(-2*numpy.pi+0.1, -0.1), 'b':(-2*numpy.pi+0.1, -0.1), 'c':(-2*numpy.pi+0.1, -0.1) }
O = tq.Objective.ExpectationValue(U=U, H=H)
result = tq.minimize(objective=O, method='TNC', initial_values=zeroes, method_bounds=bounds)
result.history.plot()
result.history.plot('angles')