<jupyter_start><jupyter_text>MeanHamilMinimizer native scipy The purpose of this notebook is to describe a naive but pedagogical,first baby step, in the implementationof what is called in Qubiter the Mean Hamiltonian Minimization problem.The qc history of this problem started with quantum chemists planning touse on a qc the phase estimation algo invented by Kitaev? (an algo thatis also implemented in Qubiter) to estimate the energy levels (eigenvalues) of simple molecules, initially H2. Then a bunch of peoplerealized, heck, rather than trying to estimate the eigenvalues of aHamiltonian by estimating the phase changes it causes, we can estimatethose eigenvalues more efficiently by estimating the mean value of thatHamiltonian as measured empirically on a qc. Basically, just theRayleigh-Ritz method, one of the oldest tricks in the book. One of thefirst papers to propose this mean idea ishttps://arxiv.org/abs/1304.3061 Their algo is commonly referred to bythe ungainly name VQE (Variational Quantum Eigensolver) VQE wasoriginally applied to do quantum chemistry with a qc. But now Rigettiand others have renamed it hybrid quantum-classical quantum computingand pointed out that it's an algo that has wide applicability, not justto quantum chemistry.The idea behind hybrid quantum-classical is very simple. One has aclassical box CBox and a quantum box QBox. The gates of QBox depend on Ngate parameters. QBox sends info to CBox. CBox sends back to QBox N newgate parameters that will lower some cost function. This feedbackprocess between CBox and QBox continues until the cost is minimized. Thecost function is the mean value of a Hamiltonian which is estimatedempirically from data obtained from the qc which resides inside the QBox.To minimize a function of N continuous parameters, one can use somemethods like simulated annealing and Powell that do not requirecalculating derivatives, or one can use methods that do use derivatives.Another possible separation is between methods that don't care whichlocal minimum they find, as long as they find one of them, and thosemethods that try to find the best local minimum of them all, the socalled global minimum. Yet another separation is between methods thatallow constraints and those that don't.Among the methods that do use derivatives, the so called gradient basedmethods only use the 1st derivative, whereas other methods use bothfirst (Jacobian) and second (Hessian) derivatives. The performance ofthose that use both 1st and 2nd derivatives degrades quickly as N grows.Besides, calculating 2nd derivatives is very expensive. Hence, methodsthat use the 2nd derivatives are practically useless in the neuralnetwork field where N is usually very large. In that field, gradientbased methods rule.A method that uses no derivatives is Powell. A gradient based methodthat is designed to have a fast convergence rate is the ConjugateGradient (CG) method. Another gradient based method is back-propagation(BP). BP can be implemented as distributed computing much more easilythan other gradient based methods so it is favored by the most popularcomputer programs for doing distributed AI, such as PyTorch andTensorflow.Qubiter can perform minimization using various minlibs (minimization software libraries) such as 'scipy', 'autograd', 'pytorch', 'tflow'. It can also use various devices (aka simulators or backends), either virtual or real, to do the minimization. Non-scipy minlibs implement backprop.The 'scipy' minlib is a wrapper for the scipy function`scipy.optimize.minimize`. This scipy umbrella method implements manyminimization methods, including Powell and CG.https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.htmlBy a native device, we mean one that uses Qubiter native simulators likeSEO_simulator.So, without further ado, here is an example of the use of class `MeanHamilMinimizer` with a scipy minlib and native device. $\newcommand{\bra}[1]{\left\langle{1}\right|}$$\newcommand{\ket}[1]{\left|{1}\right\rangle}$test: $\bra{\psi}M\ket{\phi}$ First change the directory to the Qubiter directory and add it to the path environment variable<jupyter_code>import os
import sys
print(os.getcwd())
os.chdir('../../')
print(os.getcwd())
sys.path.insert(0,os.getcwd())<jupyter_output>C:\Users\rrtuc\Desktop\backed up\python-projects\qubiter\qubiter\jupyter_notebooks
C:\Users\rrtuc\Desktop\backed up\python-projects\qubiter<jupyter_text>Next we construct a simple 4 qubit circuit that depends on two placeholder variables`1` and `2`. These are the continuous variables that we will vary to minimize a cost function. The cost function will be specified later.<jupyter_code>from qubiter.adv_applications.MeanHamil_native import *
from qubiter.adv_applications.MeanHamilMinimizer import *
num_bits = 4
file_prefix = 'mean_hamil_native_test'
emb = CktEmbedder(num_bits, num_bits)
wr = SEO_writer(file_prefix, emb)
wr.write_Rx(2, rads=np.pi/7)
wr.write_Rx(1, rads='#2*.5')
wr.write_Rn(3, rads_list=['#1', '-#1*3', '#2'])
wr.write_Rx(1, rads='-my_fun#2#1')
wr.write_cnot(2, 3)
wr.close_files()<jupyter_output><empty_output><jupyter_text>The code above wrote inside Qubiter's `io_folder`, two files,an English file and a Picture file. We next ask the writer object wr to print those two files for us<jupyter_code>wr.print_eng_file()
wr.print_pic_file()<jupyter_output>|   Rx  |   |   
|   |   Rx  |   
R   |   |   |   
|   |   Rx  |   
X---@   |   |<jupyter_text>The circuit depends on a placeholder function called `my_fun`.This function will remain fixed during theminimization process but it must be defined and it must be passed in inside an input dictionary to the constructor of class `MeanHamil`<jupyter_code>def my_fun(x, y):
    return x + .5*y

fun_name_to_fun = {'my_fun': my_fun}<jupyter_output><empty_output><jupyter_text>One must also pass into the constructor of `MeanHamil`, the value of a Hamiltonian called `hamil`.Qubiter stores `hamil` in an object of the class `QubitOperator`from the open source Python library `OpenFermion`.Note that  the `QubitOperator` constructor simplifies `hamil` automatically.Hamiltonians are Hermitian, so after `QubitOperator`finishes simplifying `hamil`, the coefficient of every "term"of `hamil` must be real.<jupyter_code>hamil = QubitOperator('X1 Y3 X1 Y1', .4) + QubitOperator('Y2 X1', .7)
print('hamil=\n', hamil)<jupyter_output>hamil=
 0.7 [X1 Y2] +
0.4 [Y1 Y3]<jupyter_text>So what is the purpose of this Hamiltonian `hamil`?The cost function to be minimized is defined as the mean value of `hamil`.More precisely, if $\ket{\psi}$ is the ouput of the circuitspecified above, then the cost function equals $\bra{\psi} | H | \ket{\psi}$. The initial values for parameters `1` and `2` to be minimizedmust also be passed in inside an input dictionary to the constructor of class `MeanHamilMinimizer`. Variable `all_var_nums` contains the keys of that dictionary.> Note: Results are very heavily dependent on initial x_val, probably because,due to the periodic nature of qubit rotations, there are many local minima<jupyter_code>init_var_num_to_rads = {1: 1., 2: 3.}
all_var_nums = init_var_num_to_rads.keys()<jupyter_output><empty_output><jupyter_text>For convenience, we define a function `case()` that creates anobject of `MeanHamilMinimizer`, asks that object to minimize the cost function,and then `case()` returns the final result of that minimization process.As advertised at the beginning of this notebook, we use various sub-methods of `scipy.optimize.minimize`<jupyter_code>num_samples = 0
print_hiatus = 25
verbose = False
np.random.seed(1234)

emp_mhamil = MeanHamil_native(file_prefix, num_bits, hamil,
            all_var_nums, fun_name_to_fun, simulator_name='SEO_simulator', num_samples=num_samples)
targ_mhamil = MeanHamil_native(file_prefix, num_bits, hamil,
            all_var_nums, fun_name_to_fun, simulator_name='SEO_simulator') # zero samples
def case(**kwargs):
    return MeanHamilMinimizer(emp_mhamil, targ_mhamil,
                 all_var_nums, init_var_num_to_rads,
                 print_hiatus, verbose).find_min(minlib='scipy', **kwargs)<jupyter_output><empty_output><jupyter_text>We focus on two cases, num_samples=0 and num_samples > 0.  When `MeanHamil` is told that num_samples=0, it does no sampling. It just calculatesthe mean value of `hamil` "exactly', using the exactfinal state vector calculated by Qubiter's `SEO_simulator` class<jupyter_code>min_method = 'Powell'
emp_mhamil.num_samples = 0
case(method=min_method)<jupyter_output>x_val~ (#1, #2)
iter=0, cost=-0.129256, x_val=1.000000, 3.000000
iter=25, cost=-0.128526, x_val=2.275041, 3.209272
iter=50, cost=-0.247852, x_val=1.105711, 3.540960
iter=75, cost=-0.241892, x_val=0.978839, 3.768145<jupyter_text>When num_samples > 0, Qubiter calculates the final state vectorof the circuit, then it samples that num_samples times,obtains an empirical probability distribution from that,and then it calculates the mean value of `hamil` using that empirical distribution<jupyter_code>min_method = 'Powell'
emp_mhamil.num_samples = 1000
case(method=min_method)
# very sensitive to eps, seems to be hitting discontinuity
min_method='CG'
num_sample= 0
case(options={'disp': True, 'eps':1e-2})<jupyter_output>x_val~ (#1, #2)
iter=0, cost=-0.139800, x_val=1.000000, 3.000000
iter=25, cost=-0.128000, x_val=0.999909, 2.999462
iter=50, cost=-0.158200, x_val=1.009999, 2.999992
Warning: Desired error not necessarily achieved due to precision loss.
         Current function value: -0.163000
         Iterations: 1
         Function evaluations: 75
         Gradient evaluations: 16
{'fraction_non_alphanumeric': 0.07697916666666667, 'fraction_numerical': 0.0265625, 'mean_word_length': 5.413493653974616, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 35, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '48212', 'license': 'Apache-2.0', 'n_tokens_mistral': 3082, 'n_tokens_neox': 2822, 'n_words': 1123, 'path': 'qubiter/jupyter_notebooks/MeanHamilMinimizer_native_scipy.ipynb', 'repo_name': 'yourball/qubiter', 'size': '9600'}