The Quantum Exact Simulation Toolkit v4.1.0
Loading...
Searching...
No Matches
paulis.cpp
1/** @file
2 * API functions for creating PauliStr and PauliStrSum,
3 * and initialising and reporting them
4 *
5 * @author Tyson Jones
6 */
7
8#include "quest/include/precision.h"
9#include "quest/include/paulis.h"
10
11#include "quest/src/core/validation.hpp"
12#include "quest/src/core/printer.hpp"
13#include "quest/src/core/utilities.hpp"
14#include "quest/src/core/parser.hpp"
15#include "quest/src/core/memory.hpp"
16#include "quest/src/core/errors.hpp"
17#include "quest/src/core/bitwise.hpp"
18#include "quest/src/cpu/cpu_config.hpp"
19#include "quest/src/comm/comm_config.hpp"
20#include "quest/src/comm/comm_routines.hpp"
21
22#include <iostream>
23#include <vector>
24#include <string>
25#include <array>
26
27using std::string;
28using std::vector;
29using std::array;
30
31
32
33/*
34 * PRIVATE CONSTANTS
35 */
36
37
38static const int MAX_NUM_PAULIS_PER_MASK = sizeof(PAULI_MASK_TYPE) * 8 / 2;
39static const int MAX_NUM_PAULIS_PER_STR = MAX_NUM_PAULIS_PER_MASK * 2;
40
41
42
43/*
44 * PRIVATE UTILITIES
45 */
46
47
48int getPauliFromMaskAt(PAULI_MASK_TYPE mask, int ind) {
49
50 return getTwoAdjacentBits(mask, 2*ind); // bits at (ind+1, ind)
51}
52
53
54bool didAnyAllocsFailOnAnyNode(PauliStrSum sum) {
55
56 bool anyFail = (
57 ! mem_isAllocated(sum.strings) ||
58 ! mem_isAllocated(sum.coeffs) ||
59 ! mem_isAllocated(sum.isApproxHermitian) );
60
61 if (comm_isInit())
62 anyFail = comm_isTrueOnAllNodes(anyFail);
63
64 return anyFail;
65}
66
67
68void freePauliStrSum(PauliStrSum sum) {
69
70 // these do not need to be allocated (freeing nullptr is legal)
71 cpu_deallocPauliStrings(sum.strings);
72 cpu_deallocArray(sum.coeffs);
73 util_deallocEpsilonSensitiveHeapFlag(sum.isApproxHermitian);
74}
75
76
77void freeAllMemoryIfAnyAllocsFailed(PauliStrSum sum) {
78
79 // do nothing if everything allocated successfully between all nodes
80 if (!didAnyAllocsFailOnAnyNode(sum))
81 return;
82
83 // otherwise free every successful allocation (freeing nullptr is legal)
84 freePauliStrSum(sum);
85}
86
87
88
89/*
90 * INTERNAL UTILITIES
91 *
92 * callable by other internal files but which are not exposed in the header
93 * because we do not wish to make them visible to users. Ergo other internal
94 * files must declare these functions as extern where needed. Yes, it's ugly :(
95 */
96
97
98bool paulis_isIdentity(PauliStr str) {
99
100 return
101 (str.lowPaulis == 0) &&
102 (str.highPaulis == 0);
103}
104
105
106int paulis_getPauliAt(PauliStr str, int ind) {
107
108 return (ind < MAX_NUM_PAULIS_PER_MASK)?
109 getPauliFromMaskAt(str.lowPaulis, ind) :
110 getPauliFromMaskAt(str.highPaulis, ind - MAX_NUM_PAULIS_PER_MASK);
111}
112
113
114int paulis_getIndOfLefmostNonIdentityPauli(PauliStr str) {
115
116 int ind = (str.highPaulis == 0)? 0 : MAX_NUM_PAULIS_PER_MASK;
117 auto mask = (str.highPaulis == 0)? str.lowPaulis : str.highPaulis;
118
119 while (mask) {
120 mask >>= 2;
121 ind++;
122 }
123
124 return ind - 1;
125}
126
127
128int paulis_getIndOfLefmostNonIdentityPauli(PauliStr* strings, qindex numStrings) {
129
130 int maxInd = 0;
131
132 for (qindex i=0; i<numStrings; i++) {
133 int ind = paulis_getIndOfLefmostNonIdentityPauli(strings[i]);
134 if (ind > maxInd)
135 maxInd = ind;
136 }
137
138 return maxInd;
139}
140
141
142int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum) {
143
144 return paulis_getIndOfLefmostNonIdentityPauli(sum.strings, sum.numTerms);
145}
146
147
148bool paulis_containsXOrY(PauliStr str) {
149
150 int maxInd = paulis_getIndOfLefmostNonIdentityPauli(str);
151
152 for (int i=0; i<=maxInd; i++) {
153 int pauli = paulis_getPauliAt(str, i);
154
155 if (pauli == 1 || pauli == 2)
156 return true;
157 }
158
159 return false;
160}
161
162
163bool paulis_containsXOrY(PauliStrSum sum) {
164
165 for (qindex i=0; i<sum.numTerms; i++)
166 if (paulis_containsXOrY(sum.strings[i]))
167 return true;
168
169 return false;
170}
171
172
173bool paulis_hasOddNumY(PauliStr str) {
174
175 bool odd = false;
176
177 for (int targ=0; targ < MAX_NUM_PAULIS_PER_STR; targ++)
178 if (paulis_getPauliAt(str, targ) == 2)
179 odd = !odd;
180
181 return odd;
182}
183
184
185int paulis_getPrefixZSign(Qureg qureg, vector<int> prefixZ) {
186
187 int sign = 1;
188
189 // each Z contributes +- 1
190 for (int qubit : prefixZ)
191 sign *= util_getRankBitOfQubit(qubit, qureg)? -1 : 1;
192
193 return sign;
194}
195
196
197qcomp paulis_getPrefixPaulisElem(Qureg qureg, vector<int> prefixY, vector<int> prefixZ) {
198
199 // each Z contributes +- 1
200 qcomp elem = paulis_getPrefixZSign(qureg, prefixZ);
201
202 // each Y contributes -+ i
203 for (int qubit : prefixY)
204 elem *= 1_i * (util_getRankBitOfQubit(qubit, qureg)? 1 : -1);
205
206 return elem;
207}
208
209
210vector<int> paulis_getInds(PauliStr str) {
211
212 int maxInd = paulis_getIndOfLefmostNonIdentityPauli(str);
213
214 vector<int> inds(0);
215 inds.reserve(maxInd+1);
216
217 for (int i=0; i<=maxInd; i++)
218 if (paulis_getPauliAt(str, i) != 0)
219 inds.push_back(i);
220
221 return inds;
222}
223
224
225array<vector<int>,3> paulis_getSeparateInds(PauliStr str, Qureg qureg) {
226
227 vector<int> iXYZ = paulis_getInds(str);
228 vector<int> iX, iY, iZ;
229
230 vector<int>* ptrs[] = {&iX, &iY, &iZ};
231
232 for (int i : iXYZ)
233 ptrs[paulis_getPauliAt(str, i) - 1]->push_back(i);
234
235 return {iX, iY, iZ};
236}
237
238
239PauliStr paulis_getShiftedPauliStr(PauliStr str, int pauliShift) {
240
241 if (pauliShift <= 0 || pauliShift >= MAX_NUM_PAULIS_PER_MASK)
242 error_pauliStrShiftedByIllegalAmount();
243
244 int numBitsPerPauli = 2;
245 int numMaskBits = numBitsPerPauli * MAX_NUM_PAULIS_PER_MASK;
246 int bitShift = numBitsPerPauli * pauliShift;
247
248 // record the bits we will lose from lowPaulis, to move to highPaulis
249 PAULI_MASK_TYPE lostBits = getBitsLeftOfIndex(str.lowPaulis, numMaskBits - bitShift - 1);
250
251 // ensure we actually lose these bits from lowPaulis
252 PAULI_MASK_TYPE lowerBits = getBitsRightOfIndex(str.lowPaulis, numMaskBits - bitShift) << bitShift;
253
254 // and add them to highPaulis; we don't have to force lose upper bits of high paulis
255 PAULI_MASK_TYPE upperBits = concatenateBits(str.highPaulis, lostBits, bitShift);
256
257 // return a new stack PauliStr instance (avoiding C++20 initialiser)
258 PauliStr out;
259 out.lowPaulis = lowerBits;
260 out.highPaulis = upperBits;
261 return out;
262}
263
264
265PauliStr paulis_getKetAndBraPauliStr(PauliStr str, Qureg qureg) {
266
267 PauliStr shifted = paulis_getShiftedPauliStr(str, qureg.numQubits);
268
269 // return a new stack PauliStr instance (avoiding C++20 initialiser)
270 PauliStr out;
271 out.lowPaulis = str.lowPaulis | shifted.lowPaulis;
272 out.highPaulis = str.highPaulis | shifted.highPaulis;
273 return out;
274}
275
276
277PAULI_MASK_TYPE paulis_getKeyOfSameMixedAmpsGroup(PauliStr str) {
278
279 PAULI_MASK_TYPE key = 0;
280
281 // in theory, we can reduce the number of involved operations by bit-shifting
282 // str left by 1, XOR'ing this with str, and retaining every 2nd bit, producing
283 // e.g. key=0110 from str=IXYZ. However, this is an insignificant speedup which
284 // risks sneaky bugs related to handling str's two masks.
285
286 int maxInd = paulis_getIndOfLefmostNonIdentityPauli(str);
287
288 for (int i=0; i<=maxInd; i++) {
289 int pauli = paulis_getPauliAt(str, i);
290 int isXY = (pauli == 1 || pauli == 2);
291 key |= (isXY << i);
292 }
293
294 return key;
295}
296
297
298
299/*
300 * PAULI STRING INITIALISATION
301 *
302 * some of which are exposed directly to C, and some of which are C++-only overloads
303 */
304
305
306extern "C" PauliStr getPauliStr(const char* paulis, int* indices, int numPaulis) {
307 validate_newPauliStrParams(paulis, indices, numPaulis, MAX_NUM_PAULIS_PER_STR, __func__);
308
309 // begin masks at all-identity 'I' = 0
310 PAULI_MASK_TYPE lowPaulis = 0;
311 PAULI_MASK_TYPE highPaulis = 0;
312
313 // change targeted indices to the given Paulis
314 for (int i=0; i<numPaulis; i++) {
315
316 // cast single Pauli to full precision mask to enable below shifts
317 auto pauli = (PAULI_MASK_TYPE) parser_getPauliIntFromChar(paulis[i]);
318
319 // add the Pauli to either the lower or upper pauli masks
320 if (indices[i] < MAX_NUM_PAULIS_PER_MASK)
321 lowPaulis |= pauli << (2*indices[i]);
322 else
323 highPaulis |= pauli << (2*(indices[i] - MAX_NUM_PAULIS_PER_MASK));
324 }
325
326 // return a new stack PauliStr instance (avoiding C++20 initialiser)
327 PauliStr out;
328 out.lowPaulis = lowPaulis;
329 out.highPaulis = highPaulis;
330 return out;
331}
332
333
334PauliStr getPauliStr(int* paulis, int* indices, int numPaulis) {
335 validate_newPauliStrParams(paulis, indices, numPaulis, MAX_NUM_PAULIS_PER_STR, __func__);
336
337 // validation ensures never causes stack overflow
338 char pauliChars[MAX_NUM_PAULIS_PER_STR + 1]; // +1 for null-terminal
339
340 // make a char array from the pauli codes, using an arbitrary
341 // choice of the Pauli characters accepted by the API (like IXYZ)
342 for (int i=0; i<numPaulis; i++)
343 pauliChars[i] = "IXYZ"[paulis[i]];
344
345 // including the trailing null char, used to infer string end/length
346 pauliChars[numPaulis] = '\0';
347
348 return getPauliStr(pauliChars, indices, numPaulis);
349}
350
351
352extern "C" PauliStr _getPauliStrFromInts(int* paulis, int* indices, int numPaulis) {
353
354 return getPauliStr(paulis, indices, numPaulis);
355}
356
357
358PauliStr getPauliStr(string paulis, int* indices, int numPaulis) {
359
360 // additionally validate 'paulis' string has 'numPaulis' chars
361 validate_newPauliStrNumChars(paulis.length(), numPaulis, __func__);
362
363 return getPauliStr(paulis.data(), indices, numPaulis); // validates
364}
365
366PauliStr getPauliStr(string paulis, vector<int> indices) {
367
368 // additionally validate 'paulis' string has 'numPaulis' chars
369 validate_newPauliStrNumChars(paulis.length(), indices.size(), __func__);
370
371 return getPauliStr(paulis.data(), indices.data(), indices.size()); // validates
372}
373
374PauliStr getPauliStr(string paulis) {
375
376 // pedantically validate the string length isn't so long that it would stackoverflow a vector
377 validate_newPauliStrNumPaulis(paulis.size(), MAX_NUM_PAULIS_PER_STR, __func__);
378
379 // automatically target the lowest-index qubits, interpreting rightmost is least significant
380 vector<int> indices(paulis.size());
381 for (size_t i=0; i<paulis.size(); i++)
382 indices[i] = paulis.size() - 1 - i;
383
384 return getPauliStr(paulis, indices); // validates
385}
386
387
388
389/*
390 * PAULI STRING SUM CREATION
391 *
392 * some of which are exposed directly to C, and some of which are C++-only overloads
393 */
394
395
396extern "C" PauliStrSum createPauliStrSum(PauliStr* strings, qcomp* coeffs, qindex numTerms) {
397
398 // note we do not require nor impose the strings to be unique
399 validate_newPauliStrSumParams(numTerms, __func__);
400
401 // prepare output PauliStrSum (avoiding C++20 designated initialiser)
402 PauliStrSum out;
403 out.numTerms = numTerms;
404 out.strings = cpu_allocPauliStrings(numTerms); // nullptr if failed
405 out.coeffs = cpu_allocArray(numTerms); // nullptr if failed
406 out.isApproxHermitian = util_allocEpsilonSensitiveHeapFlag(); // nullptr if failed
407
408 // if either alloc failed, clear both before validation to avoid leak
409 freeAllMemoryIfAnyAllocsFailed(out);
410 validate_newPauliStrSumAllocs(out, numTerms*sizeof(PauliStr), numTerms*sizeof(qcomp), __func__);
411
412 // otherwise copy given data into new heap structure, and set initial flags
413 cpu_copyPauliStrSum(out, strings, coeffs);
414 util_setFlagToUnknown(out.isApproxHermitian);
415
416 return out;
417}
418
419PauliStrSum createPauliStrSum(vector<PauliStr> strings, vector<qcomp> coeffs) {
420
421 // additionally validate 'strings' and 'coeffs' are the same length
422 validate_newPauliStrSumMatchingListLens(strings.size(), coeffs.size(), __func__);
423
424 return createPauliStrSum(strings.data(), coeffs.data(), coeffs.size()); // validates
425}
426
427
428extern "C" PauliStrSum createInlinePauliStrSum(const char* str) {
429
430 // str must be null-terminated
431 return createInlinePauliStrSum(string(str));
432}
433
435
436 bool rightIsLeastSig = true;
437 return parser_validateAndParsePauliStrSum(str, rightIsLeastSig, __func__);
438}
439
440
441extern "C" PauliStrSum createPauliStrSumFromFile(const char* fn) {
442
443 // fn must be null-terminated
444 return createPauliStrSumFromFile(string(fn));
445}
446
448 validate_canReadFile(fn, __func__);
449
450 // all distributed nodes will simultaneously read the file (that's fine)
451 string str = parser_loadFile(fn);
452
453 bool rightIsLeastSig = true;
454 return parser_validateAndParsePauliStrSum(str, rightIsLeastSig, __func__);
455}
456
457
459
460 // fn must be null-terminated
461 return createPauliStrSumFromReversedFile(string(fn));
462}
463
465 validate_canReadFile(fn, __func__);
466
467 // all distributed nodes will simultaneously read the file (that's fine)
468 string str = parser_loadFile(fn);
469
470 bool rightIsLeastSig = false;
471 return parser_validateAndParsePauliStrSum(str, rightIsLeastSig, __func__);
472}
473
474
475
476/*
477 * DESTROYERS
478 */
479
480
481extern "C" void destroyPauliStrSum(PauliStrSum sum) {
482 validate_pauliStrSumFields(sum, __func__);
483
484 freePauliStrSum(sum);
485}
486
487
488
489/*
490 * API REPORTERS
491 */
492
493
494extern "C" void reportPauliStr(PauliStr str) {
495
496 // no header, so no indentation
497 string indent = "";
498 print_elemsWithoutNewline(str, indent);
499
500 // print all user-set newlines (including none)
501 print_newlines();
502}
503
504
505extern "C" void reportPauliStrSum(PauliStrSum sum) {
506 validate_pauliStrSumFields(sum, __func__);
507 validate_numReportedNewlinesAboveZero(__func__);
508
509 // calculate memory usage
510 qindex numStrBytes = sum.numTerms * sizeof *sum.strings;
511 qindex numCoeffBytes = sum.numTerms * sizeof *sum.coeffs;
512 qindex numStrucBytes = sizeof(sum);
513
514 // we don't bother checking for overflow since total memory scales
515 // linearly with user input parameters, unlike Qureg and matrices.
516 qindex numTotalBytes = numStrBytes + numCoeffBytes + numStrucBytes;
517
518 print_header(sum, numTotalBytes);
519 print_elems(sum);
520
521 // exclude mandatory newline above
522 print_oneFewerNewlines();
523}
PauliStrSum createPauliStrSumFromFile(const char *fn)
Definition paulis.cpp:441
PauliStrSum createPauliStrSum(PauliStr *strings, qcomp *coeffs, qindex numTerms)
Definition paulis.cpp:396
PauliStrSum createInlinePauliStrSum(const char *str)
Definition paulis.cpp:428
PauliStrSum createPauliStrSumFromReversedFile(const char *fn)
Definition paulis.cpp:458
PauliStr getPauliStr(const char *paulis, int *indices, int numPaulis)
Definition paulis.cpp:306
void destroyPauliStrSum(PauliStrSum sum)
Definition paulis.cpp:481
void reportPauliStrSum(PauliStrSum sum)
Definition paulis.cpp:505
void reportPauliStr(PauliStr str)
Definition paulis.cpp:494
long long unsigned int PAULI_MASK_TYPE
Definition precision.h:69
Definition qureg.h:49