Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
N_Qubit_Decomposition_Tabu_Search.cpp
Go to the documentation of this file.
1 /*
2 Created on Fri Jun 26 14:13:26 2020
3 Copyright 2020 Peter Rakyta, Ph.D.
4 
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8 
9  http://www.apache.org/licenses/LICENSE-2.0
10 
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 
17 @author: Peter Rakyta, Ph.D.
18 */
25 #include "Random_Orthogonal.h"
26 #include "Random_Unitary.h"
27 #include "n_aryGrayCodeCounter.h"
28 #include <random>
29 
30 #include "X.h"
31 
32 #include <time.h>
33 #include <stdlib.h>
34 
35 #include <iostream>
36 
37 #ifdef __DFE__
38 #include "common_DFE.h"
39 #endif
40 
41 
42 
48 
49 
50 }
51 
60 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix Umtx_in, int qbit_num_in, std::map<std::string, Config_Element>& config, int accelerator_num ) : N_Qubit_Decomposition_Tree_Search( Umtx_in, qbit_num_in, config, accelerator_num) {
61 
62 
63 }
64 
68 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix_float Umtx_in, int qbit_num_in, std::map<std::string, Config_Element>& config, int accelerator_num ) : N_Qubit_Decomposition_Tree_Search( Umtx_in, qbit_num_in, config, accelerator_num) {
69 
70 
71 }
72 
73 
74 
84 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix Umtx_in, int qbit_num_in, std::vector<matrix_base<int>> topology_in, std::map<std::string, Config_Element>& config, int accelerator_num ) : N_Qubit_Decomposition_Tree_Search( Umtx_in, qbit_num_in, topology_in, config, accelerator_num ) {
85 
86  // A string labeling the gate operation
87  name = "Tabu_Search";
88 
89 }
90 
94 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix_float Umtx_in, int qbit_num_in, std::vector<matrix_base<int>> topology_in, std::map<std::string, Config_Element>& config, int accelerator_num ) : N_Qubit_Decomposition_Tree_Search( Umtx_in, qbit_num_in, topology_in, config, accelerator_num ) {
95 
96  // A string labeling the gate operation
97  name = "Tabu_Search";
98 
99 }
100 
105 
106 }
107 
108 
109 
110 
111 
116 Gates_block*
118 
119 
120  double optimization_tolerance_loc;
121  long long level_max;
122  if ( config.count("optimization_tolerance") > 0 ) {
123  config["optimization_tolerance"].get_property( optimization_tolerance_loc );
124 
125  }
126  else {
127  optimization_tolerance_loc = optimization_tolerance;
128  }
129 
130  if (config.count("tree_level_max") > 0 ){
131  config["tree_level_max"].get_property( level_max );
132  }
133  else {
134  level_max = 14;
135  }
136 
137  level_limit = (int)level_max;
138 
139  GrayCodeCNOT gcode_best_solution = tabu_search_over_gate_structures();
140 
141 
142  if (current_minimum > optimization_tolerance_loc) {
143  std::stringstream sstream;
144  sstream << "Decomposition did not reached prescribed high numerical precision." << std::endl;
145  print(sstream, 1);
146  }
147 
148  return construct_gate_structure_from_Gray_code( gcode_best_solution );
149 
150 }
151 
152 
153 
154 
161 
162 
163  double optimization_tolerance_loc;
164  if ( config.count("optimization_tolerance") > 0 ) {
165  config["optimization_tolerance"].get_property( optimization_tolerance_loc );
166  }
167  else {
168  optimization_tolerance_loc = optimization_tolerance;
169  }
170 
171  // set the limits for the N-ary Gray code
172  /*
173  int n_ary_limit_max = topology.size();
174  matrix_base<int> n_ary_limits( 1, levels ); //array containing the limits of the individual Gray code elements
175  memset( n_ary_limits.get_data(), n_ary_limit_max, n_ary_limits.size()*sizeof(int) );
176 
177  for( int idx=0; idx<n_ary_limits.size(); idx++) {
178  n_ary_limits[idx] = n_ary_limit_max;
179  }
180 
181 */
182  GrayCodeCNOT gcode;
183 /*
184  // initiate Gray code to structure containing no CNOT gates
185  for( int idx=0; idx<gcode.size(); idx++ ) {
186  gcode[idx] = -1;
187  }
188 */
189 
190  GrayCodeCNOT gcode_best_solution = gcode;
191 
192 
193  std::uniform_real_distribution<double> unif(0.0,1.0);
194  std::default_random_engine re;
195 
196  std::vector<GrayCodeCNOT> possible_gate_structures;
197 
198  long long use_osr = 0;
199  if (config.count("use_osr") > 0) {
200  config["use_osr"].get_property(use_osr);
201  }
202  if (qbit_num <= 1) use_osr = 0;
203  double Fnorm = std::sqrt(static_cast<double>(1 << qbit_num));
204  double osr_tol = 1e-3;
205  std::vector<std::vector<int>> all_cuts = unique_cuts(qbit_num);
206  std::uniform_real_distribution<> distrib_real(0.0, 2 * M_PI);
207  MinCnotBoundSolver osr_bound_solver(qbit_num, all_cuts, topology);
208  double inverz_temperature = use_osr ? 3.0 : 1.0;
209 
210  while( true ) {
211 
212 
213 
214 
215 
216  Gates_block* gate_structure_loc = construct_gate_structure_from_Gray_code( gcode, !use_osr );
217 
218 
219  // ----------- start the decomposition -----------
220 
221  std::stringstream sstream;
222  sstream << "Starting optimization with " << gate_structure_loc->get_gate_num() << " decomposing layers." << std::endl;
223  print(sstream, 1);
224 
225  N_Qubit_Decomposition_custom&& cDecomp_custom_random = perform_optimization( use_osr ? nullptr : gate_structure_loc );
226  double current_minimum_tmp = cDecomp_custom_random.get_current_minimum();
227  if (use_osr) {
228  cDecomp_custom_random.set_cost_function_variant(OSR_ENTANGLEMENT);
229  SearchNode sn = evaluate_path(cDecomp_custom_random, osr_bound_solver, all_cuts, Fnorm, osr_tol, distrib_real, gen, gcode);
230  const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& best_osr_result = sn.get_best_osr_result();
231  int cnot_lower_bound = std::get<0>(best_osr_result);
232  current_minimum_tmp = cnot_lower_bound + std::get<1>(best_osr_result) / (qbit_num + (qbit_num >= 4 ? 2 : 0));
233 
234  if (cnot_lower_bound == 0) {
235  double current_inner_min;
236  Gates_block* gate_structure_loc = construct_gate_structure_from_Gray_code(gcode);
237  for (int iter = 0; iter < 5; iter++) {
238  N_Qubit_Decomposition_custom&& cDecomp_custom_random = perform_optimization(gate_structure_loc );
239  current_inner_min = cDecomp_custom_random.get_current_minimum();
240  if ( current_inner_min < optimization_tolerance_loc ) {
241  current_minimum = current_inner_min;
242  optimized_parameters_mtx = cDecomp_custom_random.get_optimized_parameters().copy();
244  gcode_best_solution = gcode;
245  break;
246  }
247  }
248  delete( gate_structure_loc );
249  gate_structure_loc = NULL;
250  if ( current_inner_min < optimization_tolerance_loc ) {
251  break;
252  }
253  }
254  }
255 
256  delete( gate_structure_loc );
257  gate_structure_loc = NULL;
258 
259 
260  increment_num_iters(cDecomp_custom_random.get_num_iters()); // retrive the number of iterations spent on optimization
261 
262  sstream.str("");
263  sstream << "Optimization with " << gcode.size() << " levels converged to " << current_minimum_tmp;
264  print(sstream, 1);
265 
266 /*
267  std::cout << current_minimum << " " << current_minimum_tmp << std::endl;
268  gcode.print_matrix();
269 */
270 
271 
272  tested_gate_structures.insert( gcode );
273 
274 
275 
276 
277 
278  if( current_minimum_tmp < current_minimum) {
279  // accept the current gate structure in tabu search
280 
281  current_minimum = current_minimum_tmp;
282  gcode_best_solution = gcode;
283  optimized_parameters_mtx = cDecomp_custom_random.get_optimized_parameters();
285 
286  possible_gate_structures.clear();
287  insert_into_best_solution( gcode, current_minimum_tmp );
288 
289  }
290  else {
291  // accept the current gate structure in tabu search with a given probability
292 
293  double random_double = unif(re);
294  double number_to_test = exp( -inverz_temperature*(current_minimum_tmp-current_minimum) );
295 
296  if( random_double < number_to_test ) {
297  //std::cout << "accepting worse solution " << current_minimum << " " << current_minimum_tmp << std::endl;
298  // accept the intermediate solution
299  current_minimum = current_minimum_tmp;
300  gcode_best_solution = gcode;
301  optimized_parameters_mtx = cDecomp_custom_random.get_optimized_parameters();
303 
304  possible_gate_structures.clear();
305  insert_into_best_solution( gcode, current_minimum_tmp );
306  }
307 
308  }
309 
310  if ( current_minimum < optimization_tolerance_loc ) {
311 //std::cout << "solution found" << std::endl;
312 //gcode_best_solution.print_matrix();
313  break;
314  }
315 
316 
317  if( possible_gate_structures.size() == 0 ) {
318  // determine possible gate structures that can be obtained with a single change (i.e. changing one two-qubit block)
319  possible_gate_structures = determine_mutated_structures( gcode_best_solution );
320 
321  }
322 
323 
324 
325  if( possible_gate_structures.size() == 0 ) {
326 
327  while( best_solutions.size() > 0 ) {
328 
329  std::pair<GrayCodeCNOT, double> pair = best_solutions[0];
330  best_solutions.erase( best_solutions.begin() );
331 
332  gcode_best_solution = std::get<0>(pair);
333  current_minimum = std::get<1>(pair);
334 
335  possible_gate_structures = determine_mutated_structures( gcode_best_solution );
336 
337  if( possible_gate_structures.size() > 0 || best_solutions.size() == 0 ) {
338  break;
339  }
340 
341  }
342 
343  }
344 
345  if ( possible_gate_structures.size() == 0 ) {
346  //std::cout << "tttttttttttttttttttttttttttttt " << best_solutions.size() << std::endl;
347  break;
348  }
349 
350 /*
351  std::cout << "uuuuuuuuuuuuuuuuuuuuuuuu size:" << possible_gate_structures.size() << std::endl;
352  for( int idx=0; idx<possible_gate_structures.size(); idx++ ) {
353 
354  GrayCodeCNOT& gcode = possible_gate_structures[ idx ];
355 
356  gcode.print_matrix();
357 
358  }
359 
360 std::cout << "uuuuuuuuuuuuuuuuuuuuuuuu 2" << std::endl;
361 */
362 //int levels_current = gcode.size();
363  gcode = draw_gate_structure_from_list( possible_gate_structures );
364 
365 /*
366 if ( levels_current < gcode.size() ) {
367 std::cout << " increasing the gate structure" << std::endl;
368 }
369 else if ( levels_current > gcode.size() ) {
370 std::cout << " decreasing the gate structure" << std::endl;
371 }
372  */
373 
374  }
375 
376  return gcode_best_solution;
377 
378 }
379 
380 
381 
387 void
389 
390 //std::cout << "N_Qubit_Decomposition_Tabu_Search::insert_into_best_solution a " << best_solutions.size() << " " << minimum_ << std::endl;
391 
392  if ( best_solutions.size() == 0 ) {
393  best_solutions.insert( best_solutions.begin(), std::make_pair(gcode_, minimum_) );
394  }
395 
396  for( std::vector<std::pair<GrayCodeCNOT, double>>::iterator it=best_solutions.begin(); it!=best_solutions.end(); it++ ) {
397 
398  double minimum = std::get<1>( *it );
399 
400  if( minimum > minimum_) {
401  best_solutions.insert( it, std::make_pair(gcode_, minimum_) );
402  break;
403  }
404 
405  }
406 
407  if( best_solutions.size() > 40 ) {
408  best_solutions.erase( best_solutions.end() - 1 );
409  }
410 
411 //std::cout << "N_Qubit_Decomposition_Tabu_Search::insert_into_best_solution b " << best_solutions.size() << std::endl;
412 
413 }
414 
415 
421 std::vector<GrayCodeCNOT>
423 
424 
425  std::vector<GrayCodeCNOT> possible_structures_list;
426  int n_ary_limit_max = static_cast<int>(topology.size());
427 /*
428  std::cout << "ooooooooooooo " << n_ary_limit_max << std::endl;
429  for( int idx=0; idx<topology.size(); idx++ ) {
430  topology[idx].print_matrix();
431  }
432 */
433 
434  // modify current two-qubit blocks
435  for( int gcode_idx=0; gcode_idx<gcode.size(); gcode_idx++ ) {
436 
437  for( int gcode_element=0; gcode_element<n_ary_limit_max; gcode_element++ ) {
438 
439  GrayCodeCNOT gcode_modified = gcode.copy();
440  gcode_modified[gcode_idx] = gcode_element;
441 
442  // add the modified Gray code if not present in the list of visited gate structures
443  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
444  possible_structures_list.push_back( gcode_modified );
445  }
446 
447  }
448 
449 
450  }
451 
452  // generate structures with a less two-qubit blocks by one
453  for( int gcode_idx=0; gcode_idx<gcode.size(); gcode_idx++ ) {
454 
455  GrayCodeCNOT&& gcode_modified = gcode.remove_Digit( gcode_idx );
456 
457  // add the modified Gray code if not present in the list of visited gate structures
458  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
459  possible_structures_list.push_back( gcode_modified );
460  }
461 
462  }
463 
464 
465  if ( gcode.size() == level_limit ) {
466  // dont add further two-qubit layer
467  return possible_structures_list;
468  }
469 
470 
471  // generates structure with an extra two-qubit block
472  GrayCodeCNOT&& gcode_extended = gcode.add_Digit( n_ary_limit_max );
473 
474  for( int gcode_element=0; gcode_element<n_ary_limit_max; gcode_element++ ) {
475 
476  GrayCodeCNOT gcode_modified = gcode_extended.copy();
477  gcode_modified[ gcode_extended.size()-1 ] = gcode_element;
478 
479  // add the modified Gray code if not present in the list of visited gate structures
480  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
481  possible_structures_list.push_back( gcode_modified );
482  }
483 
484  }
485 
486  return possible_structures_list;
487 
488 }
489 
490 
491 
499 
500  if ( gcodes.size() == 0 ) {
501  std::string err("N_Qubit_Decomposition_Tabu_Search::draw_gate_structure_from_list: The list of gates structure is empty." );
502  throw( err );
503  }
504 
505  GrayCodeCNOT gcode = gcodes[0];
506 
507  int levels = gcode.size();
508 
509  // the probability distribution is weighted by the number of two-qubit gates in the gate structure
510  // the probability weights should be smaller if containing more two-qubit gates
511  matrix_base<int> weights( static_cast<int>(gcodes.size()), 1 );
512 
513  int fact = 4;
514 
515  for( size_t gcode_idx=0; gcode_idx<gcodes.size(); gcode_idx++ ) {
516 
517  gcode = gcodes[ gcode_idx ];
518  weights[ static_cast<int>(gcode_idx) ] = fact*(levels);
519 
520  for( int gcode_element_idx=0; gcode_element_idx<gcode.size(); gcode_element_idx++ ) {
521  if( gcode[gcode_element_idx] > -1 ) {
522  weights[ static_cast<int>(gcode_idx) ] = weights[ static_cast<int>(gcode_idx) ] - fact;
523  }
524  }
525 
526  }
527 
528 /*
529  std::cout << "weights" << std::endl;
530  weights.print_matrix();
531 */
532  // calculate the sum of weights to normalize for the probability distribution
533  int weight_sum = 0;
534  for( int idx=0; idx<weights.size(); idx++ ) {
535  weight_sum = weight_sum + weights[idx];
536  }
537 
538 
539  std::random_device dev;
540  std::mt19937 rng(dev());
541  std::uniform_int_distribution<std::mt19937::result_type> dist(0,weight_sum); // distribution in range [0, weight_sum]
542 
543  int random_num = dist(rng);
544  int weight_sum_partial = 0;
545  int chosen_idx = 0;
546  for( int idx=0; idx<weights.size(); idx++ ) {
547 
548  weight_sum_partial = weight_sum_partial + weights[idx];
549 
550  if( random_num < weight_sum_partial ) {
551  chosen_idx = idx;
552  break;
553  }
554 
555  }
556 
557 
558 
559  GrayCodeCNOT chosen_gcode = gcodes[ chosen_idx ];
560  gcodes.erase( gcodes.begin() + chosen_idx );
561 
562  return chosen_gcode;
563 
564 }
565 
566 
567 
568 
Gates_block * determine_gate_structure(Matrix_real &optimized_parameters_mtx)
Call determine the gate structrue of the decomposing circuit.
void insert_into_best_solution(const GrayCodeCNOT &gcode_, double minimum_)
Call to store a given solution among the best ones.
void print(const std::stringstream &sstream, int verbose_level=1) const
Call to print output messages in the function of the verbosity level.
Definition: logging.cpp:55
Copyright 2021 Budapest Quantum Computing Group.
std::vector< std::vector< int > > unique_cuts(int n)
int get_num_iters()
Get the number of processed iterations during the optimization process.
std::vector< std::pair< GrayCodeCNOT, double > > best_solutions
Matrix_real copy() const
Call to create a copy of the matrix.
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
int levels
[creating decomp class]
double get_current_minimum()
Call to get the obtained minimum of the cost function.
N_Qubit_Decomposition_Tabu_Search()
Nullary constructor of the class.
void increment_num_iters(int delta=1)
Atomically increment the tracked number of optimization iterations.
N_Qubit_Decomposition_custom perform_optimization(Gates_block *gate_structure_loc)
Call to perform the optimization on the given gate structure.
int level_limit
The maximal number of adaptive layers used in the decomposition.
void sync_optimized_parameters_float()
Synchronize the float32 parameter mirror from the double optimizer storage.
int get_gate_num()
Call to get the number of gates grouped in the class.
GrayCodeCNOT draw_gate_structure_from_list(std::vector< GrayCodeCNOT > &gcodes)
Call to sample a gate structure from a list of gate structures to test in the optimization process...
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
double optimization_tolerance
The maximal allowed error of the optimization problem (The error of the decomposition would scale wit...
GrayCode_base remove_Digit(const int idx) const
Call to add a new digit to the Gray code.
int accelerator_num
number of utilized accelerators
std::vector< matrix_base< int > > topology
A vector of index pairs encoding the connectivity between the qubits.
#define M_PI
Definition: qgd_math.h:42
Matrix_real get_optimized_parameters()
Call to get the optimized parameters.
virtual ~N_Qubit_Decomposition_Tabu_Search()
Destructor of the class.
GrayCode_base add_Digit(const intType n_ary_limit) const
Call to add a new digit to the Gray code.
SearchNode evaluate_path(N_Qubit_Decomposition_custom &cDecomp_custom_random, MinCnotBoundSolver &osr_bound_solver, std::vector< std::vector< int >> &all_cuts, double Fnorm, double osr_tol, std::uniform_real_distribution<> &distrib_real, std::mt19937 &gen, const GrayCodeCNOT &path)
std::vector< GrayCodeCNOT > determine_mutated_structures(const GrayCodeCNOT &gcode)
Call to generate a list of mutated gate structures.
Double-precision complex matrix (float64).
Definition: matrix.h:38
int size() const
Call to get the number of the allocated elements.
GrayCode_base copy() const
Call to create a copy of the state.
A class responsible for grouping two-qubit (CNOT,CZ,CH) and one-qubit gates into layers.
Definition: Gates_block.h:44
Header file for a class implementing the adaptive gate decomposition algorithm of arXiv:2203...
std::string name
A string labeling the gate operation.
Definition: Gate.h:92
Single-precision complex matrix (float32).
Definition: matrix_float.h:41
std::map< std::string, Config_Element > config
config metadata utilized during the optimization
Gates_block * construct_gate_structure_from_Gray_code(const GrayCodeCNOT &gcode, bool finalize=true)
Call to construct a gate structure corresponding to the configuration of the two-qubit gates describe...
std::unordered_set< GrayCodeCNOT, GrayCodeHashCNOT > tested_gate_structures
the set of already examined gate structures (mapped to n-ary Gray codes)
const std::tuple< int, double, std::vector< int >, std::vector< std::pair< int, double > > > & get_best_osr_result() const
Header file for the paralleized calculation of the cost function of the final optimization problem (s...
volatile double current_minimum
The current minimum of the optimization problem.
int qbit_num
number of qubits spanning the matrix of the operation
Definition: Gate.h:94
Header file for DFE support in unitary simulation.
Matrix_real optimized_parameters_mtx
The optimized parameters for the gates.
void set_cost_function_variant(cost_function_type variant)
Call to set the variant of the cost function used in the calculations.
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
std::mt19937 gen
Standard mersenne_twister_engine seeded with rd()
GrayCodeCNOT tabu_search_over_gate_structures()
Perform tabu serach over gate structures.