The Quantum Exact Simulation Toolkit v4.0.0
Loading...
Searching...
No Matches
paulis.cpp
1/** @file
2 * Unit tests of the paulis module.
3 *
4 * @author Tyson Jones
5 *
6 * @defgroup unitpaulis Paulis
7 * @ingroup unittests
8 */
9
10#include "quest/include/quest.h"
11
12#include <catch2/catch_test_macros.hpp>
13#include <catch2/matchers/catch_matchers_string.hpp>
14#include <catch2/generators/catch_generators_range.hpp>
15
16#include "tests/utils/qvector.hpp"
17#include "tests/utils/qmatrix.hpp"
18#include "tests/utils/macros.hpp"
19#include "tests/utils/cache.hpp"
20#include "tests/utils/convert.hpp"
21#include "tests/utils/compare.hpp"
22#include "tests/utils/linalg.hpp"
23#include "tests/utils/random.hpp"
24
25#include <string>
26#include <vector>
27#include <iostream>
28#include <fstream>
29#include <cstdlib>
30
31using std::vector;
32using Catch::Matchers::ContainsSubstring;
33
34
35
36/*
37 * UTILITIES
38 */
39
40#define TEST_CATEGORY \
41 LABEL_UNIT_TAG "[paulis]"
42
43
44
45/**
46 * TESTS
47 *
48 * @ingroup unitpaulis
49 * @{
50 */
51
52
53TEST_CASE( "getPauliStr", TEST_CATEGORY ) {
54
55 SECTION( LABEL_CORRECTNESS ) {
56
57 GENERATE( range(0,10) );
58
59 std::string charSet = GENERATE( "IXYZ", "ixyz", "0123", "iX2z" );
60
61 int numPaulis = GENERATE( 1, 2, 5, 10, 20, 30, 31, 32, 33, 60, 64 );
62 auto targets = getRandomSubRange(0, 64, numPaulis);
63
64 // not necessary to add a terminal char when we specify numPaulis
65 vector<char> pauliChars(numPaulis, 'I');
66 vector<int> pauliInts(numPaulis, 0);
67 long long unsigned lowValue = 0;
68 long long unsigned highValue = 0;
69
70 for (int i=0; i<numPaulis; i++) {
71 int v = getRandomInt(1, 3+1);
72 pauliInts[i] = v;
73 pauliChars[i] = charSet[v];
74
75 int t = targets[i];
76 (t < 32)?
77 (lowValue += v * getPow2(2 * t)): // pow4
78 (highValue += v * getPow2(2 * (t-32))); // pow4
79 }
80
81 SECTION( LABEL_C_INTERFACE ) {
82
83 SECTION( "from chars" ) {
84
85 CAPTURE( targets, pauliChars );
86
87 PauliStr str = getPauliStr(pauliChars.data(), targets.data(), numPaulis);
88
89 REQUIRE( str.lowPaulis == lowValue );
90 REQUIRE( str.highPaulis == highValue );
91 }
92
93 SECTION( "from ints" ) {
94
95 CAPTURE( targets, pauliChars );
96
97 PauliStr str = getPauliStr(pauliInts.data(), targets.data(), numPaulis);
98
99 REQUIRE( str.lowPaulis == lowValue );
100 REQUIRE( str.highPaulis == highValue );
101 }
102
103 SECTION( "from literal" ) {
104
105 // lazily ignores some above prepared vars
106
107 int targ = targets[0];
108 CAPTURE( targ );
109
110 const char* in = "X";
111 PauliStr str = getPauliStr(in, &targ, 1);
112 REQUIRE( str.lowPaulis == ((targ < 32)? getPow2(2*targ) : 0) );
113 REQUIRE( str.highPaulis == ((targ >= 32)? getPow2(2*(targ-32)) : 0) );
114 }
115 }
116
117 SECTION( LABEL_CPP_INTERFACE ) {
118
119 // std::string() requires pauliChars includes a terminal char
120 pauliChars.push_back('\0');
121 std::string in = std::string(pauliChars.data());
122
123 SECTION( "from string" ) {
124
125 CAPTURE( targets, pauliChars );
126
127 PauliStr str = getPauliStr(in, targets.data(), numPaulis);
128
129 REQUIRE( str.lowPaulis == lowValue );
130 REQUIRE( str.highPaulis == highValue );
131 }
132
133 SECTION( "from vector" ) {
134
135 CAPTURE( targets, pauliChars );
136
137 PauliStr str = getPauliStr(in, targets);
138
139 REQUIRE( str.lowPaulis == lowValue );
140 REQUIRE( str.highPaulis == highValue );
141 }
142
143 SECTION( "from literal" ) {
144
145 // lazily ignores some above prepared vars
146
147 int targ = targets[0];
148 CAPTURE( targ );
149
150 PauliStr str = getPauliStr("X", &targ, 1);
151 REQUIRE( str.lowPaulis == ((targ < 32)? getPow2(2*targ) : 0) );
152 REQUIRE( str.highPaulis == ((targ >= 32)? getPow2(2*(targ-32)) : 0) );
153 }
154
155 SECTION( "from only string" ) {
156
157 CAPTURE( targets, pauliChars );
158
159 char chars[65]; // 64 + terminal char
160 chars[64] = '\0';
161 for (int i=0; i<64; i++)
162 chars[i] = 'I';
163
164 // string is DECREASING significance
165 for (int i=0; i<numPaulis; i++)
166 chars[64-targets[i]-1] = pauliChars[i];
167
168 std::string all = std::string(chars);
169 PauliStr str = getPauliStr(all);
170
171 CAPTURE( all );
172 REQUIRE( str.lowPaulis == lowValue );
173 REQUIRE( str.highPaulis == highValue );
174 }
175 }
176 }
177
178 SECTION( LABEL_VALIDATION ) {
179
180 SECTION( "invalid target" ) {
181
182 int target = GENERATE( -1, 64, 65, 9999 );
183
184 REQUIRE_THROWS_WITH( getPauliStr("X", {target}), ContainsSubstring("Invalid index") );
185 }
186
187 SECTION( "duplicated target" ) {
188
189 REQUIRE_THROWS_WITH( getPauliStr("XY", {0,0}), ContainsSubstring("duplicate") );
190 }
191
192 SECTION( "invalid number of paulis" ) {
193
194 int numPaulis = GENERATE( -1, 0 );
195
196 REQUIRE_THROWS_WITH( getPauliStr("X", nullptr, numPaulis), ContainsSubstring("must contain at least one Pauli operator") );
197 }
198
199 SECTION( "string terminated early" ) {
200
201 REQUIRE_THROWS_WITH( getPauliStr("X", {1,2}), ContainsSubstring("different number of Pauli operators") && ContainsSubstring("qubit indices") );
202 }
203
204 SECTION( "unrecognised char" ) {
205
206 REQUIRE_THROWS_WITH( getPauliStr("hi", {1,2}), ContainsSubstring("unrecognised Pauli character") );
207 }
208 }
209}
210
211
212TEST_CASE( "getInlinePauliStr", TEST_CATEGORY ) {
213
214 SECTION( LABEL_CORRECTNESS ) {
215
216 PauliStr str;
217 long long unsigned val;
218
219 val = 1 + 2*4 + 3*4*4 + 0*4*4*4;
220 str = getInlinePauliStr("XYZI", {0,1,2,3});
221 REQUIRE( str.lowPaulis == val );
222 str = getInlinePauliStr("xyzi", {0,1,2,3});
223 REQUIRE( str.lowPaulis == val );
224 str = getInlinePauliStr("1230", {0,1,2,3});
225 REQUIRE( str.lowPaulis == val );
226
227 val = (1*4*4*4 + 2*4*4 + 3*4 + 0*4);
228 str = getInlinePauliStr("XYZI", {3,2,1,0});
229 REQUIRE( str.lowPaulis == val );
230 str = getInlinePauliStr("xyzi", {3,2,1,0});
231 REQUIRE( str.lowPaulis == val );
232 str = getInlinePauliStr("1230", {3,2,1,0});
233 REQUIRE( str.lowPaulis == val );
234
235 val *= (1ULL << (2 * (63 - 3 - 32)));
236 str = getInlinePauliStr("XYZ", {63,62,61});
237 REQUIRE( str.highPaulis == val );
238 str = getInlinePauliStr("xyz", {63,62,61});
239 REQUIRE( str.highPaulis == val );
240 str = getInlinePauliStr("123", {63,62,61});
241 REQUIRE( str.highPaulis == val );
242 }
243
244 SECTION( LABEL_VALIDATION ) {
245
246 // here, only the C++ interface can be tested
247
248 SECTION( "invalid target" ) {
249
250 int target = GENERATE( -1, 64, 65, 9999 );
251
252 REQUIRE_THROWS_WITH( getInlinePauliStr("X", {target}), ContainsSubstring("Invalid index") );
253 }
254
255 SECTION( "duplicated target" ) {
256
257 REQUIRE_THROWS_WITH( getInlinePauliStr("XY", {0,0}), ContainsSubstring("duplicate") );
258 }
259
260 SECTION( "string terminated early" ) {
261
262 REQUIRE_THROWS_WITH( getInlinePauliStr("X", {1,2}), ContainsSubstring("different number of Pauli operators") && ContainsSubstring("qubit indices") );
263 }
264
265 SECTION( "unrecognised char" ) {
266
267 REQUIRE_THROWS_WITH( getInlinePauliStr("ABC", {1,2,3}), ContainsSubstring("unrecognised Pauli character") );
268 }
269 }
270}
271
272
273TEST_CASE( "createPauliStrSum", TEST_CATEGORY ) {
274
275 SECTION( LABEL_CORRECTNESS ) {
276
277 // the utilities for automatically checking
278 // correctness of this function would "beg the
279 // question", so we instead compare to a
280 // hardcoded problem solved externally
281
282 int numQubits = 2;
283
284 vector<PauliStr> strings = {
285 getPauliStr("XY", {0,1}),
286 getPauliStr("ZX", {0,1}),
287 getPauliStr("YZ", {0,1})
288 };
289 vector<qcomp> coeffs = {1, 2, 3_i};
290
291 qmatrix ref = {
292 { 0, 3, 2, -1_i},
293 { -3, 0, -1_i, -2},
294 { 2, 1_i, 0, -3},
295 {1_i, -2, 3, 0}};
296
297 SECTION( LABEL_C_INTERFACE ) {
298
299 PauliStrSum sum = createPauliStrSum(strings.data(), coeffs.data(), coeffs.size());
300
301 REQUIRE( sum.numTerms == (qindex) strings.size() );
302 REQUIRE( *(sum.isApproxHermitian) == -1 );
303
304 REQUIRE_AGREE( getMatrix(sum,numQubits), ref );
305
307 }
308
309 SECTION( LABEL_CPP_INTERFACE ) {
310
311 PauliStrSum sum = createPauliStrSum(strings, coeffs);
312
313 REQUIRE( sum.numTerms == (qindex) strings.size() );
314 REQUIRE( *(sum.isApproxHermitian) == -1 );
315
316 REQUIRE_AGREE( getMatrix(sum,numQubits), ref );
317
319 }
320 }
321
322 SECTION( LABEL_VALIDATION ) {
323
324 SECTION( "number of terms" ) {
325
326 int numTerms = GENERATE( -1, 0 );
327
328 REQUIRE_THROWS_WITH( createPauliStrSum(nullptr, nullptr, numTerms), ContainsSubstring("number of terms must be a positive integer") );
329 }
330
331 SECTION( "mismatching lengths" ) {
332
333 // specific to the C++ interface
334
335 REQUIRE_THROWS_WITH( createPauliStrSum({}, {.1}), ContainsSubstring("different number of Pauli strings") && ContainsSubstring("coefficients") );
336 }
337 }
338}
339
340
341TEST_CASE( "createInlinePauliStrSum", TEST_CATEGORY ) {
342
343 SECTION( LABEL_CORRECTNESS ) {
344
345 // C++ interface does not mandate literal
346
347 SECTION( "pauli parsing" ) {
348
349 // ZYXI
350 unsigned ref = 0 + 1*4 + 2*4*4 + 3*4*4*4;
351 auto str = GENERATE(
352 ".1i ZYXI", ".1I Z Y X I", ".1j ZY X I ",
353 ".1i zyxi", ".1I z y x i", ".1j zy x i ",
354 ".1i 3210", ".1I 3 2 1 0", ".1J 32 1 0 ",
355 ".1i Zy1i", ".1I 3 Y X 0", ".1J Zy 1 I "
356 );
357
359 REQUIRE( sum.strings[0].lowPaulis == ref );
361 }
362
363 SECTION( "coefficient parsing" ) {
364
365 vector<std::string> strs = {"1 X", "0 X", "0.1 X", "5E2-1i X", "-1E-50i X", "1 - 6E-5i X", "-1.5E-15 - 5.123E-30i 0"};
366 vector<qcomp> coeffs = { 1, 0, 0.1, 5E2-1_i, -(1E-50)*1_i, 1 -(6E-5)*1_i, qcomp(-1.5E-15, -5.123E-30) };
367
368 size_t i = GENERATE_REF( range(0, (int) strs.size()) );
369 CAPTURE( strs[i], coeffs[i] );
370
372 REQUIRE_AGREE( sum.coeffs[0], coeffs[i] ); // should be strict
374 }
375
376 SECTION( "newlines" ) {
377
379 + 5E2-1i XYZ
380 - 1E-50i IXY
381 + 1 - 6E-5i IIX
382 0 III
383 5. XXX
384 .5 ZZZ
385 )");
386
387 REQUIRE( sum.numTerms == 6 );
388 REQUIRE( sum.strings[3].lowPaulis == 0ULL );
389 REQUIRE_AGREE( sum.coeffs[5], qcomp(.5,0) ); // should be strict
390
392 }
393 }
394
395 SECTION( LABEL_VALIDATION ) {
396
397 SECTION( "env not init" ) {
398
399 // no way to test this
400 SUCCEED( );
401 }
402
403 SECTION( "empty" ) {
404
405 auto str = GENERATE( "", " ", "\n", " \n " );
406
407 REQUIRE_THROWS_WITH( createInlinePauliStrSum(str), ContainsSubstring("empty") );
408 }
409
410 SECTION( "uninterpretable" ) {
411
412 auto str = GENERATE( "X", "1", "a X", "-1 H", "0 .3", "1 23456" );
413
414 REQUIRE_THROWS_WITH( createInlinePauliStrSum(str), ContainsSubstring("Could not interpret") );
415
416 REQUIRE_NOTHROW( createInlinePauliStrSum("1 2 3") ); // = 1 * YZ and is legal
417 }
418
419 SECTION( "inconsistent number of qubits" ) {
420
421 REQUIRE_THROWS_WITH( createInlinePauliStrSum("3 XYZ \n 2 YX"), ContainsSubstring("inconsistent") );
422 }
423
424 SECTION( "too many qubits" ) {
425
426 // C++ interface permits both literal AND passing existing string
427 std::string str = "1 XXXXXYYYYYZZZZZIIIIIXXXXXYYYYYZZZZZIIIIIXXXXXYYYYYZZZZZIIIIIXXXXX"; // 65 paulis
428
429 REQUIRE_THROWS_WITH( createInlinePauliStrSum(str), ContainsSubstring("exceeds the maximum of 64") );
430 }
431 }
432}
433
434
435TEST_CASE( "createPauliStrSumFromFile", TEST_CATEGORY ) {
436
437 SECTION( LABEL_CORRECTNESS ) {
438
439 std::string fn = "test.txt";
440
441 // file contents can be identical to createInlinePauliStrSum input above
442 if (getQuESTEnv().rank == 0) {
443 std::ofstream file;
444 file.open(fn);
445 file << R"(
446 + 5E2-1i XYZ
447 - 1E-50i IXY
448 + 1 - 6E-5i IIX
449 0 III
450 5. IXX
451 .5 ZYX
452 )";
453 file.close();
454 }
455
456 // all nodes must wait for root to finish writing
457 syncQuESTEnv();
458
460
461 REQUIRE( sum.strings[0].lowPaulis == 3 + 2*4 + 1*4*4 );
462
463 REQUIRE( sum.numTerms == 6 );
464 REQUIRE( sum.coeffs[0] == qcomp(500, -1) );
465 REQUIRE( sum.strings[3].lowPaulis == 0ULL );
466 REQUIRE( sum.coeffs[5] == qcomp(.5,0) );
467
469 }
470
471 SECTION( LABEL_VALIDATION ) {
472
473 // we skip all tests which overlap createInlinePauliStrSum
474 // above, since the function simplify parses the file then
475 // calls createInlinePauliStrSum(), and writing everything
476 // to file to subsequently test this function is a chore
477
478 SECTION( "bad file name" ) {
479
480 auto fn = GENERATE( "", " ", "\n", "nonexistentfile.txt" );
481
482 REQUIRE_THROWS_WITH( createPauliStrSumFromFile(fn), ContainsSubstring("Could not load and read the given file") );
483 }
484 }
485}
486
487
488TEST_CASE( "createPauliStrSumFromReversedFile", TEST_CATEGORY ) {
489
490 SECTION( LABEL_CORRECTNESS ) {
491
492 std::string fn = "test.txt";
493
494 // file contents can be identical to createInlinePauliStrSum input above
495 if (getQuESTEnv().rank == 0) {
496 std::ofstream file;
497 file.open(fn);
498 file << R"(
499 + 5E2-1i XYZ
500 - 1E-50i IXY
501 + 1 - 6E-5i IIX
502 0 III
503 5. IXX
504 .5 ZYX
505 )";
506 file.close();
507 }
508
509 // all nodes must wait for root to finish writing
510 syncQuESTEnv();
511
513
514 // reversed order from createPauliStrSumFromFile() above
515 REQUIRE( sum.strings[0].lowPaulis == 1 + 2*4 + 3*4*4 );
516
517 REQUIRE( sum.numTerms == 6 );
518 REQUIRE( sum.coeffs[0] == qcomp(500, -1) );
519 REQUIRE( sum.strings[3].lowPaulis == 0ULL );
520 REQUIRE( sum.coeffs[5] == qcomp(.5,0) );
521
523 }
524
525 SECTION( LABEL_VALIDATION ) {
526
527 // we skip all tests which overlap createInlinePauliStrSum
528 // above, since the function simplify parses the file then
529 // calls createInlinePauliStrSum(), and writing everything
530 // to file to subsequently test this function is a chore
531
532 SECTION( "bad file name" ) {
533
534 auto fn = GENERATE( "", " ", "\n", "nonexistentfile.txt" );
535
536 REQUIRE_THROWS_WITH( createPauliStrSumFromReversedFile(fn), ContainsSubstring("Could not load and read the given file") );
537 }
538 }
539}
540
541
542TEST_CASE( "destroyPauliStrSum", TEST_CATEGORY ) {
543
544 SECTION( LABEL_CORRECTNESS ) {
545
547 REQUIRE_NOTHROW( destroyPauliStrSum(sum) );
548 }
549
550 SECTION( LABEL_VALIDATION ) {
551
552 /// @todo fails in MSVC for unknown reason
553 #ifndef _MSC_VER
554 // sanitizer messes with default initialisation
555 #ifndef SANITIZER_IS_ACTIVE
556 SECTION( "not created" ) {
557
558 PauliStrSum sum;
559
560 // uninitialised sum fields can be coincidentally
561 // valid on some platforms (Github Actions linux
562 // gcc), so we force invalidity
563 sum.numTerms = -1;
564 sum.isApproxHermitian = nullptr; // else seg-faults if field accessed
565
566 REQUIRE_THROWS_WITH( destroyPauliStrSum(sum),
567 ContainsSubstring("invalid fields") ||
568 ContainsSubstring("heap pointers was unexpectedly NULL") ||
569 ContainsSubstring("It is likely the structure was not created by its proper function")
570 );
571 }
572 #endif
573 #endif
574 }
575}
576
577
578/** @} (end defgroup) */
579
580
581
582/**
583 * @todo
584 * UNTESTED FUNCTIONS
585 */
586
587
588void reportPauliStr(PauliStr str);
589
QuESTEnv getQuESTEnv()
void syncQuESTEnv()
PauliStr getInlinePauliStr(const char *paulis, { list })
PauliStrSum createPauliStrSumFromFile(const char *fn)
Definition paulis.cpp:431
PauliStrSum createPauliStrSum(PauliStr *strings, qcomp *coeffs, qindex numTerms)
Definition paulis.cpp:385
PauliStrSum createInlinePauliStrSum(const char *str)
Definition paulis.cpp:418
PauliStrSum createPauliStrSumFromReversedFile(const char *fn)
Definition paulis.cpp:448
PauliStr getPauliStr(const char *paulis, int *indices, int numPaulis)
Definition paulis.cpp:296
void destroyPauliStrSum(PauliStrSum sum)
Definition paulis.cpp:471
void reportPauliStrSum(PauliStrSum str)
Definition paulis.cpp:495
void reportPauliStr(PauliStr str)
Definition paulis.cpp:484
int getRandomInt(int min, int maxExcl)
Definition random.cpp:90
TEST_CASE("getPauliStr", TEST_CATEGORY)
Definition paulis.cpp:53