Sequential Quantum Gate Decomposer  v1.9.6
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 
62 N_Qubit_Decomposition::N_Qubit_Decomposition( Matrix_float Umtx_in, int qbit_num_in, bool optimize_layer_num_in, std::map<std::string, Config_Element>& config_in, guess_type initial_guess_in ) : Optimization_Interface(Umtx_in, qbit_num_in, optimize_layer_num_in, config_in, initial_guess_in) {
63 
65 
66 }
67 
68 
69 
74 
75 
76 }
77 
78 
79 
80 
85 void
87 
88 
89 
90  //The stringstream input to store the output messages.
91  std::stringstream sstream;
92  sstream << "***************************************************************" << std::endl;
93  sstream << "Starting to disentangle " << qbit_num << "-qubit matrix" << std::endl;
94  sstream << "***************************************************************" << std::endl << std::endl << std::endl;
95  print(sstream, 1);
96 
97 
98 
99 
100  // temporarily turn off OpenMP parallelism
101 #if BLAS==0 // undefined BLAS
104 #elif BLAS==1 // MKL
105  num_threads = mkl_get_max_threads();
106  MKL_Set_Num_Threads(1);
107 #elif BLAS==2 //OpenBLAS
108  num_threads = openblas_get_num_threads();
109  openblas_set_num_threads(1);
110 #endif
111 
112  //measure the time for the decompositin
113  tbb::tick_count start_time = tbb::tick_count::now();
114 
115  // create an instance of class to disentangle the given qubit pair
117 
118  // setting the verbosity
119  cSub_decomposition->set_verbose( verbose );
120 
121  // setting the debugfile name
122  cSub_decomposition->set_debugfile( debugfile_name );
123 
124  // setting the maximal number of layers used in the subdecomposition
125  cSub_decomposition->set_max_layer_num( max_layer_num );
126 
127  // setting the number of successive identical blocks used in the subdecomposition
128  cSub_decomposition->set_identical_blocks( identical_blocks );
129 
130  // setting the iteration loops in each step of the optimization process
131  cSub_decomposition->set_iteration_loops( iteration_loops );
132 
133  // set custom gate structure if given
134  std::map<int,Gates_block*>::iterator key_it = gate_structure.find( qbit_num );
135  if ( key_it != gate_structure.end() ) {
136  cSub_decomposition->set_custom_gate_layers( gate_structure[qbit_num] );
137  }
138 
139  // The maximal error of the optimization problem
140  cSub_decomposition->set_optimization_tolerance( optimization_tolerance );
141 
142  // setting the maximal number of iterations in the disentangling process
143  cSub_decomposition->optimization_block = optimization_block;
144 
145  // setting the number of operators in one sub-layer of the disentangling process
146  //cSub_decomposition->max_outer_iterations = self.max_outer_iterations
147 
148  //start to disentangle the qubit pair
149  cSub_decomposition->disentangle_submatrices();
150  if ( !cSub_decomposition->subdisentaglement_done) {
151  return;
152  }
153 //return;
154  // saving the subunitarization gates
155  extract_subdecomposition_results( cSub_decomposition );
156 
157  delete cSub_decomposition;
158  cSub_decomposition = NULL;
159 
160  // decompose the qubits in the disentangled submatrices
162 
163 
164 
165  if (finalize_decomp) {
166  // finalizing the decompostition
168 
169  int optimization_block_orig = optimization_block;
170  if ( optimization_block > 0 ) {
172  }
173  //max_outer_iterations = 4;
174 
175 
176  // final tuning of the decomposition parameters
178 
179  optimization_block = optimization_block_orig;
180 
181 
182  // calculating the final error of the decomposition
183  Matrix matrix_decomposed = Umtx.copy();
184  apply_to( optimized_parameters_mtx, matrix_decomposed );
185 
186  calc_decomposition_error( matrix_decomposed );
187 
188 
189  sstream.str("");
190  sstream << "In the decomposition with error = " << decomposition_error << " were used " << layer_num << " gates with:" << std::endl;
191 
192  // get the number of gates used in the decomposition
193  std::map<std::string, int>&& gate_nums = get_gate_nums();
194 
195  for( auto it=gate_nums.begin(); it != gate_nums.end(); it++ ) {
196  sstream << it->second << " " << it->first << " gates" << std::endl;
197  }
198 
199 
200  sstream << std::endl;
201  tbb::tick_count current_time = tbb::tick_count::now();
202 
203  sstream << "--- In total " << (current_time - start_time).seconds() << " seconds elapsed during the decomposition ---" << std::endl;
204  print(sstream, 1);
205 
206 
207 
208 
209  }
210 
211 #if BLAS==0 // undefined BLAS
213 #elif BLAS==1 //MKL
214  MKL_Set_Num_Threads(num_threads);
215 #elif BLAS==2 //OpenBLAS
216  openblas_set_num_threads(num_threads);
217 #endif
218 
219 }
220 
221 
222 
223 
227 void
229 
230 
232  std::stringstream sstream;
233  sstream << "Decomposition was already finalized" << std::endl;
234  print(sstream, 1);
235  return;
236  }
237 
238  if (qbit_num == 2) {
239  return;
240  }
241 
242  // obtaining the subdecomposed submatrices
243  Matrix subdecomposed_matrix_mtx = Umtx.copy();
244  apply_to( optimized_parameters_mtx, subdecomposed_matrix_mtx );
245  QGD_Complex16* subdecomposed_matrix = subdecomposed_matrix_mtx.get_data();
246 
247  // get the most unitary submatrix
248  // get the number of 2qubit submatrices
249  int submatrices_num_row = 2;
250 
251  // get the size of the submatrix
252  int submatrix_size = int(matrix_size/2);
253 
254  // fill up the submatrices and select the most unitary submatrix
255 
256  Matrix most_unitary_submatrix_mtx = Matrix(submatrix_size, submatrix_size );
257  QGD_Complex16* most_unitary_submatrix = most_unitary_submatrix_mtx.get_data();
258  double unitary_error_min = 1e8;
259 
260  for (int idx=0; idx<submatrices_num_row; idx++) { // in range(0,submatrices_num_row):
261  for (int jdx=0; jdx<submatrices_num_row; jdx++) { // in range(0,submatrices_num_row):
262 
263  Matrix submatrix_mtx = Matrix(submatrix_size, submatrix_size);
264  QGD_Complex16* submatrix = submatrix_mtx.get_data();
265 
266  for ( int row_idx=0; row_idx<submatrix_size; row_idx++ ) {
267  int matrix_offset = idx*(matrix_size*submatrix_size) + jdx*(submatrix_size) + row_idx*matrix_size;
268  int submatrix_offset = row_idx*submatrix_size;
269  memcpy(submatrix+submatrix_offset, subdecomposed_matrix+matrix_offset, submatrix_size*sizeof(QGD_Complex16));
270  }
271 
272  // calculate the product of submatrix*submatrix'
273  Matrix submatrix_mtx_adj = submatrix_mtx;
274  submatrix_mtx_adj.transpose();
275  submatrix_mtx_adj.conjugate();
276  Matrix submatrix_prod = dot( submatrix_mtx, submatrix_mtx_adj);
277 
278  // subtract corner element
279  QGD_Complex16 corner_element = submatrix_prod[0];
280  for (int row_idx=0; row_idx<submatrix_size; row_idx++) {
281  submatrix_prod[row_idx*submatrix_size+row_idx].real = submatrix_prod[row_idx*submatrix_size+row_idx].real - corner_element.real;
282  submatrix_prod[row_idx*submatrix_size+row_idx].imag = submatrix_prod[row_idx*submatrix_size+row_idx].imag - corner_element.imag;
283  }
284 
285  double unitary_error = cblas_dznrm2( submatrix_size*submatrix_size, submatrix_prod.get_data(), 1 );
286 
287  if (unitary_error < unitary_error_min) {
288  unitary_error_min = unitary_error;
289  memcpy(most_unitary_submatrix, submatrix, submatrix_size*submatrix_size*sizeof(QGD_Complex16));
290  }
291 
292  }
293  }
294 
295  // if the qubit number in the submatirx is greater than 2 new N-qubit decomposition is started
296 
297  // create class tp decompose submatrices
298  N_Qubit_Decomposition* cdecomposition = new N_Qubit_Decomposition(most_unitary_submatrix_mtx, qbit_num-1, optimize_layer_num, config, initial_guess);
299 
300  // setting the verbosity
301  cdecomposition->set_verbose( verbose );
302 
303  // setting the debugfile name
304  cdecomposition->set_debugfile( debugfile_name );
305 
306  // Maximal number of iteartions in the optimization process
307  cdecomposition->set_max_iteration(max_outer_iterations);
308 
309  // Set the number of identical successive blocks in the sub-decomposition
310  cdecomposition->set_identical_blocks(identical_blocks);
311 
312  // Set the maximal number of layers for the sub-decompositions
313  cdecomposition->set_max_layer_num(max_layer_num);
314 
315  // setting the iteration loops in each step of the optimization process
316  cdecomposition->set_iteration_loops( iteration_loops );
317 
318  // setting gate layer
319  cdecomposition->set_optimization_blocks( optimization_block );
320 
321  // set custom gate structure if given
322  cdecomposition->set_custom_gate_structure( gate_structure );
323 
324  // set the toleration of the optimization process
326 
327  // starting the decomposition of the random unitary
328  cdecomposition->start_decomposition(false);
329 
330 
331  // saving the decomposition gates
332  extract_subdecomposition_results( reinterpret_cast<Sub_Matrix_Decomposition*>(cdecomposition) );
333 
334  delete cdecomposition;
335 
336 }
337 
338 
339 
344 
345  // store the gates, initial unitary and optimized parameters
346  Matrix Umtx_save = Umtx.copy();
347  Gates_block* gates_save = static_cast<Gates_block*>(this)->clone();
348  Matrix_real optimized_parameters_mtx_save = optimized_parameters_mtx;
349 
350 
351  // get the transformed matrix resulted by the gates in the list
353 
354 
355 
356  // preallocate the storage for the finalizing parameters
358 
359  release_gates();
361 
362  for( int target_qbit=0; target_qbit<qbit_num; target_qbit++) {
364  }
365 
366 
367  Matrix_real solution_guess_tmp = Matrix_real(1, parameter_num);
368  memset( solution_guess_tmp.get_data(), 0, solution_guess_tmp.size()*sizeof(double) );
369 
370  solve_layer_optimization_problem( parameter_num, solution_guess_tmp );
371 
372  //std::cout << "current_minimum: " << current_minimum << std::endl;
373 
374  // combine results
375  Gates_block* gates_save2 = static_cast<Gates_block*>(this)->clone();
376  Matrix_real optimized_parameters_mtx_save2 = optimized_parameters_mtx;
377 
378  release_gates();
379  this->combine( gates_save );
380  this->combine( gates_save2 );
381 
382  Matrix_real parameters_joined( 1, optimized_parameters_mtx_save.size()+optimized_parameters_mtx_save2.size() );
383  memcpy( parameters_joined.get_data(), optimized_parameters_mtx_save.get_data(), optimized_parameters_mtx_save.size()*sizeof(double) );
384 
385  memcpy( parameters_joined.get_data()+optimized_parameters_mtx_save.size(),
386  optimized_parameters_mtx_save2.get_data(),
387  optimized_parameters_mtx_save2.size()*sizeof(double) );
388 
389  optimized_parameters_mtx = parameters_joined;
390 
391  Umtx = Umtx_save;
392 
393  Matrix final_matrix = Umtx.copy();
394  apply_to( optimized_parameters_mtx, final_matrix );
395 
396  // indicate that the decomposition was finalized
398 
399  // calculating the final error of the decomposition
400  subtract_diag( final_matrix, final_matrix[0] );
401  decomposition_error = cblas_dznrm2( matrix_size*matrix_size, (void*)final_matrix.get_data(), 1 );
402 
403  std::stringstream sstream;
404 
405  // get the number of gates used in the decomposition
406  std::map<std::string, int>&& gate_nums = get_gate_nums();
407 
408  for( auto it=gate_nums.begin(); it != gate_nums.end(); it++ ) {
409  sstream << it->second << " " << it->first << " gates" << std::endl;
410  }
411 
412 
413  //The stringstream input to store the output messages
414  sstream << "The error of the decomposition after finalyzing gates is " << decomposition_error << " with " << layer_num << " layers" << std::endl;
415  print(sstream, 1);
416 
417 
418 
419 
420 }
421 
422 
427 void
429 
430 
431  // get the unitarization parameters
432  int parameter_num_sub_decomp = cSub_decomposition->get_parameter_num();
433 
434  // adding the unitarization parameters to the ones stored in the class
435  Matrix_real optimized_parameters_tmp(1, parameter_num+parameter_num_sub_decomp);
436  Matrix_real parameters_submatrix = Matrix_real( optimized_parameters_tmp.get_data()+parameter_num, 1, parameter_num_sub_decomp );
437 
438  cSub_decomposition->get_optimized_parameters(parameters_submatrix.get_data());
439 
440  if ( optimized_parameters_mtx.size() > 0 ) {
441  memcpy(optimized_parameters_tmp.get_data(), optimized_parameters_mtx.get_data(), parameter_num*sizeof(double));
442  }
443 
444  optimized_parameters_mtx = optimized_parameters_tmp;
445 
446  // cloning the gate list obtained during the subdecomposition
447  std::vector<Gate*> sub_decomp_ops = cSub_decomposition->get_gates();
448  int gate_num = cSub_decomposition->get_gate_num();
449 
450  for ( int idx = 0; idx<gate_num; idx++) {
451  Gate* op = sub_decomp_ops[idx];
452  Gate* op_cloned = op->clone();
453  add_gate( op_cloned );
454 
455  }
456 }
457 
458 
459 
460 
461 
466 void N_Qubit_Decomposition::set_custom_gate_structure( std::map<int, Gates_block*> gate_structure_in ) {
467 
468 
469  for ( std::map<int,Gates_block*>::iterator it=gate_structure_in.begin(); it!= gate_structure_in.end(); it++ ) {
470  int key = it->first;
471 
472  std::map<int,Gates_block*>::iterator key_it = gate_structure.find( key );
473 
474  if ( key_it != gate_structure.end() ) {
475  gate_structure.erase( key_it );
476  }
477 
478  gate_structure.insert( std::pair<int,Gates_block*>(key, it->second->clone()));
479 
480  }
481 
482 }
483 
484 
485 
486 
493 int N_Qubit_Decomposition::set_identical_blocks( int n, int identical_blocks_in ) {
494 
495  std::map<int,int>::iterator key_it = identical_blocks.find( n );
496 
497  if ( key_it != identical_blocks.end() ) {
498  identical_blocks.erase( key_it );
499  }
500 
501  identical_blocks.insert( std::pair<int, int>(n, identical_blocks_in) );
502 
503  return 0;
504 
505 }
506 
507 
508 
514 int N_Qubit_Decomposition::set_identical_blocks( std::map<int, int> identical_blocks_in ) {
515 
516  for ( std::map<int,int>::iterator it=identical_blocks_in.begin(); it!= identical_blocks_in.end(); it++ ) {
517  set_identical_blocks( it->first, it->second );
518  }
519 
520  return 0;
521 
522 }
523 
524 
525 
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:1351
void add_gate(Gate *gate)
Append a general gate to the list of gates.
key
Definition: noise.py:86
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:98
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.
virtual Gates_block * clone() override
Create a clone of the present class.
int layer_num
number of gate layers
Definition: Gates_block.h:51
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...
int matrix_size
The size N of the NxN matrix associated with the operations.
Definition: Gate.h:106
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...
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.
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.
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
Matrix copy() const
Call to create a copy of the matrix.
Definition: matrix.h:57
void set_optimization_tolerance(double tolerance_in)
Call to set the tolerance of the optimization processes.
Double-precision complex matrix (float64).
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:277
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:44
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.
Single-precision complex matrix (float32).
Definition: matrix_float.h:41
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:86
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:108
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.
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:94
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 get_parameter_num() override
Call to get the number of free parameters.
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:41
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