Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
Random_Unitary.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 You should have received a copy of the GNU General Public License
18 along with this program. If not, see http://www.gnu.org/licenses/.
19 
20 @author: Peter Rakyta, Ph.D.
21 */
27 #include "Random_Unitary.h"
28 #include "qgd_math.h"
29 #include <vector>
30 
31 
38 Matrix
39 few_CNOT_unitary( int qbit_num, int cnot_num) {
40 
41  // the current number of CNOT gates
42  int cnot_num_curr = 0;
43 
44  // the size of the matrix
45  int matrix_size = Power_of_2(qbit_num);
46 
47  // The unitary describing each qubits in their initial state
48  Matrix mtx = create_identity( matrix_size );
49 
50  // constructing the unitary
51  while (true) {
52  int cnot_or_u3 = rand() % 5 + 1;
53 
54  CNOT* cnot_op = NULL;
55  U3* u3_op = NULL;
56 
57  Matrix gate_matrix;
58 
59  if (cnot_or_u3 <= 4) {
60  // creating random parameters for the U3 operation
61  Matrix_real parameters(1, 3);
62  parameters[0] = double(rand())/RAND_MAX*4*M_PI;
63  parameters[1] = double(rand())/RAND_MAX*2*M_PI;
64  parameters[2] = double(rand())/RAND_MAX*2*M_PI;
65 
66 
67  // randomly choose the target qbit
68  int target_qbit = rand() % qbit_num;
69 
70  // creating the U3 gate
71  u3_op = new U3(qbit_num, target_qbit);
72 
73  // get the matrix of the operation
74 
75  gate_matrix = u3_op->get_matrix(parameters);
76  }
77  else if ( cnot_or_u3 == 5 ) {
78  // randomly choose the target qbit
79  int target_qbit = rand() % qbit_num;
80 
81  // randomly choose the control qbit
82  int control_qbit = rand() % qbit_num;
83 
84  if (target_qbit == control_qbit) {
85  gate_matrix = create_identity( matrix_size );
86  }
87  else {
88 
89  // creating the CNOT gate
90  cnot_op = new CNOT(qbit_num, control_qbit, target_qbit);
91 
92  // get the matrix of the operation
93  gate_matrix = cnot_op->get_matrix();
94 
95  cnot_num_curr = cnot_num_curr + 1;
96  }
97  }
98  else {
99  gate_matrix = create_identity( matrix_size );
100  }
101 
102 
103  // get the current unitary
104  Matrix mtx_tmp = dot(gate_matrix, mtx);
105  mtx = mtx_tmp;
106 
107  delete u3_op;
108  u3_op = NULL;
109 
110  delete cnot_op;
111  cnot_op = NULL;
112 
113  // exit the loop if the maximal number of CNOT gates reached
114  if (cnot_num_curr >= cnot_num) {
115  return mtx;
116  }
117 
118  }
119 
120 }
121 
122 
123 // -----------------------------------------------------------------------
124 // Implementation helpers – anonymous namespace
125 //
126 // Templatized on scalar (double or float) so both precisions share one
127 // code path with no conversion. The public class methods are thin
128 // dispatchers that call construct_unitary_tmpl<scalar>.
129 // -----------------------------------------------------------------------
130 
131 namespace {
132 
133 // Type traits: map scalar type → complex element type + matrix type + identity factory
134 template<typename scalar> struct RunTraits;
135 
136 template<> struct RunTraits<double> {
137  using complex = QGD_Complex16;
138  using matrix = Matrix;
139  static matrix identity(int dim) { return create_identity(dim); }
140 };
141 
142 template<> struct RunTraits<float> {
143  using complex = QGD_Complex8;
144  using matrix = Matrix_float;
145  static matrix identity(int dim) { return create_identity_float(dim); }
146 };
147 
148 // γ – Eq (11): always ±1; computed in double, cast to scalar
149 template<typename scalar>
150 inline scalar gamma_impl(int dim) {
151  return static_cast<scalar>(
152  std::pow(-1.0, 0.25*(2*dim - 1 + std::pow(-1.0, dim)))
153  );
154 }
155 
156 // Index mapping: β + (α-1)(α-2)/2
157 inline int convert_index_impl(int varalpha, int varbeta) {
158  return varbeta + (varalpha - 1)*(varalpha - 2)/2;
159 }
160 
161 // Q matrix – Eq (9)
162 template<typename scalar>
163 typename RunTraits<scalar>::matrix
164 Q_tmpl( typename RunTraits<scalar>::complex u1,
165  typename RunTraits<scalar>::complex u2 )
166 {
167  using MatrixT = typename RunTraits<scalar>::matrix;
168  MatrixT ret(2, 2);
169  ret[0] = u2;
170  ret[1] = u1;
171  ret[2].real = -u1.real;
172  ret[2].imag = u1.imag;
173  ret[3].real = u2.real;
174  ret[3].imag = -u2.imag;
175  return ret;
176 }
177 
178 // E_{α,β}: dim×dim unit-basis matrix with 1 at (varalpha, varbeta)
179 template<typename scalar>
180 typename RunTraits<scalar>::matrix
181 E_alpha_beta_tmpl(int varalpha, int varbeta, int dim)
182 {
183  using ComplexT = typename RunTraits<scalar>::complex;
184  using MatrixT = typename RunTraits<scalar>::matrix;
185  MatrixT ret(dim, dim);
186  memset(ret.get_data(), 0, dim*dim*sizeof(ComplexT));
187  ret[varalpha*dim + varbeta].real = scalar(1);
188  return ret;
189 }
190 
191 // I_{α,β}: identity with zeros at diagonal positions (α,α) and (β,β)
192 template<typename scalar>
193 typename RunTraits<scalar>::matrix
194 I_alpha_beta_tmpl(int varalpha, int varbeta, int dim)
195 {
196  typename RunTraits<scalar>::matrix ret = RunTraits<scalar>::identity(dim);
197  ret[varalpha*dim + varalpha].real = scalar(0);
198  ret[varbeta*dim + varbeta ].real = scalar(0);
199  return ret;
200 }
201 
202 // M matrix – Eq (8)
203 template<typename scalar>
204 typename RunTraits<scalar>::matrix
205 M_tmpl(int varalpha, int varbeta,
206  typename RunTraits<scalar>::complex s,
207  typename RunTraits<scalar>::complex t,
208  int dim)
209 {
210  using MatrixT = typename RunTraits<scalar>::matrix;
211  MatrixT Qloc = Q_tmpl<scalar>(s, t);
212  MatrixT ret1 = E_alpha_beta_tmpl<scalar>(varbeta, varbeta, dim);
213  MatrixT ret2 = E_alpha_beta_tmpl<scalar>(varbeta, varalpha, dim);
214  MatrixT ret3 = E_alpha_beta_tmpl<scalar>(varalpha, varbeta, dim);
215  MatrixT ret4 = E_alpha_beta_tmpl<scalar>(varalpha, varalpha, dim);
216  mult(Qloc[0], ret1);
217  mult(Qloc[1], ret2);
218  mult(Qloc[2], ret3);
219  mult(Qloc[3], ret4);
220  MatrixT ret(dim, dim);
221  for (int idx = 0; idx < dim*dim; ++idx) {
222  ret[idx].real = ret1[idx].real + ret2[idx].real + ret3[idx].real + ret4[idx].real;
223  ret[idx].imag = ret1[idx].imag + ret2[idx].imag + ret3[idx].imag + ret4[idx].imag;
224  }
225  return ret;
226 }
227 
228 // Ω matrix – Eq (6)
229 template<typename scalar>
230 typename RunTraits<scalar>::matrix
231 Omega_tmpl(int varalpha, int varbeta,
232  typename RunTraits<scalar>::complex x,
233  typename RunTraits<scalar>::complex y,
234  int dim)
235 {
236  using MatrixT = typename RunTraits<scalar>::matrix;
237  MatrixT ret = I_alpha_beta_tmpl<scalar>(varalpha, varbeta, dim);
238  // threshold = 3 + δ(dim,2): equals 4 when dim==2, 3 otherwise
239  const int threshold = 3 + (dim == 2 ? 1 : 0);
240  MatrixT Mloc;
241  if (varalpha + varbeta != threshold) {
242  Mloc = M_tmpl<scalar>(varalpha, varbeta, x, y, dim);
243  } else {
244  y.imag = -y.imag;
245  Mloc = M_tmpl<scalar>(varalpha, varbeta, x,
246  mult(gamma_impl<scalar>(dim), y), dim);
247  }
248  for (int idx = 0; idx < dim*dim; ++idx) {
249  ret[idx].real += Mloc[idx].real;
250  ret[idx].imag += Mloc[idx].imag;
251  }
252  return ret;
253 }
254 
255 // Core algorithm – arXiv:1303.5904v1, native in scalar precision
256 template<typename scalar>
257 typename RunTraits<scalar>::matrix
258 construct_unitary_tmpl(scalar* vartheta, scalar* varphi, scalar* varkappa, int dim)
259 {
260  using ComplexT = typename RunTraits<scalar>::complex;
261  using MatrixT = typename RunTraits<scalar>::matrix;
262 
263  MatrixT ret = RunTraits<scalar>::identity(dim);
264 
265  for (int varalpha = 1; varalpha < dim; ++varalpha) {
266  for (int varbeta = 0; varbeta < varalpha; ++varbeta) {
267  const int param_idx = convert_index_impl(varalpha, varbeta);
268  const scalar theta_loc = vartheta[param_idx];
269  const scalar phi_loc = varphi[param_idx];
270 
271  // Eq (26): a = cos(θ)·exp(iφ), sign-negated per paper
272  scalar cos_theta, sin_theta, cos_phi_loc, sin_phi_loc;
273  qgd_sincos<scalar>(theta_loc, &sin_theta, &cos_theta);
274  qgd_sincos<scalar>(phi_loc, &sin_phi_loc, &cos_phi_loc);
275  ComplexT a;
276  a.real = scalar(cos_theta * cos_phi_loc);
277  a.imag = scalar(cos_theta * sin_phi_loc);
278 
279  // Eq (28): ε = δ(α-1,β)·κ_{α-1}; b = sin(θ)·exp̄(iε)
280  const scalar varepsilon = (varalpha - 1 == varbeta)
281  ? varkappa[varalpha - 1] : scalar(0);
282  scalar cos_epsilon, sin_epsilon;
283  qgd_sincos<scalar>(varepsilon, &sin_epsilon, &cos_epsilon);
284  ComplexT b;
285  b.real = scalar(sin_theta * cos_epsilon);
286  b.imag = scalar(sin_theta * sin_epsilon);
287 
288  a.real = -a.real;
289  b.imag = -b.imag;
290 
291  MatrixT Omega_loc = Omega_tmpl<scalar>(varalpha, varbeta, a, b, dim);
292  ret = dot(ret, Omega_loc);
293  }
294  }
295 
296  // Apply overall phase γ – Eq (11)
297  ComplexT gamma_loc;
298  gamma_loc.real = gamma_impl<scalar>(dim);
299  gamma_loc.imag = scalar(0);
300  for (int idx = 0; idx < dim*dim; ++idx) {
301  ret[idx] = mult(ret[idx], gamma_loc);
302  }
303 
304  return ret;
305 }
306 
307 } // anonymous namespace
308 
309 
316 
317  if (dim_in < 2) {
318  throw "wrong dimension";
319  }
320 
321  // number of qubits
322  dim = dim_in;
323 
324 }
325 
326 
331 Matrix
333 
334  const int n = int(dim*(dim-1)/2);
335 
336  double* vartheta = (double*) qgd_calloc( n, sizeof(double), 64 );
337  double* varphi = (double*) qgd_calloc( n, sizeof(double), 64 );
338  double* varkappa = (double*) qgd_calloc( dim - 1, sizeof(double), 64 );
339 
340  srand( static_cast<unsigned int>(time(NULL)) );
341 
342  for (int idx = 0; idx < n; ++idx) vartheta[idx] = (2*double(rand())/RAND_MAX - 1)*2*M_PI;
343  for (int idx = 0; idx < n; ++idx) varphi[idx] = (2*double(rand())/RAND_MAX - 1)*2*M_PI;
344  for (int idx = 0; idx < dim - 1; ++idx) varkappa[idx] = (2*double(rand())/RAND_MAX - 1)*2*M_PI;
345 
346  Matrix Umtx = construct_unitary_tmpl<double>(vartheta, varphi, varkappa, dim);
347 
348  qgd_free(vartheta);
349  qgd_free(varphi);
350  qgd_free(varkappa);
351 
352  return Umtx;
353 }
354 
355 
363 Matrix
364 Random_Unitary::Construct_Unitary_Matrix( double* vartheta, double* varphi, double* varkappa ) {
365  return construct_unitary_tmpl<double>(vartheta, varphi, varkappa, dim);
366 }
367 
368 
378 Random_Unitary::Construct_Unitary_Matrix( float* vartheta, float* varphi, float* varkappa ) {
379  return construct_unitary_tmpl<float>(vartheta, varphi, varkappa, dim);
380 }
381 
382 
388 Matrix
390  return construct_unitary_tmpl<double>(
391  parameters,
392  parameters + int(dim*(dim-1)/2),
393  parameters + int(dim*(dim-1)),
394  dim );
395 }
396 
397 
398 
399 
400 
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
Copyright (C) Miklos Maroti, 2021 SPDX-License-Identifier: Apache-2.0.
Definition: U3.h:19
Matrix few_CNOT_unitary(int qbit_num, int cnot_num)
Call to create a random unitary constructed by CNOT operation between randomly chosen qubits and by r...
void * qgd_calloc(int element_num, int size, int alignment)
custom defined memory allocation function.
Definition: common.cpp:93
void qgd_free(void *ptr)
custom defined memory release function.
Definition: common.cpp:121
Structure type representing single-precision complex numbers.
Definition: QGDTypes.h:46
QGD_Complex16 mult(QGD_Complex16 &a, QGD_Complex16 &b)
Call to calculate the product of two complex scalars.
Definition: common.cpp:298
virtual Matrix get_matrix(Matrix_real &parameters, int parallel)
Call to retrieve the gate matrix.
Definition: Gate.cpp:269
matrix_size
[load Umtx]
Definition: example.py:58
#define M_PI
Definition: qgd_math.h:42
Umtx
The unitary to be decomposed.
Definition: example.py:53
Structure type representing complex numbers in the SQUANDER package.
Definition: QGDTypes.h:38
A class representing a CNOT operation.
Definition: CNOT.h:35
int Power_of_2(int n)
Calculates the n-th power of 2.
Definition: common.cpp:136
Double-precision complex matrix (float64).
Definition: matrix.h:38
Random_Unitary(int dim_in)
Constructor.
Single-precision complex matrix (float32).
Definition: matrix_float.h:41
Matrix create_identity(int matrix_size)
Call to create an identity matrix.
Definition: common.cpp:182
Matrix Construct_Unitary_Matrix()
Construct a random unitary with internally generated float64 parameters.
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
Matrix_float create_identity_float(int matrix_size)
Call to create a single-precision complex identity matrix.
Definition: common.cpp:203