Sequential Quantum Gate Decomposer  v1.9.6
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, 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 = static_cast<int8_t>(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  //std::cout << idx << " " << row_idx << " " << row_idx_pair << " " << tmp << std::endl;
267 
268  int stride_kernel = index_pair_distance * Umtx.stride;
269 
270  for (int jdx = 0; jdx<dim_over_2; jdx++ ) {
271 
272  int col_idx = jdx >> target_qbit; // higher bits of idx
273  col_idx = col_idx << (target_qbit+1);
274 
275  int tmp = (jdx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
276 
277 
278  col_idx = col_idx + tmp; // the index corresponding to state 0 of the target qbit
279 
280  int col_idx_pair = col_idx ^ index_pair_distance;
281 
282 
283  Matrix kernel_up = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx, 2, 1, stride_kernel );
284  Matrix kernel_down = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx_pair, 2, 1, stride_kernel );
285 
286  Matrix_real chanels_kernel( chanels_reshaped.get_data() + idx*chanels_reshaped.stride + 4*jdx, 1, 4, chanels_reshaped.stride);
287  get_nn_chanels_from_kernel( kernel_up, kernel_down, chanels_kernel);
288 
289 
290 
291  }
292  }
293 
294 
295  return;
296 
297 }
298 
305 void NN::get_nn_chanels( int qbit_num, const Matrix& Umtx, Matrix_real& chanels) {
306 
307 
308  if ( Umtx.rows != Umtx.cols ) {
309  std::string err("The unitary must be a square matrix.");
310  throw err;
311  }
312 
313  if ( Umtx.rows <= 0 ) {
314  std::string err("The unitary must be larger than 0x0.");
315  throw err;
316  }
317 
318 
319 
320  int dim = Umtx.rows;
321  int dim_over_2 = dim/2;
322 
323  if ( chanels.size() != dim_over_2*dim_over_2*4*qbit_num ) {
324  chanels = Matrix_real( dim_over_2, dim_over_2*4*qbit_num );
325  }
326 
327  Matrix_real chanels_reshaped( chanels.get_data(), dim_over_2, dim_over_2*4*qbit_num );
328 
329 
330 
331 
332  //std::cout << "target_qbit: " << target_qbit << " index pair distance: " << index_pair_distance << std::endl;
333 
334 
335 
336 
337  // calculate the individual chanels
338  for (int idx = 0; idx<dim_over_2; idx++ ) {
339 
340  for (int jdx = 0; jdx<dim_over_2; jdx++ ) {
341 
342  for (int target_qbit=0; target_qbit<qbit_num; target_qbit++) {
343 
344  int index_pair_distance = 1 << target_qbit;
345 
346  // row index pairs
347  int row_idx = idx >> target_qbit; // higher bits of idx
348  row_idx = row_idx << (target_qbit+1);
349 
350  int tmp_idx = (idx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
351 
352 
353  row_idx = row_idx + tmp_idx; // the index corresponding to state 0 of the target qbit
354 
355  //std::cout << idx << " " << row_idx << " " << row_idx_pair << " " << tmp << std::endl;
356 
357  int stride_kernel = index_pair_distance * Umtx.stride;
358 
359 
360 
361  // column index pairs
362  int col_idx = jdx >> target_qbit; // higher bits of idx
363  col_idx = col_idx << (target_qbit+1);
364 
365  int tmp_jdx = (jdx & ( (1 << (target_qbit)) - 1 ) ); // lower target_bit bits from idx
366 
367 
368  col_idx = col_idx + tmp_jdx; // the index corresponding to state 0 of the target qbit
369 
370  int col_idx_pair = col_idx ^ index_pair_distance;
371 
372 
373  Matrix kernel_up = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx, 2, 1, stride_kernel );
374  Matrix kernel_down = Matrix(Umtx.get_data() + row_idx*Umtx.stride + col_idx_pair, 2, 1, stride_kernel );
375 
376  Matrix_real chanels_kernel( chanels_reshaped.get_data() + idx*chanels_reshaped.stride + 4*qbit_num*jdx + 4*target_qbit, 1, 4, chanels_reshaped.stride);
377  get_nn_chanels_from_kernel( kernel_up, kernel_down, chanels_kernel);
378 
379  }
380 
381  }
382  }
383 
384 
385  return;
386 
387 
388 }
389 
390 
391 
399 void
400 NN::get_nn_chanels(int qbit_num, int levels, Matrix_real& chanels, matrix_base<int8_t>& nontrivial_adaptive_layers) {
401 
402 
403 
404 
405  // empty config parameters
406  std::map<std::string, Config_Element> config_int;
407 
408 
409  // creating a class to decompose the unitary
410  N_Qubit_Decomposition_adaptive cDecompose( Matrix(0,0), qbit_num, 0, 0, topology, config_int );
411 
412  //adding decomposing layers to the gat structure
413  for( int idx=0; idx<levels; idx++) {
414  cDecompose.add_adaptive_layers();
415  }
416 
417  cDecompose.add_finalyzing_layer();
418 
419 
420  //get the number of free parameters
421  int num_of_parameters = cDecompose.get_parameter_num();
422 
423 //std::cout << "number of free parameters: " << num_of_parameters << std::endl;
424 
425 
426  // create randomized parameters having number of nontrivial adaptive blocks determined by the parameter nontrivial_ratio
428  create_randomized_parameters( num_of_parameters, qbit_num, levels, parameters, nontrivial_adaptive_layers );
429 
430 //parameters.print_matrix();
431 
432  // getting the unitary corresponding to quantum circuit
433  Matrix&& Umtx = cDecompose.get_matrix( parameters );
434 
435  // generate chanels
436  get_nn_chanels( qbit_num, Umtx, chanels );
437 
438 
439 
440 
441 
442 
443 }
444 
445 
446 
447 
456 void
457 NN::get_nn_chanels(int qbit_num, int levels, int samples_num, Matrix_real& chanels, matrix_base<int8_t>& nontrivial_adaptive_layers) {
458 
459 
460  // temporarily turn off OpenMP parallelism
461 #if BLAS==0 // undefined BLAS
464 #elif BLAS==1 // MKL
465  num_threads = mkl_get_max_threads();
466  MKL_Set_Num_Threads(1);
467 #elif BLAS==2 //OpenBLAS
468  num_threads = openblas_get_num_threads();
469  openblas_set_num_threads(1);
470 #endif
471 
472 //Matrix_real parameters;
473 
474  if ( samples_num == 1 ) {
475  get_nn_chanels(qbit_num, levels, chanels, nontrivial_adaptive_layers);
476  return;
477  }
478 
479  if ( samples_num < 1 ) {
480  std::string err("Number of samples must be greater than 0.");
481  throw err;
482  }
483 
484 
485  // query the first sample to infer the memory needed to be allocated
486  Matrix_real chanels_1;
487  matrix_base<int8_t> nontrivial_adaptive_layers_1;
488 
489  get_nn_chanels(qbit_num, levels, chanels_1, nontrivial_adaptive_layers_1);
490 
491  // allocate memory for the outputs
492  chanels = Matrix_real(1, samples_num*chanels_1.size());
493  //parameters = Matrix_real(samples_num, parameters_1.size());
494  nontrivial_adaptive_layers = matrix_base<int8_t>( 1, samples_num*nontrivial_adaptive_layers_1.size() );
495  memset( chanels.get_data(), 0, chanels.size()*sizeof(double) );
496  memset( nontrivial_adaptive_layers.get_data(), 0, nontrivial_adaptive_layers.size()*sizeof(int8_t) );
497 
498  // copy the result of the first iteration into the output
499  memcpy( chanels.get_data(), chanels_1.get_data(), chanels_1.size()*sizeof(double) );
500  //memcpy( parameters.get_data(), parameters_1.get_data(), parameters_1.size()*sizeof(double) );
501  memcpy( nontrivial_adaptive_layers.get_data(), nontrivial_adaptive_layers_1.get_data(), nontrivial_adaptive_layers_1.size()*sizeof(int8_t) );
502 
503  // do the remaining cycles
504 
505  tbb::parallel_for( tbb::blocked_range<int>(1,samples_num), [&](tbb::blocked_range<int> r) {
506 
507  for (int idx=r.begin(); idx<r.end(); idx++) {
508 
509 // for (int idx=1; idx<samples_num; idx++) {
510 
511  Matrix_real chanels_idx( chanels.get_data()+idx*chanels_1.size(), 1, chanels_1.size() );
512 
513  //Matrix_real parameters_idx;// parameters.get_data()+idx*parameters_1.size(), 1, parameters_1.size() );
514  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() );
515 
516  get_nn_chanels(qbit_num, levels, chanels_idx, nontrivial_adaptive_layers_idx);
517 // }
518 
519 
520  }
521 
522  });
523 
524 
525 
526 
527 
528 //Matrix_real chanels_reshaped(chanels.get_data(), chanels.size()/4, 4);
529 //chanels_reshaped.print_matrix();
530 
531 
532 //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());
533 //nontrivial_adaptive_layers_reshaped.print_matrix();
534 
535 //std::cout << chanels_1.size() << " " << parameters_1.size() << std::endl;
536 
537 
538 #if BLAS==0 // undefined BLAS
540 #elif BLAS==1 //MKL
541  MKL_Set_Num_Threads(num_threads);
542 #elif BLAS==2 //OpenBLAS
543  openblas_set_num_threads(num_threads);
544 #endif
545 
546 
547 }
548 
549 
NN()
Nullary constructor of the class.
Definition: NN.cpp:34
std::mt19937 gen
Standard mersenne_twister_engine seeded with rd()
Definition: NN.h:72
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:76
std::vector< matrix_base< int > > topology
connectivity between the wubits
Definition: NN.h:74
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 rows
The number of rows.
Definition: matrix_base.hpp:42
int cols
The number of columns.
Definition: matrix_base.hpp:44
#define M_PI
Definition: qgd_math.h:42
Umtx
The unitary to be decomposed.
Definition: example.py:53
Matrix get_matrix(Matrix_real &parameters) override
Call to retrieve the gate matrix (Which is the product of all the gate matrices stored in the gate bl...
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...
Double-precision complex matrix (float64).
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:70
int get_parameter_num() override
Call to get the number of free parameters.
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
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