From 2dae90efda318c111f6b675a27a3e1ecf2274d8a Mon Sep 17 00:00:00 2001 From: nathandelcid Date: Sat, 6 Jun 2026 13:37:39 -0500 Subject: [PATCH 1/4] feat: new lifecycle tutorial --- examples/tutorials/min_quest_lifecycle.c | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/tutorials/min_quest_lifecycle.c diff --git a/examples/tutorials/min_quest_lifecycle.c b/examples/tutorials/min_quest_lifecycle.c new file mode 100644 index 000000000..53863929e --- /dev/null +++ b/examples/tutorials/min_quest_lifecycle.c @@ -0,0 +1,58 @@ +/** @file + * Minimal end-to-end example of a standard QuEST simulation. + * + * This example demonstrates the typical lifecycle of a QuEST program: + * 1. initialise the QuEST environment, + * 2. create a quantum register, + * 3. initialise its state, + * 4. apply operations, + * 5. report or calculate results, + * 6. destroy allocated objects, + * 7. finalise the environment. + * + * @author Nathan Delcid + */ + +#include "quest.h" + +int main(void) { + + // Initialise the QuEST environment. This prepares available hardware + // backends such as MPI, GPU acceleration and multithreading if enabled. + initQuESTEnv(); + + // Create a 2-qubit statevector Qureg. + // + // Its amplitudes correspond to: + // alpha_0 |00> + // alpha_1 |01> + // alpha_2 |10> + // alpha_3 |11> + // + // Qubit 0 is the rightmost, least-significant qubit. + Qureg qureg = createQureg(2); + + // Initialise the register to |00>. + initZeroState(qureg); + + // Apply some example operations. + // + // Hadamard on qubit 0 prepares: + // (|00> + |01>) / sqrt(2) + // + // Pauli-X on qubit 1 then flips the left qubit, giving: + // (|10> + |11>) / sqrt(2) + applyHadamard(qureg, 0); + applyPauliX(qureg, 1); + + // Report the resulting statevector amplitudes. + reportQureg(qureg); + + // Free memory allocated for the Qureg. + destroyQureg(qureg); + + // Finalise the QuEST environment. + finalizeQuESTEnv(); + + return 0; +} From f69c3ff1b4c078e793b8d65312ed753fa8045905 Mon Sep 17 00:00:00 2001 From: nathandelcid Date: Mon, 8 Jun 2026 09:56:08 -0500 Subject: [PATCH 2/4] chore: implement SWAP fusion --- quest/src/comm/comm_routines.cpp | 69 +++++++++++++++++++ quest/src/comm/comm_routines.hpp | 4 +- quest/src/core/localiser.cpp | 106 ++++++++++++++++++++++++++++-- quest/src/cpu/cpu_subroutines.cpp | 32 +++++++++ quest/src/cpu/cpu_subroutines.hpp | 6 +- 5 files changed, 210 insertions(+), 7 deletions(-) diff --git a/quest/src/comm/comm_routines.cpp b/quest/src/comm/comm_routines.cpp index cf6956454..60232046f 100644 --- a/quest/src/comm/comm_routines.cpp +++ b/quest/src/comm/comm_routines.cpp @@ -242,6 +242,56 @@ void exchangeArrays(qcomp* send, qcomp* recv, qindex numElems, int pairRank) { } +void exchangeSubBufferChunks(Qureg qureg, const vector& pairRanks, const vector& recvTagBases, qindex sendTagBase, qindex chunkSize) { +#if QUEST_COMPILE_MPI + + if (pairRanks.empty()) + return; + + MPI_Comm mpiComm = comm_getMpiComm(); + + qindex sendInd = getSubBufferSendInd(qureg); + qindex recvInd = getBufferRecvInd(); + + auto [messageSize, numMessages] = dividePow2PayloadIntoMessages(chunkSize); + qindex maxTagBase = sendTagBase; + for (qindex tagBase : recvTagBases) + maxTagBase = std::max(maxTagBase, tagBase); + + qindex numTaggedMessages = numMessages * (maxTagBase + 1); + if (numTaggedMessages > getMaxNumMessages()) + error_commNumMessagesExceedTagMax(); + + qindex numRequests = 2 * numMessages * pairRanks.size(); + vector requests(numRequests, MPI_REQUEST_NULL); + + qindex reqInd = 0; + for (qindex c=0; c<(qindex) pairRanks.size(); c++) { + qindex chunkOffset = c * chunkSize; + + for (qindex m=0; m(recvTagBases[c]*numMessages + m); + int sendTag = static_cast(sendTagBase*numMessages + m); + qindex messageOffset = chunkOffset + m*messageSize; + + MPI_Irecv( + &qureg.cpuCommBuffer[recvInd + messageOffset], + messageSize, MPI_QCOMP, pairRanks[c], recvTag, mpiComm, &requests[reqInd++]); + + MPI_Isend( + &qureg.cpuCommBuffer[sendInd + messageOffset], + messageSize, MPI_QCOMP, pairRanks[c], sendTag, mpiComm, &requests[reqInd++]); + } + } + + MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); + +#else + error_commButEnvNotDistributed(); +#endif +} + + /* * PRIVATE ASYNC SEND AND RECEIVE @@ -533,6 +583,25 @@ void comm_exchangeSubBuffers(Qureg qureg, qindex numAmps, int pairRank) { } +void comm_exchangeSubBufferChunks(Qureg qureg, const vector& pairRanks, const vector& recvTagBases, qindex sendTagBase, qindex chunkSize) { + + qindex sendInd = getSubBufferSendInd(qureg); + qindex recvInd = getBufferRecvInd(); + qindex totalSize = chunkSize * pairRanks.size(); + + assert_commBoundsAreValid(qureg, sendInd, recvInd, totalSize); + assert_bufferSendRecvDoesNotOverlap(sendInd, recvInd, totalSize); + assert_commQuregIsDistributed(qureg); + if (pairRanks.size() != recvTagBases.size()) + error_commGivenInconsistentNumSubArraysANodes(); + + for (int pairRank : pairRanks) + assert_pairRankIsDistinct(qureg, pairRank); + + exchangeSubBufferChunks(qureg, pairRanks, recvTagBases, sendTagBase, chunkSize); +} + + void comm_asynchSendSubBuffer(Qureg qureg, qindex numElems, int pairRank) { auto [sendInd, recvInd] = getSubBufferSendRecvInds(qureg); diff --git a/quest/src/comm/comm_routines.hpp b/quest/src/comm/comm_routines.hpp index e75e889f6..97fcb08a2 100644 --- a/quest/src/comm/comm_routines.hpp +++ b/quest/src/comm/comm_routines.hpp @@ -31,6 +31,8 @@ void comm_exchangeAmpsToBuffers(Qureg qureg, int pairRank); void comm_exchangeSubBuffers(Qureg qureg, qindex numAmpsAndRecvInd, int pairRank); +void comm_exchangeSubBufferChunks(Qureg qureg, const vector& pairRanks, const vector& recvTagBases, qindex sendTagBase, qindex chunkSize); + void comm_asynchSendSubBuffer(Qureg qureg, qindex numElems, int pairRank); void comm_receiveArrayToBuffer(Qureg qureg, qindex numElems, int pairRank); @@ -81,4 +83,4 @@ vector comm_gatherStringsToRoot(char* localChars, int maxNumLocalCh -#endif // COMM_ROUTINES_HPP \ No newline at end of file +#endif // COMM_ROUTINES_HPP diff --git a/quest/src/core/localiser.cpp b/quest/src/core/localiser.cpp index 83a23b921..a15155032 100644 --- a/quest/src/core/localiser.cpp +++ b/quest/src/core/localiser.cpp @@ -24,8 +24,10 @@ #include "quest/src/core/localiser.hpp" #include "quest/src/core/accelerator.hpp" #include "quest/src/comm/comm_config.hpp" +#include "quest/src/comm/comm_indices.hpp" #include "quest/src/comm/comm_routines.hpp" #include "quest/src/cpu/cpu_config.hpp" +#include "quest/src/cpu/cpu_subroutines.hpp" #include "quest/src/gpu/gpu_config.hpp" #include @@ -893,6 +895,85 @@ void localiser_statevec_anyCtrlSwap(Qureg qureg, ConstList64 ctrls, ConstList64 */ +qindex getBitMaskOfQubitsInPattern(ConstList64 qubits, qindex pattern) { + + qindex mask = 0; + for (size_t i=0; i remotePatterns; + vector pairRanks; + + for (qindex pattern=0; pattern waveRanks; + vector recvTagBases; + + for (qindex c=0; c= 2 && + ctrls.empty() && + qureg.isDistributed && + !qureg.isGpuAccelerated + ) { + multiSwapBetweenPrefixAndSuffix(qureg, suffixTargs, prefixInds); + return; + } + + // otherwise, fall back to per-SWAP communication + for (size_t i=0; i qindex cpu_statevec_packAmpsIntoBuffer(Qureg qureg, ConstList64 qubitInds, ConstList64 qubitStates); +void cpu_statevec_packAmpsIntoBufferAtOffset(Qureg qureg, ConstList64 sortedQubits, qindex qubitStateMask, qindex bufferOffset); + +void cpu_statevec_unpackAmpsFromBufferAtOffset(Qureg qureg, ConstList64 sortedQubits, qindex qubitStateMask, qindex bufferOffset); + qindex cpu_statevec_packPairSummedAmpsIntoBuffer(Qureg qureg, int qubit1, int qubit2, int qubit3, int bit2); @@ -202,4 +206,4 @@ void cpu_statevec_initDebugState_sub(Qureg qureg); void cpu_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg); -#endif // CPU_SUBROUTINES_HPP \ No newline at end of file +#endif // CPU_SUBROUTINES_HPP From 174c413b64d1a272183b60b1b1104d124fdffbd7 Mon Sep 17 00:00:00 2001 From: nathandelcid <89547706+nathandelcid@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:27:36 -0500 Subject: [PATCH 3/4] delete redundant ex --- examples/tutorials/min_quest_lifecycle.c | 58 ------------------------ 1 file changed, 58 deletions(-) delete mode 100644 examples/tutorials/min_quest_lifecycle.c diff --git a/examples/tutorials/min_quest_lifecycle.c b/examples/tutorials/min_quest_lifecycle.c deleted file mode 100644 index 53863929e..000000000 --- a/examples/tutorials/min_quest_lifecycle.c +++ /dev/null @@ -1,58 +0,0 @@ -/** @file - * Minimal end-to-end example of a standard QuEST simulation. - * - * This example demonstrates the typical lifecycle of a QuEST program: - * 1. initialise the QuEST environment, - * 2. create a quantum register, - * 3. initialise its state, - * 4. apply operations, - * 5. report or calculate results, - * 6. destroy allocated objects, - * 7. finalise the environment. - * - * @author Nathan Delcid - */ - -#include "quest.h" - -int main(void) { - - // Initialise the QuEST environment. This prepares available hardware - // backends such as MPI, GPU acceleration and multithreading if enabled. - initQuESTEnv(); - - // Create a 2-qubit statevector Qureg. - // - // Its amplitudes correspond to: - // alpha_0 |00> - // alpha_1 |01> - // alpha_2 |10> - // alpha_3 |11> - // - // Qubit 0 is the rightmost, least-significant qubit. - Qureg qureg = createQureg(2); - - // Initialise the register to |00>. - initZeroState(qureg); - - // Apply some example operations. - // - // Hadamard on qubit 0 prepares: - // (|00> + |01>) / sqrt(2) - // - // Pauli-X on qubit 1 then flips the left qubit, giving: - // (|10> + |11>) / sqrt(2) - applyHadamard(qureg, 0); - applyPauliX(qureg, 1); - - // Report the resulting statevector amplitudes. - reportQureg(qureg); - - // Free memory allocated for the Qureg. - destroyQureg(qureg); - - // Finalise the QuEST environment. - finalizeQuESTEnv(); - - return 0; -} From 1a844e9457bfaaf1036182e2ba2ed1afb24fa810 Mon Sep 17 00:00:00 2001 From: nathandelcid Date: Tue, 9 Jun 2026 09:37:02 -0500 Subject: [PATCH 4/4] Fix multi-swap fallback target handling --- quest/src/core/localiser.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/quest/src/core/localiser.cpp b/quest/src/core/localiser.cpp index a15155032..63759834d 100644 --- a/quest/src/core/localiser.cpp +++ b/quest/src/core/localiser.cpp @@ -988,7 +988,7 @@ void anyCtrlMultiSwapBetweenPrefixAndSuffix(Qureg qureg, ConstList64 ctrls, Cons /// a communicator which may be inelegant alongside our own distribution scheme. List64 suffixTargs = lists_getEmptyList64(); - List64 prefixInds = lists_getEmptyList64(); + List64 prefixTargs = lists_getEmptyList64(); for (size_t i=0; i