Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
Sub_Matrix_Decomposition.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 
26 #include "BFGS_Powell.h"
27 
28 #include <tbb/enumerable_thread_specific.h>
29 
30 
31 #ifdef __MPI__
32 #include <mpi.h>
33 #endif // MPI
34 
35 //tbb::spin_mutex my_mutex;
36 
42 
43  // logical value. Set true if finding the minimum number of gate layers is required (default), or false when the maximal number of CNOT gates is used (ideal for general unitaries).
44  optimize_layer_num = false;
45 
46  // A string describing the type of the class
48 
49  // The global minimum of the optimization problem
51 
52  // logical value indicating whether the quasi-unitarization of the submatrices was done or not
53  subdisentaglement_done = false;
54 
55  // custom gate structure used in the decomposition
56  unit_gate_structure = NULL;
57 
59 
60 }
61 
62 
71 Sub_Matrix_Decomposition::Sub_Matrix_Decomposition( Matrix Umtx_in, int qbit_num_in, bool optimize_layer_num_in, std::map<std::string, Config_Element>& config_in, guess_type initial_guess_in= CLOSE_TO_ZERO ) : Decomposition_Base(Umtx_in, qbit_num_in, config_in, initial_guess_in) {
72 
73  // logical value. Set true if finding the minimum number of gate layers is required (default), or false when the maximal number of CNOT gates is used (ideal for general unitaries).
74  optimize_layer_num = optimize_layer_num_in;
75 
76  // A string describing the type of the class
78 
79  // The global minimum of the optimization problem
81 
82  // number of iteratrion loops in the optimization
83  iteration_loops[2] = 3;
84 
85  // logical value indicating whether the quasi-unitarization of the submatrices was done or not
86  subdisentaglement_done = false;
87 
88  // filling in numbers that were not given in the input
89  for ( std::map<int,int>::iterator it = max_layer_num_def.begin(); it!=max_layer_num_def.end(); it++) {
90  if ( max_layer_num.count( it->first ) == 0 ) {
91  max_layer_num.insert( std::pair<int, int>(it->first, it->second) );
92  }
93  }
94 
95  // custom gate structure used in the decomposition
96  unit_gate_structure = NULL;
97 
99 
100 
101 }
102 
103 
108 
109  if ( unit_gate_structure != NULL ) {
110  delete unit_gate_structure;
111  }
112  unit_gate_structure = NULL;
113 
114 }
115 
116 
117 
118 
119 
120 
125 
126  if (subdisentaglement_done) {
127  std::stringstream sstream;
128  sstream << "Sub-disentaglement already done." << std::endl;
129  print(sstream, 2);
130  return;
131  }
132 
133  std::stringstream sstream;
134  sstream << std::endl << "Disentagling submatrices." << std::endl;
135  print(sstream, 2);
136 
137 
138  // setting the global target minimum
141 
142  // check if it needed to do the subunitarization
144  std::stringstream sstream;
145  sstream << "Disentanglig not needed" << std::endl;
146  print(sstream, 2);
148  subdisentaglement_done = true;
149  return;
150  }
151 
152 
153  if ( !check_optimization_solution() ) {
154  // Adding the gates of the successive layers
155 
156  //measure the time for the decompositin
157  tbb::tick_count start_time = tbb::tick_count::now();
158 
159  // the maximal number of layers in the subdeconposition
160  int max_layer_num_loc;
161  try {
162  max_layer_num_loc = max_layer_num[qbit_num];
163  }
164  catch (...) {
165  std::stringstream sstream;
166  sstream << "Layer number not given" << std::endl;
167  print(sstream, 0);
168  throw sstream.str();
169  }
170 
171  while ( layer_num < max_layer_num_loc ) {
172 
173  // add another gate layers to the gate structure used in the decomposition
174  add_gate_layers();
175 
176  // get the number of blocks
177  layer_num = static_cast<int>(gates.size());
178 
179  // Do the optimization
180  if (optimize_layer_num || layer_num >= max_layer_num_loc ) {
181 
182  // solve the optimization problem to find the correct minimum
183  solve_optimization_problem( NULL, 0);
184 
186  break;
187  }
188  }
189 
190  }
191 
192 
193  tbb::tick_count current_time = tbb::tick_count::now();
194 
195  std::stringstream sstream;
196  sstream << "--- " << (current_time - start_time).seconds() << " seconds elapsed during the decomposition ---" << std::endl << std::endl;
197  print(sstream, 2);
198 
199  }
200 
201 
202 
204  std::stringstream sstream;
205  sstream << "Sub-disentaglement was succesfull." << std::endl << std::endl;
206  print(sstream, 1);
207  }
208  else {
209  std::stringstream sstream;
210  sstream << "Sub-disentaglement did not reach the tolerance limit." << std::endl << std::endl;
211  print(sstream, 1);
212  }
213 
214 
215  // indicate that the unitarization of the sumbatrices was done
216  subdisentaglement_done = true;
217 
218  // The subunitarized matrix
221 
222 }
223 
228 
229  if ( unit_gate_structure == NULL ) {
230  // add default gate structure if custom is not given
232  return;
233  }
234 
236  // add custom gate structure
237 
238  // the number of succeeding identical layers in the subdecomposition
239  int identical_blocks_loc;
240  try {
241  identical_blocks_loc = identical_blocks[qbit_num];
242  if (identical_blocks_loc==0) {
243  identical_blocks_loc = 1;
244  }
245  }
246  catch (...) {
247  identical_blocks_loc=1;
248  }
249 
250  // get the list of sub-blocks in the custom gate structure
251  std::vector<Gate*> gates = unit_gate_structure->get_gates();
252 
253  for (std::vector<Gate*>::iterator gates_it = gates.begin(); gates_it != gates.end(); gates_it++ ) {
254 
255  // The current gate
256  Gate* gate = *gates_it;
257 
258  for (int idx=0; idx<identical_blocks_loc; idx++) {
259  add_gate( (Gate*)(gate->clone()) );
260  }
261 
262  }
263 
264 
265 }
266 
267 
268 
273 
274  int control_qbit_loc = qbit_num-1;
275 
276  // the number of succeeding identical layers in the subdecomposition
277  int identical_blocks_loc;
278  try {
279  identical_blocks_loc = identical_blocks[qbit_num];
280  if (identical_blocks_loc==0) {
281  identical_blocks_loc = 1;
282  }
283  }
284  catch (...) {
285  identical_blocks_loc=1;
286  }
287 
288 
289  for (int target_qbit_loc = 0; target_qbit_loc<control_qbit_loc; target_qbit_loc++ ) {
290 
291  for (int idx=0; idx<identical_blocks_loc; idx++) {
292 
293  // creating block of gates
294  Gates_block* block = new Gates_block( qbit_num );
295 
296  // adding U3 gate to the block
297  block->add_u3(target_qbit_loc);
298  block->add_u3(control_qbit_loc);
299 
300 
301  // add CNOT gate to the block
302  block->add_cnot(target_qbit_loc, control_qbit_loc);
303 
304  // adding the opeartion block to the gates
305  add_gate( block );
306 
307  }
308  }
309 
310 
311 }
312 
313 
314 
320 
321  unit_gate_structure = block_in->clone();
322 
323 }
324 
325 
326 
327 
328 
335 
336 
337  if (gates.size() == 0 ) {
338  return;
339  }
340 
341 
342  if (solution_guess.size() == 0 ) {
343  solution_guess = Matrix_real(num_of_parameters,1);
344  }
345 
346 
347  if (optimized_parameters_mtx.size() == 0) {
348  optimized_parameters_mtx = Matrix_real(1, num_of_parameters);
349  memcpy(optimized_parameters_mtx.get_data(), solution_guess.get_data(), num_of_parameters*sizeof(double) );
350  }
351 
352 
353  // maximal number of iteration loops
354  int iteration_loops_max;
355  try {
356  iteration_loops_max = std::max(iteration_loops[qbit_num], 1);
357  }
358  catch (...) {
359  iteration_loops_max = 1;
360  }
361 
362  // maximal number of inner iterations overriden by config
363  long long max_inner_iterations_loc;
364  if ( config.count("max_inner_iterations") > 0 ) {
365  config["max_inner_iterations"].get_property( max_inner_iterations_loc );
366  }
367  else {
368  max_inner_iterations_loc =max_inner_iterations;
369  }
370 
371  // random generator of real numbers
372  std::uniform_real_distribution<> distrib_real(0.0, 2*M_PI);
373 
374  // do the optimization loops
375  for (long long idx=0; idx<iteration_loops_max; idx++) {
376 
377  BFGS_Powell cBFGS_Powell(optimization_problem_combined, this);
378  double f = cBFGS_Powell.Start_Optimization(solution_guess, static_cast<long>(max_inner_iterations));
379 
380 
381  if (current_minimum > f) {
382  current_minimum = f;
383  memcpy( optimized_parameters_mtx.get_data(), solution_guess.get_data(), num_of_parameters*sizeof(double) );
384  for ( int jdx=0; jdx<num_of_parameters; jdx++) {
385  solution_guess[jdx] = solution_guess[jdx] + distrib_real(gen)/100;
386  }
387  }
388  else {
389  for ( int jdx=0; jdx<num_of_parameters; jdx++) {
390  solution_guess[jdx] = solution_guess[jdx] + distrib_real(gen);
391  }
392  }
393 
394 #ifdef __MPI__
395  MPI_Bcast( (void*)solution_guess.get_data(), num_of_parameters, MPI_DOUBLE, 0, MPI_COMM_WORLD);
396 #endif
397 
398 
399 
400  }
401 
402 
403 }
404 
405 
406 
407 
414 
415  // get the transformed matrix with the gates in the list
416  Matrix_real parameters_mtx(parameters, 1, parameter_num );
417  static tbb::enumerable_thread_specific<Matrix> matrix_new_tls;
418  Matrix& matrix_new = matrix_new_tls.local();
419  Umtx.copy_to(matrix_new);
420  apply_to( parameters_mtx, matrix_new );
421 
422 #ifdef DEBUG
423  if (matrix_new.isnan()) {
424  std::stringstream sstream;
425  sstream << "Sub_Matrix_Decomposition::optimization_problem: matrix_new contains NaN a. Exiting" << std::endl;
426  print(sstream, 1);
427  }
428 #endif
429 
430  double cost_function = get_submatrix_cost_function(matrix_new);
431 
432 
433  return cost_function;
434 }
435 
436 
444 
445  Sub_Matrix_Decomposition* instance = reinterpret_cast<Sub_Matrix_Decomposition*>(void_instance);
446  std::vector<Gate*> gates_loc = instance->get_gates();
447 
448  static tbb::enumerable_thread_specific<Matrix> matrix_new_tls;
449  Matrix& matrix_new = matrix_new_tls.local();
450  instance->get_Umtx().copy_to(matrix_new);
451  instance->apply_to( parameters, matrix_new );
452 
453 #ifdef DEBUG
454  if (matrix_new.isnan()) {
455  std::stringstream sstream;
456  sstream << "Sub_Matrix_Decomposition::optimization_problem matrix_new contains NaN b." << std::endl;
457  print(sstream, 1);
458  }
459 #endif
460 
461  double cost_function = get_submatrix_cost_function(matrix_new); //NEW METHOD
462 
463 
464  return cost_function;
465 }
466 
467 
468 
469 
470 
478 
479  // The function value at x0
480  double f0;
481 
482  // calculate the approximate gradient
483  optimization_problem_combined( parameters, void_instance, &f0, grad);
484 
485 }
486 
487 
488 
497 
498  Sub_Matrix_Decomposition* instance = reinterpret_cast<Sub_Matrix_Decomposition*>(void_instance);
499 
500  int parameter_num_loc = instance->get_parameter_num();
501 
502  // storage for the function values calculated at the displaced points x
503  Matrix_real f(1, grad.size());
504 
505  // the difference in one direction in the parameter for the gradient calculation
506  double dparam = 1e-8;
507 
508  auto calculate_displaced_cost = [&](int i) {
509  if (i == (int)parameters.size()) {
510  // calculate function value at x0
511  *f0 = instance->optimization_problem(parameters, reinterpret_cast<void*>(instance));
512  }
513  else {
514 
515  static tbb::enumerable_thread_specific<Matrix_real> parameters_d_tls;
516  Matrix_real& parameters_d = parameters_d_tls.local();
517  parameters.copy_to(parameters_d);
518  parameters_d[i] = parameters_d[i] + dparam;
519 
520  // calculate the cost function at the displaced point
521  f[i] = instance->optimization_problem(parameters_d, reinterpret_cast<void*>(instance));
522 
523  }
524  };
525 
526  if (instance->get_parallel_configuration() == 0) {
527  for (int i=0; i<parameter_num_loc+1; ++i) {
528  calculate_displaced_cost(i);
529  }
530  }
531  else {
532  // calculate the function values at displaced x and the central x0 points through TBB parallel for
533  tbb::parallel_for(0, parameter_num_loc+1, 1, [&](int i) {
534  calculate_displaced_cost(i);
535  });
536  }
537 
538 
539 /*
540  // sequential version
541  functor_sub_optimization_grad<Sub_Matrix_Decomposition> tmp = functor_grad<Sub_Matrix_Decomposition>( parameters, instance, f, f0, dparam );
542  #pragma omp parallel for
543  for (int idx=0; idx<parameter_num_loc+1; idx++) {
544  tmp(idx);
545  }
546 */
547 
548 
549  for (int idx=0; idx<parameter_num_loc; idx++) {
550  // set the gradient
551 #ifdef DEBUG
552  if (isnan(f->data[idx])) {
553  std::string err("Sub_Matrix_Decomposition::optimization_problem_combined: f->data[i] is NaN.");
554  throw err;
555  }
556 #endif // DEBUG
557  grad[idx] = (f[idx]-(*f0))/dparam;
558  }
559 
560 
561 
562 }
563 
564 
565 
566 
567 
568 
569 
570 
571 
572 
579 int Sub_Matrix_Decomposition::set_identical_blocks( int qbit, int identical_blocks_in ) {
580 
581  std::map<int,int>::iterator key_it = identical_blocks.find( qbit );
582 
583  if ( key_it != identical_blocks.end() ) {
584  identical_blocks.erase( key_it );
585  }
586 
587  identical_blocks.insert( std::pair<int, int>(qbit, identical_blocks_in) );
588 
589  return 0;
590 
591 }
592 
593 
599 int Sub_Matrix_Decomposition::set_identical_blocks( std::map<int, int> identical_blocks_in ) {
600 
601  for ( std::map<int,int>::iterator it=identical_blocks_in.begin(); it!= identical_blocks_in.end(); it++ ) {
602  set_identical_blocks( it->first, it->second );
603  }
604 
605  return 0;
606 
607 }
608 
609 
615 
616 
618 
619  // setting computational parameters
625 
626  if ( extract_gates(static_cast<Gates_block*>(ret)) != 0 ) {
627  std::string err("Sub_Matrix_Decomposition::clone(): extracting gates was not succesfull.");
628  throw err;
629  }
630 
631  return ret;
632 
633 }
bool optimize_layer_num
logical value. Set true to optimize the minimum number of gate layers required in the decomposition...
Sub_Matrix_Decomposition()
Nullary constructor of the class.
static void optimization_problem_grad(Matrix_real parameters, void *void_instance, Matrix_real &grad)
Calculate the approximate derivative (f-f0)/(x-x0) of the cost function with respect to the free para...
void set_custom_gate_layers(Gates_block *block_in)
Call to set custom layers to the gate structure that are intended to be used in the subdecomposition...
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
Gates_block * unit_gate_structure
Custom gate structure describing the gate structure used in the decomposition. The gate structure is ...
Matrix get_Umtx()
Call to retrive a pointer to the unitary to be transformed.
void disentangle_submatrices()
Start the optimization process to disentangle the most significant qubit from the others...
virtual Gate * clone()
Call to create a clone of the present class.
Definition: Gate.cpp:1351
void add_gate(Gate *gate)
Append a general gate to the list of gates.
long long max_inner_iterations
maximal number of inner iterations
int set_max_layer_num(int n, int max_layer_num_in)
Set the maximal number of layers used in the subdecomposition of the n-th qubit.
std::vector< Gate * > get_gates()
Call to get the gates stored in the class.
virtual Gates_block * clone() override
Create a clone of the present class.
int layer_num
number of gate layers
Definition: Gates_block.h:51
void add_cnot(int target_qbit, int control_qbit)
Append a CNOT gate gate to the list of gates.
std::map< int, int > max_layer_num
A map of <int n: int num> indicating that how many layers should be used in the subdecomposition proc...
scalar * get_data() const
Call to get the pointer to the stored data.
guess_type initial_guess
type to guess the initial values for the optimization. Possible values: ZEROS=0, RANDOM=1, CLOSE_TO_ZERO=2
std::map< int, int > identical_blocks
A map of <int n: int num> indicating that how many identical succesive blocks should be used in the d...
double get_submatrix_cost_function(Matrix &matrix)
Call to calculate the cost function of a given matrix during the submatrix decomposition process...
int set_identical_blocks(int qbit, int identical_blocks_in)
Set the number of identical successive blocks during the subdecomposition of the qbit-th qubit...
std::vector< Gate * > gates
The list of stored gates.
Definition: Gates_block.h:49
int max_outer_iterations
Maximal number of iterations allowed in the optimization process.
A class implementing the BFGS optimizer based on conjugate gradient direction method of M...
Definition: BFGS_Powell.h:26
gate_type type
The type of the operation (see enumeration gate_type)
Definition: Gate.h:96
double Start_Optimization(Matrix_real &x, long maximal_iterations_in=5001)
Call this method to start the optimization.
#define M_PI
Definition: qgd_math.h:42
void add_u3(int target_qbit)
Append a U3 gate to the list of gates.
bool subdisentaglement_done
logical value indicating whether the disentamglement of a qubit from the othetrs was done or not ...
virtual void add_gate_layers()
Call to add further layer to the gate structure used in the subdecomposition.
Sub_Matrix_Decomposition * clone()
Call to create a clone of the present class.
std::map< int, int > iteration_loops
A map of <int n: int num> indicating the number of iteration in each step of the decomposition.
virtual void apply_to(Matrix_real &parameters_mtx, Matrix &input, int parallel=0) override
Call to apply the gate on the input array/matrix Gates_block*input.
~Sub_Matrix_Decomposition()
Destructor of the class.
void add_default_gate_layers()
Call to add default gate layers to the gate structure used in the subdecomposition.
A class containing basic methods for the decomposition process.
Matrix copy() const
Call to create a copy of the matrix.
Definition: matrix.h:57
void copy_to(Matrix_real &target) const
Copy the matrix to a reusable double-precision target matrix.
int extract_gates(Gates_block *op_block)
Call to extract the gates stored in the class.
Double-precision complex matrix (float64).
Definition: matrix.h:38
void copy_to(matrix_base< scalar > &target) const
Copy the current matrix storage into a reusable target matrix.
int size() const
Call to get the number of the allocated elements.
Gates_block()
Default constructor of the class.
Definition: Gates_block.cpp:82
A class responsible for grouping two-qubit (CNOT,CZ,CH) and one-qubit gates into layers.
Definition: Gates_block.h:44
Matrix subdecomposed_mtx
The subdecomposed matrix.
guess_type
Type definition of the types of the initial guess.
std::map< std::string, Config_Element > config
config metadata utilized during the optimization
Base class for the representation of general gate operations.
Definition: Gate.h:86
static void optimization_problem_combined(Matrix_real parameters, void *void_instance, double *f0, Matrix_real &grad)
Call to calculate both the cost function and the its gradient components.
Header file for a class responsible for the disentanglement of one qubit from the others...
int parameter_num
the number of free parameters of the operation
Definition: Gate.h:108
double optimization_problem(double *parameters)
The optimization problem of the final optimization.
volatile double current_minimum
The current minimum of the optimization problem.
void solve_optimization_problem(double *solution_guess, int solution_guess_num)
This method can be used to solve the main optimization problem which is devidid into sub-layer optimi...
Matrix Umtx
The unitary to be decomposed.
static std::map< int, int > max_layer_num_def
A map of <int n: int num> indicating that how many layers should be used in the subdecomposition proc...
int qbit_num
number of qubits spanning the matrix of the operation
Definition: Gate.h:94
void set_max_iteration(int max_outer_iterations_in)
Call to set the maximal number of the iterations in the optimization process.
bool check_optimization_solution()
check_optimization_solution
A class responsible for the disentanglement of one qubit from the others.
int optimization_block
number of gate blocks used in one shot of the optimization process
int set_iteration_loops(int n, int iteration_loops_in)
Set the number of iteration loops during the subdecomposition of the n-th qubit.
Matrix_real optimized_parameters_mtx
The optimized parameters for the gates.
int get_parallel_configuration()
Get the parallel configuration from the config.
int get_parameter_num() override
Call to get the number of free parameters.
double global_target_minimum
The global target minimum of the optimization problem.
Header file for the paralleized calculation of the cost function of the subdecomposition (supporting ...
void set_optimization_blocks(int optimization_block_in)
Call to set the number of gate blocks to be optimized in one shot.
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()
void solve_layer_optimization_problem(int num_of_parameters, Matrix_real solution_guess_gsl)
Call to solve layer by layer the optimization problem.