The Quantum Exact Simulation Toolkit v4.3.0
Loading...
Searching...
No Matches
linalg.cpp
1/** @file
2 * Testing utilities which perform linear algebra
3 * routines upon reference qvector and qmatrix.
4 * These are slow, serial, un-optimised, defensively-
5 * designed routines.
6 *
7 * @author Tyson Jones
8 */
9
10#include "qvector.hpp"
11#include "qmatrix.hpp"
12#include "linalg.hpp"
13#include "macros.hpp"
14#include "compare.hpp"
15
16#include <algorithm>
17#include <vector>
18#include <limits>
19
20using std::vector;
21
22
23
24/*
25 * SCALAR OPERATIONS
26 */
27
28
29int getLog2(qindex a) {
30 DEMAND( a >= 0 );
31 DEMAND( (a & (a - 1)) == 0 ); // is pow2
32
33 int n = 0;
34 while (a >>= 1)
35 n++;
36
37 return n;
38}
39
40
41qindex getPow2(int a) {
42 DEMAND( a >= 0 );
43
44 return ((qindex) 1) << a;
45}
46
47
48int getBitAt(qindex num, int ind) {
49 DEMAND( num >= 0 );
50
51 return (num >> ind) & 1;
52}
53
54
55vector<int> getBits(qindex num, int numBits) {
56 DEMAND( numBits > 0 );
57
58 // out ordered least to most significant
59 vector<int> out(numBits);
60
61 for (int i=0; i<numBits; i++)
62 out[i] = getBitAt(num, i);
63
64 return out;
65}
66
67
68qindex getBitsAt(qindex num, vector<int> inds) {
69 DEMAND( num >= 0 );
70
71 qindex out = 0;
72
73 for (size_t i=0; i<inds.size(); i++)
74 out |= getBitAt(num, inds[i]) << i;
75
76 return out;
77}
78
79
80qindex setBitAt(qindex num, int ind, int bit) {
81 DEMAND( num >= 0 );
82
83 qindex one = 1;
84 return (num & ~(one << ind)) | (bit << ind);
85}
86
87
88qindex setBitsAt(qindex num, vector<int> inds, qindex bits) {
89
90 for (size_t i=0; i<inds.size(); i++)
91 num = setBitAt(num, inds[i], getBitAt(bits, i));
92
93 return num;
94}
95
96
97qindex getNumPermutations(int n, int k) {
98 DEMAND( n >= k );
99
100 constexpr auto max = std::numeric_limits<qindex>::max();
101
102 // P(n, k) = n! / (n-k)!
103 qindex p = 1;
104 for (int t=n-k+1; t<=n; t++)
105 p *= (p < max / t)? t : 0; // set to 0 on overflow
106
107 return p;
108}
109
110
111
112/*
113 * VECTOR OPERATIONS
114 */
115
116
117qcomp getSum(qvector vec) {
118
119 qcomp sum = qcomp(0,0);
120 qcomp y, t, c=sum;
121
122 // complex Kahan summation
123 for (auto& x : vec) {
124 y = x - c;
125 t = sum + y;
126 c = ( t - sum ) - y;
127 sum = t;
128 }
129
130 return sum;
131}
132
133
134qreal getSum(vector<qreal> vec) {
135
136 // in = real(vec)
137 qvector in = getZeroVector(vec.size());
138 for (size_t i=0; i<in.size(); i++)
139 in[i] = qcomp(vec[i],0);
140
141 return std::real(getSum(in));
142}
143
144
145qvector getNormalised(qvector vec) {
146
147 // prob[i] = abs(vec[i])^2
148 vector<qreal> probs(vec.size());
149 for (size_t i=0; i<vec.size(); i++)
150 probs[i] = std::norm(vec[i]);
151
152 // normalise vector
153 qreal norm = getSum(probs);
154 qreal fac = 1 / std::sqrt(norm);
155 for (auto& x : vec)
156 x *= fac;
157
158 return vec;
159}
160
161
162qvector getDiscreteFourierTransform(qvector in, bool inverse) {
163 DEMAND( in.size() > 0 );
164
165 size_t dim = in.size();
166 qvector out = getZeroVector(dim);
167
168 // PI must be accurate here
169 qreal pi = 3.14159265358979323846;
170 qreal a = 1 / std::sqrt(dim);
171 qreal b = (inverse ? -1 : 1) * 2 * pi / dim;
172
173 for (size_t x=0; x<dim; x++)
174 for (size_t y=0; y<dim; y++)
175 out[x] += a * std::exp(b * x * y * 1_i) * in[y];
176
177 return out;
178}
179
180
181qvector getDiscreteFourierTransform(qvector in, vector<int> targs, bool inverse) {
182 DEMAND( in.size() > 0 );
183
184 size_t dim = in.size();
185 qvector out = getZeroVector(dim);
186
187 qindex len = getPow2(targs.size());
188 qreal pi = 3.14159265358979323846;
189 qreal a = 1 / std::sqrt(len);
190 qreal b = (inverse ? -1 : 1) * 2 * pi / len;
191
192 for (size_t i=0; i<dim; i++) {
193 size_t x = getBitsAt(i, targs);
194 for (size_t y=0; y<len; y++) {
195 qindex j = setBitsAt(i, targs, y);
196 out[j] += a * std::exp(b * x * y * 1_i) * in[i];
197 }
198 }
199
200 return out;
201}
202
203
204
205/*
206 * VECTOR & VECTOR OPERATIONS
207 */
208
209
210qcomp getInnerProduct(qvector bra, qvector ket) {
211 DEMAND( bra.size() == ket.size() );
212
213 qcomp out = 0;
214
215 for (size_t i=0; i<bra.size(); i++)
216 out += std::conj(bra[i]) * ket[i];
217
218 return out;
219}
220
221
222qmatrix getOuterProduct(qvector ket, qvector bra) {
223 DEMAND( bra.size() == ket.size() );
224
225 qmatrix out = getZeroMatrix(bra.size());
226
227 for (size_t i=0; i<ket.size(); i++)
228 for (size_t j=0; j<ket.size(); j++)
229 out[i][j] = ket[i] * std::conj(bra[j]);
230
231 return out;
232}
233
234
235
236/*
237 * MATRIX OPERATIONS
238 */
239
240
241bool isDiagonal(qmatrix m) {
242
243 for (size_t r=0; r<m.size(); r++)
244 for (size_t c=0; c<m.size(); c++)
245 if (r!=c && m[r][c] != 0_i)
246 return false;
247
248 return true;
249}
250
251
252bool isApproxUnitary(qmatrix m) {
253
254 // should be identity
255 qmatrix md = m * getConjugateTranspose(m);
256 qmatrix id = getIdentityMatrix(m.size());
257 return doMatricesAgree(md, id);
258}
259
260
261qcomp getTrace(qmatrix m) {
262
263 qcomp out = 0;
264 for (size_t r=0; r<m.size(); r++)
265 out += m[r][r];
266
267 return out;
268}
269
270
271qmatrix getTranspose(qmatrix m) {
272
273 qmatrix out = getZeroMatrix(m.size());
274
275 for (size_t r=0; r<m.size(); r++)
276 for (size_t c=0; c<m.size(); c++)
277 out[r][c] = m[c][r];
278
279 return out;
280}
281
282
283qmatrix getConjugate(qmatrix m) {
284
285 for (auto& row : m)
286 for (auto& elem : row)
287 elem = std::conj(elem);
288
289 return m;
290}
291
292
293qmatrix getConjugateTranspose(qmatrix m) {
294 DEMAND( m.size() > 0 );
295
296 // unlike most functions which assume qmatrix
297 // is square, this one cheekily handles when
298 // 'm' is non-square, since necessary for
299 // computing partial traces
300
301 qmatrix out(m[0].size(), qvector(m.size()));
302
303 for (size_t r=0; r<out.size(); r++)
304 for (size_t c=0; c<out[0].size(); c++)
305 out[r][c] = std::conj(m[c][r]);
306
307 return out;
308}
309
310
311qmatrix getPowerOfDiagonalMatrix(qmatrix m, qcomp p) {
312 DEMAND( isDiagonal(m) );
313
314 qmatrix out = getZeroMatrix(m.size());
315
316 // pow(qcomp,qcomp) introduces wildly erroneous
317 // imaginary components when both base is real
318 // and negative, and exponent is real and integer
319 // (so ergo does not produce complex numbers).
320 // We divert to real-pow in that scenario!
321
322 for (size_t i=0; i<m.size(); i++) {
323 bool mIsRe = std::imag(m[i][i]) == 0;
324 bool mIsNeg = std::real(m[i][i]) < 0;
325 bool pIsRe = std::imag(p) == 0;
326 bool pIsInt = std::trunc(std::real(p)) == std::real(p);
327
328 // use pow(qreal,qreal) or pow(qcomp,qcomp)
329 out[i][i] = (mIsRe && mIsNeg && pIsRe && pIsInt)?
330 qcomp(std::pow(std::real(m[i][i]), std::real(p)),0):
331 std::pow(m[i][i], p);
332 }
333
334 return out;
335}
336
337
339 DEMAND( isDiagonal(m) );
340
341 qmatrix out = getZeroMatrix(m.size());
342
343 for (size_t i=0; i<m.size(); i++)
344 out[i][i] = std::exp(m[i][i]);
345
346 return out;
347}
348
349
350qmatrix getExponentialOfPauliMatrix(qcomp arg, qmatrix m) {
351
352 // exp(-i arg/2 m) where m = prod(paulis)
353 qmatrix id = getIdentityMatrix(m.size());
354 qmatrix out = std::cos(arg/2)*id - 1_i*std::sin(arg/2)*m;
355 return out;
356}
357
358
359qmatrix getExponentialOfNormalisedPauliVector(qreal arg, qreal x, qreal y, qreal z) {
360
361 // exp(-arg/2 i [x^ X + y^ Y + z^ Z])
362 qreal n = std::sqrt(x*x + y*y + z*z);
363 x /= n;
364 y /= n;
365 z /= n;
366
367 qmatrix id = getIdentityMatrix(2);
368 qmatrix out = std::cos(arg/2)*id - 1_i*std::sin(arg/2)*(
369 x * getPauliMatrix(1) +
370 y * getPauliMatrix(2) +
371 z * getPauliMatrix(3));
372
373 return out;
374}
375
376
377qmatrix getOrthonormalisedRows(qmatrix matr) {
378
379 // perform the Gram-Schmidt process, processing each row of matr in-turn
380 for (size_t i=0; i<matr.size(); i++) {
381 qvector row = matr[i];
382
383 // compute new orthogonal row by subtracting proj row onto prevs
384 for (int k=i-1; k>=0; k--) {
385
386 // compute inner_product(row, prev) = row . conj(prev)
387 qcomp prod = getInnerProduct(matr[k], row);
388
389 // subtract (proj row onto prev) = (prod * prev) from final row
390 matr[i] -= prod * matr[k];
391 }
392
393 // normalise the row
394 matr[i] = getNormalised(matr[i]);
395 }
396
397 // return the new orthonormal matrix
398 return matr;
399}
400
401
402qmatrix getProjector(int outcome) {
403 DEMAND( outcome == 0 || outcome == 1 );
404
405 qmatrix out = getZeroMatrix(2);
406 out[outcome][outcome] = 1.;
407 return out;
408}
409
410
411qmatrix getProjector(vector<int> targets, vector<int> outcomes, int numQubits) {
412 DEMAND( targets.size() == outcomes.size() );
413 DEMAND( numQubits > *std::max_element(targets.begin(), targets.end()) );
414
415 // prepare { |0><0|, I, I, |1><1|, ... }
416 vector<qmatrix> matrices(numQubits, getIdentityMatrix(2));
417 for (size_t i=0; i<targets.size(); i++)
418 matrices[targets[i]] = getProjector(outcomes[i]);
419
420 return getKroneckerProduct(matrices);
421}
422
423
424qmatrix getPartialTrace(qmatrix in, vector<int> targets) {
425 DEMAND( in.size() > getPow2(targets.size()) );
426
427 auto numTargs = targets.size();
428 auto numQubits = getLog2(in.size());
429 auto numTargVals = getPow2(numTargs);
430
431 qmatrix out = getZeroMatrix(getPow2(numQubits - numTargs));
432
433 for (qindex v=0; v<numTargVals; v++) {
434
435 // prepare { |0>, I, I, |1>, ... }
436 vector<qmatrix> matrices(numQubits, getIdentityMatrix(2));
437 for (size_t t=0; t<numTargs; t++) {
438 int bit = getBitAt(v, t);
439 matrices[targets[t]] = {
440 {bit? 0.:1.},
441 {bit? 1.:0.}};
442 }
443
444 qmatrix ket = getKroneckerProduct(matrices);
445 qmatrix bra = getConjugateTranspose(ket);
446 out += bra * in * ket;
447 }
448
449 return out;
450}
451
452
453qmatrix getControlledMatrix(qmatrix matrix, int numCtrls) {
454
455 size_t dim = getPow2(numCtrls) * matrix.size();
456 size_t off = dim - matrix.size();
457
458 qmatrix out = getIdentityMatrix(dim);
459 setSubMatrix(out, matrix, off, off);
460
461 return out;
462}
463
464
465qmatrix getMixture(vector<qmatrix> densmatrs, vector<qreal> probs) {
466 DEMAND( densmatrs.size() > 0 );
467
468 qmatrix out = getZeroMatrix(densmatrs[0].size());
469 for (size_t i=0; i<densmatrs.size(); i++)
470 out += probs[i] * densmatrs[i];
471
472 return out;
473}
474
475
476qmatrix getMixture(vector<qvector> statevecs, vector<qreal> probs) {
477
478 vector<qmatrix> densmatrs(statevecs.size());
479 for (size_t i=0; i<statevecs.size(); i++)
480 densmatrs[i] = getOuterProduct(statevecs[i], statevecs[i]);
481
482 return getMixture(densmatrs, probs);
483}
484
485
486qmatrix getSuperOperator(vector<qmatrix> matrices) {
487 DEMAND( matrices.size() > 0 );
488
489 size_t dim = matrices[0].size();
490
491 // out = sum_m conj(m) (x) m
492 qmatrix out = getZeroMatrix(dim * dim);
493 for (auto& matr : matrices)
494 out += getKroneckerProduct(getConjugate(matr), matr);
495
496 return out;
497}
498
499
500
501/*
502 * MATRIX & VECTOR OPERATIONS
503 */
504
505
506qvector operator * (const qmatrix& m, const qvector& v) {
507 DEMAND( m.size() == v.size() );
508
509 qvector out = getZeroVector(v.size());
510
511 for (size_t r=0; r<v.size(); r++)
512 for (size_t c=0; c<v.size(); c++)
513 out[r] += m[r][c] * v[c];
514
515 return out;
516}
517
518
519
520/*
521 * MATRIX & MATRIX OPERATIONS
522 */
523
524
525qmatrix getKroneckerProduct(qmatrix a, qmatrix b) {
526
527 // we permit the matrices to be non-square which is
528 // pretty cheeky (since qmatrix is assumed square with
529 // a 2^N dimension by most other functions), but is
530 // necessary for us to compute partial traces
531
532 size_t aRows = a.size();
533 size_t bRows = b.size();
534 size_t aCols = a[0].size();
535 size_t bCols = b[0].size();
536
537 qmatrix out(aRows * bRows, qvector(aCols * bCols));
538
539 for (size_t r=0; r<bRows; r++)
540 for (size_t c=0; c<bCols; c++)
541 for (size_t i=0; i<aRows; i++)
542 for (size_t j=0; j<aCols; j++)
543 out[r+bRows*i][c+bCols*j] = a[i][j] * b[r][c];
544
545 return out;
546}
547
548
549qmatrix getKroneckerProduct(vector<qmatrix> matrices) {
550
551 qmatrix out = getIdentityMatrix(1);
552
553 // matrices[n-1] (x) ... (x) matrices[0]
554 for (auto& m : matrices)
555 out = getKroneckerProduct(m, out);
556
557 return out;
558}
559
560
561qmatrix getKroneckerProduct(qmatrix m, int count) {
562 DEMAND( count >= 1 );
563
564 qmatrix out = getIdentityMatrix(1);
565
566 for (int n=0; n<count; n++)
567 out = getKroneckerProduct(out, m);
568
569 return out;
570}
571
572
573
574/*
575 * MATRIX COLLECTIONS
576 */
577
578
579bool isApproxCPTP(vector<qmatrix> matrices) {
580 DEMAND( matrices.size() >= 1 );
581
582 size_t dim = matrices[0].size();
583 qmatrix id = getIdentityMatrix(dim);
584 qmatrix sum = getZeroMatrix(dim);
585
586 for (auto& m : matrices)
587 sum += getConjugateTranspose(m) * m;
588
589 return doMatricesAgree(sum, id);
590}
qmatrix getKroneckerProduct(qmatrix a, qmatrix b)
Definition linalg.cpp:525
qmatrix getConjugateTranspose(qmatrix m)
Definition linalg.cpp:293
qmatrix getExponentialOfDiagonalMatrix(qmatrix m)
Definition linalg.cpp:338
qmatrix getIdentityMatrix(size_t dim)
Definition qmatrix.cpp:30
void setSubMatrix(qmatrix &dest, qmatrix sub, size_t r, size_t c)
Definition qmatrix.cpp:203
qmatrix getZeroMatrix(size_t dim)
Definition qmatrix.cpp:18