From dc2a35fd1fca233386d37a88a2776998c4c8fd54 Mon Sep 17 00:00:00 2001 From: Vasco Ferreira Date: Tue, 20 Jan 2026 16:14:50 +0000 Subject: [PATCH 01/10] feat(utilities): add invertPermutation --- quest/src/core/utilities.cpp | 18 ++++++++++++++++++ quest/src/core/utilities.hpp | 8 +++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 999bd9a7..861ccc73 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -1229,3 +1229,21 @@ void util_tryAllocMatrix(vector> &matr, qindex numRows, qindex num errFunc(); } } + + + +/* + * OTHER + */ + +vector util_invertPermutation(const vector& permutation) { + qindex numTerms = permutation.size(); + vector out(numTerms); + + // invert permutation + for (qindex i = 0; i < numTerms; i++) { + out[permutation[i]] = i; + } + + return out; +} diff --git a/quest/src/core/utilities.hpp b/quest/src/core/utilities.hpp index 4b7fb5db..25c59940 100644 --- a/quest/src/core/utilities.hpp +++ b/quest/src/core/utilities.hpp @@ -430,4 +430,10 @@ void util_tryAllocMatrix(vector> &vec, qindex numRows, qindex numC -#endif // UTILITIES_HPP \ No newline at end of file +/* + * OTHER + */ + +vector util_invertPermutation(const vector& permutation); + +#endif // UTILITIES_HPP From 975d78843ef7464f30f294d0260c37336ba26c6b Mon Sep 17 00:00:00 2001 From: Vasco Ferreira Date: Tue, 20 Jan 2026 16:32:18 +0000 Subject: [PATCH 02/10] feat(paulilogic): implement permutation and sorting --- quest/src/core/paulilogic.cpp | 27 +++++++++++++++++++++++++++ quest/src/core/paulilogic.hpp | 6 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 1a90dce2..4ac748ab 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -13,6 +13,7 @@ #include "quest/src/core/bitwise.hpp" #include "quest/src/core/errors.hpp" +#include #include #include #include @@ -307,6 +308,32 @@ qindex paulis_getTargetBitMask(PauliStrSum sum) { } +void paulis_applyPermutation(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_sortGeneric(PauliStrSum sum, std::function comparator) { + + // 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_invertPermutation(gatherPermutation); + paulis_applyPermutation(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 diff --git a/quest/src/core/paulilogic.hpp b/quest/src/core/paulilogic.hpp index f3748b5e..bc8b725b 100644 --- a/quest/src/core/paulilogic.hpp +++ b/quest/src/core/paulilogic.hpp @@ -76,6 +76,10 @@ int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum); qindex paulis_getTargetBitMask(PauliStrSum sum); +void paulis_applyPermutation(PauliStrSum sum, vector permutation); + +void paulis_sortGeneric(PauliStrSum sum, std::function comparator); + // below are used exclusively by Trotterisation @@ -88,4 +92,4 @@ void paulis_setPauliStrSumToScaledProdOfAdjointWithSelf(PauliStrSum out, qreal f void paulis_setPauliStrSumToShiftedConj(PauliStrSum out, PauliStrSum in, int numQubits); -#endif // PAULILOGIC_HPP \ No newline at end of file +#endif // PAULILOGIC_HPP From ca70b405a94066d408bde7e954825a5f4acddb10 Mon Sep 17 00:00:00 2001 From: Vasco Ferreira Date: Tue, 20 Jan 2026 16:33:15 +0000 Subject: [PATCH 03/10] feat(api): add paulistrsum lexicographic and magnitude sorting --- quest/include/paulis.h | 88 ++++++++++++++++++++++++++++++++++++++++ quest/src/api/paulis.cpp | 29 +++++++++++++ 2 files changed, 117 insertions(+) diff --git a/quest/include/paulis.h b/quest/include/paulis.h index 1d08169f..e4645923 100644 --- a/quest/include/paulis.h +++ b/quest/include/paulis.h @@ -99,6 +99,9 @@ typedef struct { * * @defgroup paulis_reporters Reporters * @brief Functions for printing Pauli data structures. + * + * @defgroup paulis_setters Setters + * @brief Functions for overwriting the elements of Pauli data structures. */ @@ -420,6 +423,91 @@ extern "C" { +/* + * SETTERS + */ + +// enable invocation by both C and C++ binaries +#ifdef __cplusplus +extern "C" { +#endif + + + /** @ingroup paulis_setters + * + * Reorders the terms within a @p sum of weighted Pauli strings to sort Pauli + * strings into lexicographic (dictionary) ordering. + * + * @formulae + * Let @f$ H = @f$ @p sum, which can be represented as + * @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 + * @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. + * + * @throws @validationerror + * - if @p sum is not initialised. + * + * @see + * - sortPauliStrSumMagnitude() + * @author Vasco Ferreira + */ + void sortPauliStrSumLexicographic(PauliStrSum sum); + + + /** @ingroup paulis_setters + * + * Reorders the terms within a @p sum of weighted Pauli strings to sort Pauli + * strings into decreasing magnitude weights. + * + * @formulae + * Let @f$ H = @f$ @p sum, represented as the weighted sum + * @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 + * @f[ + * |c_{\pi(i)}| > |c_{\pi(j)}| \, \forall \, \pi(i) < \pi(j). + * @f] + * + * @param[in,out] sum a weighted sum of Pauli strings to reorder. + * + * @throws @validationerror + * - if @p sum is not initialised. + * + * @see + * - sortPauliStrSumLexicographic() + * + * @author Vasco Ferreira + */ + void sortPauliStrSumMagnitude(PauliStrSum sum); + + +// end de-mangler +#ifdef __cplusplus +} +#endif + + + #endif // PAULIS_H /** @} */ // (end file-wide doxygen defgroup) diff --git a/quest/src/api/paulis.cpp b/quest/src/api/paulis.cpp index 5a2a122c..c53bf996 100644 --- a/quest/src/api/paulis.cpp +++ b/quest/src/api/paulis.cpp @@ -291,3 +291,32 @@ extern "C" void reportPauliStrSum(PauliStrSum sum) { // exclude mandatory newline above print_oneFewerNewlines(); } + + + +/* + * SETTERS + */ + +extern "C" void sortPauliStrSumLexicographic(PauliStrSum sum) { + validate_pauliStrSumFields(sum, __func__); + + auto lexSort = [&](qindex i, qindex j) { + PauliStr strI = sum.strings[i]; + PauliStr strJ = sum.strings[j]; + return std::tie(strI.highPaulis, strI.lowPaulis) < std::tie(strJ.highPaulis, strJ.lowPaulis); + }; + + paulis_sortGeneric(sum, lexSort); +} + +extern "C" void sortPauliStrSumMagnitude(PauliStrSum sum) { + validate_pauliStrSumFields(sum, __func__); + + auto magSort = [&](qindex i, qindex j) { + return std::norm(sum.coeffs[i]) > std::norm(sum.coeffs[j]); + }; + + paulis_sortGeneric(sum, magSort); +} + From 1b834a0df10ffd10ce50b3901e573c0b293ea09d Mon Sep 17 00:00:00 2001 From: Vasco Ferreira Date: Tue, 20 Jan 2026 16:33:41 +0000 Subject: [PATCH 04/10] tests(paulis): add sorting tests --- tests/unit/paulis.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/unit/paulis.cpp b/tests/unit/paulis.cpp index 8b6fc4f9..e3339100 100644 --- a/tests/unit/paulis.cpp +++ b/tests/unit/paulis.cpp @@ -587,6 +587,61 @@ TEST_CASE( "destroyPauliStrSum", TEST_CATEGORY ) { } } +TEST_CASE( "sortPauliStrSumLexicographic", TEST_CATEGORY ) { + + SECTION( LABEL_CORRECTNESS ) { + + vector coeffs = {0.1_i, 2+1_i, 5, 3+4_i}; + vector strings = { + getPauliStr("XY", {31,32}), + getPauliStr("YX", {0,1}), + getPauliStr("II", {0,1}), + getPauliStr("YY", {31,32}) + }; + + 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)); + + destroyPauliStrSum(sum); + } +} + +TEST_CASE( "sortPauliStrSumMagnitude", TEST_CATEGORY ) { + + SECTION( LABEL_CORRECTNESS ) { + + vector coeffs = {0.1_i, 2+1_i, 5, 3+4_i}; + vector strings = { + getPauliStr("XY", {0,1}), + getPauliStr("ZX", {0,1}), + getPauliStr("II", {0,1}), + getPauliStr("YZ", {0,1}) + }; + + 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); + + destroyPauliStrSum(sum); + } +} + /** @} (end defgroup) */ From 9f00655cd976421fd3cb2f94361e4ce14bac0a5d Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:09:36 -0500 Subject: [PATCH 05/10] renaming SETTERS to SORTING since not traditional setters --- quest/src/api/paulis.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quest/src/api/paulis.cpp b/quest/src/api/paulis.cpp index c53bf996..1992943f 100644 --- a/quest/src/api/paulis.cpp +++ b/quest/src/api/paulis.cpp @@ -295,9 +295,10 @@ extern "C" void reportPauliStrSum(PauliStrSum sum) { /* - * SETTERS + * SORTING */ + extern "C" void sortPauliStrSumLexicographic(PauliStrSum sum) { validate_pauliStrSumFields(sum, __func__); @@ -310,6 +311,7 @@ extern "C" void sortPauliStrSumLexicographic(PauliStrSum sum) { paulis_sortGeneric(sum, lexSort); } + extern "C" void sortPauliStrSumMagnitude(PauliStrSum sum) { validate_pauliStrSumFields(sum, __func__); @@ -319,4 +321,3 @@ extern "C" void sortPauliStrSumMagnitude(PauliStrSum sum) { paulis_sortGeneric(sum, magSort); } - From f7a2e47b3d8a55559b230230e77c88ac5a0450c8 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:10:50 -0500 Subject: [PATCH 06/10] renamed util_invertPermutation to util_getInversePermutation since imperative "invert" might incorrectly imply the passed list is modified. --- quest/src/core/paulilogic.cpp | 2 +- quest/src/core/utilities.cpp | 30 +++++++++++------------------- quest/src/core/utilities.hpp | 10 +++------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 4ac748ab..5af91cc1 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -330,7 +330,7 @@ void paulis_sortGeneric(PauliStrSum sum, std::function com std::stable_sort(gatherPermutation.begin(), gatherPermutation.end(), comparator); // invert permutation and apply - vector scatterPermutation = util_invertPermutation(gatherPermutation); + vector scatterPermutation = util_getInversePermutation(gatherPermutation); paulis_applyPermutation(sum, scatterPermutation); } diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 861ccc73..01cc2a6d 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -368,7 +368,7 @@ bool util_willSumOverflow(vector terms) { /* - * VECTOR REDUCTION + * LIST PROCESSING */ qreal util_getSum(vector list) { @@ -387,6 +387,16 @@ qreal util_getSum(vector list) { return sum; } +vector util_getInversePermutation(vector permutation) { + qindex numTerms = permutation.size(); + vector out(numTerms); + + for (qindex i = 0; i < numTerms; i++) + out[permutation[i]] = i; + + return out; +} + /* @@ -1229,21 +1239,3 @@ void util_tryAllocMatrix(vector> &matr, qindex numRows, qindex num errFunc(); } } - - - -/* - * OTHER - */ - -vector util_invertPermutation(const vector& permutation) { - qindex numTerms = permutation.size(); - vector out(numTerms); - - // invert permutation - for (qindex i = 0; i < numTerms; i++) { - out[permutation[i]] = i; - } - - return out; -} diff --git a/quest/src/core/utilities.hpp b/quest/src/core/utilities.hpp index 25c59940..f2d7087e 100644 --- a/quest/src/core/utilities.hpp +++ b/quest/src/core/utilities.hpp @@ -240,11 +240,13 @@ qcomp* util_getGpuMemPtr(T matr) { /* - * VECTOR REDUCTION + * LIST PROCESSING */ qreal util_getSum(vector list); +vector util_getInversePermutation(vector permutation); + /* @@ -430,10 +432,4 @@ void util_tryAllocMatrix(vector> &vec, qindex numRows, qindex numC -/* - * OTHER - */ - -vector util_invertPermutation(const vector& permutation); - #endif // UTILITIES_HPP From 89a05946662eabb0f94a5be335ae8988bf013395 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:25:06 -0500 Subject: [PATCH 07/10] added unguarded alloc cautions --- quest/src/core/paulilogic.cpp | 5 +++++ quest/src/core/utilities.cpp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 5af91cc1..25fc5424 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -322,8 +322,12 @@ void paulis_applyPermutation(PauliStrSum sum, vector scatterPermutation) } } + void paulis_sortGeneric(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); @@ -334,6 +338,7 @@ void paulis_sortGeneric(PauliStrSum sum, std::function com paulis_applyPermutation(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 diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 01cc2a6d..a5ca635b 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -388,6 +388,10 @@ qreal util_getSum(vector list) { } 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); From 691ecffee9f749c1e6bf77ac251c1da67f870c5a Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:28:09 -0500 Subject: [PATCH 08/10] clarified names --- quest/src/api/paulis.cpp | 4 ++-- quest/src/core/paulilogic.cpp | 6 +++--- quest/src/core/paulilogic.hpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/quest/src/api/paulis.cpp b/quest/src/api/paulis.cpp index 1992943f..e7f85a88 100644 --- a/quest/src/api/paulis.cpp +++ b/quest/src/api/paulis.cpp @@ -308,7 +308,7 @@ extern "C" void sortPauliStrSumLexicographic(PauliStrSum sum) { return std::tie(strI.highPaulis, strI.lowPaulis) < std::tie(strJ.highPaulis, strJ.lowPaulis); }; - paulis_sortGeneric(sum, lexSort); + paulis_sortTermsViaComparator(sum, lexSort); } @@ -319,5 +319,5 @@ extern "C" void sortPauliStrSumMagnitude(PauliStrSum sum) { return std::norm(sum.coeffs[i]) > std::norm(sum.coeffs[j]); }; - paulis_sortGeneric(sum, magSort); + paulis_sortTermsViaComparator(sum, magSort); } diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 25fc5424..7b4702d7 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -308,7 +308,7 @@ qindex paulis_getTargetBitMask(PauliStrSum sum) { } -void paulis_applyPermutation(PauliStrSum sum, vector scatterPermutation) { +void paulis_applyPermutationToTerms(PauliStrSum sum, vector scatterPermutation) { // permutation passed by value since we modify it // scatterPermutation[i] = destination index for element originally at i @@ -323,7 +323,7 @@ void paulis_applyPermutation(PauliStrSum sum, vector scatterPermutation) } -void paulis_sortGeneric(PauliStrSum sum, std::function comparator) { +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 @@ -335,7 +335,7 @@ void paulis_sortGeneric(PauliStrSum sum, std::function com // invert permutation and apply vector scatterPermutation = util_getInversePermutation(gatherPermutation); - paulis_applyPermutation(sum, scatterPermutation); + paulis_applyPermutationToTerms(sum, scatterPermutation); } diff --git a/quest/src/core/paulilogic.hpp b/quest/src/core/paulilogic.hpp index bc8b725b..5a77855d 100644 --- a/quest/src/core/paulilogic.hpp +++ b/quest/src/core/paulilogic.hpp @@ -76,9 +76,9 @@ int paulis_getIndOfLefmostNonIdentityPauli(PauliStrSum sum); qindex paulis_getTargetBitMask(PauliStrSum sum); -void paulis_applyPermutation(PauliStrSum sum, vector permutation); +void paulis_applyPermutationToTerms(PauliStrSum sum, vector permutation); -void paulis_sortGeneric(PauliStrSum sum, std::function comparator); +void paulis_sortTermsViaComparator(PauliStrSum sum, std::function comparator); // below are used exclusively by Trotterisation From 00f91ba5c54ebf913c83d1b4730c6ec80f101ef0 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:32:13 -0500 Subject: [PATCH 09/10] added missing includes --- quest/src/core/paulilogic.cpp | 1 + quest/src/core/paulilogic.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 7b4702d7..502b4a38 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -17,6 +17,7 @@ #include #include #include +#include using std::vector; diff --git a/quest/src/core/paulilogic.hpp b/quest/src/core/paulilogic.hpp index 5a77855d..52ffe05f 100644 --- a/quest/src/core/paulilogic.hpp +++ b/quest/src/core/paulilogic.hpp @@ -15,6 +15,7 @@ #include #include #include +#include using std::vector; From 54064a5595fb3b4ed2f7040b591f7e50ad2093a9 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Fri, 27 Feb 2026 21:37:15 -0500 Subject: [PATCH 10/10] add missing include --- quest/src/core/paulilogic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 502b4a38..aa7a06ca 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using std::vector;