diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 999bd9a72..b49838d16 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -22,6 +22,7 @@ #include "quest/src/core/utilities.hpp" #include "quest/src/core/validation.hpp" #include "quest/src/cpu/cpu_config.hpp" +#include "quest/src/gpu/gpu_subroutines.hpp" #include "quest/src/comm/comm_config.hpp" #include "quest/src/comm/comm_routines.hpp" @@ -482,11 +483,9 @@ template bool getUnitarity(T elems, qindex dim, qreal eps) { assert_utilsGivenNonZeroEpsilon(eps); - /// @todo - /// consider multithreading or GPU-accelerating this - /// when caller is big and e.g. has GPU memory - // check m * dagger(m) == identity + bool unitary = true; + #pragma omp parallel for reduction(&&:unitary) if(dim >= MIN_DIM_FOR_UTIL_DENSE_UNITARITY_MULTITHREADING && getQuESTEnv().isMultithreaded) for (qindex r=0; r= MIN_DIM_FOR_UTIL_DIAG_UNITARITY_MULTITHREADING && getQuESTEnv().isMultithreaded) for (qindex i=0; i= MIN_DIM_FOR_UTIL_DENSE_UNITARITY_GPU) { + *(m.isApproxUnitary) = gpu_compmatr_isUnitary_sub(m, eps); + } + else { + *(m.isApproxUnitary) = getUnitarity(m.cpuElems, m.numRows, eps); + } + } // eps may have been ignored return *(m.isApproxUnitary); @@ -539,8 +543,15 @@ bool util_isUnitary(CompMatr m, qreal eps) { bool util_isUnitary(DiagMatr m, qreal eps) { // compute and record unitarity if not already known - if (*(m.isApproxUnitary) == validate_STRUCT_PROPERTY_UNKNOWN_FLAG) - *(m.isApproxUnitary) = getUnitarity(m.cpuElems, m.numElems, eps); + if (*(m.isApproxUnitary) == validate_STRUCT_PROPERTY_UNKNOWN_FLAG) { + + if (util_isGpuAcceleratedMatrix(m) && m.numElems >= MIN_DIM_FOR_UTIL_DIAG_UNITARITY_GPU) { + *(m.isApproxUnitary) = gpu_diagmatr_isUnitary_sub(m, eps); + } + else { + *(m.isApproxUnitary) = getUnitarity(m.cpuElems, m.numElems, eps); + } + } // eps may have been ignored return *(m.isApproxUnitary); @@ -570,33 +581,29 @@ template bool getHermiticity(T elems, qindex dim, qreal eps) { assert_utilsGivenNonZeroEpsilon(eps); - /// @todo - /// consider multithreading or GPU-accelerating this - /// when caller is big and e.g. has GPU memory - // check adjoint(elems) == elems + bool hermitian = true; + #pragma omp parallel for reduction(&&:hermitian) if(dim >= MIN_DIM_FOR_UTIL_DENSE_HERMITICITY_MULTITHREADING && getQuESTEnv().isMultithreaded) for (qindex r=0; r= MIN_DIM_FOR_UTIL_DIAG_HERMITICITY_MULTITHREADING && getQuESTEnv().isMultithreaded) for (qindex i=0; i= MIN_DIM_FOR_UTIL_DENSE_HERMITICITY_GPU) { + *(m.isApproxHermitian) = gpu_compmatr_isHermitian_sub(m, eps); + } + else { + *(m.isApproxHermitian) = getHermiticity(m.cpuElems, m.numRows, eps); + } + } // eps may have been ignored return *(m.isApproxHermitian); @@ -618,8 +632,15 @@ bool util_isHermitian(CompMatr m, qreal eps) { bool util_isHermitian(DiagMatr m, qreal eps) { // compute and record hermiticity if not already known - if (*(m.isApproxHermitian) == validate_STRUCT_PROPERTY_UNKNOWN_FLAG) - *(m.isApproxHermitian) = getHermiticity(m.cpuElems, m.numElems, eps); + if (*(m.isApproxHermitian) == validate_STRUCT_PROPERTY_UNKNOWN_FLAG) { + + if (util_isGpuAcceleratedMatrix(m) && m.numElems >= MIN_DIM_FOR_UTIL_DIAG_HERMITICITY_GPU) { + *(m.isApproxHermitian) = gpu_diagmatr_isHermitian_sub(m, eps); + } + else { + *(m.isApproxHermitian) = getHermiticity(m.cpuElems, m.numElems, eps); + } + } // eps may have been ignored return *(m.isApproxHermitian); @@ -647,10 +668,6 @@ bool util_isHermitian(FullStateDiagMatr m, qreal eps) { bool getWhetherNonZero(qcomp* diags, qindex dim, qreal eps) { assert_utilsGivenNonZeroEpsilon(eps); - /// @todo - /// consider multithreading or GPU-accelerating this - /// when caller is big and e.g. has GPU memory - for (qindex i=0; i= MIN_DIM_FOR_UTIL_CPTP_GPU) { + *(map.isApproxCPTP) = gpu_krausmap_isCPTP_sub(map, eps); + return *(map.isApproxCPTP); + } // check whether each element satisfies Identity = sum dagger(m)*m + bool cptp = true; + #pragma omp parallel for reduction(&&:cptp) if(map.numRows >= MIN_DIM_FOR_UTIL_CPTP_MULTITHREADING && getQuESTEnv().isMultithreaded) for (qindex r=0; r eps) { - // by recording the result and returning immediately - *(map.isApproxCPTP) = 0; - return *(map.isApproxCPTP); + // by recording the result (not returning immediately with many threads) + cptp = false; } } } - // always true by this point + *(map.isApproxCPTP) = cptp; return *(map.isApproxCPTP); } diff --git a/quest/src/core/utilities.hpp b/quest/src/core/utilities.hpp index 4b7fb5db6..1667a8265 100644 --- a/quest/src/core/utilities.hpp +++ b/quest/src/core/utilities.hpp @@ -270,6 +270,31 @@ CompMatr2 util_getTranspose(CompMatr2 matrix); +/* + * OPENMP MULTITHREADING THRESHOLDS + */ + +#define MIN_DIM_FOR_UTIL_DENSE_UNITARITY_MULTITHREADING 16 // In testing, only multithreading w/ >= 4 qubits gives performance improvement +#define MIN_DIM_FOR_UTIL_DIAG_UNITARITY_MULTITHREADING 1024 // >= 10 qubits + +#define MIN_DIM_FOR_UTIL_DENSE_HERMITICITY_MULTITHREADING 128 // >= 7 qubits +#define MIN_DIM_FOR_UTIL_DIAG_HERMITICITY_MULTITHREADING 2048 // >= 11 qubits + +#define MIN_DIM_FOR_UTIL_CPTP_MULTITHREADING 16 // >= 4 qubits + +/* + * GPU MULTITHREADING THRESHOLDS + */ + +#define MIN_DIM_FOR_UTIL_DENSE_UNITARITY_GPU 128 // In testing, only gpu acceleration w/ >= 7 qubits gives performance improvement +#define MIN_DIM_FOR_UTIL_DIAG_UNITARITY_GPU 4096 // >= 12 qubits + +#define MIN_DIM_FOR_UTIL_DENSE_HERMITICITY_GPU 1024 // >= 10 qubits +#define MIN_DIM_FOR_UTIL_DIAG_HERMITICITY_GPU 16384 // >= 14 qubits + +#define MIN_DIM_FOR_UTIL_CPTP_GPU 128 // >= 7 qubits + + /* * MATRIX PROPERTIES */ diff --git a/quest/src/gpu/gpu_subroutines.cpp b/quest/src/gpu/gpu_subroutines.cpp index 5e18048f7..6c993ef45 100644 --- a/quest/src/gpu/gpu_subroutines.cpp +++ b/quest/src/gpu/gpu_subroutines.cpp @@ -1895,3 +1895,70 @@ void gpu_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg) error_gpuSimButGpuNotCompiled(); #endif } + + + +/* + * MATRIX PROPERTIES + */ + + +bool gpu_compmatr_isUnitary_sub(CompMatr matr, qreal eps) { + +#if COMPILE_CUDA || COMPILE_CUQUANTUM + + return thrust_compmatr_isUnitary_sub(matr, eps); + +#else + error_gpuSimButGpuNotCompiled(); + return false; +#endif +} + +bool gpu_diagmatr_isUnitary_sub(DiagMatr matr, qreal eps){ + +#if COMPILE_CUDA || COMPILE_CUQUANTUM + + return thrust_diagmatr_isUnitary_sub(matr, eps); + +#else + error_gpuSimButGpuNotCompiled(); + return false; +#endif +} + +bool gpu_compmatr_isHermitian_sub(CompMatr matr, qreal eps){ + +#if COMPILE_CUDA || COMPILE_CUQUANTUM + + return thrust_compmatr_isHermitian_sub(matr, eps); + +#else + error_gpuSimButGpuNotCompiled(); + return false; +#endif +} + +bool gpu_diagmatr_isHermitian_sub(DiagMatr matr, qreal eps){ + +#if COMPILE_CUDA || COMPILE_CUQUANTUM + + return thrust_diagmatr_isHermitian_sub(matr, eps); + +#else + error_gpuSimButGpuNotCompiled(); + return false; +#endif +} + +bool gpu_krausmap_isCPTP_sub(KrausMap map, qreal eps){ + +#if COMPILE_CUDA || COMPILE_CUQUANTUM + + return thrust_krausmap_isCPTP_sub(map, eps); + +#else + error_gpuSimButGpuNotCompiled(); + return false; +#endif +} diff --git a/quest/src/gpu/gpu_subroutines.hpp b/quest/src/gpu/gpu_subroutines.hpp index ff42c2239..8176a8d63 100644 --- a/quest/src/gpu/gpu_subroutines.hpp +++ b/quest/src/gpu/gpu_subroutines.hpp @@ -196,4 +196,19 @@ void gpu_statevec_initDebugState_sub(Qureg qureg); void gpu_statevec_initUnnormalisedUniformlyRandomPureStateAmps_sub(Qureg qureg); +/* + * MATRIX PROPERTIES + */ + +bool gpu_compmatr_isUnitary_sub(CompMatr matr, qreal eps); + +bool gpu_diagmatr_isUnitary_sub(DiagMatr matr, qreal eps); + +bool gpu_compmatr_isHermitian_sub(CompMatr matr, qreal eps); + +bool gpu_diagmatr_isHermitian_sub(DiagMatr matr, qreal eps); + +bool gpu_krausmap_isCPTP_sub(KrausMap map, qreal eps); + + #endif // GPU_SUBROUTINES_HPP \ No newline at end of file diff --git a/quest/src/gpu/gpu_thrust.cuh b/quest/src/gpu/gpu_thrust.cuh index 9f8d8f1ab..a8404846b 100644 --- a/quest/src/gpu/gpu_thrust.cuh +++ b/quest/src/gpu/gpu_thrust.cuh @@ -59,6 +59,8 @@ #include #include +#include + /* @@ -641,6 +643,123 @@ struct functor_setRandomStateVecAmp : public thrust::unary_function{ + + cu_qcomp* matr; + qreal eps; + qindex dim; + + functor_compmatr_isUnitaryTerm(cu_qcomp* matr, qreal eps, qindex dim) : + matr(matr), eps(eps), dim(dim) + {} + + __host__ __device__ bool operator()(qindex i) { + + qindex r = i / dim; + qindex c = i % dim; + + cu_qcomp elem = getCuQcomp(0, 0); + for (qindex k=0; k{ + + cu_qcomp* diags; + qreal eps; + + functor_diagmatr_isUnitaryTerm(cu_qcomp* diags, qreal eps) : + diags(diags), eps(eps) + {} + + __host__ __device__ bool operator()(qindex i) { + + // We want |sqrt(norm) - 1| <= eps; + // equivalent to (1-eps)^2 <= norm <= (1+eps)^2 + // eps^2 is small, so approximate bound as + // [1-2eps, 1+2eps], or |norm - 1| <= 2*eps + + qreal norm = getCompNorm(diags[i]); + return fabs(norm - 1) <= 2 * eps; + } +}; + +struct functor_compmatr_isHermitianTerm : public thrust::unary_function{ + + // check adjoint(elems) == elems + + cu_qcomp* matr; + qreal eps; + qindex dim; + + functor_compmatr_isHermitianTerm(cu_qcomp* matr, qreal eps, qindex dim) : + matr(matr), eps(eps), dim(dim) + {} + + __host__ __device__ bool operator()(qindex i) { + + qindex row = i / dim; + qindex col = i % dim; + + + if (col >= row) + return true; + + cu_qcomp elem = matr[row * dim + col]; + cu_qcomp conjOfMirror = getCompConj(matr[col * dim + row]); + + return getCompNorm(elem - conjOfMirror) <= eps; + } +}; + +struct functor_diagmatr_isHermitianTerm : public thrust::unary_function{ + + cu_qcomp* diags; + qreal eps; + + functor_diagmatr_isHermitianTerm(cu_qcomp* diags, qreal eps) : + diags(diags), eps(eps) + {} + + __host__ __device__ bool operator()(qindex i) { + qreal imag = diags[i].y; + return fabs(imag) <= eps; + } +}; + +struct functor_krausmap_isCPTPTerm : public thrust::unary_function{ + + cu_qcomp* matr; + qreal eps; + qindex dim; + qindex numMatrices; + + functor_krausmap_isCPTPTerm(cu_qcomp* matr, qreal eps, qindex dim, qindex numMatrices) : + matr(matr), eps(eps), dim(dim), numMatrices(numMatrices) + {} + + __host__ __device__ bool operator()(qindex i) { + + qindex row = i / dim; + qindex col = i % dim; + + cu_qcomp elem = getCuQcomp(0, 0); + for (qindex n=0; n() + ); +} + +bool thrust_diagmatr_isUnitary_sub(DiagMatr matr, qreal eps){ + + qindex dim = matr.numElems; + + //functor accepts an index and returns a boolean + auto functor = functor_diagmatr_isUnitaryTerm(toCuQcomps(matr.gpuElems), eps); + + auto indIter = thrust::make_counting_iterator(0); + qindex numIts = dim; + + return thrust::transform_reduce( + indIter, indIter + numIts, + functor, true, thrust::logical_and() + ); +} + +bool thrust_compmatr_isHermitian_sub(CompMatr matr, qreal eps){ + + qindex dim = matr.numRows; + + //functor accepts an index and returns a boolean + auto functor = functor_compmatr_isHermitianTerm(toCuQcomps(matr.gpuElemsFlat), eps, dim); + + auto indIter = thrust::make_counting_iterator(0); + qindex numIts = dim * dim; + + return thrust::transform_reduce( + indIter, indIter + numIts, + functor, true, thrust::logical_and() + ); +} + +bool thrust_diagmatr_isHermitian_sub(DiagMatr matr, qreal eps){ + + qindex dim = matr.numElems; + + //functor accepts an index and returns a boolean + auto functor = functor_diagmatr_isHermitianTerm(toCuQcomps(matr.gpuElems), eps); + + auto indIter = thrust::make_counting_iterator(0); + qindex numIts = dim; + + return thrust::transform_reduce( + indIter, indIter + numIts, + functor, true, thrust::logical_and() + ); +} + +bool thrust_krausmap_isCPTP_sub(KrausMap map, qreal eps){ + + qindex dim = map.numRows; + int numMatrices = map.numMatrices; + + //map.matrices is CPU-only + //flatten to copy over to GPU, but matrices and rows are non-contigious + vector hostMatrices(numMatrices * dim * dim); + for (int n=0; n devMatrices(hostMatrices); + cu_qcomp* devMatricesPtr = toCuQcomps(thrust::raw_pointer_cast(devMatrices.data())); + + //functor accepts an index and returns a boolean + auto functor = functor_krausmap_isCPTPTerm(devMatricesPtr, eps, dim, numMatrices); + + auto indIter = thrust::make_counting_iterator(0); + qindex numIts = dim * dim; + + return thrust::transform_reduce( + indIter, indIter + numIts, + functor, true, thrust::logical_and() + ); +} + + + #endif // GPU_THRUST_HPP \ No newline at end of file