Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
N_Qubit_Decomposition_Cost_Function.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 "QGDTypes.h"
25 
26 #include <vector>
27 #include <algorithm>
28 
29 #define LAPACK_ROW_MAJOR 101
30 #define LAPACK_COL_MAJOR 102
31 
32 
33 
34 #ifdef __cplusplus
35 extern "C"
36 {
37 #endif
38 
39 // LAPACKE function declarations using QGD_Complex16
40 extern "C" int LAPACKE_zgesvd( int matrix_order, char jobu, char jobvt,
41  int m, int n, QGD_Complex16* a,
42  int lda, double* s, QGD_Complex16* u,
43  int ldu, QGD_Complex16* vt,
44  int ldvt, double* superb );
45 
46 extern "C" int LAPACKE_zgesdd(int matrix_order, char jobz, int m, int n, QGD_Complex16* a,
47  int lda, double* s, QGD_Complex16* u, int ldu,
48  QGD_Complex16* vt, int ldvt);
49 extern "C" int LAPACKE_cgesvd( int matrix_order, char jobu, char jobvt,
50  int m, int n, QGD_Complex8* a,
51  int lda, float* s, QGD_Complex8* u,
52  int ldu, QGD_Complex8* vt,
53  int ldvt, float* superb );
54 
55 extern "C" int LAPACKE_cgesdd(int matrix_order, char jobz, int m, int n, QGD_Complex8* a,
56  int lda, float* s, QGD_Complex8* u, int ldu,
57  QGD_Complex8* vt, int ldvt);
58 #define USE_SDD
59 #define USE_COL_MAJ
60 
61 #ifdef __cplusplus
62 }
63 #endif
64 
65 
66 
73 double get_cost_function(const Matrix& matrix, int trace_offset) {
74 
75  int matrix_size = matrix.cols ;
76 /*
77  tbb::combinable<double> priv_partial_cost_functions{[](){return 0;}};
78  tbb::parallel_for( tbb::blocked_range<int>(0, matrix_size, 1), functor_cost_fnc( matrix, &priv_partial_cost_functions ));
79 */
80 /*
81  //sequential version
82  functor_cost_fnc tmp = functor_cost_fnc( matrix, matrix_size, partial_cost_functions, matrix_size );
83  #pragma omp parallel for
84  for (int idx=0; idx<matrix_size; idx++) {
85  tmp(idx);
86  }
87 */
88 /*
89  // calculate the final cost function
90  double cost_function = 0;
91  priv_partial_cost_functions.combine_each([&cost_function](double a) {
92  cost_function = cost_function + a;
93  });
94 */
95 
96 /*
97 #ifdef USE_AVX
98 
99 
100  __m128d trace_128 = _mm_setr_pd(0.0, 0.0);
101  double* matrix_data = (double*)matrix.get_data();
102  int offset = 2*(matrix.stride+1);
103 
104  for (int idx=0; idx<matrix_size; idx++) {
105 
106  // get the diagonal element
107  __m128d element_128 = _mm_load_pd(matrix_data);
108 
109  // add the diagonal elements to the trace
110  trace_128 = _mm_add_pd(trace_128, element_128);
111 
112  matrix_data = matrix_data + offset;
113  }
114 
115 
116  trace_128 = _mm_mul_pd(trace_128, trace_128);
117  double cost_function = std::sqrt(1.0 - (trace_128[0] + trace_128[1])/(matrix_size*matrix_size));
118 
119 #else
120 
121  QGD_Complex16 trace;
122  memset( &trace, 0.0, 2*sizeof(double) );
123  //trace.real = 0.0;
124  //trace.imag = 0.0;
125 
126  for (int idx=0; idx<matrix_size; idx++) {
127 
128  trace.real += matrix[idx*matrix.stride + idx].real;
129  trace.imag += matrix[idx*matrix.stride + idx].imag;
130  }
131 
132  double cost_function = std::sqrt(1.0 - (trace.real*trace.real + trace.imag*trace.imag)/(matrix_size*matrix_size));
133 #endif
134 */
135 
136 
137  double trace_real = 0.0;
138 
139  if ( trace_offset == 0 ) {
140 
141  for (int idx=0; idx<matrix_size; idx++) {
142 
143  trace_real += matrix[idx*matrix.stride + idx].real;
144 
145  }
146  }
147  else {
148 
149  for (int idx=0; idx<matrix_size; idx++) {
150 
151  trace_real += matrix[(idx+trace_offset)*matrix.stride + idx].real;
152 
153  }
154 
155  }
156 
157  //double cost_function = std::sqrt(1.0 - trace_real/matrix_size);
158  double cost_function = (1.0 - trace_real/matrix_size);
159 
160  return cost_function;
161 
162 }
163 
164 double get_cost_function(const Matrix_float& matrix, int trace_offset) {
165 
166  int matrix_size = matrix.cols;
167  double trace_real = 0.0;
168 
169  if ( trace_offset == 0 ) {
170  for (int idx=0; idx<matrix_size; idx++) {
171  trace_real += matrix[idx*matrix.stride + idx].real;
172  }
173  }
174  else {
175  for (int idx=0; idx<matrix_size; idx++) {
176  trace_real += matrix[(idx+trace_offset)*matrix.stride + idx].real;
177  }
178  }
179 
180  return 1.0 - trace_real/matrix_size;
181 }
182 
183 
184 
191 Matrix_real get_cost_function_with_correction(const Matrix& matrix, int qbit_num, int trace_offset) {
192 
193  Matrix_real ret(1,2);
194 
195  // calculate the cost function
196  ret[0] = get_cost_function( matrix, trace_offset );
197 
198 
199 
200  // calculate teh first correction
201 
202  int matrix_size = matrix.cols;
203 
204  double trace_real = 0.0;
205 
206  if ( trace_offset == 0 ) {
207  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
208 
209  int qbit_error_mask = 1 << qbit_idx;
210 
211  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
212 
213  // determine the row index pair with one bit error at the given qbit_idx
214  int row_idx = col_idx ^ qbit_error_mask;
215 
216  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
217  }
218  }
219 
220  }
221  else {
222 
223 
224  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
225 
226  int qbit_error_mask = 1 << qbit_idx;
227 
228  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
229 
230  // determine the row index pair with one bit error at the given qbit_idx
231  int row_idx = (col_idx + trace_offset) ^ qbit_error_mask;
232 // std::cout << matrix[row_idx*matrix.stride + col_idx].real << " " << row_idx << " " << col_idx << std::endl;
233  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
234  }
235  }
236 
237 
238  }
239 
240  //double cost_function = std::sqrt(1.0 - trace_real/matrix_size);
241  double cost_function = trace_real/matrix_size;
242 
243 //std::cout << cost_function << std::endl;
244 //exit(1);
245 
246  ret[1] = cost_function;
247 
248  return ret;
249 
250 
251 
252 }
253 
255 
256  Matrix_real_float ret(1,2);
257 
258  ret[0] = static_cast<float>(get_cost_function( matrix, trace_offset ));
259 
260  int matrix_size = matrix.cols;
261  double trace_real = 0.0;
262 
263  if ( trace_offset == 0 ) {
264  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
265  int qbit_error_mask = 1 << qbit_idx;
266  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
267  int row_idx = col_idx ^ qbit_error_mask;
268  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
269  }
270  }
271  }
272  else {
273  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
274  int qbit_error_mask = 1 << qbit_idx;
275  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
276  int row_idx = (col_idx + trace_offset) ^ qbit_error_mask;
277  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
278  }
279  }
280  }
281 
282  ret[1] = static_cast<float>(trace_real/matrix_size);
283  return ret;
284 }
285 
286 
287 
288 
295 Matrix_real get_cost_function_with_correction2(const Matrix& matrix, int qbit_num, int trace_offset) {
296 
297 
298  Matrix_real ret(1,3);
299 
300  // calculate the cost function
301  ret[0] = get_cost_function( matrix, trace_offset );
302 
303 
304 
305  // calculate the first correction
306 
307  int matrix_size = matrix.cols;
308 
309  double trace_real = 0.0;
310 
311  if ( trace_offset == 0 ) {
312  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
313 
314  int qbit_error_mask = 1 << qbit_idx;
315 
316  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
317 
318  // determine the row index pair with one bit error at the given qbit_idx
319  int row_idx = col_idx ^ qbit_error_mask;
320 
321  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
322  }
323  }
324 
325  }
326  else {
327 
328 
329  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
330 
331  int qbit_error_mask = 1 << qbit_idx;
332 
333  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
334 
335  // determine the row index pair with one bit error at the given qbit_idx
336  int row_idx = (col_idx+trace_offset) ^ qbit_error_mask;
337 
338  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
339  }
340  }
341 
342 
343  }
344 
345  double cost_function = trace_real/matrix_size;
346 
347  ret[1] = cost_function;
348 
349 
350 
351 
352  // calculate the second correction
353 
354  trace_real = 0.0;
355 
356  if ( trace_offset == 0 ) {
357  for (int qbit_idx=0; qbit_idx<qbit_num-1; qbit_idx++) {
358  for (int qbit_idx2=qbit_idx+1; qbit_idx2<qbit_num; qbit_idx2++) {
359 
360  int qbit_error_mask = (1 << qbit_idx) + (1 << qbit_idx2);
361 
362  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
363 
364  // determine the row index pair with one bit error at the given qbit_idx
365  int row_idx = col_idx ^ qbit_error_mask;
366 
367  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
368  }
369 
370  }
371  }
372  }
373  else {
374 
375  for (int qbit_idx=0; qbit_idx<qbit_num-1; qbit_idx++) {
376  for (int qbit_idx2=qbit_idx+1; qbit_idx2<qbit_num; qbit_idx2++) {
377 
378  int qbit_error_mask = (1 << qbit_idx) + (1 << qbit_idx2);
379 
380  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
381 
382  // determine the row index pair with one bit error at the given qbit_idx
383  int row_idx = (col_idx+trace_offset) ^ qbit_error_mask;
384 
385  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
386  }
387 
388  }
389  }
390 
391 
392  }
393 
394  double cost_function2 = trace_real/matrix_size;
395 
396  ret[2] = cost_function2;
397 
398  return ret;
399 
400 
401 
402 
403 
404 }
405 
407 
408  Matrix_real_float correction1 = get_cost_function_with_correction(matrix, qbit_num, trace_offset);
409  Matrix_real_float ret(1,3);
410  ret[0] = correction1[0];
411  ret[1] = correction1[1];
412 
413  int matrix_size = matrix.cols;
414  double trace_real = 0.0;
415 
416  if ( trace_offset == 0 ) {
417  for (int qbit_idx=0; qbit_idx<qbit_num-1; qbit_idx++) {
418  for (int qbit_idx2=qbit_idx+1; qbit_idx2<qbit_num; qbit_idx2++) {
419  int qbit_error_mask = (1 << qbit_idx) + (1 << qbit_idx2);
420  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
421  int row_idx = col_idx ^ qbit_error_mask;
422  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
423  }
424  }
425  }
426  }
427  else {
428  for (int qbit_idx=0; qbit_idx<qbit_num-1; qbit_idx++) {
429  for (int qbit_idx2=qbit_idx+1; qbit_idx2<qbit_num; qbit_idx2++) {
430  int qbit_error_mask = (1 << qbit_idx) + (1 << qbit_idx2);
431  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
432  int row_idx = (col_idx+trace_offset) ^ qbit_error_mask;
433  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
434  }
435  }
436  }
437  }
438 
439  ret[2] = static_cast<float>(trace_real/matrix_size);
440  return ret;
441 }
442 
444 {
445  double ret = 0.0;
446  for (int rowidx = 0; rowidx < matrix.rows; rowidx++) {
447  int baseidx = rowidx*matrix.stride;
448  for (int colidx = 0; colidx < matrix.cols; colidx++) {
449  if (rowidx == colidx) {
450  ret += (matrix[baseidx+colidx].real - 1.0) * (matrix[baseidx+colidx].real - 1.0) + matrix[baseidx+colidx].imag * matrix[baseidx+colidx].imag;
451  } else {
452  ret += matrix[baseidx+colidx].real * matrix[baseidx+colidx].real + matrix[baseidx+colidx].imag * matrix[baseidx+colidx].imag;
453  }
454  }
455  }
456  return ret;
457 }
458 
460 {
461  Matrix ret(matrix.rows, matrix.cols);
462  for (int rowidx = 0; rowidx < matrix.rows; rowidx++) {
463  int baseidx = rowidx*matrix.stride;
464  for (int colidx = 0; colidx < matrix.cols; colidx++) {
465  if (rowidx == colidx) {
466  ret[baseidx+colidx].real = 2 * (matrix[baseidx+colidx].real - 1.0);
467  ret[baseidx+colidx].imag = 2 * matrix[baseidx+colidx].imag;
468  } else {
469  ret[baseidx+colidx].real = 2 * matrix[baseidx+colidx].real;
470  ret[baseidx+colidx].imag = 2 * matrix[baseidx+colidx].imag;
471  }
472  }
473  }
474  return ret;
475 }
476 
483 
484  int matrix_size = matrix.cols;
485  double trace_real=0.0;
486  double trace_imag=0.0;
487  QGD_Complex16 ret;
488 
489  for (int idx=0; idx<matrix_size; idx++) {
490 
491  trace_real += matrix[idx*matrix.stride + idx].real;
492  trace_imag += matrix[idx*matrix.stride + idx].imag;
493 
494  }
495  ret.real = trace_real;
496  ret.imag = trace_imag;
497 
498  return ret;
499 }
500 
502 
503  int matrix_size = matrix.cols;
504  double trace_real=0.0;
505  double trace_imag=0.0;
506  QGD_Complex16 ret;
507 
508  for (int idx=0; idx<matrix_size; idx++) {
509  trace_real += matrix[idx*matrix.stride + idx].real;
510  trace_imag += matrix[idx*matrix.stride + idx].imag;
511  }
512  ret.real = trace_real;
513  ret.imag = trace_imag;
514 
515  return ret;
516 }
517 
525 
526  double d = 1.0/matrix.cols;
527  double cost_function = 0.0;
528  QGD_Complex16 ret = get_trace(matrix);
529  cost_function = 1.0-d*d*(ret.real*ret.real+ret.imag*ret.imag);
530 
531  return cost_function;
532 }
533 
535 
536  double d = 1.0/matrix.cols;
537  QGD_Complex16 ret = get_trace(matrix);
538  return 1.0-d*d*(ret.real*ret.real+ret.imag*ret.imag);
539 }
540 
541 double get_infidelity(Matrix& matrix){
542 
543  double d = matrix.cols;
544  double cost_function = 0.0;
545  QGD_Complex16 ret = get_trace(matrix);
546  cost_function = 1.0-((ret.real*ret.real+ret.imag*ret.imag)/d+1)/(d+1);
547 
548  return cost_function;
549 }
550 
551 double get_infidelity(Matrix_float& matrix){
552 
553  double d = matrix.cols;
554  QGD_Complex16 ret = get_trace(matrix);
555  return 1.0-((ret.real*ret.real+ret.imag*ret.imag)/d+1)/(d+1);
556 }
557 
565 
566  Matrix ret(1,2);
567 
568  QGD_Complex16 trace_tmp = get_trace(matrix);
569 
570  int matrix_size = matrix.cols;
571 
572  double trace_real = 0.0;
573  double trace_imag = 0.0;
574 
575  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
576 
577  int qbit_error_mask = 1 << qbit_idx;
578 
579  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
580 
581  // determine the row index pair with one bit error at the given qbit_idx
582  int row_idx = col_idx ^ qbit_error_mask;
583 
584  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
585  trace_imag += matrix[row_idx*matrix.stride + col_idx].imag;
586  }
587  }
588 
589  ret[0].real = trace_tmp.real;
590  ret[0].imag = trace_tmp.imag;
591  ret[1].real = trace_real;
592  ret[1].imag = trace_imag;
593 
594  return ret;
595 }
596 
597 
605 
606  Matrix ret(1,3);
607 
608  QGD_Complex16 trace_tmp = get_trace(matrix);
609 
610  int matrix_size = matrix.cols;
611 
612  double trace_real = 0.0;
613  double trace_imag = 0.0;
614 
615  for (int qbit_idx=0; qbit_idx<qbit_num; qbit_idx++) {
616 
617  int qbit_error_mask = 1 << qbit_idx;
618 
619  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
620 
621  // determine the row index pair with one bit error at the given qbit_idx
622  int row_idx = col_idx ^ qbit_error_mask;
623 
624  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
625  trace_imag += matrix[row_idx*matrix.stride + col_idx].imag;
626  }
627  }
628 
629 
630  ret[1].real = trace_real;
631  ret[1].imag = trace_imag;
632 
633 
634 
635  // calculate the second correction
636 
637  trace_real = 0.0;
638  trace_imag = 0.0;
639 
640  for (int qbit_idx=0; qbit_idx<qbit_num-1; qbit_idx++) {
641  for (int qbit_idx2=qbit_idx+1; qbit_idx2<qbit_num; qbit_idx2++) {
642 
643  int qbit_error_mask = (1 << qbit_idx) + (1 << qbit_idx2);
644 
645  for (int col_idx=0; col_idx<matrix_size; col_idx++) {
646 
647  // determine the row index pair with one bit error at the given qbit_idx
648  int row_idx = col_idx ^ qbit_error_mask;
649 
650  trace_real += matrix[row_idx*matrix.stride + col_idx].real;
651  trace_imag += matrix[row_idx*matrix.stride + col_idx].imag;
652  }
653 
654  }
655  }
656 
657 
658  ret[2].real = trace_real;
659  ret[2].imag = trace_imag;
660  ret[0].real = trace_tmp.real;
661  ret[0].imag = trace_tmp.imag;
662 
663  return ret;
664 }
665 
674 functor_cost_fnc::functor_cost_fnc( Matrix matrix_in, tbb::combinable<double>* partial_cost_functions_in ) {
675 
676  matrix = matrix_in;
677  data = matrix.get_data();
678  partial_cost_functions = partial_cost_functions_in;
679 }
680 
685 void functor_cost_fnc::operator()( tbb::blocked_range<int> r ) const {
686 
687  int matrix_size = matrix.rows;
688  double &cost_function_priv = partial_cost_functions->local();
689 
690  for ( int row_idx = r.begin(); row_idx != r.end(); row_idx++) {
691 
692  if ( row_idx > matrix_size ) {
693  std::string err("Error: row idx should be less than the number of roes in the matrix.");
694  throw err;
695  }
696 
697  // getting the corner element
698  QGD_Complex16 corner_element = data[0];
699 
700 
701  // Calculate the |x|^2 value of the elements of the matrix and summing them up to calculate the partial cost function
702  double partial_cost_function = 0;
703  int idx_offset = row_idx*matrix_size;
704  int idx_max = idx_offset + row_idx;
705  for ( int idx=idx_offset; idx<idx_max; idx++ ) {
706  partial_cost_function = partial_cost_function + data[idx].real*data[idx].real + data[idx].imag*data[idx].imag;
707  }
708 
709  int diag_element_idx = row_idx*matrix_size + row_idx;
710  double diag_real = data[diag_element_idx].real - corner_element.real;
711  double diag_imag = data[diag_element_idx].imag - corner_element.imag;
712  partial_cost_function = partial_cost_function + diag_real*diag_real + diag_imag*diag_imag;
713 
714 
715  idx_offset = idx_max + 1;
716  idx_max = row_idx*matrix_size + matrix_size;
717  for ( int idx=idx_offset; idx<idx_max; idx++ ) {
718  partial_cost_function = partial_cost_function + data[idx].real*data[idx].real + data[idx].imag*data[idx].imag;
719  }
720 
721  // storing the calculated partial cost function
722  cost_function_priv = cost_function_priv + partial_cost_function;
723 
724  }
725 }
726 
727 
728 
729 
730 
731 
732 // base-2 logarithm, rounding down
733 static inline uint32_t lg_down(uint32_t v) {
734  unsigned int r; // result of log2(v) will go here
735  unsigned int shift;
736 
737  r = (v > 0xFFFF) << 4; v >>= r;
738  shift = (v > 0xFF ) << 3; v >>= shift; r |= shift;
739  shift = (v > 0xF ) << 2; v >>= shift; r |= shift;
740  shift = (v > 0x3 ) << 1; v >>= shift; r |= shift;
741  r |= (v >> 1);
742  return r;
743 }
744 
745 // base-2 logarithm, rounding up
746 static inline uint32_t lg_up(uint32_t x) {
747  return x <= 1 ? 0 : lg_down(x - 1) + 1;
748 }
749 
750 // Helper: extract bits at positions 'pos' from integer x into a packed integer (LSB order)
751 static inline int extract_bits(int x, const std::vector<int>& pos) {
752  int y = 0, k = 0;
753  for (int p : pos) { y |= ((x >> p) & 1) << k; ++k; }
754  return y;
755 }
756 
757 // Index: row-major 2^n x 2^n
758 static inline size_t mat_idx(int row, int col, int nrows, int ncols) {
759 #ifdef USE_COL_MAJ
760  return (size_t)row + (size_t)col * (size_t)nrows;
761 #else
762  return (size_t)row * (size_t)ncols + (size_t)col;
763 #endif
764 }
765 
766 //https://www.sciencedirect.com/science/article/pii/S0024379518303446
767 //https://arxiv.org/abs/2007.02490
768 //https://journals.aps.org/pra/abstract/10.1103/PhysRevA.105.062430
769 //https://arxiv.org/pdf/2111.03132
770 // Build the (dA*dA) x (dB*dB) OSR matrix M for cut A|B from U (2^n x 2^n), row-major.
771 // M_{ (a' * dA + a), (b' * dB + b) } = U_{ (a',b'), (a,b) }.
772 template<class MatrixT, class ComplexT>
773 static std::vector<ComplexT> build_osr_matrix(const MatrixT& U, int n,
774  const std::vector<int>& A, // qubits on A
775  int& m_rows, int& m_cols)
776 {
777  std::vector<int> A_sorted = A;
778  std::sort(A_sorted.begin(), A_sorted.end());
779  std::vector<int> B;
780  B.reserve(n - (int)A_sorted.size());
781  for (int q = 0; q < n; ++q)
782  if (!std::binary_search(A_sorted.begin(), A_sorted.end(), q)) B.push_back(q);
783 
784  const int dA = 1 << (int)A_sorted.size();
785  const int dB = 1 << (n - (int)A_sorted.size());
786  const int N = 1 << n;
787 
788  m_rows = dA * dA;
789  m_cols = dB * dB;
790  std::vector<ComplexT> M;
791  M.resize((size_t)m_rows * (size_t)m_cols);
792 
793  // Row-major indexing: U[in + out*N] is element (in, out)
794  for (int in = 0; in < N; ++in) {
795  const int a = extract_bits(in, A_sorted) * dA;
796  const int b = extract_bits(in, B) * dB;
797  for (int out = 0; out < N; ++out) {
798  const int ap = extract_bits(out, A_sorted);
799  const int bp = extract_bits(out, B);
800  const int r = a + ap; // row in M
801  const int c = b + bp; // col in M
802  const auto& val = U[(size_t)in + (size_t)out * (size_t)N];
803  M[mat_idx(r, c, m_rows, m_cols)] = val;
804  }
805  }
806  return M;
807 }
808 
809 template<class MatrixT, class ComplexT>
810 static void accumulate_grad_for_cut(MatrixT& accum, const std::vector<double>& G,
811  const std::vector<ComplexT> & Umat,
812  const std::vector<ComplexT> & VTmat,
813  int n, const std::vector<int>& A, int rank=-1) // qubits on A
814 {
815  std::vector<int> A_sorted = A;
816  std::sort(A_sorted.begin(), A_sorted.end());
817  std::vector<int> B;
818  B.reserve(n - (int)A_sorted.size());
819  for (int q = 0; q < n; ++q)
820  if (!std::binary_search(A_sorted.begin(), A_sorted.end(), q)) B.push_back(q);
821 
822  const int dA = 1 << (int)A_sorted.size();
823  const int dB = 1 << (n - (int)A_sorted.size());
824  const int N = 1 << n;
825 
826  int m_rows = dA * dA;
827  int m_cols = dB * dB;
828 
829  int k = std::min(m_rows, m_cols);
830  int tot_dyadic = static_cast<int>(G.size());
831 
832  // Row-major indexing: accum[in + out*N] is element (in, out)
833  for (int in = 0; in < N; ++in) {
834  const int a = extract_bits(in, A_sorted) * dA;
835  const int b = extract_bits(in, B) * dB;
836  for (int out = 0; out < N; ++out) {
837  const int ap = extract_bits(out, A_sorted);
838  const int bp = extract_bits(out, B);
839  const int r = a + ap; // row in M
840  const int c = b + bp; // col in M
841  double val_real = 0.0;
842  double val_imag = 0.0;
843  if (rank < 0) {
844  // Old dyadic mode: G[i] applies to singular index 2^i
845  for (int i = 0; i < tot_dyadic; i++) {
846  int idx = 1<<i; //here we would conjugate again but that cancels out so we do nothing
847  if (idx >= k) break; // critical safety check
848  ComplexT u_val = Umat[mat_idx(r, idx, m_rows, k)];
849  ComplexT vt_val = VTmat[mat_idx(idx, c, k, m_cols)];
850  // Multiply: G[i] * u_val * vt_val
851  // (a+bi) * (c+di) = (ac-bd) + (ad+bc)i
852  double re_prod = u_val.real * vt_val.real - u_val.imag * vt_val.imag;
853  double im_prod = u_val.real * vt_val.imag + u_val.imag * vt_val.real;
854  // Multiply by G[i] and add to val
855  val_real += G[i] * re_prod;
856  val_imag += G[i] * im_prod;
857  }
858  } else {
859  // New rank-tail mode: G[j] applies directly to singular index j
860  const int diag_len = std::min<int>((int)G.size(), k);
861  for (int j = 0; j < diag_len; ++j) {
862  const ComplexT u_val = Umat[mat_idx(r, j, m_rows, k)];
863  const ComplexT vt_val = VTmat[mat_idx(j, c, k, m_cols)];
864 
865  const double re_prod = u_val.real * vt_val.real - u_val.imag * vt_val.imag;
866  const double im_prod = u_val.real * vt_val.imag + u_val.imag * vt_val.real;
867 
868  val_real += G[j] * re_prod;
869  val_imag += G[j] * im_prod;
870  }
871  }
872  accum[(size_t)in + (size_t)out * (size_t)N].real += static_cast<decltype(accum[0].real)>(val_real);
873  accum[(size_t)in + (size_t)out * (size_t)N].imag += static_cast<decltype(accum[0].imag)>(val_imag);
874  }
875  }
876 }
877 
878 static int lapack_gesdd_dispatch(int lapack_layout, char jobz, int m, int n, QGD_Complex16* a,
879  int lda, double* s, QGD_Complex16* u, int ldu,
880  QGD_Complex16* vt, int ldvt) {
881  return LAPACKE_zgesdd(lapack_layout, jobz, m, n, a, lda, s, u, ldu, vt, ldvt);
882 }
883 
884 static int lapack_gesdd_dispatch(int lapack_layout, char jobz, int m, int n, QGD_Complex8* a,
885  int lda, float* s, QGD_Complex8* u, int ldu,
886  QGD_Complex8* vt, int ldvt) {
887  return LAPACKE_cgesdd(lapack_layout, jobz, m, n, a, lda, s, u, ldu, vt, ldvt);
888 }
889 
890 #ifndef USE_SDD
891 static int lapack_gesvd_dispatch(int lapack_layout, char jobu, char jobvt, int m, int n, QGD_Complex16* a,
892  int lda, double* s, QGD_Complex16* u, int ldu,
893  QGD_Complex16* vt, int ldvt, double* superb) {
894  return LAPACKE_zgesvd(lapack_layout, jobu, jobvt, m, n, a, lda, s, u, ldu, vt, ldvt, superb);
895 }
896 
897 static int lapack_gesvd_dispatch(int lapack_layout, char jobu, char jobvt, int m, int n, QGD_Complex8* a,
898  int lda, float* s, QGD_Complex8* u, int ldu,
899  QGD_Complex8* vt, int ldvt, float* superb) {
900  return LAPACKE_cgesvd(lapack_layout, jobu, jobvt, m, n, a, lda, s, u, ldu, vt, ldvt, superb);
901 }
902 #endif
903 
904 template<class ComplexT, class RealT>
905 static std::vector<double> osr(std::vector<ComplexT>& A, int m_rows, int m_cols, double Fnorm)
906 {
907  int k = std::min(m_rows, m_cols);
908  std::vector<RealT> S(k);
909 #ifdef USE_COL_MAJ
910  constexpr int lapack_layout = LAPACK_COL_MAJOR;
911  const int lda = m_rows;
912  const int ldu = m_rows;
913  const int ldvt = k; // VT is k x m_cols in col-major
914 #else
915  constexpr int lapack_layout = LAPACK_ROW_MAJOR;
916  const int lda = m_cols;
917  const int ldu = k; // U is m_rows x k in row-major
918  const int ldvt = m_cols;
919 #endif
920 #ifdef USE_SDD
921  int info = lapack_gesdd_dispatch(lapack_layout,
922  'N',
923  m_rows, m_cols,
924  A.data(), lda,
925  S.data(),
926  nullptr, ldu,
927  nullptr, ldvt);
928 #else
929  std::vector<RealT> superb(std::max(1, k - 1)); // REQUIRED for complex *gesvd
930  // We don’t need U/V; job='N' for economy; gesvd is fine too.
931  int info = lapack_gesvd_dispatch(lapack_layout,
932  'N','N',
933  m_rows, m_cols,
934  A.data(), lda,
935  S.data(),
936  nullptr, ldu,
937  nullptr, ldvt,
938  superb.data());
939 #endif
940  if (info != 0) {
941  throw std::runtime_error("gesvd failed, info=" + std::to_string(info));
942  }
943  std::vector<double> normalized;
944  normalized.reserve(S.size());
945  for (RealT s : S) normalized.push_back(static_cast<double>(s)/Fnorm); //normalize
946  //std::copy(S.begin(), S.end(), std::ostream_iterator<double>(std::cout, " ")); std::cout << std::endl;
947  return normalized;
948 }
949 
950 // Numerical rank via LAPACKE_zgesdd/svd (SVD)
951 static int numerical_rank_osr(std::vector<double> S, double tol)
952 {
953  int rnk = 0;
954  for (double s : S) if (s > S[0]*tol) ++rnk;
955  return lg_up(rnk);
956 }
957 
958 // Generate all k-combinations of {0,1,...,n-1} of size r
959 static void combinations_recursive(int n, int r, int start,
960  std::vector<int>& current,
961  std::vector<std::vector<int>>& out)
962 {
963  if ((int)current.size() == r) {
964  out.push_back(current);
965  return;
966  }
967  for (int i = start; i < n; ++i) {
968  current.push_back(i);
969  combinations_recursive(n, r, i + 1, current, out);
970  current.pop_back();
971  }
972 }
973 
974 // Return all nontrivial unordered bipartitions (no complements)
975 std::vector<std::vector<int>> unique_cuts(int n)
976 {
977  std::vector<std::vector<int>> cuts;
978  if (n <= 1) return cuts;
979 
980  for (int r = 1; r <= n / 2; ++r) {
981  std::vector<std::vector<int>> combs;
982  std::vector<int> current;
983  combinations_recursive(n, r, 0, current, combs);
984 
985  for (auto& S : combs) {
986  if (r < n - r) {
987  cuts.push_back(S);
988  } else { // r == n - r (only for even n)
989  std::vector<int> comp;
990  for (int q = 0; q < n; ++q)
991  if (std::find(S.begin(), S.end(), q) == S.end())
992  comp.push_back(q);
993  // lexicographic tie-break: keep only smaller one
994  if (S < comp)
995  cuts.push_back(S);
996  }
997  }
998  }
999  return cuts;
1000 }
1001 
1002 inline double logsumexp_smoothmax(const std::vector<double>& Lc, double tau=1e-2){
1003  double m = *std::max_element(Lc.begin(), Lc.end());
1004  double sum = 0.0;
1005  for (double v : Lc) sum += std::exp((v - m)/tau);
1006  return tau * std::log(sum) + m;
1007 }
1008 
1009 // Assumes S are nonnegative singular values (ideally sorted desc).
1010 double weighted_loss_for_rank(const std::vector<double>& S, int rank, double rho=0.1, double tol=1e-4) {
1011  const size_t start = size_t(1) << rank;
1012  const int start_i = static_cast<int>(start);
1013  double w = 1.0;
1014  double acc = 0.0;
1015  for (int k = static_cast<int>(S.size()) - 1; k >= start_i; --k) {
1016  double val = S[k]; // - S[0] * tol;
1017  acc += w * val * val;
1018  w *= rho; // geometric weight rho^k
1019  }
1020  return acc;
1021 }
1022 
1023 // rank = 0 -> target rank 1 -> tail starts at index 1
1024 // rank = 1 -> target rank 2 -> tail starts at index 2
1025 // rank = 2 -> target rank 4 -> tail starts at index 4
1026 double loss_for_rank(const std::vector<double>& S, int rank) {
1027  const size_t start = size_t(1) << rank;
1028  if (start >= S.size()) return 0.0;
1029 
1030  double acc = 0.0;
1031  for (size_t i = start; i < S.size(); ++i) {
1032  acc += S[i] * S[i];
1033  }
1034  return acc;
1035 }
1036 
1037 double avg_loss_for_rank(const std::vector<std::vector<double>>& cuts_S, int rank) {
1038  if (cuts_S.empty()) return 0.0;
1039 
1040  double tot = 0.0;
1041  for (const auto& S : cuts_S) {
1042  tot += loss_for_rank(S, rank);
1043  }
1044  return tot / static_cast<double>(cuts_S.size());
1045 }
1046 
1047 double cuts_softmax_rank_cost(const std::vector<std::vector<double>>& cuts_S,
1048  int rank, double tau = 1e-2) {
1049  if (tau <= 0.0) {
1050  throw std::invalid_argument("cuts_softmax_rank_cost: tau must be > 0");
1051  }
1052  if (cuts_S.empty()) return 0.0;
1053 
1054  std::vector<double> Lc;
1055  Lc.reserve(cuts_S.size());
1056  for (const auto& S : cuts_S) {
1057  Lc.push_back(loss_for_rank(S, rank));
1058  }
1059  return logsumexp_smoothmax(Lc, tau);
1060 }
1061 
1062 std::vector<double> loss_for_rank_grad_diag(const std::vector<double>& S, int rank, double Fnorm) {
1063  const size_t n = S.size();
1064  const size_t start = size_t(1) << rank;
1065 
1066  std::vector<double> grad(n, 0.0);
1067  if (start >= n) return grad;
1068 
1069  const double invF = 1.0 / Fnorm;
1070  for (size_t i = start; i < n; ++i) {
1071  grad[i] = 2.0 * S[i] * invF;
1072  }
1073  return grad;
1074 }
1075 
1076 std::vector<std::vector<double>> cuts_avg_rank_grad(
1077  const std::vector<std::vector<double>>& cuts_S,
1078  int rank,
1079  double Fnorm)
1080 {
1081  const size_t C = cuts_S.size();
1082  if (C == 0) return {};
1083 
1084  const double scale = 1.0 / static_cast<double>(C);
1085 
1086  std::vector<std::vector<double>> G;
1087  G.reserve(C);
1088 
1089  for (const auto& S : cuts_S) {
1090  std::vector<double> g = loss_for_rank_grad_diag(S, rank, Fnorm);
1091  for (double& v : g) v *= scale;
1092  G.emplace_back(std::move(g));
1093  }
1094  return G;
1095 }
1096 
1097 std::vector<std::vector<double>> cuts_softmax_rank_grad(
1098  const std::vector<std::vector<double>>& cuts_S,
1099  int rank,
1100  double Fnorm,
1101  double tau = 1e-2)
1102 {
1103  const size_t C = cuts_S.size();
1104  if (C == 0) return {};
1105  if (tau <= 0.0) {
1106  throw std::invalid_argument("cuts_softmax_rank_grad: tau must be > 0");
1107  }
1108 
1109  // 1) per-cut losses
1110  std::vector<double> Lc(C, 0.0);
1111  for (size_t c = 0; c < C; ++c) {
1112  Lc[c] = loss_for_rank(cuts_S[c], rank);
1113  }
1114 
1115  // 2) softmax weights
1116  const double m = *std::max_element(Lc.begin(), Lc.end());
1117  std::vector<double> w(C, 0.0);
1118  double Z = 0.0;
1119  for (size_t c = 0; c < C; ++c) {
1120  w[c] = std::exp((Lc[c] - m) / tau);
1121  Z += w[c];
1122  }
1123  if (Z <= 0.0) Z = 1.0;
1124  for (size_t c = 0; c < C; ++c) {
1125  w[c] /= Z;
1126  }
1127 
1128  // 3) weighted per-cut gradients
1129  std::vector<std::vector<double>> G;
1130  G.reserve(C);
1131 
1132  for (size_t c = 0; c < C; ++c) {
1133  std::vector<double> g = loss_for_rank_grad_diag(cuts_S[c], rank, Fnorm);
1134  for (double& v : g) v *= w[c];
1135  G.emplace_back(std::move(g));
1136  }
1137  return G;
1138 }
1139 
1140 // Assumes S are nonnegative singular values (ideally sorted desc).
1141 double tail_loss(const std::vector<double>& S, int max_dyadic, double rho=0.1, double tol=1e-4) {
1142  int tot_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(S.size())));
1143  double w = 1.0;
1144  double acc = 0.0;
1145  for (int k = max_dyadic-1; k >= 0; --k) {
1146  if (k < tot_dyadic) {
1147  double val = S[static_cast<size_t>(1) << k] - S[0] * tol;
1148  acc += w * val * val;
1149  }
1150  w *= rho; // geometric weight rho^k
1151  }
1152  return acc;
1153 }
1154 
1155 double avg_tail_loss(const std::vector<std::vector<double>>& cuts_S, double rho=0.1) {
1156  double tot = 0.0;
1157  int max_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(std::max_element(
1158  cuts_S.begin(), cuts_S.end(),
1159  [](const std::vector<double>& a, const std::vector<double>& b) {
1160  return a.size() < b.size();
1161  })->size())));
1162  for (const auto& S : cuts_S)
1163  tot += tail_loss(S, max_dyadic, rho);
1164  return tot / static_cast<double>(cuts_S.size());
1165 }
1166 
1167 // Aggregated cost over cuts: softmax (log-sum-exp) of per-cut tail losses
1168 double cuts_softmax_tail_cost(const std::vector<std::vector<double>>& cuts_S,
1169  double rho=0.1, double tau=1e-2)
1170 {
1171  if (tau <= 0.0) throw std::invalid_argument("cuts_softmax_tail_cost: tau must be > 0");
1172  std::vector<double> Lc; Lc.reserve(cuts_S.size());
1173  int max_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(std::max_element(
1174  cuts_S.begin(), cuts_S.end(),
1175  [](const std::vector<double>& a, const std::vector<double>& b) {
1176  return a.size() < b.size();
1177  })->size())));
1178  for (const auto& S : cuts_S)
1179  Lc.push_back(tail_loss(S, max_dyadic, rho));
1180  return logsumexp_smoothmax(Lc, tau);
1181 }
1182 
1183 // Gradient w.r.t. the singular values (diagonal of dL/dΣ):
1184 std::vector<double> tail_loss_grad_diag(const std::vector<double>& S, int max_dyadic, double Fnorm, double rho=0.1, double tol=1e-4) {
1185  const size_t n = S.size();
1186 
1187  // c_k = rho^k / Mk for k=1..n-1, then prefix sum C_j = sum_{k=1}^j c_k
1188  int tot_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(n)));
1189  std::vector<double> grad(tot_dyadic, 0.0);
1190  double w = 1.0;
1191  for (int k = max_dyadic-1; k >= 0; --k) {
1192  if (k < tot_dyadic) {
1193  int idx = 1 << k;
1194  grad[k] = 2.0 * w * S[idx] * (1.0-tol) / Fnorm; //1-tol not needed if using stop-grad
1195  }
1196  w *= rho; // w = rho^k
1197  }
1198  return grad;
1199 }
1200 
1201 std::vector<std::vector<double>> cuts_avg_tail_grad(const std::vector<std::vector<double>>& cuts_S, double Fnorm, double rho=0.1) {
1202  const size_t C = cuts_S.size();
1203  int max_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(std::max_element(
1204  cuts_S.begin(), cuts_S.end(),
1205  [](const std::vector<double>& a, const std::vector<double>& b) {
1206  return a.size() < b.size();
1207  })->size())));
1208  std::vector<std::vector<double>> Lc;
1209  Lc.reserve(C);
1210  for (size_t c = 0; c < C; ++c) {
1211  Lc.emplace_back(tail_loss_grad_diag(cuts_S[c], max_dyadic, Fnorm * C, rho));
1212  }
1213  return Lc;
1214 }
1215 
1216 
1217 // Gradient w.r.t. singular values (same length as S).
1218 // Only dyadic positions (1,2,4,...) get nonzero entries; others are 0.
1219 std::vector<std::vector<double>> cuts_softmax_tail_grad(
1220  const std::vector<std::vector<double>>& cuts_S, double Fnorm,
1221  double rho=0.1, double tau=1e-2)
1222 {
1223  const size_t C = cuts_S.size();
1224  if (C == 0) return {};
1225  int max_dyadic = static_cast<int>(lg_up(static_cast<uint32_t>(std::max_element(
1226  cuts_S.begin(), cuts_S.end(),
1227  [](const std::vector<double>& a, const std::vector<double>& b) {
1228  return a.size() < b.size();
1229  })->size())));
1230 
1231  // 1) per-cut losses
1232  std::vector<double> Lc(C, 0.0);
1233  for (size_t c = 0; c < C; ++c)
1234  Lc[c] = tail_loss(cuts_S[c], max_dyadic, rho);
1235 
1236  // 2) softmax weights w_c = exp((Lc - m)/tau) / Z
1237  const double m = *std::max_element(Lc.begin(), Lc.end());
1238  std::vector<double> w(C, 0.0);
1239  double Z = 0.0;
1240  for (size_t c = 0; c < C; ++c) { w[c] = std::exp((Lc[c] - m)/tau); Z += w[c]; }
1241  for (size_t c = 0; c < C; ++c) w[c] /= (Z > 0.0 ? Z : 1.0);
1242 
1243  // 3) dL/dS^{(c)} = w_c * dL_c/dS^{(c)}
1244  std::vector<std::vector<double>> G; G.reserve(C);
1245  for (size_t c = 0; c < C; ++c) {
1246  std::vector<double> gc = tail_loss_grad_diag(cuts_S[c], max_dyadic, Fnorm, rho);
1247  for (double& v : gc) v *= w[c];
1248  G.emplace_back(gc);
1249  }
1250  return G;
1251 }
1252 
1253 // Public: operator-Schmidt rank across cut A|B
1254 std::pair<int, double> operator_schmidt_rank(const Matrix& U, int n,
1255  const std::vector<int>& A_qubits,
1256  double Fnorm, double tol)
1257 {
1258 
1259  int mr=0, mc=0;
1260  std::vector<QGD_Complex16> M = build_osr_matrix<Matrix, QGD_Complex16>(U, n, A_qubits, mr, mc);
1261  std::vector<double> S = osr<QGD_Complex16, double>(M, mr, mc, Fnorm);
1262  int min_cnot = numerical_rank_osr(S, tol);
1263  return std::pair<int, double>(min_cnot,
1264  //tail_loss(S, static_cast<int>(lg_up(static_cast<uint32_t>(S.size()))))
1265  weighted_loss_for_rank(S, min_cnot)
1266  );
1267 }
1268 
1269 std::pair<int, double> operator_schmidt_rank(const Matrix_float& U, int n,
1270  const std::vector<int>& A_qubits,
1271  double Fnorm, double tol)
1272 {
1273 
1274  int mr=0, mc=0;
1275  std::vector<QGD_Complex8> M = build_osr_matrix<Matrix_float, QGD_Complex8>(U, n, A_qubits, mr, mc);
1276  std::vector<double> S = osr<QGD_Complex8, float>(M, mr, mc, Fnorm);
1277  int min_cnot = numerical_rank_osr(S, tol);
1278  return std::pair<int, double>(min_cnot,
1279  weighted_loss_for_rank(S, min_cnot)
1280  );
1281 }
1282 
1283 template<class MatrixT, class ComplexT, class RealT>
1284 static double get_osr_entanglement_test_impl(MatrixT& matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1285  //double hscost = get_hilbert_schmidt_test(matrix);
1286  int qbit_num = lg_down(matrix.rows);
1287  const auto& cuts = use_cuts.size() == 0 ? unique_cuts(qbit_num) : use_cuts;
1288  double Fnorm = std::sqrt(matrix.rows);
1289  std::vector<std::vector<double>> allS;
1290  allS.reserve(cuts.size());
1291  for (const auto& cut : cuts) {
1292  int mr=0, mc=0;
1293  std::vector<ComplexT> M = build_osr_matrix<MatrixT, ComplexT>(matrix, qbit_num, cut, mr, mc);
1294  std::vector<double> S = osr<ComplexT, RealT>(M, mr, mc, Fnorm);
1295  allS.emplace_back(S);
1296  //printf("%f ", S[0]);
1297  }
1298  double res;
1299  if (rank == -1) {
1300  res = use_softmax ? cuts_softmax_tail_cost(allS, 1.0) : avg_tail_loss(allS, 0.9);
1301  } else {
1302  res = use_softmax ? cuts_softmax_rank_cost(allS, rank) : avg_loss_for_rank(allS, rank);
1303  }
1304  //printf("%f\n", res);
1305  return res;
1306 }
1307 
1308 double get_osr_entanglement_test(Matrix& matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1309  return get_osr_entanglement_test_impl<Matrix, QGD_Complex16, double>(matrix, use_cuts, rank, use_softmax);
1310 }
1311 
1312 double get_osr_entanglement_test(Matrix_float& matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1313  return get_osr_entanglement_test_impl<Matrix_float, QGD_Complex8, float>(matrix, use_cuts, rank, use_softmax);
1314 }
1315 
1316 template<class ComplexT>
1317 struct OSRTriplet {
1318  std::vector<double> singulars;
1319  std::vector<ComplexT> left_factors;
1320  std::vector<ComplexT> right_factors;
1321 
1322  OSRTriplet() = default;
1323 
1324  OSRTriplet(std::vector<double> s,
1325  std::vector<ComplexT> u,
1326  std::vector<ComplexT> vt)
1327  : singulars(std::move(s)),
1328  left_factors(std::move(u)),
1329  right_factors(std::move(vt)) {}
1330 };
1331 
1332 // Build M with build_osr_matrix, then SVD (econ) and grab top triplet.
1333 template<class MatrixT, class ComplexT, class RealT>
1335  const MatrixT& U, // (N x N), row-major, N = 1<<q
1336  int q, // number of qubits
1337  const std::vector<int>& A, // qubits on side A
1338  double Fnorm, // e.g., sqrt(N)
1339  int &m_rows, int &m_cols
1340 ){
1341  // 1) Build M for this cut
1342 
1343  std::vector<ComplexT> M = build_osr_matrix<MatrixT, ComplexT>(U, q, A, m_rows, m_cols);
1344 
1345  const int k = std::min(m_rows, m_cols);
1346 
1347  // 2) Allocate outputs for SVD (econ)
1348  std::vector<RealT> S(k);
1349  std::vector<ComplexT> Umat((size_t)m_rows * (size_t)k); // m x k
1350  std::vector<ComplexT> VTmat((size_t)k * (size_t)m_cols); // k x n
1351 
1352  // 3) SVD: M = U * diag(S) * VT (VT = V^H)
1353  // Row-major API handles leading dims as col counts.
1354 #ifdef USE_COL_MAJ
1355  constexpr int lapack_layout = LAPACK_COL_MAJOR;
1356  const int lda = m_rows;
1357  const int ldu = m_rows;
1358  const int ldvt = k; // VT is k x m_cols in col-major
1359 #else
1360  constexpr int lapack_layout = LAPACK_ROW_MAJOR;
1361  const int lda = m_cols;
1362  const int ldu = k; // U is m_rows x k in row-major
1363  const int ldvt = m_cols;
1364 #endif
1365 #ifdef USE_SDD
1366  int info = lapack_gesdd_dispatch(
1367  lapack_layout,
1368  'S', // econ / thin U, VT
1369  m_rows, m_cols,
1370  M.data(), lda, // a, lda (row-major => lda = ncols)
1371  S.data(),
1372  Umat.data(), ldu, // U is (m_rows x k), row-major => ldu = k
1373  VTmat.data(), ldvt // VT is (k x m_cols), row-major => ldvt = m_cols
1374  );
1375 #else
1376  std::vector<RealT> superb(std::max(1, k - 1)); // REQUIRED for complex *gesvd
1377  int info = lapack_gesvd_dispatch(
1378  lapack_layout,
1379  'S', 'S', // econ U, VT
1380  m_rows, m_cols,
1381  M.data(), lda, // a, lda (row-major -> lda = n)
1382  S.data(),
1383  Umat.data(), ldu, // U (m x k), ldu = k (row-major)
1384  VTmat.data(), ldvt, // VT (k x n), ldvt = n
1385  superb.data()
1386  );
1387 #endif
1388  if (info != 0) {
1389  throw std::runtime_error("gesvd failed, info=" + std::to_string(info));
1390  }
1391  std::vector<double> S_normalized;
1392  S_normalized.reserve(S.size());
1393  for (RealT s : S) S_normalized.push_back(static_cast<double>(s)/Fnorm); // normalized singular value
1394 
1395  return OSRTriplet<ComplexT>(std::move(S_normalized), std::move(Umat), std::move(VTmat));
1396 }
1397 
1398 template<class MatrixT, class ComplexT, class RealT>
1399 static MatrixT get_deriv_osr_entanglement_impl(MatrixT &matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1400  int qbit_num = lg_down(matrix.rows);
1401  const auto& cuts = use_cuts.size() == 0 ? unique_cuts(qbit_num) : use_cuts;
1402  double Fnorm = std::sqrt(matrix.rows);
1403  MatrixT deriv(matrix.rows, matrix.cols);
1404  std::fill(deriv.data, deriv.data+deriv.size(), ComplexT{0.0, 0.0});
1405  // Compute the derivative of the OSR entanglement cost function
1406  std::vector<OSRTriplet<ComplexT>> triplets;
1407  std::vector<std::vector<double>> allS;
1408  triplets.reserve(cuts.size());
1409  for (const auto& cut : cuts) {
1410  // 1) top k triplet on the normalized reshape M_c
1411  int m_rows = 0, m_cols = 0;
1412  OSRTriplet<ComplexT> triplet = top_k_triplet_for_cut<MatrixT, ComplexT, RealT>(matrix, qbit_num, cut, Fnorm, m_rows, m_cols);
1413  allS.emplace_back(std::move(triplet.singulars));
1414  OSRTriplet<ComplexT> stored;
1415  stored.left_factors = std::move(triplet.left_factors);
1416  stored.right_factors = std::move(triplet.right_factors);
1417  triplets.emplace_back(std::move(stored));
1418  }
1419  if (rank == -1) {
1420  if (use_softmax) allS = cuts_softmax_tail_grad(allS, Fnorm, 1.0);
1421  else allS = cuts_avg_tail_grad(allS, Fnorm, 0.9);
1422  } else {
1423  if (use_softmax) allS = cuts_softmax_rank_grad(allS, rank, Fnorm);
1424  else allS = cuts_avg_rank_grad(allS, rank, Fnorm);
1425  }
1426  for (int i = 0; i < (int)cuts.size(); ++i) {
1427  triplets[i].singulars = std::move(allS[i]);
1428  }
1429  for (int i = 0; i < (int)cuts.size(); ++i) {
1430  const OSRTriplet<ComplexT>& triplet = triplets[i];
1431  accumulate_grad_for_cut<MatrixT, ComplexT>(deriv,
1432  triplet.singulars,
1433  triplet.left_factors,
1434  triplet.right_factors,
1435  qbit_num,
1436  cuts[i], rank);
1437  }
1438  return deriv;
1439 }
1440 
1441 Matrix get_deriv_osr_entanglement(Matrix &matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1442  return get_deriv_osr_entanglement_impl<Matrix, QGD_Complex16, double>(matrix, use_cuts, rank, use_softmax);
1443 }
1444 
1445 Matrix_float get_deriv_osr_entanglement(Matrix_float &matrix, std::vector<std::vector<int>> &use_cuts, int rank, bool use_softmax) {
1446  return get_deriv_osr_entanglement_impl<Matrix_float, QGD_Complex8, float>(matrix, use_cuts, rank, use_softmax);
1447 }
1448 
1449 // Compute grad component = Re Tr( A^† B ) for A = dL/dU, B = dU/dθ
1450 // A and B are (rows x cols) with row-major leading dimension.
1452 {
1453  double acc = 0.0;
1454  for (int r = 0; r < A.rows; ++r) {
1455  int offs = r * A.stride;
1456  for (int c = 0; c < A.cols; ++c) {
1457  acc += A[offs + c].real * B[offs + c].real + A[offs + c].imag * B[offs + c].imag;
1458  }
1459  }
1460  return acc; // Re Tr(A^† B)
1461 }
1462 
1464 {
1465  double acc = 0.0;
1466  for (int r = 0; r < A.rows; ++r) {
1467  int offs = r * A.stride;
1468  for (int c = 0; c < A.cols; ++c) {
1469  acc += A[offs + c].real * B[offs + c].real + A[offs + c].imag * B[offs + c].imag;
1470  }
1471  }
1472  return acc;
1473 }
static std::vector< ComplexT > build_osr_matrix(const MatrixT &U, int n, const std::vector< int > &A, int &m_rows, int &m_cols)
double get_hilbert_schmidt_test(Matrix &matrix)
Call co calculate the cost function of the optimization process according to https://arxiv.org/pdf/2210.09191.pdf.
double avg_loss_for_rank(const std::vector< std::vector< double >> &cuts_S, int rank)
double cuts_softmax_tail_cost(const std::vector< std::vector< double >> &cuts_S, double rho=0.1, double tau=1e-2)
Class to store single-precision real arrays and properties.
int LAPACKE_zgesdd(int matrix_order, char jobz, int m, int n, QGD_Complex16 *a, int lda, double *s, QGD_Complex16 *u, int ldu, QGD_Complex16 *vt, int ldvt)
Definition: S.h:11
QGD_Complex16 * data
Pointer to the data stored in the matrix.
std::vector< ComplexT > left_factors
static int lapack_gesdd_dispatch(int lapack_layout, char jobz, int m, int n, QGD_Complex16 *a, int lda, double *s, QGD_Complex16 *u, int ldu, QGD_Complex16 *vt, int ldvt)
static int extract_bits(int x, const std::vector< int > &pos)
static uint32_t lg_up(uint32_t x)
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
double get_infidelity(Matrix &matrix)
Call to calculate infidelity.
Structure type representing single-precision complex numbers.
Definition: QGDTypes.h:46
std::vector< std::vector< double > > cuts_avg_rank_grad(const std::vector< std::vector< double >> &cuts_S, int rank, double Fnorm)
std::vector< ComplexT > right_factors
std::vector< std::vector< double > > cuts_softmax_tail_grad(const std::vector< std::vector< double >> &cuts_S, double Fnorm, double rho=0.1, double tau=1e-2)
std::vector< double > tail_loss_grad_diag(const std::vector< double > &S, int max_dyadic, double Fnorm, double rho=0.1, double tol=1e-4)
OSRTriplet(std::vector< double > s, std::vector< ComplexT > u, std::vector< ComplexT > vt)
static MatrixT get_deriv_osr_entanglement_impl(MatrixT &matrix, std::vector< std::vector< int >> &use_cuts, int rank, bool use_softmax)
static int numerical_rank_osr(std::vector< double > S, double tol)
scalar * get_data() const
Call to get the pointer to the stored data.
std::vector< std::vector< int > > unique_cuts(int n)
Definition: Z.h:11
static void combinations_recursive(int n, int r, int start, std::vector< int > &current, std::vector< std::vector< int >> &out)
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
Matrix matrix
Array stroing the matrix.
static size_t mat_idx(int row, int col, int nrows, int ncols)
Custom types for the SQUANDER package.
functor_cost_fnc(Matrix matrix_in, tbb::combinable< double > *partial_cost_functions_in)
Constructor of the class.
std::vector< std::vector< double > > cuts_avg_tail_grad(const std::vector< std::vector< double >> &cuts_S, double Fnorm, double rho=0.1)
double tail_loss(const std::vector< double > &S, int max_dyadic, double rho=0.1, double tol=1e-4)
double logsumexp_smoothmax(const std::vector< double > &Lc, double tau=1e-2)
static uint32_t lg_down(uint32_t v)
void operator()(tbb::blocked_range< int > r) const
Operator to calculate the partial cost function derived from the row of the matrix labeled by row_idx...
double cuts_softmax_rank_cost(const std::vector< std::vector< double >> &cuts_S, int rank, double tau=1e-2)
Matrix get_trace_with_correction2(Matrix &matrix, int qbit_num)
Call co calculate the Hilbert Schmidt testof the optimization process, and the first correction to th...
Matrix_real get_cost_function_with_correction(const Matrix &matrix, int qbit_num, int trace_offset)
Call co calculate the cost function of the optimization process, and the first correction to the cost...
Structure type representing complex numbers in the SQUANDER package.
Definition: QGDTypes.h:38
Double-precision complex matrix (float64).
Definition: matrix.h:38
double get_cost_function_sum_of_squares(Matrix &matrix)
double get_cost_function(const Matrix &matrix, int trace_offset)
Call co calculate the cost function during the final optimization process.
std::vector< std::vector< double > > cuts_softmax_rank_grad(const std::vector< std::vector< double >> &cuts_S, int rank, double Fnorm, double tau=1e-2)
Single-precision complex matrix (float32).
Definition: matrix_float.h:41
Matrix get_deriv_sum_of_squares(Matrix &matrix)
static double get_osr_entanglement_test_impl(MatrixT &matrix, std::vector< std::vector< int >> &use_cuts, int rank, bool use_softmax)
double avg_tail_loss(const std::vector< std::vector< double >> &cuts_S, double rho=0.1)
std::vector< double > loss_for_rank_grad_diag(const std::vector< double > &S, int rank, double Fnorm)
double weighted_loss_for_rank(const std::vector< double > &S, int rank, double rho=0.1, double tol=1e-4)
Header file for the paralleized calculation of the cost function of the final optimization problem (s...
static void accumulate_grad_for_cut(MatrixT &accum, const std::vector< double > &G, const std::vector< ComplexT > &Umat, const std::vector< ComplexT > &VTmat, int n, const std::vector< int > &A, int rank=-1)
static OSRTriplet< ComplexT > top_k_triplet_for_cut(const MatrixT &U, int q, const std::vector< int > &A, double Fnorm, int &m_rows, int &m_cols)
double loss_for_rank(const std::vector< double > &S, int rank)
double real
the real part of a complex number
Definition: QGDTypes.h:40
Matrix get_trace_with_correction(Matrix &matrix, int qbit_num)
Call co calculate the Hilbert Schmidt testof the optimization process, and the first correction to th...
int LAPACKE_cgesdd(int matrix_order, char jobz, int m, int n, QGD_Complex8 *a, int lda, float *s, QGD_Complex8 *u, int ldu, QGD_Complex8 *vt, int ldvt)
int LAPACKE_cgesvd(int matrix_order, char jobu, char jobvt, int m, int n, QGD_Complex8 *a, int lda, float *s, QGD_Complex8 *u, int ldu, QGD_Complex8 *vt, int ldvt, float *superb)
int LAPACKE_zgesvd(int matrix_order, char jobu, char jobvt, int m, int n, QGD_Complex16 *a, int lda, double *s, QGD_Complex16 *u, int ldu, QGD_Complex16 *vt, int ldvt, double *superb)
std::pair< int, double > operator_schmidt_rank(const Matrix &U, int n, const std::vector< int > &A_qubits, double Fnorm, double tol)
static std::vector< double > osr(std::vector< ComplexT > &A, int m_rows, int m_cols, double Fnorm)
double real_trace_conj_dot(Matrix &A, Matrix &B)
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
tbb::combinable< double > * partial_cost_functions
array storing the partial cost functions
double imag
the imaginary part of a complex number
Definition: QGDTypes.h:42
double get_osr_entanglement_test(Matrix &matrix, std::vector< std::vector< int >> &use_cuts, int rank, bool use_softmax)
Matrix get_deriv_osr_entanglement(Matrix &matrix, std::vector< std::vector< int >> &use_cuts, int rank, bool use_softmax)
QGD_Complex16 get_trace(Matrix &matrix)
Call to calculate the real and imaginary parts of the trace.
Matrix_real get_cost_function_with_correction2(const Matrix &matrix, int qbit_num, int trace_offset)
Call co calculate the cost function of the optimization process, and the first correction to the cost...