Sequential Quantum Gate Decomposer  v1.9.3
Powerful decomposition of general unitarias into one- and two-qubit gates gates
NN.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 */
23 #include "NN.h"
25 #include <cstdlib>
26 #include <time.h>
27 
28 #include "tbb/tbb.h"
29 
30 
34 NN::NN() {
35 
36  // seedign the random generator
37  gen = std::mt19937(rd());
38 
39 
40 #if CBLAS==1
41  num_threads = mkl_get_max_threads();
42 #elif CBLAS==2
43  num_threads = openblas_get_num_threads();
44 #endif
45 
46 
47 
48 }
49 
50 
51 
56 NN::NN( std::vector<matrix_base<int>> topology_in ) {
57 
58  // seedign the random generator
59  gen = std::mt19937(rd());
60 
61 
62  // setting the topology
63  topology = topology_in;
64 
65 
66 #if CBLAS==1
67  num_threads = mkl_get_max_threads();
68 #elif CBLAS==2
69  num_threads = openblas_get_num_threads();
70 #endif
71 
72 
73 
74 }
75 
76 
81 void
83 
84  if ( parameters.size() != num_of_parameters ) {
85  parameters = Matrix_real( 1, num_of_parameters );
86  memset( parameters.get_data(), 0.0, parameters.size()*sizeof(double) );
87  }
88 
89 
90  // the number of adaptive layers in one level
91  int num_of_adaptive_layers = qbit_num*(qbit_num-1)/2 * levels;
92 
93  if ( nontrivial_adaptive_layers.size() != num_of_adaptive_layers ) {
94  nontrivial_adaptive_layers = matrix_base<int8_t>( num_of_adaptive_layers, 1);
95  }
96 
97 
98  //parameters[0:qbit_num*3] = np.random.rand(qbit_num*3)*2*np.pi
99  //parameters[2*qbit_num:3*qbit_num] = np.random.rand(qbit_num)*2*np.pi/4
100  //parameters[qbit_num:2*qbit_num] = np.random.rand(qbit_num)*2*np.pi/4
101  //parameters[3*qbit_num-1] = 0
102  //parameters[3*qbit_num-2] = 0
103 
104  std::uniform_int_distribution<int16_t> distrib(0, 1);
105  std::uniform_real_distribution<> distrib_real(0.0, 2*M_PI);
106  std::uniform_real_distribution<> distrib_real2(0.0, M_PI);
107 
108 
109 
110 
111  for(int idx = 0; idx < 3*qbit_num; idx++) {
112  if ( idx % 3 == 0 ) {
113  parameters[idx] = distrib_real2(gen); // values for theta/2 of U3 can be reduced to [0,PI], since 2PI phase shift can be agregated into Phi and Lambda
114  }
115  else {
116  parameters[idx] = distrib_real(gen);
117  }
118  }
119 
120 
121  for( int layer_idx=0; layer_idx<num_of_adaptive_layers; layer_idx++) {
122 
123  int8_t nontrivial_adaptive_layer = distrib(gen);
124  nontrivial_adaptive_layers[layer_idx] = nontrivial_adaptive_layer;
125 
126  if (nontrivial_adaptive_layer) {
127 
128  // set the radom parameters of the chosen adaptive layer
129  int start_idx = qbit_num*3 + layer_idx*7;
130 
131  int end_idx = start_idx + 7;
132 
133 
134  for(int jdx = start_idx; jdx < end_idx; jdx++) {
135  if ( (jdx-start_idx) % 3 == 0 ) {
136  parameters[jdx] = distrib_real2(gen); // values for theta/2 of U3 can be reduced to [0,PI], since 2PI phase shift can be agregated into Phi and Lambda
137  }
138  else {
139  parameters[jdx] = distrib_real(gen);
140  }
141  }
142 
143 
144  }
145 
146  }
147 
148 
149  return;
150 
151 
152 }
153 
154 
159 void
160 NN::get_nn_chanels_from_kernel( Matrix& kernel_up, Matrix& kernel_down, Matrix_real& chanels) {
161 
162  //kernel.print_matrix();
163 
164  // calculate expectation values of the Pauli operators
165 
166 
167  QGD_Complex16& element00 = kernel_up[0];
168  QGD_Complex16& element01 = kernel_down[0];
169  QGD_Complex16& element10 = kernel_up[kernel_up.stride];
170  QGD_Complex16& element11 = kernel_down[kernel_down.stride];
171 
172  //conj(e00)*e00 - conj(e10)*e10 ) -- expectation value of Z operator
173  double Z0 = element00.real*element00.real + element00.imag*element00.imag - element10.real*element10.real - element10.imag*element10.imag;
174  double Z1 = element01.real*element01.real + element01.imag*element01.imag - element11.real*element11.real - element11.imag*element11.imag;
175 
176  //conj(e00)*e10 + conj(e10)*e00 -- expectation value of X operator
177  double X0 = element00.real*element10.real + element00.imag*element10.imag + element10.real*element00.real + element10.imag*element00.imag;
178  double X1 = element01.real*element11.real + element01.imag*element11.imag + element11.real*element01.real + element11.imag*element01.imag;
179 
180  //i*( conj(e00)*e10 - conj(e10)*e00 ) -- expectation value of Y operator
181  double Y0 = (element00.real*element10.imag - element00.imag*element10.real - element10.real*element00.imag + element10.imag*element00.real);
182  double Y1 = (element01.real*element11.imag - element01.imag*element11.real - element11.real*element01.imag + element11.imag*element01.real);
183 
184  double phi1 = std::atan2( Y0, X0 );
185  phi1 = phi1 < 0 ? 2*M_PI + phi1 : phi1;
186  double phi2 = std::atan2( Y1, X1 ) + M_PI;
187  phi2 = phi2 < 0 ? 2*M_PI + phi2 : phi2;
188 
189 
190  double theta1;
191  if ( std::abs(X0) > 1e-8 ) {
192  theta1 = std::atan2( X0/cos(phi1), Z0 )/2 ;
193  }
194  else {
195  theta1 = std::atan2( Y0/sin(phi1), Z0 )/2;
196  }
197 
198 
199  double theta2;
200  if ( std::abs(X1) > 1e-8 ) {
201  theta2 = (M_PI + atan2( X1/cos(phi2), Z1 ))/2;
202  }
203  else {
204  theta2 = (M_PI + atan2( Y1/sin(phi2), Z1 ))/2;
205  }
206 
207  chanels[0] = theta1;
208  chanels[1] = phi1;
209  chanels[2] = theta2;
210  chanels[3] = phi2;
211 
212 //std::cout << theta1 << " " << phi1 << " " << theta2 << " " << phi2 << std::endl;
213 
214 }
215 
222 void NN::get_nn_chanels( const Matrix& Umtx, const int& target_qbit, Matrix_real& chanels) {
223 
224 
225  if ( Umtx.rows != Umtx.cols ) {
226  std::string err("The unitary must be a square matrix.");
227  throw err;
228  }
229 
230  if ( Umtx.rows <= 0 ) {
231  std::string err("The unitary must be larger than 0x0.");
232  throw err;
233  }
234 
235 
236 
237  int dim = Umtx.rows;
238  int dim_over_2 = dim/2;
239 
240  if ( chanels.size() != dim_over_2*dim_over_2*4 ) {
241  chanels = Matrix_real( dim_over_2, dim_over_2*4 );
242  }
243 
244  Matrix_real chanels_reshaped( chanels.get_data(), dim_over_2, dim_over_2*4 );
245 
246 
247 
248  int index_pair_distance = 1 << target_qbit;
249 
250  //std::cout << "target_qbit: " << target_qbit << " index pair distance: " << index_pair_distance << std::endl;
251 
252 
253 
254 
255  // calculate the individual chanels
256  for (int idx = 0; idx<dim_over_2; idx++ ) {
257 
258  int row_idx = idx >> target_qbit; // higher bits of idx
259  row_idx = row_idx << (target_qbit+1);
260 
261  int tmp = (idx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
262 
263 
264  row_idx = row_idx + tmp; // the index corresponding to state 0 of the target qbit
265 
266  int row_idx_pair = row_idx ^ index_pair_distance;
267  //std::cout << idx << " " << row_idx << " " << row_idx_pair << " " << tmp << std::endl;
268 
269  int stride_kernel = index_pair_distance * Umtx.stride;
270 
271  for (int jdx = 0; jdx<dim_over_2; jdx++ ) {
272 
273  int col_idx = jdx >> target_qbit; // higher bits of idx
274  col_idx = col_idx << (target_qbit+1);
275 
276  int tmp = (jdx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
277 
278 
279  col_idx = col_idx + tmp; // the index corresponding to state 0 of the target qbit
280 
281  int col_idx_pair = col_idx ^ index_pair_distance;
282 
283 
284  Matrix kernel_up = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx, 2, 1, stride_kernel );
285  Matrix kernel_down = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx_pair, 2, 1, stride_kernel );
286 
287  Matrix_real chanels_kernel( chanels_reshaped.get_data() + idx*chanels_reshaped.stride + 4*jdx, 1, 4, chanels_reshaped.stride);
288  get_nn_chanels_from_kernel( kernel_up, kernel_down, chanels_kernel);
289 
290 
291 
292  }
293  }
294 
295 
296  return;
297 
298 }
299 
306 void NN::get_nn_chanels( int qbit_num, const Matrix& Umtx, Matrix_real& chanels) {
307 
308 
309  if ( Umtx.rows != Umtx.cols ) {
310  std::string err("The unitary must be a square matrix.");
311  throw err;
312  }
313 
314  if ( Umtx.rows <= 0 ) {
315  std::string err("The unitary must be larger than 0x0.");
316  throw err;
317  }
318 
319 
320 
321  int dim = Umtx.rows;
322  int dim_over_2 = dim/2;
323 
324  if ( chanels.size() != dim_over_2*dim_over_2*4*qbit_num ) {
325  chanels = Matrix_real( dim_over_2, dim_over_2*4*qbit_num );
326  }
327 
328  Matrix_real chanels_reshaped( chanels.get_data(), dim_over_2, dim_over_2*4*qbit_num );
329 
330 
331 
332 
333  //std::cout << "target_qbit: " << target_qbit << " index pair distance: " << index_pair_distance << std::endl;
334 
335 
336 
337 
338  // calculate the individual chanels
339  for (int idx = 0; idx<dim_over_2; idx++ ) {
340 
341  for (int jdx = 0; jdx<dim_over_2; jdx++ ) {
342 
343  for (int target_qbit=0; target_qbit<qbit_num; target_qbit++) {
344 
345  int index_pair_distance = 1 << target_qbit;
346 
347  // row index pairs
348  int row_idx = idx >> target_qbit; // higher bits of idx
349  row_idx = row_idx << (target_qbit+1);
350 
351  int tmp_idx = (idx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
352 
353 
354  row_idx = row_idx + tmp_idx; // the index corresponding to state 0 of the target qbit
355 
356  int row_idx_pair = row_idx ^ index_pair_distance;
357  //std::cout << idx << " " << row_idx << " " << row_idx_pair << " " << tmp << std::endl;
358 
359  int stride_kernel = index_pair_distance * Umtx.stride;
360 
361 
362 
363  // column index pairs
364  int col_idx = jdx >> target_qbit; // higher bits of idx
365  col_idx = col_idx << (target_qbit+1);
366 
367  int tmp_jdx = (jdx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
368 
369 
370  col_idx = col_idx + tmp_jdx; // the index corresponding to state 0 of the target qbit
371 
372  int col_idx_pair = col_idx ^ index_pair_distance;
373 
374 
375  Matrix kernel_up = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx, 2, 1, stride_kernel );
376  Matrix kernel_down = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx_pair, 2, 1, stride_kernel );
377 
378  Matrix_real chanels_kernel( chanels_reshaped.get_data() + idx*chanels_reshaped.stride + 4*qbit_num*jdx + 4*target_qbit, 1, 4, chanels_reshaped.stride);
379  get_nn_chanels_from_kernel( kernel_up, kernel_down, chanels_kernel);
380 
381  }
382 
383  }
384  }
385 
386 
387  return;
388 
389 
390 }
391 
392 
393 
401 void
402 NN::get_nn_chanels(int qbit_num, int levels, Matrix_real& chanels, matrix_base<int8_t>& nontrivial_adaptive_layers) {
403 
404 
405 
406 
407 
408  //matrix size of the unitary
409  int matrix_size = 1 << qbit_num;
410 
411  // empty config parameters
412  std::map<std::string, Config_Element> config_int;
413 
414 
415  // creating a class to decompose the unitary
416  N_Qubit_Decomposition_adaptive cDecompose( Matrix(0,0), qbit_num, 0, 0, topology, config_int );
417 
418  //adding decomposing layers to the gat structure
419  for( int idx=0; idx<levels; idx++) {
420  cDecompose.add_adaptive_layers();
421  }
422 
423  cDecompose.add_finalyzing_layer();
424 
425 
426  //get the number of free parameters
427  int num_of_parameters = cDecompose.get_parameter_num();
428 
429 //std::cout << "number of free parameters: " << num_of_parameters << std::endl;
430 
431 
432  // create randomized parameters having number of nontrivial adaptive blocks determined by the parameter nontrivial_ratio
434  create_randomized_parameters( num_of_parameters, qbit_num, levels, parameters, nontrivial_adaptive_layers );
435 
436 //parameters.print_matrix();
437 
438  // getting the unitary corresponding to quantum circuit
439  Matrix&& Umtx = cDecompose.get_matrix( parameters );
440 
441  // generate chanels
442  get_nn_chanels( qbit_num, Umtx, chanels );
443 
444 
445 
446 
447 
448 
449 }
450 
451 
452 
453 
462 void
463 NN::get_nn_chanels(int qbit_num, int levels, int samples_num, Matrix_real& chanels, matrix_base<int8_t>& nontrivial_adaptive_layers) {
464 
465 
466  // temporarily turn off OpenMP parallelism
467 #if BLAS==0 // undefined BLAS
470 #elif BLAS==1 // MKL
471  num_threads = mkl_get_max_threads();
472  MKL_Set_Num_Threads(1);
473 #elif BLAS==2 //OpenBLAS
474  num_threads = openblas_get_num_threads();
475  openblas_set_num_threads(1);
476 #endif
477 
478 //Matrix_real parameters;
479 
480  if ( samples_num == 1 ) {
481  get_nn_chanels(qbit_num, levels, chanels, nontrivial_adaptive_layers);
482  return;
483  }
484 
485  if ( samples_num < 1 ) {
486  std::string err("Number of samples must be greater than 0.");
487  throw err;
488  }
489 
490 
491  // query the first sample to infer the memory needed to be allocated
492  Matrix_real chanels_1;
493  matrix_base<int8_t> nontrivial_adaptive_layers_1;
494 
495  get_nn_chanels(qbit_num, levels, chanels_1, nontrivial_adaptive_layers_1);
496 
497  // allocate memory for the outputs
498  chanels = Matrix_real(1, samples_num*chanels_1.size());
499  //parameters = Matrix_real(samples_num, parameters_1.size());
500  nontrivial_adaptive_layers = matrix_base<int8_t>( 1, samples_num*nontrivial_adaptive_layers_1.size() );
501  memset( chanels.get_data(), 0.0, chanels.size()*sizeof(double) );
502  memset( nontrivial_adaptive_layers.get_data(), 0, nontrivial_adaptive_layers.size()*sizeof(int8_t) );
503 
504  // copy the result of the first iteration into the output
505  memcpy( chanels.get_data(), chanels_1.get_data(), chanels_1.size()*sizeof(double) );
506  //memcpy( parameters.get_data(), parameters_1.get_data(), parameters_1.size()*sizeof(double) );
507  memcpy( nontrivial_adaptive_layers.get_data(), nontrivial_adaptive_layers_1.get_data(), nontrivial_adaptive_layers_1.size()*sizeof(int8_t) );
508 
509  // do the remaining cycles
510 
511  tbb::parallel_for( tbb::blocked_range<int>(1,samples_num), [&](tbb::blocked_range<int> r) {
512 
513  for (int idx=r.begin(); idx<r.end(); idx++) {
514 
515 // for (int idx=1; idx<samples_num; idx++) {
516 
517  Matrix_real chanels_idx( chanels.get_data()+idx*chanels_1.size(), 1, chanels_1.size() );
518 
519  //Matrix_real parameters_idx;// parameters.get_data()+idx*parameters_1.size(), 1, parameters_1.size() );
520  matrix_base<int8_t> nontrivial_adaptive_layers_idx( nontrivial_adaptive_layers.get_data()+idx*nontrivial_adaptive_layers_1.size(), 1, nontrivial_adaptive_layers_1.size() );
521 
522  get_nn_chanels(qbit_num, levels, chanels_idx, nontrivial_adaptive_layers_idx);
523 // }
524 
525 
526  }
527 
528  });
529 
530 
531 
532 
533 
534 //Matrix_real chanels_reshaped(chanels.get_data(), chanels.size()/4, 4);
535 //chanels_reshaped.print_matrix();
536 
537 
538 //matrix_base<int8_t> nontrivial_adaptive_layers_reshaped(nontrivial_adaptive_layers.get_data(), nontrivial_adaptive_layers.size()/nontrivial_adaptive_layers_1.size(), nontrivial_adaptive_layers_1.size());
539 //nontrivial_adaptive_layers_reshaped.print_matrix();
540 
541 //std::cout << chanels_1.size() << " " << parameters_1.size() << std::endl;
542 
543 
544 #if BLAS==0 // undefined BLAS
546 #elif BLAS==1 //MKL
547  MKL_Set_Num_Threads(num_threads);
548 #elif BLAS==2 //OpenBLAS
549  openblas_set_num_threads(num_threads);
550 #endif
551 
552 
553 }
554 
555 
NN()
Nullary constructor of the class.
Definition: NN.cpp:34
std::mt19937 gen
Standard mersenne_twister_engine seeded with rd()
Definition: NN.h:75
void add_adaptive_layers()
Call to add adaptive layers to the gate structure stored by the class.
int stride
The column stride of the array. (The array elements in one row are a_0, a_1, ... a_{cols-1}, 0, 0, 0, 0. The number of zeros is stride-cols)
Definition: matrix_base.hpp:46
void add_finalyzing_layer()
Call to add finalyzing layer (single qubit rotations on all of the qubits) to the gate structure stor...
cDecompose
[create decomposition class] creating a class to decompose the unitary
Definition: example.py:62
int levels
[creating decomp class]
int num_threads
Store the number of OpenMP threads. (During the calculations OpenMP multithreading is turned off...
Definition: NN.h:79
std::vector< matrix_base< int > > topology
connectivity between the wubits
Definition: NN.h:77
Matrix get_matrix(Matrix_real &parameters)
Call to retrieve the gate matrix (Which is the product of all the gate matrices stored in the gate bl...
void get_nn_chanels_from_kernel(Matrix &kernel_up, Matrix &kernel_down, Matrix_real &chanels)
call retrieve the channels for the neural network associated with a single 2x2 kernel ...
Definition: NN.cpp:160
scalar * get_data() const
Call to get the pointer to the stored data.
void create_randomized_parameters(int num_of_parameters, int qbit_num, int levels, Matrix_real &parameters, matrix_base< int8_t > &nontrivial_adaptive_layers)
Call to construct random parameter, with limited number of non-trivial adaptive layers.
Definition: NN.cpp:82
int get_parameter_num()
Call to get the number of free parameters.
int rows
The number of rows.
Definition: matrix_base.hpp:42
int cols
The number of columns.
Definition: matrix_base.hpp:44
matrix_size
[load Umtx]
Definition: example.py:58
Umtx
The unitary to be decomposed.
Definition: example.py:53
Structure type representing complex numbers in the SQUANDER package.
Definition: QGDTypes.h:38
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
Class to store data of complex arrays and its properties.
Definition: matrix.h:38
int size() const
Call to get the number of the allocated elements.
void omp_set_num_threads(int num_threads)
Set the number of threads on runtime in MKL.
double real
the real part of a complex number
Definition: QGDTypes.h:40
Header file for a class implementing the adaptive gate decomposition algorithm of arXiv:2203...
void get_nn_chanels(const Matrix &Umtx, const int &target_qbit, Matrix_real &chanels)
call retrieve the channels for the neural network associated with a single unitary ...
Definition: NN.cpp:222
std::random_device rd
Will be used to obtain a seed for the random number engine.
Definition: NN.h:73
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:39
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