From 8769083314f6a35ad075263ff9accc1a2008f3af Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown <8394906+otbrown@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:11:11 +0100 Subject: [PATCH 01/11] gpu_thrust.cuh: removed thrust::[unary|binary]_function to fix compatibility with CUDA 13. Fixes #693. --- quest/src/gpu/gpu_thrust.cuh | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/quest/src/gpu/gpu_thrust.cuh b/quest/src/gpu/gpu_thrust.cuh index 9f8d8f1a..8c11188e 100644 --- a/quest/src/gpu/gpu_thrust.cuh +++ b/quest/src/gpu/gpu_thrust.cuh @@ -175,21 +175,21 @@ auto getEndPtr(FullStateDiagMatr matr) { */ -struct functor_getAmpConj : public thrust::unary_function { +struct functor_getAmpConj { __host__ __device__ cu_qcomp operator()(cu_qcomp amp) { return getCompConj(amp); } }; -struct functor_getAmpNorm : public thrust::unary_function { +struct functor_getAmpNorm { __host__ __device__ qreal operator()(cu_qcomp amp) { return getCompNorm(amp); } }; -struct functor_getAmpReal : public thrust::unary_function { +struct functor_getAmpReal { __host__ __device__ qreal operator()(cu_qcomp amp) { return getCompReal(amp); @@ -197,14 +197,14 @@ struct functor_getAmpReal : public thrust::unary_function { }; -struct functor_getAmpConjProd : public thrust::binary_function { +struct functor_getAmpConjProd { __host__ __device__ cu_qcomp operator()(cu_qcomp braAmp, cu_qcomp ketAmp) { return getCompConj(braAmp) * ketAmp; } }; -struct functor_getNormOfAmpDif : public thrust::binary_function { +struct functor_getNormOfAmpDif { __host__ __device__ qreal operator()(cu_qcomp amp1, cu_qcomp amp2) { return getCompNorm(amp1 - amp2); @@ -212,7 +212,7 @@ struct functor_getNormOfAmpDif : public thrust::binary_function { +struct functor_getExpecStateVecZTerm { // this functor computes a single term from the sum // in the expectation value of Z of a statevector @@ -229,7 +229,7 @@ struct functor_getExpecStateVecZTerm : public thrust::binary_function { +struct functor_getExpecDensMatrZTerm { // this functor computes a single term from the sum // in the expectation value of Z of a density matrix @@ -252,7 +252,7 @@ struct functor_getExpecDensMatrZTerm : public thrust::unary_function { +struct functor_getExpecStateVecPauliTerm { // this functor computes a single term from the sum in the // expectation value of a Pauli str (which necessarily contains @@ -276,7 +276,7 @@ struct functor_getExpecStateVecPauliTerm : public thrust::unary_function { +struct functor_getExpecDensMatrPauliTerm { // this functor computes a single term from the sum in the // expectation value of a Pauli str (which necessarily contains @@ -304,7 +304,7 @@ struct functor_getExpecDensMatrPauliTerm : public thrust::unary_function -struct functor_getExpecDensMatrDiagMatrTerm : public thrust::unary_function { +struct functor_getExpecDensMatrDiagMatrTerm { // this functor computes a single term from the sum in the expectation // value of a FullStateDiagMatr upon a density matrix @@ -372,7 +372,7 @@ struct functor_setAmpToPauliStrSumElem { }; -struct functor_mixAmps : public thrust::binary_function { +struct functor_mixAmps { // this functor linearly combines the given pair // of amplitudes, weighted by the fixed qreals, @@ -389,7 +389,7 @@ struct functor_mixAmps : public thrust::binary_function -struct functor_multiplyElemPowerWithAmpOrNorm : public thrust::binary_function { +struct functor_multiplyElemPowerWithAmpOrNorm { // this functor multiplies a diagonal matrix element // raised to a power (templated to optimise away the @@ -415,7 +415,7 @@ struct functor_multiplyElemPowerWithAmpOrNorm : public thrust::binary_function { +struct functor_getDiagInd { // this functor accepts the index of a statevector // basis-state and produces the index of a density @@ -435,7 +435,7 @@ struct functor_getDiagInd : public thrust::unary_function { template -struct functor_insertBits : public thrust::unary_function { +struct functor_insertBits { // this functor inserts bits into a qindex value, and // is used to enumerate specific basis-state indices @@ -463,8 +463,7 @@ struct functor_insertBits : public thrust::unary_function { template -struct functor_getFidelityTerm : public thrust::unary_function -{ +struct functor_getFidelityTerm { int rank, numQubits; qindex logNumAmpsPerNode, numAmpsPerCol; cu_qcomp *rho, *psi; @@ -503,7 +502,7 @@ struct functor_getFidelityTerm : public thrust::unary_function template -struct functor_projectStateVec : public thrust::binary_function { +struct functor_projectStateVec { // this functor multiplies an amp with zero or a // renormalisation codfficient, depending on whether @@ -540,7 +539,7 @@ struct functor_projectStateVec : public thrust::binary_function -struct functor_projectDensMatr : public thrust::binary_function { +struct functor_projectDensMatr { // this functor multiplies an amp with zero or a // renormalisation coefficient, depending on whether @@ -585,7 +584,7 @@ struct functor_projectDensMatr : public thrust::binary_function { +struct functor_setRandomStateVecAmp { // this functor generates a random, unnormalised // statevector amplitude which, after normalisation From 50484635368765f88c8bcfe53e7eae8401d3c25f Mon Sep 17 00:00:00 2001 From: Erich Essmann Date: Mon, 27 Oct 2025 17:21:40 +0000 Subject: [PATCH 02/11] Simplify installation path configuration (#697) * Simplify installation path configuration Removed unnecessary path normalization and appending for installation. * Updates CMake config for conditional installs Modifies CMakeLists to conditionally build shared libraries and install binaries only at the top-level project. Introduces the INSTALL_BINARIES option to control the inclusion of example binaries in the installation process. Corrects a typo from 'RATH' to 'RPATH' for build configurations. --- CMakeLists.txt | 28 +++++++++++++++++----------- examples/CMakeLists.txt | 12 +++++++----- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb826e18..7831a17e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,8 +40,6 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) # GNUInstallDirs to provide sensible default install directory names -cmake_path(SET ORG_INSTALL_PATH NORMALIZE "${CMAKE_INSTALL_PREFIX}") -cmake_path(APPEND CMAKE_INSTALL_PREFIX "quest") include(GNUInstallDirs) include(CMakePackageConfigHelpers) @@ -74,10 +72,11 @@ endif() # Library type -# Shared library by default -option(BUILD_SHARED_LIBS "Build shared library. Turned ON by default." ON) -message(STATUS "Shared library is turned ${BUILD_SHARED_LIBS}. Set BUILD_SHARED_LIBS to modify.") - +# Shared library by default when we are the top level cmake project +if(PROJECT_IS_TOP_LEVEL) + option(BUILD_SHARED_LIBS "Build shared library. Turned ON by default." ON) + message(STATUS "Shared library is turned ${BUILD_SHARED_LIBS}. Set BUILD_SHARED_LIBS to modify.") +endif () # Library naming set(LIB_NAME QuEST @@ -185,6 +184,7 @@ option( ) message(STATUS "Disabling of deprecated API warnings is turned ${DISABLE_DEPRECATION_WARNINGS}. Set DISABLE_DEPRECATION_WARNINGS to modify.") +option(INSTALL_BINARIES "Whether to include example and user binaries in the install." OFF) # ============================ @@ -565,10 +565,12 @@ add_executable(min_example ) target_link_libraries(min_example PRIVATE QuEST::QuEST) -install(TARGETS min_example - RUNTIME - DESTINATION ${CMAKE_INSTALL_BINDIR} -) +if (INSTALL_BINARIES) + install(TARGETS min_example + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif () # all examples optionally built @@ -577,7 +579,7 @@ if (BUILD_EXAMPLES) endif() -## RATH +## RPATH set(BUILD_RPATH_USE_ORIGIN ON) if(APPLE) set(_RPATH_ORIGIN "@loader_path") @@ -732,3 +734,7 @@ install( NAMESPACE QuEST:: DESTINATION "${QuEST_INSTALL_CONFIGDIR}" ) + +if(PROJECT_IS_TOP_LEVEL) + include(CPack) +endif () \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 00d4c343..afc8f85d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,11 +20,13 @@ function(add_example direc in_fn) add_executable(${target} ${in_fn}) target_link_libraries(${target} PUBLIC QuEST) - install( - TARGETS ${target} - RUNTIME - DESTINATION ${out_dir} - ) + if (INSTALL_BINARIES) + install( + TARGETS ${target} + RUNTIME + DESTINATION ${out_dir} + ) + endif () set_target_properties(${target} PROPERTIES From 05220287cd43c7471e12332e79d5926088ccaa58 Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Tue, 28 Oct 2025 15:09:33 +0000 Subject: [PATCH 03/11] Doc binary install. (#701) * docs/cmake.md: fixed formatting of non-default options for mt and distribution * cmake: wrapped user source install in if(INSTALL_BINARIES) * docs/cmake.md: added INSTALL_BINARIES option --- CMakeLists.txt | 6 +++++- docs/cmake.md | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7831a17e..5d308795 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -625,7 +625,11 @@ if (USER_SOURCE AND OUTPUT_EXE) add_executable(${OUTPUT_EXE} ${USER_SOURCE}) target_link_libraries(${OUTPUT_EXE} PUBLIC QuEST) - install(TARGETS ${OUTPUT_EXE} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + if (INSTALL_BINARIES) + install(TARGETS ${OUTPUT_EXE} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + setup_quest_rpath(${OUTPUT_EXE}) endif() diff --git a/docs/cmake.md b/docs/cmake.md index 9240b679..d3c23ee4 100644 --- a/docs/cmake.md +++ b/docs/cmake.md @@ -36,8 +36,9 @@ make | `VERBOSE_LIB_NAME` | (`OFF`), `ON` | When turned on `LIB_NAME` will be modified according to the other configuration options chosen. For example compiling QuEST with multithreading, distribution, and double precision with `VERBOSE_LIB_NAME` turned on creates `libQuEST-fp2+mt+mpi.so`. | | `FLOAT_PRECISION` | (`2`), `1`, `4` | Determines which floating-point precision QuEST will use: double, single, or quad. *Note: Quad precision is not supported when also compiling for GPU.* | | `BUILD_EXAMPLES` | (`OFF`), `ON` | Determines whether the example programs will be built alongside QuEST. Note that `min_example` is always built. | -| `ENABLE_MULTITHREADING` | (`ON`), OFF | Determines whether QuEST will be built with support for parallelisation with OpenMP. | -| `ENABLE_DISTRIBUTION` | (`OFF`), ON | Determines whether QuEST will be built with support for parallelisation with MPI. | +| `INSTALL_BINARIES` | (`OFF`), `ON` | Determines whether compiled binaries such as the examples will be installed as well as the QuEST library. | +| `ENABLE_MULTITHREADING` | (`ON`), `OFF` | Determines whether QuEST will be built with support for parallelisation with OpenMP. | +| `ENABLE_DISTRIBUTION` | (`OFF`), `ON` | Determines whether QuEST will be built with support for parallelisation with MPI. | | `ENABLE_CUDA` | (`OFF`), `ON` | Determines whether QuEST will be built with support for NVIDIA GPU acceleration. If turned on, `CMAKE_CUDA_ARCHITECTURES` should probably also be set. | | `ENABLE_CUQUANTUM` | (`OFF`), `ON` | Determines whether QuEST will make use of the NVIDIA CuQuantum library. Cannot be turned on if `ENABLE_CUDA` is off. | | `ENABLE_HIP` | (`OFF`), `ON` | Determines whether QuEST will be built with support for AMD GPU acceleration. If turned on, `CMAKE_HIP_ARCHITECTURES` should probably also be set. | From ae0ed4d4e023c7fd4e189b07b245d76bd732f053 Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Tue, 28 Oct 2025 15:11:02 +0000 Subject: [PATCH 04/11] Thrust Overflow Fix (#699) * gpu_thrust.cuh: modified initial thrust counting iterator declarations to use long long to avoid overflow at >30 qubits. Fixes #698. * patched test of rightapplyCompMatr distributed validation The operation validation tests previously always uses a statevector to test the "targeted amps fit in node" validation, though the rightapply*() functions cannot accept statevectors, instead only density matrices. Because the "was given a density matrix" validation happens before "targeted amps fit in node" validation, the latter intended triggered error was beaten out by the earlier unintended one. Now, we are careful to pass a density matrix Qureg to the validation of "targeted amps fit in node" when triggered by a function which 'right-applies' (and is ergo only compatible with density matrices) * changed literals to defensive type --------- Co-authored-by: Tyson Jones --- quest/src/core/bitwise.hpp | 4 +++- quest/src/gpu/gpu_thrust.cuh | 30 +++++++++++++++--------------- tests/unit/operations.cpp | 6 ++++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/quest/src/core/bitwise.hpp b/quest/src/core/bitwise.hpp index 5479c4ee..4d455c2d 100644 --- a/quest/src/core/bitwise.hpp +++ b/quest/src/core/bitwise.hpp @@ -28,7 +28,9 @@ */ -#define QINDEX_ONE 1ULL +// alternatives to type-unsafe literals +constexpr qindex QINDEX_ZERO = 0; // used by gpu_thrust.cuh +constexpr qindex QINDEX_ONE = 1; // used only here INLINE qindex powerOf2(int exponent) { diff --git a/quest/src/gpu/gpu_thrust.cuh b/quest/src/gpu/gpu_thrust.cuh index 8c11188e..3b653dfd 100644 --- a/quest/src/gpu/gpu_thrust.cuh +++ b/quest/src/gpu/gpu_thrust.cuh @@ -665,7 +665,7 @@ void thrust_fullstatediagmatr_setElemsToPauliStrSum(FullStateDiagMatr out, Pauli rank, out.numElems, logNumElemsPerNode, in.numTerms, toCuQcomps(out.gpuElems), devCoeffsPtr, devStringsPtr); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + out.numElemsPerNode; thrust::for_each(indIter, endIter, functor); } @@ -708,7 +708,7 @@ void thrust_densmatr_setAmpsToPauliStrSum_sub(Qureg qureg, PauliStrSum sum) { qureg.rank, powerOf2(qureg.numQubits), qureg.logNumAmpsPerNode, sum.numTerms, toCuQcomps(qureg.gpuAmps), devCoeffsPtr, devStringsPtr); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + qureg.numAmpsPerNode; thrust::for_each(indIter, endIter, functor); } @@ -771,7 +771,7 @@ qreal thrust_densmatr_calcTotalProb_sub(Qureg qureg) { /// reduction may be suboptimal; it may be necessary to /// implement a custom numerically-stable CUDA reduction. - auto rawIter = thrust::make_counting_iterator(0); + auto rawIter = thrust::make_counting_iterator(QINDEX_ZERO); auto indIter = thrust::make_transform_iterator(rawIter, functor_getDiagInd(qureg)); auto ampIter = thrust::make_permutation_iterator(getStartPtr(qureg), indIter); auto probIter= thrust::make_transform_iterator(ampIter, functor_getAmpReal()); @@ -791,7 +791,7 @@ qreal thrust_statevec_calcProbOfMultiQubitOutcome_sub(Qureg qureg, vector q auto indFunctor = functor_insertBits(getPtr(sortedQubits), valueMask, qubits.size()); auto probFunctor = functor_getAmpNorm(); - auto rawIter = thrust::make_counting_iterator(0); + auto rawIter = thrust::make_counting_iterator(QINDEX_ZERO); auto indIter = thrust::make_transform_iterator(rawIter, indFunctor); auto ampIter = thrust::make_permutation_iterator(getStartPtr(qureg), indIter); auto probIter = thrust::make_transform_iterator(ampIter, probFunctor); @@ -814,7 +814,7 @@ qreal thrust_densmatr_calcProbOfMultiQubitOutcome_sub(Qureg qureg, vector q auto diagIndFunctor = functor_getDiagInd(qureg); auto probFunctor = functor_getAmpReal(); - auto rawIter = thrust::make_counting_iterator(0); + auto rawIter = thrust::make_counting_iterator(QINDEX_ZERO); auto indIter = thrust::make_transform_iterator(rawIter, basisIndFunctor); auto diagIter= thrust::make_transform_iterator(indIter, diagIndFunctor); auto ampIter = thrust::make_permutation_iterator(getStartPtr(qureg), diagIter); @@ -864,7 +864,7 @@ cu_qcomp thrust_densmatr_calcFidelityWithPureState_sub(Qureg rho, Qureg psi) { rho.rank, rho.numQubits, rho.logNumAmpsPerNode, psi.numAmps, toCuQcomps(rho.gpuAmps), toCuQcomps(psi.gpuAmps)); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); qindex numIts = rho.numAmpsPerNode; cu_qcomp init = getCuQcomp(0, 0); @@ -888,7 +888,7 @@ qreal thrust_statevec_calcExpecAnyTargZ_sub(Qureg qureg, vector targs) { auto functor = functor_getExpecStateVecZTerm(mask); qreal init = 0; - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + qureg.numAmpsPerNode; return thrust::inner_product( @@ -905,7 +905,7 @@ cu_qcomp thrust_densmatr_calcExpecAnyTargZ_sub(Qureg qureg, vector targs) { auto functor = functor_getExpecDensMatrZTerm(dim, ind, mask, toCuQcomps(qureg.gpuAmps)); cu_qcomp init = getCuQcomp(0, 0); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + powerOf2(qureg.logNumColsPerNode); return thrust::transform_reduce(indIter, endIter, functor, init, thrust::plus()); @@ -920,7 +920,7 @@ cu_qcomp thrust_statevec_calcExpecPauliStr_subA(Qureg qureg, vector x, vect auto functor = functor_getExpecStateVecPauliTerm(maskXY, maskYZ, ampsPtr, ampsPtr); // amps=pairAmps cu_qcomp init = getCuQcomp(0, 0); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + qureg.numAmpsPerNode; cu_qcomp value = thrust::transform_reduce(indIter, endIter, functor, init, thrust::plus()); @@ -938,7 +938,7 @@ cu_qcomp thrust_statevec_calcExpecPauliStr_subB(Qureg qureg, vector x, vect auto functor = functor_getExpecStateVecPauliTerm(maskXY, maskYZ, ampsPtr, buffPtr); cu_qcomp init = getCuQcomp(0, 0); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + qureg.numAmpsPerNode; cu_qcomp value = thrust::transform_reduce(indIter, endIter, functor, init, thrust::plus()); @@ -956,7 +956,7 @@ cu_qcomp thrust_densmatr_calcExpecPauliStr_sub(Qureg qureg, vector x, vecto auto functor = functor_getExpecDensMatrPauliTerm(mXY, mYZ, dim, ind, toCuQcomps(qureg.gpuAmps)); cu_qcomp init = getCuQcomp(0, 0); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + powerOf2(qureg.logNumColsPerNode); cu_qcomp value = thrust::transform_reduce(indIter, endIter, functor, init, thrust::plus()); @@ -995,7 +995,7 @@ cu_qcomp thrust_densmatr_calcExpecFullStateDiagMatr_sub(Qureg qureg, FullStateDi auto functor = functor_getExpecDensMatrDiagMatrTerm(dim, ind, ampsPtr, elemsPtr, expo); cu_qcomp init = getCuQcomp(0, 0); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto endIter = indIter + powerOf2(qureg.logNumColsPerNode); return thrust::transform_reduce(indIter, endIter, functor, init, thrust::plus()); @@ -1016,7 +1016,7 @@ void thrust_statevec_multiQubitProjector_sub(Qureg qureg, vector qubits, ve auto projFunctor = functor_projectStateVec( getPtr(devQubits), qubits.size(), retainValue, renorm); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto ampIter = getStartPtr(qureg); qindex numIts = qureg.numAmpsPerNode; @@ -1033,7 +1033,7 @@ void thrust_densmatr_multiQubitProjector_sub(Qureg qureg, vector qubits, ve getPtr(devQubits), qubits.size(), qureg.rank, qureg.numQubits, qureg.logNumAmpsPerNode, retainValue, renorm); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto ampIter = getStartPtr(qureg); qindex numIts = qureg.numAmpsPerNode; @@ -1073,7 +1073,7 @@ void thrust_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qure unsigned seed = rand_getThreadSharedRandomSeed(qureg.isDistributed); auto functor = functor_setRandomStateVecAmp(seed); - auto indIter = thrust::make_counting_iterator(0); + auto indIter = thrust::make_counting_iterator(QINDEX_ZERO); auto ampIter = getStartPtr(qureg); qindex numIts = qureg.numAmpsPerNode; diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 05d46418..654bc9c2 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1042,8 +1042,10 @@ void testOperationValidation(auto operation) { SECTION( "targeted amps fit in node" ) { - // simplest to trigger validation using a statevector - qureg = getCachedStatevecs().begin()->second; + // use any qureg which is otherwise compatible + qureg = (Apply == rightapply)? + getCachedDensmatrs().begin()->second: + getCachedStatevecs().begin()->second; // can only be validated when environment AND qureg // are distributed (over more than 1 node, of course) From 3a9afdb09f4710b12077f59080cc464fb731dc86 Mon Sep 17 00:00:00 2001 From: Tyson Jones Date: Thu, 27 Nov 2025 06:54:41 -0500 Subject: [PATCH 05/11] patched validation test of rightapplyCompMatr (#703) addresses #700 --- tests/unit/operations.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 654bc9c2..adbffed3 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1043,6 +1043,8 @@ void testOperationValidation(auto operation) { SECTION( "targeted amps fit in node" ) { // use any qureg which is otherwise compatible + // (but beware statevecs vs density matrices permit + // different num targets before validation is triggered) qureg = (Apply == rightapply)? getCachedDensmatrs().begin()->second: getCachedStatevecs().begin()->second; @@ -1055,7 +1057,8 @@ void testOperationValidation(auto operation) { // can only be validated if forced ctrl qubits permit // enough remaining targets int minNumCtrls = (Ctrls == one)? 1 : 0; - int minNumTargs = numQubits - qureg.logNumNodes + 1; + int numVecQubits = (qureg.isDensityMatrix)? 2*numQubits : numQubits; + int minNumTargs = numVecQubits - qureg.logNumNodes + 1; int maxNumTargs = numQubits - minNumCtrls; if (minNumTargs > maxNumTargs) return; From e4d0d55c34a1170e477b933e15b9f9465637defc Mon Sep 17 00:00:00 2001 From: Vasco Ferreira <140641650+vaferreiQMT@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:09:58 +0000 Subject: [PATCH 06/11] Random permutation of strings in Trotterisation (#702) * Implement PauliStrSum random permutations inspired by [arXiv:1805.08385](https://arxiv.org/abs/1805.08385) * Add randomisation to Trotter functions * Document random Pauli permutations for Trotterisation * Add test for Trotter randomisation --- examples/extended/dynamics.c | 5 +- examples/extended/dynamics.cpp | 5 +- quest/include/trotterisation.h | 98 +++++++++++++++++++------------- quest/src/api/trotterisation.cpp | 66 ++++++++++++++------- quest/src/core/randomiser.cpp | 20 +++++++ quest/src/core/randomiser.hpp | 10 +++- tests/unit/trotterisation.cpp | 80 +++++++++++++++++++++++--- 7 files changed, 210 insertions(+), 74 deletions(-) diff --git a/examples/extended/dynamics.c b/examples/extended/dynamics.c index 0d6763c1..03abcc16 100644 --- a/examples/extended/dynamics.c +++ b/examples/extended/dynamics.c @@ -153,11 +153,12 @@ int main() { int order = 4; int reps = 5; int steps = 20; + bool randomise = true; for (int i=0; i qreal time = dt * (i+1); @@ -187,7 +188,7 @@ int main() { // verify results by uninterrupted higher-order simulation to target time initPlusState(qureg); - applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps); + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps, randomise); reportScalar("final ", calcExpecPauliStrSum(qureg, observ)); // clean up diff --git a/examples/extended/dynamics.cpp b/examples/extended/dynamics.cpp index 95245fb8..63614538 100644 --- a/examples/extended/dynamics.cpp +++ b/examples/extended/dynamics.cpp @@ -150,11 +150,12 @@ int main() { int order = 4; int reps = 5; int steps = 20; + bool randomise = true; for (int i=0; i qreal time = dt * (i+1); @@ -181,7 +182,7 @@ int main() { // verify results by uninterrupted higher-order simulation to target time initPlusState(qureg); - applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps); + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt*steps, order+2, reps*steps, randomise); reportScalar("final ", calcExpecPauliStrSum(qureg, observ)); // clean up diff --git a/quest/include/trotterisation.h b/quest/include/trotterisation.h index 667eedd9..7173c11b 100644 --- a/quest/include/trotterisation.h +++ b/quest/include/trotterisation.h @@ -10,9 +10,12 @@ * @{ */ + #ifndef TROTTERISATION_H #define TROTTERISATION_H +#include + #include "quest/include/qureg.h" #include "quest/include/paulis.h" #include "quest/include/matrices.h" @@ -40,9 +43,12 @@ extern "C" { * * Effects an approximation to the exponential of @p sum, weighted by @p angle times @f$ i @f$, upon @p qureg, * via the symmetrized Trotter-Suzuki decomposition (arXiv). + * * Increasing @p reps (the number of Trotter repetitions) or @p order (an even, positive integer or one) * improves the accuracy of the approximation by reducing the "Trotter error" due to non-commuting - * terms of @p sum, though increases the runtime linearly and exponentially respectively. + * terms of @p sum, though increases the runtime linearly and exponentially respectively. + * Using @p permutePaulis the ordering of terms in the sum can also be randomised, which generally + * improves the accuracy of the approximation for low order decompositions (arXiv). * * @formulae * @@ -103,6 +109,15 @@ 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 + * @f[ + \hat{H} = \sum\limits_j^T c_{\pi(j)} \, \hat{\sigma}_{\pi(j)} + * @f] + * where @f$ \pi @f$ is a randomly selected permutation. + * + * @important + * Using @p permutePaulis=true will cause @p sum to be mutated by the Trotterisation. + * * @equivalences * * - By passing @f$ \theta = - \Delta t / \hbar @f$, this function approximates unitary time evolution of a closed @@ -137,11 +152,12 @@ extern "C" { * - This function only ever effects @f$ \exp \left(\iu \, \theta \, \hat{H} \right) @f$ exactly * 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] 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. + * @param[in,out] qureg the state to modify. + * @param[in] sum a weighted sum of Pauli strings to approximately exponentiate. + * @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. + * @param[in] permutePaulis whether to randomly reorder Pauli strings at each repetition. * * @throws @validationerror * - if @p qureg or @p sum are uninitialised. @@ -157,7 +173,7 @@ extern "C" { * * @author Tyson Jones */ -void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyetdoced @@ -165,7 +181,7 @@ void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyControlledCompMatr1() -void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyetdoced @@ -173,7 +189,7 @@ void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, Pauli /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiControlledCompMatr1() -void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls, int numControls, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyetdoced @@ -181,7 +197,7 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiStateControlledCompMatr1() -void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /** @notyettested @@ -218,11 +234,12 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con * - This function only ever effects @f$ \exp \left(\iu \, \theta \, \hat{H} \right) @f$ exactly * 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] 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. + * @param[in,out] qureg the state to modify. + * @param[in] sum a weighted sum of Pauli strings to approximately exponentiate. + * @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. + * @param[in] permutePaulis whether to randomly reorder Pauli strings at each repetition. * * @throws @validationerror * - if @p qureg or @p sum are uninitialised. @@ -232,7 +249,7 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con * * @author Tyson Jones */ -void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps); +void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); // end de-mangler @@ -248,7 +265,7 @@ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, q /// @notyetdoced /// @cppvectoroverload /// @see applyTrotterizedMultiControlledPauliStrSumGadget() -void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyettested @@ -256,7 +273,7 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, std::vector states, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, std::vector controls, std::vector states, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); #endif // __cplusplus @@ -353,11 +370,12 @@ extern "C" { * - applyTrotterizedNoisyTimeEvolution() * - applyTrotterizedNonUnitaryPauliStrSumGadget() * - * @param[in,out] qureg the state to modify. - * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. - * @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. + * @param[in,out] qureg the state to modify. + * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. + * @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. + * @param[in] permutePaulis whether to randomly reorder Pauli strings at each repetition. * * @throws @validationerror * - if @p qureg or @p hamil are uninitialised. @@ -368,7 +386,7 @@ extern "C" { * * @author Tyson Jones */ -void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps); +void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); /** @notyettested @@ -484,11 +502,12 @@ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal * - applyTrotterizedUnitaryTimeEvolution() * - applyTrotterizedNonUnitaryPauliStrSumGadget() * - * @param[in,out] qureg the state to modify. - * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. - * @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. + * @param[in,out] qureg the state to modify. + * @param[in] hamil the Hamiltonian as a a weighted sum of Pauli strings. + * @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. + * @param[in] permutePaulis whether to randomly reorder Pauli strings at each repetition. * * @throws @validationerror * - if @p qureg or @p hamil are uninitialised. @@ -499,7 +518,7 @@ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal * * @author Tyson Jones */ -void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps); +void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); /** @notyettested @@ -622,14 +641,15 @@ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qrea * - applyTrotterizedUnitaryTimeEvolution() * - applyTrotterizedImaginaryTimeEvolution() * - * @param[in,out] qureg the density-matrix state to evolve and modify. - * @param[in] hamil the Hamiltonian of the qubit system (excludes any environment). - * @param[in] damps the damping rates of each jump operator in @p jumps. - * @param[in] jumps the jump operators specified as PauliStrSum. - * @param[in] numJumps the length of list @p jumps (and @p damps). - * @param[in] time the duration through which to evolve the state. - * @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. + * @param[in,out] qureg the density-matrix state to evolve and modify. + * @param[in] hamil the Hamiltonian of the qubit system (excludes any environment). + * @param[in] damps the damping rates of each jump operator in @p jumps. + * @param[in] jumps the jump operators specified as PauliStrSum. + * @param[in] numJumps the length of list @p jumps (and @p damps). + * @param[in] time the duration through which to evolve the state. + * @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. + * @param[in] permutePaulis whether to randomly reorder Pauli strings at each repetition. * * @throws @validationerror * - if @p qureg, @p hamil or any element of @p jumps are uninitialised. @@ -646,7 +666,7 @@ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qrea * * @author Tyson Jones */ -void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStrSum* jumps, int numJumps, qreal time, int order, int reps); +void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStrSum* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); // end de-mangler diff --git a/quest/src/api/trotterisation.cpp b/quest/src/api/trotterisation.cpp index af25b961..700077aa 100644 --- a/quest/src/api/trotterisation.cpp +++ b/quest/src/api/trotterisation.cpp @@ -15,6 +15,7 @@ #include "quest/src/core/localiser.hpp" #include "quest/src/core/paulilogic.hpp" #include "quest/src/core/errors.hpp" +#include "quest/src/core/randomiser.hpp" #include @@ -84,7 +85,7 @@ void internal_applyHigherOrderTrotterRepetition( void internal_applyAllTrotterRepetitions( Qureg qureg, int* controls, int* states, int numControls, - PauliStrSum sum, qcomp angle, int order, int reps, bool onlyLeftApply + PauliStrSum sum, qcomp angle, int order, int reps, bool onlyLeftApply, bool permutePaulis ) { // exp(i angle sum) = identity when angle=0 if (angle == qcomp(0,0)) @@ -98,9 +99,12 @@ void internal_applyAllTrotterRepetitions( qcomp arg = angle / reps; // perform carefully-ordered sequence of gadgets - for (int r=0; r -> U |psi>, rho -> U rho U^dagger bool onlyLeftApply = false; - internal_applyAllTrotterRepetitions(qureg, nullptr, nullptr, 0, sum, angle, order, reps, onlyLeftApply); + internal_applyAllTrotterRepetitions(qureg, nullptr, nullptr, 0, sum, angle, order, reps, onlyLeftApply, permutePaulis); } -void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis) { validate_quregFields(qureg, __func__); validate_pauliStrSumFields(sum, __func__); validate_pauliStrSumTargets(sum, qureg, __func__); @@ -167,10 +171,13 @@ void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle validate_trotterParams(qureg, order, reps, __func__); bool onlyLeftApply = false; - internal_applyAllTrotterRepetitions(qureg, nullptr, nullptr, 0, sum, angle, order, reps, onlyLeftApply); + internal_applyAllTrotterRepetitions(qureg, nullptr, nullptr, 0, sum, angle, order, reps, onlyLeftApply, permutePaulis); } -void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedControlledPauliStrSumGadget( + Qureg qureg, int control, PauliStrSum sum, + qreal angle, int order, int reps, bool permutePaulis +) { validate_quregFields(qureg, __func__); validate_pauliStrSumFields(sum, __func__); validate_pauliStrSumIsHermitian(sum, __func__); @@ -178,10 +185,13 @@ void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, Pauli validate_trotterParams(qureg, order, reps, __func__); bool onlyLeftApply = false; - internal_applyAllTrotterRepetitions(qureg, &control, nullptr, 1, sum, angle, order, reps, onlyLeftApply); + internal_applyAllTrotterRepetitions(qureg, &control, nullptr, 1, sum, angle, order, reps, onlyLeftApply, permutePaulis); } -void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls, int numControls, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedMultiControlledPauliStrSumGadget( + Qureg qureg, int* controls, int numControls, PauliStrSum sum, + qreal angle, int order, int reps, bool permutePaulis +) { validate_quregFields(qureg, __func__); validate_pauliStrSumFields(sum, __func__); validate_pauliStrSumIsHermitian(sum, __func__); @@ -189,10 +199,13 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls validate_trotterParams(qureg, order, reps, __func__); bool onlyLeftApply = false; - internal_applyAllTrotterRepetitions(qureg, controls, nullptr, numControls, sum, angle, order, reps, onlyLeftApply); + internal_applyAllTrotterRepetitions(qureg, controls, nullptr, numControls, sum, angle, order, reps, onlyLeftApply, permutePaulis); } -void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedMultiStateControlledPauliStrSumGadget( + Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, + qreal angle, int order, int reps, bool permutePaulis +) { validate_quregFields(qureg, __func__); validate_pauliStrSumFields(sum, __func__); validate_pauliStrSumIsHermitian(sum, __func__); @@ -201,20 +214,26 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con validate_trotterParams(qureg, order, reps, __func__); bool onlyLeftApply = false; - internal_applyAllTrotterRepetitions(qureg, controls, states, numControls, sum, angle, order, reps, onlyLeftApply); + internal_applyAllTrotterRepetitions(qureg, controls, states, numControls, sum, angle, order, reps, onlyLeftApply, permutePaulis); } } // end de-mangler -void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, vector controls, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedMultiControlledPauliStrSumGadget( + Qureg qureg, vector controls, PauliStrSum sum, + qreal angle, int order, int reps, bool permutePaulis +) { - applyTrotterizedMultiControlledPauliStrSumGadget(qureg, controls.data(), controls.size(), sum, angle, order, reps); + applyTrotterizedMultiControlledPauliStrSumGadget(qureg, controls.data(), controls.size(), sum, angle, order, reps, permutePaulis); } -void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, vector controls, vector states, PauliStrSum sum, qreal angle, int order, int reps) { +void applyTrotterizedMultiStateControlledPauliStrSumGadget( + Qureg qureg, vector controls, vector states, PauliStrSum sum, + qreal angle, int order, int reps, bool permutePaulis +) { validate_controlsMatchStates(controls.size(), states.size(), __func__); - applyTrotterizedMultiStateControlledPauliStrSumGadget(qureg, controls.data(), states.data(), controls.size(), sum, angle, order, reps); + applyTrotterizedMultiStateControlledPauliStrSumGadget(qureg, controls.data(), states.data(), controls.size(), sum, angle, order, reps, permutePaulis); } @@ -225,7 +244,7 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, vector 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]); + } +} diff --git a/quest/src/core/randomiser.hpp b/quest/src/core/randomiser.hpp index 179e5ccb..b867fae7 100644 --- a/quest/src/core/randomiser.hpp +++ b/quest/src/core/randomiser.hpp @@ -4,6 +4,7 @@ * quantum measurements and randomly initialising Quregs. * * @author Tyson Jones + * @author Vasco Ferreira (PauliStrSum permutation) */ #ifndef RANDOMISER_HPP @@ -61,4 +62,11 @@ qcomp rand_getThreadPrivateRandomAmp(std::mt19937_64 &gen, std::normal_distribut -#endif // RANDOMISER_HPP \ No newline at end of file +/* + * PAULI STRINGS + */ + + +void rand_permutePauliStrSum(PauliStrSum &sum); + +#endif // RANDOMISER_HPP diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index cc691402..dbf16786 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -9,6 +9,11 @@ #include "quest.h" +#include + +#include "tests/utils/cache.hpp" +#include "tests/utils/random.hpp" + /* @@ -19,24 +24,83 @@ LABEL_UNIT_TAG "[trotterisation]" +void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& refFunc, auto& regularFunc, auto& randFunc) { + + for (auto& [label, refQureg]: quregs) { + + DYNAMIC_SECTION( label ) { + initDebugState(refQureg); + + Qureg regularQureg = createCloneQureg(refQureg); + Qureg randQureg = createCloneQureg(refQureg); + + refFunc(refQureg); + regularFunc(regularQureg); + randFunc(randQureg); + + double regularDistance = calcDistance(regularQureg, refQureg); + double randDistance = calcDistance(randQureg, refQureg); + + REQUIRE( randDistance < regularDistance ); + + destroyQureg(regularQureg); + destroyQureg(randQureg); + } + } +} + +/** + * @todo + * Basic validation for randomisation, should be expanded and merged + * once the Trotterisation function tests have been implemented. + */ + +TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = getNumCachedQubits(); + int numTerms = 25; + int reps = 50; + double time = 1.0; + + int refOrder = 4; + int order = GENERATE_COPY(1, 2); + + GENERATE( range(0, 10) ); + PauliStrSum sum = createRandomPauliStrSum(numQubits, numTerms); + + auto refFunc = [&](Qureg qureg) { applyTrotterizedUnitaryTimeEvolution(qureg, sum, time, refOrder, reps, false); }; + auto regularFunc = [&](Qureg qureg) { applyTrotterizedUnitaryTimeEvolution(qureg, sum, time, order, reps, false); }; + auto randFunc = [&](Qureg qureg) { applyTrotterizedUnitaryTimeEvolution(qureg, sum, time, order, reps, true); }; + + TEST_ON_CACHED_QUREGS(getCachedDensmatrs(), refFunc, regularFunc, randFunc); + TEST_ON_CACHED_QUREGS(getCachedStatevecs(), refFunc, regularFunc, randFunc); + + destroyPauliStrSum(sum); + + } + +} + /** * @todo * UNTESTED FUNCTIONS */ -void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps); +void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); -void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls, int numControls, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps); +void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps); +void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); -void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps); +void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); -void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps); +void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); From 62ac321eea63c962398dda0fb1b0a3f96884ce3c Mon Sep 17 00:00:00 2001 From: Maurice Jamieson Date: Thu, 22 Jan 2026 17:25:15 +0000 Subject: [PATCH 07/11] Added unit tests requested by Quantum Motion (#706) * Added unit tests requested by Quantum Motion * tests/unit/trotterisation.cpp: updated time evo calls to new API * tests/unit/trotterisation.cpp: updated authorlist * Fixed valgrind errors * tests/unit/trotterisation.cpp: tuned floating-point comparison epsilon to account for worst-case scenario which is single precision, single thread * added get-arbitrary-qureg test util since it will be used frequently by new input validation * removed Qureg creation in validation tests so that test failures do not cause memory leaks and e.g. add to valgrind noise. Tests now instead use getArbitraryCachedStatevec() or getArbitraryCachedDensmatr() to obtain an existing qureg with an arbitrary deployment. * restoring missing-validation comments since the validation for these functions wasn't added. Such functions have additional tests to their tested counterparts; for example, validating that matrix elements are non-zero when given a negative exponent * fixing test category * added missing operation validation tests * fixed indentation * making spacing consistent and adding a missing Hermiticity validation to applyTrotterizedUnitaryTimeEvolution test * added warning about untested deployments * removed defunct signature * patching C++ validation err msg Previously, an error message of the C++ API was not substituting in values for its placeholder variables. This affected the C++ variants of the below functions when passing vectors for the targets and outcome parameters of mismatching length: - calcProbOfMultiQubitOutcome - leftapplyMultiQubitProjector - rightapplyMultiQubitProjector - applyMultiQubitProjector - applyForcedMultiQubitMeasurement * added missing C++ API signatures * added C++-API validation tests * updated doc warnings * added Vasco to Trotter API authorlist * merged Tyson's patches --------- Co-authored-by: Tyson Jones Co-authored-by: Maurice Jamieson Co-authored-by: Oliver Thomson Brown Co-authored-by: Oliver Thomson Brown <8394906+otbrown@users.noreply.github.com> --- quest/include/multiplication.h | 22 +- quest/include/operations.h | 24 +- quest/include/trotterisation.h | 33 +- quest/src/api/operations.cpp | 5 + quest/src/core/validation.cpp | 2 +- tests/unit/initialisations.cpp | 7 +- tests/unit/operations.cpp | 806 +++++++++++++++++++++++++++++++-- tests/unit/trotterisation.cpp | 570 ++++++++++++++++++++++- tests/utils/cache.cpp | 16 + tests/utils/cache.hpp | 3 + 10 files changed, 1422 insertions(+), 66 deletions(-) diff --git a/quest/include/multiplication.h b/quest/include/multiplication.h index 81d27a50..6422e69f 100644 --- a/quest/include/multiplication.h +++ b/quest/include/multiplication.h @@ -746,7 +746,6 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated /// @see /// - leftapplyCompMatr1() /// - applyQubitProjector() @@ -754,7 +753,6 @@ void leftapplyQubitProjector(Qureg qureg, int qubit, int outcome); /// @notyetdoced -/// @notyetvalidated /// @see /// - leftapplyCompMatr1() /// - applyMultiQubitProjector() @@ -762,7 +760,6 @@ void leftapplyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int n /// @notyetdoced -/// @notyetvalidated /// @see /// - rightapplyCompMatr1() /// - applyQubitProjector() @@ -782,6 +779,25 @@ void rightapplyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int } #endif +#ifdef __cplusplus + + +/// @notyetdoced +/// @cppvectoroverload +/// @see leftapplyMultiQubitProjector() +void leftapplyMultiQubitProjector(Qureg qureg, std::vector qubits, std::vector outcomes); + + +/// @notyetdoced +/// @cppvectoroverload +/// @see rightapplyMultiQubitProjector() +void rightapplyMultiQubitProjector(Qureg qureg, std::vector qubits, std::vector outcomes); + + +#endif + +/** @} */ + /** diff --git a/quest/include/operations.h b/quest/include/operations.h index b138f200..bd74e1f8 100644 --- a/quest/include/operations.h +++ b/quest/include/operations.h @@ -2291,32 +2291,26 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated int applyQubitMeasurement(Qureg qureg, int target); /// @notyetdoced -/// @notyetvalidated int applyQubitMeasurementAndGetProb(Qureg qureg, int target, qreal* probability); /// @notyetdoced -/// @notyetvalidated qreal applyForcedQubitMeasurement(Qureg qureg, int target, int outcome); /// @notyetdoced -/// @notyetvalidated qindex applyMultiQubitMeasurement(Qureg qureg, int* qubits, int numQubits); /// @notyetdoced -/// @notyetvalidated qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, int* qubits, int numQubits, qreal* probability); /// @notyetdoced -/// @notyetvalidated qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, int numQubits); @@ -2328,16 +2322,18 @@ qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated +/// @notyetdoced +/// @cppvectoroverload +/// @see applyMultiQubitMeasurement() +qindex applyMultiQubitMeasurement(Qureg qureg, std::vector qubits); + + /// @notyetdoced /// @cppvectoroverload /// @see applyMultiQubitMeasurementAndGetProb() qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, std::vector qubits, qreal* probability); -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyForcedMultiQubitMeasurement() @@ -2363,12 +2359,10 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated void applyQubitProjector(Qureg qureg, int target, int outcome); /// @notyetdoced -/// @notyetvalidated void applyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int numQubits); @@ -2380,8 +2374,6 @@ void applyMultiQubitProjector(Qureg qureg, int* qubits, int* outcomes, int numQu #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyMultiQubitProjector() @@ -2407,12 +2399,10 @@ extern "C" { /// @notyetdoced -/// @notyetvalidated void applyQuantumFourierTransform(Qureg qureg, int* targets, int numTargets); /// @notyetdoced -/// @notyetvalidated void applyFullQuantumFourierTransform(Qureg qureg); @@ -2424,8 +2414,6 @@ void applyFullQuantumFourierTransform(Qureg qureg); #ifdef __cplusplus -/// @notyettested -/// @notyetvalidated /// @notyetdoced /// @cppvectoroverload /// @see applyQuantumFourierTransform() diff --git a/quest/include/trotterisation.h b/quest/include/trotterisation.h index 7173c11b..9e7cad25 100644 --- a/quest/include/trotterisation.h +++ b/quest/include/trotterisation.h @@ -172,12 +172,17 @@ extern "C" { * - applyTrotterizedUnitaryTimeEvolution() * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyControlledCompMatr1() @@ -186,6 +191,10 @@ void applyTrotterizedControlledPauliStrSumGadget(Qureg qureg, int control, Pauli /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiControlledCompMatr1() @@ -194,6 +203,10 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls /// @notyetdoced /// @notyettested +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see /// - applyTrotterizedPauliStrSumGadget() /// - applyMultiStateControlledCompMatr1() @@ -248,6 +261,7 @@ void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* con * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); @@ -264,6 +278,10 @@ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, q /// @notyetvalidated /// @notyetdoced /// @cppvectoroverload +/// +/// @author Tyson Jones +/// @author Vasco Ferreira (randomisation) +/// /// @see applyTrotterizedMultiControlledPauliStrSumGadget() void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); @@ -272,6 +290,10 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, std::vector controls, std::vector states, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); @@ -294,9 +316,7 @@ extern "C" { #endif -/** @notyettested - * - * Unitarily time evolves @p qureg for the duration @p time under the time-independent Hamiltonian @p hamil, +/** Unitarily time evolves @p qureg for the duration @p time under the time-independent Hamiltonian @p hamil, * as approximated by symmetrized Trotterisation of the specified @p order and number of cycles @p reps. * * @formulae @@ -385,13 +405,12 @@ extern "C" { * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); -/** @notyettested - * - * Simulates imaginary-time evolution of @p qureg for the duration @p tau under the time-independent +/** Simulates imaginary-time evolution of @p qureg for the duration @p tau under the time-independent * Hamiltonian @p hamil, as approximated by symmetrized Trotterisation of the specified @p order and * number of cycles @p reps. * @@ -517,6 +536,7 @@ void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); @@ -665,6 +685,7 @@ void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qrea * - if @p reps is not a positive integer. * * @author Tyson Jones + * @author Vasco Ferreira (randomisation) */ void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStrSum* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); diff --git a/quest/src/api/operations.cpp b/quest/src/api/operations.cpp index e458605a..47a81e49 100644 --- a/quest/src/api/operations.cpp +++ b/quest/src/api/operations.cpp @@ -1717,6 +1717,11 @@ qreal applyForcedMultiQubitMeasurement(Qureg qureg, int* qubits, int* outcomes, } // end de-mangler +qindex applyMultiQubitMeasurement(Qureg qureg, vector qubits) { + + return applyMultiQubitMeasurement(qureg, qubits.data(), qubits.size()); +} + qindex applyMultiQubitMeasurementAndGetProb(Qureg qureg, vector qubits, qreal* probability) { return applyMultiQubitMeasurementAndGetProb(qureg, qubits.data(), qubits.size(), probability); diff --git a/quest/src/core/validation.cpp b/quest/src/core/validation.cpp index 2639b46f..3ac48505 100644 --- a/quest/src/core/validation.cpp +++ b/quest/src/core/validation.cpp @@ -3765,7 +3765,7 @@ void validate_measurementOutcomesMatchTargets(int numQubits, int numOutcomes, co {"${NUM_QUBITS}", numQubits}, {"${NUM_OUTCOMES}", numOutcomes}}; - assertThat(numQubits == numOutcomes, report::MEASUREMENT_OUTCOMES_MISMATCH_NUM_TARGETS, caller); + assertThat(numQubits == numOutcomes, report::MEASUREMENT_OUTCOMES_MISMATCH_NUM_TARGETS, vars, caller); } diff --git a/tests/unit/initialisations.cpp b/tests/unit/initialisations.cpp index 649b9290..ac1f1abd 100644 --- a/tests/unit/initialisations.cpp +++ b/tests/unit/initialisations.cpp @@ -486,8 +486,7 @@ TEST_CASE( "setQuregToWeightedSum", TEST_CATEGORY ) { SECTION( LABEL_VALIDATION ) { - // arbitrary existing qureg - Qureg qureg = getCachedStatevecs().begin()->second; + Qureg qureg = getArbitraryCachedStatevec(); SECTION( "out qureg uninitialised" ) { @@ -702,7 +701,7 @@ TEST_CASE( "setQuregToMixture", TEST_CATEGORY ) { SECTION( "out qureg is statevector" ) { - Qureg badQureg = getCachedStatevecs().begin()->second; + Qureg badQureg = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( setQuregToMixture(badQureg, nullptr, nullptr, 1), @@ -717,7 +716,7 @@ TEST_CASE( "setQuregToMixture", TEST_CATEGORY ) { // hide a statevector among them int badInd = GENERATE_COPY( range(0,numIn) ); - inQuregs[badInd] = getCachedStatevecs().begin()->second;; + inQuregs[badInd] = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( setQuregToMixture(qureg, nullptr, inQuregs.data(), numIn), diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index adbffed3..a0a99086 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "tests/utils/config.hpp" @@ -1046,8 +1047,8 @@ void testOperationValidation(auto operation) { // (but beware statevecs vs density matrices permit // different num targets before validation is triggered) qureg = (Apply == rightapply)? - getCachedDensmatrs().begin()->second: - getCachedStatevecs().begin()->second; + getArbitraryCachedDensmatr(): + getArbitraryCachedStatevec(); // can only be validated when environment AND qureg // are distributed (over more than 1 node, of course) @@ -1137,9 +1138,8 @@ void testOperationValidation(auto operation) { if (Apply != rightapply) return; - // use any statevector - qureg = getCachedStatevecs().begin()->second; - + // override qureg in apiyFunc with a statevector + qureg = getArbitraryCachedStatevec(); REQUIRE_THROWS_WITH( apiFunc(), ContainsSubstring("Expected a density matrix") ); } @@ -1411,7 +1411,55 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targs[] = {0, 1, 2}; + int numTargs = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(badQureg, targs, numTargs), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, badTargs, 3), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargs[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, dupTargs, 3), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE_COPY( -1, 0 ); + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("targets") || ContainsSubstring("target qubits") + ); + + badNumTargs = qureg.numQubits+1; + REQUIRE_THROWS_WITH( + applyQuantumFourierTransform(qureg, targs, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1456,7 +1504,20 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyFullQuantumFourierTransform(badQureg), + ContainsSubstring("invalid Qureg") + ); + } + } } @@ -1482,7 +1543,40 @@ TEST_CASE( "applyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + applyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } @@ -1508,7 +1602,75 @@ TEST_CASE( "applyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + applyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("outcomes") && ContainsSubstring("inconsistent with the given number of qubits") + ); + } + + // projector does NOT validate outcome probability + } } @@ -1547,7 +1709,77 @@ TEST_CASE( "applyForcedQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + // below validation tests assume qubit 0 can collapse to either outcome + // (which does not require normalisation; qureg can be in the debug state) + initDebugState(qureg); + REQUIRE( calcProbOfQubitOutcome(qureg, 0, 0) > getValidationEpsilon() ); + REQUIRE( calcProbOfQubitOutcome(qureg, 0, 1) > getValidationEpsilon() ); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + SECTION( "improbable outcome" ) { + + // precisely zero probability outcome + initZeroState(qureg); + int badOutcome = 1; // impossible + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("impossibly unlikely") + ); + + // outcome of non-zero probability smaller than epsilon + qreal badTheta = 1E-8; + applyRotateX(qureg, 0, badTheta); // causes prob(1) = sin^2(theta) ~ 2.5E-17 + REQUIRE_THROWS_WITH( + applyForcedQubitMeasurement(qureg, 0, badOutcome), + ContainsSubstring("impossibly unlikely") + ); + + // confirm that >epsilon probability is fine + initZeroState(qureg); + qreal goodTheta = 0.1; + applyRotateX(qureg, 0, goodTheta); + REQUIRE( + calcProbOfQubitOutcome(qureg, 0, badOutcome) > getValidationEpsilon() + ); + REQUIRE_NOTHROW( + applyForcedQubitMeasurement(qureg, 0, badOutcome) + ); + + // restore qureg state + initDebugState(qureg); + } + } } @@ -1593,7 +1825,111 @@ TEST_CASE( "applyForcedMultiQubitMeasurement", TEST_CATEGORY_OPS ) { setValidationEpsilonToDefault(); } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + // below validation tests assume the above parameters are valid (not impossibly unlikely) + initDebugState(qureg); + REQUIRE( calcProbOfMultiQubitOutcome(qureg, targets, outcomes, numTargets) > getValidationEpsilon() ); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 )}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "improbable outcomes" ) { + + // impossible outcome + initZeroState(qureg); + int badOutcomes[] = {0, 0, 1}; + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("impossibly unlikely") + ); + + // outcome of non-zero probability smaller than epsilon + qreal badTheta = 1E-8; + applyRotateX(qureg, targets[2], badTheta); + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("impossibly unlikely") + ); + + // confirm that >epsilon probability is fine + initZeroState(qureg); + qreal goodTheta = 0.1; + applyRotateX(qureg, targets[2], goodTheta); + int goodOutcomes[] = {0, 0, 1}; + REQUIRE( + calcProbOfMultiQubitOutcome(qureg, targets, goodOutcomes, numTargets) > getValidationEpsilon() + ); + REQUIRE_NOTHROW( + applyForcedMultiQubitMeasurement(qureg, targets, goodOutcomes, numTargets) + ); + + // restore qureg state + initDebugState(qureg); + } + + SECTION( "targets mismatch outcomes (C++ only)") { + + REQUIRE_THROWS_WITH( + applyForcedMultiQubitMeasurement(qureg, {0,1}, {0,1,1}), + ContainsSubstring("inconsistent") + ); + } + } } @@ -1630,7 +1966,55 @@ TEST_CASE( "applyMultiQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(badQureg, targets, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY(-1, qureg.numQubits)}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, badTargets, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, dupTargets, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurement(qureg, targets, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1669,7 +2053,56 @@ TEST_CASE( "applyMultiQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int numTargets = 3; + qreal outProb = 0; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(badQureg, targets, numTargets, nullptr), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits )}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, badTargets, numTargets, &outProb), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, dupTargets, numTargets, &outProb), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + applyMultiQubitMeasurementAndGetProb(qureg, targets, badNumTargs, &outProb), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + } } @@ -1705,7 +2138,29 @@ TEST_CASE( "applyQubitMeasurement", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurement(badQureg, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitMeasurement(qureg, badTarget), + ContainsSubstring("target") + ); + } + } } @@ -1743,7 +2198,30 @@ TEST_CASE( "applyQubitMeasurementAndGetProb", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + qreal outProb = 0; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(badQureg, 0, &outProb), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + applyQubitMeasurementAndGetProb(qureg, badTarget, &outProb), + ContainsSubstring("target") + ); + } + } } @@ -1859,7 +2337,23 @@ TEST_CASE( "applyNonUnitaryPauliGadget", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStr str = getPauliStr("XY", {0, 1}); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyNonUnitaryPauliGadget(badQureg, str, qcomp(0.5, 0)), + ContainsSubstring("invalid Qureg") + ); + } + + /// @todo remaining input validation + } } @@ -2097,7 +2591,7 @@ TEST_CASE( "rightapplyFullStateDiagMatrPower", TEST_CATEGORY_MULT LABEL_MIXED_DE } -TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2119,11 +2613,44 @@ TEST_CASE( "leftapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + leftapplyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2144,11 +2671,53 @@ TEST_CASE( "rightapplyQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedDensmatr(); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(badQureg, 0, 0), + ContainsSubstring("received a statevector") + ); + } + + SECTION( "invalid target qubit" ) { + + int badTarget = GENERATE_COPY( -1, qureg.numQubits ); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(qureg, badTarget, 0), + ContainsSubstring("target") + ); + } + + SECTION( "invalid outcome" ) { + + int badOutcome = GENERATE_COPY( -1, 2 ); + REQUIRE_THROWS_WITH( + rightapplyQubitProjector(qureg, 0, badOutcome), + ContainsSubstring("outcome") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2170,11 +2739,79 @@ TEST_CASE( "leftapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + leftapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + + // projector does NOT validate outcome probability + } } -TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { +TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_MULT ) { PREPARE_TEST( numQubits, statevecQuregs, densmatrQuregs, statevecRef, densmatrRef ); @@ -2195,7 +2832,84 @@ TEST_CASE( "rightapplyMultiQubitProjector", TEST_CATEGORY_OPS ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedDensmatr(); + int targets[] = {0, 1, 2}; + int outcomes[] = {0, 1, 0}; + int numTargets = 3; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "qureg is not density matrix" ) { + + Qureg badQureg = getArbitraryCachedStatevec(); + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(badQureg, targets, outcomes, numTargets), + ContainsSubstring("received a statevector") + ); + } + + SECTION( "invalid target qubits" ) { + + int badTargets[] = {0, 1, GENERATE_COPY( -1, qureg.numQubits) }; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, badTargets, outcomes, numTargets), + ContainsSubstring("target") + ); + } + + SECTION( "duplicate target qubits" ) { + + int dupTargets[] = {0, 1, 1}; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, dupTargets, outcomes, numTargets), + ContainsSubstring("duplicate") + ); + } + + SECTION( "invalid number of targets" ) { + + int badNumTargs = GENERATE( 0, -1 ); + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("targets") + ); + + badNumTargs = qureg.numQubits + 1; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, outcomes, badNumTargs), + ContainsSubstring("exceeds the number of qubits in the Qureg") + ); + } + + SECTION( "invalid outcomes" ) { + + int badOutcomes[] = {0, 1, GENERATE( -1, 2 ) }; + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, targets, badOutcomes, numTargets), + ContainsSubstring("outcome") + ); + } + + SECTION( "targets mismatch outcomes (C++ only)" ) { + + REQUIRE_THROWS_WITH( + rightapplyMultiQubitProjector(qureg, {0,1,2}, {0,1}), + ContainsSubstring("inconsistent") + ); + } + + // projector does NOT validate outcome probability + } } @@ -2225,7 +2939,27 @@ TEST_CASE( "leftapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) { SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + leftapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyPauliStrSum(sum); + + /// @todo remaining input validation + } } @@ -2254,7 +2988,27 @@ TEST_CASE( "rightapplyPauliStrSum", TEST_CATEGORY_MULT LABEL_MIXED_DEPLOY_TAG ) SECTION( LABEL_DENSMATR ) { TEST_ON_CACHED_QUREGS(densmatrQuregs, densmatrRef, testFunc); } } - /// @todo input validation + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum sum = createRandomPauliStrSum(numQubits, 2); + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + Qureg workspace = createCloneQureg(qureg); + REQUIRE_THROWS_WITH( + rightapplyPauliStrSum(badQureg, sum, workspace), + ContainsSubstring("invalid Qureg") + ); + destroyQureg(workspace); + } + + destroyPauliStrSum(sum); + + /// @todo remaining input validation + } } diff --git a/tests/unit/trotterisation.cpp b/tests/unit/trotterisation.cpp index dbf16786..35af81ba 100644 --- a/tests/unit/trotterisation.cpp +++ b/tests/unit/trotterisation.cpp @@ -2,6 +2,9 @@ * Unit tests of the trotterisation module. * * @author Tyson Jones + * @author Vasco Ferreira (initial Pauli permutation tests) + * @author Maurice Jamieson (real and imaginary time evolution tests) + * @author Oliver Thomson Brown (real and imaginary time evolution tests) * * @defgroup unittrotter Trotterisation * @ingroup unittests @@ -9,12 +12,22 @@ #include "quest.h" +#include #include +#include +#include +#include "tests/utils/macros.hpp" #include "tests/utils/cache.hpp" +#include "tests/utils/compare.hpp" #include "tests/utils/random.hpp" +#include +#include +using std::vector; +using std::string; +using namespace Catch::Matchers; /* * UTILITIES @@ -23,9 +36,7 @@ #define TEST_CATEGORY \ LABEL_UNIT_TAG "[trotterisation]" - void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& refFunc, auto& regularFunc, auto& randFunc) { - for (auto& [label, refQureg]: quregs) { DYNAMIC_SECTION( label ) { @@ -49,12 +60,133 @@ void TEST_ON_CACHED_QUREGS(quregCache quregs, auto& refFunc, auto& regularFunc, } } +/* + * Prepare a Hamiltonian H under which dynamical + * evolution will be simulated via Trotterisation + * of unitary-time evolution operator e^(-itH). + * If the Hamiltonian was fixed/known in advance, + * we could instead use createInlinePauliStrSum() + * + * (Adapted from dynamics.cpp, @author Tyson Jones) + */ +PauliStrSum createHeisenbergHamiltonian(int numQubits) { + + // we prepare a Heisenberg XYZ spin-ring Hamiltonian, + // i.e. H = -1/2 sum( Jx XX + Jy YY + Jz ZZ + h Z ) + // upon all nearest neighbour qubits, with periodicity. + // The coefficients must be real for H to be Hermitian + // and ergo its time-evolution operator to be unitary, + // although they must be represented with a qcomp type. + vector operators = {"XX", "YY", "ZZ", "Z"}; + vector coefficients = {.1, .2, .3, .4}; // Jx,Jy,Jz,h + + // we will populate the below vectors with 4*numQubits + // elements which we could pre-allocate with .reserve, + // but we might incur Donald Knuth's justified wrath. + vector allStrings; + vector allCoeffs; + + // prepare all XX + YY + ZZ + for (int p=0; p<3; p++) { + for (int i=0; i targs = {i, (i+1)%numQubits}; + PauliStr str = getPauliStr(operators[p], targs); + + allStrings.push_back(str); + allCoeffs.push_back(coefficients[p]); + } + } + + // prepare Z + for (int i=0; i strings(numQubits); + vector coeffs(numQubits); + + for (int i=0; i}^{N} Z_{i}Z_{j} + * where, + * \mu = magField, + * J = interactionStrength, + * indicates nearest-neighbour interactions only, + * and boundary conditions are periodic such that site N-1 interacts with site 0. + * + * The asymmetricBias term can be used to break the symmetry of the system + * in order to 'choose' a preferred antiferromagnetic state, and ensure repeatable + * predictable outcomes. + * It adds a term of the form: + * -BZ_{0} + */ +PauliStrSum createIsingHamiltonian(int numQubits, qreal magField, + qreal interactionStrength, qreal asymmetricBias) { + const int NTERMS = 2 * numQubits + 1; + + vector coeffs; + vector pauli_terms; + coeffs.reserve(NTERMS); + pauli_terms.reserve(NTERMS); + + for (int i = 0; i < numQubits; ++i) { + pauli_terms.push_back(getPauliStr("Z", {i})); + coeffs.push_back(getQcomp(-magField, 0)); + + int next = (i + 1) % numQubits; + pauli_terms.push_back(getPauliStr("ZZ", {i, next})); + coeffs.push_back(getQcomp(-interactionStrength, 0)); + } + + pauli_terms.push_back(getPauliStr("Z", {0})); + coeffs.push_back(getQcomp(-asymmetricBias, 0)); + + return createPauliStrSum(pauli_terms, coeffs); +} + + +/* + * TESTS + */ + + /** * @todo * Basic validation for randomisation, should be expanded and merged * once the Trotterisation function tests have been implemented. */ - TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { SECTION( LABEL_CORRECTNESS ) { @@ -80,13 +212,439 @@ TEST_CASE( "randomisedTrotter", TEST_CATEGORY ) { destroyPauliStrSum(sum); } +} + +/* +* Time evolution tests +* @todo Add Pauli permutation variants +*/ +TEST_CASE( "applyTrotterizedUnitaryTimeEvolution", TEST_CATEGORY ) { + + // BEWARE: this test creates a new Qureg below which will have + // deployments chosen by the auto-deployer; it is ergo unpredictable + // whether it will be multithreaded, GPU-accelerated or distributed. + // This test is ergo checking only a single, unspecified deployment, + // unlike other tests which check all deployments. This is tolerable + // since (non-randomised) Trotterisation is merely invoking routines + // (Pauli gadgets) already independently tested across deployments + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = 20; + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + bool permutePaulis = false; + + PauliStrSum hamil = createHeisenbergHamiltonian(numQubits); + PauliStrSum observ = createAlternatingPauliObservable(numQubits); + + qreal dt = 0.1; + int order = 4; + int reps = 5; + int steps = 10; + + // nudge the epsilon used by internal validation functions up a bit + // as the time evolution operation plays badly with single precision + // Defaults for validation epsilon are: + // - 1E-5 at single precision + // - 1E-12 at double precision + // - 1E-13 at quad precision + qreal initialValidationEps = getValidationEpsilon(); + setValidationEpsilon(2 * initialValidationEps); + + /* + * Tolerance for floating-point comparisons + * Note that the underlying numerics are sensitive to the float + * precision AND to the number of threads. As such we set quite + * large epsilon values to account for the worst-case scenario which + * is single precision, single thread. The baseline for these results + * is double precision, multiple threads. + * + * Values (assuming default initialValidationEps) are: + * Single precision: + * obsEps = 0.03 + * normEps = 0.001 + * + * Double precision: + * obsEps = 3E-9 + * normEps = 1E-10 + * + * Quad precision: + * obsEps = 3E-10 + * normEps = 1E-11 + */ + qreal obsEps = 3E3 * initialValidationEps; + qreal normEps = 100 * initialValidationEps; + + vector refObservables = { + 19.26827777028073, + 20.34277275871839, + 21.21120737889526, + 21.86585902741717, + 22.30371711358924, + 22.52644660547882, + 22.54015748825067, + 22.35499202583118, + 21.9845541501027, + 21.44521638719462 + }; + + for (int i = 0; i < steps; i++) { + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, dt, order, reps, permutePaulis); + qreal expec = calcExpecPauliStrSum(qureg, observ); + + REQUIRE_THAT( expec, WithinAbs(refObservables[i], obsEps) ); + } + + // Verify state remains normalized + REQUIRE_THAT( calcTotalProb(qureg), WithinAbs(1.0, normEps) ); + + // Restore validation epsilon + setValidationEpsilon(initialValidationEps); + + destroyQureg(qureg); + destroyPauliStrSum(hamil); + destroyPauliStrSum(observ); + } + + SECTION( LABEL_VALIDATION ) { + + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum hamil = createHeisenbergHamiltonian(qureg.numQubits); + bool permutePaulis = false; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(badQureg, hamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + + PauliStrSum badHamil = hamil; + badHamil.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, badHamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Pauli") + ); + } + + SECTION( "hamiltonian not hermitian" ) { + + vector strings; + vector coeffs; + strings.push_back(getPauliStr("X", {0})); + coeffs.push_back(getQcomp(1.0, 1.0)); + PauliStrSum nonHermitian = createPauliStrSum(strings, coeffs); + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Hermitian") + ); + destroyPauliStrSum(nonHermitian); + } + + SECTION( "pauli sum exceeds qureg qubits" ) { + + PauliStrSum largeHamil = createHeisenbergHamiltonian(qureg.numQubits + 1); + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, largeHamil, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyPauliStrSum(largeHamil); + } + + SECTION( "invalid trotter order (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 0, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, -2, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 3, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, 0, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedUnitaryTimeEvolution(qureg, hamil, 0.1, 4, -3, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + destroyPauliStrSum(hamil); + } +} + + +TEST_CASE( "applyTrotterizedImaginaryTimeEvolution", TEST_CATEGORY ) { + + // BEWARE: this test creates a new Qureg below which will have + // deployments chosen by the auto-deployer; it is ergo unpredictable + // whether it will be multithreaded, GPU-accelerated or distributed. + // This test is ergo checking only a single, unspecified deployment, + // unlike other tests which check all deployments. This is tolerable + // since (non-randomised) Trotterisation is merely invoking routines + // (Pauli gadgets) already independently tested across deployments + + SECTION( LABEL_CORRECTNESS ) { + + int numQubits = 16; + qreal tau = 0.1; + int order = 6; + int reps = 5; + int steps = 10; + bool permutePaulis = false; + + // Tolerance for ground state amplitude + qreal eps = 1E-2; + + // Ground state: all qubits align down (driven by strong magnetic field) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 10.0, 0.0, 0.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + qcomp amp = getQuregAmp(qureg, 0); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 1; i < (1LL << numQubits); i++) { + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: all qubits align up (driven by opposite magnetic field) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, -10.0, 0.0, 0.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + long long last_state = (1LL << numQubits) - 1; + qcomp amp = getQuregAmp(qureg, last_state); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 0; i < (1LL << numQubits); i++) { + if (i == last_state) continue; + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: all qubits align down (driven by ferromagnetic interactions and bias) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, 10.0, 10.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + qcomp amp = getQuregAmp(qureg, 0); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 1; i < (1LL << numQubits); i++) { + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + + // Ground state: alternating pattern (driven by antiferromagnetic interactions) + { + Qureg qureg = createQureg(numQubits); + initPlusState(qureg); + + PauliStrSum ising = createIsingHamiltonian(numQubits, 0.0, -10.0, 10.0); + + for (int i = 0; i < steps; ++i) { + applyTrotterizedImaginaryTimeEvolution(qureg, ising, tau, order, reps, permutePaulis); + setQuregToRenormalized(qureg); + } + + unsigned long long idx = 0; + for (int i = 0; i < numQubits / 2; ++i) { + idx += (1ULL << (2*i + 1)); + } + + qcomp amp = getQuregAmp(qureg, idx); + qreal amp_mag = amp.real() * amp.real() + amp.imag() * amp.imag(); + + REQUIRE_THAT( amp_mag, WithinAbs(1.0, eps) ); + + for (long long i = 0; i < (1LL << numQubits); i++) { + if (i == idx) continue; + qcomp other_amp = getQuregAmp(qureg, i); + qreal other_mag = other_amp.real() * other_amp.real() + + other_amp.imag() * other_amp.imag(); + REQUIRE( other_mag < eps ); + } + + destroyQureg(qureg); + destroyPauliStrSum(ising); + } + } + + SECTION( LABEL_VALIDATION ) { + Qureg qureg = getArbitraryCachedStatevec(); + PauliStrSum ising = createIsingHamiltonian(qureg.numQubits, 1.0, 1.0, 0.0); + bool permutePaulis = false; + + SECTION( "qureg uninitialised" ) { + + Qureg badQureg = qureg; + badQureg.numQubits = -1; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(badQureg, ising, 0.1, 4, 5, permutePaulis), + ContainsSubstring("invalid Qureg") + ); + } + + SECTION( "pauli sum uninitialized" ) { + + PauliStrSum badIsing = ising; + badIsing.numTerms = 0; + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, badIsing, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Pauli") + ); + } + + SECTION( "pauli sum exceeds qureg qubits" ) { + + PauliStrSum largeIsing = createIsingHamiltonian(qureg.numQubits+1, 1.0, 1.0, 0.0); + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, largeIsing, 0.1, 4, 5, permutePaulis), + ContainsSubstring("only compatible") + ); + destroyPauliStrSum(largeIsing); + } + + SECTION( "hamiltonian not hermitian" ) { + + vector strings; + vector coeffs; + strings.push_back(getPauliStr("X", {0})); + coeffs.push_back(getQcomp(1.0, 1.0)); + PauliStrSum nonHermitian = createPauliStrSum(strings, coeffs); + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, nonHermitian, 0.1, 4, 5, permutePaulis), + ContainsSubstring("Hermitian") + ); + destroyPauliStrSum(nonHermitian); + } + + SECTION( "invalid trotter order (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 0, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, -2, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter order (odd, not 1)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 3, 5, permutePaulis), + ContainsSubstring("order") + ); + } + + SECTION( "invalid trotter reps (zero)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, 0, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + SECTION( "invalid trotter reps (negative)" ) { + + REQUIRE_THROWS_WITH( + applyTrotterizedImaginaryTimeEvolution(qureg, ising, 0.1, 4, -3, permutePaulis), + ContainsSubstring("repetitions") + ); + } + + destroyPauliStrSum(ising); + } } /** * @todo - * UNTESTED FUNCTIONS + * UNTESTED FUNCTIONS (NOT YET VALIDATED BY REFERENCE TESTS) */ void applyTrotterizedNonUnitaryPauliStrSumGadget(Qureg qureg, PauliStrSum sum, qcomp angle, int order, int reps, bool permutePaulis); @@ -99,8 +657,4 @@ void applyTrotterizedMultiControlledPauliStrSumGadget(Qureg qureg, int* controls void applyTrotterizedMultiStateControlledPauliStrSumGadget(Qureg qureg, int* controls, int* states, int numControls, PauliStrSum sum, qreal angle, int order, int reps, bool permutePaulis); -void applyTrotterizedUnitaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal time, int order, int reps, bool permutePaulis); - -void applyTrotterizedImaginaryTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal tau, int order, int reps, bool permutePaulis); - void applyTrotterizedNoisyTimeEvolution(Qureg qureg, PauliStrSum hamil, qreal* damps, PauliStr* jumps, int numJumps, qreal time, int order, int reps, bool permutePaulis); diff --git a/tests/utils/cache.cpp b/tests/utils/cache.cpp index ec5001bc..0cb8a603 100644 --- a/tests/utils/cache.cpp +++ b/tests/utils/cache.cpp @@ -168,6 +168,22 @@ quregCache getAltCachedDensmatrs() { return densmatrs2; } +Qureg getArbitraryCachedStatevec() { + + // must not be called pre-creation nor post-destruction + DEMAND( !statevecs1.empty() ); + + return statevecs1.begin()->second; +} + +Qureg getArbitraryCachedDensmatr() { + + // must not be called pre-creation nor post-destruction + DEMAND( !densmatrs1.empty() ); + + return densmatrs1.begin()->second; +} + /* diff --git a/tests/utils/cache.hpp b/tests/utils/cache.hpp index ec8c71c7..87d8382f 100644 --- a/tests/utils/cache.hpp +++ b/tests/utils/cache.hpp @@ -40,6 +40,9 @@ quregCache getCachedDensmatrs(); quregCache getAltCachedStatevecs(); quregCache getAltCachedDensmatrs(); +Qureg getArbitraryCachedStatevec(); +Qureg getArbitraryCachedDensmatr(); + qvector getRefStatevec(); qmatrix getRefDensmatr(); From e137e6f8df75ed47700b9bca193250e0349304ef Mon Sep 17 00:00:00 2001 From: Vasco Ferreira <140641650+vaferreiQMT@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:23:22 +0000 Subject: [PATCH 08/11] Add inverse to QFT implementation (#705) --- README.md | 2 +- docs/tutorial.md | 5 +-- quest/include/deprecated.h | 5 +-- quest/include/operations.h | 67 +++++++++++++++++++++++++++++++++--- quest/src/api/operations.cpp | 45 ++++++++++++++++-------- tests/unit/operations.cpp | 22 +++++++----- tests/utils/linalg.cpp | 8 ++--- tests/utils/linalg.hpp | 4 +-- 8 files changed, 120 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 9eb85bfd..f1dd5e87 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ initRandomPureState(qureg); applyHadamard(qureg, 0); applyControlledRotateX(qureg, 0, 1, angle); -applyFullQuantumFourierTransform(qureg); +applyFullQuantumFourierTransform(qureg, inverse); reportQureg(qureg); diff --git a/docs/tutorial.md b/docs/tutorial.md index a006a3fc..b3e99706 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -626,7 +626,8 @@ applyCompMatr1(qureg, 0, m); QuEST includes a few convenience functions for effecting [QFT](https://quest-kit.github.io/QuEST/group__op__qft.html) and [Trotter](https://quest-kit.github.io/QuEST/group__op__paulistrsum.html) circuits. ```cpp -applyQuantumFourierTransform(qureg, targets, 3); +bool inverse = false; +applyQuantumFourierTransform(qureg, targets, 3, inverse); qreal time = .3; int order = 4; @@ -865,4 +866,4 @@ This is important because it ensures: > [!CAUTION] > After calling `finalizeQuESTEnv()`, MPI will close and if being accessed directly by the user, will enter an undefined state. Subsequent calls to MPI routines may return gibberish, and distributed machines will lose their ability to communicate. It is recommended to call `finalizeQuESTEnv()` immediately before exiting. -You are now a QuEST expert 🎉 though there are _many_ more functions in the [API](https://quest-kit.github.io/QuEST/group__api.html) not covered here. Go forth and simulate! \ No newline at end of file +You are now a QuEST expert 🎉 though there are _many_ more functions in the [API](https://quest-kit.github.io/QuEST/group__api.html) not covered here. Go forth and simulate! diff --git a/quest/include/deprecated.h b/quest/include/deprecated.h index 1bc8bda5..1a63b204 100644 --- a/quest/include/deprecated.h +++ b/quest/include/deprecated.h @@ -21,6 +21,7 @@ #include "quest.h" #include "stdlib.h" +#include @@ -1321,11 +1322,11 @@ static inline void _multiControlledMultiRotatePauli(Qureg qureg, int* ctrls, int #define applyFullQFT(...) \ _WARN_FUNC_RENAMED("applyFullQFT()", "applyFullQuantumFourierTransform()") \ - applyFullQuantumFourierTransform(__VA_ARGS__) + applyFullQuantumFourierTransform(__VA_ARGS__, false) #define applyQFT(...) \ _WARN_FUNC_RENAMED("applyQFT()", "applyQuantumFourierTransform()") \ - applyQuantumFourierTransform(__VA_ARGS__) + applyQuantumFourierTransform(__VA_ARGS__, false) diff --git a/quest/include/operations.h b/quest/include/operations.h index bd74e1f8..8cacdf44 100644 --- a/quest/include/operations.h +++ b/quest/include/operations.h @@ -25,6 +25,8 @@ #include "quest/include/matrices.h" #include "quest/include/channels.h" +#include + #ifdef __cplusplus #include #endif @@ -2398,12 +2400,67 @@ extern "C" { #endif -/// @notyetdoced -void applyQuantumFourierTransform(Qureg qureg, int* targets, int numTargets); +/** + * Applies the Quantum Fourier Transform upon the specified @p targets of @p qureg. + * Alternatively, applies the Inverse Quantum Fourier Transform according to @p inverse. + * + * @formulae + * + * Letting @f$ N @f$ = @p numTargets, the @f$ N @f$ qubit Quantum Fourier Transform maps each + * computational basis state of the targeted qubits, @f$ \ket{j} @f$, according to + * @f[ + \ket{j} \rightarrow \frac{1}{\sqrt{2^N}} \sum_{k=0}^{2^N-1} e^{2 \pi i j k / 2^N} \ket{k}. + * @f] + * Similarly the Inverse Quantum Fourier Transform maps each basis state like + * @f[ + \ket{j} \rightarrow \frac{1}{\sqrt{2^N}} \sum_{k=0}^{2^N-1} e^{-2 \pi i j k / 2^N} \ket{k}. + * @f] + * + * @param[in,out] qureg the state to modify. + * @param[in] targets the indices of the target qubits. + * @param[in] numTargets the length of list @p targets + * @param[in] inverse whether to apply the inverse QFT or forward QFT + * @throws @validationerror + * - if @p qureg is uninitialised. +* - if @p targets are invalid qubit indices. +* - if @p targets are not unique. + * - if @p numTargets < 1. + * @see + * - applyFullQuantumFourierTransform() + * @author Vasco Ferreira + */ +void applyQuantumFourierTransform(Qureg qureg, int* targets, int numTargets, bool inverse); -/// @notyetdoced -void applyFullQuantumFourierTransform(Qureg qureg); +/** + * Applies the Quantum Fourier Transform upon all qubits in @p qureg. Alternatively, + * applies the Inverse Quantum Fourier Transform according to @p inverse. + * + * @formulae + * + * The Quantum Fourier Transform maps each computational basis state @f$ \ket{j} @f$ + * in an @f$ N @f$ qubit @p qureg according to + * @f[ + \ket{j} \rightarrow \frac{1}{\sqrt{2^N}} \sum_{k=0}^{2^N-1} e^{2 \pi i j k / 2^N} \ket{k}. + * @f] + * Similarly the Inverse Quantum Fourier Transform maps each basis state like + * @f[ + \ket{j} \rightarrow \frac{1}{\sqrt{2^N}} \sum_{k=0}^{2^N-1} e^{-2 \pi i j k / 2^N} \ket{k}. + * @f] + * + * @equivalences + * + * - This function wraps applyQuantumFourierTransform(), passing all qubits in the @p qureg as targets. + * + * @param[in,out] qureg the state to modify. + * @param[in] inverse whether to apply the inverse QFT or forward QFT + * @throws @validationerror + * - if @p qureg is uninitialised. + * @see + * - applyQuantumFourierTransform() + * @author Vasco Ferreira + */ +void applyFullQuantumFourierTransform(Qureg qureg, bool inverse); // end de-mangler @@ -2417,7 +2474,7 @@ void applyFullQuantumFourierTransform(Qureg qureg); /// @notyetdoced /// @cppvectoroverload /// @see applyQuantumFourierTransform() -void applyQuantumFourierTransform(Qureg qureg, std::vector targets); +void applyQuantumFourierTransform(Qureg qureg, std::vector targets, bool inverse); #endif // __cplusplus diff --git a/quest/src/api/operations.cpp b/quest/src/api/operations.cpp index 47a81e49..88141e23 100644 --- a/quest/src/api/operations.cpp +++ b/quest/src/api/operations.cpp @@ -1741,7 +1741,7 @@ qreal applyForcedMultiQubitMeasurement(Qureg qureg, vector qubits, vector=0; n--) { - applyHadamard(qureg, targets[n]); - for (int m=0; m=0; n--) { + applyHadamard(qureg, targets[n]); + + for (int m=0; m targets) { +void applyQuantumFourierTransform(Qureg qureg, vector targets, bool inverse) { - applyQuantumFourierTransform(qureg, targets.data(), targets.size()); + applyQuantumFourierTransform(qureg, targets.data(), targets.size(), inverse); } diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index a0a99086..95fa4614 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1375,16 +1375,18 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { int numTargs = GENERATE_COPY( range(1,numQubits+1) ); auto targs = GENERATE_TARGS( numQubits, numTargs ); + bool inverse = GENERATE(false, true); CAPTURE( targs ); SECTION( LABEL_STATEVEC ) { auto testFunc = [&](Qureg qureg, qvector& ref) { - applyQuantumFourierTransform(qureg, targs.data(), targs.size()); - ref = getDisceteFourierTransform(ref, targs); + applyQuantumFourierTransform(qureg, targs.data(), targs.size(), inverse); + ref = getDiscreteFourierTransform(ref, targs, inverse); }; + CAPTURE(inverse); TEST_ON_CACHED_QUREGS(statevecQuregs, statevecRef, testFunc); } @@ -1398,15 +1400,16 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { // overwrite the Qureg debug state set by caller to above mixture setQuregToReference(qureg, getMixture(states, probs)); - applyQuantumFourierTransform(qureg, targs.data(), targs.size()); + applyQuantumFourierTransform(qureg, targs.data(), targs.size(), inverse); ref = getZeroMatrix(ref.size()); for (size_t i=0; i 0 ); size_t dim = in.size(); @@ -166,7 +166,7 @@ qvector getDisceteFourierTransform(qvector in) { // PI must be accurate here qreal pi = 3.14159265358979323846; qreal a = 1 / std::sqrt(dim); - qreal b = 2 * pi / dim; + qreal b = (inverse ? -1 : 1) * 2 * pi / dim; for (size_t x=0; x targs) { +qvector getDiscreteFourierTransform(qvector in, vector targs, bool inverse) { DEMAND( in.size() > 0 ); size_t dim = in.size(); @@ -185,7 +185,7 @@ qvector getDisceteFourierTransform(qvector in, vector targs) { qindex len = getPow2(targs.size()); qreal pi = 3.14159265358979323846; qreal a = 1 / std::sqrt(len); - qreal b = 2 * pi / len; + qreal b = (inverse ? -1 : 1) * 2 * pi / len; for (size_t i=0; i vec); qcomp getSum(qvector); qvector getNormalised(qvector); -qvector getDisceteFourierTransform(qvector); -qvector getDisceteFourierTransform(qvector in, vector targs); +qvector getDiscreteFourierTransform(qvector in, bool inverse); +qvector getDiscreteFourierTransform(qvector in, vector targs, bool inverse); qcomp getInnerProduct(qvector bra, qvector ket); qmatrix getOuterProduct(qvector ket, qvector bra); From 18fadc516a0d691ca4fd96ee2dbdf9ade276a83d Mon Sep 17 00:00:00 2001 From: Vasco Ferreira <140641650+vaferreiQMT@users.noreply.github.com> Date: Thu, 26 Feb 2026 04:22:37 +0000 Subject: [PATCH 09/11] Fix missing inverse argument to QFT validation tests --- tests/unit/operations.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/unit/operations.cpp b/tests/unit/operations.cpp index 95fa4614..0e33220d 100644 --- a/tests/unit/operations.cpp +++ b/tests/unit/operations.cpp @@ -1419,13 +1419,14 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { Qureg qureg = getArbitraryCachedStatevec(); int targs[] = {0, 1, 2}; int numTargs = 3; + bool inverse = false; SECTION( "qureg uninitialised" ) { Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(badQureg, targs, numTargs), + applyQuantumFourierTransform(badQureg, targs, numTargs, inverse), ContainsSubstring("invalid Qureg") ); } @@ -1434,7 +1435,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { int badTargs[] = {0, 1, qureg.numQubits}; // latter is too large REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, badTargs, 3), + applyQuantumFourierTransform(qureg, badTargs, 3, inverse), ContainsSubstring("target") ); } @@ -1443,7 +1444,7 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { int dupTargs[] = {0, 1, 1}; REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, dupTargs, 3), + applyQuantumFourierTransform(qureg, dupTargs, 3, inverse), ContainsSubstring("duplicate") ); } @@ -1452,13 +1453,13 @@ TEST_CASE( "applyQuantumFourierTransform", TEST_CATEGORY_OPS ) { int badNumTargs = GENERATE_COPY( -1, 0 ); REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, targs, badNumTargs), + applyQuantumFourierTransform(qureg, targs, badNumTargs, inverse), ContainsSubstring("targets") || ContainsSubstring("target qubits") ); badNumTargs = qureg.numQubits+1; REQUIRE_THROWS_WITH( - applyQuantumFourierTransform(qureg, targs, badNumTargs), + applyQuantumFourierTransform(qureg, targs, badNumTargs, inverse), ContainsSubstring("exceeds the number of qubits in the Qureg") ); } @@ -1513,13 +1514,14 @@ TEST_CASE( "applyFullQuantumFourierTransform", TEST_CATEGORY_OPS ) { SECTION( LABEL_VALIDATION ) { Qureg qureg = getArbitraryCachedStatevec(); + bool inverse = false; SECTION( "qureg uninitialised" ) { Qureg badQureg = qureg; badQureg.numQubits = -1; REQUIRE_THROWS_WITH( - applyFullQuantumFourierTransform(badQureg), + applyFullQuantumFourierTransform(badQureg, inverse), ContainsSubstring("invalid Qureg") ); } From d9425b84c25c87105bb75c247705fca33f9eefc6 Mon Sep 17 00:00:00 2001 From: Vasco Ferreira <140641650+vaferreiQMT@users.noreply.github.com> Date: Sat, 28 Feb 2026 02:41:24 +0000 Subject: [PATCH 10/11] Implement Pauli sorting (#708) --- quest/include/paulis.h | 88 +++++++++++++++++++++++++++++++++++ quest/src/api/paulis.cpp | 30 ++++++++++++ quest/src/core/paulilogic.cpp | 34 ++++++++++++++ quest/src/core/paulilogic.hpp | 7 ++- quest/src/core/utilities.cpp | 16 ++++++- quest/src/core/utilities.hpp | 6 ++- tests/unit/paulis.cpp | 55 ++++++++++++++++++++++ 7 files changed, 232 insertions(+), 4 deletions(-) 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..e7f85a88 100644 --- a/quest/src/api/paulis.cpp +++ b/quest/src/api/paulis.cpp @@ -291,3 +291,33 @@ extern "C" void reportPauliStrSum(PauliStrSum sum) { // exclude mandatory newline above print_oneFewerNewlines(); } + + + +/* + * SORTING + */ + + +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_sortTermsViaComparator(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_sortTermsViaComparator(sum, magSort); +} diff --git a/quest/src/core/paulilogic.cpp b/quest/src/core/paulilogic.cpp index 1a90dce2..aa7a06ca 100644 --- a/quest/src/core/paulilogic.cpp +++ b/quest/src/core/paulilogic.cpp @@ -13,9 +13,12 @@ #include "quest/src/core/bitwise.hpp" #include "quest/src/core/errors.hpp" +#include #include #include #include +#include +#include using std::vector; @@ -307,6 +310,37 @@ 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 diff --git a/quest/src/core/paulilogic.hpp b/quest/src/core/paulilogic.hpp index f3748b5e..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; @@ -76,6 +77,10 @@ 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 @@ -88,4 +93,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 diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 999bd9a7..a5ca635b 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,20 @@ 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; +} + /* diff --git a/quest/src/core/utilities.hpp b/quest/src/core/utilities.hpp index 4b7fb5db..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,4 +432,4 @@ void util_tryAllocMatrix(vector> &vec, qindex numRows, qindex numC -#endif // UTILITIES_HPP \ No newline at end of file +#endif // UTILITIES_HPP 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 b7d4a291927ba64d24bcac265ad7618a8ee218e0 Mon Sep 17 00:00:00 2001 From: Oliver Thomson Brown Date: Wed, 8 Apr 2026 16:15:43 +0100 Subject: [PATCH 11/11] Fix HIP install failures in CI (#718) CI updated to use latest AMD ROCm install instructions. As of this commit corresponding to ROCm 7.2. --------- Co-authored-by: Oliver Thomson Brown <8394906+otbrown@users.noreply.github.com> --- .github/workflows/compile.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index fa7ca423..0950b7db 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -211,13 +211,28 @@ jobs: - name: Install ROCm if: ${{ matrix.hip == 'ON' }} run: | + sudo apt update + sudo apt upgrade sudo apt install "linux-headers-$(uname -r)" "linux-modules-extra-$(uname -r)" - sudo apt install python3-setuptools python3-wheel - sudo usermod -a -G render,video $USER - wget https://repo.radeon.com/amdgpu-install/6.3.3/ubuntu/noble/amdgpu-install_6.3.60303-1_all.deb - sudo apt install ./amdgpu-install_6.3.60303-1_all.deb + # Make the directory if it doesn't exist yet. + # This location is recommended by the distribution maintainers. + sudo mkdir --parents --mode=0755 /etc/apt/keyrings + # Download the key, convert the signing-key to a full + # keyring required by apt and store in the keyring directory + wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | \ + gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null + sudo tee /etc/apt/sources.list.d/amdgpu.list << EOF + deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.2 noble main + deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/graphics/7.2/ubuntu noble main + EOF + sudo tee /etc/apt/preferences.d/rocm-pin-600 << EOF + Package: * + Pin: release o=repo.radeon.com + Pin-Priority: 600 + EOF sudo apt update - sudo apt install amdgpu-dkms rocm + sudo apt autoremove + sudo apt install rocm rocm-hip-sdk rocm-hip-runtime-dev echo "${{ env.rocm_path }}" >> $GITHUB_PATH # invoke cmake, disabling LTO (it duplicates symbols with CUDA + MPI)