diff --git a/quest/include/paulis.h b/quest/include/paulis.h index e4645923..275b0036 100644 --- a/quest/include/paulis.h +++ b/quest/include/paulis.h @@ -66,11 +66,16 @@ typedef struct { qindex numTerms; - // arbitrarily-sized collection of Pauli strings and their - // coefficients are stored in heap memory. + // numTerms-sized collection of Pauli strings and their + // corresponding coefficients, stored in heap memory. PauliStr* strings; qcomp* coeffs; + // numTerms-sized list of term indices, consulted by some + // routines (such as Trotter functions) to effectively + // reorder the terms (strings and coeffs) + qindex* ordering; + // whether the sum constitutes a Hermitian operator (0, 1, or -1 to indicate unknown), // which is lazily evaluated when a function validates Hermiticity them. The flag is // stored in heap so even copies of structs are mutable, but the pointer is immutable; @@ -101,7 +106,7 @@ typedef struct { * @brief Functions for printing Pauli data structures. * * @defgroup paulis_setters Setters - * @brief Functions for overwriting the elements of Pauli data structures. + * @brief Functions for modifying existing Pauli data structures. */ @@ -435,27 +440,26 @@ extern "C" { /** @ingroup paulis_setters * - * Reorders the terms within a @p sum of weighted Pauli strings to sort Pauli - * strings into lexicographic (dictionary) ordering. + * Reorders the terms within a @p sum of weighted Pauli strings so that Pauli strings + * are ordered lexicographically. + * + * This affects @p sum.ordering, and will change the behaviour of functions like + * applyTrotterizedPauliStrSumGadget() when randomised ordering is disabled. * * @formulae - * Let @f$ H = @f$ @p sum, which can be represented as + * + * Let @f$ H = @f$ @p sum, satisfying * @f[ H = \sum\limits_j c_j \, \hat{\sigma}_j * @f] * where @f$ c_j @f$ is the coefficient of the @f$ j @f$-th PauliStr @f$ \hat{\sigma}_j @f$. * - * This function constructs and applies the permutation @f$ \pi @f$ to @f$ H @f$ - * @f[ - H = \sum\limits_j c_{\pi(j)} \, \hat{\sigma}_{\pi(j)} - * @f] - * such that + * This function modifies @p sum.ordering to the permutation @f$ \pi @f$ such that * @f[ * \hat{\sigma}_{\pi(i)} <_{lex} \hat{\sigma}_{\pi(j)} \ \forall \ \pi(i) < \pi(j). * @f] * - * - * @param[in,out] sum a weighted sum of Pauli strings to reorder. + * @param[in,out] sum a weighted sum of Pauli strings to reorder (via @p sum.ordering) * * @throws @validationerror * - if @p sum is not initialised. @@ -469,21 +473,21 @@ extern "C" { /** @ingroup paulis_setters * - * Reorders the terms within a @p sum of weighted Pauli strings to sort Pauli - * strings into decreasing magnitude weights. + * Reorders the terms within a @p sum of weighted Pauli strings such that + * coefficients are ordered with decreasing magnitude. + * + * This affects only @p sum.ordering, and will change the behaviour of functions like + * applyTrotterizedPauliStrSumGadget() when randomised ordering is disabled. * * @formulae - * Let @f$ H = @f$ @p sum, represented as the weighted sum + * + * Let @f$ H = @f$ @p sum, satisfying * @f[ H = \sum\limits_j c_j \, \hat{\sigma}_j * @f] * where @f$ c_j @f$ is the coefficient of the @f$ j @f$-th PauliStr @f$ \hat{\sigma}_j @f$. * - * This function constructs and applies the permutation @f$ \pi @f$ to @f$ H @f$ - * @f[ - H = \sum\limits_j c_{\pi(j)} \, \hat{\sigma}_{\pi(j)} - * @f] - * such that + * This function modifies @p sum.ordering to the permutation @f$ \pi @f$ such that * @f[ * |c_{\pi(i)}| > |c_{\pi(j)}| \, \forall \, \pi(i) < \pi(j). * @f] diff --git a/quest/include/trotterisation.h b/quest/include/trotterisation.h index 9e7cad25..3bfc9191 100644 --- a/quest/include/trotterisation.h +++ b/quest/include/trotterisation.h @@ -109,14 +109,16 @@ extern "C" { * > These formulations are taken from 'Finding Exponential Product Formulas * > of Higher Orders', Naomichi Hatano and Masuo Suzuki (2005) (arXiv). * - * When @p permutePaulis=true the terms of @p sum are effected in a random order at each repetition. That is, each repetition of the Trotter-Suzuki decomposition is evaluated with the sum + * When @p permutePaulis=true, the terms of @p sum are effected in a random order at each repetition. + * That is, each repetition of the Trotter-Suzuki decomposition is evaluated with the sum * @f[ \hat{H} = \sum\limits_j^T c_{\pi(j)} \, \hat{\sigma}_{\pi(j)} * @f] - * where @f$ \pi @f$ is a randomly selected permutation. + * where @f$ \pi @f$ is a randomly selected permutation. When @p permutePaulis=false, the fixed + * ordering @f$ \pi = @f$ @p sum.ordering is used. * * @important - * Using @p permutePaulis=true will cause @p sum to be mutated by the Trotterisation. + * Using @p permutePaulis=true will mutate the ordering of @p sum (specifically @p sum.ordering). * * @equivalences * @@ -153,7 +155,8 @@ extern "C" { * when all PauliStr in @p sum = @f$ \hat{H} @f$ commute, or @p reps @f$ \rightarrow \infty @f$. * * @param[in,out] qureg the state to modify. - * @param[in] sum a weighted sum of Pauli strings to approximately exponentiate. + * @param[in,out] sum a weighted sum of Pauli strings to approximately exponentiate, + * with ordering mutated when @p permutePaulis=true. * @param[in] angle the prefactor of @p sum times @f$ i @f$ in the exponent. * @param[in] order the order of the Trotter-Suzuki decomposition (e.g. @p 1, @p 2, @p 4, ...). * @param[in] reps the number of Trotter repetitions. @@ -248,7 +251,8 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con * when all PauliStr in @p sum = @f$ \hat{H} @f$ commute. * * @param[in,out] qureg the state to modify. - * @param[in] sum a weighted sum of Pauli strings to approximately exponentiate. + * @param[in,out] sum a weighted sum of Pauli strings to approximately exponentiate, + * with ordering mutated when @p permutePaulis=true. * @param[in] angle an effective prefactor of @p sum in the exponent. * @param[in] order the order of the Trotter-Suzuki decomposition (e.g. @p 1, @p 2, @p 4, ...). * @param[in] reps the number of Trotter repetitions. @@ -391,7 +395,8 @@ extern "C" { * - applyTrotterizedNonUnitaryPauliStrSumGadget() * * @param[in,out] qureg the state to modify. - * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. + * @param[in,out] hamil the Hamiltonian as a a weighted sum of Pauli strings, + * with ordering mutated when @p permutePaulis=true. * @param[in] time the duration over which to simulate evolution. * @param[in] order the order of the Trotter-Suzuki decomposition (e.g. @p 1, @p 2, @p 4, ...). * @param[in] reps the number of Trotter repetitions. @@ -522,7 +527,8 @@ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal * - applyTrotterizedNonUnitaryPauliStrSumGadget() * * @param[in,out] qureg the state to modify. - * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. + * @param[in,out] hamil the Hamiltonian as a a weighted sum of Pauli strings, + * with ordering mutated when @p permutePaulis=true. * @param[in] tau the duration over which to simulate imaginary-time evolution. * @param[in] order the order of the Trotter-Suzuki decomposition (e.g. @p 1, @p 2, @p 4, ...). * @param[in] reps the number of Trotter repetitions. @@ -548,6 +554,11 @@ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qrea * evolution approximated by symmetrized Trotterisation of the specified @p order and number of cycles * @p reps. * + * Note the ordering of all passed PauliStrSum (through functions like sortPauliStrSumMagnitude()) will + * affect that of the internally created super-propagator and ergo the Trotter accuracy. This is overridden + * by passing @p permutePaulis=true, whereby the super-propagator order is randomised every Trotter repetition. + * This never mutates the ordering of all passed PauliStrSum. + * * @formulae * * Let @f$ \rho = @f$ @p qureg, @f$ \hat{H} = @f$ @p hamil, @f$ t = @f$ @p time, and denote the @f$ i @f$-th diff --git a/quest/src/api/paulis.cpp b/quest/src/api/paulis.cpp index e7f85a88..7853af5b 100644 --- a/quest/src/api/paulis.cpp +++ b/quest/src/api/paulis.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include using std::string; using std::vector; @@ -34,8 +36,9 @@ using std::vector; bool didAnyAllocsFailOnAnyNode(PauliStrSum sum) { bool anyFail = ( - ! mem_isAllocated(sum.strings) || - ! mem_isAllocated(sum.coeffs) || + ! mem_isAllocated(sum.strings) || + ! mem_isAllocated(sum.coeffs) || + ! mem_isAllocated(sum.ordering) || ! mem_isAllocated(sum.isApproxHermitian) ); if (comm_isInit()) @@ -50,6 +53,7 @@ void freePauliStrSum(PauliStrSum sum) { // these do not need to be allocated (freeing nullptr is legal) cpu_deallocPauliStrings(sum.strings); cpu_deallocArray(sum.coeffs); + cpu_deallocIndices(sum.ordering); util_deallocEpsilonSensitiveHeapFlag(sum.isApproxHermitian); } @@ -173,6 +177,7 @@ extern "C" PauliStrSum createPauliStrSum(PauliStr* strings, qcomp* coeffs, qinde out.numTerms = numTerms; out.strings = cpu_allocPauliStrings(numTerms); // nullptr if failed out.coeffs = cpu_allocArray(numTerms); // nullptr if failed + out.ordering = cpu_allocIndices(numTerms); // nullptr if failed out.isApproxHermitian = util_allocEpsilonSensitiveHeapFlag(); // nullptr if failed // if either alloc failed, clear both before validation to avoid leak @@ -182,6 +187,7 @@ extern "C" PauliStrSum createPauliStrSum(PauliStr* strings, qcomp* coeffs, qinde // otherwise copy given data into new heap structure, and set initial flags cpu_copyPauliStrSum(out, strings, coeffs); util_setFlagToUnknown(out.isApproxHermitian); + std::iota(out.ordering, out.ordering + out.numTerms, 0); return out; } @@ -308,7 +314,8 @@ extern "C" void sortPauliStrSumLexicographic(PauliStrSum sum) { return std::tie(strI.highPaulis, strI.lowPaulis) < std::tie(strJ.highPaulis, strJ.lowPaulis); }; - paulis_sortTermsViaComparator(sum, lexSort); + std::iota(sum.ordering, sum.ordering + sum.numTerms, 0); + std::stable_sort(sum.ordering, sum.ordering + sum.numTerms, lexSort); } @@ -319,5 +326,6 @@ extern "C" void sortPauliStrSumMagnitude(PauliStrSum sum) { return std::norm(sum.coeffs[i]) > std::norm(sum.coeffs[j]); }; - paulis_sortTermsViaComparator(sum, magSort); + std::iota(sum.ordering, sum.ordering + sum.numTerms, 0); + std::stable_sort(sum.ordering, sum.ordering + sum.numTerms, magSort); } diff --git a/quest/src/api/trotterisation.cpp b/quest/src/api/trotterisation.cpp index 700077aa..cf0583f5 100644 --- a/quest/src/api/trotterisation.cpp +++ b/quest/src/api/trotterisation.cpp @@ -18,6 +18,7 @@ #include "quest/src/core/randomiser.hpp" #include +#include using std::vector; @@ -33,9 +34,12 @@ void internal_applyFirstOrderTrotterRepetition( ) { // apply each sum term as a gadget, in forward or reverse order for (qindex i=0; i -> exp(i angle * coeff * term)|psi> qcomp arg = angle * coeff; @@ -100,16 +104,18 @@ void internal_applyAllTrotterRepetitions( // perform carefully-ordered sequence of gadgets for (int r=0; r superStrings; vector superCoeffs; - auto callbackString = [&]() { validate_tempAllocSucceeded(false, numSuperTerms, sizeof(PauliStr), __func__); }; - auto callbackCoeff = [&]() { validate_tempAllocSucceeded(false, numSuperTerms, sizeof(qcomp), __func__); }; - util_tryAllocVector(superStrings, numSuperTerms, callbackString); - util_tryAllocVector(superCoeffs, numSuperTerms, callbackCoeff); - + vector superOrdering; + auto callbackString = [&]() { validate_tempAllocSucceeded(false, numSuperTerms, sizeof(PauliStr), __func__); }; + auto callbackCoeff = [&]() { validate_tempAllocSucceeded(false, numSuperTerms, sizeof(qcomp), __func__); }; + auto callbackOrdering = [&]() { validate_tempAllocSucceeded(false, numSuperTerms, sizeof(qindex), __func__); }; + util_tryAllocVector(superStrings, numSuperTerms, callbackString); + util_tryAllocVector(superCoeffs, numSuperTerms, callbackCoeff); + util_tryAllocVector(superOrdering, numSuperTerms, callbackOrdering); + + // construct super-propagator term by term, in an order affected by + // both hamil.ordering and jumps[].ordering (inside paulis_setters()) qindex superTermInd = 0; // collect -i[H,rho] terms for (qindex n=0; n #include #include -#include -#include using std::vector; @@ -310,37 +308,6 @@ qindex paulis_getTargetBitMask(PauliStrSum sum) { } -void paulis_applyPermutationToTerms(PauliStrSum sum, vector scatterPermutation) { - // permutation passed by value since we modify it - - // scatterPermutation[i] = destination index for element originally at i - for (qindex i = 0; i < sum.numTerms; i++) { - while (scatterPermutation[i] != i) { - qindex j = scatterPermutation[i]; - std::swap(sum.strings[i], sum.strings[j]); - std::swap(sum.coeffs[i], sum.coeffs[j]); - std::swap(scatterPermutation[i], scatterPermutation[j]); - } - } -} - - -void paulis_sortTermsViaComparator(PauliStrSum sum, std::function comparator) { - - // TODO: below is an unguarded vector alloc, forgiven since a subsequent - // change (giving PauliStrSum an 'ordering' list) supersedes it - - // gatherPermutation[j] = source index of element placed at j - vector gatherPermutation(sum.numTerms); - std::iota(gatherPermutation.begin(), gatherPermutation.end(), 0); - std::stable_sort(gatherPermutation.begin(), gatherPermutation.end(), comparator); - - // invert permutation and apply - vector scatterPermutation = util_getInversePermutation(gatherPermutation); - paulis_applyPermutationToTerms(sum, scatterPermutation); -} - - void paulis_setPauliStrSumToScaledTensorProdOfConjWithSelf(PauliStrSum out, qreal factor, PauliStrSum in, int numQubits) { // sets out = factor * conj(in) (x) in, where in has dim of numQubits @@ -354,9 +321,13 @@ void paulis_setPauliStrSumToScaledTensorProdOfConjWithSelf(PauliStrSum out, qrea for (qindex j=0; j #include #include -#include using std::vector; @@ -77,10 +76,6 @@ int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum); qindex paulis_getTargetBitMask(PauliStrSum sum); -void paulis_applyPermutationToTerms(PauliStrSum sum, vector permutation); - -void paulis_sortTermsViaComparator(PauliStrSum sum, std::function comparator); - // below are used exclusively by Trotterisation diff --git a/quest/src/core/randomiser.cpp b/quest/src/core/randomiser.cpp index 1e9b4a94..554d929d 100644 --- a/quest/src/core/randomiser.cpp +++ b/quest/src/core/randomiser.cpp @@ -22,6 +22,7 @@ #include #include #include +#include using std::vector; @@ -271,18 +272,13 @@ qcomp rand_getThreadPrivateRandomAmp(std::mt19937_64 &gen, std::normal_distribut /* - * PAULI STRINGS + * PAULI STRING SUM PERMUTATION */ void rand_permutePauliStrSum(PauliStrSum &sum) { - // permute ordering of terms inplace using Fisher-Yates - for (qindex i = sum.numTerms - 1; i > 0; --i) { - std::uniform_int_distribution distrib(0, i); - qindex j = distrib(mainGenerator); - - std::swap(sum.coeffs[i], sum.coeffs[j]); - std::swap(sum.strings[i], sum.strings[j]); - } + // reset ordering to a valid [0,numTerms) before shuffling + std::iota(sum.ordering, sum.ordering + sum.numTerms, 0); + std::shuffle(sum.ordering, sum.ordering + sum.numTerms, mainGenerator); } diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index a5ca635b..e7265fd5 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -387,20 +387,6 @@ qreal util_getSum(vector list) { return sum; } -vector util_getInversePermutation(vector permutation) { - - // TODO: below is an unguarded vector alloc, forgiven since a subsequent - // change (giving PauliStrSum an 'ordering' list) supersedes it - - qindex numTerms = permutation.size(); - vector out(numTerms); - - for (qindex i = 0; i < numTerms; i++) - out[permutation[i]] = i; - - return out; -} - /* @@ -1211,6 +1197,7 @@ void tryAllocVector(vector &vec, qindex size, std::function errFunc) void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } +void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } void util_tryAllocVector(vector &vec, qindex size, std::function errFunc) { tryAllocVector(vec, size, errFunc); } diff --git a/quest/src/core/utilities.hpp b/quest/src/core/utilities.hpp index f2d7087e..181d4bec 100644 --- a/quest/src/core/utilities.hpp +++ b/quest/src/core/utilities.hpp @@ -245,8 +245,6 @@ qcomp* util_getGpuMemPtr(T matr) { qreal util_getSum(vector list); -vector util_getInversePermutation(vector permutation); - /* @@ -420,6 +418,7 @@ vector util_getVector(Qureg* ptr, int length); void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); +void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); void util_tryAllocVector(vector &vec, qindex size, std::function errFunc); diff --git a/quest/src/core/validation.cpp b/quest/src/core/validation.cpp index 3ac48505..649840a0 100644 --- a/quest/src/core/validation.cpp +++ b/quest/src/core/validation.cpp @@ -3339,8 +3339,9 @@ void validate_pauliStrSumFields(PauliStrSum sum, const char* caller) { assertThat(sum.numTerms > 0, report::INVALID_PAULI_STR_SUM_FIELDS, {{"${NUM_TERMS}", sum.numTerms}}, caller); - assertThat(mem_isAllocated(sum.coeffs), report::INVALID_PAULI_STR_HEAP_PTR, caller); - assertThat(mem_isAllocated(sum.strings), report::INVALID_PAULI_STR_HEAP_PTR, caller); + assertThat(mem_isAllocated(sum.coeffs), report::INVALID_PAULI_STR_HEAP_PTR, caller); + assertThat(mem_isAllocated(sum.strings), report::INVALID_PAULI_STR_HEAP_PTR, caller); + assertThat(mem_isAllocated(sum.ordering), report::INVALID_PAULI_STR_HEAP_PTR, caller); assertThat(mem_isAllocated(sum.isApproxHermitian), report::INVALID_HEAP_FLAG_PTR, caller); @@ -3350,6 +3351,9 @@ void validate_pauliStrSumFields(PauliStrSum sum, const char* caller) { {"${BAD_FLAG}", flag}, {"${UNKNOWN_FLAG}", validate_STRUCT_PROPERTY_UNKNOWN_FLAG}}; assertThat(flag == 0 || flag == 1 || flag == validate_STRUCT_PROPERTY_UNKNOWN_FLAG, report::INVALID_HEAP_FLAG_VALUE, vars, caller); + + // note we DO NOT check whether sum.ordering is valid, i.e. whether it is a unique list of + // integers from 0 to sum.numTerms (exclusive) in an arbitrary order } void validate_pauliStrSumIsHermitian(PauliStrSum sum, const char* caller) { diff --git a/quest/src/cpu/cpu_config.cpp b/quest/src/cpu/cpu_config.cpp index c11ec224..cd1ab084 100644 --- a/quest/src/cpu/cpu_config.cpp +++ b/quest/src/cpu/cpu_config.cpp @@ -412,6 +412,16 @@ void cpu_deallocPauliStrings(PauliStr* strings) { free(strings); } +qindex* cpu_allocIndices(qindex length) { + return (qindex*) calloc(length, sizeof(qindex)); +} + +void cpu_deallocIndices(qindex* indices) { + + // safe to free if nullptr + free(indices); +} + /* diff --git a/quest/src/cpu/cpu_config.hpp b/quest/src/cpu/cpu_config.hpp index 21d39e35..5b422362 100644 --- a/quest/src/cpu/cpu_config.hpp +++ b/quest/src/cpu/cpu_config.hpp @@ -43,6 +43,8 @@ int cpu_getCurrentNumThreads(); * MEMORY ALLOCATION */ +long cpu_getPageSize(); + qcomp* cpu_allocArray(qindex length); void cpu_deallocArray(qcomp* arr); @@ -64,8 +66,9 @@ void cpu_deallocHeapFlag(int* ptr); PauliStr* cpu_allocPauliStrings(qindex numStrings); void cpu_deallocPauliStrings(PauliStr* strings); +qindex* cpu_allocIndices(qindex length); +void cpu_deallocIndices(qindex* indices); -long cpu_getPageSize(); /* diff --git a/tests/unit/paulis.cpp b/tests/unit/paulis.cpp index e3339100..86a620ad 100644 --- a/tests/unit/paulis.cpp +++ b/tests/unit/paulis.cpp @@ -591,10 +591,11 @@ TEST_CASE( "sortPauliStrSumLexicographic", TEST_CATEGORY ) { SECTION( LABEL_CORRECTNESS ) { - vector coeffs = {0.1_i, 2+1_i, 5, 3+4_i}; + vector coeffs = {0.1_i, 2+1_i, 5, 3+4_i, .3}; // ignored vector strings = { getPauliStr("XY", {31,32}), getPauliStr("YX", {0,1}), + getPauliStr("YX", {1,0}), getPauliStr("II", {0,1}), getPauliStr("YY", {31,32}) }; @@ -602,14 +603,15 @@ TEST_CASE( "sortPauliStrSumLexicographic", TEST_CATEGORY ) { PauliStrSum sum = createPauliStrSum(strings, coeffs); sortPauliStrSumLexicographic(sum); - REQUIRE(sum.coeffs[0] == 5+0_i); - REQUIRE(sum.coeffs[1] == 2+1_i); - REQUIRE(sum.coeffs[3] == 3+4_i); - - REQUIRE(sum.strings[0].lowPaulis == 0); - REQUIRE(sum.strings[1].lowPaulis == 2 + 1*4); - REQUIRE(sum.strings[3].highPaulis == 2); - REQUIRE(sum.strings[3].lowPaulis == 2*std::pow(4, 31)); + vector refOrder = { + 3, // I1 I0 + 1, // X1 Y0 + 2, // Y1 X0 + 0, // Y32 X31 + 4 // Y32 Y31 + }; + vector apiOrder = vector(sum.ordering, sum.ordering + sum.numTerms); + REQUIRE( refOrder == apiOrder ); destroyPauliStrSum(sum); } @@ -630,13 +632,10 @@ TEST_CASE( "sortPauliStrSumMagnitude", TEST_CATEGORY ) { PauliStrSum sum = createPauliStrSum(strings, coeffs); sortPauliStrSumMagnitude(sum); - REQUIRE(sum.coeffs[0] == 5+0_i); - REQUIRE(sum.coeffs[1] == 3+4_i); - REQUIRE(sum.coeffs[3] == 0+0.1_i); - - REQUIRE(sum.strings[0].lowPaulis == 0); - REQUIRE(sum.strings[1].lowPaulis == 2 + 3*4); - REQUIRE(sum.strings[3].lowPaulis == 1 + 2*4); + // sorting is stable, so coeff=5 precedes coeff=3+4i + vector refOrder = {2, 3, 1, 0}; + vector apiOrder = vector(sum.ordering, sum.ordering + sum.numTerms); + REQUIRE( refOrder == apiOrder ); destroyPauliStrSum(sum); }