Sequential Quantum Gate Decomposer  v1.9.3
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 
61 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix Umtx_in, int qbit_num_in, int level_limit_in, std::map<std::string, Config_Element>& config, int accelerator_num ) : N_Qubit_Decomposition_Tree_Search( Umtx_in, qbit_num_in, level_limit_in, config, accelerator_num) {
62 
63 
64 }
65 
66 
67 
78 N_Qubit_Decomposition_Tabu_Search::N_Qubit_Decomposition_Tabu_Search( Matrix Umtx_in, int qbit_num_in, int level_limit_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, level_limit_in, topology_in, config, accelerator_num ) {
79 
80  // A string labeling the gate operation
81  name = "Tabu_Search";
82 
83 }
84 
89 
90 }
91 
92 
93 
94 
95 
100 Gates_block*
102 
103 
104  double optimization_tolerance_loc;
105  long long level_max;
106  if ( config.count("optimization_tolerance") > 0 ) {
107  config["optimization_tolerance"].get_property( optimization_tolerance_loc );
108 
109  }
110  else {
111  optimization_tolerance_loc = optimization_tolerance;
112  }
113 
114  if (config.count("tree_level_max") > 0 ){
115  std::cout << "lefut\n";
116  config["tree_level_max"].get_property( level_max );
117  }
118  else {
119  level_max = 14;
120  }
121 
122  level_limit = (int)level_max;
123 
124  GrayCode gcode_best_solution = tabu_search_over_gate_structures();
125 
126 
127  if (current_minimum > optimization_tolerance_loc) {
128  std::stringstream sstream;
129  sstream << "Decomposition did not reached prescribed high numerical precision." << std::endl;
130  print(sstream, 1);
131  }
132 
133  return construct_gate_structure_from_Gray_code( gcode_best_solution );
134 
135 }
136 
137 
138 
139 
144 GrayCode
146 
147 
148  double optimization_tolerance_loc;
149  if ( config.count("optimization_tolerance") > 0 ) {
150  config["optimization_tolerance"].get_property( optimization_tolerance_loc );
151  }
152  else {
153  optimization_tolerance_loc = optimization_tolerance;
154  }
155 
156  // set the limits for the N-ary Gray code
157  /*
158  int n_ary_limit_max = topology.size();
159  matrix_base<int> n_ary_limits( 1, levels ); //array containing the limits of the individual Gray code elements
160  memset( n_ary_limits.get_data(), n_ary_limit_max, n_ary_limits.size()*sizeof(int) );
161 
162  for( int idx=0; idx<n_ary_limits.size(); idx++) {
163  n_ary_limits[idx] = n_ary_limit_max;
164  }
165 
166 */
167  GrayCode gcode;
168 /*
169  // initiate Gray code to structure containing no CNOT gates
170  for( int idx=0; idx<gcode.size(); idx++ ) {
171  gcode[idx] = -1;
172  }
173 */
174 
175  GrayCode gcode_best_solution = gcode;
176 
177 
178  std::uniform_real_distribution<double> unif(0.0,1.0);
179  std::default_random_engine re;
180 
181  double inverz_temperature = 1.0;
182  std::vector<GrayCode> possible_gate_structures;
183 
184  while( true ) {
185 
186 
187 
188 
189 
190  Gates_block* gate_structure_loc = construct_gate_structure_from_Gray_code( gcode );
191 
192 
193  // ----------- start the decomposition -----------
194 
195  std::stringstream sstream;
196  sstream << "Starting optimization with " << gate_structure_loc->get_gate_num() << " decomposing layers." << std::endl;
197  print(sstream, 1);
198 
199  N_Qubit_Decomposition_custom&& cDecomp_custom_random = perform_optimization( gate_structure_loc );
200 
201  delete( gate_structure_loc );
202  gate_structure_loc = NULL;
203 
204 
205  number_of_iters += cDecomp_custom_random.get_num_iters(); // retrive the number of iterations spent on optimization
206 
207  double current_minimum_tmp = cDecomp_custom_random.get_current_minimum();
208  sstream.str("");
209  sstream << "Optimization with " << gcode.size() << " levels converged to " << current_minimum_tmp;
210  print(sstream, 1);
211 
212 /*
213  std::cout << current_minimum << " " << current_minimum_tmp << std::endl;
214  gcode.print_matrix();
215 */
216 
217 
218  tested_gate_structures.insert( gcode );
219 
220 
221 
222 
223 
224  if( current_minimum_tmp < current_minimum) {
225  // accept the current gate structure in tabu search
226 
227  current_minimum = current_minimum_tmp;
228  gcode_best_solution = gcode;
229  optimized_parameters_mtx = cDecomp_custom_random.get_optimized_parameters();
230 
231  possible_gate_structures.clear();
232  insert_into_best_solution( gcode, current_minimum_tmp );
233 
234  }
235  else {
236  // accept the current gate structure in tabu search with a given probability
237 
238  double random_double = unif(re);
239  double number_to_test = exp( -inverz_temperature*(current_minimum_tmp-current_minimum) );
240 
241  if( random_double < number_to_test ) {
242  //std::cout << "accepting worse solution " << current_minimum << " " << current_minimum_tmp << std::endl;
243  // accept the intermediate solution
244  current_minimum = current_minimum_tmp;
245  gcode_best_solution = gcode;
246  optimized_parameters_mtx = cDecomp_custom_random.get_optimized_parameters();
247 
248  possible_gate_structures.clear();
249  insert_into_best_solution( gcode, current_minimum_tmp );
250  }
251 
252  }
253 
254  if ( current_minimum < optimization_tolerance_loc ) {
255 //std::cout << "solution found" << std::endl;
256 //gcode_best_solution.print_matrix();
257  break;
258  }
259 
260 
261  if( possible_gate_structures.size() == 0 ) {
262  // determine possible gate structures that can be obtained with a single change (i.e. changing one two-qubit block)
263  possible_gate_structures = determine_mutated_structures( gcode_best_solution );
264 
265  }
266 
267 
268 
269  if( possible_gate_structures.size() == 0 ) {
270 
271  while( best_solutions.size() > 0 ) {
272 
273  auto pair = best_solutions[0];
274  best_solutions.erase( best_solutions.begin() );
275 
276  gcode_best_solution = std::get<0>(pair);
277  current_minimum = std::get<1>(pair);
278 
279  possible_gate_structures = determine_mutated_structures( gcode_best_solution );
280 
281  if( possible_gate_structures.size() > 0 || best_solutions.size() == 0 ) {
282  break;
283  }
284 
285  }
286 
287  }
288 
289  if ( possible_gate_structures.size() == 0 ) {
290  std::cout << "tttttttttttttttttttttttttttttt " << best_solutions.size() << std::endl;
291  break;
292  }
293 
294 /*
295  std::cout << "uuuuuuuuuuuuuuuuuuuuuuuu size:" << possible_gate_structures.size() << std::endl;
296  for( int idx=0; idx<possible_gate_structures.size(); idx++ ) {
297 
298  GrayCode& gcode = possible_gate_structures[ idx ];
299 
300  gcode.print_matrix();
301 
302  }
303 
304 std::cout << "uuuuuuuuuuuuuuuuuuuuuuuu 2" << std::endl;
305 */
306 //int levels_current = gcode.size();
307  gcode = draw_gate_structure_from_list( possible_gate_structures );
308 
309 /*
310 if ( levels_current < gcode.size() ) {
311 std::cout << " increasing the gate structure" << std::endl;
312 }
313 else if ( levels_current > gcode.size() ) {
314 std::cout << " decreasing the gate structure" << std::endl;
315 }
316  */
317 
318  }
319 
320  return gcode_best_solution;
321 
322 }
323 
324 
325 
331 void
333 
334 //std::cout << "N_Qubit_Decomposition_Tabu_Search::insert_into_best_solution a " << best_solutions.size() << " " << minimum_ << std::endl;
335 
336  if ( best_solutions.size() == 0 ) {
337  best_solutions.insert( best_solutions.begin(), std::make_pair(gcode_, minimum_) );
338  }
339 
340  for( auto it=best_solutions.begin(); it!=best_solutions.end(); it++ ) {
341 
342  double minimum = std::get<1>( *it );
343 
344  if( minimum > minimum_) {
345  best_solutions.insert( it, std::make_pair(gcode_, minimum_) );
346  break;
347  }
348 
349  }
350 
351  if( best_solutions.size() > 40 ) {
352  best_solutions.erase( best_solutions.end() - 1 );
353  }
354 
355 //std::cout << "N_Qubit_Decomposition_Tabu_Search::insert_into_best_solution b " << best_solutions.size() << std::endl;
356 
357 }
358 
359 
365 std::vector<GrayCode>
367 
368 
369  std::vector<GrayCode> possible_structures_list;
370  int n_ary_limit_max = topology.size();
371 /*
372  std::cout << "ooooooooooooo " << n_ary_limit_max << std::endl;
373  for( int idx=0; idx<topology.size(); idx++ ) {
374  topology[idx].print_matrix();
375  }
376 */
377 
378  // modify current two-qubit blocks
379  for( int gcode_idx=0; gcode_idx<gcode.size(); gcode_idx++ ) {
380 
381  for( int gcode_element=0; gcode_element<n_ary_limit_max; gcode_element++ ) {
382 
383  GrayCode gcode_modified = gcode.copy();
384  gcode_modified[gcode_idx] = gcode_element;
385 
386  // add the modified Gray code if not present in the list of visited gate structures
387  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
388  possible_structures_list.push_back( gcode_modified );
389  }
390 
391  }
392 
393 
394  }
395 
396  // generate structures with a less two-qubit blocks by one
397  for( int gcode_idx=0; gcode_idx<gcode.size(); gcode_idx++ ) {
398 
399  GrayCode&& gcode_modified = gcode.remove_Digit( gcode_idx );
400 
401  // add the modified Gray code if not present in the list of visited gate structures
402  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
403  possible_structures_list.push_back( gcode_modified );
404  }
405 
406  }
407 
408 
409  if ( gcode.size() == level_limit ) {
410  // dont add further two-qubit layer
411  return possible_structures_list;
412  }
413 
414 
415  // generates structure with an extra two-qubit block
416  GrayCode&& gcode_extended = gcode.add_Digit( n_ary_limit_max );
417 
418  for( int gcode_element=0; gcode_element<n_ary_limit_max; gcode_element++ ) {
419 
420  GrayCode gcode_modified = gcode_extended.copy();
421  gcode_modified[ gcode_extended.size()-1 ] = gcode_element;
422 
423  // add the modified Gray code if not present in the list of visited gate structures
424  if( tested_gate_structures.count( gcode_modified ) == 0 ) {
425  possible_structures_list.push_back( gcode_modified );
426  }
427 
428  }
429 
430  return possible_structures_list;
431 
432 }
433 
434 
435 
441 GrayCode
443 
444  if ( gcodes.size() == 0 ) {
445  std::string err("N_Qubit_Decomposition_Tabu_Search::draw_gate_structure_from_list: The list of gates structure is empty." );
446  throw( err );
447  }
448 
449  GrayCode gcode = gcodes[0];
450 
451  int levels = gcode.size();
452 
453  // the probability distribution is weighted by the number of two-qubit gates in the gate structure
454  // the probability weights should be smaller if containing more two-qubit gates
455  matrix_base<int> weights( gcodes.size(), 1 );
456 
457  int fact = 4;
458 
459  for( int gcode_idx=0; gcode_idx<gcodes.size(); gcode_idx++ ) {
460 
461  gcode = gcodes[ gcode_idx ];
462  weights[ gcode_idx ] = fact*(levels);
463 
464  for( int gcode_element_idx=0; gcode_element_idx<gcode.size(); gcode_element_idx++ ) {
465  if( gcode[gcode_element_idx] > -1 ) {
466  weights[ gcode_idx ] = weights[ gcode_idx ] - fact;
467  }
468  }
469 
470  }
471 
472 /*
473  std::cout << "weights" << std::endl;
474  weights.print_matrix();
475 */
476  // calculate the sum of weights to normalize for the probability distribution
477  int weight_sum = 0;
478  for( int idx=0; idx<weights.size(); idx++ ) {
479  weight_sum = weight_sum + weights[idx];
480  }
481 
482 
483  std::random_device dev;
484  std::mt19937 rng(dev());
485  std::uniform_int_distribution<std::mt19937::result_type> dist(0,weight_sum); // distribution in range [0, weight_sum]
486 
487  int random_num = dist(rng);
488  int weight_sum_partial = 0;
489  int chosen_idx = 0;
490  for( int idx=0; idx<weights.size(); idx++ ) {
491 
492  weight_sum_partial = weight_sum_partial + weights[idx];
493 
494  if( random_num < weight_sum_partial ) {
495  chosen_idx = idx;
496  break;
497  }
498 
499  }
500 
501 
502 
503  GrayCode chosen_gcode = gcodes[ chosen_idx ];
504  gcodes.erase( gcodes.begin() + chosen_idx );
505 
506  return chosen_gcode;
507 
508 }
509 
510 
511 
512 
513 
514 
Gates_block * determine_gate_structure(Matrix_real &optimized_parameters_mtx)
Call determine the gate structrue of the decomposing circuit.
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
GrayCode draw_gate_structure_from_list(std::vector< GrayCode > &gcodes)
Call to sample a gate structure from a list of gate structures to test in the optimization process...
Copyright 2021 Budapest Quantum Computing Group.
int get_num_iters()
Get the number of processed iterations during the optimization process.
Header file for a class representing the X gate.
std::vector< GrayCode > determine_mutated_structures(const GrayCode &gcode)
Call to generate a list of mutated gate structures.
double current_minimum
The current minimum of the optimization problem.
std::vector< std::pair< GrayCode, double > > best_solutions
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.
N_Qubit_Decomposition_custom perform_optimization(Gates_block *gate_structure_loc)
Call to perform the optimization on the given gate structure.
GrayCode tabu_search_over_gate_structures()
Perform tabu serach over gate structures.
int level_limit
The maximal number of adaptive layers used in the decomposition.
int get_gate_num()
Call to get the number of gates grouped in the class.
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.
int number_of_iters
number of iterations
Matrix_real get_optimized_parameters()
Call to get the optimized parameters.
virtual ~N_Qubit_Decomposition_Tabu_Search()
Destructor of the class.
void insert_into_best_solution(const GrayCode &gcode_, double minimum_)
Call to store a given solution among the best ones.
GrayCode_base add_Digit(const intType n_ary_limit) const
Call to add a new digit to the Gray code.
Class to store data of complex arrays and its properties.
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:41
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:79
std::map< std::string, Config_Element > config
config metadata utilized during the optimization
std::unordered_set< GrayCode, GrayCodeHash > tested_gate_structures
the set of already examined gate structures (mapped to n-ary Gray codes)
Header file for the paralleized calculation of the cost function of the final optimization problem (s...
Header file for DFE support in unitary simulation.
Matrix_real optimized_parameters_mtx
The optimized parameters for the gates.
Gates_block * construct_gate_structure_from_Gray_code(const GrayCode &gcode)
Call to construct a gate structure corresponding to the configuration of the two-qubit gates describe...
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:39