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