Source code for qubiter.adv_applications.MeanHamilMinimizer

from qubiter.adv_applications.CostMinimizer import *
from qubiter.StateVec import *
import qubiter.utilities_gen as utg
from qubiter.CodaSEO_writer import *
from qubiter.SEO_simulator import *
from qubiter.CktEmbedder import *
from openfermion.ops import QubitOperator
import sys

[docs]class MeanHamilMinimizer(CostMinimizer): """ This class is a child of class CostMinimizer. It's purpose is to minimize a cost function which equals the mean value of a Hamiltonian. We refer to this task as the Mean Hamiltonian Minimization Problem. The qc history of this problem started with quantum chemists planning to use on a qc the phase estimation algo invented by Kitaev? (an algo that is also implemented in Qubiter) to estimate the energy levels ( eigenvalues) of simple molecules, initially H2. Then a bunch of people realized, heck, rather than trying to estimate the eigenvalues of a Hamiltonian by estimating the phase changes it causes, we can estimate those eigenvalues more efficiently by estimating the mean value of that Hamiltonian as measured empirically on a qc. Basically, just the Rayleigh-Ritz method, one of the oldest tricks in the book. One of the first papers to propose this mean idea is Their algo is commonly referred to by the ungainly name VQE (Variational Quantum Eigensolver). VQE was originally applied to do quantum chemistry with a qc. But now Rigetti and others have renamed it hybrid quantum-classical quantum computing and pointed out that it's an algo that has wide applicability, not just to quantum chemistry. The idea behind hybrid quantum-classical is very simple. One has a classical box CBox and a quantum box QBox. The gates of QBox depend on N gate parameters. QBox sends info to CBox. CBox sends back to QBox N new gate parameters that will lower some cost function. This feedback process between CBox and QBox continues until the cost is minimized. The cost function is the mean value of a Hamiltonian which is estimated empirically from data obtained from the qc which resides inside the QBox. To minimize a function of N continuous parameters, one can use some methods like simulated annealing and Powell that do not require calculating derivatives, or one can use methods that do use derivatives. Another possible separation is between methods that don't care which local minimum they find, as long as they find one of them, and those methods that try to find the best local minimum of them all, the so called global minimum. Yet another separation is between methods that allow constraints and those that don't. Among the methods that do use derivatives, the so called gradient based methods only use the 1st derivative, whereas other methods use both first (Jacobian) and second (Hessian) derivatives. The performance of those that use both 1st and 2nd derivatives degrades quickly as N grows. Besides, calculating 2nd derivatives is very expensive. Hence, methods that use the 2nd derivatives are practically useless in the neural network field where N is usually very large. In that field, gradient based methods rule. A method that uses no derivatives is Powell. A gradient based method that is designed to have a fast convergence rate is the Conjugate Gradient (CG) method. Another gradient based method is back-propagation (BP). BP can be implemented as distributed computing much more easily than other gradient based methods so it is favored by the most popular computer programs for doing distributed AI, such as PyTorch and TensorFlow. Qubiter can perform minimization using various minlibs (minimization software libraries) such as 'scipy', 'autograd', 'tflow', 'pytorch'. It can also use various devices (aka simulators or backends), either virtual or real, to do the minimization. For example, tensorflow is a minlib and SEO_simulator_tf is a backend that is native to qubiter and uses tensorflow. By a native device, we mean one that uses Qubiter native simulators like SEO_simulator and SEO_simulator_tf. Non-scipy minlibs implement backprop. The 'scipy' minlib is a wrapper for the scipy function `scipy.optimize.minimize`. This scipy umbrella method implements many minimization methods, including Powell and CG. Attributes ---------- all_var_nums : list[int] this is a list of distinct ints that identify each continuous variable (i.e., parameter, non-functional placeholder variable) on which the cost function depends. The ordering corresponds to the ordering of self.init_x_val emp_mhamil : MeanHamil Empirical mean Hamiltonian, used to evaluate cost init_x_val : nm.ndarray this array gives the initial values in radians for the cost function being minimized. The ordering corresponds to the ordering of self.all_var_nums targ_mhamil : MeanHamil Target mean Hamiltonian, used to evaluate targ_cost """
[docs] def __init__(self, emp_mhamil, targ_mhamil, all_var_nums, init_var_num_to_rads, print_hiatus=1, verbose=False): """ Constructor Parameters ---------- emp_mhamil : MeanHamil targ_mhamil : MeanHamil all_var_nums : list[int] init_var_num_to_rads : dict[int, float] print_hiatus : int verbose : bool Returns ------- """ self.emp_mhamil = emp_mhamil self.targ_mhamil = targ_mhamil self.all_var_nums = all_var_nums assert emp_mhamil.all_var_nums == all_var_nums assert targ_mhamil.all_var_nums == all_var_nums init_x_val = [init_var_num_to_rads[k] for k in all_var_nums] self.init_x_val = np.array(init_x_val) CostMinimizer.__init__(self, init_x_val, print_hiatus, verbose)
[docs] def cost_fun(self, x_val): """ This method wraps self.emp_mhamil.get_mean_val(). This method will also print out, whenever it is called, a report of the current values of x and cost (and targ_cost if it is available). Parameters ---------- x_val : np.ndarray Returns ------- float """ var_num_to_rads = dict(zip(self.all_var_nums, list(x_val))) cost = self.emp_mhamil.get_mean_val(var_num_to_rads) self.cur_x_val = x_val self.cur_cost = cost self.broadcast_cost_fun_call() self.iter_count += 1 return cost
[docs] def targ_cost_fun(self, x_val): """ Returns the cost, predicted from theory, rather than estimated from data as in cost_fun(). This method mimics the method cost_fun(), but that one wraps self.emp_mhamil.get_mean_val(). This one wraps self.targ_mhamil.get_mean_val(). Parameters ---------- x_val : np.ndarray Returns ------- float """ # print('inside_targ_cost', x_val) var_num_to_rads = dict(zip(self.all_var_nums, list(x_val))) # print('mmmmmmmmmaaaaaa', var_num_to_rads) assert self.targ_mhamil.num_samples == 0,\ 'predict cost with zero samples' cost = self.targ_mhamil.get_mean_val(var_num_to_rads) # print('bbvvv-cost', cost) return cost
[docs] def find_min(self, minlib, **kwargs): """ This method finds minimum of cost function. It allows user to choose among several possible minlibs, namely, 'scipy', 'autograd', 'tflow', 'pytorch'. minlib parameters can be passed in via kwargs. kwargs (keyword arguments) minlib = scipy the keyword args of scipy.optimize.minimize minlib = autograd, tflow num_inter : float number of iterations (an iteration is every time call cost_fun) descent_rate : float positive float, constant that multiplies gradient of cost function being minimized. Often denoted as eta Parameters ---------- minlib : str kwargs : dict Returns ------- OptimizeResult | None OptimizeResult is a class (basically an enum) defined in scipy.optimize to hold the output results of scipy.optimize.minimize """ print('x_val~ (' + ', '.join(['#' + str(k) for k in self.all_var_nums]) + ')') def targ_cost(xx): # self argument seems to confuse grad return self.targ_cost_fun(xx) if minlib == 'scipy': import scipy minimizer_fun = scipy.optimize.minimize opt_result = minimizer_fun(self.cost_fun, self.init_x_val, **kwargs) if self.verbose: print('*********final optimum result' ' (final step=' + str(self.iter_count) + '):\n', opt_result) return opt_result elif minlib == 'autograd': from autograd import grad num_iter = utg.get_value(kwargs, 'num_iter') rate = utg.get_value(kwargs, 'descent_rate') self.cur_x_val = self.init_x_val for step in range(num_iter): xlist = list(self.cur_x_val) # print('mmbbb', self.cur_x_val, xlist) self.cur_targ_cost = targ_cost(self.cur_x_val) self.cur_cost = self.cost_fun(self.cur_x_val) # print('kkkhhh', grad(targ_cost)(self.cur_x_val)) for dwrt in range(len(xlist)): self.cur_x_val[dwrt] -= \ rate*grad(targ_cost)(self.cur_x_val)[dwrt] elif minlib == 'tflow': import tensorflow as tf assert tf.executing_eagerly() num_iter = utg.get_value(kwargs, 'num_iter') rate = utg.get_value(kwargs, 'descent_rate') self.init_x_val = tf.convert_to_tensor(self.init_x_val) self.cur_x_val = self.init_x_val # optimizer = tf.train.GradientDescentOptimizer(rate) for step in range(num_iter): with tf.GradientTape() as tape: self.cur_targ_cost = targ_cost(self.cur_x_val) # print('**********cccccccc', self.cur_x_val, # self.cur_targ_cost) self.cur_cost = self.cost_fun(self.cur_x_val) grads = tape.gradient(self.cur_targ_cost, self.cur_x_val) self.cur_x_val -= rate*grads # optimizer.apply_gradients(zip(grads, self.cur_x_val)) elif minlib == 'pytorch': assert False, 'not yet' else: assert False, 'unsupported find_min() minlib'
if __name__ == "__main__": def main(): print(5) main()