Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
common/grad_descend.cpp
Go to the documentation of this file.
1 /*
2 Copyright 2020 Peter Rakyta, Ph.D.
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 
17 #include <stdio.h>
18 #include <stdlib.h>
19 #define _USE_MATH_DEFINES
20 #include <math.h>
21 #include <cfloat>
22 #include <grad_descend.h>
23 
24 
31 Grad_Descend::Grad_Descend(void (* f_pointer) (Matrix_real, void *, double *, Matrix_real&), void* meta_data_in) {
32 
33  maximal_iterations = 5001;
35 
36  // numerical precision used in the calculations
37  num_precision = 1.42e-14;
38 
39 
40 
41  costfnc__and__gradient = f_pointer;
42  export_fnc = NULL;
43  meta_data = meta_data_in;
44 
45  // number of function calls during the optimization process
47 
48 }
49 
50 
57 Grad_Descend::Grad_Descend(void (* f_pointer) (Matrix_real, void *, double *, Matrix_real&), void (* export_pointer)(double , Matrix_real&, void* ), void* meta_data_in) {
58 
59  maximal_iterations = 5001;
61 
62  // numerical precision used in the calculations
63  num_precision = 1.42e-14;
64 
65 
66 
67  costfnc__and__gradient = f_pointer;
68  export_fnc = export_pointer;
69  meta_data = meta_data_in;
70 
71  // number of function calls during the optimization process
73 
74 }
75 
76 
77 
83 double Grad_Descend::Start_Optimization(Matrix_real &x, long maximal_iterations_in)
84 {
85 
86 
87 
88  variable_num = x.size();
89 
90 
91  // set the maximal number of iterations
92  maximal_iterations = maximal_iterations_in;
93 
95 
96 
97 
98 
99  // test for dimension
100  if ( variable_num <= 0 ) {
101  return DBL_MAX;
102  }
103 
104 
105  // Minimize the objective function
106  double f;
107  Optimize(x, f);
108 
109  return f;
110 
111 }
112 
113 
114 
115 
127 void Grad_Descend::line_search(Matrix_real& x, Matrix_real& g, Matrix_real& search_direction, Matrix_real& x0_search, Matrix_real& g0_search, double& maximal_step, double& d__dot__g0, double& f)
128 {
129 
130 
131 
132 
133  memcpy( x0_search.get_data(), x.get_data(), x.size()*sizeof(double) );
134  memcpy( g0_search.get_data(), g.get_data(), g.size()*sizeof(double) );
135 
136 
137  long max_loops = 50;//1 << 30;
138 
139 
140 
141  Matrix_real g_best = g.copy();
142 
143 
144  double step = std::min(1.0, maximal_step);
145 
146  double f_lowest = f;
147  double f_highest = f;
148 
149  double step_highest = maximal_step;
150  double step_lowest = 0.0;
151 
152  double step_best = 0.0;
153  double f_best = f;
154  double d__dot__g_abs_best = fabs(d__dot__g0);
155 
156  double d__dot__g_highest = 0.0;
157 
158 
159  for( long iter_idx=0; iter_idx<max_loops; iter_idx++) {
160 
161  for (long idx = 0; idx < variable_num; idx++) {
162  x[idx] = x0_search[idx] + step * search_direction[idx];
163  }
164 
165 
166  get_f_ang_gradient(x, f, g);
167 
168  // overlap between the search direction and the gradient
169  double d__dot__g_current = 0.0;
170  for (long idx = 0; idx < variable_num; idx++) {
171  d__dot__g_current += search_direction[idx] * g[idx];
172  }
173 
174  // update best solution
175  if (f < f_best || fabs(d__dot__g_current) < d__dot__g_abs_best) {
176 
177  step_best = step;
178  f_best = f;
179  d__dot__g_abs_best = fabs(d__dot__g_current);
180 
181  memcpy( g_best.get_data(), g.get_data(), g.size()*sizeof(double) );
182 
183  }
184 
185  // exit the loop if maximal function calls reached
187  break;
188  }
189 
190 
191  // modify the upper and lower bound of the step inetrval
192  if (f < f_lowest + step * 0.1 * d__dot__g0) { // Armijo test (Wolfe 1st condition)
193 
194  if (d__dot__g_current >= d__dot__g0 * 0.7) { // Wolfe 2nd (curvature) condition to exit the iterations
195  break;
196  }
197 
198  step_lowest = step;
199  f_lowest = f;
200 
201 
202  }
203  else {
204 
205  step_highest = step;
206  f_highest = f;
207  d__dot__g_highest = d__dot__g_current;
208 
209 
210  }
211 
212 
213 
214 
215 
216  // Calculate the next step length
217 
218  if (iter_idx == 0 || step_lowest > 0.0) {
219 
220  // It is critical to use the inexact line search (ILS) to ensure the global convergence of the nonlinear conjugate gradient (CG) method
221 
222  // fit a parabola a*step^2 + b*step + c to the points (step_lowest, f_lowest), (step_highest, f_highest) with a derivate d__dot__g_highest at step=step_highest
223  // then d__dot__g_lowest_fit is the derivate of the parabola at step=step_lowest
224  // f_highest = a*step_highest^2 + b*step_highest + c
225  // f_lowest = a*step_lowest^2 + b*step_lowest + c
226  double d__dot__g_lowest_fit = (f_highest - f_lowest) / (step_highest - step_lowest)*2.0 - d__dot__g_highest;
227 
228  // move on the parabola
229  double scale = 0.0;
230 
231  // the tip of the parabola is at step_lowest + scale * (step_highest - step_lowest)
232  scale = -d__dot__g_lowest_fit*0.5 / (d__dot__g_highest - d__dot__g_lowest_fit);
233  scale = std::max( 0.1, scale);
234 
235  step = step_lowest + scale * (step_highest - step_lowest);
236 
237  }
238  else {
239 
240  // In later BFGS iterations the minimum might be closer to the initial x0_search.
241  // Thus scaling down the step when step_lowest is still zero and the landscape near x0_serach need to be explored.
242  step = step * 0.1;
243 
244  if (step < num_precision) {
245  break;
246  }
247 
248  }
249 
250  } // for loop
251 
252 
253 
254  // copy the best solution in place of the current solution
255  if (step != step_best) {
256 
257  step = step_best;
258  f = f_best;
259 
260  g = g_best;
261 
262  for (long idx = 0; idx < variable_num; idx++) {
263  x[idx] = x0_search[idx] + step * search_direction[idx];
264  }
265  }
266 
267  if (step == 0.0 ) {
269  }
270 
271  return;
272 }
273 
274 
275 
276 
283 {
284 
285  // the initial gradient during the line search
286  Matrix_real g0_search( variable_num, 1 );
287 
288  // the initial point during the line search
289  Matrix_real x0_search( variable_num, 1 );
290 
291  // The calculated graient of the cost function at position x
292  Matrix_real g( variable_num, 1 );
293 
294  // the current search direction
295  Matrix_real search_direction( variable_num, 1 );
296 
298 
299 
300 
301  // inner product of the search direction and the gradient
302  double d__dot__g;
303 
304  double maximal_step; // The upper bound of the allowed step in the search direction
305 
307  get_f_ang_gradient(x, f, g);
308  }
309 
310  double fprev = fabs(f + f + 1.0);
311 
312  // Calculate the next search direction.
313 
314 
315  while ( true ) {
316 
317  maximal_step = 0.0;
318 
319  double search_direction__grad_overlap = 0.0;
320  get_search_direction(g, search_direction, search_direction__grad_overlap);
321 
322 
323  // Calculate the bound on the step-length
324 
325  if (search_direction__grad_overlap < 0.0) {
326  get_Maximal_Line_Search_Step(search_direction, maximal_step, search_direction__grad_overlap);
327  }
328  d__dot__g = search_direction__grad_overlap;
329 
330 
331 
332 
333  double gradient_norm = 0.0;
334  for (long idx = 0; idx < variable_num; idx++) {
335  gradient_norm += g[idx] * g[idx];
336  }
337 
338  double acc=1e-19;
339  // Test for convergence by measuring the magnitude of the gradient.
340  if (gradient_norm <= acc * acc) {
342  return;
343  }
344 
345  // in case the cost fuction does not show decrement in the direction of the line search
346  if (d__dot__g >= 0.0) {
348  return;
349  }
350 
351 
352  // terminate cycles if the cost function is not decreased any more
353  if (f >= fprev) {
355  return;
356  }
357 
358  fprev = f;
359 
360  if ( export_fnc ) {
361  export_fnc( f, x, meta_data );
362  }
363 
364 
365  // terminate if maximal number of iteration reached
368  return;
369  }
370 
371 
372  // perform line search in the direction search_direction
373  line_search(x, g, search_direction, x0_search, g0_search, maximal_step, d__dot__g, f);
374 
376  return;
377  }
378 
379 
380 
381  }
382 
383 
384 
385 
386 }
387 
388 
389 
390 
391 
392 
399 void Grad_Descend::get_Maximal_Line_Search_Step(Matrix_real& search_direction, double& maximal_step, double& search_direction__grad_overlap)
400 {
401 
402  // Set steps to constraint boundaries and find the least positive one.
403 
404 
405  maximal_step = 0.0;
406 
407  // the optimization landscape is periodic in 2PI
408  // the maximal step will be the 2PI step in the direction of the smallest component of the search direction
409  for( long kdx = 0; kdx < variable_num; kdx++ ) {
410 
411  // skip the current direction if it is too small
412  if ( fabs(search_direction[kdx]) < 1e-5 ) {
413  continue;
414  }
415 
416  double step_bound_tmp = std::abs(2*M_PI/search_direction[kdx]);
417 
418 
419 
420  if (maximal_step == 0.0 || step_bound_tmp < maximal_step) {
421 
422  maximal_step = step_bound_tmp;
423 
424  }
425 
426  }
427 
428  return;
429 
430 }
431 
432 
433 
434 
442 {
443 
446 
447  return;
448 }
449 
450 
451 
452 
459 void Grad_Descend::get_search_direction(Matrix_real& g, Matrix_real& search_direction, double& search_direction__grad_overlap )
460 {
461 
462 
463  // calculate the search direction d by d = -g
464  memset(search_direction.get_data(), 0, search_direction.size()*sizeof(double) );
465 
466  for (long row_idx = 0; row_idx < variable_num; row_idx++) {
467  search_direction[row_idx] = -g[row_idx];
468  }
469 
470 
471  // test variable to see downhill or uphill
472  search_direction__grad_overlap = 0.0;
473 
474  for (long idx = 0; idx < variable_num; idx++) {
475  search_direction__grad_overlap += search_direction[idx] * g[idx];
476  }
477 
478  return;
479 }
480 
481 
482 
487 
488 }
void * meta_data
additional data needed to evaluate the cost function
Definition: grad_descend.h:56
void get_f_ang_gradient(Matrix_real &x, double &f, Matrix_real &g)
Call this method to obtain the cost function and its gradient at a gives position given by x...
Matrix_real copy() const
Call to create a copy of the matrix.
void get_Maximal_Line_Search_Step(Matrix_real &search_direction, double &maximal_step, double &search_direction__grad_overlap)
Call this method to obtain the maximal step size during the line search.
long maximal_iterations
maximal count of iterations during the optimization
Definition: grad_descend.h:41
void line_search(Matrix_real &x, Matrix_real &g, Matrix_real &search_direction, Matrix_real &x0_search, Matrix_real &g0_search, double &maximal_step, double &d__dot__g, double &f)
Call to perform inexact line search terminated with Wolfe 1st and 2nd conditions. ...
scalar * get_data() const
Call to get the pointer to the stored data.
void(* export_fnc)(double, Matrix_real &, void *)
function pointer to evaluate the cost function and its gradient vector
Definition: grad_descend.h:53
double Start_Optimization(Matrix_real &x, long maximal_iterations_in=5001)
Call this method to start the optimization.
~Grad_Descend()
Destructor of the class.
#define M_PI
Definition: qgd_math.h:42
virtual void Optimize(Matrix_real &x, double &f)
Call this method to start the optimization process.
enum solver_status status
status of the solver
Definition: grad_descend.h:59
long function_call_count
number of function calls during the optimization process
Definition: grad_descend.h:44
int size() const
Call to get the number of the allocated elements.
Grad_Descend(void(*f_pointer)(Matrix_real, void *, double *, Matrix_real &), void *meta_data_in)
Constructor of the class.
int variable_num
number of independent variables in the problem
Definition: grad_descend.h:38
double num_precision
numerical precision used in the calculations
Definition: grad_descend.h:47
virtual void get_search_direction(Matrix_real &g, Matrix_real &search_direction, double &search_direction__grad_overlap)
Method to get the search direction in the next line search.
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
void(* costfnc__and__gradient)(Matrix_real x, void *params, double *f, Matrix_real &g)
function pointer to evaluate the cost function and its gradient vector
Definition: grad_descend.h:50