Sequential Quantum Gate Decomposer  v1.9.3
Powerful decomposition of general unitarias into one- and two-qubit gates gates
N_Qubit_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 */
24 #include "N_Qubit_Decomposition.h"
26 
27 
28 
34 
36 
37 
38 
39 }
40 
41 
42 
43 
44 
53 N_Qubit_Decomposition::N_Qubit_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 ) : Optimization_Interface(Umtx_in, qbit_num_in, optimize_layer_num_in, config_in, initial_guess_in) {
54 
56 
57 }
58 
59 
60 
65 
66 
67 }
68 
69 
70 
71 
76 void
78 
79 
80 
81  //The stringstream input to store the output messages.
82  std::stringstream sstream;
83  sstream << "***************************************************************" << std::endl;
84  sstream << "Starting to disentangle " << qbit_num << "-qubit matrix" << std::endl;
85  sstream << "***************************************************************" << std::endl << std::endl << std::endl;
86  print(sstream, 1);
87 
88 
89 
90 
91  // temporarily turn off OpenMP parallelism
92 #if BLAS==0 // undefined BLAS
95 #elif BLAS==1 // MKL
96  num_threads = mkl_get_max_threads();
97  MKL_Set_Num_Threads(1);
98 #elif BLAS==2 //OpenBLAS
99  num_threads = openblas_get_num_threads();
100  openblas_set_num_threads(1);
101 #endif
102 
103  //measure the time for the decompositin
104  tbb::tick_count start_time = tbb::tick_count::now();
105 
106  // create an instance of class to disentangle the given qubit pair
108 
109  // setting the verbosity
110  cSub_decomposition->set_verbose( verbose );
111 
112  // setting the debugfile name
113  cSub_decomposition->set_debugfile( debugfile_name );
114 
115  // setting the maximal number of layers used in the subdecomposition
116  cSub_decomposition->set_max_layer_num( max_layer_num );
117 
118  // setting the number of successive identical blocks used in the subdecomposition
119  cSub_decomposition->set_identical_blocks( identical_blocks );
120 
121  // setting the iteration loops in each step of the optimization process
122  cSub_decomposition->set_iteration_loops( iteration_loops );
123 
124  // set custom gate structure if given
125  std::map<int,Gates_block*>::iterator key_it = gate_structure.find( qbit_num );
126  if ( key_it != gate_structure.end() ) {
127  cSub_decomposition->set_custom_gate_layers( gate_structure[qbit_num] );
128  }
129 
130  // The maximal error of the optimization problem
131  cSub_decomposition->set_optimization_tolerance( optimization_tolerance );
132 
133  // setting the maximal number of iterations in the disentangling process
134  cSub_decomposition->optimization_block = optimization_block;
135 
136  // setting the number of operators in one sub-layer of the disentangling process
137  //cSub_decomposition->max_outer_iterations = self.max_outer_iterations
138 
139  //start to disentangle the qubit pair
140  cSub_decomposition->disentangle_submatrices();
141  if ( !cSub_decomposition->subdisentaglement_done) {
142  return;
143  }
144 //return;
145  // saving the subunitarization gates
146  extract_subdecomposition_results( cSub_decomposition );
147 
148  delete cSub_decomposition;
149  cSub_decomposition = NULL;
150 
151  // decompose the qubits in the disentangled submatrices
153 
154 
155 
156  if (finalize_decomp) {
157  // finalizing the decompostition
159 
160  int optimization_block_orig = optimization_block;
161  if ( optimization_block > 0 ) {
163  }
164  //max_outer_iterations = 4;
165 
166 
167  // final tuning of the decomposition parameters
169 
170  optimization_block = optimization_block_orig;
171 
172 
173  // calculating the final error of the decomposition
174  Matrix matrix_decomposed = Umtx.copy();
175  apply_to( optimized_parameters_mtx, matrix_decomposed );
176 
177  calc_decomposition_error( matrix_decomposed );
178 
179 
180  sstream.str("");
181  sstream << "In the decomposition with error = " << decomposition_error << " were used " << layer_num << " gates with:" << std::endl;
182 
183  // get the number of gates used in the decomposition
184  std::map<std::string, int>&& gate_nums = get_gate_nums();
185 
186  for( auto it=gate_nums.begin(); it != gate_nums.end(); it++ ) {
187  sstream << it->second << " " << it->first << " gates" << std::endl;
188  }
189 
190 
191  sstream << std::endl;
192  tbb::tick_count current_time = tbb::tick_count::now();
193 
194  sstream << "--- In total " << (current_time - start_time).seconds() << " seconds elapsed during the decomposition ---" << std::endl;
195  print(sstream, 1);
196 
197 
198 
199 
200  }
201 
202 #if BLAS==0 // undefined BLAS
204 #elif BLAS==1 //MKL
205  MKL_Set_Num_Threads(num_threads);
206 #elif BLAS==2 //OpenBLAS
207  openblas_set_num_threads(num_threads);
208 #endif
209 
210 }
211 
212 
213 
214 
218 void
220 
221 
223  std::stringstream sstream;
224  sstream << "Decomposition was already finalized" << std::endl;
225  print(sstream, 1);
226  return;
227  }
228 
229  if (qbit_num == 2) {
230  return;
231  }
232 
233  // obtaining the subdecomposed submatrices
234  Matrix subdecomposed_matrix_mtx = Umtx.copy();
235  apply_to( optimized_parameters_mtx, subdecomposed_matrix_mtx );
236  QGD_Complex16* subdecomposed_matrix = subdecomposed_matrix_mtx.get_data();
237 
238  // get the most unitary submatrix
239  // get the number of 2qubit submatrices
240  int submatrices_num_row = 2;
241 
242  // get the size of the submatrix
243  int submatrix_size = int(matrix_size/2);
244 
245  // fill up the submatrices and select the most unitary submatrix
246 
247  Matrix most_unitary_submatrix_mtx = Matrix(submatrix_size, submatrix_size );
248  QGD_Complex16* most_unitary_submatrix = most_unitary_submatrix_mtx.get_data();
249  double unitary_error_min = 1e8;
250 
251  for (int idx=0; idx<submatrices_num_row; idx++) { // in range(0,submatrices_num_row):
252  for (int jdx=0; jdx<submatrices_num_row; jdx++) { // in range(0,submatrices_num_row):
253 
254  Matrix submatrix_mtx = Matrix(submatrix_size, submatrix_size);
255  QGD_Complex16* submatrix = submatrix_mtx.get_data();
256 
257  for ( int row_idx=0; row_idx<submatrix_size; row_idx++ ) {
258  int matrix_offset = idx*(matrix_size*submatrix_size) + jdx*(submatrix_size) + row_idx*matrix_size;
259  int submatrix_offset = row_idx*submatrix_size;
260  memcpy(submatrix+submatrix_offset, subdecomposed_matrix+matrix_offset, submatrix_size*sizeof(QGD_Complex16));
261  }
262 
263  // calculate the product of submatrix*submatrix'
264  Matrix submatrix_mtx_adj = submatrix_mtx;
265  submatrix_mtx_adj.transpose();
266  submatrix_mtx_adj.conjugate();
267  Matrix submatrix_prod = dot( submatrix_mtx, submatrix_mtx_adj);
268 
269  // subtract corner element
270  QGD_Complex16 corner_element = submatrix_prod[0];
271  for (int row_idx=0; row_idx<submatrix_size; row_idx++) {
272  submatrix_prod[row_idx*submatrix_size+row_idx].real = submatrix_prod[row_idx*submatrix_size+row_idx].real - corner_element.real;
273  submatrix_prod[row_idx*submatrix_size+row_idx].imag = submatrix_prod[row_idx*submatrix_size+row_idx].imag - corner_element.imag;
274  }
275 
276  double unitary_error = cblas_dznrm2( submatrix_size*submatrix_size, submatrix_prod.get_data(), 1 );
277 
278  if (unitary_error < unitary_error_min) {
279  unitary_error_min = unitary_error;
280  memcpy(most_unitary_submatrix, submatrix, submatrix_size*submatrix_size*sizeof(QGD_Complex16));
281  }
282 
283  }
284  }
285 
286  // if the qubit number in the submatirx is greater than 2 new N-qubit decomposition is started
287 
288  // create class tp decompose submatrices
289  N_Qubit_Decomposition* cdecomposition = new N_Qubit_Decomposition(most_unitary_submatrix_mtx, qbit_num-1, optimize_layer_num, config, initial_guess);
290 
291  // setting the verbosity
292  cdecomposition->set_verbose( verbose );
293 
294  // setting the debugfile name
295  cdecomposition->set_debugfile( debugfile_name );
296 
297  // Maximal number of iteartions in the optimization process
298  cdecomposition->set_max_iteration(max_outer_iterations);
299 
300  // Set the number of identical successive blocks in the sub-decomposition
301  cdecomposition->set_identical_blocks(identical_blocks);
302 
303  // Set the maximal number of layers for the sub-decompositions
304  cdecomposition->set_max_layer_num(max_layer_num);
305 
306  // setting the iteration loops in each step of the optimization process
307  cdecomposition->set_iteration_loops( iteration_loops );
308 
309  // setting gate layer
310  cdecomposition->set_optimization_blocks( optimization_block );
311 
312  // set custom gate structure if given
313  cdecomposition->set_custom_gate_structure( gate_structure );
314 
315  // set the toleration of the optimization process
317 
318  // starting the decomposition of the random unitary
319  cdecomposition->start_decomposition(false);
320 
321 
322  // saving the decomposition gates
323  extract_subdecomposition_results( reinterpret_cast<Sub_Matrix_Decomposition*>(cdecomposition) );
324 
325  delete cdecomposition;
326 
327 }
328 
329 
330 
335 
336  // store the gates, initial unitary and optimized parameters
337  Matrix Umtx_save = Umtx.copy();
338  Gates_block* gates_save = static_cast<Gates_block*>(this)->clone();
339  Matrix_real optimized_parameters_mtx_save = optimized_parameters_mtx;
340 
341 
342  // get the transformed matrix resulted by the gates in the list
344 
345 
346 
347  // preallocate the storage for the finalizing parameters
349 
350  release_gates();
352 
353  for( int target_qbit=0; target_qbit<qbit_num; target_qbit++) {
355  }
356 
357 
358  Matrix_real solution_guess_tmp = Matrix_real(1, parameter_num);
359  memset( solution_guess_tmp.get_data(), 0, solution_guess_tmp.size()*sizeof(double) );
360 
361  solve_layer_optimization_problem( parameter_num, solution_guess_tmp );
362 
363  //std::cout << "current_minimum: " << current_minimum << std::endl;
364 
365  // combine results
366  Gates_block* gates_save2 = static_cast<Gates_block*>(this)->clone();
367  Matrix_real optimized_parameters_mtx_save2 = optimized_parameters_mtx;
368 
369  release_gates();
370  this->combine( gates_save );
371  this->combine( gates_save2 );
372 
373  Matrix_real parameters_joined( 1, optimized_parameters_mtx_save.size()+optimized_parameters_mtx_save2.size() );
374  memcpy( parameters_joined.get_data(), optimized_parameters_mtx_save.get_data(), optimized_parameters_mtx_save.size()*sizeof(double) );
375 
376  memcpy( parameters_joined.get_data()+optimized_parameters_mtx_save.size(),
377  optimized_parameters_mtx_save2.get_data(),
378  optimized_parameters_mtx_save2.size()*sizeof(double) );
379 
380  optimized_parameters_mtx = parameters_joined;
381 
382  Umtx = Umtx_save;
383 
384  Matrix final_matrix = Umtx.copy();
385  apply_to( optimized_parameters_mtx, final_matrix );
386 
387  // indicate that the decomposition was finalized
389 
390  // calculating the final error of the decomposition
391  subtract_diag( final_matrix, final_matrix[0] );
392  decomposition_error = cblas_dznrm2( matrix_size*matrix_size, (void*)final_matrix.get_data(), 1 );
393 
394  std::stringstream sstream;
395 
396  // get the number of gates used in the decomposition
397  std::map<std::string, int>&& gate_nums = get_gate_nums();
398 
399  for( auto it=gate_nums.begin(); it != gate_nums.end(); it++ ) {
400  sstream << it->second << " " << it->first << " gates" << std::endl;
401  }
402 
403 
404  //The stringstream input to store the output messages
405  sstream << "The error of the decomposition after finalyzing gates is " << decomposition_error << " with " << layer_num << " layers" << std::endl;
406  print(sstream, 1);
407 
408 
409 
410 
411 }
412 
413 
418 void
420 
421 
422  // get the unitarization parameters
423  int parameter_num_sub_decomp = cSub_decomposition->get_parameter_num();
424 
425  // adding the unitarization parameters to the ones stored in the class
426  Matrix_real optimized_parameters_tmp(1, parameter_num+parameter_num_sub_decomp);
427  Matrix_real parameters_submatrix = Matrix_real( optimized_parameters_tmp.get_data()+parameter_num, 1, parameter_num_sub_decomp );
428 
429  cSub_decomposition->get_optimized_parameters(parameters_submatrix.get_data());
430 
431  if ( optimized_parameters_mtx.size() > 0 ) {
432  memcpy(optimized_parameters_tmp.get_data(), optimized_parameters_mtx.get_data(), parameter_num*sizeof(double));
433  }
434 
435  optimized_parameters_mtx = optimized_parameters_tmp;
436 
437  // cloning the gate list obtained during the subdecomposition
438  std::vector<Gate*> sub_decomp_ops = cSub_decomposition->get_gates();
439  int gate_num = cSub_decomposition->get_gate_num();
440 
441  for ( int idx = 0; idx<gate_num; idx++) {
442  Gate* op = sub_decomp_ops[idx];
443  Gate* op_cloned = op->clone();
444  add_gate( op_cloned );
445 
446  }
447 }
448 
449 
450 
451 
452 
457 void N_Qubit_Decomposition::set_custom_gate_structure( std::map<int, Gates_block*> gate_structure_in ) {
458 
459 
460  for ( std::map<int,Gates_block*>::iterator it=gate_structure_in.begin(); it!= gate_structure_in.end(); it++ ) {
461  int key = it->first;
462 
463  std::map<int,Gates_block*>::iterator key_it = gate_structure.find( key );
464 
465  if ( key_it != gate_structure.end() ) {
466  gate_structure.erase( key_it );
467  }
468 
469  gate_structure.insert( std::pair<int,Gates_block*>(key, it->second->clone()));
470 
471  }
472 
473 }
474 
475 
476 
477 
484 int N_Qubit_Decomposition::set_identical_blocks( int n, int identical_blocks_in ) {
485 
486  std::map<int,int>::iterator key_it = identical_blocks.find( n );
487 
488  if ( key_it != identical_blocks.end() ) {
489  identical_blocks.erase( key_it );
490  }
491 
492  identical_blocks.insert( std::pair<int, int>(n, identical_blocks_in) );
493 
494  return 0;
495 
496 }
497 
498 
499 
505 int N_Qubit_Decomposition::set_identical_blocks( std::map<int, int> identical_blocks_in ) {
506 
507  for ( std::map<int,int>::iterator it=identical_blocks_in.begin(); it!= identical_blocks_in.end(); it++ ) {
508  set_identical_blocks( it->first, it->second );
509  }
510 
511  return 0;
512 
513 }
514 
515 
516 
517 
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:38
N_Qubit_Decomposition()
Nullary constructor of the class.
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
void set_optimizer(optimization_aglorithms alg_in)
Call to set the optimizer engine to be used in solving the optimization problem.
bool decomposition_finalized
The optimized parameters for the gates.
std::map< std::string, int > get_gate_nums()
Call to get the number of the individual gate types in the list of gates.
void disentangle_submatrices()
Start the optimization process to disentangle the most significant qubit from the others...
std::map< int, int > identical_blocks
A map of <int n: int num> indicating that how many identical successive blocks should be used in the ...
virtual Gate * clone()
Call to create a clone of the present class.
Definition: Gate.cpp:513
void add_gate(Gate *gate)
Append a general gate to the list of gates.
void decompose_submatrix()
Start the decompostion process to recursively decompose the submatrices.
int target_qbit
The index of the qubit on which the operation acts (target_qbit >= 0)
Definition: Gate.h:85
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.
void release_gates()
Call to release the stored gates.
int layer_num
number of gate layers
Definition: Gates_block.h:48
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...
virtual void apply_to(Matrix_real &parameters_mtx, Matrix &input, int parallel=0)
Call to apply the gate on the input array/matrix Gates_block*input.
int matrix_size
The size N of the NxN matrix associated with the operations.
Definition: Gate.h:89
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
virtual void start_decomposition(bool finalize_decomp=true)
Start the disentanglig process of the unitary.
int get_gate_num()
Call to get the number of gates grouped in the class.
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...
void finalize_decomposition()
After the main optimization problem is solved, the indepent qubits can be rotated into state |0> by t...
int max_outer_iterations
Maximal number of iterations allowed in the optimization process.
double optimization_tolerance
The maximal allowed error of the optimization problem (The error of the decomposition would scale wit...
int get_parameter_num()
Call to get the number of free parameters.
void set_custom_gate_structure(std::map< int, Gates_block *> gate_structure_in)
Call to set custom layers to the gate structure that are intended to be used in the subdecomposition...
void set_debugfile(std::string debugfile)
Call to set the debugfile name.
Definition: logging.cpp:95
virtual ~N_Qubit_Decomposition()
Destructor of the class.
Matrix_real get_optimized_parameters()
Call to get the optimized parameters.
void add_u3(int target_qbit)
Append a U3 gate to the list of gates.
double cblas_dznrm2(OPENBLAS_CONST blasint N, OPENBLAS_CONST void *X, OPENBLAS_CONST blasint incX)
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
bool subdisentaglement_done
logical value indicating whether the disentamglement of a qubit from the othetrs was done or not ...
void combine(Gates_block *op_block)
Call to append the gates of an gate block to the current block.
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
Store the number of OpenMP threads. (During the calculations OpenMP multithreading is turned off...
Structure type representing complex numbers in the SQUANDER package.
Definition: QGDTypes.h:38
int verbose
Set the verbosity level of the output messages.
Definition: logging.h:50
void set_optimization_tolerance(double tolerance_in)
Call to set the tolerance of the optimization processes.
virtual Gates_block * clone()
Create a clone of the present class.
Class to store data of complex arrays and its properties.
Definition: matrix.h:38
void calc_decomposition_error(Matrix &decomposed_matrix)
Calculate the error of the decomposition according to the spectral norm of , where is the unitary pr...
int size() const
Call to get the number of the allocated elements.
void subtract_diag(Matrix &mtx, QGD_Complex16 scalar)
Call to subtract a scalar from the diagonal of a complex matrix.
Definition: common.cpp:238
std::string debugfile_name
String variable. Set the debug file name.
Definition: logging.h:56
A class responsible for grouping two-qubit (CNOT,CZ,CH) and one-qubit gates into layers.
Definition: Gates_block.h:41
void omp_set_num_threads(int num_threads)
Set the number of threads on runtime in MKL.
void transpose()
Call to transpose (or un-transpose) the matrix for CBLAS functions.
void extract_subdecomposition_results(Sub_Matrix_Decomposition *cSub_decomposition)
Call to extract and store the calculated parameters and gates of the sub-decomposition processes...
guess_type
Type definition of the types of the initial guess.
void set_verbose(int verbose_in)
Call to set the verbose attribute.
Definition: logging.cpp:85
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:73
std::map< int, Gates_block * > gate_structure
A map of <int n: Gates_block* block> describing custom gate structure to be used in the decomposition...
int parameter_num
the number of free parameters of the operation
Definition: Gate.h:91
void solve_layer_optimization_problem(int num_of_parameters, Matrix_real solution_guess)
Call to solve layer by layer the optimization problem via calling one of the implemented algorithms...
Header file for the paralleized calculation of the cost function of the final optimization problem (s...
Matrix Umtx
The unitary to be decomposed.
Matrix copy()
Call to create a copy of the matrix.
Definition: matrix.cpp:105
double real
the real part of a complex number
Definition: QGDTypes.h:40
void final_optimization()
final optimization procedure improving the accuracy of the decompositin when all the qubits were alre...
void conjugate()
Call to conjugate (or un-conjugate) the matrix for CBLAS functions.
int qbit_num
number of qubits spanning the matrix of the operation
Definition: Gate.h:81
bool optimize_layer_num
logical value. Set true to optimize the minimum number of gate layers required in the decomposition...
int finalizing_parameter_num
the number of the finalizing (deterministic) parameters of gates rotating the disentangled qubits int...
void set_max_iteration(int max_outer_iterations_in)
Call to set the maximal number of the iterations in the optimization process.
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
double decomposition_error
error 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.
Header file for a class to determine the decomposition of an N-qubit unitary into a sequence of CNOT ...
Matrix_real optimized_parameters_mtx
The optimized parameters for the gates.
int set_identical_blocks(int n, int identical_blocks_in)
Set the number of identical successive blocks during the subdecomposition of the n-th qubit...
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:39
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
double imag
the imaginary part of a complex number
Definition: QGDTypes.h:42
int omp_get_max_threads()
get the number of threads in MKL