Quantum Gate Decomposer  v1.3
Powerful decomposition of almost any unitary into U3 and CNOT gates
Decomposition_Base.cpp
Go to the documentation of this file.
1 /*
2 Created on Fri Jun 26 14:13:26 2020
3 Copyright (C) 2020 Peter Rakyta, Ph.D.
4 
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see http://www.gnu.org/licenses/.
17 
18 @author: Peter Rakyta, Ph.D.
19 */
24 #include "qgd/Decomposition_Base.h"
25 
26 
27 // default layer numbers
29 
30 
38 Decomposition_Base::Decomposition_Base( Matrix Umtx_in, int qbit_num_in, guess_type initial_guess_in= CLOSE_TO_ZERO ) : Operation_block(qbit_num_in) {
39 
41 
42  // Logical variable. Set true for verbose mode, or to false to suppress output messages.
43  verbose = true;
44 
45  // the unitary operator to be decomposed
46  Umtx = Umtx_in;
47 
48  // logical value describing whether the decomposition was finalized or not
50 
51  // A string describing the type of the class
53 
54  // error of the unitarity of the final decomposition
56 
57  // number of finalizing (deterministic) opertaions counted from the top of the array of operations
59 
60  // the number of the finalizing (deterministic) parameters counted from the top of the optimized_parameters list
62 
63  // The current minimum of the optimization problem
64  current_minimum = 1e10;
65 
66  // The global minimum of the optimization problem
68 
69  // logical value describing whether the optimization problem was solved or not
71 
72  // number of iteratrion loops in the finale optimization
73  //iteration_loops = dict()
74 
75  // The maximal allowed error of the optimization problem
77 
78  // Maximal number of iteartions in the optimization process
79  max_iterations = 1e8;
80 
81  // number of operators in one sub-layer of the optimization process
83 
84  // method to guess initial values for the optimization. Possible values: ZEROS, RANDOM, CLOSE_TO_ZERO (default)
85  initial_guess = initial_guess_in;
86 
87  // optimized parameters
88  optimized_parameters = NULL;
89 
90 
91 #if CBLAS==1
92  num_threads = mkl_get_max_threads();
93 #elif CBLAS==2
94  num_threads = openblas_get_num_threads();
95 #endif
96 
97 }
98 
103 
104  if (optimized_parameters != NULL ) {
106  optimized_parameters = NULL;
107  }
108 
109 }
110 
111 
116 void Decomposition_Base::set_optimization_blocks( int optimization_block_in) {
117  optimization_block = optimization_block_in;
118 }
119 
124 void Decomposition_Base::set_max_iteration( int max_iterations_in) {
125  max_iterations = max_iterations_in;
126 }
127 
128 
133 
134  // get the transformed matrix resulted by the operations in the list
135  Matrix transformed_matrix = get_transformed_matrix( optimized_parameters, operations.begin(), operations.size(), Umtx );
136 
137  // preallocate the storage for the finalizing parameters
139  double* finalizing_parameters = (double*)qgd_calloc(finalizing_parameter_num,sizeof(double), 64);
140 
141  // obtaining the final operations of the decomposition
142  Operation_block* finalizing_operations = new Operation_block( qbit_num );;
143  Matrix final_matrix = get_finalizing_operations( transformed_matrix, finalizing_operations, finalizing_parameters );
144 
145  // adding the finalizing operations to the list of operations
146  // adding the opeartion block to the operations
147  add_operation_to_front( finalizing_operations );
148 // TODO: use memcpy
149  double* optimized_parameters_tmp = (double*)qgd_calloc( (parameter_num),sizeof(double), 64 );
150  for (int idx=0; idx < finalizing_parameter_num; idx++) {
151  optimized_parameters_tmp[idx] = finalizing_parameters[idx];
152  }
153  for (unsigned int idx=0; idx < parameter_num-finalizing_parameter_num; idx++) {
154  optimized_parameters_tmp[idx+finalizing_parameter_num] = optimized_parameters[idx];
155  }
157  qgd_free( finalizing_parameters);
158  optimized_parameters = NULL;
159  finalizing_parameters = NULL;
160  optimized_parameters = optimized_parameters_tmp;
161  optimized_parameters_tmp = NULL;
162 
163  finalizing_operations_num = finalizing_operations->get_operation_num();
164 
165 
166  // indicate that the decomposition was finalized
168 
169  // calculating the final error of the decomposition
170  subtract_diag( final_matrix, final_matrix[0] );
171  decomposition_error = cblas_dznrm2( matrix_size*matrix_size, (void*)final_matrix.get_data(), 1 );
172 
173  // get the number of gates used in the decomposition
175 
176 
177  if (verbose) {
178  printf( "The error of the decomposition after finalyzing operations is %f with %d layers containing %d U3 operations and %d CNOT gates.\n", decomposition_error, layer_num, gates_num.u3, gates_num.cnot );
179  }
180 
181 }
182 
183 
188 void Decomposition_Base::list_operations( int start_index ) {
189 
191 
192 }
193 
194 
195 
203 Matrix Decomposition_Base::get_finalizing_operations( Matrix& mtx, Operation_block* finalizing_operations, double* finalizing_parameters ) {
204 
205 
206  int parameter_idx = finalizing_parameter_num-1;
207 
208  Matrix mtx_tmp = mtx.copy();
209 
210 
211  double Theta, Lambda, Phi;
212  for (int target_qbit=0; target_qbit<qbit_num; target_qbit++ ) {
213 
214  // get the base indices of the taget qubit states |0>, where all other qubits are in state |0>
215  int state_0 = 0;
216 
217  // get the base indices of the taget qubit states |1>, where all other qubits are in state |0>
218  int state_1 = Power_of_2(target_qbit);
219 
220  // finalize the 2x2 submatrix with z-y-z rotation
221  QGD_Complex16 element00 = mtx[state_0*matrix_size+state_0];
222  QGD_Complex16 element01 = mtx[state_0*matrix_size+state_1];
223  QGD_Complex16 element10 = mtx[state_1*matrix_size+state_0];
224  QGD_Complex16 element11 = mtx[state_1*matrix_size+state_1];
225 
226  // finalize the 2x2 submatrix with z-y-z rotation
227  double cos_theta_2 = sqrt(element00.real*element00.real + element00.imag*element00.imag)/sqrt(element00.real*element00.real + element00.imag*element00.imag + element01.real*element01.real + element01.imag*element01.imag);
228  Theta = 2*acos( cos_theta_2 );
229 
230  if ( sqrt(element00.real*element00.real + element00.imag*element00.imag) < 1e-7 ) {
231  Phi = atan2(element10.imag, element10.real); //np.angle( submatrix[1,0] )
232  Lambda = atan2(-element01.imag, -element01.real); //np.angle( -submatrix[0,1] )
233  }
234  else if ( sqrt(element10.real*element10.real + element10.imag*element10.imag) < 1e-7 ) {
235  Phi = 0;
236  Lambda = atan2(element11.imag*element00.real - element11.real*element00.imag, element11.real*element00.real + element11.imag*element00.imag); //np.angle( element11*np.conj(element00))
237  }
238  else {
239  Phi = atan2(element10.imag*element00.real - element10.real*element00.imag, element10.real*element00.real + element10.imag*element00.imag); //np.angle( element10*np.conj(element00))
240  Lambda = atan2(-element01.imag*element00.real + element01.real*element00.imag, -element01.real*element00.real - element01.imag*element00.imag); //np.angle( -element01*np.conj(element00))
241  }
242 
243  double parameters_loc[3];
244  parameters_loc[0] = Theta;
245  parameters_loc[1] = M_PI-Lambda;
246  parameters_loc[2] = M_PI-Phi;
247 
248  U3* u3_loc = new U3( qbit_num, target_qbit, true, true, true);
249 
250  // adding the new operation to the list of finalizing operations
251  finalizing_parameters[parameter_idx] = M_PI-Phi; //np.concatenate((parameters_loc, finalizing_parameters))
252  parameter_idx--;
253  finalizing_parameters[parameter_idx] = M_PI-Lambda; //np.concatenate((parameters_loc, finalizing_parameters))
254  parameter_idx--;
255  finalizing_parameters[parameter_idx] = Theta; //np.concatenate((parameters_loc, finalizing_parameters))
256  parameter_idx--;
257 
258  finalizing_operations->add_operation_to_front( u3_loc );
259  // get the new matrix
260 
261 
262  Matrix u3_mtx = u3_loc->get_matrix(parameters_loc);
263 
264  Matrix tmp2 = apply_operation( u3_mtx, mtx_tmp);
265  mtx_tmp = tmp2;
266 
267  }
268 
269 
270  return mtx_tmp;
271 
272 
273 }
274 
275 
276 
277 
283 void Decomposition_Base::solve_optimization_problem( double* solution_guess, int solution_guess_num ) {
284 
285 
286  if ( operations.size() == 0 ) {
287  return;
288  }
289 
290  // array containing minimums to check convergence of the solution
291  int min_vec_num = 20;
292  double minimum_vec[min_vec_num];
293  for ( int idx=0; idx<min_vec_num; idx++) {
294  minimum_vec[idx] = 0;
295  }
296 
297  // setting the initial value for the current minimum
298  current_minimum = 1e8;
299 
300  // store the operations
301  std::vector<Operation*> operations_loc = operations;
302 
303 
304 
305  // store the number of parameters
306  int parameter_num_loc = parameter_num;
307 
308  // store the initial unitary to be decomposed
309  Matrix Umtx_loc = Umtx; // copy??
310 
311  // storing the initial computational parameters
312  int optimization_block_loc = optimization_block;
313 
314  // initialize random seed:
315  srand (time(NULL));
316 
317  // the array storing the optimized parameters
318  gsl_vector* optimized_parameters_gsl = gsl_vector_alloc (parameter_num_loc);
319 
320  // preparing solution guess for the iterations
321  if ( initial_guess == ZEROS ) {
322  for(unsigned int idx = 0; idx < parameter_num-solution_guess_num; idx++) {
323  optimized_parameters_gsl->data[idx] = 0;
324  }
325  }
326  else if ( initial_guess == RANDOM ) {
327  for(unsigned int idx = 0; idx < parameter_num-solution_guess_num; idx++) {
328  optimized_parameters_gsl->data[idx] = (2*double(rand())/double(RAND_MAX)-1)*2*M_PI;
329  }
330  }
331  else if ( initial_guess == CLOSE_TO_ZERO ) {
332  for(unsigned int idx = 0; idx < parameter_num-solution_guess_num; idx++) {
333  optimized_parameters_gsl->data[idx] = (2*double(rand())/double(RAND_MAX)-1)*2*M_PI/100;
334  }
335  }
336  else {
337  printf("bad value for initial guess\n");
338  exit(-1);
339  }
340 
341  if ( solution_guess_num > 0) {
342  memcpy(optimized_parameters_gsl->data + parameter_num-solution_guess_num, solution_guess, solution_guess_num*sizeof(double));
343  }
344 
345  // starting number of operation block applied prior to the optimalized operation blocks
346  int pre_operation_parameter_num = 0;
347 
348  // defining temporary variables for iteration cycles
349  int block_idx_end;
350  unsigned int block_idx_start = operations.size();
351  operations.clear();
352  int block_parameter_num;
353  Matrix operations_mtx_pre;
354  Operation* fixed_operation_post = new Operation( qbit_num );
355  std::vector<Matrix, tbb::cache_aligned_allocator<Matrix>> operations_mtxs_post;
356 
357  // the identity matrix used in the calculations
358  Matrix Identity = create_identity( matrix_size );
359 
360 
361  gsl_vector *solution_guess_gsl = NULL;
362 
363 
364  //measure the time for the decomposition
365  clock_t start_time = time(NULL);
366 
368  // Start the iterations
369  int iter_idx;
370  for ( iter_idx=0; iter_idx<max_iterations+1; iter_idx++) {
371 
372  //determine the range of blocks to be optimalized togedther
373  block_idx_end = block_idx_start - optimization_block;
374  if (block_idx_end < 0) {
375  block_idx_end = 0;
376  }
377 
378  // determine the number of free parameters to be optimized
379  block_parameter_num = 0;
380  for ( int block_idx=block_idx_start-1; block_idx>=block_idx_end; block_idx--) {
381  block_parameter_num = block_parameter_num + operations_loc[block_idx]->get_parameter_num();
382  }
383 
384  // ***** get the fixed operations applied before the optimized operations *****
385  if (block_idx_start < operations_loc.size() ) {
386  std::vector<Operation*>::iterator fixed_operations_pre_it = operations.begin() + 1;
387  Matrix tmp = get_transformed_matrix(optimized_parameters, fixed_operations_pre_it, operations.size()-1, operations_mtx_pre);
388  operations_mtx_pre = tmp; // copy?
389  }
390  else {
391  operations_mtx_pre = Identity.copy();
392  }
393 
394  // Transform the initial unitary upon the fixed pre-optimization operations
395  Umtx = apply_operation(operations_mtx_pre, Umtx_loc);
396 
397  // clear the operation list used in the previous iterations
398  operations.clear();
399 
400  if (optimized_parameters != NULL ) {
402  optimized_parameters = NULL;
403  }
404 
405 
406  // ***** get the fixed operations applied after the optimized operations *****
407  // create a list of post operations matrices
408  if (block_idx_start == operations_loc.size() ) {
409  // matrix of the fixed operations aplied after the operations to be varied
410  double* fixed_parameters_post = optimized_parameters_gsl->data;
411  std::vector<Operation*>::iterator fixed_operations_post_it = operations_loc.begin();
412 
413  operations_mtxs_post = get_operation_products(fixed_parameters_post, fixed_operations_post_it, block_idx_end);
414  }
415 
416  // Create a general operation describing the cumulative effect of gates following the optimized operations
417  if (block_idx_end > 0) {
418  fixed_operation_post->set_matrix( operations_mtxs_post[block_idx_end-1] );
419  }
420  else {
421  // release operation products
422  operations_mtxs_post.clear();
423  fixed_operation_post->set_matrix( Identity );
424  }
425 
426  // create a list of operations for the optimization process
427  operations.push_back( fixed_operation_post );
428  for ( unsigned int idx=block_idx_end; idx<block_idx_start; idx++ ) {
429  operations.push_back( operations_loc[idx] );
430  }
431 
432 
433  // constructing solution guess for the optimization
434  parameter_num = block_parameter_num;
435  if ( solution_guess_gsl == NULL ) {
436  solution_guess_gsl = gsl_vector_alloc (parameter_num);
437  }
438  else if ( parameter_num != solution_guess_gsl->size ) {
439  gsl_vector_free(solution_guess_gsl);
440  solution_guess_gsl = gsl_vector_alloc (parameter_num);
441  }
442  memcpy( solution_guess_gsl->data, optimized_parameters_gsl->data+parameter_num_loc - pre_operation_parameter_num - block_parameter_num, parameter_num*sizeof(double) );
443 
444  // solve the optimization problem of the block
445  solve_layer_optimization_problem( parameter_num, solution_guess_gsl );
446 
447  // add the current minimum to the array of minimums and calculate the mean
448  double minvec_mean = 0;
449  for (int idx=min_vec_num-1; idx>0; idx--) {
450  minimum_vec[idx] = minimum_vec[idx-1];
451  minvec_mean = minvec_mean + minimum_vec[idx-1];
452  }
453  minimum_vec[0] = current_minimum;
454  minvec_mean = minvec_mean + current_minimum;
455  minvec_mean = minvec_mean/min_vec_num;
456 
457  // store the obtained optimalized parameters for the block
458  memcpy( optimized_parameters_gsl->data+parameter_num_loc - pre_operation_parameter_num-block_parameter_num, optimized_parameters, parameter_num*sizeof(double) );
459 
460  if (block_idx_end == 0) {
461  block_idx_start = operations_loc.size();
462  pre_operation_parameter_num = 0;
463  }
464  else {
465  block_idx_start = block_idx_start - optimization_block;
466  pre_operation_parameter_num = pre_operation_parameter_num + block_parameter_num;
467  }
468 
469 
470  // optimization result is displayed in each 500th iteration
471  if (iter_idx % 500 == 0) {
472  if (verbose) {
473  printf("The minimum with %d layers after %d iterations is %e calculated in %f seconds\n", layer_num, iter_idx, current_minimum, float(time(NULL) - start_time));
474  fflush(stdout);
475  }
476  start_time = time(NULL);
477  }
478 
479 
480  // calculate the variance of the last 10 minimums
481  double minvec_std = sqrt(gsl_stats_variance_m( minimum_vec, 1, min_vec_num, minvec_mean));
482 
483  // conditions to break the iteration cycles
484  if (abs(minvec_std/minimum_vec[min_vec_num-1]) < optimization_tolerance ) {
485  if (verbose) {
486  printf("The iterations converged to minimum %e after %d iterations with %d layers\n", current_minimum, iter_idx, layer_num );
487  fflush(stdout);
488  }
489  break;
490  }
491  else if (check_optimization_solution()) {
492  if (verbose) {
493  printf("The minimum with %d layers after %d iterations is %e\n", layer_num, iter_idx, current_minimum);
494  }
495  break;
496  }
497 
498 
499  }
500 
501 
502  if (iter_idx == max_iterations ) {
503  if (verbose) {
504  printf("Reached maximal number of iterations\n\n");
505  }
506  }
507 
508  // restoring the parameters to originals
509  optimization_block = optimization_block_loc;
510 
511  // store the result of the optimization
512  operations.clear();
513  operations = operations_loc;
514 
515  parameter_num = parameter_num_loc;
516  if (optimized_parameters != NULL ) {
518  }
519 
520  optimized_parameters = (double*)qgd_calloc(parameter_num,sizeof(double), CACHELINE);
521  memcpy( optimized_parameters, optimized_parameters_gsl->data, parameter_num*sizeof(double) );
522 
523 
524  // free unnecessary resources
525  gsl_vector_free(optimized_parameters_gsl);
526  gsl_vector_free(solution_guess_gsl);
527  optimized_parameters_gsl = NULL;
528  solution_guess_gsl = NULL;
529 
530  delete(fixed_operation_post);
531 
532  // restore the original unitary
533  Umtx = Umtx_loc; // copy?
534 
535 
536 }
537 
538 
539 
545 void Decomposition_Base::solve_layer_optimization_problem( int num_of_parameters, gsl_vector *solution_guess_gsl) {
546  return;
547 }
548 
549 
550 
551 
556 double Decomposition_Base::optimization_problem( const double* parameters ) {
557  return current_minimum;
558 }
559 
560 
561 
562 
563 
569 
571 
572 }
573 
574 
580  return Umtx;
581 }
582 
583 
589  return matrix_size;
590 }
591 
597  double *ret = (double*)qgd_calloc( parameter_num,sizeof(double), 64);
599  return ret;
600 }
601 
607  memcpy(ret, optimized_parameters, parameter_num*sizeof(double));
608  return;
609 }
610 
618 Matrix
619 Decomposition_Base::get_transformed_matrix( const double* parameters, std::vector<Operation*>::iterator operations_it, int num_of_operations ) {
620 
621  return get_transformed_matrix( parameters, operations_it, num_of_operations, Umtx );
622 }
623 
624 
633 Matrix
634 Decomposition_Base::get_transformed_matrix( const double* parameters, std::vector<Operation*>::iterator operations_it, int num_of_operations, Matrix& initial_matrix ) {
635 
636 
637  if (num_of_operations==0) {
638  return initial_matrix.copy();
639  }
640 
641  // The matrix of the transformed matrix
642  Matrix Operation_product;
643 
644  // The matrix to be returned
645  Matrix ret_matrix;
646 
647  for (int idx=0; idx<num_of_operations; idx++) {
648 
649  // The current operation
650  Operation* operation = *operations_it;
651 
652  // The matrix of the current operation
653  Matrix operation_mtx;
654 
655  if (operation->get_type() == CNOT_OPERATION ) {
656  CNOT* cnot_operation = static_cast<CNOT*>( operation );
657  operation_mtx = cnot_operation->get_matrix();
658  }
659  else if (operation->get_type() == GENERAL_OPERATION ) {
660  operation_mtx = operation->get_matrix();
661  }
662  else if (operation->get_type() == U3_OPERATION ) {
663  U3* u3_operation = static_cast<U3*>( operation );
664  int parameters_num = u3_operation->get_parameter_num();
665  operation_mtx = u3_operation->get_matrix( parameters );
666  parameters = parameters + parameters_num;
667  }
668  else if (operation->get_type() == BLOCK_OPERATION ) {
669  Operation_block* block_operation = static_cast<Operation_block*>( operation );
670  int parameters_num = block_operation->get_parameter_num();
671  operation_mtx = block_operation->get_matrix( parameters );
672  parameters = parameters + parameters_num;
673  }
674 
675  if ( idx == 0 ) {
676  Operation_product = operation_mtx; //copy?
677  }
678  else {
679  ret_matrix = dot( Operation_product, operation_mtx );
680  Operation_product = ret_matrix; // copy?
681 
682  }
683 
684  operations_it++;
685  }
686 
687  ret_matrix = apply_operation( Operation_product, initial_matrix );
688 
689  return ret_matrix;
690 
691 
692 
693 }
694 
700 
702 }
703 
710 Matrix
711 Decomposition_Base::apply_operation( Matrix& operation_mtx, Matrix& input_matrix ) {
712 
713  // Getting the transformed state upon the transformation given by operation
714  return dot( operation_mtx, input_matrix );
715 
716 }
717 
724 int Decomposition_Base::set_max_layer_num( int n, int max_layer_num_in ) {
725 
726  std::map<int,int>::iterator key_it = max_layer_num.find( n );
727 
728  if ( key_it != max_layer_num.end() ) {
729  max_layer_num.erase( key_it );
730  }
731 
732  max_layer_num.insert( std::pair<int, int>(n, max_layer_num_in) );
733 
734  return 0;
735 
736 }
737 
738 
744 int Decomposition_Base::set_max_layer_num( std::map<int, int> max_layer_num_in ) {
745 
746 
747  for ( std::map<int,int>::iterator it = max_layer_num_in.begin(); it!=max_layer_num_in.end(); it++) {
748  set_max_layer_num( it->first, it->second );
749  }
750 
751  return 0;
752 
753 }
754 
755 
760 void Decomposition_Base::reorder_qubits( std::vector<int> qbit_list) {
761 
762  Operation_block::reorder_qubits( qbit_list );
763 
764  // now reorder the unitary to be decomposed
765 
766  // obtain the permutation indices of the matrix rows/cols
767  std::vector<int> perm_indices;
768  perm_indices.reserve(matrix_size);
769 
770  for (int idx=0; idx<matrix_size; idx++) {
771  int row_idx=0;
772 
773  // get the binary representation of idx
774  std::vector<int> bin_rep;
775  bin_rep.reserve(qbit_num);
776  for (int i = 1 << (qbit_num-1); i > 0; i = i / 2) {
777  (idx & i) ? bin_rep.push_back(1) : bin_rep.push_back(0);
778  }
779 
780  // determine the permutation row index
781  for (int jdx=0; jdx<qbit_num; jdx++) {
782  row_idx = row_idx + bin_rep[qbit_num-1-jdx]*Power_of_2(qbit_list[jdx]);
783  }
784  perm_indices.push_back(row_idx);
785  }
786 /*
787  for (auto it=qbit_list.begin(); it!=qbit_list.end(); it++) {
788  std::cout << *it;
789  }
790  std::cout << std::endl;
791 
792  for (auto it=perm_indices.begin(); it!=perm_indices.end(); it++) {
793  std::cout << *it+1 << std::endl;
794  }
795 */
796 
797  // reordering the matrix elements
798  Matrix reordered_mtx = Matrix(matrix_size, matrix_size);
799  for (int row_idx = 0; row_idx<matrix_size; row_idx++) {
800  for (int col_idx = 0; col_idx<matrix_size; col_idx++) {
801  int index_umtx = perm_indices[row_idx]*Umtx.rows + perm_indices[col_idx];
802  int index_reordered = row_idx*Umtx.rows + col_idx;
803  reordered_mtx[index_reordered] = Umtx[index_umtx];
804  }
805  }
806 
807  Umtx = reordered_mtx;
808 }
809 
810 
811 
818 int Decomposition_Base::set_iteration_loops( int n, int iteration_loops_in ) {
819 
820  std::map<int,int>::iterator key_it = iteration_loops.find( n );
821 
822  if ( key_it != iteration_loops.end() ) {
823  iteration_loops.erase( key_it );
824  }
825 
826  iteration_loops.insert( std::pair<int, int>(n, iteration_loops_in) );
827 
828  return 0;
829 
830 }
831 
832 
838 int Decomposition_Base::set_iteration_loops( std::map<int, int> iteration_loops_in ) {
839 
840  for ( std::map<int,int>::iterator it=iteration_loops_in.begin(); it!= iteration_loops_in.end(); it++ ) {
841  set_iteration_loops( it->first, it->second );
842  }
843 
844  return 0;
845 
846 }
847 
848 
849 
854 
855  // default layer numbers
856  max_layer_num_def[2] = 3;
857  max_layer_num_def[3] = 16;
858  max_layer_num_def[4] = 60;
859  max_layer_num_def[5] = 240;
860  max_layer_num_def[6] = 1350;
861  max_layer_num_def[7] = 7000;//6180;
862 
863 }
864 
865 
866 
871 
872  std::vector<Operation*> operations_tmp = prepare_operations_to_export( operations, optimized_parameters );
873 
874  // release the operations and replace them with the ones prepared to export
875  operations.clear();
876  operations = operations_tmp;
877 
878 }
879 
880 
881 
888 std::vector<Operation*> Decomposition_Base::prepare_operations_to_export( std::vector<Operation*> ops, const double* parameters ) {
889 
890  std::vector<Operation*> ops_ret;
891  int parameter_idx = 0;
892 
893 
894  for(std::vector<Operation*>::iterator it = ops.begin(); it != ops.end(); it++) {
895 
896  Operation* operation = *it;
897 
898  if (operation->get_type() == CNOT_OPERATION) {
899  ops_ret.push_back( operation );
900  }
901  else if (operation->get_type() == U3_OPERATION) {
902 
903  // definig the U3 parameters
904  double vartheta;
905  double varphi;
906  double varlambda;
907 
908  // get the inverse parameters of the U3 rotation
909 
910  U3* u3_operation = static_cast<U3*>(operation);
911 
912  if ((u3_operation->get_parameter_num() == 1) && u3_operation->is_theta_parameter()) {
913  vartheta = std::fmod( parameters[parameter_idx], 4*M_PI);
914  varphi = 0;
915  varlambda =0;
916  parameter_idx = parameter_idx + 1;
917 
918  }
919  else if ((u3_operation->get_parameter_num() == 1) && u3_operation->is_phi_parameter()) {
920  vartheta = 0;
921  varphi = std::fmod( parameters[ parameter_idx ], 2*M_PI);
922  varlambda =0;
923  parameter_idx = parameter_idx + 1;
924  }
925  else if ((u3_operation->get_parameter_num() == 1) && u3_operation->is_lambda_parameter()) {
926  vartheta = 0;
927  varphi = 0;
928  varlambda = std::fmod( parameters[ parameter_idx ], 2*M_PI);
929  parameter_idx = parameter_idx + 1;
930  }
931  else if ((u3_operation->get_parameter_num() == 2) && u3_operation->is_theta_parameter() && u3_operation->is_phi_parameter() ) {
932  vartheta = std::fmod( parameters[ parameter_idx ], 4*M_PI);
933  varphi = std::fmod( parameters[ parameter_idx+1 ], 2*M_PI);
934  varlambda = 0;
935  parameter_idx = parameter_idx + 2;
936  }
937  else if ((u3_operation->get_parameter_num() == 2) && u3_operation->is_theta_parameter() && u3_operation->is_lambda_parameter() ) {
938  vartheta = std::fmod( parameters[ parameter_idx ], 4*M_PI);
939  varphi = 0;
940  varlambda = std::fmod( parameters[ parameter_idx+1 ], 2*M_PI);
941  parameter_idx = parameter_idx + 2;
942  }
943  else if ((u3_operation->get_parameter_num() == 2) && u3_operation->is_phi_parameter() && u3_operation->is_lambda_parameter() ) {
944  vartheta = 0;
945  varphi = std::fmod( parameters[ parameter_idx], 2*M_PI);
946  varlambda = std::fmod( parameters[ parameter_idx+1 ], 2*M_PI);
947  parameter_idx = parameter_idx + 2;
948  }
949  else if ((u3_operation->get_parameter_num() == 3)) {
950  vartheta = std::fmod( parameters[ parameter_idx ], 4*M_PI);
951  varphi = std::fmod( parameters[ parameter_idx+1 ], 2*M_PI);
952  varlambda = std::fmod( parameters[ parameter_idx+2 ], 2*M_PI);
953  parameter_idx = parameter_idx + 3;
954  }
955  else {
956  printf("wrong parameters in U3 class\n");
957  exit(-1);
958  }
959 
960  u3_operation->set_optimized_parameters( vartheta, varphi, varlambda );
961  ops_ret.push_back( static_cast<Operation*>(u3_operation) );
962 
963 
964  }
965  else if (operation->get_type() == BLOCK_OPERATION) {
966  Operation_block* block_operation = static_cast<Operation_block*>(operation);
967  const double* parameters_layer = parameters + parameter_idx;
968 
969  std::vector<Operation*> ops_loc = prepare_operations_to_export(block_operation, parameters_layer);
970  parameter_idx = parameter_idx + block_operation->get_parameter_num();
971 
972  ops_ret.insert( ops_ret.end(), ops_loc.begin(), ops_loc.end() );
973  }
974 
975  }
976 
977 
978  return ops_ret;
979 
980 
981 }
982 
983 
990 std::vector<Operation*> Decomposition_Base::prepare_operations_to_export( Operation_block* block_op, const double* parameters ) {
991 
992  std::vector<Operation*> ops_tmp = block_op->get_operations();
993  std::vector<Operation*> ops_ret = prepare_operations_to_export( ops_tmp, parameters );
994 
995  return ops_ret;
996 
997 }
998 
999 
1009 int Decomposition_Base::get_operation( unsigned int n, operation_type &type, int &target_qbit, int &control_qbit, double* parameters ) {
1010 
1011 //printf("n: %d\n", n);
1012  // get the n-th operation if exists
1013  if ( n >= operations.size() ) {
1014  return -1;
1015  }
1016 
1017  Operation* operation = operations[n];
1018 //printf("operation type: %d\n", operation->get_type());
1019 
1020 
1021  if (operation->get_type() == CNOT_OPERATION) {
1022  type = operation->get_type();
1023  target_qbit = operation->get_target_qbit();
1024  control_qbit = operation->get_control_qbit();
1025  memset( parameters, 0, 3*sizeof(double) );
1026  return 0;
1027  }
1028  else if (operation->get_type() == U3_OPERATION) {
1029  U3* u3_operation = static_cast<U3*>(operation);
1030  type = u3_operation->get_type();
1031  target_qbit = u3_operation->get_target_qbit();
1032  control_qbit = operation->get_control_qbit();
1033  u3_operation->get_optimized_parameters(parameters);
1034 //printf("c %f, %f, %f\n", parameters[0], parameters[1], parameters[2] );
1035  return 0;
1036  }
1037  else {
1038  return -2;
1039  }
1040 
1041 }
1042 
1043 
1048 void Decomposition_Base::set_verbose( bool verbose_in ) {
1049 
1050  verbose = verbose_in;
1051 
1052 }
1053 
1054 
1060 
1061  return decomposition_error;
1062 
1063 }
1064 
Matrix dot(Matrix &A, Matrix &B)
Call to calculate the product of two complex matrices by calling method zgemm3m from the CBLAS librar...
Definition: dot.cpp:20
A class representing a U3 operation.
Definition: U3.h:35
void reorder_qubits(std::vector< int > qbit_list)
Call to reorder the qubits in the matrix of the operations.
std::vector< Operation * > get_operations()
Call to get the operations stored in the class.
Matrix get_finalizing_operations(Matrix &mtx, Operation_block *finalizing_operations, double *finalizing_parameters)
This method determine the operations needed to rotate the indepent qubits into the state |0>
Matrix get_transformed_matrix(const double *parameters, std::vector< Operation * >::iterator operations_it, int num_of_operations)
Calculate the transformed matrix resulting by an array of operations on the matrix Umtx.
void finalize_decomposition()
After the main optimization problem is solved, the indepent qubits can be rotated into state |0> by t...
Base class for the representation of one- and two-qubit operations.
Definition: Operation.h:40
void prepare_operations_to_export()
Call to prepare the optimized operations to export.
Matrix get_decomposed_matrix()
Calculate the decomposed matrix resulted by the effect of the optimized operations on the unitary Umt...
size_t rows
The number of rows.
Definition: matrix_base.h:23
int target_qbit
The index of the qubit on which the operation acts (target_qbit >= 0)
Definition: Operation.h:50
int control_qbit
The index of the qubit which acts as a control qubit (control_qbit >= 0) in controlled operations.
Definition: Operation.h:52
bool decomposition_finalized
logical value describing whether the decomposition was finalized or not (i.e. whether the decomposed ...
void * qgd_calloc(size_t element_num, size_t size, size_t alignment)
custom defined memory allocation function.
Definition: common.cpp:38
Matrix get_Umtx()
Call to retrive a pointer to the unitary to be transformed.
int get_parameter_num()
Call to get the number of free parameters.
Operation()
Default constructor of the class.
Definition: Operation.cpp:32
double current_minimum
The current minimum of the optimization problem.
bool optimization_problem_solved
logical value describing whether the optimization problem was solved or not
void qgd_free(void *ptr)
custom defined memory release function.
Definition: common.cpp:66
int get_target_qbit()
Call to get the index of the target qubit.
Definition: Operation.cpp:149
Matrix apply_operation(Matrix &operation_mtx, Matrix &input_matrix)
Apply an operations on the input matrix.
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.
Structure type conatining numbers of gates.
Definition: QGDTypes.h:47
scalar * get_data()
Call to get the pointer to the stored data.
Definition: matrix_base.h:221
void get_optimized_parameters(double *parameters_in)
Call to get the final optimized parameters of the operation.
Definition: U3.cpp:502
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...
void set_optimized_parameters(double Theta, double Phi, double Lambda)
Call to set the final optimized parameters of the operation.
Definition: U3.cpp:483
int matrix_size
The size N of the NxN matrix associated with the operations.
Definition: Operation.h:54
guess_type initial_guess
type to guess the initial values for the optimization. Possible values: ZEROS=0, RANDOM=1,...
void list_operations(const double *parameters, int start_index)
Call to print the list of operations stored in the block of operations for a specific set of paramete...
#define CACHELINE
Definition: QGDTypes.h:34
static void Init_max_layer_num()
Initializes default layer numbers.
bool is_theta_parameter()
Call to check whether theta is a free parameter of the gate.
Definition: U3.cpp:402
std::vector< Matrix, tbb::cache_aligned_allocator< Matrix > > get_operation_products(double *parameters, std::vector< Operation * >::iterator operations_it, int num_of_operations)
Calculate the list of gate operation matrices such that the i>0-th element in the result list is the ...
int finalizing_operations_num
number of finalizing (deterministic) opertaions rotating the disentangled qubits into state |0>.
double * get_optimized_parameters()
Call to get the optimized parameters.
Decomposition_Base(Matrix Umtx_in, int qbit_num_in, guess_type initial_guess_in)
Contructor of the class.
double optimization_tolerance
The maximal allowed error of the optimization problem.
operation_type type
The type of the operation (see enumeration operation_type)
Definition: Operation.h:48
int u3
The number of U3 gates.
Definition: QGDTypes.h:49
double get_decomposition_error()
Call to get the error of the decomposition.
int get_Umtx_size()
Call to get the size of the unitary to be transformed.
virtual void solve_layer_optimization_problem(int num_of_parameters, gsl_vector *solution_guess_gsl)
Abstarct function to be used to solve a single sub-layer optimization problem.
int qbit_num
number of qubits spanning the matrix of the operation
Definition: Operation.h:46
A class responsible for grouping CNOT and U3 operations into layers.
Operation_block()
Deafult constructor of the class.
void set_max_iteration(int max_iterations_in)
Call to set the maximal number of the iterations in the optimization process.
std::map< int, int > iteration_loops
A map of <int n: int num> indicating the number of iteration in each step of the decomposition.
int num_threads
Number of outer OpenMP threads. (During the calculations OpenMP multithreading is turned off....
unsigned int parameter_num
the number of free parameters of the operation
Definition: Operation.h:56
gates_num get_gate_nums()
Call to get the number of the individual gate types in the list of operations.
Structure type representing complex numbers in the QGD package.
Definition: QGDTypes.h:39
A class representing a CNOT operation.
Definition: CNOT.h:36
int layer_num
number of operation layers
int Power_of_2(int n)
Calculates the n-th power of 2.
Definition: common.cpp:81
Matrix get_matrix()
Call to retrieve the operation matrix.
Definition: Operation.cpp:97
Class to store data of complex arrays and its properties.
Definition: matrix.h:12
Header file for a class containing basic methods for the decomposition process.
virtual ~Decomposition_Base()
Destructor of the class.
void subtract_diag(Matrix &mtx, QGD_Complex16 scalar)
Call to subtract a scalar from the diagonal of a complex matrix.
Definition: common.cpp:346
std::vector< Operation * > operations
The list of stored operations.
unsigned int get_parameter_num()
Call to get the number of free parameters.
Definition: Operation.cpp:165
void set_matrix(Matrix input)
Call to set the stored matrix in the operation.
Definition: Operation.cpp:109
void reorder_qubits(std::vector< int > qbit_list)
Call to reorder the qubits in the matrix of the operation.
bool verbose
Logical variable. Set true for verbose mode, or to false to suppress output messages.
guess_type
Type definition of the types of the initial guess.
Matrix get_matrix(const double *parameters)
Call to retrieve the operation matrix (Which is the product of all the operation matrices stored in t...
double * optimized_parameters
The optimized parameters for the operations.
int get_operation(unsigned int n, operation_type &type, int &target_qbit, int &control_qbit, double *parameters)
Call to prepare the optimized operations to export.
void add_operation_to_front(Operation *operation)
Add an operation to the front of the list of operations.
Matrix create_identity(int matrix_size)
Call to create an identity matrix.
Definition: common.cpp:128
operation_type get_type()
Call to get the type of the operation.
Definition: Operation.cpp:174
bool is_lambda_parameter()
Call to check whether Lambda is a free parameter of the gate.
Definition: U3.cpp:419
bool is_phi_parameter()
Call to check whether Phi is a free parameter of the gate.
Definition: U3.cpp:411
Matrix get_matrix(const double *parameters)
Call to retrieve the operation matrix.
Definition: U3.cpp:138
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.
Matrix copy()
Call to create a copy of the matrix.
Definition: matrix.cpp:54
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...
double real
the real part of a complex number
Definition: QGDTypes.h:41
int get_operation_num()
Call to get the number of operations grouped in the class.
void set_verbose(bool verbose_in)
Call to set the verbose attribute to true or false.
Matrix get_matrix()
Call to retrieve the operation matrix.
Definition: CNOT.cpp:79
int finalizing_parameter_num
the number of the finalizing (deterministic) parameters of operations rotating the disentangled qubit...
int get_control_qbit()
Call to get the index of the control qubit.
Definition: Operation.cpp:157
bool check_optimization_solution()
check_optimization_solution
int optimization_block
number of operation blocks used in one shot of the optimization process
double decomposition_error
error of the unitarity of the final decomposition
int set_iteration_loops(int n, int iteration_loops_in)
Set the number of iteration loops during the subdecomposition of the n-th qubit.
operation_type
Type definition of operation types (also generalized for decomposition classes derived from the class...
Definition: Operation.h:33
int cnot
The number of CNOT gates.
Definition: QGDTypes.h:51
void list_operations(int start_index)
Call to print the operations decomposing the initial unitary.
double global_target_minimum
The global target minimum of the optimization problem.
void set_optimization_blocks(int optimization_block_in)
Call to set the number of operation blocks to be optimized in one shot.
double imag
the imaginary part of a complex number
Definition: QGDTypes.h:43
int max_iterations
Maximal number of iterations allowed in the optimization process.
virtual double optimization_problem(const double *parameters)
This is an abstact definition of function giving the cost functions measuring the entaglement of the ...