The Quantum Exact Simulation Toolkit v4.3.0
Loading...
Searching...
No Matches
operations.cpp
1/** @file
2 * Unit tests of both multiplication and operations modules, since
3 * they use inextricable testing logic. Beware that because the
4 * operation functions have so much interface and test-semantic
5 * overlap (e.g. the logic of control qubits, of control-states,
6 * of density matrix variants), this file has opted to make
7 * extensive use of generics and metaprogramming, to avoid the
8 * code duplication of defining each function an independent test.
9 * This may have been a mistake, and the file now full of spaghetti
10 * comprised of advanced, misused C++ facilities. View at your own
11 * peril!
12 *
13 * @author Tyson Jones
14 *
15 * @defgroup unitops Operations
16 * @ingroup unittests
17 *
18 * @defgroup unitmult Multiplication
19 * @ingroup unittests
20 */
21
22#include "quest.h"
23
24#include <catch2/catch_test_macros.hpp>
25#include <catch2/catch_template_test_macros.hpp>
26#include <catch2/matchers/catch_matchers_string.hpp>
27#include <catch2/matchers/catch_matchers_floating_point.hpp>
28#include <catch2/generators/catch_generators_range.hpp>
29
30#include "tests/utils/config.hpp"
31#include "tests/utils/cache.hpp"
32#include "tests/utils/qvector.hpp"
33#include "tests/utils/qmatrix.hpp"
34#include "tests/utils/compare.hpp"
35#include "tests/utils/convert.hpp"
36#include "tests/utils/evolve.hpp"
37#include "tests/utils/linalg.hpp"
38#include "tests/utils/lists.hpp"
39#include "tests/utils/measure.hpp"
40#include "tests/utils/macros.hpp"
41#include "tests/utils/random.hpp"
42
43#include <tuple>
44
45using std::tuple;
46using Catch::Matchers::ContainsSubstring;
47
48
49/*
50 * INTERNAL QUEST FUNCITONS
51 */
52
53extern int paulis_getPauliAt(PauliStr str, int ind);
54
55
56/*
57 * UTILITIES
58 */
59
60#define TEST_CATEGORY_OPS \
61 LABEL_UNIT_TAG "[operations]"
62
63#define TEST_CATEGORY_MULT \
64 LABEL_UNIT_TAG "[multiplication]"
65
66
67/*
68 * reference operator matrices used by testing
69 */
70
71namespace FixedMatrices {
72
73 qmatrix H = {
74 {1/std::sqrt(2), 1/std::sqrt(2)},
75 {1/std::sqrt(2), -1/std::sqrt(2)}};
76
77 qmatrix X = getPauliMatrix(1);
78 qmatrix Y = getPauliMatrix(2);
79 qmatrix Z = getPauliMatrix(3);
80
81 qreal PI = 3.14159265358979323846;
82 qmatrix T = {
83 {1, 0},
84 {0, std::exp(1_i * PI/4)}};
85
86 qmatrix S = {
87 {1, 0},
88 {0, 1_i}};
89
90 qmatrix SWAP = {
91 {1, 0, 0, 0},
92 {0, 0, 1, 0},
93 {0, 1, 0, 0},
94 {0, 0, 0, 1}};
95
96 qmatrix sqrtSWAP = {
97 {1, 0, 0, 0},
98 {0, (1+1_i)/2, (1-1_i)/2, 0},
99 {0, (1-1_i)/2, (1+1_i)/2, 0},
100 {0, 0, 0, 1}};
101}
102
103namespace ParameterisedMatrices {
104
105 auto Rx = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::X); };
106 auto Ry = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::Y); };
107 auto Rz = [](qreal p) { return getExponentialOfPauliMatrix(p, FixedMatrices::Z); };
108
109 auto PS = [](qreal p) { return qmatrix{{1, 0}, {0, std::exp(p*1_i)}}; };
110 auto PS2 = [](qreal p) { return getControlledMatrix(PS(p), 1); };
111}
112
113namespace VariableSizeMatrices {
114
115 auto X = [](int n) { return getKroneckerProduct(FixedMatrices::X, n); };
116 auto PF = [](int n) { return getControlledMatrix(FixedMatrices::Z, n - 1); };
117}
118
119namespace VariableSizeParameterisedMatrices {
120
121 auto Z = [](qreal p, int n) {
122 qmatrix m = getKroneckerProduct(FixedMatrices::Z, n);
123 return getExponentialOfPauliMatrix(p, m);
124 };
125
126 auto PS = [](qreal p, int n) {
127 qmatrix m = ParameterisedMatrices::PS(p);
128 return getControlledMatrix(m, n - 1);
129 };
130}
131
132
133/*
134 * execute 'function' upon each kind of cached qureg
135 * (e.g. distributed, GPU-accelerated, etc) and a
136 * reference state (T1 = qvector or qmatrix), when
137 * both are initialised in the debug state, and
138 * thereafter confirm they approximately agree. Each
139 * qureg deployment is featured in a separate test
140 * section, so are accounted distinctly.
141 */
142
143void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& reference, auto& function) {
144
145 for (auto& [label, qureg]: quregs) {
146
147 DYNAMIC_SECTION( label ) {
148
149 // no need to validate whether qureg successfully
150 // enters the debug state here, because the below
151 // serial setToDebugState() is guaranteed to succeed
152 initDebugState(qureg);
153 setToDebugState(reference);
154
155 function(qureg, reference);
156 REQUIRE_AGREE( qureg, reference );
157 }
158 }
159}
160
161void TEST_ON_CACHED_QUREG_AND_MATRIX(quregCache quregs, matrixCache matrices, auto apiFunc, auto refState, auto refMatr, auto refFunc) {
162
163 for (auto& [labelA, qureg]: quregs) {
164 for (auto& [labelB, matrix]: matrices) {
165
166 // skip illegal (distributed matrix, local qureg) combo
167 if (matrix.isDistributed && ! qureg.isDistributed)
168 continue;
169
170 DYNAMIC_SECTION( labelA + LABEL_DELIMITER + labelB ) {
171
172 // set qureg and reference to debug
173 initDebugState(qureg);
174 setToDebugState(refState);
175
176 // set API matrix to pre-initialised ref matrix
177 setFullStateDiagMatr(matrix, 0, getDiagonals(refMatr));
178
179 // API and reference functions should produce agreeing states
180 apiFunc(qureg, matrix);
181 refFunc(refState, refMatr);
182 REQUIRE_AGREE( qureg, refState );
183 }
184 }
185 }
186}
187
188
189/*
190 * simply avoids boilerplate
191 */
192
193#define PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ) \
194 int numQubits = getNumCachedQubits(); \
195 auto statevecQuregs = getCachedStatevecs(); \
196 auto densmatrQuregs = getCachedDensmatrs(); \
197 qvector statevecRef = getZeroVector(getPow2(numQubits)); \
198 qmatrix densmatrRef = getZeroMatrix(getPow2(numQubits));
199
200
201/*
202 * Template flags for specifying what kind of additional
203 * arguments (in addition to ctrls/states/targs below) are
204 * accepted by an API operation, when passing said operation
205 * to automated testing facilities. This is NOT consulted
206 * when generically invoking the API operation (we instead
207 * used variadic templates above for that), but IS used
208 * by the testing code to decide how to prepare inputs.
209 *
210 * For example:
211 * - applyHadamard: none
212 * - applyRotateX: scalar
213 * - applyRotateAroundAxis: axisrots
214 * - applyDiagMatr1: diagmatr
215 * - applyDiagMatrPower: diagpower
216 * - applyCompMatr: compmatr
217 * - applyPauliStr: paulistr
218 * - applyPauliGadget: pauligad
219*/
220
221enum ArgsFlag { none, scalar, axisrots, diagmatr, diagpower, compmatr, paulistr, pauligad };
222
223
224/*
225 * Template flags for specifying how many control and
226 * target qubits are accepted by an API operation.
227 * Value 'anystates' is reserved for control qubits,
228 * indicating that ctrls must accompany ctrl-states in
229 * the API signature.
230 *
231 * For example:
232 * - applyHadamard: Ctrls=zero, Targs=one
233 * - applyControlledSwap: Ctrls=one, Targs=two
234 * - applyMultiControlledCompMatr: Ctrls=any, Targs=any
235 * - applyMultiStateControlledT: Ctrls=anystates, Targs=one
236 */
237
238enum NumQubitsFlag { zero, one, two, any, anystates };
239
240void assertNumQubitsFlagsAreValid(NumQubitsFlag ctrlsFlag, NumQubitsFlag targsFlag) {
241
242 DEMAND(
243 ctrlsFlag == zero ||
244 ctrlsFlag == one ||
245 ctrlsFlag == any ||
246 ctrlsFlag == anystates );
247
248 DEMAND(
249 targsFlag == zero ||
250 targsFlag == one ||
251 targsFlag == two ||
252 targsFlag == any);
253}
254
255void assertNumQubitsFlagsValid(
256 NumQubitsFlag ctrlsFlag, NumQubitsFlag targsFlag,
257 vector<int> ctrls, vector<int> states, vector<int> targs
258) {
259 assertNumQubitsFlagsAreValid(ctrlsFlag, targsFlag);
260
261 // we explicitly permit targsFlag=zero while
262 // targs.size() != 0, which occurs when targs
263 // are supplied to the API through an alternate
264 // argument (e.g. a PauliStr)
265
266 if (targsFlag == one)
267 DEMAND( targs.size() == 1 );
268
269 if (targsFlag == two)
270 DEMAND( targs.size() == 2 );
271
272 if (ctrlsFlag == zero)
273 DEMAND( ctrls.size() == 0 );
274
275 if (ctrlsFlag == one)
276 DEMAND( ctrls.size() == 1 );
277
278 if (ctrlsFlag == anystates)
279 DEMAND( states.size() == ctrls.size() );
280 else
281 DEMAND( states.size() == 0 );
282}
283
284
285/*
286 * extract the runtime values of the number of control
287 * and target qubtits from their compile-time templates.
288 * When their number is permitted to be 'any', we use
289 * a generator to successively test all possible numbers.
290 * As such, this function is named similar to a Catch2
291 * macro so the caller recognises it is a generator.
292 */
293
294template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
295int GENERATE_NUM_TARGS(int numQuregQubits) {
296
297 assertNumQubitsFlagsAreValid(zero, Targs);
298 DEMAND( Targs != one || numQuregQubits >= 1 );
299 DEMAND( Targs != two || numQuregQubits >= 2 );
300 DEMAND( numQuregQubits > 0 );
301
302 // single choice if #targs is compile-time set
303 if constexpr (Targs == one)
304 return 1;
305 if constexpr (Targs == two)
306 return 2;
307
308 if constexpr (Targs == any) {
309
310 // we can target all qubits...
311 int maxNumTargs = numQuregQubits;
312
313 // unless we're applying CompMatr in distributed
314 // mode. Technically we can support all targets
315 // if the Qureg is a density matrix or not
316 // distributed, but we safely choose the min here
317 if (Args == compmatr)
318 maxNumTargs = numQuregQubits - getLog2(getQuESTEnv().numNodes);
319
320 // we must also ensure there is space left for a forced ctrl
321 if (Ctrls == one && maxNumTargs == numQuregQubits)
322 maxNumTargs -= 1;
323
324 return GENERATE_COPY( range(1, maxNumTargs+1) );
325 }
326}
327
328template <NumQubitsFlag Ctrls>
329int GENERATE_NUM_CTRLS(int numFreeQubits) {
330
331 assertNumQubitsFlagsAreValid(Ctrls, one);
332 DEMAND( Ctrls != one || numFreeQubits >= 1 );
333 DEMAND( numFreeQubits >= 0 );
334
335 if constexpr (Ctrls == zero)
336 return 0;
337
338 if constexpr (Ctrls == one)
339 return 1;
340
341 if constexpr (Ctrls == any || Ctrls == anystates)
342 return GENERATE_COPY( range(0, numFreeQubits+1) );
343}
344
345
346/*
347 * invoke an API operation (e.g. applyHadamard), passing
348 * any elements of (ctrls,states,targs) that it accepts
349 * (as informed by the template values) along with any
350 * additional arguments. We explicitly accept numCtrls
351 * and numTargs, rather than inferring them from ctrls
352 * and targs, such that invalid numbers (like -1) can be
353 * passed by input validation testing.
354 *
355 * This big, ugly bespoke function is necessary, rather
356 * than a simple variadic template, because the QuEST
357 * API accepts fixed numbers of qubits as individual
358 * arguments, rather than as lists/vectors/pointers. Note
359 * that our use of variadic templates (args) means we do
360 * not need to include ArgsFlag as a template parameter.
361 */
362
363template <NumQubitsFlag Ctrls, NumQubitsFlag Targs>
364void invokeApiOperation(
365 auto operation, Qureg qureg,
366 vector<int> ctrls, vector<int> states, int numCtrls,
367 vector<int> targs, int numTargs,
368 auto&... args
369) {
370 assertNumQubitsFlagsValid(Ctrls, Targs, ctrls, states, targs);
371
372 if constexpr (Ctrls == zero) {
373 if constexpr (Targs == zero) operation(qureg, args...);
374 if constexpr (Targs == one) operation(qureg, targs[0], args...);
375 if constexpr (Targs == two) operation(qureg, targs[0], targs[1], args...);
376 if constexpr (Targs == any) operation(qureg, targs.data(), numTargs, args...);
377 }
378 if constexpr (Ctrls == one) {
379 if constexpr (Targs == zero) operation(qureg, ctrls[0], args...);
380 if constexpr (Targs == one) operation(qureg, ctrls[0], targs[0], args...);
381 if constexpr (Targs == two) operation(qureg, ctrls[0], targs[0], targs[1], args...);
382 if constexpr (Targs == any) operation(qureg, ctrls[0], targs.data(), numTargs, args...);
383 }
384 if constexpr (Ctrls == any) {
385 if constexpr (Targs == zero) operation(qureg, ctrls.data(), numCtrls, args...);
386 if constexpr (Targs == one) operation(qureg, ctrls.data(), numCtrls, targs[0], args...);
387 if constexpr (Targs == two) operation(qureg, ctrls.data(), numCtrls, targs[0], targs[1], args...);
388 if constexpr (Targs == any) operation(qureg, ctrls.data(), numCtrls, targs.data(), numTargs, args...);
389 }
390 if constexpr (Ctrls == anystates) {
391 if constexpr (Targs == zero) operation(qureg, ctrls.data(), states.data(), numCtrls, args...);
392 if constexpr (Targs == one) operation(qureg, ctrls.data(), states.data(), numCtrls, targs[0], args...);
393 if constexpr (Targs == two) operation(qureg, ctrls.data(), states.data(), numCtrls, targs[0], targs[1], args...);
394 if constexpr (Targs == any) operation(qureg, ctrls.data(), states.data(), numCtrls, targs.data(), numTargs, args...);
395 }
396}
397
398// overload to avoid passing numCtrls and numTargs
399
400template <NumQubitsFlag Ctrls, NumQubitsFlag Targs>
401void invokeApiOperation(auto operation, Qureg qureg, vector<int> ctrls, vector<int> states, vector<int> targs, auto&... args) {
402 invokeApiOperation<Ctrls,Targs>(operation, qureg, ctrls, states, ctrls.size(), targs, targs.size(), args...);
403}
404
405
406/*
407 * prepare an API matrix (e.g. CompMatr1), as per
408 * the given template parameters. Depending on the
409 * elemsFlag, the elements can be initialised to
410 * zero (0), the identity matrix (1), or randomly (2).
411 * This is used for testing API functions which accept
412 * matrices, in a function-agnostic way.
413 */
414
415template <NumQubitsFlag Targs, ArgsFlag Args>
416auto getRandomOrIdentityApiMatrix(int numTargs, int elemsFlag) {
417
418 DEMAND(
419 Args == diagmatr ||
420 Args == diagpower ||
421 Args == compmatr );
422 DEMAND(
423 elemsFlag == 0 ||
424 elemsFlag == 1 ||
425 elemsFlag == 2 );
426
427 qmatrix qm;
428 if (elemsFlag == 0)
429 qm = getZeroMatrix(getPow2(numTargs));
430 if (elemsFlag == 1)
431 qm = getIdentityMatrix(getPow2(numTargs));
432 if (elemsFlag == 2)
433 qm = (Args == compmatr)?
434 getRandomUnitary(numTargs) :
435 getRandomDiagonalUnitary(numTargs);
436
437 if constexpr (Args == compmatr && Targs == one)
438 return getCompMatr1(qm);
439
440 if constexpr (Args == compmatr && Targs == two)
441 return getCompMatr2(qm);
442
443 if constexpr (Args == compmatr && Targs == any) {
444 CompMatr cm = createCompMatr(numTargs); // must be freed
445 setCompMatr(cm, qm);
446 return cm;
447 }
448
449 qvector dv = getDiagonals(qm);
450 constexpr bool diag = (Args == diagmatr || Args == diagpower);
451
452 if constexpr (diag && Targs == one)
453 return getDiagMatr1(dv);
454
455 if constexpr (diag && Targs == two)
456 return getDiagMatr2(dv);
457
458 if constexpr (diag && Targs == any) {
459 DiagMatr dm = createDiagMatr(numTargs); // must be freed
460 setDiagMatr(dm, dv);
461 return dm;
462 }
463}
464
465template <NumQubitsFlag Targs, ArgsFlag Args> auto getZeroApiMatrix (int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 0); }
466template <NumQubitsFlag Targs, ArgsFlag Args> auto getIdentityApiMatrix(int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 1); }
467template <NumQubitsFlag Targs, ArgsFlag Args> auto getRandomApiMatrix (int numTargs) { return getRandomOrIdentityApiMatrix<Targs,Args>(numTargs, 2); }
468
469
470/*
471 * chooses random values for the remaining arguments
472 * (after controls/states/targets) to API operations,
473 * with types informed by the template parameters.
474 * For example:
475 * - applyRotateX() accepts a scalar
476 * - applyCompMatr() accepts a CompMatr
477 * - applyDiagMatrPower() accepts a DiagMatr and qcomp
478 */
479
480template <NumQubitsFlag Targs, ArgsFlag Args>
481auto getRandomRemainingArgs(vector<int> targs) {
482
483 if constexpr (Args == none)
484 return tuple{ };
485
486 if constexpr (Args == scalar) {
487 qreal angle = getRandomPhase();
488 return tuple{ angle };
489 }
490
491 if constexpr (Args == axisrots) {
492 qreal angle = getRandomPhase();
493 qreal x = getRandomReal(-1, 1);
494 qreal y = getRandomReal(-1, 1);
495 qreal z = getRandomReal(-1, 1);
496 return tuple{ angle, x, y, z };
497 }
498
499 if constexpr (Args == compmatr || Args == diagmatr) {
500 auto matrix = getRandomApiMatrix<Targs,Args>(targs.size()); // allocates heap mem
501 return tuple{ matrix };
502 }
503
504 if constexpr (Args == diagpower) {
505 DiagMatr matrix = getRandomApiMatrix<Targs,Args>(targs.size()); // allocates heap mem
506 qcomp exponent = qcomp(getRandomReal(-3, 3), 0); // real for unitarity
507 return tuple{ matrix, exponent };
508 }
509
510 if constexpr (Args == paulistr) {
511 PauliStr str = getRandomPauliStr(targs);
512 return tuple{ str };
513 }
514
515 if constexpr (Args == pauligad) {
516 PauliStr str = getRandomPauliStr(targs);
517 qreal angle = getRandomPhase();
518 return tuple{ str, angle };
519 }
520}
521
522
523template <NumQubitsFlag Targs, ArgsFlag Args>
524void freeRemainingArgs(auto args) {
525
526 if constexpr (Targs == any && Args == compmatr)
527 destroyCompMatr(std::get<0>(args));
528
529 if constexpr (Targs == any && Args == diagmatr)
530 destroyDiagMatr(std::get<0>(args));
531
532 if constexpr (Targs == any && Args == diagpower)
533 destroyDiagMatr(std::get<0>(args));
534}
535
536
537/*
538 * unpack the given reference operator matrix (a qmatrix)
539 * for an API operation, which is passed to testOperation(),
540 * and which will be effected upon the reference state (a
541 * qvector or qmatrix). The type/form of matrixRefGen depends
542 * on the type of API operation, indicated by template parameter.
543 */
544
545template <NumQubitsFlag Targs, ArgsFlag Args>
546qmatrix getReferenceMatrix(auto matrixRefGen, vector<int> targs, auto additionalArgs) {
547
548 if constexpr (Args == none && Targs != any)
549 return matrixRefGen;
550
551 if constexpr (Args == none && Targs == any)
552 return matrixRefGen(targs.size());
553
554 if constexpr (Args == scalar && Targs != any) {
555 qreal angle = std::get<0>(additionalArgs);
556 return matrixRefGen(angle);
557 }
558
559 if constexpr (Args == scalar && Targs == any) {
560 qreal angle = std::get<0>(additionalArgs);
561 return matrixRefGen(angle, targs.size());
562 }
563
564 if constexpr (Args == axisrots) {
565 qreal angle = std::get<0>(additionalArgs);
566 qreal x = std::get<1>(additionalArgs);
567 qreal y = std::get<2>(additionalArgs);
568 qreal z = std::get<3>(additionalArgs);
569 return getExponentialOfNormalisedPauliVector(angle, x, y, z);
570 }
571
572 if constexpr (Args == compmatr || Args == diagmatr) {
573 auto apiMatrix = std::get<0>(additionalArgs);
574 return getMatrix(apiMatrix);
575 }
576
577 if constexpr (Args == diagpower) {
578 auto apiMatrix = std::get<0>(additionalArgs);
579 qmatrix diag = getMatrix(apiMatrix);
580 qcomp power = std::get<1>(additionalArgs);
581 return getPowerOfDiagonalMatrix(diag, power);
582 }
583
584 if constexpr (Args == paulistr) {
585 PauliStr str = std::get<0>(additionalArgs);
586 return getMatrix(str, targs);
587 }
588
589 if constexpr (Args == pauligad) {
590 PauliStr str = std::get<0>(additionalArgs);
591 qreal angle = std::get<1>(additionalArgs);
592 qmatrix matr = getMatrix(str, targs);
593 return getExponentialOfPauliMatrix(angle, matr);
594 }
595}
596
597
598/*
599 * Template parameters which specify how the reference
600 * operatorshould be applied upon the reference state.
601 * Let |psi> be a statevector, rho be a density matrix,
602 * and matr be an operator matrix. The options perform:
603 *
604 * apply: |psi> -> matr |psi>, rho -> matr rho adj(matr)
605 * leftapply: |psi> -> matr |psi>, rho -> matr rho
606 * rightapply: rho -> rho matr
607 *
608 * Note this is necessarily a template parameter (rather
609 * than just a runtime parameter) only because the
610 * rightapplyReferenceOperator() function is defined
611 * only upon qmatrix (for density matrices)
612 */
613
614enum ApplyFlag { apply, leftapply, rightapply };
615
616
617/*
618 * display all/only relevant inputs given to an
619 * API operation when its subsequent test fails.
620 * This is like a customisation of CAPTURE, although
621 * we must use UNSCOPED_INFO (and ergo re-implement
622 * some printers) because our branching makes scopes
623 * which end CAPTURE's lifetime.
624 */
625
626
627// @todo surely this should live somewhere else,
628// and/or re-use printer_ functions as much as possible
629
630std::string toString(vector<int> list) {
631
632 std::string out = "{ ";
633 for (int& e : list)
634 out += std::to_string(e) + " ";
635 out += "}";
636 return out;
637}
638
639std::string toString(PauliStr str, vector<int> targs) {
640
641 std::string labels = "IXYZ";
642
643 // ugly but adequate - like me (call me)
644 std::string out = "";
645 for (int i=targs.size()-1; i>=0; i--)
646 out += labels[paulis_getPauliAt(str, targs[i])];
647
648 return out;
649}
650
651template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
652void CAPTURE_RELEVANT( vector<int> ctrls, vector<int> states, vector<int> targs, auto& args ) {
653
654 // display ctrls
655 if (Ctrls == one)
656 UNSCOPED_INFO( "control := " << ctrls[0] );
657 if (Ctrls == any || Ctrls == anystates )
658 UNSCOPED_INFO( "controls := " << toString(ctrls) );
659
660 // display states
661 if (Ctrls == anystates)
662 UNSCOPED_INFO( "states := " << toString(states) );
663
664 // display targs
665 if (Targs == one)
666 UNSCOPED_INFO( "target := " << targs[0] );
667
668 if (Targs == two) {
669 UNSCOPED_INFO( "target A := " << targs[0] );
670 UNSCOPED_INFO( "target B := " << targs[1] );
671 }
672 if (Targs == any)
673 UNSCOPED_INFO( "targets := " << toString(targs) );
674
675 // display rotation angle
676 if constexpr (Args == scalar)
677 UNSCOPED_INFO( "angle := " << std::get<0>(args) );
678
679 // display rotation angle and axis
680 if constexpr (Args == axisrots) {
681 UNSCOPED_INFO( "angle := " << std::get<0>(args) );
682 UNSCOPED_INFO( "x := " << std::get<1>(args) );
683 UNSCOPED_INFO( "y := " << std::get<2>(args) );
684 UNSCOPED_INFO( "z := " << std::get<3>(args) );
685 }
686
687 // note but don't display API matrices
688 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
689 UNSCOPED_INFO( "matrix := (omitted)" );
690
691 // display exponent of diagonal matrix
692 if constexpr (Args == diagpower) {
693 qcomp p = std::get<1>(args);
694 UNSCOPED_INFO( "exponent := " << std::real(p) << " + (" << std::imag(p) << ")i" );
695 }
696
697 // display PauliStr
698 if constexpr (Args == paulistr || Args == pauligad)
699 UNSCOPED_INFO( "paulis := " << toString(std::get<0>(args), targs) );
700
701 // display PauliStr angle
702 if constexpr (Args == pauligad)
703 UNSCOPED_INFO( "angle := " << std::get<1>(args) );
704}
705
706
707/*
708 * test the correctness of an API operation. The
709 * template parameters are compile-time clues
710 * about what inputs to prepare and pass to the
711 * operation, and how its reference matrix (arg
712 * matrixRefGen) is formatted.
713 */
714
715template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args, ApplyFlag Apply>
716void testOperationCorrectness(auto operation, auto matrixRefGen) {
717
718 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
719
720 // try all possible number of ctrls and targs
721 int numTargs = GENERATE_NUM_TARGS<Ctrls,Targs,Args>(numQubits);
722 int numCtrls = GENERATE_NUM_CTRLS<Ctrls>(numQubits - numTargs);
723
724 // either try all possible ctrls and targs, or randomise them
725 auto ctrlsAndTargs = GENERATE_CTRLS_AND_TARGS( numQubits, numCtrls, numTargs );
726 vector<int> ctrls = std::get<0>(ctrlsAndTargs);
727 vector<int> targs = std::get<1>(ctrlsAndTargs);
728
729 // randomise control states (if operation accepts them)
730 vector<int> states = getRandomOutcomes(numCtrls * (Ctrls == anystates));
731
732 // randomise remaining operation parameters
733 auto primaryArgs = tuple{ ctrls, states, targs };
734 auto furtherArgs = getRandomRemainingArgs<Targs,Args>(targs); // may allocate heap memory
735
736 // obtain the reference matrix for this operation
737 qmatrix matrixRef = getReferenceMatrix<Targs,Args>(matrixRefGen, targs, furtherArgs);
738
739 // PauliStr arg replaces target qubit list in API operations
740 constexpr NumQubitsFlag RevTargs = (Args==paulistr||Args==pauligad)? zero : Targs;
741
742 // disabling unitary-check validation for compmatr, since it's hard to
743 // generate numerically-precise random unitaries upon many qubits, or
744 // upon few qubits are single-precision. So we disable completely until
745 // we re-implement 'input validation' checks which force us to fix thresholds
746 (Args == compmatr)?
749
750 // prepare test function which will receive both statevectors and density matrices
751 auto testFunc = [&](Qureg qureg, auto& stateRef) -> void {
752
753 // invoke API operation, passing all args (unpacking variadic)
754 auto apiFunc = [](auto&&... args) { return invokeApiOperation<Ctrls,RevTargs>(args...); };
755 auto allArgs = std::tuple_cat(tuple{operation, qureg}, primaryArgs, furtherArgs);
756 std::apply(apiFunc, allArgs);
757
758 // update reference state (ctrls & states happen to only ever be used by apply)
759 if constexpr (Apply == apply) applyReferenceOperator( stateRef, ctrls, states, targs, matrixRef);
760 if constexpr (Apply == leftapply) leftapplyReferenceOperator( stateRef, ctrls, states, targs, matrixRef);
761 if constexpr (Apply == rightapply) rightapplyReferenceOperator(stateRef, ctrls, states, targs, matrixRef);
762 };
763
764 // report operation's input parameters if any subsequent test fails
765 CAPTURE_RELEVANT<Ctrls,Targs,Args>( ctrls, states, targs, furtherArgs );
766
767 // test API operation on all available deployment combinations (e.g. OMP, MPI, MPI+GPU, etc),
768 // though the postMultiply*() functions do not accept statevectors
769 if constexpr (Apply != rightapply) {
770 SECTION( LABEL_STATEVEC ) {
771 TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc);
772 }
773 }
774 SECTION( LABEL_DENSMATR ) {
775 TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc);
776 }
777
778 // free any heap-alloated API matrices and restore epsilon
779 freeRemainingArgs<Targs,Args>(furtherArgs);
781}
782
783
784/*
785 * test the input valdiation of an API operation.
786 * The template parameters are compile-time clues
787 * about what inputs are accepted by the operation
788 */
789
790template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args>
791auto getFixedCtrlsStatesTargs(int numQubits) {
792
793 // default each to empty
794 vector<int> targs, ctrls, states;
795
796 // assign when non-empty
797 if constexpr (Targs == one) targs = {0};
798 if constexpr (Targs == two) targs = {0,1};
799 if constexpr (Targs == any) targs = {0,1,2};
800 if constexpr (Ctrls == one) ctrls = {3};
801 if constexpr (Ctrls == any) ctrls = {3,4};
802 if constexpr (Ctrls == anystates) ctrls = {3,4};
803 if constexpr (Ctrls == anystates) states = {0,0};
804
805 DEMAND( numQubits >= targs.size() + ctrls.size() );
806
807 return tuple{ ctrls, states, targs };
808}
809
810template <NumQubitsFlag Targs, ArgsFlag Args>
811auto getFixedRemainingArgs(vector<int> targs) {
812
813 // getPauliStr uses gives length-3 hardcoded string
814 if constexpr (Args == paulistr || Args == pauligad)
815 DEMAND( targs.size() == 3 );
816
817 if constexpr (Args == none) return tuple{ };
818 if constexpr (Args == scalar) return tuple{ 0 }; // angle
819 if constexpr (Args == axisrots) return tuple{ 0, 1,1,1 }; // (angle,x,y,z)
820 if constexpr (Args == compmatr) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()) }; // id
821 if constexpr (Args == diagmatr) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()) }; // id
822 if constexpr (Args == diagpower) return tuple{ getIdentityApiMatrix<Targs,Args>(targs.size()), qcomp(1,0) }; // (id, exponent)
823 if constexpr (Args == paulistr) return tuple{ getPauliStr("XXX", targs) }; // XXX
824 if constexpr (Args == pauligad) return tuple{ getPauliStr("XXX", targs), 0 }; // (XXX, angle)
825}
826
827template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args, ApplyFlag Apply>
828void testOperationValidation(auto operation) {
829
830 // use any cached Qureg (though postMultiply*() functions accept only density matrices)
831 Qureg qureg = getCachedDensmatrs().begin()->second;
832
833 // in lieu of preparing random inputs like testOperationCorrectness()
834 // above, we instead obtain simple, fixed, compatible inputs
835 auto [ctrls,states,targs] = getFixedCtrlsStatesTargs<Ctrls,Targs,Args>(qureg.numQubits);
836 auto furtherArgs = getFixedRemainingArgs<Targs,Args>(targs);
837
838 // calling apiFunc() will pass the above args with their call-time values
839 auto apiFunc = [&]() {
840 constexpr NumQubitsFlag RevTargs = (Args==paulistr||Args==pauligad)? zero : Targs;
841 auto func = [](auto&&... allArgs) { return invokeApiOperation<Ctrls,RevTargs>(allArgs...); };
842 std::apply(func, std::tuple_cat(tuple{operation, qureg, ctrls, states, targs}, furtherArgs));
843 };
844
845 // convenience vars
846 int numQubits = qureg.numQubits;
847 int numTargs = (int) targs.size();
848 int numCtrls = (int) ctrls.size();
849
850 /// @todo
851 /// below, we return from intendedly skipped SECTIONS which
852 /// appears to work (does not corrupt test statistics, and
853 /// does not attribute skipped tests to having passed the
854 /// section) but is an undocumented Catch2 trick. Check safe!
855
856 SECTION( "qureg uninitialised" ) {
857
858 // spoof uninitialised value
859 qureg.numQubits = -123;
860 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("invalid Qureg") );
861 }
862
863 SECTION( "invalid target" ) {
864
865 // not applicable (PauliStr already made from targs)
866 if (Args == paulistr || Args == pauligad)
867 return;
868
869 // sabotage a target
870 int ind = GENERATE_COPY( range(0,numTargs) );
871 int val = GENERATE_COPY( -1, numQubits );
872 targs[ind] = val;
873
874 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid target qubit") );
875 }
876
877 SECTION( "invalid non-Identity Pauli index" ) {
878
879 if (Args != paulistr && Args != pauligad)
880 return;
881
882 PauliStr badStr = getPauliStr("X", {numQubits + 1});
883 if constexpr (Args == paulistr) furtherArgs = tuple{ badStr };
884 if constexpr (Args == pauligad) furtherArgs = tuple{ badStr, 1 };
885
886 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("highest-index non-identity Pauli operator") && ContainsSubstring("exceeds the maximum target") );
887 }
888
889 SECTION( "invalid control" ) {
890
891 if (numCtrls == 0)
892 return;
893
894 // sabotage a ctrl
895 int ind = GENERATE_COPY( range(0,numCtrls) );
896 int val = GENERATE_COPY( -1, numQubits );
897 ctrls[ind] = val;
898
899 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid control qubit") );
900 }
901
902 SECTION( "control and target collision" ) {
903
904 if (numCtrls == 0)
905 return;
906
907 // sabotage a ctrl
908 int targInd = GENERATE_COPY( range(0,numTargs) );
909 int ctrlInd = GENERATE_COPY( range(0,numCtrls) );
910 ctrls[ctrlInd] = targs[targInd];
911
912 if (Args==paulistr||Args==pauligad)
913 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("control qubit overlaps a non-identity Pauli operator") );
914 else
915 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("qubit appeared among both the control and target qubits") );
916 }
917
918 SECTION( "control states" ) {
919
920 if (states.empty())
921 return;
922
923 int ind = GENERATE_COPY( range(0,numCtrls) );
924 int val = GENERATE( -1, 2 );
925 states[ind] = val;
926
927 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("invalid control-state") );
928 }
929
930 SECTION( "repetition in controls" ) {
931
932 if (numCtrls < 2)
933 return;
934
935 int ind = GENERATE_COPY( range(1,numCtrls) );
936 ctrls[ind] = ctrls[0];
937
938 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("control qubits contained duplicates") );
939 }
940
941 SECTION( "repetition in targets" ) {
942
943 // not applicable to Pauli functions (getPauliStr would throw)
944 if (Args==paulistr||Args==pauligad)
945 return;
946
947 if (numTargs < 2)
948 return;
949
950 int ind = GENERATE_COPY( range(1,numTargs) );
951 targs[ind] = targs[0];
952
953 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("target qubits contained duplicates") );
954 }
955
956 SECTION( "number of targets" ) {
957
958 // not applicable to Pauli functions (getPauliStr would run fine,
959 // and the runtime error would be about the non-identity index)
960 if (Args == paulistr || Args == pauligad)
961 return;
962
963 if (Targs != any)
964 return;
965
966 // too few (cannot test less than 0)
967 targs = {};
968 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Must specify one or more targets") );
969
970 // too many; exceeds Qureg
971 targs = getRange(qureg.numQubits+1);
972 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("number of target qubits") && ContainsSubstring("exceeds the number of qubits in the Qureg") );
973
974 // note numTargs + numCtrls > numQubits is caught by
975 // invalid index or overlapping (ctrls,targs) validation
976 }
977
978 SECTION( "mismatching matrix size" ) {
979
980 // only relevant to variable-sized matrices
981 if (Targs != any)
982 return;
983 if (!(Args == compmatr || Args == diagmatr || Args == diagpower))
984 return;
985
986 DEMAND( numTargs > 1 );
987 DEMAND( numCtrls + numTargs < numQubits );
988
989 targs.push_back(numQubits - 1);
990 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("matrix has an inconsistent size") );
991
992 targs.pop_back();
993 targs.pop_back();
994 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("matrix has an inconsistent size") );
995 }
996
997 SECTION( "matrix unitarity" ) {
998
999 // only relevant to matrix functions...
1000 if (Args != compmatr && Args != diagmatr && Args != diagpower)
1001 return;
1002
1003 // which enforce unitarity
1004 if (Apply != apply)
1005 return;
1006
1007 if constexpr (Args == compmatr || Args == diagmatr)
1008 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(targs.size()) };
1009 if constexpr (Args == diagpower)
1010 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(targs.size()), 1 };
1011
1012 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("unitary") );
1013 }
1014
1015 SECTION( "matrix uninitialised" ) {
1016
1017 // sabotage matrix struct field
1018 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
1019 std::get<0>(furtherArgs).numQubits = -1;
1020 else
1021 return;
1022
1023 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Invalid") );
1024
1025 // must correct field so that subsequent destructor doesn't whine!
1026 if constexpr (Args == compmatr || Args == diagmatr || Args == diagpower)
1027 std::get<0>(furtherArgs).numQubits = numTargs;
1028 }
1029
1030 SECTION( "matrix unsynced" ) {
1031
1032 if (!getQuESTEnv().isGpuAccelerated)
1033 return;
1034
1035 // only relevant to variable-size matrix functions
1036 if constexpr (Targs == any && (Args == compmatr || Args == diagmatr || Args == diagpower))
1037 *(std::get<0>(furtherArgs).wasGpuSynced) = 0;
1038 else
1039 return; // avoid empty test
1040
1041 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("sync") );
1042 }
1043
1044 SECTION( "targeted amps fit in node" ) {
1045
1046 // use any qureg which is otherwise compatible
1047 // (but beware statevecs vs density matrices permit
1048 // different num targets before validation is triggered)
1049 qureg = (Apply == rightapply)?
1050 getArbitraryCachedDensmatr():
1051 getArbitraryCachedStatevec();
1052
1053 // can only be validated when environment AND qureg
1054 // are distributed (over more than 1 node, of course)
1055 if (qureg.numNodes < 2)
1056 return;
1057
1058 // can only be validated if forced ctrl qubits permit
1059 // enough remaining targets
1060 int minNumCtrls = (Ctrls == one)? 1 : 0;
1061 int numVecQubits = (qureg.isDensityMatrix)? 2*numQubits : numQubits;
1062 int minNumTargs = numVecQubits - qureg.logNumNodes + 1;
1063 int maxNumTargs = numQubits - minNumCtrls;
1064 if (minNumTargs > maxNumTargs)
1065 return;
1066
1067 // only relevant to >=2-targ dense matrices, and further
1068 // only testable with any-targ variable-size matrices, since
1069 // 4x4 matrices might always be permissable by Qureg distribution
1070 if constexpr (Args == compmatr && Targs == any) {
1071
1072 // free existing matrix to avoid leak
1073 destroyCompMatr(std::get<0>(furtherArgs));
1074
1075 // try all illegally sized matrices
1076 int numNewTargs = GENERATE_COPY( range(minNumTargs, maxNumTargs+1) );
1077 targs = getRange(numNewTargs);
1078
1079 // ensure no overlap with ctrls; just get rid of them, EXCEPT when the API
1080 // function expects explicitly one ctrl which we must always supply
1081 ctrls = vector<int>(minNumCtrls, numQubits - 1); // {} or {last}
1082 states = {};
1083
1084 // create the new illegaly-sized matrix, which will be destroyed at test-case end
1085 CompMatr matr = getIdentityApiMatrix<Targs,Args>(numNewTargs);
1086 furtherArgs = tuple{ matr };
1087
1088 CAPTURE( minNumCtrls, numNewTargs, numQubits - minNumCtrls, ctrls, targs );
1089 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("cannot simultaneously store") && ContainsSubstring("remote amplitudes") );
1090
1091 } else {
1092 return; // avoid empty test
1093 }
1094 }
1095
1096 SECTION( "non-unitary exponent" ) {
1097
1098 // not relevant for functions which do not assert unitarity
1099 if (Apply != apply)
1100 return;
1101
1102 if constexpr (Args == diagpower)
1103 furtherArgs = tuple{ std::get<0>(furtherArgs), qcomp(1,1) };
1104 else
1105 return; // avoid empty test
1106
1107 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("exponent was not approximately real") );
1108 }
1109
1110 SECTION( "diverging exponent" ) {
1111
1112 // when being applied as a unitary, abs(elem)=1 so there's no
1113 // possibility of divergence (we'd merely trigger isUnitary)
1114 if (Apply == apply)
1115 return;
1116
1117 if constexpr (Args == diagpower)
1118 furtherArgs = tuple{ getZeroApiMatrix<Targs,Args>(numTargs), qcomp(-1,0) };
1119 else
1120 return; // avoid empty test
1121
1122 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("divergences") );
1123 }
1124
1125 SECTION( "zero axis rotation" ) {
1126
1127 if constexpr (Args == axisrots) {
1128 furtherArgs = tuple{ 0, 0, 0, 0 }; // (angle,x,y,z)
1129 } else
1130 return; // avoid empty test
1131
1132 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("zero vector") );
1133 }
1134
1135 SECTION( "qureg type" ) {
1136
1137 // only postMultiply*() functions discriminate Qureg
1138 if (Apply != rightapply)
1139 return;
1140
1141 // override qureg in apiyFunc with a statevector
1142 qureg = getArbitraryCachedStatevec();
1143 REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Expected a density matrix") );
1144 }
1145
1146 freeRemainingArgs<Targs,Args>(furtherArgs);
1147}
1148
1149
1150/*
1151 * fully test an API operation, on compatible
1152 * inputs as indicated by the template flags
1153 */
1154
1155template <NumQubitsFlag Ctrls, NumQubitsFlag Targs, ArgsFlag Args, ApplyFlag Apply>
1156void testOperation(auto operation, auto matrixRefGen) {
1157
1158 assertNumQubitsFlagsAreValid(Ctrls, Targs);
1159
1160 SECTION( LABEL_CORRECTNESS ) {
1161 testOperationCorrectness<Ctrls,Targs,Args,Apply>(operation, matrixRefGen);
1162 }
1163
1164 SECTION( LABEL_VALIDATION ) {
1165 testOperationValidation<Ctrls,Targs,Args,Apply>(operation);
1166 }
1167}
1168
1169
1170/*
1171 * perform unit tests for the four distinctly-controlled
1172 * variants of the given API operation (with the specified
1173 * function name suffix). 'numtargs' indicates the number
1174 * of target qubits accepted by the operation, and 'argtype'
1175 * indicates the types of remaining arguments (if any exist).
1176 * 'matrixgen' is the matrix representation (of varying
1177 * formats) of the operation, against which it will be compared.
1178 */
1179
1180// when every API operation had NO overloads, it was sufficient to
1181// call the below simple macro. Alas, since C++ overloads have been
1182// added, the below passing of a function (e.g. applyControlledHadamard)
1183// is ambigious, not specifying whether it is the int* or vector<int>
1184// version. We now have to explicit cast the function to its specific
1185// C-compatible version. Alas, to your imminent horror, this is.. erm...
1186
1187// #define TEST_ALL_CTRL_OPERATIONS( namesuffix, numtargs, argtype, matrixgen ) \
1188// TEST_CASE( "apply" #namesuffix, TEST_CATEGORY_OPS ) { testOperation<zero, numtargs,argtype>( apply ## namesuffix, matrixgen); } \
1189// TEST_CASE( "applyControlled" #namesuffix, TEST_CATEGORY_OPS ) { testOperation<one, numtargs,argtype>( applyControlled ## namesuffix, matrixgen); } \
1190// TEST_CASE( "applyMultiControlled" #namesuffix, TEST_CATEGORY_OPS ) { testOperation<any, numtargs,argtype>( applyMultiControlled ## namesuffix, matrixgen); } \
1191// TEST_CASE( "applyMultiStateControlled" #namesuffix, TEST_CATEGORY_OPS ) { testOperation<anystates,numtargs,argtype>( applyMultiStateControlled ## namesuffix, matrixgen); }
1192
1193
1194/*
1195 * define macros to pre-processor-time generate the API function
1196 * signatures to give to static_cast to disambiguate the function
1197 * from its C++ overloads, as per the above comment. The absolute
1198 * macro nightmare below results from not being able to propagate
1199 * templates to static_cast<...> which is not actually templated!
1200 * May God forgive me for the misdeeds commited here below.
1201 */
1202
1203// produces the control qubit arguments of a function signature,
1204// with a trailing comma (since ctrls always preceed more arguments)
1205#define GET_FUNC_CTRL_SUB_SIG( numctrls ) GET_FUNC_CTRL_SUB_SIG_##numctrls
1206#define GET_FUNC_CTRL_SUB_SIG_zero
1207#define GET_FUNC_CTRL_SUB_SIG_one int,
1208#define GET_FUNC_CTRL_SUB_SIG_any int*,int,
1209#define GET_FUNC_CTRL_SUB_SIG_anystates int*,int*,int,
1210
1211// produces the target qubit arguments of a function signature, complicated
1212// by paulistr and pauligad functions not ever passign explicit targ lists.
1213// trailing comma attached only when targs exist, and final args exist.
1214// beware, macros must never have spaces between the name and open-paranthesis!
1215#define GET_FUNC_TARG_SUB_SIG( numtargs, argtype ) GET_FUNC_TARG_SUB_SIG_##argtype( numtargs )
1216#define GET_FUNC_TARG_SUB_SIG_none( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs
1217#define GET_FUNC_TARG_SUB_SIG_scalar( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1218#define GET_FUNC_TARG_SUB_SIG_axisrots( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1219#define GET_FUNC_TARG_SUB_SIG_compmatr( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1220#define GET_FUNC_TARG_SUB_SIG_diagmatr( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1221#define GET_FUNC_TARG_SUB_SIG_diagpower( numtargs ) GET_FUNC_TARG_SUB_SIG_##numtargs ,
1222#define GET_FUNC_TARG_SUB_SIG_paulistr( numtargs )
1223#define GET_FUNC_TARG_SUB_SIG_pauligad( numtargs )
1224#define GET_FUNC_TARG_SUB_SIG_one int
1225#define GET_FUNC_TARG_SUB_SIG_two int,int
1226#define GET_FUNC_TARG_SUB_SIG_any int*,int
1227
1228// produces the final arguments of a function signature (no trailing comma).
1229#define GET_FUNC_ARGS_SUB_SIG( numtargs, argtype ) GET_FUNC_ARGS_SUB_SIG_##numtargs##_##argtype
1230#define GET_FUNC_ARGS_SUB_SIG_one_none
1231#define GET_FUNC_ARGS_SUB_SIG_two_none
1232#define GET_FUNC_ARGS_SUB_SIG_any_none
1233#define GET_FUNC_ARGS_SUB_SIG_one_scalar qreal
1234#define GET_FUNC_ARGS_SUB_SIG_two_scalar qreal
1235#define GET_FUNC_ARGS_SUB_SIG_any_scalar qreal
1236#define GET_FUNC_ARGS_SUB_SIG_one_compmatr CompMatr1
1237#define GET_FUNC_ARGS_SUB_SIG_two_compmatr CompMatr2
1238#define GET_FUNC_ARGS_SUB_SIG_any_compmatr CompMatr
1239#define GET_FUNC_ARGS_SUB_SIG_one_diagmatr DiagMatr1
1240#define GET_FUNC_ARGS_SUB_SIG_two_diagmatr DiagMatr2
1241#define GET_FUNC_ARGS_SUB_SIG_any_diagmatr DiagMatr
1242#define GET_FUNC_ARGS_SUB_SIG_one_diagpower DiagMatr1,qcomp
1243#define GET_FUNC_ARGS_SUB_SIG_two_diagpower DiagMatr2,qcomp
1244#define GET_FUNC_ARGS_SUB_SIG_any_diagpower DiagMatr, qcomp
1245#define GET_FUNC_ARGS_SUB_SIG_one_axisrots qreal,qreal,qreal,qreal
1246#define GET_FUNC_ARGS_SUB_SIG_any_paulistr PauliStr
1247#define GET_FUNC_ARGS_SUB_SIG_any_pauligad PauliStr,qreal
1248
1249// produces the control-qubit-related prefix of a function name
1250#define GET_FUNC_NAME_PREFIX( numctrls ) GET_FUNC_NAME_PREFIX_##numctrls
1251#define GET_FUNC_NAME_PREFIX_zero apply
1252#define GET_FUNC_NAME_PREFIX_one applyControlled
1253#define GET_FUNC_NAME_PREFIX_any applyMultiControlled
1254#define GET_FUNC_NAME_PREFIX_anystates applyMultiStateControlled
1255
1256// produces a function name from the control qubits and the suffix, e.g. (any,T) -> applyMultiControlledT
1257#define GET_FUNC_NAME(numctrls, suffix) GET_FUNC_NAME_INNER(GET_FUNC_NAME_PREFIX(numctrls), suffix)
1258#define GET_FUNC_NAME_INNER(A, B) GET_FUNC_NAME_INNER_INNER(A, B)
1259#define GET_FUNC_NAME_INNER_INNER(A, B) A##B
1260
1261// converts the output of GET_FUNC_NAME() to a string, e.g. (any,T) -> "applyMultiControlledT"
1262#define GET_FUNC_NAME_STR(numctrls, suffix) GET_FUNC_NAME_STR_INNER( GET_FUNC_NAME(numctrls,suffix) )
1263#define GET_FUNC_NAME_STR_INNER(expr) GET_FUNC_NAME_STR_INNER_INNER(expr)
1264#define GET_FUNC_NAME_STR_INNER_INNER(symbol) #symbol
1265
1266// produces the signature of a function, e.g. (any,one,diagmatr) -> void(*)(Qureg, int*,int, int,DiagMatr1),
1267// which is the signature of applyMultiControlled(Qureg qureg, int* ctrls, int numCtrls, int targ, DiagMatr1 m);
1268// NOTE:
1269// THIS CURRENT EXCLUDES PAULISTR AND PAULIGAD argtype
1270#define GET_FUNC_SIG( numctrls, numtargs, argtype ) \
1271 void(*) ( \
1272 Qureg, \
1273 GET_FUNC_CTRL_SUB_SIG( numctrls ) \
1274 GET_FUNC_TARG_SUB_SIG( numtargs, argtype ) \
1275 GET_FUNC_ARGS_SUB_SIG( numtargs, argtype ) \
1276 )
1277
1278// produces a function name, casted to its explicit C-argument form, disambiguated from its C++ overload.
1279// e.g. (DiagMatrPower, zero, any, diagpower) -> static_cast<void(*)(Qureg,int*,int,DiagMatr,qcomp)>(applyDiagMatrPower)>
1280#define GET_CASTED_FUNC( namesuffix, numctrls, numtargs, argtype ) \
1281 static_cast< GET_FUNC_SIG(numctrls, numtargs, argtype) > ( \
1282 GET_FUNC_NAME(numctrls, namesuffix) )
1283
1284// defines a Catch2 test-case for the implied function
1285#define TEST_CASE_OPERATION( namesuffix, numctrls, numtargs, argtype, matrixgen ) \
1286 TEST_CASE( GET_FUNC_NAME_STR(numctrls, namesuffix), TEST_CATEGORY_OPS ) { \
1287 testOperation<numctrls, numtargs, argtype, apply>( \
1288 GET_CASTED_FUNC(namesuffix, numctrls, numtargs, argtype), \
1289 matrixgen); \
1290 }
1291
1292// automate the testing of a apply*() function for all its controlled variants
1293#define TEST_ALL_CTRL_OPERATIONS( namesuffix, numtargs, argtype, matrixgen ) \
1294 TEST_CASE_OPERATION( namesuffix, zero, numtargs, argtype, matrixgen ) \
1295 TEST_CASE_OPERATION( namesuffix, one, numtargs, argtype, matrixgen ) \
1296 TEST_CASE_OPERATION( namesuffix, any, numtargs, argtype, matrixgen ) \
1297 TEST_CASE_OPERATION( namesuffix, anystates, numtargs, argtype, matrixgen )
1298
1299
1300
1301/**
1302 * OPERATOR TESTS
1303 *
1304 * @ingroup unitops
1305 * @{
1306 */
1307
1308
1309/*
1310 * controlled operations
1311 */
1312
1313TEST_ALL_CTRL_OPERATIONS( PauliStr, any, paulistr, nullptr );
1314TEST_ALL_CTRL_OPERATIONS( PauliGadget, any, pauligad, nullptr );
1315TEST_ALL_CTRL_OPERATIONS( CompMatr1, one, compmatr, nullptr );
1316TEST_ALL_CTRL_OPERATIONS( CompMatr2, two, compmatr, nullptr );
1317TEST_ALL_CTRL_OPERATIONS( CompMatr, any, compmatr, nullptr );
1318TEST_ALL_CTRL_OPERATIONS( DiagMatr1, one, diagmatr, nullptr );
1319TEST_ALL_CTRL_OPERATIONS( DiagMatr2, two, diagmatr, nullptr );
1320TEST_ALL_CTRL_OPERATIONS( DiagMatr, any, diagmatr, nullptr );
1321TEST_ALL_CTRL_OPERATIONS( DiagMatrPower, any, diagpower, nullptr );
1322TEST_ALL_CTRL_OPERATIONS( Hadamard, one, none, FixedMatrices::H );
1323TEST_ALL_CTRL_OPERATIONS( PauliX, one, none, FixedMatrices::X );
1324TEST_ALL_CTRL_OPERATIONS( PauliY, one, none, FixedMatrices::Y );
1325TEST_ALL_CTRL_OPERATIONS( PauliZ, one, none, FixedMatrices::Z );
1326TEST_ALL_CTRL_OPERATIONS( T, one, none, FixedMatrices::T );
1327TEST_ALL_CTRL_OPERATIONS( S, one, none, FixedMatrices::S );
1328TEST_ALL_CTRL_OPERATIONS( Swap, two, none, FixedMatrices::SWAP );
1329TEST_ALL_CTRL_OPERATIONS( SqrtSwap, two, none, FixedMatrices::sqrtSWAP );
1330TEST_ALL_CTRL_OPERATIONS( RotateX, one, scalar, ParameterisedMatrices::Rx );
1331TEST_ALL_CTRL_OPERATIONS( RotateY, one, scalar, ParameterisedMatrices::Ry );
1332TEST_ALL_CTRL_OPERATIONS( RotateZ, one, scalar, ParameterisedMatrices::Rz );
1333TEST_ALL_CTRL_OPERATIONS( RotateAroundAxis, one, axisrots, nullptr );
1334TEST_ALL_CTRL_OPERATIONS( MultiQubitNot, any, none, VariableSizeMatrices::X );
1335TEST_ALL_CTRL_OPERATIONS( PhaseGadget, any, scalar, VariableSizeParameterisedMatrices::Z );
1336
1337
1338/*
1339 * non-controlled operations with no C++ overloads
1340 */
1341
1342TEST_CASE( "applyPhaseFlip", TEST_CATEGORY_OPS ) { testOperation<zero,one,none,apply> (applyPhaseFlip, VariableSizeMatrices::PF(1)); }
1343TEST_CASE( "applyTwoQubitPhaseFlip", TEST_CATEGORY_OPS ) { testOperation<zero,two,none,apply> (applyTwoQubitPhaseFlip, VariableSizeMatrices::PF(2)); }
1344TEST_CASE( "applyPhaseShift", TEST_CATEGORY_OPS ) { testOperation<zero,one,scalar,apply>(applyPhaseShift, ParameterisedMatrices::PS ); }
1345TEST_CASE( "applyTwoQubitPhaseShift", TEST_CATEGORY_OPS ) { testOperation<zero,two,scalar,apply>(applyTwoQubitPhaseShift, ParameterisedMatrices::PS2 ); }
1346
1347
1348/*
1349 * non-controlled operations which have a C++ overload
1350 * (because they accept qubit lists which become vector),
1351 * and so which require explicit casting to resolve the
1352 * compiler ambiguity (spaghetti 4 lyf)
1353 */
1354
1355TEST_CASE( "applyMultiQubitPhaseFlip", TEST_CATEGORY_OPS ) {
1356 auto func = static_cast<void(*)(Qureg, int*, int)>(applyMultiQubitPhaseFlip);
1357 testOperation<zero,any,none,apply>(func, VariableSizeMatrices::PF);
1358}
1359
1360TEST_CASE( "applyMultiQubitPhaseShift", TEST_CATEGORY_OPS ) {
1361 auto func = static_cast<void(*)(Qureg, int*, int, qreal)>(applyMultiQubitPhaseShift);
1362 testOperation<zero,any,scalar,apply>(func, VariableSizeParameterisedMatrices::PS);
1363}
1364
1365
1366/*
1367 * operations which need custom logic
1368 */
1369
1370TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) {
1371
1372 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1373
1374 SECTION( LABEL_CORRECTNESS ) {
1375
1376 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1377 auto targs = GENERATE_TARGS( numQubits, numTargs );
1378 bool inverse = GENERATE(false, true);
1379
1380 CAPTURE( targs );
1381
1382 SECTION( LABEL_STATEVEC ) {
1383
1384 auto testFunc = [&](Qureg qureg, qvector& ref) {
1385 applyQuantumFourierTransform(qureg, targs.data(), targs.size(), inverse);
1386 ref = getDiscreteFourierTransform(ref, targs, inverse);
1387 };
1388
1389 CAPTURE(inverse);
1390 TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc);
1391 }
1392
1393 SECTION( LABEL_DENSMATR ) {
1394
1395 // prepare a random mixture
1396 auto states = getRandomOrthonormalStateVectors(numQubits, getRandomInt(1,10));
1397 auto probs = getRandomProbabilities(states.size());
1398
1399 auto testFunc = [&](Qureg qureg, qmatrix& ref) {
1400
1401 // overwrite the Qureg debug state set by caller to above mixture
1402 setQuregToReference(qureg, getMixture(states, probs));
1403 applyQuantumFourierTransform(qureg, targs.data(), targs.size(), inverse);
1404
1405 ref = getZeroMatrix(ref.size());
1406 for (size_t i=0; i<states.size(); i++) {
1407 qvector vec = getDiscreteFourierTransform(states[i], targs, inverse);
1408 ref += probs[i] * getOuterProduct(vec, vec);
1409 }
1410 };
1411
1412 CAPTURE(inverse);
1413 TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc);
1414 }
1415 }
1416
1417 SECTION( LABEL_VALIDATION ) {
1418
1419 Qureg qureg = getArbitraryCachedStatevec();
1420 int targs[] = {0, 1, 2};
1421 int numTargs = 3;
1422 bool inverse = false;
1423
1424 SECTION( "qureg uninitialised" ) {
1425
1426 Qureg badQureg = qureg;
1427 badQureg.numQubits = -1;
1428 REQUIRE_THROWS_WITH(
1429 applyQuantumFourierTransform(badQureg, targs, numTargs, inverse),
1430 ContainsSubstring("invalid Qureg")
1431 );
1432 }
1433
1434 SECTION( "invalid target qubits" ) {
1435
1436 int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large
1437 REQUIRE_THROWS_WITH(
1438 applyQuantumFourierTransform(qureg, badTargs, 3, inverse),
1439 ContainsSubstring("target")
1440 );
1441 }
1442
1443 SECTION( "duplicate target qubits" ) {
1444
1445 int dupTargs[] = {0, 1, 1};
1446 REQUIRE_THROWS_WITH(
1447 applyQuantumFourierTransform(qureg, dupTargs, 3, inverse),
1448 ContainsSubstring("duplicate")
1449 );
1450 }
1451
1452 SECTION( "invalid number of targets" ) {
1453
1454 int badNumTargs = GENERATE_COPY( -1, 0 );
1455 REQUIRE_THROWS_WITH(
1456 applyQuantumFourierTransform(qureg, targs, badNumTargs, inverse),
1457 ContainsSubstring("targets") || ContainsSubstring("target qubits")
1458 );
1459
1460 badNumTargs = qureg.numQubits+1;
1461 REQUIRE_THROWS_WITH(
1462 applyQuantumFourierTransform(qureg, targs, badNumTargs, inverse),
1463 ContainsSubstring("exceeds the number of qubits in the Qureg")
1464 );
1465 }
1466 }
1467}
1468
1469
1470TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) {
1471
1472 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1473
1474 SECTION( LABEL_CORRECTNESS ) {
1475
1476 GENERATE( range(0,10) );
1477 bool inverse = GENERATE(false, true);
1478
1479 SECTION( LABEL_STATEVEC ) {
1480
1481 auto testFunc = [&](Qureg qureg, qvector& ref) {
1482 applyFullQuantumFourierTransform(qureg, inverse);
1483 ref = getDiscreteFourierTransform(ref, inverse);
1484 };
1485
1486 CAPTURE(inverse);
1487 TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc);
1488 }
1489
1490 SECTION( LABEL_DENSMATR ) {
1491
1492 // prepare a random mixture
1493 auto states = getRandomOrthonormalStateVectors(numQubits, getRandomInt(1,10));
1494 auto probs = getRandomProbabilities(states.size());
1495
1496 auto testFunc = [&](Qureg qureg, qmatrix& ref) {
1497
1498 // overwrite the Qureg debug state set by caller to above mixture
1499 setQuregToReference(qureg, getMixture(states, probs));
1500 applyFullQuantumFourierTransform(qureg, inverse);
1501
1502 ref = getZeroMatrix(ref.size());
1503 for (size_t i=0; i<states.size(); i++) {
1504 qvector vec = getDiscreteFourierTransform(states[i], inverse);
1505 ref += probs[i] * getOuterProduct(vec, vec);
1506 }
1507 };
1508
1509 CAPTURE(inverse);
1510 TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc);
1511 }
1512 }
1513
1514 SECTION( LABEL_VALIDATION ) {
1515
1516 Qureg qureg = getArbitraryCachedStatevec();
1517 bool inverse = false;
1518
1519 SECTION( "qureg uninitialised" ) {
1520
1521 Qureg badQureg = qureg;
1522 badQureg.numQubits = -1;
1523 REQUIRE_THROWS_WITH(
1524 applyFullQuantumFourierTransform(badQureg, inverse),
1525 ContainsSubstring("invalid Qureg")
1526 );
1527 }
1528 }
1529}
1530
1531
1532TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) {
1533
1534 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1535
1536 SECTION( LABEL_CORRECTNESS ) {
1537
1538 GENERATE( range(0,10) );
1539 int target = GENERATE_COPY( range(0,numQubits) );
1540 int outcome = GENERATE( 0, 1 );
1541
1542 qmatrix projector = getProjector(outcome);
1543
1544 auto testFunc = [&](Qureg qureg, auto& ref) {
1545 applyQubitProjector(qureg, target, outcome);
1546 applyReferenceOperator(ref, {target}, projector);
1547 };
1548
1549 CAPTURE( target, outcome );
1550 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1551 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1552 }
1553
1554 SECTION( LABEL_VALIDATION ) {
1555
1556 Qureg qureg = getArbitraryCachedStatevec();
1557
1558 SECTION( "qureg uninitialised" ) {
1559
1560 Qureg badQureg = qureg;
1561 badQureg.numQubits = -1;
1562 REQUIRE_THROWS_WITH(
1563 applyQubitProjector(badQureg, 0, 0),
1564 ContainsSubstring("invalid Qureg")
1565 );
1566 }
1567
1568 SECTION( "invalid target qubit" ) {
1569
1570 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
1571 REQUIRE_THROWS_WITH(
1572 applyQubitProjector(qureg, badTarget, 0),
1573 ContainsSubstring("target")
1574 );
1575 }
1576
1577 SECTION( "invalid outcome" ) {
1578
1579 int badOutcome = GENERATE_COPY( -1, 2 );
1580 REQUIRE_THROWS_WITH(
1581 applyQubitProjector(qureg, 0, badOutcome),
1582 ContainsSubstring("outcome")
1583 );
1584 }
1585
1586 // projector does NOT validate outcome probability
1587 }
1588}
1589
1590
1591TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) {
1592
1593 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1594
1595 SECTION( LABEL_CORRECTNESS ) {
1596
1597 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1598 auto targets = GENERATE_TARGS( numQubits, numTargs );
1599 auto outcomes = getRandomOutcomes(numTargs);
1600
1601 qmatrix projector = getProjector(targets, outcomes, numQubits);
1602
1603 auto testFunc = [&](Qureg qureg, auto& ref) {
1604 applyMultiQubitProjector(qureg, targets.data(), outcomes.data(), numTargs);
1605 applyReferenceOperator(ref, projector);
1606 };
1607
1608 CAPTURE( targets, outcomes );
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 SECTION( LABEL_VALIDATION ) {
1614
1615 Qureg qureg = getArbitraryCachedStatevec();
1616 int targets[] = {0, 1, 2};
1617 int outcomes[] = {0, 1, 0};
1618 int numTargets = 3;
1619
1620 SECTION( "qureg uninitialised" ) {
1621
1622 Qureg badQureg = qureg;
1623 badQureg.numQubits = -1;
1624 REQUIRE_THROWS_WITH(
1625 applyMultiQubitProjector(badQureg, targets, outcomes, numTargets),
1626 ContainsSubstring("invalid Qureg")
1627 );
1628 }
1629
1630 SECTION( "invalid target qubits" ) {
1631
1632 int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) };
1633 REQUIRE_THROWS_WITH(
1634 applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets),
1635 ContainsSubstring("target")
1636 );
1637 }
1638
1639 SECTION( "duplicate target qubits" ) {
1640
1641 int dupTargets[] = {0, 1, 1};
1642 REQUIRE_THROWS_WITH(
1643 applyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets),
1644 ContainsSubstring("duplicate")
1645 );
1646 }
1647
1648 SECTION( "invalid number of targets" ) {
1649
1650 int badNumTargs = GENERATE( 0, -1 );
1651 REQUIRE_THROWS_WITH(
1652 applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
1653 ContainsSubstring("targets")
1654 );
1655
1656 badNumTargs = qureg.numQubits + 1;
1657 REQUIRE_THROWS_WITH(
1658 applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
1659 ContainsSubstring("exceeds the number of qubits in the Qureg")
1660 );
1661 }
1662
1663 SECTION( "invalid outcomes" ) {
1664
1665 int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) };
1666 REQUIRE_THROWS_WITH(
1667 applyMultiQubitProjector(qureg, targets, badOutcomes, numTargets),
1668 ContainsSubstring("outcome")
1669 );
1670 }
1671
1672 SECTION( "targets mismatch outcomes (C++ only)" ) {
1673
1674 REQUIRE_THROWS_WITH(
1675 applyMultiQubitProjector(qureg, {0,1,2}, {0,1}),
1676 ContainsSubstring("outcomes") && ContainsSubstring("inconsistent with the given number of qubits")
1677 );
1678 }
1679
1680 // projector does NOT validate outcome probability
1681 }
1682}
1683
1684
1685TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) {
1686
1687 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1688
1689 SECTION( LABEL_CORRECTNESS ) {
1690
1691 GENERATE( range(0,10) );
1692 int target = GENERATE_COPY( range(0,numQubits) );
1693 int outcome = GENERATE( 0, 1 );
1694
1695 qmatrix projector = getProjector(outcome);
1696
1697 auto testFunc = [&](Qureg qureg, auto& ref) {
1698
1699 // overwrite caller's setting of initDebugState, since
1700 // that precludes outcomes=|0><0| due to zero-probability
1701 setToRandomState(ref);
1702 setQuregToReference(qureg, ref);
1703
1704 // compare the probabilities...
1705 qreal apiProb = applyForcedQubitMeasurement(qureg, target, outcome);
1706 qreal refProb = getReferenceProbability(ref, {target}, {outcome});
1707 REQUIRE_AGREE( apiProb, refProb );
1708
1709 // and the post-projection states (caller calls subsequent REQUIRE_AGREE)
1710 applyReferenceOperator(ref, {target}, projector);
1711 ref /= (qureg.isDensityMatrix)?
1712 refProb : std::sqrt(refProb);
1713 };
1714
1715 CAPTURE( target, outcome );
1716 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1717 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1718 }
1719
1720 SECTION( LABEL_VALIDATION ) {
1721
1722 Qureg qureg = getArbitraryCachedStatevec();
1723
1724 // below validation tests assume qubit 0 can collapse to either outcome
1725 // (which does not require normalisation; qureg can be in the debug state)
1726 initDebugState(qureg);
1727 REQUIRE( calcProbOfQubitOutcome(qureg, 0, 0) > getQuESTValidationEpsilon() );
1728 REQUIRE( calcProbOfQubitOutcome(qureg, 0, 1) > getQuESTValidationEpsilon() );
1729
1730 SECTION( "qureg uninitialised" ) {
1731
1732 Qureg badQureg = qureg;
1733 badQureg.numQubits = -1;
1734 REQUIRE_THROWS_WITH(
1735 applyForcedQubitMeasurement(badQureg, 0, 0),
1736 ContainsSubstring("invalid Qureg")
1737 );
1738 }
1739
1740 SECTION( "invalid target qubit" ) {
1741
1742 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
1743 REQUIRE_THROWS_WITH(
1744 applyForcedQubitMeasurement(qureg, badTarget, 0),
1745 ContainsSubstring("target")
1746 );
1747 }
1748
1749 SECTION( "invalid outcome" ) {
1750
1751 int badOutcome = GENERATE_COPY( -1, 2 );
1752 REQUIRE_THROWS_WITH(
1753 applyForcedQubitMeasurement(qureg, 0, badOutcome),
1754 ContainsSubstring("outcome")
1755 );
1756 }
1757
1758 SECTION( "improbable outcome" ) {
1759
1760 // precisely zero probability outcome
1761 initZeroState(qureg);
1762 int badOutcome = 1; // impossible
1763 REQUIRE_THROWS_WITH(
1764 applyForcedQubitMeasurement(qureg, 0, badOutcome),
1765 ContainsSubstring("impossibly unlikely")
1766 );
1767
1768 // outcome of non-zero probability smaller than epsilon
1769 qreal badTheta = 1E-8;
1770 applyRotateX(qureg, 0, badTheta); // causes prob(1) = sin^2(theta) ~ 2.5E-17
1771 REQUIRE_THROWS_WITH(
1772 applyForcedQubitMeasurement(qureg, 0, badOutcome),
1773 ContainsSubstring("impossibly unlikely")
1774 );
1775
1776 // confirm that >epsilon probability is fine
1777 initZeroState(qureg);
1778 qreal goodTheta = 0.1;
1779 applyRotateX(qureg, 0, goodTheta);
1780 REQUIRE(
1781 calcProbOfQubitOutcome(qureg, 0, badOutcome) > getQuESTValidationEpsilon()
1782 );
1783 REQUIRE_NOTHROW(
1784 applyForcedQubitMeasurement(qureg, 0, badOutcome)
1785 );
1786
1787 // restore qureg state
1788 initDebugState(qureg);
1789 }
1790 }
1791}
1792
1793
1794TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) {
1795
1796 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1797
1798 SECTION( LABEL_CORRECTNESS ) {
1799
1800 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1801 auto targets = GENERATE_TARGS( numQubits, numTargs );
1802 auto outcomes = getRandomOutcomes(numTargs);
1803
1804 qmatrix projector = getProjector(targets, outcomes, numQubits);
1805
1806 // this test may randomly request a measurement outcome which
1807 // is illegally unlikely, triggering validation; we merely
1808 // disable such validation and hope divergences don't break the test!
1810
1811 auto testFunc = [&](Qureg qureg, auto& ref) {
1812
1813 // overwrite caller's setting of initDebugState, since
1814 // that precludes outcomes=|0><0| due to zero-probability
1815 setToRandomState(ref);
1816 setQuregToReference(qureg, ref);
1817
1818 // compare the probabilities...
1819 qreal apiProb = applyForcedMultiQubitMeasurement(qureg, targets.data(), outcomes.data(), numTargs);
1820 qreal refProb = getReferenceProbability(ref, targets, outcomes);
1821 REQUIRE_AGREE( apiProb, refProb );
1822
1823 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
1824 applyReferenceOperator(ref, projector);
1825 ref /= (qureg.isDensityMatrix)?
1826 refProb : std::sqrt(refProb);
1827 };
1828
1829 CAPTURE( targets, outcomes );
1830 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1831 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1832
1834 }
1835
1836 SECTION( LABEL_VALIDATION ) {
1837
1838 Qureg qureg = getArbitraryCachedStatevec();
1839 int targets[] = {0, 1, 2};
1840 int outcomes[] = {0, 1, 0};
1841 int numTargets = 3;
1842
1843 // below validation tests assume the above parameters are valid (not impossibly unlikely)
1844 initDebugState(qureg);
1845 REQUIRE( calcProbOfMultiQubitOutcome(qureg, targets, outcomes, numTargets) > getQuESTValidationEpsilon() );
1846
1847 SECTION( "qureg uninitialised" ) {
1848
1849 Qureg badQureg = qureg;
1850 badQureg.numQubits = -1;
1851 REQUIRE_THROWS_WITH(
1852 applyForcedMultiQubitMeasurement(badQureg, targets, outcomes, numTargets),
1853 ContainsSubstring("invalid Qureg")
1854 );
1855 }
1856
1857 SECTION( "invalid target qubits" ) {
1858
1859 int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )};
1860 REQUIRE_THROWS_WITH(
1861 applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets),
1862 ContainsSubstring("target")
1863 );
1864 }
1865
1866 SECTION( "duplicate target qubits" ) {
1867
1868 int dupTargets[] = {0, 1, 1};
1869 REQUIRE_THROWS_WITH(
1870 applyForcedMultiQubitMeasurement(qureg, dupTargets, outcomes, numTargets),
1871 ContainsSubstring("duplicate")
1872 );
1873 }
1874
1875 SECTION( "invalid number of targets" ) {
1876
1877 int badNumTargs = GENERATE( -1, 0 );
1878 REQUIRE_THROWS_WITH(
1879 applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs),
1880 ContainsSubstring("targets")
1881 );
1882
1883 badNumTargs = qureg.numQubits + 1;
1884 REQUIRE_THROWS_WITH(
1885 applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs),
1886 ContainsSubstring("exceeds the number of qubits in the Qureg")
1887 );
1888 }
1889
1890 SECTION( "invalid outcomes" ) {
1891
1892 int badOutcomes[] = {0, 1, GENERATE( -1, 2 )};
1893 REQUIRE_THROWS_WITH(
1894 applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets),
1895 ContainsSubstring("outcome")
1896 );
1897 }
1898
1899 SECTION( "improbable outcomes" ) {
1900
1901 // impossible outcome
1902 initZeroState(qureg);
1903 int badOutcomes[] = {0, 0, 1};
1904 REQUIRE_THROWS_WITH(
1905 applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets),
1906 ContainsSubstring("impossibly unlikely")
1907 );
1908
1909 // outcome of non-zero probability smaller than epsilon
1910 qreal badTheta = 1E-8;
1911 applyRotateX(qureg, targets[2], badTheta);
1912 REQUIRE_THROWS_WITH(
1913 applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets),
1914 ContainsSubstring("impossibly unlikely")
1915 );
1916
1917 // confirm that >epsilon probability is fine
1918 initZeroState(qureg);
1919 qreal goodTheta = 0.1;
1920 applyRotateX(qureg, targets[2], goodTheta);
1921 int goodOutcomes[] = {0, 0, 1};
1922 REQUIRE(
1923 calcProbOfMultiQubitOutcome(qureg, targets, goodOutcomes, numTargets) > getQuESTValidationEpsilon()
1924 );
1925 REQUIRE_NOTHROW(
1926 applyForcedMultiQubitMeasurement(qureg, targets, goodOutcomes, numTargets)
1927 );
1928
1929 // restore qureg state
1930 initDebugState(qureg);
1931 }
1932
1933 SECTION( "targets mismatch outcomes (C++ only)") {
1934
1935 REQUIRE_THROWS_WITH(
1936 applyForcedMultiQubitMeasurement(qureg, {0,1}, {0,1,1}),
1937 ContainsSubstring("inconsistent")
1938 );
1939 }
1940 }
1941}
1942
1943
1944TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) {
1945
1946 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
1947
1948 SECTION( LABEL_CORRECTNESS ) {
1949
1950 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
1951 auto targets = GENERATE_TARGS( numQubits, numTargs );
1952
1953 auto testFunc = [&](Qureg qureg, auto& ref) {
1954
1955 // overwrite caller's setting of initDebugState, since
1956 // sampling requires the outcome probs are normalised
1957 setToRandomState(ref);
1958 setQuregToReference(qureg, ref);
1959
1960 // the output API state...
1961 qindex apiOut = applyMultiQubitMeasurement(qureg, targets.data(), numTargs);
1962
1963 // informs the projector which determines the post-measurement reference
1964 auto apiOutBits = getBits(apiOut, numTargs);
1965 qmatrix projector = getProjector(targets, apiOutBits, numQubits);
1966 applyReferenceOperator(ref, projector);
1967 qreal refProb = getReferenceProbability(ref, targets, apiOutBits);
1968 ref /= (qureg.isDensityMatrix)?
1969 refProb : std::sqrt(refProb);
1970 };
1971
1972 CAPTURE( targets );
1973 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
1974 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
1975 }
1976
1977 SECTION( LABEL_VALIDATION ) {
1978
1979 Qureg qureg = getArbitraryCachedStatevec();
1980 int targets[] = {0, 1, 2};
1981 int numTargets = 3;
1982
1983 SECTION( "qureg uninitialised" ) {
1984
1985 Qureg badQureg = qureg;
1986 badQureg.numQubits = -1;
1987 REQUIRE_THROWS_WITH(
1988 applyMultiQubitMeasurement(badQureg, targets, numTargets),
1989 ContainsSubstring("invalid Qureg")
1990 );
1991 }
1992
1993 SECTION( "invalid target qubits" ) {
1994
1995 int badTargets[] = {0, 1, GENERATE_COPY(-1, qureg.numQubits)};
1996 REQUIRE_THROWS_WITH(
1997 applyMultiQubitMeasurement(qureg, badTargets, numTargets),
1998 ContainsSubstring("target")
1999 );
2000 }
2001
2002 SECTION( "duplicate target qubits" ) {
2003
2004 int dupTargets[] = {0, 1, 1};
2005 REQUIRE_THROWS_WITH(
2006 applyMultiQubitMeasurement(qureg, dupTargets, numTargets),
2007 ContainsSubstring("duplicate")
2008 );
2009 }
2010
2011 SECTION( "invalid number of targets" ) {
2012
2013 int badNumTargs = GENERATE( -1, 0 );
2014 REQUIRE_THROWS_WITH(
2015 applyMultiQubitMeasurement(qureg, targets, badNumTargs),
2016 ContainsSubstring("targets")
2017 );
2018
2019 badNumTargs = qureg.numQubits + 1;
2020 REQUIRE_THROWS_WITH(
2021 applyMultiQubitMeasurement(qureg, targets, badNumTargs),
2022 ContainsSubstring("exceeds the number of qubits in the Qureg")
2023 );
2024 }
2025 }
2026}
2027
2028
2029TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) {
2030
2031 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2032
2033 SECTION( LABEL_CORRECTNESS ) {
2034
2035 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
2036 auto targets = GENERATE_TARGS( numQubits, numTargs );
2037
2038 auto testFunc = [&](Qureg qureg, auto& ref) {
2039
2040 // overwrite caller's setting of initDebugState, since
2041 // sampling requires the outcome probs are normalised
2042 setToRandomState(ref);
2043 setQuregToReference(qureg, ref);
2044
2045 // compare the measurement probability...
2046 qreal apiProb = -1;
2047 qindex apiOut = applyMultiQubitMeasurementAndGetProb(qureg, targets.data(), numTargs, &apiProb);
2048 auto apiOutBits = getBits(apiOut, numTargs);
2049 qreal refProb = getReferenceProbability(ref, targets, apiOutBits);
2050 REQUIRE_AGREE( apiProb, refProb );
2051
2052 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
2053 qmatrix projector = getProjector(targets, apiOutBits, numQubits);
2054 applyReferenceOperator(ref, projector);
2055 ref /= (qureg.isDensityMatrix)?
2056 refProb : std::sqrt(refProb);
2057 };
2058
2059 CAPTURE( targets );
2060 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2061 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2062 }
2063
2064 SECTION( LABEL_VALIDATION ) {
2065
2066 Qureg qureg = getArbitraryCachedStatevec();
2067 int targets[] = {0, 1, 2};
2068 int numTargets = 3;
2069 qreal outProb = 0;
2070
2071 SECTION( "qureg uninitialised" ) {
2072
2073 Qureg badQureg = qureg;
2074 badQureg.numQubits = -1;
2075 REQUIRE_THROWS_WITH(
2076 applyMultiQubitMeasurementAndGetProb(badQureg, targets, numTargets, nullptr),
2077 ContainsSubstring("invalid Qureg")
2078 );
2079 }
2080
2081 SECTION( "invalid target qubits" ) {
2082
2083 int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )};
2084 REQUIRE_THROWS_WITH(
2085 applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &outProb),
2086 ContainsSubstring("target")
2087 );
2088 }
2089
2090 SECTION( "duplicate target qubits" ) {
2091
2092 int dupTargets[] = {0, 1, 1};
2093 REQUIRE_THROWS_WITH(
2094 applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &outProb),
2095 ContainsSubstring("duplicate")
2096 );
2097 }
2098
2099 SECTION( "invalid number of targets" ) {
2100
2101 int badNumTargs = GENERATE( -1, 0 );
2102 REQUIRE_THROWS_WITH(
2103 applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb),
2104 ContainsSubstring("targets")
2105 );
2106
2107 badNumTargs = qureg.numQubits + 1;
2108 REQUIRE_THROWS_WITH(
2109 applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb),
2110 ContainsSubstring("exceeds the number of qubits in the Qureg")
2111 );
2112 }
2113 }
2114}
2115
2116
2117TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) {
2118
2119 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2120
2121 SECTION( LABEL_CORRECTNESS ) {
2122
2123 GENERATE( range(0,10) );
2124 int target = GENERATE_COPY( range(0,numQubits) );
2125
2126 auto testFunc = [&](Qureg qureg, auto& ref) {
2127
2128 // overwrite caller's setting of initDebugState, since
2129 // sampling requires the outcome probs are normalised
2130 setToRandomState(ref);
2131 setQuregToReference(qureg, ref);
2132
2133 // the output API state...
2134 int apiOut = applyQubitMeasurement(qureg, target);
2135
2136 // informs the projector which determines the post-measurement reference
2137 qmatrix projector = getProjector(apiOut);
2138 applyReferenceOperator(ref, {target}, projector);
2139 qreal refProb = getReferenceProbability(ref, {target}, {apiOut});
2140 ref /= (qureg.isDensityMatrix)?
2141 refProb : std::sqrt(refProb);
2142 };
2143
2144 CAPTURE( target );
2145 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2146 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2147 }
2148
2149 SECTION( LABEL_VALIDATION ) {
2150
2151 Qureg qureg = getArbitraryCachedStatevec();
2152
2153 SECTION( "qureg uninitialised" ) {
2154
2155 Qureg badQureg = qureg;
2156 badQureg.numQubits = -1;
2157 REQUIRE_THROWS_WITH(
2158 applyQubitMeasurement(badQureg, 0),
2159 ContainsSubstring("invalid Qureg")
2160 );
2161 }
2162
2163 SECTION( "invalid target qubit" ) {
2164
2165 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
2166 REQUIRE_THROWS_WITH(
2167 applyQubitMeasurement(qureg, badTarget),
2168 ContainsSubstring("target")
2169 );
2170 }
2171 }
2172}
2173
2174
2175TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) {
2176
2177 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2178
2179 SECTION( LABEL_CORRECTNESS ) {
2180
2181 GENERATE( range(0,10) );
2182 int target = GENERATE_COPY( range(0,numQubits) );
2183
2184 auto testFunc = [&](Qureg qureg, auto& ref) {
2185
2186 // overwrite caller's setting of initDebugState, since
2187 // sampling requires the outcome probs are normalised
2188 setToRandomState(ref);
2189 setQuregToReference(qureg, ref);
2190
2191 // compare the measurement probability...
2192 qreal apiProb = -1;
2193 int apiOut = applyQubitMeasurementAndGetProb(qureg, target, &apiProb);
2194 qreal refProb = getReferenceProbability(ref, {target}, {apiOut});
2195 REQUIRE_AGREE( apiProb, refProb );
2196
2197 // and the post-measurement states (caller calls subsequent REQUIRE_AGREE)
2198 qmatrix projector = getProjector(apiOut);
2199 applyReferenceOperator(ref, {target}, projector);
2200 ref /= (qureg.isDensityMatrix)?
2201 refProb : std::sqrt(refProb);
2202 };
2203
2204 CAPTURE( target );
2205 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2206 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2207 }
2208
2209 SECTION( LABEL_VALIDATION ) {
2210
2211 Qureg qureg = getArbitraryCachedStatevec();
2212 qreal outProb = 0;
2213
2214 SECTION( "qureg uninitialised" ) {
2215
2216 Qureg badQureg = qureg;
2217 badQureg.numQubits = -1;
2218 REQUIRE_THROWS_WITH(
2219 applyQubitMeasurementAndGetProb(badQureg, 0, &outProb),
2220 ContainsSubstring("invalid Qureg")
2221 );
2222 }
2223
2224 SECTION( "invalid target qubit" ) {
2225
2226 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
2227 REQUIRE_THROWS_WITH(
2228 applyQubitMeasurementAndGetProb(qureg, badTarget, &outProb),
2229 ContainsSubstring("target")
2230 );
2231 }
2232 }
2233}
2234
2235
2236TEST_CASE( "applyFullStateDiagMatr", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_TAG ) {
2237
2238 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2239
2240 auto cachedMatrs = getCachedFullStateDiagMatrs();
2241
2242 SECTION( LABEL_CORRECTNESS ) {
2243
2244 qmatrix refMatr = getRandomDiagonalUnitary(numQubits);
2245 auto apiFunc = applyFullStateDiagMatr;
2246
2247 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2248
2249 SECTION( LABEL_STATEVEC ) {
2250
2251 auto refFunc = [&] (qvector& state, qmatrix matr) { applyReferenceOperator(state, matr); };
2252
2253 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
2254 }
2255
2256 SECTION( LABEL_DENSMATR ) {
2257
2258 auto refFunc = [&] (qmatrix& state, qmatrix matr) { applyReferenceOperator(state, matr); };
2259
2260 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2261 }
2262 }
2263
2264 /// @todo input validation
2265}
2266
2267
2268TEST_CASE( "applyFullStateDiagMatrPower", TEST_CATEGORY_OPS LABEL_MIXED_DEPLOY_TAG ) {
2269
2270 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2271
2272 auto cachedMatrs = getCachedFullStateDiagMatrs();
2273
2274 SECTION( LABEL_CORRECTNESS ) {
2275
2276 qmatrix refMatr = getRandomDiagonalUnitary(numQubits);
2277
2278 // supplying a complex exponent requires disabling
2279 // numerical validation to relax unitarity
2280 bool testRealExp = GENERATE( true, false );
2281 qcomp exponent = (testRealExp)?
2282 qcomp(getRandomReal(-2, 2), 0):
2284
2285 auto apiFunc = [&](Qureg qureg, FullStateDiagMatr matr) {
2286 return applyFullStateDiagMatrPower(qureg, matr, exponent);
2287 };
2288
2289 CAPTURE( exponent );
2290
2291 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2292
2293 if (!testRealExp)
2295
2296 SECTION( LABEL_STATEVEC ) {
2297
2298 auto refFunc = [&] (qvector& state, qmatrix matr) {
2299 matr = getPowerOfDiagonalMatrix(matr, exponent);
2300 applyReferenceOperator(state, matr);
2301 };
2302
2303 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
2304 }
2305
2306 SECTION( LABEL_DENSMATR ) {
2307
2308 auto refFunc = [&] (qmatrix& state, qmatrix matr) {
2309 matr = getPowerOfDiagonalMatrix(matr, exponent);
2310 applyReferenceOperator(state, matr);
2311 };
2312
2313 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2314 }
2315
2317 }
2318
2319 /// @todo input validation
2320}
2321
2322
2323TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) {
2324
2325 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2326
2327 SECTION( LABEL_CORRECTNESS ) {
2328
2329 // prepare a random Pauli string and angle
2330 int numTargs = GENERATE_COPY( range(1, numQubits+1) );
2331 auto targs = GENERATE_TARGS( numQubits, numTargs );
2332 PauliStr str = getRandomPauliStr(targs);
2333 qcomp angle = getRandomComplex();
2334
2335 // prepare the corresponding reference matrix exp(-i angle pauli)
2336 auto matrRef = getExponentialOfPauliMatrix(angle, getMatrix(str, numQubits));
2337
2338 auto testFunc = [&](Qureg qureg, auto& stateRef) {
2339 applyNonUnitaryPauliGadget(qureg, str, angle);
2340 applyReferenceOperator(stateRef, matrRef);
2341 };
2342
2343 CAPTURE( targs, angle );
2344 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2345 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2346 }
2347
2348 SECTION( LABEL_VALIDATION ) {
2349
2350 Qureg qureg = getArbitraryCachedStatevec();
2351 PauliStr str = getPauliStr("XY", {0, 1});
2352
2353 SECTION( "qureg uninitialised" ) {
2354
2355 Qureg badQureg = qureg;
2356 badQureg.numQubits = -1;
2357 REQUIRE_THROWS_WITH(
2358 applyNonUnitaryPauliGadget(badQureg, str, qcomp(0.5, 0)),
2359 ContainsSubstring("invalid Qureg")
2360 );
2361 }
2362
2363 /// @todo remaining input validation
2364 }
2365}
2366
2367
2368/** @} (end defgroup) */
2369
2370
2371
2372/**
2373 * OPERATOR TESTS
2374 *
2375 * @ingroup unitmult
2376 * @{
2377 */
2378
2379
2380TEST_CASE( "leftapplySwap", TEST_CATEGORY_MULT ) { testOperation<zero,two,none,leftapply>(leftapplySwap, FixedMatrices::SWAP); }
2381TEST_CASE( "leftapplyPauliX", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,leftapply>(leftapplyPauliX, FixedMatrices::X); }
2382TEST_CASE( "leftapplyPauliY", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,leftapply>(leftapplyPauliY, FixedMatrices::Y); }
2383TEST_CASE( "leftapplyPauliZ", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,leftapply>(leftapplyPauliZ, FixedMatrices::Z); }
2384TEST_CASE( "leftapplyPauliStr", TEST_CATEGORY_MULT ) { testOperation<zero,any,paulistr,leftapply>(leftapplyPauliStr, nullptr); }
2385TEST_CASE( "leftapplyPauliGadget", TEST_CATEGORY_MULT ) { testOperation<zero,any,pauligad,leftapply>(leftapplyPauliGadget, nullptr); }
2386TEST_CASE( "leftapplyCompMatr1", TEST_CATEGORY_MULT ) { testOperation<zero,one,compmatr,leftapply>(leftapplyCompMatr1, nullptr); }
2387TEST_CASE( "leftapplyCompMatr2", TEST_CATEGORY_MULT ) { testOperation<zero,two,compmatr,leftapply>(leftapplyCompMatr2, nullptr); }
2388TEST_CASE( "leftapplyDiagMatr1", TEST_CATEGORY_MULT ) { testOperation<zero,one,diagmatr,leftapply>(leftapplyDiagMatr1, nullptr); }
2389TEST_CASE( "leftapplyDiagMatr2", TEST_CATEGORY_MULT ) { testOperation<zero,two,diagmatr,leftapply>(leftapplyDiagMatr2, nullptr); }
2390
2391TEST_CASE( "rightapplySwap", TEST_CATEGORY_MULT ) { testOperation<zero,two,none,rightapply>(rightapplySwap, FixedMatrices::SWAP); }
2392TEST_CASE( "rightapplyPauliX", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,rightapply>(rightapplyPauliX, FixedMatrices::X); }
2393TEST_CASE( "rightapplyPauliY", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,rightapply>(rightapplyPauliY, FixedMatrices::Y); }
2394TEST_CASE( "rightapplyPauliZ", TEST_CATEGORY_MULT ) { testOperation<zero,one,none,rightapply>(rightapplyPauliZ, FixedMatrices::Z); }
2395TEST_CASE( "rightapplyPauliStr", TEST_CATEGORY_MULT ) { testOperation<zero,any,paulistr,rightapply>(rightapplyPauliStr, nullptr); }
2396TEST_CASE( "rightapplyPauliGadget", TEST_CATEGORY_MULT ) { testOperation<zero,any,pauligad,rightapply>(rightapplyPauliGadget, nullptr); }
2397TEST_CASE( "rightapplyCompMatr1", TEST_CATEGORY_MULT ) { testOperation<zero,one,compmatr,rightapply>(rightapplyCompMatr1, nullptr); }
2398TEST_CASE( "rightapplyCompMatr2", TEST_CATEGORY_MULT ) { testOperation<zero,two,compmatr,rightapply>(rightapplyCompMatr2, nullptr); }
2399TEST_CASE( "rightapplyDiagMatr1", TEST_CATEGORY_MULT ) { testOperation<zero,one,diagmatr,rightapply>(rightapplyDiagMatr1, nullptr); }
2400TEST_CASE( "rightapplyDiagMatr2", TEST_CATEGORY_MULT ) { testOperation<zero,two,diagmatr,rightapply>(rightapplyDiagMatr2, nullptr); }
2401
2402
2403/*
2404 * C++ overloads which accept qubit lists as vectors
2405 * and so which require explicit casting to resolve the
2406 * compiler ambiguity (spaghetti 4 lyf)
2407 */
2408
2409
2410TEST_CASE( "leftapplyCompMatr", TEST_CATEGORY_MULT ) {
2411 auto func = static_cast<void(*)(Qureg, int*, int, CompMatr)>(leftapplyCompMatr);
2412 testOperation<zero,any,compmatr,leftapply>(func, nullptr);
2413}
2414
2415TEST_CASE( "leftapplyDiagMatr", TEST_CATEGORY_MULT ) {
2416 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr)>(leftapplyDiagMatr);
2417 testOperation<zero,any,diagmatr,leftapply>(func, nullptr);
2418}
2419
2420TEST_CASE( "leftapplyDiagMatrPower", TEST_CATEGORY_MULT ) {
2421 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr, qcomp)>(leftapplyDiagMatrPower);
2422 testOperation<zero,any,diagpower,leftapply>(func, nullptr);
2423}
2424
2425TEST_CASE( "leftapplyMultiQubitNot", TEST_CATEGORY_MULT ) {
2426 auto func = static_cast<void(*)(Qureg, int*, int)>(leftapplyMultiQubitNot);
2427 testOperation<zero,any,none,leftapply>(func, VariableSizeMatrices::X);
2428}
2429
2430TEST_CASE( "leftapplyPhaseGadget", TEST_CATEGORY_MULT ) {
2431 auto func = static_cast<void(*)(Qureg, int*, int, qreal)>(leftapplyPhaseGadget);
2432 testOperation<zero,any,scalar,leftapply>(func, VariableSizeParameterisedMatrices::Z);
2433}
2434
2435
2436TEST_CASE( "rightapplyCompMatr", TEST_CATEGORY_MULT ) {
2437 auto func = static_cast<void(*)(Qureg, int*, int, CompMatr)>(rightapplyCompMatr);
2438 testOperation<zero,any,compmatr,rightapply>(func, nullptr);
2439}
2440
2441TEST_CASE( "rightapplyDiagMatr", TEST_CATEGORY_MULT ) {
2442 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr)>(rightapplyDiagMatr);
2443 testOperation<zero,any,diagmatr,rightapply>(func, nullptr);
2444}
2445
2446TEST_CASE( "rightapplyDiagMatrPower", TEST_CATEGORY_MULT ) {
2447 auto func = static_cast<void(*)(Qureg, int*, int, DiagMatr, qcomp)>(rightapplyDiagMatrPower);
2448 testOperation<zero,any,diagpower,rightapply>(func, nullptr);
2449}
2450
2451TEST_CASE( "rightapplyMultiQubitNot", TEST_CATEGORY_MULT ) {
2452 auto func = static_cast<void(*)(Qureg, int*, int)>(rightapplyMultiQubitNot);
2453 testOperation<zero,any,none,rightapply>(func, VariableSizeMatrices::X);
2454}
2455
2456TEST_CASE( "rightapplyPhaseGadget", TEST_CATEGORY_MULT ) {
2457 auto func = static_cast<void(*)(Qureg, int*, int, qreal)>(rightapplyPhaseGadget);
2458 testOperation<zero,any,scalar,rightapply>(func, VariableSizeParameterisedMatrices::Z);
2459}
2460
2461
2462/*
2463 * operations which need custom logic
2464 */
2465
2466
2467TEST_CASE( "leftapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2468
2469 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2470
2471 auto cachedMatrs = getCachedFullStateDiagMatrs();
2472
2473 SECTION( LABEL_CORRECTNESS ) {
2474
2475 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
2476 auto apiFunc = leftapplyFullStateDiagMatr;
2477
2478 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2479
2480 SECTION( LABEL_STATEVEC ) {
2481
2482 auto refFunc = [&] (qvector& state, qmatrix matr) { leftapplyReferenceOperator(state, matr); };
2483
2484 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
2485 }
2486
2487 SECTION( LABEL_DENSMATR ) {
2488
2489 auto refFunc = [&] (qmatrix& state, qmatrix matr) { leftapplyReferenceOperator(state, matr); };
2490
2491 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2492 }
2493 }
2494
2495 /// @todo input validation
2496}
2497
2498
2499TEST_CASE( "rightapplyFullStateDiagMatr", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2500
2501 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2502
2503 auto cachedMatrs = getCachedFullStateDiagMatrs();
2504
2505 SECTION( LABEL_CORRECTNESS ) {
2506
2507 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
2508 auto apiFunc = rightapplyFullStateDiagMatr;
2509
2510 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2511
2512 SECTION( LABEL_DENSMATR ) {
2513
2514 auto refFunc = [&] (qmatrix& state, qmatrix matr) { rightapplyReferenceOperator(state, matr); };
2515
2516 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2517 }
2518 }
2519
2520 /// @todo input validation
2521}
2522
2523
2524TEST_CASE( "leftapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2525
2526 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2527
2528 auto cachedMatrs = getCachedFullStateDiagMatrs();
2529
2530 SECTION( LABEL_CORRECTNESS ) {
2531
2532 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
2533 qcomp exponent = getRandomComplex();
2534
2535 auto apiFunc = [&](Qureg qureg, FullStateDiagMatr matr) {
2536 return leftapplyFullStateDiagMatrPower(qureg, matr, exponent);
2537 };
2538
2539 CAPTURE( exponent );
2540
2541 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2542
2543 SECTION( LABEL_STATEVEC ) {
2544
2545 auto refFunc = [&] (qvector& state, qmatrix matr) {
2546 matr = getPowerOfDiagonalMatrix(matr, exponent);
2547 leftapplyReferenceOperator(state, matr);
2548 };
2549
2550 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedSV, cachedMatrs, apiFunc, refSV, refMatr, refFunc);
2551 }
2552
2553 SECTION( LABEL_DENSMATR ) {
2554
2555 auto refFunc = [&] (qmatrix& state, qmatrix matr) {
2556 matr = getPowerOfDiagonalMatrix(matr, exponent);
2557 leftapplyReferenceOperator(state, matr);
2558 };
2559
2560 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2561 }
2562 }
2563
2564 /// @todo input validation
2565}
2566
2567
2568TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2569
2570 PREPARE_TEST( numQubits, cachedSV, cachedDM, refSV, refDM );
2571
2572 auto cachedMatrs = getCachedFullStateDiagMatrs();
2573
2574 SECTION( LABEL_CORRECTNESS ) {
2575
2576 qmatrix refMatr = getRandomDiagonalMatrix(getPow2(numQubits));
2577 qcomp exponent = getRandomComplex();
2578
2579 auto apiFunc = [&](Qureg qureg, FullStateDiagMatr matr) {
2580 return rightapplyFullStateDiagMatrPower(qureg, matr, exponent);
2581 };
2582
2583 CAPTURE( exponent );
2584
2585 GENERATE( range(0, getNumTestedMixedDeploymentRepetitions()) );
2586
2587 SECTION( LABEL_DENSMATR ) {
2588
2589 auto refFunc = [&] (qmatrix& state, qmatrix matr) {
2590 matr = getPowerOfDiagonalMatrix(matr, exponent);
2591 rightapplyReferenceOperator(state, matr);
2592 };
2593
2594 TEST_ON_CACHED_QUREG_AND_MATRIX( cachedDM, cachedMatrs, apiFunc, refDM, refMatr, refFunc);
2595 }
2596 }
2597
2598 /// @todo input validation
2599}
2600
2601
2602TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) {
2603
2604 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2605
2606 SECTION( LABEL_CORRECTNESS ) {
2607
2608 GENERATE( range(0,10) );
2609 int target = GENERATE_COPY( range(0,numQubits) );
2610 int outcome = GENERATE( 0, 1 );
2611
2612 qmatrix projector = getProjector(outcome);
2613
2614 auto testFunc = [&](Qureg qureg, auto& ref) {
2615 leftapplyQubitProjector(qureg, target, outcome);
2616 leftapplyReferenceOperator(ref, {target}, projector);
2617 };
2618
2619 CAPTURE( target, outcome );
2620 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2621 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2622 }
2623
2624 SECTION( LABEL_VALIDATION ) {
2625
2626 Qureg qureg = getArbitraryCachedStatevec();
2627
2628 SECTION( "qureg uninitialised" ) {
2629
2630 Qureg badQureg = qureg;
2631 badQureg.numQubits = -1;
2632 REQUIRE_THROWS_WITH(
2633 leftapplyQubitProjector(badQureg, 0, 0),
2634 ContainsSubstring("invalid Qureg")
2635 );
2636 }
2637
2638 SECTION( "invalid target qubit" ) {
2639
2640 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
2641 REQUIRE_THROWS_WITH(
2642 leftapplyQubitProjector(qureg, badTarget, 0),
2643 ContainsSubstring("target")
2644 );
2645 }
2646
2647 SECTION( "invalid outcome" ) {
2648
2649 int badOutcome = GENERATE_COPY( -1, 2 );
2650 REQUIRE_THROWS_WITH(
2651 leftapplyQubitProjector(qureg, 0, badOutcome),
2652 ContainsSubstring("outcome")
2653 );
2654 }
2655
2656 // projector does NOT validate outcome probability
2657 }
2658}
2659
2660
2661TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) {
2662
2663 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2664
2665 SECTION( LABEL_CORRECTNESS ) {
2666
2667 GENERATE( range(0,10) );
2668 int target = GENERATE_COPY( range(0,numQubits) );
2669 int outcome = GENERATE( 0, 1 );
2670
2671 qmatrix projector = getProjector(outcome);
2672
2673 auto testFunc = [&](Qureg qureg, auto& ref) {
2674 rightapplyQubitProjector(qureg, target, outcome);
2675 rightapplyReferenceOperator(ref, {target}, projector);
2676 };
2677
2678 CAPTURE( target, outcome );
2679 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2680 }
2681
2682 SECTION( LABEL_VALIDATION ) {
2683
2684 Qureg qureg = getArbitraryCachedDensmatr();
2685
2686 SECTION( "qureg uninitialised" ) {
2687
2688 Qureg badQureg = qureg;
2689 badQureg.numQubits = -1;
2690 REQUIRE_THROWS_WITH(
2691 rightapplyQubitProjector(badQureg, 0, 0),
2692 ContainsSubstring("invalid Qureg")
2693 );
2694 }
2695
2696 SECTION( "qureg is not density matrix" ) {
2697
2698 Qureg badQureg = getArbitraryCachedStatevec();
2699 REQUIRE_THROWS_WITH(
2700 rightapplyQubitProjector(badQureg, 0, 0),
2701 ContainsSubstring("received a statevector")
2702 );
2703 }
2704
2705 SECTION( "invalid target qubit" ) {
2706
2707 int badTarget = GENERATE_COPY( -1, qureg.numQubits );
2708 REQUIRE_THROWS_WITH(
2709 rightapplyQubitProjector(qureg, badTarget, 0),
2710 ContainsSubstring("target")
2711 );
2712 }
2713
2714 SECTION( "invalid outcome" ) {
2715
2716 int badOutcome = GENERATE_COPY( -1, 2 );
2717 REQUIRE_THROWS_WITH(
2718 rightapplyQubitProjector(qureg, 0, badOutcome),
2719 ContainsSubstring("outcome")
2720 );
2721 }
2722
2723 // projector does NOT validate outcome probability
2724 }
2725}
2726
2727
2728TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) {
2729
2730 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2731
2732 SECTION( LABEL_CORRECTNESS ) {
2733
2734 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
2735 auto targets = GENERATE_TARGS( numQubits, numTargs );
2736 auto outcomes = getRandomOutcomes(numTargs);
2737
2738 qmatrix projector = getProjector(targets, outcomes, numQubits);
2739
2740 auto testFunc = [&](Qureg qureg, auto& ref) {
2741 leftapplyMultiQubitProjector(qureg, targets.data(), outcomes.data(), numTargs);
2742 leftapplyReferenceOperator(ref, projector);
2743 };
2744
2745 CAPTURE( targets, outcomes );
2746 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2747 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2748 }
2749
2750 SECTION( LABEL_VALIDATION ) {
2751
2752 Qureg qureg = getArbitraryCachedStatevec();
2753 int targets[] = {0, 1, 2};
2754 int outcomes[] = {0, 1, 0};
2755 int numTargets = 3;
2756
2757 SECTION( "qureg uninitialised" ) {
2758
2759 Qureg badQureg = qureg;
2760 badQureg.numQubits = -1;
2761 REQUIRE_THROWS_WITH(
2762 leftapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets),
2763 ContainsSubstring("invalid Qureg")
2764 );
2765 }
2766
2767 SECTION( "invalid target qubits" ) {
2768
2769 int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) };
2770 REQUIRE_THROWS_WITH(
2771 leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets),
2772 ContainsSubstring("target")
2773 );
2774 }
2775
2776 SECTION( "duplicate target qubits" ) {
2777
2778 int dupTargets[] = {0, 1, 1};
2779 REQUIRE_THROWS_WITH(
2780 leftapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets),
2781 ContainsSubstring("duplicate")
2782 );
2783 }
2784
2785 SECTION( "invalid number of targets" ) {
2786
2787 int badNumTargs = GENERATE( 0, -1 );
2788 REQUIRE_THROWS_WITH(
2789 leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
2790 ContainsSubstring("targets")
2791 );
2792
2793 badNumTargs = qureg.numQubits + 1;
2794 REQUIRE_THROWS_WITH(
2795 leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
2796 ContainsSubstring("exceeds the number of qubits in the Qureg")
2797 );
2798 }
2799
2800 SECTION( "invalid outcomes" ) {
2801
2802 int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) };
2803 REQUIRE_THROWS_WITH(
2804 leftapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets),
2805 ContainsSubstring("outcome")
2806 );
2807 }
2808
2809 SECTION( "targets mismatch outcomes (C++ only)" ) {
2810
2811 REQUIRE_THROWS_WITH(
2812 leftapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}),
2813 ContainsSubstring("inconsistent")
2814 );
2815 }
2816
2817 // projector does NOT validate outcome probability
2818 }
2819}
2820
2821
2822TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) {
2823
2824 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2825
2826 SECTION( LABEL_CORRECTNESS ) {
2827
2828 int numTargs = GENERATE_COPY( range(1,numQubits+1) );
2829 auto targets = GENERATE_TARGS( numQubits, numTargs );
2830 auto outcomes = getRandomOutcomes(numTargs);
2831
2832 qmatrix projector = getProjector(targets, outcomes, numQubits);
2833
2834 auto testFunc = [&](Qureg qureg, auto& ref) {
2835 rightapplyMultiQubitProjector(qureg, targets.data(), outcomes.data(), numTargs);
2836 rightapplyReferenceOperator(ref, projector);
2837 };
2838
2839 CAPTURE( targets, outcomes );
2840 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2841 }
2842
2843 SECTION( LABEL_VALIDATION ) {
2844
2845 Qureg qureg = getArbitraryCachedDensmatr();
2846 int targets[] = {0, 1, 2};
2847 int outcomes[] = {0, 1, 0};
2848 int numTargets = 3;
2849
2850 SECTION( "qureg uninitialised" ) {
2851
2852 Qureg badQureg = qureg;
2853 badQureg.numQubits = -1;
2854 REQUIRE_THROWS_WITH(
2855 rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets),
2856 ContainsSubstring("invalid Qureg")
2857 );
2858 }
2859
2860 SECTION( "qureg is not density matrix" ) {
2861
2862 Qureg badQureg = getArbitraryCachedStatevec();
2863 REQUIRE_THROWS_WITH(
2864 rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets),
2865 ContainsSubstring("received a statevector")
2866 );
2867 }
2868
2869 SECTION( "invalid target qubits" ) {
2870
2871 int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) };
2872 REQUIRE_THROWS_WITH(
2873 rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets),
2874 ContainsSubstring("target")
2875 );
2876 }
2877
2878 SECTION( "duplicate target qubits" ) {
2879
2880 int dupTargets[] = {0, 1, 1};
2881 REQUIRE_THROWS_WITH(
2882 rightapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets),
2883 ContainsSubstring("duplicate")
2884 );
2885 }
2886
2887 SECTION( "invalid number of targets" ) {
2888
2889 int badNumTargs = GENERATE( 0, -1 );
2890 REQUIRE_THROWS_WITH(
2891 rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
2892 ContainsSubstring("targets")
2893 );
2894
2895 badNumTargs = qureg.numQubits + 1;
2896 REQUIRE_THROWS_WITH(
2897 rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs),
2898 ContainsSubstring("exceeds the number of qubits in the Qureg")
2899 );
2900 }
2901
2902 SECTION( "invalid outcomes" ) {
2903
2904 int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) };
2905 REQUIRE_THROWS_WITH(
2906 rightapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets),
2907 ContainsSubstring("outcome")
2908 );
2909 }
2910
2911 SECTION( "targets mismatch outcomes (C++ only)" ) {
2912
2913 REQUIRE_THROWS_WITH(
2914 rightapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}),
2915 ContainsSubstring("inconsistent")
2916 );
2917 }
2918
2919 // projector does NOT validate outcome probability
2920 }
2921}
2922
2923
2924TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2925
2926 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2927
2928 SECTION( LABEL_CORRECTNESS ) {
2929
2930 int numQubits = getNumCachedQubits();
2931 int numTerms = GENERATE_COPY( 1, 2, 10 );
2932
2933 PauliStrSum sum = createRandomPauliStrSum(numQubits, numTerms);
2934
2935 auto testFunc = [&](Qureg qureg, auto& ref) {
2936
2937 // must use (and ergo make) an identically-deployed workspace
2938 Qureg workspace = createCloneQureg(qureg);
2939 leftapplyPauliStrSum(qureg, sum, workspace);
2940 destroyQureg(workspace);
2941
2942 ref = getMatrix(sum, numQubits) * ref;
2943 };
2944
2945 CAPTURE( numTerms );
2946 SECTION( LABEL_STATEVEC ) { TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); }
2947 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2948 }
2949
2950 SECTION( LABEL_VALIDATION ) {
2951
2952 Qureg qureg = getArbitraryCachedStatevec();
2953 PauliStrSum sum = createRandomPauliStrSum(numQubits, 2);
2954
2955 SECTION( "qureg uninitialised" ) {
2956
2957 Qureg badQureg = qureg;
2958 badQureg.numQubits = -1;
2959 Qureg workspace = createCloneQureg(qureg);
2960 REQUIRE_THROWS_WITH(
2961 leftapplyPauliStrSum(badQureg, sum, workspace),
2962 ContainsSubstring("invalid Qureg")
2963 );
2964 destroyQureg(workspace);
2965 }
2966
2967 destroyPauliStrSum(sum);
2968
2969 /// @todo remaining input validation
2970 }
2971}
2972
2973
2974TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) {
2975
2976 PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef );
2977
2978 SECTION( LABEL_CORRECTNESS ) {
2979
2980 int numQubits = getNumCachedQubits();
2981 int numTerms = GENERATE_COPY( 1, 2, 10 );
2982
2983 PauliStrSum sum = createRandomPauliStrSum(numQubits, numTerms);
2984
2985 auto testFunc = [&](Qureg qureg, auto& ref) {
2986
2987 // must use (and ergo make) an identically-deployed workspace
2988 Qureg workspace = createCloneQureg(qureg);
2989 rightapplyPauliStrSum(qureg, sum, workspace);
2990 destroyQureg(workspace);
2991
2992 ref = ref * getMatrix(sum, numQubits);
2993 };
2994
2995 CAPTURE( numTerms );
2996 SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); }
2997 }
2998
2999 SECTION( LABEL_VALIDATION ) {
3000
3001 Qureg qureg = getArbitraryCachedStatevec();
3002 PauliStrSum sum = createRandomPauliStrSum(numQubits, 2);
3003
3004 SECTION( "qureg uninitialised" ) {
3005
3006 Qureg badQureg = qureg;
3007 badQureg.numQubits = -1;
3008 Qureg workspace = createCloneQureg(qureg);
3009 REQUIRE_THROWS_WITH(
3010 rightapplyPauliStrSum(badQureg, sum, workspace),
3011 ContainsSubstring("invalid Qureg")
3012 );
3013 destroyQureg(workspace);
3014 }
3015
3016 destroyPauliStrSum(sum);
3017
3018 /// @todo remaining input validation
3019 }
3020}
3021
3022
3023/** @} (end defgroup) */
qreal calcProbOfQubitOutcome(Qureg qureg, int qubit, int outcome)
qreal calcProbOfMultiQubitOutcome(Qureg qureg, int *qubits, int *outcomes, int numQubits)
void setQuESTValidationEpsilonToDefault()
Definition debug.cpp:108
qreal getQuESTValidationEpsilon()
Definition debug.cpp:115
void setQuESTValidationEpsilon(qreal eps)
Definition debug.cpp:100
QuESTEnv getQuESTEnv()
void initZeroState(Qureg qureg)
void initDebugState(Qureg qureg)
CompMatr createCompMatr(int numQubits)
Definition matrices.cpp:213
DiagMatr createDiagMatr(int numQubits)
Definition matrices.cpp:248
void destroyDiagMatr(DiagMatr matrix)
Definition matrices.cpp:399
void destroyCompMatr(CompMatr matrix)
Definition matrices.cpp:398
static CompMatr2 getCompMatr2(qcomp **in)
Definition matrices.h:837
static CompMatr1 getCompMatr1(qcomp **in)
Definition matrices.h:811
static DiagMatr2 getDiagMatr2(qcomp *in)
Definition matrices.h:889
static DiagMatr1 getDiagMatr1(qcomp *in)
Definition matrices.h:864
void setDiagMatr(DiagMatr out, qcomp *in)
Definition matrices.cpp:431
void setCompMatr(CompMatr matr, qcomp **vals)
Definition matrices.cpp:423
void setFullStateDiagMatr(FullStateDiagMatr out, qindex startInd, qcomp *in, qindex numElems)
Definition matrices.cpp:443
void rightapplyCompMatr1(Qureg qureg, int target, CompMatr1 matrix)
void leftapplyCompMatr1(Qureg qureg, int target, CompMatr1 matrix)
void rightapplyCompMatr2(Qureg qureg, int target1, int target2, CompMatr2 matrix)
void leftapplyCompMatr2(Qureg qureg, int target1, int target2, CompMatr2 matr)
void leftapplyCompMatr(Qureg qureg, int *targets, int numTargets, CompMatr matrix)
void rightapplyCompMatr(Qureg qureg, int *targets, int numTargets, CompMatr matrix)
void leftapplyDiagMatr1(Qureg qureg, int target, DiagMatr1 matr)
void rightapplyDiagMatr1(Qureg qureg, int target, DiagMatr1 matrix)
void leftapplyDiagMatr2(Qureg qureg, int target1, int target2, DiagMatr2 matr)
void rightapplyDiagMatr2(Qureg qureg, int target1, int target2, DiagMatr2 matrix)
void rightapplyDiagMatr(Qureg qureg, int *targets, int numTargets, DiagMatr matrix)
void leftapplyDiagMatrPower(Qureg qureg, int *targets, int numTargets, DiagMatr matrix, qcomp exponent)
void rightapplyDiagMatrPower(Qureg qureg, int *targets, int numTargets, DiagMatr matrix, qcomp exponent)
void leftapplyDiagMatr(Qureg qureg, int *targets, int numTargets, DiagMatr matrix)
void rightapplyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix)
void leftapplyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix)
void rightapplyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent)
void leftapplyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent)
void leftapplyMultiQubitNot(Qureg qureg, int *targets, int numTargets)
void rightapplyMultiQubitNot(Qureg qureg, int *targets, int numTargets)
void leftapplyPauliX(Qureg qureg, int target)
void rightapplyPauliY(Qureg qureg, int target)
void leftapplyPauliY(Qureg qureg, int target)
void leftapplyPauliZ(Qureg qureg, int target)
void rightapplyPauliX(Qureg qureg, int target)
void rightapplyPauliZ(Qureg qureg, int target)
void leftapplyPauliGadget(Qureg qureg, PauliStr str, qreal angle)
void rightapplyPauliGadget(Qureg qureg, PauliStr str, qreal angle)
void rightapplyPauliStr(Qureg qureg, PauliStr str)
void leftapplyPauliStr(Qureg qureg, PauliStr str)
void rightapplyPauliStrSum(Qureg qureg, PauliStrSum sum, Qureg workspace)
void leftapplyPauliStrSum(Qureg qureg, PauliStrSum sum, Qureg workspace)
void rightapplyPhaseGadget(Qureg qureg, int *targets, int numTargets, qreal angle)
void leftapplyPhaseGadget(Qureg qureg, int *targets, int numTargets, qreal angle)
void leftapplyMultiQubitProjector(Qureg qureg, int *qubits, int *outcomes, int numQubits)
void rightapplyQubitProjector(Qureg qureg, int qubit, int outcome)
void rightapplyMultiQubitProjector(Qureg qureg, int *qubits, int *outcomes, int numQubits)
void leftapplyQubitProjector(Qureg qureg, int qubit, int outcome)
void leftapplySwap(Qureg qureg, int qubit1, int qubit2)
void rightapplySwap(Qureg qureg, int qubit1, int qubit2)
void applyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix)
void applyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent)
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 applyNonUnitaryPauliGadget(Qureg qureg, PauliStr str, qcomp angle)
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 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 applyQuantumFourierTransform(Qureg qureg, int *targets, int numTargets, bool inverse)
void applyFullQuantumFourierTransform(Qureg qureg, bool inverse)
void applyRotateX(Qureg qureg, int target, qreal angle)
PauliStr getPauliStr(const char *paulis, int *indices, int numPaulis)
Definition paulis.cpp:76
void destroyPauliStrSum(PauliStrSum sum)
Definition paulis.cpp:251
Qureg createCloneQureg(Qureg qureg)
Definition qureg.cpp:325
void destroyQureg(Qureg qureg)
Definition qureg.cpp:340
qmatrix getKroneckerProduct(qmatrix a, qmatrix b)
Definition linalg.cpp:525
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