The Quantum Exact Simulation Toolkit v4.0.0
Loading...
Searching...
No Matches
operations.cpp
1/** @file
2 * Unit tests of the operations module. Beware that because the
3 * operation functions have so much interface and test-semantic
4 * overlap (e.g. the logic of control qubits, of control-states,
5 * of density matrix variants), this file has opted to make
6 * extensive use of generics and metaprogramming, to avoid the
7 * code duplication of defining each function an independent test.
8 * This may have been a mistake, and the file now full of spaghetti
9 * comprised of advanced, misused C++ facilities. View at your own
10 * peril!
11 *
12 * @author Tyson Jones
13 *
14 * @defgroup unitops Operations
15 * @ingroup unittests
16 */
17
18#include "quest/include/quest.h"
19
20#include <catch2/catch_test_macros.hpp>
21#include <catch2/catch_template_test_macros.hpp>
22#include <catch2/matchers/catch_matchers_string.hpp>
23#include <catch2/generators/catch_generators_range.hpp>
24
25#include "tests/utils/cache.hpp"
26#include "tests/utils/qvector.hpp"
27#include "tests/utils/qmatrix.hpp"
28#include "tests/utils/compare.hpp"
29#include "tests/utils/convert.hpp"
30#include "tests/utils/evolve.hpp"
31#include "tests/utils/linalg.hpp"
32#include "tests/utils/lists.hpp"
33#include "tests/utils/measure.hpp"
34#include "tests/utils/macros.hpp"
35#include "tests/utils/random.hpp"
36
37#include <tuple>
38
39using std::tuple;
40using Catch::Matchers::ContainsSubstring;
41
42
43
44/*
45 * UTILITIES
46 */
47
48#define TEST_CATEGORY \
49 LABEL_UNIT_TAG "[operations]"
50
51
52/*
53 * reference operator matrices used by testing
54 */
55
56namespace FixedMatrices {
57
58 qmatrix H = {
59 {1/std::sqrt(2), 1/std::sqrt(2)},
60 {1/std::sqrt(2), -1/std::sqrt(2)}};
61
62 qmatrix X = getPauliMatrix(1);
63 qmatrix Y = getPauliMatrix(2);
64 qmatrix Z = getPauliMatrix(3);
65
66 qreal PI = 3.14159265358979323846;
67 qmatrix T = {
68 {1, 0},
69 {0, std::exp(1_i * PI/4)}};
70
71 qmatrix S = {
72 {1, 0},
73 {0, 1_i}};
74
75 qmatrix SWAP = {
76 {1, 0, 0, 0},
77 {0, 0, 1, 0},
78 {0, 1, 0, 0},
79 {0, 0, 0, 1}};
80
81 qmatrix sqrtSWAP = {
82 {1, 0, 0, 0},
83 {0, (1+1_i)/2, (1-1_i)/2, 0},
84 {0, (1-1_i)/2, (1+1_i)/2, 0},
85 {0, 0, 0, 1}};
86}
87
88namespace ParameterisedMatrices {
89
90 auto Rx = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::X); };
91 auto Ry = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::Y); };
92 auto Rz = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::Z); };
93
94 auto PS = [](qreal p) { return qmatrix{{1, 0}, {0, std::exp(p*1_i)}}; };
95 auto PS2 = [](qreal p) { return getControlledMatrix(PS(p), 1); };
96}
97
98namespace VariableSizeMatrices {
99
100 auto X = [](int n) { return getKroneckerProduct(FixedMatrices::X, n); };
101 auto PF = [](int n) { return getControlledMatrix(FixedMatrices::Z, n - 1); };
102}
103
104namespace VariableSizeParameterisedMatrices {
105
106 auto Z = [](qreal p, int n) {
107 qmatrix m = getKroneckerProduct(FixedMatrices::Z, n);
108 return getExponentialOfPauliMatrix(p, m);
109 };
110
111 auto PS = [](qreal p, int n) {
112 qmatrix m = ParameterisedMatrices::PS(p);
113 return getControlledMatrix(m, n - 1);
114 };
115}
116
117
118/*
119 * execute 'function' upon each kind of cached qureg
120 * (e.g. distributed, GPU-accelerated, etc) and a
121 * reference state (T1 = qvector or qmatrix), when
122 * both are initialised in the debug state, and
123 * thereafter confirm they approximately agree. Each
124 * qureg deployment is featured in a separate test
125 * section, so are accounted distinctly.
126 */
127
128void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& reference, auto& function) {
129
130 for (auto& [label, qureg]: quregs) {
131
132 DYNAMIC_SECTION( label ) {
133
134 // no need to validate whether qureg successfully
135 // enters the debug state here, because the below
136 // serial setToDebugState() is guaranteed to succeed
137 initDebugState(qureg);
138 setToDebugState(reference);
139
140 function(qureg, reference);
141 REQUIRE_AGREE( qureg, reference );
142 }
143 }
144}
145
146void TEST_ON_CACHED_QUREG_AND_MATRIX(quregCache quregs, matrixCache matrices, auto apiFunc, auto refState, auto refMatr, auto refFunc) {
147
148 for (auto& [labelA, qureg]: quregs) {
149 for (auto& [labelB, matrix]: matrices) {
150
151 // skip illegal (distributed matrix, local qureg) combo
152 if (matrix.isDistributed && ! qureg.isDistributed)
153 continue;
154
155 DYNAMIC_SECTION( labelA + LABEL_DELIMITER + labelB ) {
156
157 // set qureg and reference to debug
158 initDebugState(qureg);
159 setToDebugState(refState);
160
161 // set API matrix to pre-initialised ref matrix
162 setFullStateDiagMatr(matrix, 0, getDiagonals(refMatr));
163
164 // API and reference functions should produce agreeing states
165 apiFunc(qureg, matrix);
166 refFunc(refState, refMatr);
167 REQUIRE_AGREE( qureg, refState );
168 }
169 }
170 }
171}
172
173
174/*
175 * simply avoids boilerplate
176 */
177
178#define PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ) \
179 int numQubits = getNumCachedQubits(); \
180 auto statevecQuregs = getCachedStatevecs(); \
181 auto densmatrQuregs = getCachedDensmatrs(); \
182 qvector statevecRef = getZeroVector(getPow2(numQubits)); \
183 qmatrix densmatrRef = getZeroMatrix(getPow2(numQubits));
184
185
186/*
187 * Template flags for specifying what kind of additional
188 * arguments (in addition to ctrls/states/targs below) are
189 * accepted by an API operation, when passing said operation
190 * to automated testing facilities. This is NOT consulted
191 * when generically invoking the API operation (we instead
192 * used variadic templates above for that), but IS used
193 * by the testing code to decide how to prepare inputs.
194 *
195 * For example:
196 * - applyHadamard: none
197 * - applyRotateX: scalar
198 * - applyRotateAroundAxis: axisrots
199 * - applyDiagMatr1: diagmatr
200 * - applyDiagMatrPower: diagpower
201 * - applyCompMatr: compmatr
202 * - applyPauliStr: paulistr
203 * - applyPauliGadgt: pauligad
204*/
205
206enum ArgsFlag { none, scalar, axisrots, diagmatr, diagpower, compmatr, paulistr, pauligad };
207
208
209/*
210 * Template flags for specifying how many control and
211 * target qubits are accepted by an API operation.
212 * Value 'anystates' is reserved for control qubits,
213 * indicating that ctrls must accompany ctrl-states in
214 * the API signature.
215 *
216 * For example:
217 * - applyHadamard: Ctrls=zero, Targs=one
218 * - applyControlledSwap: Ctrls=one, Targs=two
219 * - applyMultiControlledCompMatr: Ctrls=any, Targs=any
220 * - applyMultiStateControlledT: Ctrls=anystates, Targs=one
221 */
222
223enum NumQubitsFlag { zero, one, two, any, anystates };
224
225void assertNumQubitsFlagsAreValid(NumQubitsFlag ctrlsFlag, NumQubitsFlag targsFlag) {
226
227 DEMAND(
228 ctrlsFlag == zero ||
229 ctrlsFlag == one ||
230 ctrlsFlag == any ||
231 ctrlsFlag == anystates );
232
233 DEMAND(
234 targsFlag == zero ||
235 targsFlag == one ||
236 targsFlag == two ||
237 targsFlag == any);
238}
239
240void assertNumQubitsFlagsValid(
241 NumQubitsFlag ctrlsFlag, NumQubitsFlag targsFlag,
242 vector<int> ctrls, vector<int> states, vector<int> targs
243) {
244 assertNumQubitsFlagsAreValid(ctrlsFlag, targsFlag);
245
246 // we explicitly permit targsFlag=zero while
247 // targs.size() != 0, which occurs when targs
248 // are supplied to the API through an alternate
249 // argument (e.g. a PauliStr)
250
251 if (targsFlag == one)
252 DEMAND( targs.size() == 1 );
253
254 if (targsFlag == two)
255 DEMAND( targs.size() == 2 );
256
257 if (ctrlsFlag == zero)
258 DEMAND( ctrls.size() == 0 );
259
260 if (ctrlsFlag == one)
261 DEMAND( ctrls.size() == 1 );
262
263 if (ctrlsFlag == anystates)
264 DEMAND( states.size() == ctrls.size() );
265 else
266 DEMAND( states.size() == 0 );
267}
268
269
270/*
271 * extract the runtime values of the number of control
272 * and target qubtits from their compile-time templates.
273 * When their number is permitted to be 'any', we use
274 * a generator to successively test all possible numbers.
275 * As such, this function is named similar to a Catch2
276 * macro so the caller recognises it is a generator.
277 */
278
279template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
280int GENERATE_NUM_TARGS(int numQuregQubits) {
281
282 assertNumQubitsFlagsAreValid(zero, Targs);
283 DEMAND( Targs != one || numQuregQubits >= 1 );
284 DEMAND( Targs != two || numQuregQubits >= 2 );
285 DEMAND( numQuregQubits > 0 );
286
287 // single choice if #targs is compile-time set
288 if constexpr (Targs == one)
289 return 1;
290 if constexpr (Targs == two)
291 return 2;
292
293 if constexpr (Targs == any) {
294
295 // we can target all qubits...
296 int maxNumTargs = numQuregQubits;
297
298 // unless we're applying CompMatr in distributed
299 // mode. Technically we can support all targets
300 // if the Qureg is a density matrix or not
301 // distributed, but we safely choose the min here
302 if (Args == compmatr)
303 maxNumTargs = numQuregQubits - getLog2(getQuESTEnv().numNodes);
304
305 // we must also ensure there is space left for a forced ctrl
306 if (Ctrls == one && maxNumTargs == numQuregQubits)
307 maxNumTargs -= 1;
308
309 return GENERATE_COPY( range(1, maxNumTargs+1) );
310 }
311}
312
313template <NumQubitsFlag Ctrls>
314int GENERATE_NUM_CTRLS(int numFreeQubits) {
315
316 assertNumQubitsFlagsAreValid(Ctrls, one);
317 DEMAND( Ctrls != one || numFreeQubits >= 1 );
318 DEMAND( numFreeQubits >= 0 );
319
320 if constexpr (Ctrls == zero)
321 return 0;
322
323 if constexpr (Ctrls == one)
324 return 1;
325
326 if constexpr (Ctrls == any || Ctrls == anystates)
327 return GENERATE_COPY( range(0, numFreeQubits+1) );
328}
329
330
331/*
332 * invoke an API operation (e.g. applyHadamard), passing
333 * any elements of (ctrls,states,targs) that it accepts
334 * (as informed by the template values) along with any
335 * additional arguments. We explicitly accept numCtrls
336 * and numTargs, rather than inferring them from ctrls
337 * and targs, such that invalid numbers (like -1) can be
338 * passed by input validation testing.
339 *
340 * This big, ugly bespoke function is necessary, rather
341 * than a simple variadic template, because the QuEST
342 * API accepts fixed numbers of qubits as individual
343 * arguments, rather than as lists/vectors/pointers. Note
344 * that our use of variadic templates (args) means we do
345 * not need to include ArgsFlag as a template parameter.
346 */
347
348template <NumQubitsFlag Ctrls, NumQubitsFlag Targs>
349void invokeApiOperation(
350 auto operation, Qureg qureg,
351 vector<int> ctrls, vector<int> states, int numCtrls,
352 vector<int> targs, int numTargs,
353 auto&... args
354) {
355 assertNumQubitsFlagsValid(Ctrls, Targs, ctrls, states, targs);
356
357 if constexpr (Ctrls == zero) {
358 if constexpr (Targs == zero) operation(qureg, args...);
359 if constexpr (Targs == one) operation(qureg, targs[0], args...);
360 if constexpr (Targs == two) operation(qureg, targs[0], targs[1], args...);
361 if constexpr (Targs == any) operation(qureg, targs.data(), numTargs, args...);
362 }
363 if constexpr (Ctrls == one) {
364 if constexpr (Targs == zero) operation(qureg, ctrls[0], args...);
365 if constexpr (Targs == one) operation(qureg, ctrls[0], targs[0], args...);
366 if constexpr (Targs == two) operation(qureg, ctrls[0], targs[0], targs[1], args...);
367 if constexpr (Targs == any) operation(qureg, ctrls[0], targs.data(), numTargs, args...);
368 }
369 if constexpr (Ctrls == any) {
370 if constexpr (Targs == zero) operation(qureg, ctrls.data(), numCtrls, args...);
371 if constexpr (Targs == one) operation(qureg, ctrls.data(), numCtrls, targs[0], args...);
372 if constexpr (Targs == two) operation(qureg, ctrls.data(), numCtrls, targs[0], targs[1], args...);
373 if constexpr (Targs == any) operation(qureg, ctrls.data(), numCtrls, targs.data(), numTargs, args...);
374 }
375 if constexpr (Ctrls == anystates) {
376 if constexpr (Targs == zero) operation(qureg, ctrls.data(), states.data(), numCtrls, args...);
377 if constexpr (Targs == one) operation(qureg, ctrls.data(), states.data(), numCtrls, targs[0], args...);
378 if constexpr (Targs == two) operation(qureg, ctrls.data(), states.data(), numCtrls, targs[0], targs[1], args...);
379 if constexpr (Targs == any) operation(qureg, ctrls.data(), states.data(), numCtrls, targs.data(), numTargs, args...);
380 }
381}
382
383// overload to avoid passing numCtrls and numTargs
384
385template <NumQubitsFlag Ctrls, NumQubitsFlag Targs>
386void invokeApiOperation(auto operation, Qureg qureg, vector<int> ctrls, vector<int> states, vector<int> targs, auto&... args) {
387 invokeApiOperation<Ctrls,Targs>(operation, qureg, ctrls, states, ctrls.size(), targs, targs.size(), args...);
388}
389
390
391/*
392 * prepare an API matrix (e.g. CompMatr1), as per
393 * the given template parameters. Depending on the
394 * elemsFlag, the elements can be initialised to
395 * zero (0), the identity matrix (1), or randomly (2).
396 * This is used for testing API functions which accept
397 * matrices, in a function-agnostic way.
398 */
399
400template <NumQubitsFlag Targs, ArgsFlag Args>
401auto getRandomOrIdentityApiMatrix(int numTargs, int elemsFlag) {
402
403 DEMAND(
404 Args == diagmatr ||
405 Args == diagpower ||
406 Args == compmatr );
407 DEMAND(
408 elemsFlag == 0 ||
409 elemsFlag == 1 ||
410 elemsFlag == 2 );
411
412 qmatrix qm;
413 if (elemsFlag == 0)
414 qm = getZeroMatrix(getPow2(numTargs));
415 if (elemsFlag == 1)
416 qm = getIdentityMatrix(getPow2(numTargs));
417 if (elemsFlag == 2)
418 qm = (Args == compmatr)?
419 getRandomUnitary(numTargs) :
420 getRandomDiagonalUnitary(numTargs);
421
422 if constexpr (Args == compmatr && Targs == one)
423 return getCompMatr1(qm);
424
425 if constexpr (Args == compmatr && Targs == two)
426 return getCompMatr2(qm);
427
428 if constexpr (Args == compmatr && Targs == any) {
429 CompMatr cm = createCompMatr(numTargs); // must be freed
430 setCompMatr(cm, qm);
431 return cm;
432 }
433
434 qvector dv = getDiagonals(qm);
435 constexpr bool diag = (Args == diagmatr || Args == diagpower);
436
437 if constexpr (diag && Targs == one)
438 return getDiagMatr1(dv);
439
440 if constexpr (diag && Targs == two)
441 return getDiagMatr2(dv);
442
443 if constexpr (diag && Targs == any) {
444 DiagMatr dm = createDiagMatr(numTargs); // must be freed
445 setDiagMatr(dm, dv);
446 return dm;
447 }
448}
449
450template <NumQubitsFlag Targs, ArgsFlag Args> auto getZeroApiMatrix (int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 0); }
451template <NumQubitsFlag Targs, ArgsFlag Args> auto getIdentityApiMatrix(int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 1); }
452template <NumQubitsFlag Targs, ArgsFlag Args> auto getRandomApiMatrix (int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 2); }
453
454
455/*
456 * chooses random values for the remaining arguments
457 * (after controls/states/targets) to API operations,
458 * with types informed by the template parameters.
459 * For example:
460 * - applyRotateX() accepts a scalar
461 * - applyCompMatr() accepts a CompMatr
462 * - applyDiagMatrPower() accepts a DiagMatr and qcomp
463 */
464
465template <NumQubitsFlag Targs, ArgsFlag Args>
466auto getRandomRemainingArgs(vector<int> targs) {
467
468 if constexpr (Args == none)
469 return tuple{ };
470
471 if constexpr (Args == scalar) {
472 qreal angle = getRandomPhase();
473 return tuple{ angle };
474 }
475
476 if constexpr (Args == axisrots) {
477 qreal angle = getRandomPhase();
478 qreal x = getRandomReal(-1, 1);
479 qreal y = getRandomReal(-1, 1);
480 qreal z = getRandomReal(-1, 1);
481 return tuple{ angle, x, y, z };
482 }
483
484 if constexpr (Args == compmatr || Args == diagmatr) {
485 auto matrix = getRandomApiMatrix<Targs,Args>(targs.size()); // allocates heap mem
486 return tuple{ matrix };
487 }
488
489 if constexpr (Args == diagpower) {
490 DiagMatr matrix = getRandomApiMatrix<Targs,Args>(targs.size()); // allocates heap mem
491 qcomp exponent = qcomp(getRandomReal(-3, 3), 0); // real for unitarity
492 return tuple{ matrix, exponent };
493 }
494
495 if constexpr (Args == paulistr) {
496 PauliStr str = getRandomPauliStr(targs);
497 return tuple{ str };
498 }
499
500 if constexpr (Args == pauligad) {
501 PauliStr str = getRandomPauliStr(targs);
502 qreal angle = getRandomPhase();
503 return tuple{ str, angle };
504 }
505}
506
507
508template <NumQubitsFlag Targs, ArgsFlag Args>
509void freeRemainingArgs(auto args) {
510
511 if constexpr (Targs == any && Args == compmatr)
512 destroyCompMatr(std::get<0>(args));
513
514 if constexpr (Targs == any && Args == diagmatr)
515 destroyDiagMatr(std::get<0>(args));
516
517 if constexpr (Targs == any && Args == diagpower)
518 destroyDiagMatr(std::get<0>(args));
519}
520
521
522/*
523 * unpack the given reference operator matrix (a qmatrix)
524 * for an API operation, which is passed to testOperation(),
525 * and which will be effected upon the reference state (a
526 * qvector or qmatrix). The type/form of matrixRefGen depends
527 * on the type of API operation, indicated by template parameter.
528 */
529
530template <NumQubitsFlag Targs, ArgsFlag Args>
531qmatrix getReferenceMatrix(auto matrixRefGen, vector<int> targs, auto additionalArgs) {
532
533 if constexpr (Args == none && Targs != any)
534 return matrixRefGen;
535
536 if constexpr (Args == none && Targs == any)
537 return matrixRefGen(targs.size());
538
539 if constexpr (Args == scalar && Targs != any) {
540 qreal angle = std::get<0>(additionalArgs);
541 return matrixRefGen(angle);
542 }
543
544 if constexpr (Args == scalar && Targs == any) {
545 qreal angle = std::get<0>(additionalArgs);
546 return matrixRefGen(angle, targs.size());
547 }
548
549 if constexpr (Args == axisrots) {
550 qreal angle = std::get<0>(additionalArgs);
551 qreal x = std::get<1>(additionalArgs);
552 qreal y = std::get<2>(additionalArgs);
553 qreal z = std::get<3>(additionalArgs);
554 return getExponentialOfNormalisedPauliVector(angle, x, y, z);
555 }
556
557 if constexpr (Args == compmatr || Args == diagmatr) {
558 auto apiMatrix = std::get<0>(additionalArgs);
559 return getMatrix(apiMatrix);
560 }
561
562 if constexpr (Args == diagpower) {
563 auto apiMatrix = std::get<0>(additionalArgs);
564 qmatrix diag = getMatrix(apiMatrix);
565 qcomp power = std::get<1>(additionalArgs);
566 return getPowerOfDiagonalMatrix(diag, power);
567 }
568
569 if constexpr (Args == paulistr) {
570 PauliStr str = std::get<0>(additionalArgs);
571 return getMatrix(str, targs);
572 }
573
574 if constexpr (Args == pauligad) {
575 PauliStr str = std::get<0>(additionalArgs);
576 qreal angle = std::get<1>(additionalArgs);
577 qmatrix matr = getMatrix(str, targs);
578 return getExponentialOfPauliMatrix(angle, matr);
579 }
580}
581
582
583/*
584 * display all/only relevant inputs given to an
585 * API operation when its subsequent test fails.
586 * This is like a customisation of CAPTURE, although
587 * we must use UNSCOPED_INFO (and ergo re-implement
588 * some printers) because our branching makes scopes
589 * which end CAPTURE's lifetime.
590 */
591
592
593// @todo surely this should live somewhere else,
594// and/or re-use printer_ functions as much as possible
595
596std::string toString(vector<int> list) {
597
598 std::string out = "{ ";
599 for (int& e : list)
600 out += std::to_string(e) + " ";
601 out += "}";
602 return out;
603}
604
605extern int paulis_getPauliAt(PauliStr str, int ind);
606
607std::string toString(PauliStr str, vector<int> targs) {
608
609 std::string labels = "IXYZ";
610
611 // ugly but adequate - like me (call me)
612 std::string out = "";
613 for (int i=targs.size()-1; i>=0; i--)
614 out += labels[paulis_getPauliAt(str, targs[i])];
615
616 return out;
617}
618
619template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
620void CAPTURE_RELEVANT( vector<int> ctrls, vector<int> states, vector<int> targs, auto& args ) {
621
622 // display ctrls
623 if (Ctrls == one)
624 UNSCOPED_INFO( "control := " << ctrls[0] );
625 if (Ctrls == any || Ctrls == anystates )
626 UNSCOPED_INFO( "controls := " << toString(ctrls) );
627
628 // display states
629 if (Ctrls == anystates)
630 UNSCOPED_INFO( "states := " << toString(states) );
631
632 // display targs
633 if (Targs == one)
634 UNSCOPED_INFO( "target := " << targs[0] );
635
636 if (Targs == two) {
637 UNSCOPED_INFO( "target A := " << targs[0] );
638 UNSCOPED_INFO( "target B := " << targs[1] );
639 }
640 if (Targs == any)
641 UNSCOPED_INFO( "targets := " << toString(targs) );
642
643 // display rotation angle
644 if constexpr (Args == scalar)
645 UNSCOPED_INFO( "angle := " << std::get<0>(args) );
646
647 // display rotation angle and axis
648 if constexpr (Args == axisrots) {
649 UNSCOPED_INFO( "angle := " << std::get<0>(args) );
650 UNSCOPED_INFO( "x := " << std::get<1>(args) );
651 UNSCOPED_INFO( "y := " << std::get<2>(args) );
652 UNSCOPED_INFO( "z := " << std::get<3>(args) );
653 }
654
655 // note but don't display API matrices
656 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
657 UNSCOPED_INFO( "matrix := (omitted)" );
658
659 // display exponent of diagonal matrix
660 if constexpr (Args == diagpower) {
661 qcomp p = std::get<1>(args);
662 UNSCOPED_INFO( "exponent := " << std::real(p) << " + (" << std::imag(p) << ")i" );
663 }
664
665 // display PauliStr
666 if constexpr (Args == paulistr || Args == pauligad)
667 UNSCOPED_INFO( "paulis := " << toString(std::get<0>(args), targs) );
668
669 // display PauliStr angle
670 if constexpr (Args == pauligad)
671 UNSCOPED_INFO( "angle := " << std::get<1>(args) );
672}
673
674
675/*
676 * test the correctness of an API operation. The
677 * template parameters are compile-time clues
678 * about what inputs to prepare and pass to the
679 * operation, and how its reference matrix (arg
680 * matrixRefGen) is formatted.
681 */
682
683template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
684void testOperationCorrectness(auto operation, auto matrixRefGen, bool multiplyOnly) {
685
686 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
687
688 // try all possible number of ctrls and targs
689 int numTargs = GENERATE_NUM_TARGS<Ctrls,Targs,Args>(numQubits);
690 int numCtrls = GENERATE_NUM_CTRLS<Ctrls>(numQubits - numTargs);
691
692 // either try all possible ctrls and targs, or randomise them
693 auto ctrlsAndTargs = GENERATE_CTRLS_AND_TARGS( numQubits, numCtrls, numTargs );
694 vector<int> ctrls = std::get<0>(ctrlsAndTargs);
695 vector<int> targs = std::get<1>(ctrlsAndTargs);
696
697 // randomise control states (if operation accepts them)
698 vector<int> states = getRandomOutcomes(numCtrls * (Ctrls == anystates));
699
700 // randomise remaining operation parameters
701 auto primaryArgs = tuple{ ctrls, states, targs };
702 auto furtherArgs = getRandomRemainingArgs<Targs,Args>(targs); // may allocate heap memory
703
704 // obtain the reference matrix for this operation
705 qmatrix matrixRef = getReferenceMatrix<Targs,Args>(matrixRefGen, targs, furtherArgs);
706
707 // PauliStr arg replaces target qubit list in API operations
708 constexpr NumQubitsFlag RevTargs = (Args==paulistr||Args==pauligad)? zero : Targs;
709
710 // disabling unitary-check validation for compmatr, since it's hard to
711 // generate numerically-precise random unitaries upon many qubits, or
712 // upon few qubits are single-precision. So we disable completely until
713 // we re-implement 'input validation' checks which force us to fix thresholds
714 (Args == compmatr)?
717
718 // prepare test function which will receive both statevectors and density matrices
719 auto testFunc = [&](Qureg qureg, auto& stateRef) -> void {
720
721 // invoke API operation, passing all args (unpacking variadic)
722 auto apiFunc = [](auto&&... args) { return invokeApiOperation<Ctrls,RevTargs>(args...); };
723 auto allArgs = std::tuple_cat(tuple{operation, qureg}, primaryArgs, furtherArgs);
724 std::apply(apiFunc, allArgs);
725
726 // update reference state
727 (multiplyOnly)?
728 multiplyReferenceOperator(stateRef, ctrls, states, targs, matrixRef):
729 applyReferenceOperator(stateRef, ctrls, states, targs, matrixRef);
730 };
731
732 // report operation's input parameters if any subsequent test fails
733 CAPTURE_RELEVANT<Ctrls,Targs,Args>( ctrls, states, targs, furtherArgs );
734
735 // test API operation on all available deployment combinations (e.g. OMP, MPI, MPI+GPU, etc)
736 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
737 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
738
739 // free any heap-alloated API matrices and restore epsilon
740 freeRemainingArgs<Targs,Args>(furtherArgs);
742}
743
744
745/*
746 * test the input valdiation of an API operation.
747 * The template parameters are compile-time clues
748 * about what inputs are accepted by the operation
749 */
750
751template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
752auto getFixedCtrlsStatesTargs(int numQubits) {
753
754 // default each to empty
755 vector<int> targs, ctrls, states;
756
757 // assign when non-empty
758 if constexpr (Targs == one) targs = {0};
759 if constexpr (Targs == two) targs = {0,1};
760 if constexpr (Targs == any) targs = {0,1,2};
761 if constexpr (Ctrls == one) ctrls = {3};
762 if constexpr (Ctrls == any) ctrls = {3,4};
763 if constexpr (Ctrls == anystates) ctrls = {3,4};
764 if constexpr (Ctrls == anystates) states = {0,0};
765
766 DEMAND( numQubits >= targs.size() + ctrls.size() );
767
768 return tuple{ ctrls, states, targs };
769}
770
771template <NumQubitsFlag Targs, ArgsFlag Args>
772auto getFixedRemainingArgs(vector<int> targs) {
773
774 // getPauliStr uses gives length-3 hardcoded string
775 if constexpr (Args == paulistr || Args == pauligad)
776 DEMAND( targs.size() == 3 );
777
778 if constexpr (Args == none) return tuple{ };
779 if constexpr (Args == scalar) return tuple{ 0 }; // angle
780 if constexpr (Args == axisrots) return tuple{ 0, 1,1,1 }; // (angle,x,y,z)
781 if constexpr (Args == compmatr) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()) }; // id
782 if constexpr (Args == diagmatr) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()) }; // id
783 if constexpr (Args == diagpower) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()), qcomp(1,0) }; // (id, exponent)
784 if constexpr (Args == paulistr) return tuple{ getPauliStr("XXX", targs) }; // XXX
785 if constexpr (Args == pauligad) return tuple{ getPauliStr("XXX", targs), 0 }; // (XXX, angle)
786}
787
788template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
789void testOperationValidation(auto operation, bool multiplyOnly) {
790
791 // use any cached Qureg
792 Qureg qureg = getCachedStatevecs().begin()->second;
793
794 // in lieu of preparing random inputs like testOperationCorrectness()
795 // above, we instead obtain simple, fixed, compatible inputs
796 auto [ctrls,states,targs] = getFixedCtrlsStatesTargs<Ctrls,Targs,Args>(qureg.numQubits);
797 auto furtherArgs = getFixedRemainingArgs<Targs,Args>(targs);
798
799 // calling apiFunc() will pass the above args with their call-time values
800 auto apiFunc = [&]() {
801 constexpr NumQubitsFlag RevTargs = (Args==paulistr||Args==pauligad)? zero : Targs;
802 auto func = [](auto&&... allArgs) { return invokeApiOperation<Ctrls,RevTargs>(allArgs...); };
803 std::apply(func, std::tuple_cat(tuple{operation, qureg, ctrls, states, targs}, furtherArgs));
804 };
805
806 // convenience vars
807 int numQubits = qureg.numQubits;
808 int numTargs = (int) targs.size();
809 int numCtrls = (int) ctrls.size();
810
811 /// @todo
812 /// below, we return from intendedly skipped SECTIONS which
813 /// appears to work (does not corrupt test statistics, and
814 /// does not attribute skipped tests to having passed the
815 /// section) but is an undocumented Catch2 trick. Check safe!
816
817 SECTION( "qureg uninitialised" ) {
818
819 // spoof uninitialised value
820 qureg.numQubits = -123;
821 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("invalid Qureg") );
822 }
823
824 SECTION( "invalid target" ) {
825
826 // not applicable (PauliStr already made from targs)
827 if (Args == paulistr || Args == pauligad)
828 return;
829
830 // sabotage a target
831 int ind = GENERATE_COPY( range(0,numTargs) );
832 int val = GENERATE_COPY( -1, numQubits );
833 targs[ind] = val;
834
835 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid target qubit") );
836 }
837
838 SECTION( "invalid non-Identity Pauli index" ) {
839
840 if (Args != paulistr && Args != pauligad)
841 return;
842
843 PauliStr badStr = getPauliStr("X", {numQubits + 1});
844 if constexpr (Args == paulistr) furtherArgs = tuple{ badStr };
845 if constexpr (Args == pauligad) furtherArgs = tuple{ badStr, 1 };
846
847 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("highest-index non-identity Pauli operator") && ContainsSubstring("exceeds the maximum target") );
848 }
849
850 SECTION( "invalid control" ) {
851
852 if (numCtrls == 0)
853 return;
854
855 // sabotage a ctrl
856 int ind = GENERATE_COPY( range(0,numCtrls) );
857 int val = GENERATE_COPY( -1, numQubits );
858 ctrls[ind] = val;
859
860 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid control qubit") );
861 }
862
863 SECTION( "control and target collision" ) {
864
865 if (numCtrls == 0)
866 return;
867
868 // sabotage a ctrl
869 int targInd = GENERATE_COPY( range(0,numTargs) );
870 int ctrlInd = GENERATE_COPY( range(0,numCtrls) );
871 ctrls[ctrlInd] = targs[targInd];
872
873 if (Args==paulistr||Args==pauligad)
874 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("control qubit overlaps a non-identity Pauli operator") );
875 else
876 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("qubit appeared among both the control and target qubits") );
877 }
878
879 SECTION( "control states" ) {
880
881 if (states.empty())
882 return;
883
884 int ind = GENERATE_COPY( range(0,numCtrls) );
885 int val = GENERATE( -1, 2 );
886 states[ind] = val;
887
888 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("invalid control-state") );
889 }
890
891 SECTION( "repetition in controls" ) {
892
893 if (numCtrls < 2)
894 return;
895
896 int ind = GENERATE_COPY( range(1,numCtrls) );
897 ctrls[ind] = ctrls[0];
898
899 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("control qubits contained duplicates") );
900 }
901
902 SECTION( "repetition in targets" ) {
903
904 // not applicable to Pauli functions (getPauliStr would throw)
905 if (Args==paulistr||Args==pauligad)
906 return;
907
908 if (numTargs < 2)
909 return;
910
911 int ind = GENERATE_COPY( range(1,numTargs) );
912 targs[ind] = targs[0];
913
914 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("target qubits contained duplicates") );
915 }
916
917 SECTION( "number of targets" ) {
918
919 // not applicable to Pauli functions (getPauliStr would run fine,
920 // and the runtime error would be about the non-identity index)
921 if (Args == paulistr || Args == pauligad)
922 return;
923
924 if (Targs != any)
925 return;
926
927 // too few (cannot test less than 0)
928 targs = {};
929 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Must specify one or more targets") );
930
931 // too many; exceeds Qureg
932 targs = getRange(qureg.numQubits+1);
933 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("number of target qubits") && ContainsSubstring("exceeds the number of qubits in the Qureg") );
934
935 // note numTargs + numCtrls > numQubits is caught by
936 // invalid index or overlapping (ctrls,targs) validation
937 }
938
939 SECTION( "mismatching matrix size" ) {
940
941 // only relevant to variable-sized matrices
942 if (Targs != any)
943 return;
944 if (!(Args == compmatr || Args == diagmatr || Args == diagpower))
945 return;
946
947 DEMAND( numTargs > 1 );
948 DEMAND( numCtrls + numTargs < numQubits );
949
950 targs.push_back(numQubits - 1);
951 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("matrix has an inconsistent size") );
952
953 targs.pop_back();
954 targs.pop_back();
955 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("matrix has an inconsistent size") );
956 }
957
958 SECTION( "matrix unitarity" ) {
959
960 // only relevant to matrix functions...
961 if (Args != compmatr && Args != diagmatr && Args != diagpower)
962 return;
963
964 // which enforce unitarity
965 if (multiplyOnly)
966 return;
967
968 if constexpr (Args == compmatr || Args == diagmatr)
969 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(targs.size()) };
970 if constexpr (Args == diagpower)
971 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(targs.size()), 1 };
972
973 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("unitary") );
974 }
975
976 SECTION( "matrix uninitialised" ) {
977
978 // sabotage matrix struct field
979 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
980 std::get<0>(furtherArgs).numQubits = -1;
981 else
982 return;
983
984 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid") );
985
986 // must correct field so that subsequent destructor doesn't whine!
987 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
988 std::get<0>(furtherArgs).numQubits = numTargs;
989 }
990
991 SECTION( "matrix unsynced" ) {
992
993 if (!getQuESTEnv().isGpuAccelerated)
994 return;
995
996 // only relevant to variable-size matrix functions
997 if constexpr (Targs == any && (Args == compmatr || Args == diagmatr || Args == diagpower))
998 *(std::get<0>(furtherArgs).wasGpuSynced) = 0;
999 else
1000 return; // avoid empty test
1001
1002 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("sync") );
1003 }
1004
1005 SECTION( "targeted amps fit in node" ) {
1006
1007 // can only be validated when environment AND qureg
1008 // are distributed (over more than 1 node, of course)
1009 if (qureg.numNodes < 2)
1010 return;
1011
1012 // can only be validated if forced ctrl qubits permit
1013 // enough remaining targets
1014 int minNumCtrls = (Ctrls == one)? 1 : 0;
1015 int minNumTargs = numQubits - qureg.logNumNodes + 1;
1016 int maxNumTargs = numQubits - minNumCtrls;
1017 if (minNumTargs > maxNumTargs)
1018 return;
1019
1020 // only relevant to >=2-targ dense matrices, and further
1021 // only testable with any-targ variable-size matrices, since
1022 // 4x4 matrices might always be permissable by Qureg distribution
1023 if constexpr (Args == compmatr && Targs == any) {
1024
1025 // free existing matrix to avoid leak
1026 destroyCompMatr(std::get<0>(furtherArgs));
1027
1028 // try all illegally sized matrices
1029 int numNewTargs = GENERATE_COPY( range(minNumTargs, maxNumTargs+1) );
1030 targs = getRange(numNewTargs);
1031
1032 // ensure no overlap with ctrls; just get rid of them, EXCEPT when the API
1033 // function expects explicitly one ctrl which we must always supply
1034 ctrls = vector<int>(minNumCtrls, numQubits - 1); // {} or {last}
1035 states = {};
1036
1037 // create the new illegaly-sized matrix, which will be destroyed at test-case end
1038 CompMatr matr = getIdentityApiMatrix<Targs,Args>(numNewTargs);
1039 furtherArgs = tuple{ matr };
1040
1041 CAPTURE( minNumCtrls, numNewTargs, numQubits - minNumCtrls, ctrls, targs );
1042 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("cannot simultaneously store") && ContainsSubstring("remote amplitudes") );
1043
1044 } else {
1045 return; // avoid empty test
1046 }
1047 }
1048
1049 SECTION( "non-unitary exponent" ) {
1050
1051 // not relevant for functions which do not assert unitarity
1052 if (multiplyOnly)
1053 return;
1054
1055 if constexpr (Args == diagpower)
1056 furtherArgs = tuple{ std::get<0>(furtherArgs), qcomp(1,1) };
1057 else
1058 return; // avoid empty test
1059
1060 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("exponent was not approximately real") );
1061 }
1062
1063 SECTION( "diverging exponent" ) {
1064
1065 // when being applied as a unitary, abs(elem)=1 so there's no
1066 // possibility of divergence (we'd merely trigger isUnitary)
1067 if (!multiplyOnly)
1068 return;
1069
1070 if constexpr (Args == diagpower)
1071 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(numTargs), qcomp(-1,0) };
1072 else
1073 return; // avoid empty test
1074
1075 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("divergences") );
1076 }
1077
1078 SECTION( "zero axis rotation" ) {
1079
1080 if constexpr (Args == axisrots) {
1081 furtherArgs = tuple{ 0, 0, 0, 0 }; // (angle,x,y,z)
1082 } else
1083 return; // avoid empty test
1084
1085 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("zero vector") );
1086 }
1087
1088 freeRemainingArgs<Targs,Args>(furtherArgs);
1089}
1090
1091
1092/*
1093 * fully test an API operation, on compatible
1094 * inputs as indicated by the template flags
1095 */
1096
1097template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
1098void testOperation(auto operation, auto matrixRefGen, bool multiplyOnly) {
1099
1100 assertNumQubitsFlagsAreValid(Ctrls, Targs);
1101
1102 SECTION( LABEL_CORRECTNESS ) {
1103 testOperationCorrectness<Ctrls,Targs,Args>(operation, matrixRefGen, multiplyOnly);
1104 }
1105
1106 SECTION( LABEL_VALIDATION ) {
1107 testOperationValidation<Ctrls,Targs,Args>(operation, multiplyOnly);
1108 }
1109}
1110
1111template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
1112void testOperation(auto operation, auto matrixRefGen) {
1113
1114 bool multiplyOnly = false;
1115 testOperation<Ctrls,Targs,Args>(operation, matrixRefGen, multiplyOnly);
1116}
1117
1118
1119/*
1120 * perform unit tests for the four distinctly-controlled
1121 * variants of the given API operation (with the specified
1122 * function name suffix). 'numtargs' indicates the number
1123 * of target qubits accepted by the operation, and 'argtype'
1124 * indicates the types of remaining arguments (if any exist).
1125 * 'matrixgen' is the matrix representation (of varying
1126 * formats) of the operation, against which it will be compared.
1127 */
1128
1129// when every API operation had NO overloads, it was sufficient to
1130// call the below simple macro. Alas, since C++ overloads have been
1131// added, the below passing of a function (e.g. applyControlledHadamard)
1132// is ambigious, not specifying whether it is the int* or vector<int>
1133// version. We now have to explicit cast the function to its specific
1134// C-compatible version. Alas, to your imminent horror, this is.. erm...
1135
1136// #define TEST_ALL_CTRL_OPERATIONS( namesuffix, numtargs, argtype, matrixgen ) \
1137// TEST_CASE( "apply" #namesuffix, TEST_CATEGORY ) { testOperation<zero, numtargs,argtype>( apply ## namesuffix, matrixgen); } \
1138// TEST_CASE( "applyControlled" #namesuffix, TEST_CATEGORY ) { testOperation<one, numtargs,argtype>( applyControlled ## namesuffix, matrixgen); } \
1139// TEST_CASE( "applyMultiControlled" #namesuffix, TEST_CATEGORY ) { testOperation<any, numtargs,argtype>( applyMultiControlled ## namesuffix, matrixgen); } \
1140// TEST_CASE( "applyMultiStateControlled" #namesuffix, TEST_CATEGORY ) { testOperation<anystates,numtargs,argtype>( applyMultiStateControlled ## namesuffix, matrixgen); }
1141
1142
1143/*
1144 * define macros to pre-processor-time generate the API function
1145 * signatures to give to static_cast to disambiguate the function
1146 * from its C++ overloads, as per the above comment. The absolute
1147 * macro nightmare below results from not being able to propagate
1148 * templates to static_cast<...> which is not actually templated!
1149 * May God forgive me for the misdeeds commited here below.
1150 */
1151
1152// produces the control qubit arguments of a function signature,
1153// with a trailing comma (since ctrls always preceed more arguments)
1154#define GET_FUNC_CTRL_SUB_SIG( numctrls ) GET_FUNC_CTRL_SUB_SIG_##numctrls
1155#define GET_FUNC_CTRL_SUB_SIG_zero
1156#define GET_FUNC_CTRL_SUB_SIG_one int,
1157#define GET_FUNC_CTRL_SUB_SIG_any int*,int,
1158#define GET_FUNC_CTRL_SUB_SIG_anystates int*,int*,int,
1159
1160// produces the target qubit arguments of a function signature, complicated
1161// by paulistr and pauligad functions not ever passign explicit targ lists.
1162// trailing comma attached only when targs exist, and final args exist.
1163// beware, macros must never have spaces between the name and open-paranthesis!
1164#define GET_FUNC_TARG_SUB_SIG( numtargs, argtype ) GET_FUNC_TARG_SUB_SIG_##argtype( numtargs )
1165#define GET_FUNC_TARG_SUB_SIG_none( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs
1166#define GET_FUNC_TARG_SUB_SIG_scalar( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1167#define GET_FUNC_TARG_SUB_SIG_axisrots( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1168#define GET_FUNC_TARG_SUB_SIG_compmatr( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1169#define GET_FUNC_TARG_SUB_SIG_diagmatr( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1170#define GET_FUNC_TARG_SUB_SIG_diagpower( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1171#define GET_FUNC_TARG_SUB_SIG_paulistr( numtargs )
1172#define GET_FUNC_TARG_SUB_SIG_pauligad( numtargs )
1173#define GET_FUNC_TARG_SUB_SIG_one int
1174#define GET_FUNC_TARG_SUB_SIG_two int,int
1175#define GET_FUNC_TARG_SUB_SIG_any int*,int
1176
1177// produces the final arguments of a function signature (no trailing comma).
1178#define GET_FUNC_ARGS_SUB_SIG( numtargs, argtype ) GET_FUNC_ARGS_SUB_SIG_##numtargs##_##argtype
1179#define GET_FUNC_ARGS_SUB_SIG_one_none
1180#define GET_FUNC_ARGS_SUB_SIG_two_none
1181#define GET_FUNC_ARGS_SUB_SIG_any_none
1182#define GET_FUNC_ARGS_SUB_SIG_one_scalar qreal
1183#define GET_FUNC_ARGS_SUB_SIG_two_scalar qreal
1184#define GET_FUNC_ARGS_SUB_SIG_any_scalar qreal
1185#define GET_FUNC_ARGS_SUB_SIG_one_compmatr CompMatr1
1186#define GET_FUNC_ARGS_SUB_SIG_two_compmatr CompMatr2
1187#define GET_FUNC_ARGS_SUB_SIG_any_compmatr CompMatr
1188#define GET_FUNC_ARGS_SUB_SIG_one_diagmatr DiagMatr1
1189#define GET_FUNC_ARGS_SUB_SIG_two_diagmatr DiagMatr2
1190#define GET_FUNC_ARGS_SUB_SIG_any_diagmatr DiagMatr
1191#define GET_FUNC_ARGS_SUB_SIG_one_diagpower DiagMatr1,qcomp
1192#define GET_FUNC_ARGS_SUB_SIG_two_diagpower DiagMatr2,qcomp
1193#define GET_FUNC_ARGS_SUB_SIG_any_diagpower DiagMatr, qcomp
1194#define GET_FUNC_ARGS_SUB_SIG_one_axisrots qreal,qreal,qreal,qreal
1195#define GET_FUNC_ARGS_SUB_SIG_any_paulistr PauliStr
1196#define GET_FUNC_ARGS_SUB_SIG_any_pauligad PauliStr,qreal
1197
1198// produces the control-qubit-related prefix of a function name
1199#define GET_FUNC_NAME_PREFIX( numctrls ) GET_FUNC_NAME_PREFIX_##numctrls
1200#define GET_FUNC_NAME_PREFIX_zero apply
1201#define GET_FUNC_NAME_PREFIX_one applyControlled
1202#define GET_FUNC_NAME_PREFIX_any applyMultiControlled
1203#define GET_FUNC_NAME_PREFIX_anystates applyMultiStateControlled
1204
1205// produces a function name from the control qubits and the suffix, e.g. (any,T) -> applyMultiControlledT
1206#define GET_FUNC_NAME(numctrls, suffix) GET_FUNC_NAME_INNER(GET_FUNC_NAME_PREFIX(numctrls), suffix)
1207#define GET_FUNC_NAME_INNER(A, B) GET_FUNC_NAME_INNER_INNER(A, B)
1208#define GET_FUNC_NAME_INNER_INNER(A, B) A##B
1209
1210// converts the output of GET_FUNC_NAME() to a string, e.g. (any,T) -> "applyMultiControlledT"
1211#define GET_FUNC_NAME_STR(numctrls, suffix) GET_FUNC_NAME_STR_INNER( GET_FUNC_NAME(numctrls,suffix) )
1212#define GET_FUNC_NAME_STR_INNER(expr) GET_FUNC_NAME_STR_INNER_INNER(expr)
1213#define GET_FUNC_NAME_STR_INNER_INNER(symbol) #symbol
1214
1215// produces the signature of a function, e.g. (any,one,diagmatr) -> void(*)(Qureg, int*,int, int,DiagMatr1),
1216// which is the signature of applyMultiControlled(Qureg qureg, int* ctrls, int numCtrls, int targ, DiagMatr1 m);
1217// NOTE:
1218// THIS CURRENT EXCLUDES PAULISTR AND PAULIGAD argtype
1219#define GET_FUNC_SIG( numctrls, numtargs, argtype ) \
1220 void(*) ( \
1221 Qureg, \
1222 GET_FUNC_CTRL_SUB_SIG( numctrls ) \
1223 GET_FUNC_TARG_SUB_SIG( numtargs, argtype ) \
1224 GET_FUNC_ARGS_SUB_SIG( numtargs, argtype ) \
1225 )
1226
1227// produces a function name, casted to its explicit C-argument form, disambiguated from its C++ overload.
1228// e.g. (DiagMatrPower, zero, any, diagpower) -> static_cast<void(*)(Qureg,int*,int,DiagMatr,qcomp)>(applyDiagMatrPower)>
1229#define GET_CASTED_FUNC( namesuffix, numctrls, numtargs, argtype ) \
1230 static_cast< GET_FUNC_SIG(numctrls, numtargs, argtype) > ( \
1231 GET_FUNC_NAME(numctrls, namesuffix) )
1232
1233// defines a Catch2 test-case for the implied function
1234#define TEST_CASE_OPERATION( namesuffix, numctrls, numtargs, argtype, matrixgen ) \
1235 TEST_CASE( GET_FUNC_NAME_STR(numctrls, namesuffix), TEST_CATEGORY ) { \
1236 testOperation<numctrls, numtargs, argtype>( \
1237 GET_CASTED_FUNC(namesuffix, numctrls, numtargs, argtype), \
1238 matrixgen); \
1239 }
1240
1241// automate the testing of a function for all its controlled variants
1242#define TEST_ALL_CTRL_OPERATIONS( namesuffix, numtargs, argtype, matrixgen ) \
1243 TEST_CASE_OPERATION( namesuffix, zero, numtargs, argtype, matrixgen ) \
1244 TEST_CASE_OPERATION( namesuffix, one, numtargs, argtype, matrixgen ) \
1245 TEST_CASE_OPERATION( namesuffix, any, numtargs, argtype, matrixgen ) \
1246 TEST_CASE_OPERATION( namesuffix, anystates, numtargs, argtype, matrixgen )
1247
1248
1249
1250/**
1251 * TESTS
1252 *
1253 * @ingroup unitops
1254 * @{
1255 */
1256
1257
1258/*
1259 * controlled operations
1260 */
1261
1262TEST_ALL_CTRL_OPERATIONS( PauliStr, any, paulistr, nullptr );
1263TEST_ALL_CTRL_OPERATIONS( PauliGadget, any, pauligad, nullptr );
1264TEST_ALL_CTRL_OPERATIONS( CompMatr1, one, compmatr, nullptr );
1265TEST_ALL_CTRL_OPERATIONS( CompMatr2, two, compmatr, nullptr );
1266TEST_ALL_CTRL_OPERATIONS( CompMatr, any, compmatr, nullptr );
1267TEST_ALL_CTRL_OPERATIONS( DiagMatr1, one, diagmatr, nullptr );
1268TEST_ALL_CTRL_OPERATIONS( DiagMatr2, two, diagmatr, nullptr );
1269TEST_ALL_CTRL_OPERATIONS( DiagMatr, any, diagmatr, nullptr );
1270TEST_ALL_CTRL_OPERATIONS( DiagMatrPower, any, diagpower, nullptr );
1271TEST_ALL_CTRL_OPERATIONS( Hadamard, one, none, FixedMatrices::H );
1272TEST_ALL_CTRL_OPERATIONS( PauliX, one, none, FixedMatrices::X );
1273TEST_ALL_CTRL_OPERATIONS( PauliY, one, none, FixedMatrices::Y );
1274TEST_ALL_CTRL_OPERATIONS( PauliZ, one, none, FixedMatrices::Z );
1275TEST_ALL_CTRL_OPERATIONS( T, one, none, FixedMatrices::T );
1276TEST_ALL_CTRL_OPERATIONS( S, one, none, FixedMatrices::S );
1277TEST_ALL_CTRL_OPERATIONS( Swap, two, none, FixedMatrices::SWAP );
1278TEST_ALL_CTRL_OPERATIONS( SqrtSwap, two, none, FixedMatrices::sqrtSWAP );
1279TEST_ALL_CTRL_OPERATIONS( RotateX, one, scalar, ParameterisedMatrices::Rx );
1280TEST_ALL_CTRL_OPERATIONS( RotateY, one, scalar, ParameterisedMatrices::Ry );
1281TEST_ALL_CTRL_OPERATIONS( RotateZ, one, scalar, ParameterisedMatrices::Rz );
1282TEST_ALL_CTRL_OPERATIONS( RotateAroundAxis, one, axisrots, nullptr );
1283TEST_ALL_CTRL_OPERATIONS( MultiQubitNot, any, none, VariableSizeMatrices::X );
1284TEST_ALL_CTRL_OPERATIONS( PhaseGadget, any, scalar, VariableSizeParameterisedMatrices::Z );
1285
1286
1287/*
1288 * non-controlled operations with no C++ overloads
1289 */
1290
1291TEST_CASE( "multiplyPauliStr", TEST_CATEGORY ) { testOperation<zero,any,paulistr>(multiplyPauliStr, nullptr, true); }
1292TEST_CASE( "multiplyPauliGadget", TEST_CATEGORY ) { testOperation<zero,any,pauligad>(multiplyPauliGadget, nullptr, true); }
1293TEST_CASE( "multiplyCompMatr1", TEST_CATEGORY ) { testOperation<zero,one,compmatr>(multiplyCompMatr1, nullptr, true); }
1294TEST_CASE( "multiplyCompMatr2", TEST_CATEGORY ) { testOperation<zero,two,compmatr>(multiplyCompMatr2, nullptr, true); }
1295TEST_CASE( "multiplyDiagMatr1", TEST_CATEGORY ) { testOperation<zero,one,diagmatr>(multiplyDiagMatr1, nullptr, true); }
1296TEST_CASE( "multiplyDiagMatr2", TEST_CATEGORY ) { testOperation<zero,two,diagmatr>(multiplyDiagMatr2, nullptr, true); }
1297TEST_CASE( "applyPhaseFlip", TEST_CATEGORY ) { testOperation<zero,one,none> (applyPhaseFlip, VariableSizeMatrices::PF(1)); }
1298TEST_CASE( "applyTwoQubitPhaseFlip", TEST_CATEGORY ) { testOperation<zero,two,none> (applyTwoQubitPhaseFlip, VariableSizeMatrices::PF(2)); }
1299TEST_CASE( "applyPhaseShift", TEST_CATEGORY ) { testOperation<zero,one,scalar>(applyPhaseShift, ParameterisedMatrices::PS); }
1300TEST_CASE( "applyTwoQubitPhaseShift", TEST_CATEGORY ) { testOperation<zero,two,scalar>(applyTwoQubitPhaseShift, ParameterisedMatrices::PS2); }
1301
1302
1303/*
1304 * non-controlled operations which have a C++ overload
1305 * (because they accept qubit lists which become vector),
1306 * and so which require explicit casting to resolve the
1307 * compiler ambiguity
1308 */
1309
1310TEST_CASE( "multiplyCompMatr", TEST_CATEGORY ) {
1311 auto func = static_cast<void(*)(Qureg, int*, int, CompMatr)>(multiplyCompMatr);
1312 testOperation<zero,any,compmatr>(func, nullptr, true);
1313}
1314
1315TEST_CASE( "multiplyDiagMatr", TEST_CATEGORY ) {
1316 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr)>(multiplyDiagMatr);
1317 testOperation<zero,any,diagmatr>(func, nullptr, true);
1318}
1319
1320TEST_CASE( "multiplyDiagMatrPower", TEST_CATEGORY ) {
1321 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr, qcomp)>(multiplyDiagMatrPower);
1322 testOperation<zero,any,diagpower>(func, nullptr, true);
1323}
1324
1325TEST_CASE( "multiplyMultiQubitNot", TEST_CATEGORY ) {
1326 auto func = static_cast<void(*)(Qureg, int*, int)>(multiplyMultiQubitNot);
1327 testOperation<zero,any,none>(func, VariableSizeMatrices::X, true);
1328}
1329
1330TEST_CASE( "multiplyPhaseGadget", TEST_CATEGORY ) {
1331 auto func = static_cast<void(*)(Qureg, int*, int, qreal)>(multiplyPhaseGadget);
1332 testOperation<zero,any,scalar>(func, VariableSizeParameterisedMatrices::Z, true);
1333}
1334
1335TEST_CASE( "applyMultiQubitPhaseFlip", TEST_CATEGORY ) {
1336 auto func = static_cast<void(*)(Qureg, int*, int)>(applyMultiQubitPhaseFlip);
1337 testOperation<zero,any,none>(func, VariableSizeMatrices::PF);
1338}
1339
1340TEST_CASE( "applyMultiQubitPhaseShift", TEST_CATEGORY ) {
1341 auto func = static_cast<void(*)(Qureg, int*, int, qreal)>(applyMultiQubitPhaseShift);
1342 testOperation<zero,any,scalar>(func, VariableSizeParameterisedMatrices::PS);
1343}
1344
1345
1346/*
1347 * operations which need custom logic
1348 */
1349
1350TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY ) {
1351
1352 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1353
1354 SECTION( LABEL_CORRECTNESS ) {
1355
1356 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1357 auto targs = GENERATE_TARGS( numQubits, numTargs );
1358
1359 CAPTURE( targs );
1360
1361 SECTION( LABEL_STATEVEC ) {
1362
1363 auto testFunc = [&](Qureg qureg, qvector& ref) {
1364 applyQuantumFourierTransform(qureg, targs.data(), targs.size());
1365 ref = getDisceteFourierTransform(ref, targs);
1366 };
1367
1368 TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc);
1369 }
1370
1371 SECTION( LABEL_DENSMATR ) {
1372
1373 // prepare a random mixture
1374 auto states = getRandomOrthonormalStateVectors(numQubits, getRandomInt(1,10));
1375 auto probs = getRandomProbabilities(states.size());
1376
1377 auto testFunc = [&](Qureg qureg, qmatrix& ref) {
1378
1379 // overwrite the Qureg debug state set by caller to above mixture
1380 setQuregToReference(qureg, getMixture(states, probs));
1381 applyQuantumFourierTransform(qureg, targs.data(), targs.size());
1382
1383 ref = getZeroMatrix(ref.size());
1384 for (size_t i=0; i<states.size(); i++) {
1385 qvector vec = getDisceteFourierTransform(states[i], targs);
1386 ref += probs[i] * getOuterProduct(vec, vec);
1387 }
1388 };
1389
1390 TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc);
1391 }
1392 }
1393
1394 /// @todo input validation
1395}
1396
1397
1398TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY ) {
1399
1400 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1401
1402 SECTION( LABEL_CORRECTNESS ) {
1403
1404 GENERATE( range(0,10) );
1405
1406 SECTION( LABEL_STATEVEC ) {
1407
1408 auto testFunc = [&](Qureg qureg, qvector& ref) {
1410 ref = getDisceteFourierTransform(ref);
1411 };
1412
1413 TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc);
1414 }
1415
1416 SECTION( LABEL_DENSMATR ) {
1417
1418 // prepare a random mixture
1419 auto states = getRandomOrthonormalStateVectors(numQubits, getRandomInt(1,10));
1420 auto probs = getRandomProbabilities(states.size());
1421
1422 auto testFunc = [&](Qureg qureg, qmatrix& ref) {
1423
1424 // overwrite the Qureg debug state set by caller to above mixture
1425 setQuregToReference(qureg, getMixture(states, probs));
1427
1428 ref = getZeroMatrix(ref.size());
1429 for (size_t i=0; i<states.size(); i++) {
1430 qvector vec = getDisceteFourierTransform(states[i]);
1431 ref += probs[i] * getOuterProduct(vec, vec);
1432 }
1433 };
1434
1435 TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc);
1436 }
1437 }
1438
1439 /// @todo input validation
1440}
1441
1442
1443TEST_CASE( "applyQubitProjector", TEST_CATEGORY ) {
1444
1445 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1446
1447 SECTION( LABEL_CORRECTNESS ) {
1448
1449 GENERATE( range(0,10) );
1450 int target = GENERATE_COPY( range(0,numQubits) );
1451 int outcome = GENERATE( 0, 1 );
1452
1453 qmatrix projector = getProjector(outcome);
1454
1455 auto testFunc = [&](Qureg qureg, auto& ref) {
1456 applyQubitProjector(qureg, target, outcome);
1457 applyReferenceOperator(ref, {target}, projector);
1458 };
1459
1460 CAPTURE( target, outcome );
1461 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1462 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1463 }
1464
1465 /// @todo input validation
1466}
1467
1468
1469TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY ) {
1470
1471 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1472
1473 SECTION( LABEL_CORRECTNESS ) {
1474
1475 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1476 auto targets = GENERATE_TARGS( numQubits, numTargs );
1477 auto outcomes = getRandomOutcomes(numTargs);
1478
1479 qmatrix projector = getProjector(targets, outcomes, numQubits);
1480
1481 auto testFunc = [&](Qureg qureg, auto& ref) {
1482 applyMultiQubitProjector(qureg, targets.data(), outcomes.data(), numTargs);
1483 applyReferenceOperator(ref, projector);
1484 };
1485
1486 CAPTURE( targets, outcomes );
1487 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1488 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1489 }
1490
1491 /// @todo input validation
1492}
1493
1494
1495TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY ) {
1496
1497 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1498
1499 SECTION( LABEL_CORRECTNESS ) {
1500
1501 GENERATE( range(0,10) );
1502 int target = GENERATE_COPY( range(0,numQubits) );
1503 int outcome = GENERATE( 0, 1 );
1504
1505 qmatrix projector = getProjector(outcome);
1506
1507 auto testFunc = [&](Qureg qureg, auto& ref) {
1508
1509 // overwrite caller's setting of initDebugState, since
1510 // that precludes outcomes=|0><0| due to zero-probability
1511 setToRandomState(ref);
1512 setQuregToReference(qureg, ref);
1513
1514 // compare the probabilities...
1515 qreal apiProb = applyForcedQubitMeasurement(qureg, target, outcome);
1516 qreal refProb = getReferenceProbability(ref, {target}, {outcome});
1517 REQUIRE_AGREE( apiProb, refProb );
1518
1519 // and the post-projection states (caller calls subsequent REQUIRE_AGREE)
1520 applyReferenceOperator(ref, {target}, projector);
1521 ref /= (qureg.isDensityMatrix)?
1522 refProb : std::sqrt(refProb);
1523 };
1524
1525 CAPTURE( target, outcome );
1526 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1527 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1528 }
1529
1530 /// @todo input validation
1531}
1532
1533
1534TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY ) {
1535
1536 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1537
1538 SECTION( LABEL_CORRECTNESS ) {
1539
1540 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1541 auto targets = GENERATE_TARGS( numQubits, numTargs );
1542 auto outcomes = getRandomOutcomes(numTargs);
1543
1544 qmatrix projector = getProjector(targets, outcomes, numQubits);
1545
1546 // this test may randomly request a measurement outcome which
1547 // is illegally unlikely, triggering validation; we merely
1548 // disable such validation and hope divergences don't break the test!
1550
1551 auto testFunc = [&](Qureg qureg, auto& ref) {
1552
1553 // overwrite caller's setting of initDebugState, since
1554 // that precludes outcomes=|0><0| due to zero-probability
1555 setToRandomState(ref);
1556 setQuregToReference(qureg, ref);
1557
1558 // compare the probabilities...
1559 qreal apiProb = applyForcedMultiQubitMeasurement(qureg, targets.data(), outcomes.data(), numTargs);
1560 qreal refProb = getReferenceProbability(ref, targets, outcomes);
1561 REQUIRE_AGREE( apiProb, refProb );
1562
1563 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
1564 applyReferenceOperator(ref, projector);
1565 ref /= (qureg.isDensityMatrix)?
1566 refProb : std::sqrt(refProb);
1567 };
1568
1569 CAPTURE( targets, outcomes );
1570 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1571 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1572
1574 }
1575
1576 /// @todo input validation
1577}
1578
1579
1580TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY ) {
1581
1582 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1583
1584 SECTION( LABEL_CORRECTNESS ) {
1585
1586 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1587 auto targets = GENERATE_TARGS( numQubits, numTargs );
1588
1589 auto testFunc = [&](Qureg qureg, auto& ref) {
1590
1591 // overwrite caller's setting of initDebugState, since
1592 // sampling requires the outcome probs are normalised
1593 setToRandomState(ref);
1594 setQuregToReference(qureg, ref);
1595
1596 // the output API state...
1597 qindex apiOut = applyMultiQubitMeasurement(qureg, targets.data(), numTargs);
1598
1599 // informs the projector which determines the post-measurement reference
1600 auto apiOutBits = getBits(apiOut, numTargs);
1601 qmatrix projector = getProjector(targets, apiOutBits, numQubits);
1602 applyReferenceOperator(ref, projector);
1603 qreal refProb = getReferenceProbability(ref, targets, apiOutBits);
1604 ref /= (qureg.isDensityMatrix)?
1605 refProb : std::sqrt(refProb);
1606 };
1607
1608 CAPTURE( targets );
1609 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1610 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1611 }
1612
1613 /// @todo input validation
1614}
1615
1616
1617TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY ) {
1618
1619 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1620
1621 SECTION( LABEL_CORRECTNESS ) {
1622
1623 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1624 auto targets = GENERATE_TARGS( numQubits, numTargs );
1625
1626 auto testFunc = [&](Qureg qureg, auto& ref) {
1627
1628 // overwrite caller's setting of initDebugState, since
1629 // sampling requires the outcome probs are normalised
1630 setToRandomState(ref);
1631 setQuregToReference(qureg, ref);
1632
1633 // compare the measurement probability...
1634 qreal apiProb = -1;
1635 qindex apiOut = applyMultiQubitMeasurementAndGetProb(qureg, targets.data(), numTargs, &apiProb);
1636 auto apiOutBits = getBits(apiOut, numTargs);
1637 qreal refProb = getReferenceProbability(ref, targets, apiOutBits);
1638 REQUIRE_AGREE( apiProb, refProb );
1639
1640 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
1641 qmatrix projector = getProjector(targets, apiOutBits, numQubits);
1642 applyReferenceOperator(ref, projector);
1643 ref /= (qureg.isDensityMatrix)?
1644 refProb : std::sqrt(refProb);
1645 };
1646
1647 CAPTURE( targets );
1648 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1649 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1650 }
1651
1652 /// @todo input validation
1653}
1654
1655
1656TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY ) {
1657
1658 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1659
1660 SECTION( LABEL_CORRECTNESS ) {
1661
1662 GENERATE( range(0,10) );
1663 int target = GENERATE_COPY( range(0,numQubits) );
1664
1665 auto testFunc = [&](Qureg qureg, auto& ref) {
1666
1667 // overwrite caller's setting of initDebugState, since
1668 // sampling requires the outcome probs are normalised
1669 setToRandomState(ref);
1670 setQuregToReference(qureg, ref);
1671
1672 // the output API state...
1673 int apiOut = applyQubitMeasurement(qureg, target);
1674
1675 // informs the projector which determines the post-measurement reference
1676 qmatrix projector = getProjector(apiOut);
1677 applyReferenceOperator(ref, {target}, projector);
1678 qreal refProb = getReferenceProbability(ref, {target}, {apiOut});
1679 ref /= (qureg.isDensityMatrix)?
1680 refProb : std::sqrt(refProb);
1681 };
1682
1683 CAPTURE( target );
1684 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1685 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1686 }
1687
1688 /// @todo input validation
1689}
1690
1691
1692TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY ) {
1693
1694 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1695
1696 SECTION( LABEL_CORRECTNESS ) {
1697
1698 GENERATE( range(0,10) );
1699 int target = GENERATE_COPY( range(0,numQubits) );
1700
1701 auto testFunc = [&](Qureg qureg, auto& ref) {
1702
1703 // overwrite caller's setting of initDebugState, since
1704 // sampling requires the outcome probs are normalised
1705 setToRandomState(ref);
1706 setQuregToReference(qureg, ref);
1707
1708 // compare the measurement probability...
1709 qreal apiProb = -1;
1710 int apiOut = applyQubitMeasurementAndGetProb(qureg, target, &apiProb);
1711 qreal refProb = getReferenceProbability(ref, {target}, {apiOut});
1712 REQUIRE_AGREE( apiProb, refProb );
1713
1714 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
1715 qmatrix projector = getProjector(apiOut);
1716 applyReferenceOperator(ref, {target}, projector);
1717 ref /= (qureg.isDensityMatrix)?
1718 refProb : std::sqrt(refProb);
1719 };
1720
1721 CAPTURE( target );
1722 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1723 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1724 }
1725
1726 /// @todo input validation
1727}
1728
1729
1730TEST_CASE( "multiplyFullStateDiagMatr", TEST_CATEGORY LABEL_MIXED_DEPLOY_TAG ) {
1731
1732 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
1733
1734 auto cachedMatrs = getCachedFullStateDiagMatrs();
1735
1736 SECTION( LABEL_CORRECTNESS ) {
1737
1738 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
1739 auto apiFunc = multiplyFullStateDiagMatr;
1740
1741 GENERATE( range(0, TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS) );
1742
1743 SECTION( LABEL_STATEVEC ) {
1744
1745 auto refFunc = [&] (qvector& state, qmatrix matr) { multiplyReferenceOperator(state, matr); };
1746
1747 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
1748 }
1749
1750 SECTION( LABEL_DENSMATR ) {
1751
1752 auto refFunc = [&] (qmatrix& state, qmatrix matr) { multiplyReferenceOperator(state, matr); };
1753
1754 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
1755 }
1756 }
1757
1758 /// @todo input validation
1759}
1760
1761
1762TEST_CASE( "multiplyFullStateDiagMatrPower", TEST_CATEGORY LABEL_MIXED_DEPLOY_TAG ) {
1763
1764 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
1765
1766 auto cachedMatrs = getCachedFullStateDiagMatrs();
1767
1768 SECTION( LABEL_CORRECTNESS ) {
1769
1770 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
1771 qcomp exponent = getRandomComplex();
1772
1773 auto apiFunc = [&](Qureg qureg, FullStateDiagMatr matr) {
1774 return multiplyFullStateDiagMatrPower(qureg, matr, exponent);
1775 };
1776
1777 CAPTURE( exponent );
1778
1779 GENERATE( range(0, TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS) );
1780
1781 SECTION( LABEL_STATEVEC ) {
1782
1783 auto refFunc = [&] (qvector& state, qmatrix matr) {
1784 matr = getPowerOfDiagonalMatrix(matr, exponent);
1785 multiplyReferenceOperator(state, matr);
1786 };
1787
1788 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
1789 }
1790
1791 SECTION( LABEL_DENSMATR ) {
1792
1793 auto refFunc = [&] (qmatrix& state, qmatrix matr) {
1794 matr = getPowerOfDiagonalMatrix(matr, exponent);
1795 multiplyReferenceOperator(state, matr);
1796 };
1797
1798 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
1799 }
1800 }
1801
1802 /// @todo input validation
1803}
1804
1805
1806TEST_CASE( "applyFullStateDiagMatr", TEST_CATEGORY LABEL_MIXED_DEPLOY_TAG ) {
1807
1808 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
1809
1810 auto cachedMatrs = getCachedFullStateDiagMatrs();
1811
1812 SECTION( LABEL_CORRECTNESS ) {
1813
1814 qmatrix refMatr = getRandomDiagonalUnitary(numQubits);
1815 auto apiFunc = applyFullStateDiagMatr;
1816
1817 GENERATE( range(0, TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS) );
1818
1819 SECTION( LABEL_STATEVEC ) {
1820
1821 auto refFunc = [&] (qvector& state, qmatrix matr) { applyReferenceOperator(state, matr); };
1822
1823 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
1824 }
1825
1826 SECTION( LABEL_DENSMATR ) {
1827
1828 auto refFunc = [&] (qmatrix& state, qmatrix matr) { applyReferenceOperator(state, matr); };
1829
1830 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
1831 }
1832 }
1833
1834 /// @todo input validation
1835}
1836
1837
1838TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY LABEL_MIXED_DEPLOY_TAG ) {
1839
1840 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
1841
1842 auto cachedMatrs = getCachedFullStateDiagMatrs();
1843
1844 SECTION( LABEL_CORRECTNESS ) {
1845
1846 qmatrix refMatr = getRandomDiagonalUnitary(numQubits);
1847
1848 // supplying a complex exponent requires disabling
1849 // numerical validation to relax unitarity
1850 bool testRealExp = GENERATE( true, false );
1851 qcomp exponent = (testRealExp)?
1852 qcomp(getRandomReal(-2, 2), 0):
1854
1855 auto apiFunc = [&](Qureg qureg, FullStateDiagMatr matr) {
1856 return applyFullStateDiagMatrPower(qureg, matr, exponent);
1857 };
1858
1859 CAPTURE( exponent );
1860
1861 GENERATE( range(0, TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS) );
1862
1863 if (!testRealExp)
1865
1866 SECTION( LABEL_STATEVEC ) {
1867
1868 auto refFunc = [&] (qvector& state, qmatrix matr) {
1869 matr = getPowerOfDiagonalMatrix(matr, exponent);
1870 applyReferenceOperator(state, matr);
1871 };
1872
1873 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
1874 }
1875
1876 SECTION( LABEL_DENSMATR ) {
1877
1878 auto refFunc = [&] (qmatrix& state, qmatrix matr) {
1879 matr = getPowerOfDiagonalMatrix(matr, exponent);
1880 applyReferenceOperator(state, matr);
1881 };
1882
1883 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
1884 }
1885
1887 }
1888
1889 /// @todo input validation
1890}
1891
1892
1893TEST_CASE( "multiplyPauliStrSum", TEST_CATEGORY LABEL_MIXED_DEPLOY_TAG ) {
1894
1895 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1896
1897 SECTION( LABEL_CORRECTNESS ) {
1898
1899 int numQubits = getNumCachedQubits();
1900 int numTerms = GENERATE_COPY( 1, 2, 10 );
1901
1902 PauliStrSum sum = createRandomPauliStrSum(numQubits, numTerms);
1903
1904 auto testFunc = [&](Qureg qureg, auto& ref) {
1905
1906 // must use (and ergo make) an identically-deployed workspace
1907 Qureg workspace = createCloneQureg(qureg);
1908 multiplyPauliStrSum(qureg, sum, workspace);
1909 destroyQureg(workspace);
1910
1911 ref = getMatrix(sum, numQubits) * ref;
1912 };
1913
1914 CAPTURE( numTerms );
1915 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1916 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1917 }
1918
1919 /// @todo input validation
1920}
1921
1922
1923/** @} (end defgroup) */
1924
1925
1926
1927/**
1928 * @todo
1929 * UNTESTED FUNCTIONS
1930 */
1931
1932void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps);
void setValidationEpsilonToDefault()
Definition debug.cpp:108
void setValidationEpsilon(qreal eps)
Definition debug.cpp:100
QuESTEnv getQuESTEnv()
void initDebugState(Qureg qureg)
CompMatr createCompMatr(int numQubits)
Definition matrices.cpp:211
DiagMatr createDiagMatr(int numQubits)
Definition matrices.cpp:246
void destroyDiagMatr(DiagMatr matrix)
Definition matrices.cpp:403
void destroyCompMatr(CompMatr matrix)
Definition matrices.cpp:402
static CompMatr2 getCompMatr2(qcomp **in)
Definition matrices.h:327
static CompMatr1 getCompMatr1(qcomp **in)
Definition matrices.h:311
static DiagMatr2 getDiagMatr2(qcomp *in)
Definition matrices.h:359
static DiagMatr1 getDiagMatr1(qcomp *in)
Definition matrices.h:345
void setDiagMatr(DiagMatr out, qcomp *in)
Definition matrices.cpp:435
void setCompMatr(CompMatr matr, qcomp **vals)
Definition matrices.cpp:427
void setFullStateDiagMatr(FullStateDiagMatr out, qindex startInd, qcomp *in, qindex numElems)
Definition matrices.cpp:447
void multiplyCompMatr1(Qureg qureg, int target, CompMatr1 matrix)
void multiplyCompMatr2(Qureg qureg, int target1, int target2, CompMatr2 matr)
void multiplyCompMatr(Qureg qureg, int *targets, int numTargets, CompMatr matr)
void multiplyDiagMatr1(Qureg qureg, int target, DiagMatr1 matr)
void multiplyDiagMatr2(Qureg qureg, int target1, int target2, DiagMatr2 matr)
void multiplyDiagMatr(Qureg qureg, int *targets, int numTargets, DiagMatr matrix)
void multiplyDiagMatrPower(Qureg qureg, int *targets, int numTargets, DiagMatr matrix, qcomp exponent)
void applyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix)
void multiplyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent)
void applyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent)
void multiplyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix)
qreal applyForcedQubitMeasurement(Qureg qureg, int target, int outcome)
qindex applyMultiQubitMeasurement(Qureg qureg, int *qubits, int numQubits)
int applyQubitMeasurement(Qureg qureg, int target)
qreal applyForcedMultiQubitMeasurement(Qureg qureg, int *qubits, int *outcomes, int numQubits)
qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, int *qubits, int numQubits, qreal *probability)
int applyQubitMeasurementAndGetProb(Qureg qureg, int target, qreal *probability)
void multiplyMultiQubitNot(Qureg qureg, int *targets, int numTargets)
void multiplyPauliGadget(Qureg qureg, PauliStr str, qreal angle)
void multiplyPauliStr(Qureg qureg, PauliStr str)
void multiplyPauliStrSum(Qureg qureg, PauliStrSum sum, Qureg workspace)
void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps)
void applyMultiQubitPhaseShift(Qureg qureg, int *targets, int numTargets, qreal angle)
void applyTwoQubitPhaseShift(Qureg qureg, int target1, int target2, qreal angle)
void applyTwoQubitPhaseFlip(Qureg qureg, int target1, int target2)
void applyPhaseShift(Qureg qureg, int target, qreal angle)
void applyPhaseFlip(Qureg qureg, int target)
void multiplyPhaseGadget(Qureg qureg, int *targets, int numTargets, qreal angle)
void applyMultiQubitPhaseFlip(Qureg qureg, int *targets, int numTargets)
void applyMultiQubitProjector(Qureg qureg, int *qubits, int *outcomes, int numQubits)
void applyQubitProjector(Qureg qureg, int target, int outcome)
void applyFullQuantumFourierTransform(Qureg qureg)
void applyQuantumFourierTransform(Qureg qureg, int *targets, int numTargets)
PauliStr getPauliStr(const char *paulis, int *indices, int numPaulis)
Definition paulis.cpp:296
Qureg createCloneQureg(Qureg qureg)
Definition qureg.cpp:315
void destroyQureg(Qureg qureg)
Definition qureg.cpp:330
qmatrix getKroneckerProduct(qmatrix a, qmatrix b)
Definition linalg.cpp:523
qmatrix getExponentialOfPauliMatrix(qreal arg, qmatrix m)
Definition linalg.cpp:348
const int TEST_NUM_MIXED_DEPLOYMENT_REPETITIONS
Definition macros.hpp:63
qmatrix getIdentityMatrix(size_t dim)
Definition qmatrix.cpp:30
qmatrix getZeroMatrix(size_t dim)
Definition qmatrix.cpp:18
qcomp getRandomComplex()
Definition random.cpp:107
qmatrix getRandomUnitary(int numQb)
Definition random.cpp:348
qreal getRandomReal(qreal min, qreal maxExcl)
Definition random.cpp:63
vector< qreal > getRandomProbabilities(int numProbs)
Definition random.cpp:160
int getRandomInt(int min, int maxExcl)
Definition random.cpp:90
TEST_CASE("calcExpecPauliStr", TEST_CATEGORY)
TEST_ALL_CTRL_OPERATIONS(PauliStr, any, paulistr, nullptr)
Definition qureg.h:49