diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..b74218c50 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,330 @@ +# author : Ania Brown +# author : Jacob Wilkins +# author : Balint Koczor (Windows compatibility) +# author : Tyson Jones (testing) +# author : Oliver Thomson Brown (v4) + +cmake_minimum_required(VERSION 3.21) + +project(QuEST + VERSION 4.0.0 + DESCRIPTION "Quantum Exact Simulation Toolkit" + LANGUAGES CXX C +) + +## Dependencies + +# GNUInstallDirs to provide sensible default install directory names +include(GNUInstallDirs) + +# Maths +if (NOT MSVC) + find_library(MATH_LIBRARY m REQUIRED) +endif() + +## Configuration options + +# Build type +# Default to "Release" +# Using recipe from Kitware Blog post +# https://www.kitware.com/cmake-and-the-default-build-type/ +set(default_build_type "Release") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE + STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +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.") + +# Library naming +set(LIB_NAME QuEST + CACHE + STRING + "Change library name. LIB_NAME is QuEST by default." +) +message(STATUS "Library will be named lib${LIB_NAME}. Set LIB_NAME to modify.") + +option(VERBOSE_LIB_NAME "Modify library name based on compilation configuration. Turned OFF by default." OFF) +message(STATUS "Verbose library naming is turned ${VERBOSE_LIB_NAME}. Set VERBOSE_LIB_NAME to modify.") + +# Precision +set(FLOAT_PRECISION 2 + CACHE + STRING + "Whether to use single, double, or quad floating point precision in the state vector. {1,2,4}" +) +set_property(CACHE FLOAT_PRECISION PROPERTY STRINGS + 1 + 2 + 4 +) +message(STATUS "Precision set to ${FLOAT_PRECISION}. Set FLOAT_PRECISION to modify.") + +if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "-fp${FLOAT_PRECISION}") +endif() + +# Examples +option( + BUILD_EXAMPLES + "Whether the example programs will be built alongside the QuEST library. Turned ON by default." + ON +) +message(STATUS "Examples are turned ${BUILD_EXAMPLES}. Set BUILD_EXAMPLES to modify.") + +# Testing +option( + ENABLE_TESTING + "Whether the test suite will be built alongside the QuEST library. Turned ON by default." + ON +) +message(STATUS "Testing is turned ${ENABLE_TESTING}. Set ENABLE_TESTING to modify.") + +# Multithreading +option( + ENABLE_MULTITHREADING + "Whether QuEST will be built with shared-memory parallelism support using OpenMP. Turned ON by default." + ON +) +message(STATUS "Multithreading is turned ${ENABLE_MULTITHREADING}. Set ENABLE_MULTITHREADING to modify.") + +# Distribution +option( + ENABLE_DISTRIBUTION + "Whether QuEST will be built with distributed parallelism support using MPI. Turned OFF by default." + OFF +) +message(STATUS "Distribution is turned ${ENABLE_DISTRIBUTION}. Set ENABLE_DISTRIBUTION to modify.") + +# GPU Acceleration +option( + ENABLE_CUDA + "Whether QuEST will be built with support for NVIDIA GPU acceleration. Turned OFF by default." + OFF +) +message(STATUS "NVIDIA GPU acceleration is turned ${ENABLE_CUDA}. Set ENABLE_CUDA to modify.") + +if (ENABLE_CUDA) + option( + ENABLE_CUQUANTUM + "Whether QuEST will be built with support for NVIDIA CuQuantum. Turned OFF by default." + OFF + ) + message(STATUS "CuQuantum support is turned ${ENABLE_CUQUANTUM}. Set ENABLE_CUQUANTUM to modify.") +endif() + +option( + ENABLE_HIP + "Whether QuEST will be built with support for AMD GPU acceleration. Turned OFF by default." +) +message(STATUS "AMD GPU acceleration is turned ${ENABLE_HIP}. Set ENABLE_HIP to modify.") + +# Throw on disallowed combinations +if (ENABLE_CUDA AND ENABLE_HIP) + message(FATAL_ERROR "QuEST cannot support CUDA and HIP simultaneously.") +endif() + +if ((ENABLE_CUDA OR ENABLE_HIP) AND FLOAT_PRECISION STREQUAL 4) + message(FATAL_ERROR "Quad precision is not supported on GPU. Please disable GPU acceleration or lower precision.") +endif() + +# Deprecated API +option( + ENABLE_DEPRECATED + "Whether QuEST will be built with deprecated API support. Turned OFF by default." + OFF +) +message(STATUS "Deprecated API support is turned ${ENABLE_DEPRECATED}. Set ENABLE_DEPRECATED to modify.") + +## Library + +add_library(QuEST) + +# Add namespaced alias to support inclusion of QuEST as a subproject +add_library(QuEST::QuEST ALIAS QuEST) + +# Set include directories +target_include_directories(QuEST + PUBLIC + $ + $ +) + +# Add required C and C++ standards +target_compile_features(QuEST + PUBLIC + c_std_11 + cxx_std_17 +) + +# Turn on all compiler warnings +if (MSVC) + target_compile_options(QuEST PRIVATE /W4) +else() + target_compile_options(QuEST PRIVATE -Wall) +endif() + +# Set user options +target_compile_definitions(QuEST PUBLIC FLOAT_PRECISION=${FLOAT_PRECISION}) + +if (ENABLE_MULTITHREADING) + find_package(OpenMP REQUIRED) + target_compile_definitions(QuEST PUBLIC COMPILE_OPENMP=1) + target_link_libraries(QuEST + PUBLIC + OpenMP::OpenMP_CXX + OpenMP::OpenMP_C + ) + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+mt") + endif() +else() + target_compile_definitions(QuEST PUBLIC COMPILE_OPENMP=0) +endif() + +if (ENABLE_DISTRIBUTION) + find_package(MPI REQUIRED + COMPONENTS CXX + ) + target_compile_definitions(QuEST PUBLIC COMPILE_MPI=1) + target_link_libraries(QuEST + PUBLIC + MPI::MPI_CXX + ) + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+mpi") + endif() +else() + target_compile_definitions(QuEST PUBLIC COMPILE_MPI=0) +endif() + +if (ENABLE_CUDA) + enable_language(CUDA) + set(CMAKE_CUDA_STANDARD_REQUIRED ON) + set_property(TARGET QuEST PROPERTY CUDA_STANDARD 17) + + target_compile_definitions(QuEST PUBLIC COMPILE_CUDA=1) + + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+cuda") + endif() +else() + target_compile_definitions(QuEST PUBLIC COMPILE_CUDA=0) +endif() + +if (ENABLE_CUQUANTUM) + target_compile_definitions(QuEST PUBLIC COMPILE_CUQUANTUM=1) + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+cuquantum") + endif() +else() + target_compile_definitions(QuEST PUBLIC COMPILE_CUQUANTUM=0) +endif() + +if (ENABLE_HIP) + enable_language(HIP) + set(CMAKE_HIP_STANDARD_REQUIRED ON) + set_property(TARGET QuEST PROPERTY HIP_STANDARD 17) + + target_compile_definitions(QuEST + PUBLIC COMPILE_CUDA=1 + PUBLIC COMPILE_CUQUANTUM=0 + PUBLIC COMPILE_HIP=1 + PUBLIC __HIP_PLATFORM_AMD__ + ) + + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+hip") + endif() +endif() + +if (ENABLE_DEPRECATED) + target_compile_definitions(QuEST PUBLIC INCLUDE_DEPRECATED_FUNCTIONS=1) + + if (VERBOSE_LIB_NAME) + string(CONCAT LIB_NAME ${LIB_NAME} "+depr") + endif() +endif() + +# add math library +if (NOT MSVC) + target_link_libraries(QuEST PUBLIC ${MATH_LIBRARY}) +endif() + +# Set output name +set_target_properties(QuEST PROPERTIES OUTPUT_NAME ${LIB_NAME}) + +# Add source files +add_subdirectory(quest) + +## Examples + +if (BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +## User Source + +if (USER_SOURCE AND NOT OUTPUT_EXE) + message(SEND_ERROR "USER_SOURCE specified, but not OUTPUT_EXE.") +endif() +if (OUTPUT_EXE AND NOT USER_SOURCE) + message(SEND_ERROR "OUTPUT_EXE specified, but not USER_SOURCE.") +endif() +if (USER_SOURCE AND OUTPUT_EXE) + message(STATUS "Compiling ${USER_SOURCE} to executable ${OUTPUT_EXE}.") + + add_executable(${OUTPUT_EXE} ${USER_SOURCE}) + target_link_libraries(${OUTPUT_EXE} PUBLIC QuEST) + install(TARGETS ${OUTPUT_EXE} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + set_target_properties(${OUTPUT_EXE} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}") +endif() + +## Tests + +if (ENABLE_TESTING) + add_subdirectory(tests) +endif() + +## Install + +install(TARGETS QuEST + EXPORT QuESTTargets + LIBRARY DESTINATION quest/${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION quest/${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION quest/${CMAKE_INSTALL_BINDIR} +) + +install(EXPORT QuESTTargets + FILE QuESTTargets.cmake + NAMESPACE QuEST:: + DESTINATION "cmake/QuEST" +) + +## Export + +# Write CMake version file for QuEST +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + QuESTConfigVersion.cmake + VERSION ${PACKAGE_VERSION} + COMPATIBILITY AnyNewerVersion +) + +export(TARGETS QuEST + NAMESPACE QuEST:: + FILE QuESTTargets.cmake +) + +set(CMAKE_EXPORT_PACKAGE_REGISTRY ON) +export(PACKAGE QuEST) diff --git a/docs/cmake.md b/docs/cmake.md new file mode 100644 index 000000000..7efd6ee79 --- /dev/null +++ b/docs/cmake.md @@ -0,0 +1,36 @@ +# CMake Configuration Options in QuEST + +Version 4 of QuEST includes reworked CMake to support library builds, CMake export, and installation. Here we detail useful variables to configure the compilation of QuEST. Any of these variables can be set using the `-D` flag when invoking CMake, for example: + +``` +cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/QuEST -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DENABLE_MULTITHREADING=ON -DENABLE_DISTRIBUTION=OFF ./ +``` + +## QuEST CMake variables + +| Variable | (Default) Values | Notes | +| -------- | ---------------- | ----- | +| `LIB_NAME` | (`QuEST`), String | The QuEST library will be named `lib${LIB_NAME}.so`. Can be used to differentiate multiple versions of QuEST which have been compiled. | +| `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` | (`ON`), `OFF` | Determines whether the example programs will be built alongside QuEST. | +| `ENABLE_TESTING` | (`ON`), `OFF` | Determines whether Catch2 tests will be built alongisde QuEST. If built, tests can be run from the build directory with `make test`. | +| `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. | +| `ENABLE_DEPRECATION` | (`OFF`), `ON` | Determines whether QuEST will be built with support for the deprecated (v3) API. *Note: will generate compiler warnings, and not supported by GCC.` | +| `USER_SOURCE` | (Undefined), String | The source file for a user program which will be compiled alongside QuEST. `OUTPUT_EXE` *must* also be defined. | +| `OUTPUT_EXE` | (Undefined), String | The name of the executable which will be created from the provided `USER_SOURCE`. `USER_SOURCE` *must* also be defined. | + +## Standard CMake variables + +| Variable | Description | CMake Doc Page | +| -------- | ----------- | ----- | +| `CMAKE_BUILD_TYPE` | Whether QuEST will be built with or without optimisations and debugging info. QuEST defaults to a `Release` build which is with optimisation and without debugging info. | [CMAKE_BUILD_TYPE](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | +| `CMAKE_CXX_COMPILER` | The C++ compiler that will be used to compile QuEST. | [CMAKE_\_COMPILER](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER.html) | +| `CMAKE_C_COMPILER` | The C compiler that will be used to compile QuEST. | [CMAKE_\_COMPILER](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER.html) | +| `CMAKE_INSTALL_PREFIX` | The directory to which QuEST will be installed when `make install` is invoked. A standard GNU directory structure (lib, bin, include) will be used inside the prefix directory. | [CMAKE_INSTALL_PREFIX](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html)
[GNUInstallDirs](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html) | +| `CMAKE_CUDA_ARCHITECTURES` | Used to set the value of `arch` when compiling for NVIDIA GPU. | [CMAKE_CUDA_ARCHITECTURES](https://cmake.org/cmake/help/latest/variable/CMAKE_CUDA_ARCHITECTURES.html) | +| `CMAKE_HIP_ARCHITECTURES` | Used to set the HIP platform which QuEST is compiled for when compiling for AMD GPU. | [CMAKE_HIP_ARCHITECTURES](https://cmake.org/cmake/help/latest/variable/CMAKE_HIP_ARCHITECTURES.html) | \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..4d874a493 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(krausmaps) +add_subdirectory(matrices) +add_subdirectory(numbers) +add_subdirectory(paulis) +add_subdirectory(reporters) +add_subdirectory(superoperators) \ No newline at end of file diff --git a/examples/krausmaps/CMakeLists.txt b/examples/krausmaps/CMakeLists.txt new file mode 100644 index 000000000..c20465bd3 --- /dev/null +++ b/examples/krausmaps/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(krausmap_cpp_initialisation + initialisation.cpp +) + +target_link_libraries(krausmap_cpp_initialisation PUBLIC QuEST) + +install(TARGETS krausmap_cpp_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/krausmaps +) +set_target_properties(krausmap_cpp_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_initialisation" +) + +add_executable(krausmap_c_initialisation + initialisation.c +) + +target_link_libraries(krausmap_c_initialisation PUBLIC QuEST) + +install(TARGETS krausmap_c_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/krausmaps +) +set_target_properties(krausmap_c_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_initialisation" +) \ No newline at end of file diff --git a/examples/matrices/CMakeLists.txt b/examples/matrices/CMakeLists.txt new file mode 100644 index 000000000..de40a5629 --- /dev/null +++ b/examples/matrices/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(matrices_cpp_initialisation + initialisation.cpp +) + +target_link_libraries(matrices_cpp_initialisation PUBLIC QuEST) + +install(TARGETS matrices_cpp_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/matrices +) +set_target_properties(matrices_cpp_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_initialisation" +) + +add_executable(matrices_c_initialisation + initialisation.c +) + +target_link_libraries(matrices_c_initialisation PUBLIC QuEST) + +install(TARGETS matrices_c_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/matrices +) +set_target_properties(matrices_c_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_initialisation" +) \ No newline at end of file diff --git a/examples/numbers/CMakeLists.txt b/examples/numbers/CMakeLists.txt new file mode 100644 index 000000000..ea8616276 --- /dev/null +++ b/examples/numbers/CMakeLists.txt @@ -0,0 +1,27 @@ +add_executable(cpp_arithmetic + arithmetic.cpp +) + +target_link_libraries(cpp_arithmetic PUBLIC QuEST) + +install(TARGETS cpp_arithmetic + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/numbers +) +set_target_properties(cpp_arithmetic + PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" +) + +add_executable(c_arithmetic + arithmetic.c +) + +target_link_libraries(c_arithmetic PUBLIC QuEST) + +install(TARGETS c_arithmetic + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/numbers +) +set_target_properties(c_arithmetic + PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" +) \ No newline at end of file diff --git a/examples/paulis/CMakeLists.txt b/examples/paulis/CMakeLists.txt new file mode 100644 index 000000000..543817cac --- /dev/null +++ b/examples/paulis/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(paulis_cpp_initialisation + initialisation.cpp +) + +target_link_libraries(paulis_cpp_initialisation PUBLIC QuEST) + +install(TARGETS paulis_cpp_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/paulis +) +set_target_properties(paulis_cpp_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_initialisation" +) + +add_executable(paulis_c_initialisation + initialisation.cpp +) + +target_link_libraries(paulis_c_initialisation PUBLIC QuEST) + +install(TARGETS paulis_c_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/paulis +) +set_target_properties(paulis_c_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME c_initialisation +) \ No newline at end of file diff --git a/examples/reporters/CMakeLists.txt b/examples/reporters/CMakeLists.txt new file mode 100644 index 000000000..4b3c7c6b1 --- /dev/null +++ b/examples/reporters/CMakeLists.txt @@ -0,0 +1,127 @@ +add_executable(reporters_cpp_env + env.cpp +) + +target_link_libraries(reporters_cpp_env PUBLIC QuEST) + +install(TARGETS reporters_cpp_env + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_cpp_env + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_env" +) + +add_executable(reporters_c_env + env.c +) + +target_link_libraries(reporters_c_env PUBLIC QuEST) + +install(TARGETS reporters_c_env + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_c_env + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_env" +) + +add_executable(reporters_cpp_matrices + matrices.cpp +) + +target_link_libraries(reporters_cpp_matrices PUBLIC QuEST) + +install(TARGETS reporters_cpp_matrices + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_cpp_matrices + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_matrices" +) + +add_executable(reporters_c_matrices + matrices.c +) + +target_link_libraries(reporters_c_matrices PUBLIC QuEST) + +install(TARGETS reporters_c_matrices + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_c_matrices + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_matrices" +) + +add_executable(reporters_cpp_paulis + paulis.cpp +) + +target_link_libraries(reporters_cpp_paulis PUBLIC QuEST) + +install(TARGETS reporters_cpp_paulis + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_cpp_paulis + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_paulis" +) + +add_executable(reporters_c_paulis + paulis.c +) + +target_link_libraries(reporters_c_paulis PUBLIC QuEST) + +install(TARGETS reporters_c_paulis + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_c_paulis + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_paulis" +) + +add_executable(reporters_cpp_qureg + qureg.cpp +) + +target_link_libraries(reporters_cpp_qureg PUBLIC QuEST) + +install(TARGETS reporters_cpp_qureg + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_cpp_qureg + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_qureg" +) + +add_executable(reporters_c_qureg + qureg.c +) + +target_link_libraries(reporters_c_qureg PUBLIC QuEST) + +install(TARGETS reporters_c_qureg + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/reporters +) +set_target_properties(reporters_c_qureg + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "c_qureg" +) \ No newline at end of file diff --git a/examples/superoperators/CMakeLists.txt b/examples/superoperators/CMakeLists.txt new file mode 100644 index 000000000..ecd589f54 --- /dev/null +++ b/examples/superoperators/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(superoperators_cpp_initialisation + initialisation.cpp +) + +target_link_libraries(superoperators_cpp_initialisation PUBLIC QuEST) + +install(TARGETS superoperators_cpp_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/superoperators +) +set_target_properties(superoperators_cpp_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME "cpp_initialisation" +) + +add_executable(superoperators_c_initialisation + initialisation.cpp +) + +target_link_libraries(superoperators_c_initialisation PUBLIC QuEST) + +install(TARGETS superoperators_c_initialisation + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR}/examples/superoperators +) +set_target_properties(superoperators_c_initialisation + PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/quest/${CMAKE_INSTALL_LIBDIR}" + OUTPUT_NAME c_initialisation +) \ No newline at end of file diff --git a/quest/CMakeLists.txt b/quest/CMakeLists.txt new file mode 100644 index 000000000..f47f114a8 --- /dev/null +++ b/quest/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(src) +add_subdirectory(include) \ No newline at end of file diff --git a/quest/include/CMakeLists.txt b/quest/include/CMakeLists.txt new file mode 100644 index 000000000..cf7b94526 --- /dev/null +++ b/quest/include/CMakeLists.txt @@ -0,0 +1,4 @@ +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${CMAKE_INSTALL_PREFIX}/quest + FILES_MATCHING PATTERN "*.h" +) diff --git a/quest/include/precision.h b/quest/include/precision.h index 1b83c97a9..9ae793e4e 100644 --- a/quest/include/precision.h +++ b/quest/include/precision.h @@ -5,7 +5,7 @@ #ifndef PRECISION_H #define PRECISION_H -#include "quest/include/modes.h" +#include "modes.h" @@ -86,21 +86,21 @@ /* * RE-CONFIGURABLE DEFAULT VALIDATION PRECISION * - * which is compile-time overridable by pre-defining DEAULT_VALIDATION_EPSILON (e.g. + * which is compile-time overridable by pre-defining DEFAULT_VALIDATION_EPSILON (e.g. * in user code before importing QuEST, or passed as a preprocessor constant by the * compiler using argument -D), and runtime overridable using setValidationEpsilon() */ -#ifndef DEAULT_VALIDATION_EPSILON +#ifndef DEFAULT_VALIDATION_EPSILON #if FLOAT_PRECISION == 1 - #define DEAULT_VALIDATION_EPSILON 1E-5 + #define DEFAULT_VALIDATION_EPSILON 1E-5 #elif FLOAT_PRECISION == 2 - #define DEAULT_VALIDATION_EPSILON 1E-12 + #define DEFAULT_VALIDATION_EPSILON 1E-12 #elif FLOAT_PRECISION == 4 - #define DEAULT_VALIDATION_EPSILON 1E-13 + #define DEFAULT_VALIDATION_EPSILON 1E-13 #endif diff --git a/quest/include/qureg.h b/quest/include/qureg.h index b2e238601..fe561a691 100644 --- a/quest/include/qureg.h +++ b/quest/include/qureg.h @@ -5,7 +5,7 @@ #ifndef QUREG_H #define QUREG_H -#include "quest/include/types.h" +#include "types.h" // enable invocation by both C and C++ binaries #ifdef __cplusplus diff --git a/quest/include/types.h b/quest/include/types.h index b71200dd5..b308b560f 100644 --- a/quest/include/types.h +++ b/quest/include/types.h @@ -5,8 +5,8 @@ #ifndef TYPES_H #define TYPES_H -#include "quest/include/modes.h" -#include "quest/include/precision.h" +#include "modes.h" +#include "precision.h" diff --git a/quest/src/CMakeLists.txt b/quest/src/CMakeLists.txt new file mode 100644 index 000000000..acc03978d --- /dev/null +++ b/quest/src/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(api) +add_subdirectory(comm) +add_subdirectory(core) +add_subdirectory(cpu) +add_subdirectory(gpu) \ No newline at end of file diff --git a/quest/src/api/CMakeLists.txt b/quest/src/api/CMakeLists.txt new file mode 100644 index 000000000..a6d3137b6 --- /dev/null +++ b/quest/src/api/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(QuEST + PRIVATE + calculations.cpp + channels.cpp + debug.cpp + decoherence.cpp + environment.cpp + initialisations.cpp + matrices.cpp + modes.cpp + operations.cpp + paulis.cpp + qureg.cpp + types.cpp +) \ No newline at end of file diff --git a/quest/src/api/decoherence.cpp b/quest/src/api/decoherence.cpp index 3287bbf4b..30437c95b 100644 --- a/quest/src/api/decoherence.cpp +++ b/quest/src/api/decoherence.cpp @@ -123,8 +123,7 @@ void mixQureg(Qureg outQureg, Qureg inQureg, qreal inProb) { validate_quregFields(outQureg, __func__); validate_quregFields(inQureg, __func__); validate_probability(inProb, __func__); - validate_quregIsDensityMatrix(outQureg, __func__); - validate_quregsCanBeMixed(outQureg, inQureg, __func__); + validate_quregsCanBeMixed(outQureg, inQureg, __func__); // checks outQureg is densmatr qreal outProb = 1 - inProb; localiser_densmatr_mixQureg(outProb, outQureg, inProb, inQureg); diff --git a/quest/src/api/environment.cpp b/quest/src/api/environment.cpp index 3788deb5d..f38418d48 100644 --- a/quest/src/api/environment.cpp +++ b/quest/src/api/environment.cpp @@ -3,9 +3,9 @@ * themselves control and query the deployment environment. */ -#include "quest/include/environment.h" -#include "quest/include/precision.h" -#include "quest/include/modes.h" +#include "environment.h" +#include "precision.h" +#include "modes.h" #include "quest/src/core/errors.hpp" #include "quest/src/core/memory.hpp" diff --git a/quest/src/api/modes.cpp b/quest/src/api/modes.cpp index a9437cd4b..86e982c97 100644 --- a/quest/src/api/modes.cpp +++ b/quest/src/api/modes.cpp @@ -2,7 +2,7 @@ * API flags and functions for specifying deployment modes. */ -#include "quest/include/modes.h" +#include "modes.h" int modeflag::USE_AUTO = -1; diff --git a/quest/src/api/operations.cpp b/quest/src/api/operations.cpp index 8c9c05dc4..f0ce3eac2 100644 --- a/quest/src/api/operations.cpp +++ b/quest/src/api/operations.cpp @@ -376,8 +376,8 @@ void multiplyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix) { bool onlyMultiply = true; qcomp exponent = qcomp(1, 0); (qureg.isDensityMatrix)? - localiser_statevec_allTargDiagMatr(qureg, matrix, exponent) : - localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply); + localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply): + localiser_statevec_allTargDiagMatr(qureg, matrix, exponent); } void multiplyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent) { @@ -387,8 +387,8 @@ void multiplyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp bool onlyMultiply = true; (qureg.isDensityMatrix)? - localiser_statevec_allTargDiagMatr(qureg, matrix, exponent) : - localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply); + localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply): + localiser_statevec_allTargDiagMatr(qureg, matrix, exponent); } void applyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix) { @@ -400,8 +400,8 @@ void applyFullStateDiagMatr(Qureg qureg, FullStateDiagMatr matrix) { bool onlyMultiply = false; qcomp exponent = qcomp(1, 0); (qureg.isDensityMatrix)? - localiser_statevec_allTargDiagMatr(qureg, matrix, exponent) : - localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply); + localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply): + localiser_statevec_allTargDiagMatr(qureg, matrix, exponent); } void applyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp exponent) { @@ -412,8 +412,8 @@ void applyFullStateDiagMatrPower(Qureg qureg, FullStateDiagMatr matrix, qcomp ex bool onlyMultiply = false; (qureg.isDensityMatrix)? - localiser_statevec_allTargDiagMatr(qureg, matrix, exponent) : - localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply); + localiser_densmatr_allTargDiagMatr(qureg, matrix, exponent, onlyMultiply): + localiser_statevec_allTargDiagMatr(qureg, matrix, exponent); } diff --git a/quest/src/api/qureg.cpp b/quest/src/api/qureg.cpp index 0444b60df..a7b94e56d 100644 --- a/quest/src/api/qureg.cpp +++ b/quest/src/api/qureg.cpp @@ -3,9 +3,9 @@ * choosing their deployment modes. */ -#include "quest/include/qureg.h" -#include "quest/include/environment.h" -#include "quest/include/initialisations.h" +#include "qureg.h" +#include "environment.h" +#include "initialisations.h" #include "quest/src/core/validation.hpp" #include "quest/src/core/autodeployer.hpp" @@ -146,9 +146,6 @@ Qureg validateAndCreateCustomQureg(int numQubits, int isDensMatr, int useDistrib // automatically overwrite distrib, GPU, and multithread fields which were left as modeflag::USE_AUTO autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env); - // throw error if the user had forced multithreading but GPU accel was auto-chosen - validate_newQuregNotBothMultithreadedAndGpuAccel(useGpuAccel, useMultithread, caller); - Qureg qureg = qureg_populateNonHeapFields(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread); // always allocate CPU memory diff --git a/quest/src/comm/CMakeLists.txt b/quest/src/comm/CMakeLists.txt new file mode 100644 index 000000000..f05cce6f9 --- /dev/null +++ b/quest/src/comm/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(QuEST + PRIVATE + comm_config.cpp + comm_routines.cpp +) \ No newline at end of file diff --git a/quest/src/comm/comm_config.cpp b/quest/src/comm/comm_config.cpp index d5475a4e9..6d3287fdf 100644 --- a/quest/src/comm/comm_config.cpp +++ b/quest/src/comm/comm_config.cpp @@ -9,10 +9,10 @@ * functions should invoke the MPI API. */ -#include "quest/include/modes.h" -#include "quest/include/types.h" +#include "modes.h" +#include "types.h" -#include "quest/src/core/errors.hpp" +#include "../core/errors.hpp" #if COMPILE_MPI #include diff --git a/quest/src/core/CMakeLists.txt b/quest/src/core/CMakeLists.txt new file mode 100644 index 000000000..b0bbbfd1f --- /dev/null +++ b/quest/src/core/CMakeLists.txt @@ -0,0 +1,13 @@ +target_sources(QuEST + PRIVATE + accelerator.cpp + autodeployer.cpp + errors.cpp + localiser.cpp + memory.cpp + parser.cpp + printer.cpp + randomiser.cpp + utilities.cpp + validation.cpp +) \ No newline at end of file diff --git a/quest/src/core/autodeployer.cpp b/quest/src/core/autodeployer.cpp index 7ab13dec5..a859f9e95 100644 --- a/quest/src/core/autodeployer.cpp +++ b/quest/src/core/autodeployer.cpp @@ -5,14 +5,14 @@ * Qureg dimensions. */ -#include "quest/include/modes.h" -#include "quest/include/environment.h" +#include "modes.h" +#include "environment.h" -#include "quest/src/core/memory.hpp" -#include "quest/src/core/autodeployer.hpp" -#include "quest/src/comm/comm_config.hpp" -#include "quest/src/cpu/cpu_config.hpp" -#include "quest/src/gpu/gpu_config.hpp" +#include "../core/memory.hpp" +#include "../core/autodeployer.hpp" +#include "../comm/comm_config.hpp" +#include "../cpu/cpu_config.hpp" +#include "../gpu/gpu_config.hpp" @@ -67,7 +67,8 @@ void chooseWhetherToDistributeQureg(int numQubits, int isDensMatr, int &useDistr // it's ok if we cannot query RAM; if we'd have exceeded it, it's likely we'll exceed auto-threshold and will still distribute } catch (mem::COULD_NOT_QUERY_RAM &e) {} - // force distribution if GPU deployment is possible but we exceed local VRAM + // force distribution if GPU deployment is available but we exceed local VRAM; + // this is preferable over falling back to CPU-only which would be astonishingly slow if (useGpuAccel == 1 || useGpuAccel == modeflag::USE_AUTO) { size_t localGpuMem = gpu_getCurrentAvailableMemoryInBytes(); if (!mem_canQuregFitInMemory(numQubits, isDensMatr, 1, localGpuMem)) { @@ -76,47 +77,44 @@ void chooseWhetherToDistributeQureg(int numQubits, int isDensMatr, int &useDistr } } - // by now, we know that Qureg can definitely fit into a single GPU, or principally fit into RAM, - // but we may still wish to distribute it so that multiple Quregs don't choke up memory. + // to reach here, we know that Qureg can fit into the remaining memory of a single GPU, or principally + // fit into RAM, but we may still wish to distribute for improved parallelisation and to avoid memory saturation int effectiveNumQubitsPerNode = mem_getEffectiveNumStateVecQubitsPerNode(numQubits, isDensMatr, numEnvNodes); useDistrib = (effectiveNumQubitsPerNode >= MIN_NUM_LOCAL_QUBITS_FOR_AUTO_QUREG_DISTRIBUTION); } -void chooseWhetherToGpuAccelQureg(int numQubits, int isDensMatr, int useDistrib, int &useGpuAccel, int numQuregNodes) { +void chooseWhetherToGpuAccelQureg(int numQubits, int isDensMatr, int &useGpuAccel, int numQuregNodes) { // if the flag is already set, don't change it if (useGpuAccel != modeflag::USE_AUTO) return; - // determine the 'effective number of qubits' each GPU would have to simulate, if distributed + // determine the 'effective number of qubits' each GPU would have to simulate, if distributed... int effectiveNumQubits = mem_getEffectiveNumStateVecQubitsPerNode(numQubits, isDensMatr, numQuregNodes); - // choose to GPU accelerate only if that's not too few + // and choose to GPU accelerate only if that's not too few useGpuAccel = (effectiveNumQubits >= MIN_NUM_LOCAL_QUBITS_FOR_AUTO_QUREG_GPU_ACCELERATION); // notice there was no automatic disabling of GPU acceleration in the scenario that the local // partition exceeded GPU memory. This is because such a scenario would be catastrophically // slow and astonish users by leaving GPUs idle in intensive simulation. Instead, we auto-deploy - // to GPU and subsequent validation will notice we exceeded GPU memory. + // to GPU anyway and subsequent validation will notice we exceeded GPU memory and report an error. } -void chooseWhetherToMultithreadQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int &useMultithread, int numQuregNodes) { +void chooseWhetherToMultithreadQureg(int numQubits, int isDensMatr, int &useMultithread, int numQuregNodes) { // if the flag is already set (user-given, or inferred from env), don't change it if (useMultithread != modeflag::USE_AUTO) return; - // if GPU-aceleration was chosen, disable auto multithreading... - if (useGpuAccel) { - useMultithread = 0; - return; - } - - // otherwise, we're not GPU-accelerating, and should choose to multithread based on Qureg size + // otherwise, choose to multithread based on Qureg size int effectiveNumQubits = mem_getEffectiveNumStateVecQubitsPerNode(numQubits, isDensMatr, numQuregNodes); useMultithread = (effectiveNumQubits >= MIN_NUM_LOCAL_QUBITS_FOR_AUTO_QUREG_MULTITHREADING); + + // note the qureg may be simultaneously GPU-accelerated and so never use its + // multithreaded CPU routines, except in functions which accept multiple Quregs } @@ -125,8 +123,6 @@ void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistri // preconditions: // - the given configuration is compatible with env (assured by prior validation) // - this means no deployment is forced (=1) which is incompatible with env - // - it also means GPU-acceleration and multithreading are not simultaneously forced - // (although they may still be left automatic and need explicit revision) // disable any automatic deployments not permitted by env (it's gauranteed we never overwrite =1 to =0) if (!env.isDistributed) @@ -141,11 +137,15 @@ void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistri if (env.numNodes == 1) useDistrib = 0; - // overwrite any auto options (== modeflag::USE_AUTO) + // overwrite useDistrib chooseWhetherToDistributeQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, env.numNodes); int numQuregNodes = (useDistrib)? env.numNodes : 1; - chooseWhetherToGpuAccelQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, numQuregNodes); - chooseWhetherToMultithreadQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, numQuregNodes); + + // overwrite useGpuAccel + chooseWhetherToGpuAccelQureg(numQubits, isDensMatr, useGpuAccel, numQuregNodes); + + // overwrite useMultithread + chooseWhetherToMultithreadQureg(numQubits, isDensMatr, useMultithread, numQuregNodes); } diff --git a/quest/src/core/autodeployer.hpp b/quest/src/core/autodeployer.hpp index 8f3b8cf6a..fc448ed9b 100644 --- a/quest/src/core/autodeployer.hpp +++ b/quest/src/core/autodeployer.hpp @@ -8,7 +8,7 @@ #ifndef AUTODEPLOYER_HPP #define AUTODEPLOYER_HPP -#include "quest/include/environment.h" +#include "environment.h" diff --git a/quest/src/core/bitwise.hpp b/quest/src/core/bitwise.hpp index d23b4edcd..644cccb64 100644 --- a/quest/src/core/bitwise.hpp +++ b/quest/src/core/bitwise.hpp @@ -6,7 +6,7 @@ #ifndef BITWISE_HPP #define BITWISE_HPP -#include "quest/include/types.h" +#include "types.h" #include "quest/src/core/inliner.hpp" diff --git a/quest/src/core/errors.cpp b/quest/src/core/errors.cpp index 1e9d9ad06..07c226d83 100644 --- a/quest/src/core/errors.cpp +++ b/quest/src/core/errors.cpp @@ -48,7 +48,10 @@ void raiseInternalError(string errorMsg) { void error_functionNotImplemented(const char* caller) { string name = caller; - raiseInternalError("The function '" + name + "' has not yet been implemented."); + + // DEBUG + // raiseInternalError("The function '" + name + "' has not yet been implemented."); + print("The function '" + name + "' has not yet been implemented."); } @@ -185,7 +188,7 @@ void assert_pairRankIsDistinct(Qureg qureg, int pairRank) { void assert_bufferSendRecvDoesNotOverlap(qindex sendInd, qindex recvInd, qindex numAmps) { - if (sendInd + numAmps > recvInd) + if (sendInd < recvInd + numAmps) raiseInternalError("A distributed function attempted to send and receive portions of the buffer which overlapped."); } diff --git a/quest/src/core/localiser.cpp b/quest/src/core/localiser.cpp index fe624b3cf..3abf3ddd8 100644 --- a/quest/src/core/localiser.cpp +++ b/quest/src/core/localiser.cpp @@ -1427,6 +1427,13 @@ void localiser_densmatr_twoQubitDepolarising(Qureg qureg, int qubit1, int qubit2 bool comm1 = doesChannelRequireComm(qureg, qubit1); bool comm2 = doesChannelRequireComm(qureg, qubit2); + + + // TODO: + // this is bugged, even when non-distributed!!! + + + if (comm2 && comm1) twoQubitDepolarisingOnPrefixAndPrefix(qureg, qubit1, qubit2, prob); if (comm2 && !comm1) diff --git a/quest/src/core/memory.cpp b/quest/src/core/memory.cpp index c376531a4..dedea4a3e 100644 --- a/quest/src/core/memory.cpp +++ b/quest/src/core/memory.cpp @@ -13,9 +13,9 @@ #include "quest/include/types.h" #include "quest/include/paulis.h" -#include "quest/src/core/memory.hpp" -#include "quest/src/core/bitwise.hpp" -#include "quest/src/core/errors.hpp" +#include "../core/memory.hpp" +#include "../core/bitwise.hpp" +#include "../core/errors.hpp" #include diff --git a/quest/src/core/utilities.cpp b/quest/src/core/utilities.cpp index 176b53e53..00ed7f2ff 100644 --- a/quest/src/core/utilities.cpp +++ b/quest/src/core/utilities.cpp @@ -221,6 +221,9 @@ qindex util_getNumLocalDiagonalAmpsWithBits(Qureg qureg, vector qubits, vec if (util_getRankBitOfBraQubit(qubits[i], qureg) != outcomes[i]) return 0; + // TODO: + // I THINK THIS MAY BE BUGGED / INCORRECT LOGIC!! + // otherwise, every 2^#qubits local diagonal is consistent with outcomes qindex numColsPerNode = powerOf2(qureg.logNumColsPerNode); qindex numDiags = numColsPerNode / powerOf2(qubits.size()); diff --git a/quest/src/core/validation.cpp b/quest/src/core/validation.cpp index eceab0e93..44f9b13f4 100644 --- a/quest/src/core/validation.cpp +++ b/quest/src/core/validation.cpp @@ -167,10 +167,6 @@ namespace report { "Cannot enable multithreaded processing of a Qureg created in a non-multithreaded QuEST environment."; - string NEW_GPU_QUREG_CANNOT_USE_MULTITHREADING = - "Cannot simultaneously GPU-accelerate and multithread a Qureg. Please disable multithreading, or set it to ${AUTO_DEPLOYMENT_FLAG} for QuEST to automatically disable it when deploying to GPU."; - - string NEW_QUREG_CANNOT_FIT_INTO_NON_DISTRIB_CPU_MEM = "The non-distributed Qureg (isDensity=${IS_DENS}) of ${NUM_QUBITS} qubits would be too large (${QCOMP_BYTES} * ${EXP_BASE}^${NUM_QUBITS} bytes) to fit into a single node's RAM (${RAM_SIZE} bytes). See reportQuESTEnv(), and consider using distribution."; @@ -1002,13 +998,13 @@ bool validateconfig_isEnabled() { * Hermiticity and CPTP checks are performed */ -static qreal global_validationEpsilon = DEAULT_VALIDATION_EPSILON; +static qreal global_validationEpsilon = DEFAULT_VALIDATION_EPSILON; void validateconfig_setEpsilon(qreal eps) { global_validationEpsilon = eps; } void validateconfig_setEpsilonToDefault() { - global_validationEpsilon = DEAULT_VALIDATION_EPSILON; + global_validationEpsilon = DEFAULT_VALIDATION_EPSILON; } qreal validateconfig_getEpsilon() { return global_validationEpsilon; @@ -1430,13 +1426,6 @@ void assertQuregFitsInGpuMem(int numQubits, int isDensMatr, int isDistrib, int i } } -void validate_newQuregNotBothMultithreadedAndGpuAccel(int useGpu, int useMultithread, const char* caller) { - - // note either or both of useGpu and useMultithread are permitted to be modeflag::USE_AUTO (=-1) - tokenSubs vars = {{"${AUTO_DEPLOYMENT_FLAG}", modeflag::USE_AUTO}}; - assertThat(useGpu != 1 || useMultithread != 1, report::NEW_GPU_QUREG_CANNOT_USE_MULTITHREADING, vars, caller); -} - void validate_newQuregParams(int numQubits, int isDensMatr, int isDistrib, int isGpuAccel, int isMultithread, QuESTEnv env, const char* caller) { // some of the below validation involves getting distributed node consensus, which @@ -1452,8 +1441,6 @@ void validate_newQuregParams(int numQubits, int isDensMatr, int isDistrib, int i assertQuregNotDistributedOverTooManyNodes(numQubits, isDensMatr, isDistrib, env, caller); assertQuregFitsInCpuMem(numQubits, isDensMatr, isDistrib, env, caller); assertQuregFitsInGpuMem(numQubits, isDensMatr, isDistrib, isGpuAccel, env, caller); - - validate_newQuregNotBothMultithreadedAndGpuAccel(isGpuAccel, isMultithread, caller); } void validate_newQuregAllocs(Qureg qureg, const char* caller) { @@ -1549,8 +1536,8 @@ void assertMatrixTotalNumElemsDontExceedMaxIndex(int numQubits, bool isDense, co int maxNumQubits = mem_getMaxNumMatrixQubitsBeforeIndexOverflow(isDense); string msg = (isDense)? - report::NEW_DIAG_MATR_NUM_ELEMS_WOULD_EXCEED_QINDEX : - report::NEW_COMP_MATR_NUM_ELEMS_WOULD_EXCEED_QINDEX ; + report::NEW_COMP_MATR_NUM_ELEMS_WOULD_EXCEED_QINDEX: + report::NEW_DIAG_MATR_NUM_ELEMS_WOULD_EXCEED_QINDEX; tokenSubs vars = { {"${NUM_QUBITS}", numQubits}, diff --git a/quest/src/core/validation.hpp b/quest/src/core/validation.hpp index 926374574..ca8986170 100644 --- a/quest/src/core/validation.hpp +++ b/quest/src/core/validation.hpp @@ -95,8 +95,6 @@ void validate_newMaxNumReportedSigFigs(int numSigFigs, const char* caller); void validate_newQuregParams(int numQubits, int isDensMatr, int isDistrib, int isGpuAccel, int numCpuThreads, QuESTEnv env, const char* caller); -void validate_newQuregNotBothMultithreadedAndGpuAccel(int useGpu, int numThreads, const char* caller); - void validate_newQuregAllocs(Qureg qureg, const char* caller); diff --git a/quest/src/cpu/CMakeLists.txt b/quest/src/cpu/CMakeLists.txt new file mode 100644 index 000000000..8f713f0f3 --- /dev/null +++ b/quest/src/cpu/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(QuEST + PRIVATE + cpu_config.cpp + cpu_subroutines.cpp +) \ No newline at end of file diff --git a/quest/src/cpu/cpu_config.cpp b/quest/src/cpu/cpu_config.cpp index 4dc81cdc2..0d34d36e7 100644 --- a/quest/src/cpu/cpu_config.cpp +++ b/quest/src/cpu/cpu_config.cpp @@ -6,7 +6,7 @@ #include "quest/include/types.h" #include "quest/include/paulis.h" -#include "quest/src/core/errors.hpp" +#include "../core/errors.hpp" #include #include diff --git a/quest/src/cpu/cpu_subroutines.cpp b/quest/src/cpu/cpu_subroutines.cpp index 89c7e2bfd..65678e338 100644 --- a/quest/src/cpu/cpu_subroutines.cpp +++ b/quest/src/cpu/cpu_subroutines.cpp @@ -1627,6 +1627,9 @@ qreal cpu_densmatr_calcProbOfMultiQubitOutcome_sub(Qureg qureg, vector qubi #pragma omp parallel for reduction(+:prob) if(qureg.isMultithreaded) for (qindex n=0; n q auto ampIter = thrust::make_permutation_iterator(getStartPtr(qureg), diagIter); auto probIter = thrust::make_transform_iterator(ampIter, probFunctor); + // TODO: + // I BELIEVE THIS IS BUGGED AND/OR HAS INCORRECT LOGIC! + qindex numIts = util_getNumLocalDiagonalAmpsWithBits(qureg, qubits, outcomes); qreal prob = thrust::reduce(probIter, probIter + numIts); return prob; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e20998d51..b25f73cc6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,8 +5,8 @@ include_directories(catch) # compile Catch2 unit tests in C++14 add_executable(tests - main.cpp - utilities.cpp + test_main.cpp + test_utilities.cpp test_calculations.cpp test_data_structures.cpp test_decoherence.cpp @@ -15,23 +15,13 @@ add_executable(tests test_state_initialisations.cpp test_unitaries.cpp ) -set_property(TARGET tests PROPERTY CXX_STANDARD 14) # link QuEST -if (WIN32) - target_link_libraries(tests QuEST) -else () - target_link_libraries(tests QuEST m) -endif () +target_link_libraries(tests QuEST) -if (${DISTRIBUTED}) +if (ENABLE_DISTRIBUTION) # distributed unit tests must supply an MPI-clue to (modified) catch add_definitions(-DDISTRIBUTED_MODE) - - # compile and link C++ MPI - find_package(MPI REQUIRED) - include_directories(${MPI_CXX_INCLUDE_PATH}) - target_link_libraries(tests ${MPI_CXX_LIBRARIES}) endif () # locate Catch2 scripts for CMake diff --git a/tests/test_calculations.cpp b/tests/test_calculations.cpp index 8a19b59a3..06f781e6c 100644 --- a/tests/test_calculations.cpp +++ b/tests/test_calculations.cpp @@ -1,7 +1,14 @@ #include "catch.hpp" -#include "QuEST.h" -#include "utilities.hpp" + +// must define preprocessors to enable quest's +// deprecated v3 API, and disable the numerous +// warnings issued by its compilation +#define INCLUDE_DEPRECATED_FUNCTIONS 1 +#define DISABLE_DEPRECATION_WARNINGS 1 +#include "quest.h" + +#include "test_utilities.hpp" /* allows concise use of Contains in catch's REQUIRE_THROWS_WITH */ using Catch::Matchers::Contains; @@ -14,8 +21,8 @@ using Catch::Matchers::Contains; */ TEST_CASE( "calcDensityInnerProduct", "[calculations]" ) { - Qureg mat1 = createDensityQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat2 = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg mat1 = createForcedDensityQureg(NUM_QUBITS); + Qureg mat2 = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -40,7 +47,7 @@ TEST_CASE( "calcDensityInnerProduct", "[calculations]" ) { prod += conj(r1[i]) * r2[i]; qreal densProd = pow(abs(prod),2); - REQUIRE( calcDensityInnerProduct(mat1,mat2) == Approx(densProd) ); + REQUIRE( real(calcDensityInnerProduct(mat1,mat2)) == Approx(densProd) ); } SECTION( "mixed" ) { @@ -56,10 +63,10 @@ TEST_CASE( "calcDensityInnerProduct", "[calculations]" ) { refProd += conj(ref1[i][j]) * ref2[i][j]; REQUIRE( imag(refProd) == Approx(0).margin(REAL_EPS) ); - REQUIRE( calcDensityInnerProduct(mat1,mat2) == Approx(real(refProd)) ); + REQUIRE( real(calcDensityInnerProduct(mat1,mat2)) == Approx(real(refProd)) ); // should be invariant under ordering - REQUIRE( calcDensityInnerProduct(mat1,mat2) == Approx(calcDensityInnerProduct(mat2,mat1)) ); + REQUIRE( real(calcDensityInnerProduct(mat1,mat2)) == Approx(real(calcDensityInnerProduct(mat2,mat1))) ); } SECTION( "unnormalised" ) { @@ -75,7 +82,7 @@ TEST_CASE( "calcDensityInnerProduct", "[calculations]" ) { for (size_t j=0; j */ - - QVector sumRef = refHamil * vecRef; - qcomp prod = 0; - for (size_t i=0; i */ - int numTerms = 1; - PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); - - Qureg vec2 = createQureg(NUM_QUBITS + 1, QUEST_ENV); - REQUIRE_THROWS_WITH( calcExpecPauliHamil(vec, hamil, vec2), Contains("Dimensions") && Contains("don't match") ); - destroyQureg(vec2, QUEST_ENV); - - Qureg mat2 = createDensityQureg(NUM_QUBITS + 1, QUEST_ENV); - REQUIRE_THROWS_WITH( calcExpecPauliHamil(mat, hamil, mat2), Contains("Dimensions") && Contains("don't match") ); - destroyQureg(mat2, QUEST_ENV); - - destroyPauliHamil(hamil); - } - SECTION( "matching hamiltonian qubits" ) { - - int numTerms = 1; - PauliHamil hamil = createPauliHamil(NUM_QUBITS + 1, numTerms); + // QVector sumRef = refHamil * vecRef; + // qcomp prod = 0; + // for (size_t i=0; i paulis(numTargs); for (int i=0; i paulis(totNumCodes); + vector coeffs(numSumTerms); + setRandomPauliSum(coeffs.data(), paulis.data(), NUM_QUBITS, numSumTerms); // produce a numTargs-big matrix 'pauliSum' by pauli-matrix tensoring and summing - QMatrix pauliSum = toQMatrix(coeffs, paulis, NUM_QUBITS, numSumTerms); + QMatrix pauliSum = toQMatrix(coeffs.data(), paulis.data(), NUM_QUBITS, numSumTerms); SECTION( "state-vector" ) { @@ -473,7 +527,7 @@ TEST_CASE( "calcExpecPauliSum", "[calculations]" ) { prod += conj(vecRef[i]) * sumRef[i]; REQUIRE( imag(prod) == Approx(0).margin(10*REAL_EPS) ); - qreal res = calcExpecPauliSum(vec, paulis, coeffs, numSumTerms, vecWork); + qreal res = calcExpecPauliSum(vec, paulis.data(), coeffs.data(), numSumTerms, vecWork); REQUIRE( res == Approx(real(prod)).margin(10*REAL_EPS) ); } SECTION( "density-matrix" ) { @@ -485,7 +539,7 @@ TEST_CASE( "calcExpecPauliSum", "[calculations]" ) { tr += real(matRef[i][i]); // (get real, since we start in a non-Hermitian state, hence diagonal isn't real) - qreal res = calcExpecPauliSum(mat, paulis, coeffs, numSumTerms, matWork); + qreal res = calcExpecPauliSum(mat, paulis.data(), coeffs.data(), numSumTerms, matWork); REQUIRE( res == Approx(tr).margin(1E2*REAL_EPS) ); } } @@ -500,14 +554,14 @@ TEST_CASE( "calcExpecPauliSum", "[calculations]" ) { // make valid params int numSumTerms = 3; - VLA(qreal, coeffs, numSumTerms); - VLA(pauliOpType, codes, numSumTerms*NUM_QUBITS); + vector coeffs(numSumTerms); + vector codes(numSumTerms*NUM_QUBITS); for (int i=0; i = 1/sqrt(nv)|vec> - qreal nv = 0; - for (size_t i=0; i = sqrt(nv)*sqrt(nv) = nv, - // hence ||^2 = nv*nv - REQUIRE( calcFidelity(vec,vec) == Approx( nv*nv ) ); - - qcomp dotProd = 0; - for (size_t i=0; i = 1/sqrt(nv)|vec> + // qreal nv = 0; + // for (size_t i=0; i = sqrt(nv)*sqrt(nv) = nv, + // // hence ||^2 = nv*nv + // REQUIRE( calcFidelity(vec,vec) == Approx( nv*nv ) ); + + // qcomp dotProd = 0; + // for (size_t i=0; i ] = real[ ) ] - QVector rhs = matRef * pureRef; - qcomp dotProd = 0; - for (size_t i=0; i ] = real[ ) ] + // QVector rhs = matRef * pureRef; + // qcomp dotProd = 0; + // for (size_t i=0; i probs(numOutcomes); QVector refProbs = QVector(numOutcomes); SECTION( "state-vector" ) { @@ -906,8 +974,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { refProbs[outcome] += pow(abs(ref[i]), 2); } - calcProbOfAllOutcomes(probs, vec, qubits, numQubits); - REQUIRE( areEqual(refProbs, probs) ); + calcProbOfAllOutcomes(probs.data(), vec, qubits, numQubits); + REQUIRE( areEqual(refProbs, probs.data()) ); } SECTION( "unnormalised" ) { @@ -924,8 +992,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { refProbs[outcome] += pow(abs(ref[i]), 2); } - calcProbOfAllOutcomes(probs, vec, qubits, numQubits); - REQUIRE( areEqual(refProbs, probs) ); + calcProbOfAllOutcomes(probs.data(), vec, qubits, numQubits); + REQUIRE( areEqual(refProbs, probs.data()) ); } } SECTION( "density-matrix" ) { @@ -945,8 +1013,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { refProbs[outcome] += real(ref[i][i]); } - calcProbOfAllOutcomes(probs, mat, qubits, numQubits); - REQUIRE( areEqual(refProbs, probs) ); + calcProbOfAllOutcomes(probs.data(), mat, qubits, numQubits); + REQUIRE( areEqual(refProbs, probs.data()) ); } SECTION( "unnormalised" ) { @@ -963,8 +1031,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { refProbs[outcome] += real(ref[i][i]); } - calcProbOfAllOutcomes(probs, mat, qubits, numQubits); - REQUIRE( areEqual(refProbs, probs) ); + calcProbOfAllOutcomes(probs.data(), mat, qubits, numQubits); + REQUIRE( areEqual(refProbs, probs.data()) ); } } } @@ -976,8 +1044,13 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { SECTION( "number of qubits" ) { - numQubits = GENERATE( -1, 0, NUM_QUBITS+1 ); - REQUIRE_THROWS_WITH( calcProbOfAllOutcomes(probs, mat, qubits, numQubits), Contains("Invalid number of target qubits") ); + // too small + numQubits = GENERATE( -1, 0 ); + REQUIRE_THROWS_WITH( calcProbOfAllOutcomes(probs, mat, qubits, numQubits), Contains("specified number of target qubits") && Contains("invalid.") ); + + // too big + numQubits = NUM_QUBITS + 1; + REQUIRE_THROWS_WITH( calcProbOfAllOutcomes(probs, mat, qubits, numQubits), Contains("target qubits") && Contains("exceeds the number of qubits in the Qureg") ); } SECTION( "qubit indices" ) { @@ -990,8 +1063,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { REQUIRE_THROWS_WITH( calcProbOfAllOutcomes(probs, mat, qubits, numQubits), Contains("qubits must be unique") ); } } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -1003,8 +1076,8 @@ TEST_CASE( "calcProbOfAllOutcomes", "[calculations]" ) { */ TEST_CASE( "calcProbOfOutcome", "[calculations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -1033,22 +1106,19 @@ TEST_CASE( "calcProbOfOutcome", "[calculations]" ) { QVector ref = getRandomQVector(1<> target) & 1; // target-th bit - if (bit == 0) + if (bit == outcome) prob += pow(abs(ref[ind]), 2); } - if (outcome == 1) - prob = 1 - prob; REQUIRE( calcProbOfOutcome(vec, target, outcome) == Approx(prob) ); } } SECTION( "density-matrix" ) { - + SECTION( "pure" ) { // set mat to a random |r>> target) & 1; // target-th bit - if (bit == 0) + if (bit == outcome) tr += real(ref[ind][ind]); } - if (outcome == 1) - tr = 1 - tr; REQUIRE( calcProbOfOutcome(mat, target, outcome) == Approx(tr) ); } @@ -1111,11 +1179,11 @@ TEST_CASE( "calcProbOfOutcome", "[calculations]" ) { SECTION( "outcome value" ) { int outcome = GENERATE( -1, 2 ); - REQUIRE_THROWS_WITH( calcProbOfOutcome(vec, 0, outcome), Contains("Invalid measurement outcome") ); + REQUIRE_THROWS_WITH( calcProbOfOutcome(vec, 0, outcome), Contains("measurement outcome") && Contains("invalid") ); } } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -1126,7 +1194,7 @@ TEST_CASE( "calcProbOfOutcome", "[calculations]" ) { */ TEST_CASE( "calcPurity", "[calculations]" ) { - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -1179,15 +1247,17 @@ TEST_CASE( "calcPurity", "[calculations]" ) { } } SECTION( "input validation" ) { + + // in v4, this accepts state-vectors - SECTION( "state-vector" ) { - - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - REQUIRE_THROWS_WITH( calcPurity(vec), Contains("valid only for density matrices") ); - destroyQureg(vec, QUEST_ENV); - } + // SECTION( "state-vector" ) { + + // Qureg vec = createForcedQureg(NUM_QUBITS); + // REQUIRE_THROWS_WITH( calcPurity(vec), Contains("valid only for density matrices") ); + // destroyQureg(vec); + // } } - destroyQureg(mat, QUEST_ENV); + destroyQureg(mat); } @@ -1198,8 +1268,8 @@ TEST_CASE( "calcPurity", "[calculations]" ) { */ TEST_CASE( "calcTotalProb", "[calculations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -1253,8 +1323,8 @@ TEST_CASE( "calcTotalProb", "[calculations]" ) { // no validation SUCCEED(); } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -1265,7 +1335,7 @@ TEST_CASE( "calcTotalProb", "[calculations]" ) { */ TEST_CASE( "getAmp", "[calculations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -1275,8 +1345,8 @@ TEST_CASE( "getAmp", "[calculations]" ) { QVector ref = toQVector(vec); int ind = GENERATE( range(0,1< 25) numQb = 13; // max size - Qureg mat = createDensityQureg(numQb, QUEST_ENV); + Qureg mat = createForcedDensityQureg(numQb); REQUIRE( getNumQubits(mat) == numQb ); - destroyQureg(mat, QUEST_ENV); + destroyQureg(mat); } } SECTION( "input validation" ) { @@ -1452,7 +1525,7 @@ TEST_CASE( "getNumQubits", "[calculations]" ) { */ TEST_CASE( "getProbAmp", "[calculations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -1471,16 +1544,20 @@ TEST_CASE( "getProbAmp", "[calculations]" ) { SECTION( "state index" ) { int ind = GENERATE( -1, 1<<0| QMatrix ref = getZeroMatrix(1<<0| REQUIRE( areEqual(reg, ref) ); - destroyQureg(reg, QUEST_ENV); + destroyQureg(reg); } SECTION( "input validation") { SECTION( "number of qubits" ) { int numQb = GENERATE( -1, 0 ); - REQUIRE_THROWS_WITH( createDensityQureg(numQb, QUEST_ENV), Contains("Invalid number of qubits") ); + REQUIRE_THROWS_WITH( createForcedDensityQureg(numQb), Contains("must contain one or more qubits") ); } SECTION( "number of amplitudes" ) { + + // v4 does not consult QuESTEnv - // use local QuESTEnv to safely modify - QuESTEnv env = QUEST_ENV; + // // use local QuESTEnv to safely modify + // QuESTEnv env = getQuESTEnv(); // too many amplitudes to store in type int maxQb = (int) calcLog2(SIZE_MAX) / 2; - REQUIRE_THROWS_WITH( createDensityQureg(maxQb+1, env), Contains("Too many qubits") && Contains("size_t type") ); - - /* n-qubit density matrix contains 2^(2n) amplitudes - * so can be spread between at most 2^(2n) ranks - */ - /* env.numRanks is an int, so maxQb must be capped at 16 for this - * test to avoid an integer overflow when storing 2**(2*minQb) in it - */ - maxQb = maxQb > 16 ? 16 : maxQb; - int minQb = GENERATE_COPY( range(3,maxQb) ); - env.numRanks = (int) pow(2, 2*minQb); - int numQb = GENERATE_COPY( range(1,minQb) ); - REQUIRE_THROWS_WITH( createDensityQureg(numQb, env), Contains("Too few qubits") ); + REQUIRE_THROWS_WITH( createForcedDensityQureg(maxQb+1), + Contains("the density matrix would contain more amplitudes") && Contains("than can be addressed by the qindex type") ); + + // it is non-trivial to force an invalid distribution + // in v4, since QuESTEnv arg is no longer consulted; we + // will reserve this input validation for the full tests + + // /* n-qubit density matrix contains 2^(2n) amplitudes + // * so can be spread between at most 2^(2n) ranks + // */ + // /* env.numNodes is an int, so maxQb must be capped at 16 for this + // * test to avoid an integer overflow when storing 2**(2*minQb) in it + // */ + // maxQb = maxQb > 16 ? 16 : maxQb; + // int minQb = GENERATE_COPY( range(3,maxQb) ); + // env.numNodes = (int) pow(2, 2*minQb); + // int numQb = GENERATE_COPY( range(1,minQb) ); + // REQUIRE_THROWS_WITH( createForcedDensityQureg(numQb, env), Contains("Too few qubits") ); } SECTION( "available memory" ) { @@ -199,9 +241,11 @@ TEST_CASE( "createDensityQureg", "[data_structures]" ) { * @author Tyson Jones */ TEST_CASE( "createDiagonalOp", "[data_structures]" ) { + + QuESTEnv env = getQuESTEnv(); // must be at least one amplitude per node - int minNumQb = calcLog2(QUEST_ENV.numRanks); + int minNumQb = calcLog2(env.numNodes); if (minNumQb == 0) minNumQb = 1; @@ -209,48 +253,47 @@ TEST_CASE( "createDiagonalOp", "[data_structures]" ) { // try 10 valid number of qubits int numQb = GENERATE_COPY( range(minNumQb, minNumQb+10) ); - DiagonalOp op = createDiagonalOp(numQb, QUEST_ENV); + DiagonalOp op = createDiagonalOp(numQb, env); // check properties are correct REQUIRE( op.numQubits == numQb ); - REQUIRE( op.chunkId == QUEST_ENV.rank ); - REQUIRE( op.numChunks == QUEST_ENV.numRanks ); - REQUIRE( op.numElemsPerChunk == (1LL << numQb) / QUEST_ENV.numRanks ); - REQUIRE( op.real != NULL ); - REQUIRE( op.imag != NULL ); + REQUIRE( op.numElemsPerNode == (1LL << numQb) / (op.isDistributed? env.numNodes : 1) ); + REQUIRE( op.cpuElems != NULL ); // check all elements in CPU are zero REQUIRE( areEqual(toQVector(op), QVector(1LL << numQb)) ); // (no concise way to check this for GPU) - destroyDiagonalOp(op, QUEST_ENV); + destroyDiagonalOp(op, env); } SECTION( "input validation" ) { SECTION( "number of qubits" ) { int numQb = GENERATE( -1, 0 ); - REQUIRE_THROWS_WITH( createDiagonalOp(numQb, QUEST_ENV), Contains("Invalid number of qubits") ); + REQUIRE_THROWS_WITH( createDiagonalOp(numQb, env), Contains("must target one or more qubits") ); } SECTION( "number of elements" ) { - // use local QuESTEnv to safely modify - QuESTEnv env = QUEST_ENV; - // too many amplitudes to store in type int maxQb = (int) calcLog2(SIZE_MAX); - REQUIRE_THROWS_WITH( createDiagonalOp(maxQb+1, env), Contains("Too many qubits") && Contains("size_t type") ); - - /* env.numRanks is an int, so maxQb must be capped at 32 for this - * test to avoid an integer overflow when storing 2**minQb in it - */ - maxQb = maxQb > 32 ? 32 : maxQb; - // too few amplitudes to distribute - int minQb = GENERATE_COPY( range(2,maxQb) ); - env.numRanks = (int) pow(2, minQb); - int numQb = GENERATE_COPY( range(1,minQb) ); - REQUIRE_THROWS_WITH( createDiagonalOp(numQb, env), Contains("Too few qubits") && Contains("distributed")); + REQUIRE_THROWS_WITH( createDiagonalOp(maxQb+1, env), + Contains("the matrix would contain more elements") && Contains("than the maximum which can be addressed by the qindex type") ); + + // cannot test when there are too few elements, since the deprecated + // interface is redirecting to createFullStateDiagMatr which auto-deploys, + // and so simply automatically disables distribution + + // /* env.numNodes is an int, so maxQb must be capped at 32 for this + // * test to avoid an integer overflow when storing 2**minQb in it + // */ + // maxQb = maxQb > 32 ? 32 : maxQb; + // // too few amplitudes to distribute + // int minQb = GENERATE_COPY( range(2,maxQb) ); + // env.numNodes = (int) pow(2, minQb); + // int numQb = GENERATE_COPY( range(1,minQb) ); + // REQUIRE_THROWS_WITH( createDiagonalOp(numQb, env), Contains("Too few qubits") && Contains("distributed")); } SECTION( "available memory" ) { @@ -263,350 +306,365 @@ TEST_CASE( "createDiagonalOp", "[data_structures]" ) { -/** @sa createDiagonalOpFromPauliHamilFile - * @ingroup unittest - * @author Tyson Jones - */ -TEST_CASE( "createDiagonalOpFromPauliHamilFile", "[data_structures]" ) { +// createDiagonalOpFromPauliHamilFile removed because PauliHamil +// is deprecated, and replacement PauliStrSum (with analogous function +// createFullStateDiagMatrFromPauliStrSumFile) is incompatible - // files created & populated during the test, and deleted afterward - char fnPrefix[] = "temp_createDiagonalOpFromPauliHamilFile"; - char fn[100]; - - // each test uses a unique filename (managed by master node), to avoid file IO locks - // (it's safe for sub-test to overwrite this, since after each test, all files - // with prefix fnPrefix are deleted) - setUniqueFilename(fn, fnPrefix); - - // diagonal op must have at least one amplitude per node - int minNumQb = calcLog2(QUEST_ENV.numRanks); - if (minNumQb == 0) - minNumQb = 1; + // /** @sa createDiagonalOpFromPauliHamilFile + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "createDiagonalOpFromPauliHamilFile", "[data_structures]" ) { - SECTION( "correctness" ) { + // // files created & populated during the test, and deleted afterward + // char fnPrefix[] = "temp_createDiagonalOpFromPauliHamilFile"; + // char fn[100]; + // int fnMaxLen = 100; + + // // each test uses a unique filename (managed by master node), to avoid file IO locks + // // (it's safe for sub-test to overwrite this, since after each test, all files + // // with prefix fnPrefix are deleted) + // setUniqueFilename(fn, fnMaxLen, fnPrefix); + + // // diagonal op must have at least one amplitude per node + // int minNumQb = calcLog2(getQuESTEnv().numNodes); + // if (minNumQb == 0) + // minNumQb = 1; - SECTION( "general" ) { - - // try several Pauli Hamiltonian sizes - int numQb = GENERATE_COPY( range(minNumQb, 6+minNumQb) ); - int numTerms = GENERATE_COPY( 1, minNumQb, 10*minNumQb ); - - // create a PauliHamil with random elements - PauliHamil hamil = createPauliHamil(numQb, numTerms); - setRandomDiagPauliHamil(hamil); - - // write the Hamiltonian to file (with trailing whitespace, and trailing newline) - if (QUEST_ENV.rank == 0) { - FILE* file = fopen(fn, "w"); - int i=0; - for (int n=0; n 32 ? 32 : maxQb; + // int minQb = GENERATE_COPY( range(2,maxQb) ); + // env.numNodes = (int) pow(2, minQb); + // int numQb = GENERATE_COPY( range(1,minQb) ); + + // line = ".1 "; + // for (int q=0; q 32 ? 32 : maxQb; - int minQb = GENERATE_COPY( range(2,maxQb) ); - env.numRanks = (int) pow(2, minQb); - int numQb = GENERATE_COPY( range(1,minQb) ); - - line = ".1 "; - for (int q=0; q QVector ref = QVector(1< REQUIRE( areEqual(reg, ref) ); - destroyQureg(reg, QUEST_ENV); + destroyQureg(reg); } SECTION( "input validation") { SECTION( "number of qubits" ) { int numQb = GENERATE( -1, 0 ); - REQUIRE_THROWS_WITH( createQureg(numQb, QUEST_ENV), Contains("Invalid number of qubits") ); + REQUIRE_THROWS_WITH( createForcedQureg(numQb), Contains("must contain one or more qubits") ); } SECTION( "number of amplitudes" ) { // use local QuESTEnv to safely modify - QuESTEnv env = QUEST_ENV; + QuESTEnv env = getQuESTEnv(); // too many amplitudes to store in type int maxQb = (int) calcLog2(SIZE_MAX); - REQUIRE_THROWS_WITH( createQureg(maxQb+1, env), Contains("Too many qubits") && Contains("size_t type") ); - - // too few amplitudes to distribute - /* env.numRanks is an int, so maxQb must be capped at 32 for this - * test to avoid an integer overflow when storing 2**minQb in it - */ - maxQb = maxQb > 32 ? 32 : maxQb; - int minQb = GENERATE_COPY( range(2,maxQb) ); - env.numRanks = (int) pow(2, minQb); - int numQb = GENERATE_COPY( range(1,minQb) ); - REQUIRE_THROWS_WITH( createQureg(numQb, env), Contains("Too few qubits") ); + REQUIRE_THROWS_WITH( createForcedQureg(maxQb+1), + Contains("the statevector would contain more amplitudes") && + Contains("than the maximum which can be addressed by the qindex type") ); + + // it is non-trivial to force an invalid distribution + // in v4, since QuESTEnv arg is no longer consulted; we + // will reserve this input validation for the full tests + + // // too few amplitudes to distribute + // /* env.numNodes is an int, so maxQb must be capped at 32 for this + // * test to avoid an integer overflow when storing 2**minQb in it + // */ + // maxQb = maxQb > 32 ? 32 : maxQb; + // int minQb = GENERATE_COPY( range(2,maxQb) ); + // env.numNodes = (int) pow(2, minQb); + // int numQb = GENERATE_COPY( range(1,minQb) ); + // REQUIRE_THROWS_WITH( createForcedQureg(numQb, env), Contains("Too few qubits") ); } SECTION( "available memory" ) { @@ -704,10 +772,12 @@ TEST_CASE( "createSubDiagonalOp", "[data_structures]" ) { SECTION( "number of qubits" ) { int numQb = GENERATE( -1, 0 ); - REQUIRE_THROWS_WITH( createSubDiagonalOp(numQb), Contains("Invalid number of qubits") ); + REQUIRE_THROWS_WITH( createSubDiagonalOp(numQb), Contains("must target one or more qubits") ); numQb = 100; - REQUIRE_THROWS_WITH( createSubDiagonalOp(numQb), Contains("Too many qubits") ); + REQUIRE_THROWS_WITH( createSubDiagonalOp(numQb), + Contains("the matrix would contain more elements") && + Contains("than the maximum which can be addressed by the qindex type") ); } } } @@ -733,12 +803,12 @@ TEST_CASE( "destroyComplexMatrixN", "[data_structures]" ) { * automatically sets un-initialised ComplexMatrixN fields to * the NULL pointer. */ - ComplexMatrixN m; - m.real = NULL; + ComplexMatrixN m; + m.cpuElems = NULL; - /* the error message is also somewhat unrelated, but oh well - */ - REQUIRE_THROWS_WITH( destroyComplexMatrixN(m), Contains("The ComplexMatrixN was not successfully created") ); + /* the error message is also somewhat unrelated, but oh well + */ + REQUIRE_THROWS_WITH( destroyComplexMatrixN(m), Contains("Invalid CompMatr") ); } } } @@ -815,281 +885,304 @@ TEST_CASE( "destroySubDiagonalOp", "[data_structures]" ) { -/** @sa initComplexMatrixN - * @ingroup unittest - * @author Tyson Jones - */ -TEST_CASE( "initComplexMatrixN", "[data_structures]" ) { - - /* use of this function is illegal in C++ */ - SUCCEED( ); -} - - +// initComplexMatrixN removed because ComplexMatrixN is deprecated, +// and replacement CompMatr has analogous function setCompMatr with +// an incompatible signature -/** @sa initDiagonalOp - * @ingroup unittest - * @author Tyson Jones - */ -TEST_CASE( "initDiagonalOp", "[data_structures]" ) { - - // must be at least one amplitude per node - int minNumQb = calcLog2(QUEST_ENV.numRanks); - if (minNumQb == 0) - minNumQb = 1; - - SECTION( "correctness" ) { + // /** @sa initComplexMatrixN + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "initComplexMatrixN", "[data_structures]" ) { - // try 10 valid number of qubits - int numQb = GENERATE_COPY( range(minNumQb, minNumQb+10) ); - DiagonalOp op = createDiagonalOp(numQb, QUEST_ENV); - - long long int len = (1LL << numQb); - VLA(qreal, reals, len); - VLA(qreal, imags, len); - long long int n; - for (n=0; n elems(len); + + // VLA(qreal, reals, len); + // VLA(qreal, imags, len); + // long long int n; + // for (n=0; n +#include + +using std::vector; /** Prepares a density matrix in the debug state, and the reference QMatrix */ #define PREPARE_TEST(qureg, ref) \ - Qureg qureg = createDensityQureg(NUM_QUBITS, QUEST_ENV); \ + Qureg qureg = createForcedDensityQureg(NUM_QUBITS); \ initDebugState(qureg); \ QMatrix ref = toQMatrix(qureg); \ - assertQuregAndRefInDebugState(qureg, ref); + assertQuregAndRefInDebugState(qureg, ref); \ + setValidationEpsilon(REAL_EPS); /* allows concise use of Contains in catch's REQUIRE_THROWS_WITH */ using Catch::Matchers::Contains; @@ -27,7 +38,7 @@ TEST_CASE( "mixDamping", "[decoherence]" ) { SECTION( "correctness" ) { - int target = GENERATE( range(0,NUM_QUBITS) ); + int target = GENERATE( range(0, NUM_QUBITS) ); qreal prob = getRandomReal(0, 1); mixDamping(qureg, target, prob); @@ -52,17 +63,18 @@ TEST_CASE( "mixDamping", "[decoherence]" ) { } SECTION( "probability" ) { - REQUIRE_THROWS_WITH( mixDamping(qureg, 0, -.1), Contains("Probabilities") ); - REQUIRE_THROWS_WITH( mixDamping(qureg, 0, 1.1), Contains("Probabilities") ); + REQUIRE_THROWS_WITH( mixDamping(qureg, 0, -.1), Contains("probability is invalid") ); + REQUIRE_THROWS_WITH( mixDamping(qureg, 0, 1.1), Contains("probability is invalid") ); } SECTION( "density-matrix" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDamping(vec, 0, 0), Contains("density matrices") ); - destroyQureg(vec, QUEST_ENV); + Qureg vec = createQureg(NUM_QUBITS); + REQUIRE_NOTHROW( mixDamping(vec, 0, 0) ); // zero-prob allowed in v4 + REQUIRE_THROWS_WITH( mixDamping(vec, 0, .1), Contains("Expected a density matrix Qureg but received a statevector") ); + destroyQureg(vec, getQuESTEnv()); } } - destroyQureg(qureg, QUEST_ENV); + destroyQureg(qureg, getQuESTEnv()); } @@ -73,8 +85,8 @@ TEST_CASE( "mixDamping", "[decoherence]" ) { */ TEST_CASE( "mixDensityMatrix", "[decoherence]" ) { - Qureg qureg1 = createDensityQureg(NUM_QUBITS, QUEST_ENV); - Qureg qureg2 = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg qureg1 = createForcedDensityQureg(NUM_QUBITS); + Qureg qureg2 = createForcedDensityQureg(NUM_QUBITS); initDebugState(qureg1); initDebugState(qureg2); QMatrix ref1 = toQMatrix(qureg1); @@ -95,35 +107,38 @@ TEST_CASE( "mixDensityMatrix", "[decoherence]" ) { } SECTION( "input validation" ) { - SECTION( "probabilities") { + SECTION( "probabilities" ) { qreal prob = GENERATE( -0.1, 1.1 ); - REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, prob, qureg2), Contains("Probabilities") ); + REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, prob, qureg2), Contains("probability is invalid") ); } SECTION( "density matrices" ) { // one is statevec - Qureg state1 = createQureg(qureg1.numQubitsRepresented, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, 0, state1), Contains("density matrices") ); - REQUIRE_THROWS_WITH( mixDensityMatrix(state1, 0, qureg1), Contains("density matrices") ); + Qureg state1 = createQureg(qureg1.numQubits, getQuESTEnv()); + REQUIRE_THROWS_WITH( mixDensityMatrix(state1, 0, qureg1), Contains("The first Qureg") && Contains("must be a density matrix") ); + + // in v4, the 2nd arg can be a statevector + + // REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, 0, state1), Contains("Expected a density matrix Qureg but received a statevector") ); // both are statevec - Qureg state2 = createQureg(qureg1.numQubitsRepresented, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDensityMatrix(state1, 0, state2), Contains("density matrices") ); + Qureg state2 = createQureg(qureg1.numQubits, getQuESTEnv()); + REQUIRE_THROWS_WITH( mixDensityMatrix(state1, 0, state2), Contains("The first Qureg") && Contains("must be a density matrix") ); - destroyQureg(state1, QUEST_ENV); - destroyQureg(state2, QUEST_ENV); + destroyQureg(state1, getQuESTEnv()); + destroyQureg(state2, getQuESTEnv()); } SECTION( "matching dimensions" ) { - Qureg qureg3 = createDensityQureg(1 + qureg1.numQubitsRepresented, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, 0, qureg3), Contains("Dimensions") ); - REQUIRE_THROWS_WITH( mixDensityMatrix(qureg3, 0, qureg1), Contains("Dimensions") ); - destroyQureg(qureg3, QUEST_ENV); + Qureg qureg3 = createDensityQureg(1 + qureg1.numQubits, getQuESTEnv()); + REQUIRE_THROWS_WITH( mixDensityMatrix(qureg1, 0, qureg3), Contains("inconsistent number of qubits") ); + REQUIRE_THROWS_WITH( mixDensityMatrix(qureg3, 0, qureg1), Contains("inconsistent number of qubits") ); + destroyQureg(qureg3, getQuESTEnv()); } } - destroyQureg(qureg1, QUEST_ENV); - destroyQureg(qureg2, QUEST_ENV); + destroyQureg(qureg1, getQuESTEnv()); + destroyQureg(qureg2, getQuESTEnv()); } @@ -136,7 +151,7 @@ TEST_CASE( "mixDephasing", "[decoherence]" ) { PREPARE_TEST(qureg, ref); - SECTION( "correctness " ) { + SECTION( "correctness" ) { int target = GENERATE( range(0,NUM_QUBITS) ); qreal prob = getRandomReal(0, 1/2.); @@ -149,7 +164,7 @@ TEST_CASE( "mixDephasing", "[decoherence]" ) { REQUIRE( areEqual(qureg, ref) ); } - SECTION( "validation ") { + SECTION( "validation" ) { SECTION( "qubit index" ) { @@ -159,17 +174,18 @@ TEST_CASE( "mixDephasing", "[decoherence]" ) { } SECTION( "probability" ) { - REQUIRE_THROWS_WITH( mixDephasing(qureg, 0, -.1), Contains("Probabilities") ); - REQUIRE_THROWS_WITH( mixDephasing(qureg, 0, .6), Contains("probability") && Contains("cannot exceed 1/2") ); + REQUIRE_THROWS_WITH( mixDephasing(qureg, 0, -.1), Contains("probability is invalid") ); + REQUIRE_THROWS_WITH( mixDephasing(qureg, 0, .6), Contains("probability") && Contains("1/2") ); } SECTION( "density-matrix" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDephasing(vec, 0, 0), Contains("density matrices") ); - destroyQureg(vec, QUEST_ENV); + Qureg vec = createQureg(NUM_QUBITS); + REQUIRE_NOTHROW( mixDephasing(vec, 0, 0) ); // zero prob ok in v4 + REQUIRE_THROWS_WITH( mixDephasing(vec, 0, 0.1), Contains("Expected a density matrix Qureg but received a statevector") ); + destroyQureg(vec, getQuESTEnv()); } } - destroyQureg(qureg, QUEST_ENV); + destroyQureg(qureg, getQuESTEnv()); } @@ -208,17 +224,18 @@ TEST_CASE( "mixDepolarising", "[decoherence]" ) { } SECTION( "probability" ) { - REQUIRE_THROWS_WITH( mixDepolarising(qureg, 0, -.1), Contains("Probabilities") ); - REQUIRE_THROWS_WITH( mixDepolarising(qureg, 0, .76), Contains("probability") && Contains("cannot exceed 3/4") ); + REQUIRE_THROWS_WITH( mixDepolarising(qureg, 0, -.1), Contains("probability is invalid") ); + REQUIRE_THROWS_WITH( mixDepolarising(qureg, 0, .76), Contains("probability") && Contains("3/4") ); } SECTION( "density-matrix" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - REQUIRE_THROWS_WITH( mixDepolarising(vec, 0, 0), Contains("density matrices") ); - destroyQureg(vec, QUEST_ENV); + Qureg vec = createQureg(NUM_QUBITS); + REQUIRE_NOTHROW( mixDepolarising(vec, 0, 0) ); // zero-prob ok in v4 + REQUIRE_THROWS_WITH( mixDepolarising(vec, 0, 0.1), Contains("Expected a density matrix Qureg but received a statevector") ); + destroyQureg(vec, getQuESTEnv()); } } - destroyQureg(qureg, QUEST_ENV); + destroyQureg(qureg, getQuESTEnv()); } @@ -233,7 +250,7 @@ TEST_CASE( "mixMultiQubitKrausMap", "[decoherence]" ) { // figure out max-num (inclusive) targs allowed by hardware backend // (each node must contain as 2^(2*numTargs) amps) - int maxNumTargs = calcLog2(qureg.numAmpsPerChunk) / 2; + int maxNumTargs = calcLog2(qureg.numAmpsPerNode) / 2; SECTION( "correctness" ) { @@ -248,8 +265,8 @@ TEST_CASE( "mixMultiQubitKrausMap", "[decoherence]" ) { // int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) ); // alas, this is too slow for CI, so we instead try a fixed number of random sets: GENERATE( range(0, 10) ); - VLA(int, targs, numTargs); - setRandomTargets(targs, numTargs, NUM_QUBITS); + vector targs(numTargs); + setRandomTargets(targs, NUM_QUBITS); // try the min and max number of operators, and 2 random numbers // (there are way too many to try all!) @@ -257,22 +274,22 @@ TEST_CASE( "mixMultiQubitKrausMap", "[decoherence]" ) { int numOps = GENERATE_COPY( 1, maxNumOps, take(2,random(1,maxNumOps)) ); // use a new random map - std::vector matrs = getRandomKrausMap(numTargs, numOps); + vector matrs = getRandomKrausMap(numTargs, numOps); // create map in QuEST datatypes - VLA(ComplexMatrixN, ops, numOps); + vector ops(numOps); for (int i=0; i K_i ref K_i^dagger - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i targs(numTargs); for (int i=0; i ops(numOps); + // for (int i=0; i targs(numTargs); + // for (int i=0; i ops(numOps); + // for (int i=0; i targs(numTargs); + // for (int i=0; i matrs = getRandomKrausMap(numTargs, numOps); - VLA(ComplexMatrixN, ops, numOps); + vector matrs = getRandomKrausMap(numTargs, numOps); + vector ops(numOps); for (int i=0; i targs(numTargs); for (int i=0; i matrs = getRandomKrausMap(1, numOps); + vector matrs = getRandomKrausMap(1, numOps); - VLA(ComplexMatrix2, ops, numOps); + vector ops(numOps); for (int i=0; i K_i ref K_i^dagger - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i matrs = getRandomKrausMap(1, numOps); - VLA(ComplexMatrix2, ops, numOps); + vector matrs = getRandomKrausMap(1, numOps); + vector ops(numOps); for (int i=0; i matrs; + vector matrs; for (int i=0; i ops(numOps); for (int i=0; i K_i ref K_i^dagger - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i targs(numTargs); + setRandomTargets(targs, NUM_QUBITS); // try the min and max number of operators, and 2 random numbers // (there are way too many to try all!) @@ -690,24 +744,24 @@ TEST_CASE( "mixNonTPMultiQubitKrausMap", "[decoherence]" ) { int numOps = GENERATE_COPY( 1, maxNumOps, take(2,random(1,maxNumOps)) ); // use a new random map, of unconstrained random (2^numTargs x 2^numTargs) matrices - std::vector matrs; + vector matrs; for (int i=0; i ops(numOps); for (int i=0; i K_i ref K_i^dagger - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i targs(numTargs); for (int i=0; i ops(numOps); + // for (int i=0; i targs(numTargs); + // for (int i=0; i ops(numOps); + // for (int i=0; i targs(numTargs); + // for (int i=0; i matrs; + vector matrs; for (int i=0; i ops(numOps); for (int i=0; i K_i ref K_i^dagger int targs[2] = {targ1, targ2}; - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i matrs = getRandomKrausMap(2, numOps); + vector matrs = getRandomKrausMap(2, numOps); - VLA(ComplexMatrix4, ops, numOps); + vector ops(numOps); for (int i=0; i K_i ref K_i^dagger int targs[2] = {targ1, targ2}; - VLA(QMatrix, matrRefs, numOps); + vector matrRefs(numOps); for (int i=0; i matrs = getRandomKrausMap(2, numOps); - VLA(ComplexMatrix4, ops, numOps); + vector matrs = getRandomKrausMap(2, numOps); + vector ops(numOps); for (int i=0; i -/** The global QuESTEnv instance, to be created and destroyed once in this - * main(), so that the MPI environment is correctly created once when running - * distributed unit tests - */ -QuESTEnv QUEST_ENV; /** Redefinition of QuEST_validation's invalidQuESTInputError function, called when a - * user passes an incorrect parameter (e.g. an negative qubit index). This is + * user passes an incorrect parameter (e.g. a negative qubit index). This is * redefined here to, in lieu of printing and exiting, throw a C++ exception * which can be caught (and hence unit tested for) by Catch2 */ extern "C" void invalidQuESTInputError(const char* errMsg, const char* errFunc) { - throw errMsg; + + throw std::runtime_error(errMsg); } - + + /** Explicit declaration of main to create (destroy) the QuESTEnv before (after) * invoking the Catch unit tests */ -int main( int argc, char* argv[] ) { - QUEST_ENV = createQuESTEnv(); +int main(int argc, char* argv[]) { + + initQuESTEnv(); + setRandomTestStateSeeds(); + int result = Catch::Session().run( argc, argv ); - destroyQuESTEnv(QUEST_ENV); + + finalizeQuESTEnv(); return result; -} \ No newline at end of file +} diff --git a/tests/test_operators.cpp b/tests/test_operators.cpp index d4c32b424..bb636d4ef 100644 --- a/tests/test_operators.cpp +++ b/tests/test_operators.cpp @@ -1,15 +1,22 @@ #include "catch.hpp" -#include "QuEST.h" -#include "utilities.hpp" + +// must define preprocessors to enable quest's +// deprecated v3 API, and disable the numerous +// warnings issued by its compilation +#define INCLUDE_DEPRECATED_FUNCTIONS 1 +#define DISABLE_DEPRECATION_WARNINGS 1 +#include "quest.h" + +#include "test_utilities.hpp" /** Prepares the needed data structures for unit testing some operators. * This creates a statevector and density matrix of the size NUM_QUBITS, * and corresponding QVector and QMatrix instances for analytic comparison. */ #define PREPARE_TEST(quregVec, quregMatr, refVec, refMatr) \ - Qureg quregVec = createQureg(NUM_QUBITS, QUEST_ENV); \ - Qureg quregMatr = createDensityQureg(NUM_QUBITS, QUEST_ENV); \ + Qureg quregVec = createForcedQureg(NUM_QUBITS); \ + Qureg quregMatr = createForcedDensityQureg(NUM_QUBITS); \ initDebugState(quregVec); \ initDebugState(quregMatr); \ QVector refVec = toQVector(quregVec); \ @@ -19,16 +26,21 @@ /** Destroys the data structures made by PREPARE_TEST */ #define CLEANUP_TEST(quregVec, quregMatr) \ - destroyQureg(quregVec, QUEST_ENV); \ - destroyQureg(quregMatr, QUEST_ENV); + destroyQureg(quregVec); \ + destroyQureg(quregMatr); /* allows concise use of Contains in catch's REQUIRE_THROWS_WITH */ using Catch::Matchers::Contains; +// TODO: +// OVERRIDE AUTO-DEPLOYER FOR TESTS?! + + + // largest valid phaseFunc enum, used by input validation tests -enum phaseFunc MAX_INDEX_PHASE_FUNC = SCALED_INVERSE_SHIFTED_WEIGHTED_DISTANCE; +// enum phaseFunc MAX_INDEX_PHASE_FUNC = SCALED_INVERSE_SHIFTED_WEIGHTED_DISTANCE; @@ -39,6 +51,8 @@ enum phaseFunc MAX_INDEX_PHASE_FUNC = SCALED_INVERSE_SHIFTED_WEIGHTED_DISTANCE; TEST_CASE( "applyDiagonalOp", "[operators]" ) { PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); + + QuESTEnv env = getQuESTEnv(); SECTION( "correctness" ) { @@ -46,11 +60,9 @@ TEST_CASE( "applyDiagonalOp", "[operators]" ) { GENERATE( range(0,10) ); // make a totally random (non-Hermitian) diagonal oeprator - DiagonalOp op = createDiagonalOp(NUM_QUBITS, QUEST_ENV); - for (long long int i=0; i= 4 ); + REQUIRE( quregVec.numAmpsPerNode >= 4 ); // every test will use a unique random matrix QMatrix op = getRandomQMatrix(4); // 4-by-4 - ComplexMatrix4 matr = toComplexMatrix4(op); + ComplexMatrix4 matr = toComplexMatrix4(op); SECTION( "correctness" ) { @@ -515,7 +524,7 @@ TEST_CASE( "applyMatrix4", "[operators]" ) { SECTION( "matrix fits in node" ) { // pretend we have a very limited distributed memory - quregVec.numAmpsPerChunk = 1; + quregVec.numAmpsPerNode = 1; REQUIRE_THROWS_WITH( applyMatrix4(quregVec, 0, 1, matr), Contains("targets too many qubits")); } } @@ -533,7 +542,7 @@ TEST_CASE( "applyMatrixN", "[operators]" ) { PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); // figure out max-num (inclusive) targs allowed by hardware backend - int maxNumTargs = calcLog2(quregVec.numAmpsPerChunk); + int maxNumTargs = calcLog2(quregVec.numAmpsPerNode); SECTION( "correctness" ) { @@ -608,8 +617,7 @@ TEST_CASE( "applyMatrixN", "[operators]" ) { * currently. */ ComplexMatrixN matr; - matr.real = NULL; - matr.imag = NULL; + matr.cpuElems = NULL; REQUIRE_THROWS_WITH( applyMatrixN(quregVec, targs, numTargs, matr), Contains("created") ); } SECTION( "matrix dimensions" ) { @@ -623,7 +631,7 @@ TEST_CASE( "applyMatrixN", "[operators]" ) { SECTION( "matrix fits in node" ) { // pretend we have a very limited distributed memory (judged by matr size) - quregVec.numAmpsPerChunk = 1; + quregVec.numAmpsPerNode = 1; int qb[] = {1,2}; ComplexMatrixN matr = createComplexMatrixN(2); // prevents seg-fault if validation doesn't trigger REQUIRE_THROWS_WITH( applyMatrixN(quregVec, qb, 2, matr), Contains("targets too many qubits")); @@ -644,7 +652,7 @@ TEST_CASE( "applyMultiControlledGateMatrixN", "[operators]" ) { PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); // figure out max-num targs (inclusive) allowed by hardware backend - int maxNumTargs = calcLog2(quregVec.numAmpsPerChunk); + int maxNumTargs = calcLog2(quregVec.numAmpsPerNode); if (maxNumTargs >= NUM_QUBITS) maxNumTargs = NUM_QUBITS - 1; // leave room for min-number of control qubits @@ -763,8 +771,7 @@ TEST_CASE( "applyMultiControlledGateMatrixN", "[operators]" ) { * currently. */ ComplexMatrixN matr; - matr.real = NULL; - matr.imag = NULL; + matr.cpuElems = NULL; REQUIRE_THROWS_WITH( applyMultiControlledGateMatrixN(quregVec, ctrls, 1, targs, 3, matr), Contains("created") ); } SECTION( "matrix dimensions" ) { @@ -780,7 +787,7 @@ TEST_CASE( "applyMultiControlledGateMatrixN", "[operators]" ) { SECTION( "matrix fits in node" ) { // pretend we have a very limited distributed memory (judged by matr size) - quregVec.numAmpsPerChunk = 1; + quregVec.numAmpsPerNode = 1; int ctrls[1] = {0}; int targs[2] = {1,2}; ComplexMatrixN matr = createComplexMatrixN(2); @@ -795,2906 +802,2948 @@ TEST_CASE( "applyMultiControlledGateMatrixN", "[operators]" ) { -/** @sa applyMultiControlledMatrixN - * @ingroup unittest - * @author Tyson Jones - */ -TEST_CASE( "applyMultiControlledMatrixN", "[operators]" ) { +// applyMultiControlledMatrixN removed because replacement function +// multiplyCompMatr() does not accept control-qubits (because WHO +// would ever want to left-apply a controlled matrix onto a density +// matrix??) + + // /** @sa applyMultiControlledMatrixN + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "applyMultiControlledMatrixN", "[operators]" ) { + + // PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); + + // // figure out max-num targs (inclusive) allowed by hardware backend + // int maxNumTargs = calcLog2(quregVec.numAmpsPerNode); + // if (maxNumTargs >= NUM_QUBITS) + // maxNumTargs = NUM_QUBITS - 1; // leave room for min-number of control qubits + + // SECTION( "correctness" ) { + + // // try all possible numbers of targets and controls + // int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); + // int maxNumCtrls = NUM_QUBITS - numTargs; + // int numCtrls = GENERATE_COPY( range(1,maxNumCtrls+1) ); + + // // generate all possible valid qubit arrangements + // int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) ); + // int* ctrls = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numCtrls, targs, numTargs) ); + + // // for each qubit arrangement, use a new random unitary + // QMatrix op = getRandomQMatrix(1 << numTargs); + // ComplexMatrixN matr = createComplexMatrixN(numTargs); + // toComplexMatrixN(op, matr); + + // SECTION( "state-vector" ) { + + // applyMultiControlledMatrixN(quregVec, ctrls, numCtrls, targs, numTargs, matr); + // applyReferenceMatrix(refVec, ctrls, numCtrls, targs, numTargs, op); + // REQUIRE( areEqual(quregVec, refVec) ); + // } + // SECTION( "density-matrix" ) { + + // applyMultiControlledMatrixN(quregMatr, ctrls, numCtrls, targs, numTargs, matr); + // applyReferenceMatrix(refMatr, ctrls, numCtrls, targs, numTargs, op); + // REQUIRE( areEqual(quregMatr, refMatr, 100*REAL_EPS) ); + // } + // destroyComplexMatrixN(matr); + // } + // SECTION( "input validation" ) { + + // SECTION( "number of targets" ) { + + // // there cannot be more targets than qubits in register + // // (numTargs=NUM_QUBITS is caught elsewhere, because that implies ctrls are invalid) + // int numTargs = GENERATE( -1, 0, NUM_QUBITS+1 ); + // int targs[NUM_QUBITS+1]; // prevents seg-fault if validation doesn't trigger + // int ctrls[] = {0}; + // ComplexMatrixN matr = createComplexMatrixN(NUM_QUBITS+1); // prevent seg-fault + // toComplexMatrixN(getRandomQMatrix( 1 << (NUM_QUBITS+1)), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 1, targs, numTargs, matr), Contains("Invalid number of target")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "repetition in targets" ) { + + // int ctrls[] = {0}; + // int numTargs = 3; + // int targs[] = {1,2,2}; + // ComplexMatrixN matr = createComplexMatrixN(numTargs); // prevents seg-fault if validation doesn't trigger + // toComplexMatrixN(getRandomQMatrix(1 << numTargs), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 1, targs, numTargs, matr), Contains("target") && Contains("unique")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "number of controls" ) { + + // int numCtrls = GENERATE( -1, 0, NUM_QUBITS, NUM_QUBITS+1 ); + // int ctrls[NUM_QUBITS+1]; // avoids seg-fault if validation not triggered + // int targs[1] = {0}; + // ComplexMatrixN matr = createComplexMatrixN(1); + // toComplexMatrixN(getRandomQMatrix(1 << 1), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, numCtrls, targs, 1, matr), Contains("Invalid number of control")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "repetition in controls" ) { + + // int ctrls[] = {0,1,1}; + // int targs[] = {3}; + // ComplexMatrixN matr = createComplexMatrixN(1); + // toComplexMatrixN(getRandomQMatrix(1 << 1), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 3, targs, 1, matr), Contains("control") && Contains("unique")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "control and target collision" ) { + + // int ctrls[] = {0,1,2}; + // int targs[] = {3,1,4}; + // ComplexMatrixN matr = createComplexMatrixN(3); + // toComplexMatrixN(getRandomQMatrix(1 << 3), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 3, targs, 3, matr), Contains("Control") && Contains("target") && Contains("disjoint")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "qubit indices" ) { + + // // valid inds + // int numQb = 2; + // int qb1[2] = {0,1}; + // int qb2[2] = {2,3}; + // ComplexMatrixN matr = createComplexMatrixN(numQb); + // toComplexMatrixN(getRandomQMatrix(1 << numQb), matr); + + // // make qb1 invalid + // int inv = GENERATE( -1, NUM_QUBITS ); + // qb1[GENERATE_COPY(range(0,numQb))] = inv; + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, qb1, numQb, qb2, numQb, matr), Contains("Invalid control") ); + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, qb2, numQb, qb1, numQb, matr), Contains("Invalid target") ); + // destroyComplexMatrixN(matr); + // } + // SECTION( "matrix creation" ) { + + // int ctrls[1] = {0}; + // int targs[3] = {1,2,3}; + + // /* compilers don't auto-initialise to NULL; the below circumstance + // * only really occurs when 'malloc' returns NULL in createComplexMatrixN, + // * which actually triggers its own validation. Hence this test is useless + // * currently. + // */ + // ComplexMatrixN matr; + // matr.cpuElems = NULL; + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 1, targs, 3, matr), Contains("created") ); + // } + // SECTION( "matrix dimensions" ) { + + // int ctrls[1] = {0}; + // int targs[2] = {1,2}; + // ComplexMatrixN matr = createComplexMatrixN(3); // intentionally wrong size + // toComplexMatrixN(getRandomQMatrix(1 << 3), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 1, targs, 2, matr), Contains("matrix size")); + // destroyComplexMatrixN(matr); + // } + // SECTION( "matrix fits in node" ) { + + // // pretend we have a very limited distributed memory (judged by matr size) + // quregVec.numAmpsPerNode = 1; + // int ctrls[1] = {0}; + // int targs[2] = {1,2}; + // ComplexMatrixN matr = createComplexMatrixN(2); + // toComplexMatrixN(getRandomQMatrix(1 << 2), matr); + + // REQUIRE_THROWS_WITH( applyMultiControlledMatrixN(quregVec, ctrls, 1, targs, 2, matr), Contains("targets too many qubits")); + // destroyComplexMatrixN(matr); + // } + // } + // CLEANUP_TEST( quregVec, quregMatr ); + // } + + + +// applyMultiVarPhaseFunc removed because phase functions are +// completely deprecated, in favour of functions like +// setDiagMatrFromMultiVarFunc and setFullStateDiagMatrFromMultiDimLists + + // /** @sa applyMultiVarPhaseFunc + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "applyMultiVarPhaseFunc", "[operators]" ) { + + // PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); + + // SECTION( "correctness" ) { + + // // try every kind of binary encodings + // enum bitEncoding encoding = GENERATE( UNSIGNED,TWOS_COMPLEMENT ); + + // // try every possible number of registers + // // (between #qubits containing 1, and 1 containing #qubits) + // int numRegs; + // int maxNumRegs = 0; + // if (encoding == UNSIGNED) + // maxNumRegs = NUM_QUBITS; + // if (encoding == TWOS_COMPLEMENT) + // maxNumRegs = NUM_QUBITS/2; // floors + // numRegs = GENERATE_COPY( range(1, maxNumRegs+1) ); + + // // try every possible total number of involed qubits + // int totalNumQubits; + // int minTotalQubits = 0; + // if (encoding == UNSIGNED) + // // each register must contain at least 1 qubit + // minTotalQubits = numRegs; + // if (encoding == TWOS_COMPLEMENT) + // // each register must contain at least 2 qubits + // minTotalQubits = 2*numRegs; + // totalNumQubits = GENERATE_COPY( range(minTotalQubits,NUM_QUBITS+1) ); + + // // try every qubits subset and ordering + // int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) ); + + // // assign each sub-reg its minimum length + // int unallocQubits = totalNumQubits; + // VLA(int, numQubitsPerReg, numRegs); + // for (int i=0; i 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + + // // each register gets a random number of terms in the full phase function + // VLA(int, numTermsPerReg, numRegs); + // int numTermsTotal = 0; + // for (int r=0; r|x1> = exp(i f2[x2])|x2> (x) exp(i f1[x1])|x1> + // * and so compute a separate diagonal matrix for each sub-register with + // * phases determined only by its own variable, and Kronecker multiply + // * them together. The target qubits of the Kronecker product is then + // * just the full register, stored in *regs. + // */ + // QMatrix allRegMatr{{1}}; + // int startInd = 0; + + // for (int r=0; r 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + + // // each register gets a random number of terms in the full phase function + // VLA(int, numTermsPerReg, numRegs); + // int numTermsTotal = 0; + // for (int r=0; r|x1> = exp(i f2[x2])|x2> (x) exp(i f1[x1])|x1> + // * and so compute a separate diagonal matrix for each sub-register with + // * phases determined only by its own variable, and Kronecker multiply + // * them together. The target qubits of the Kronecker product is then + // * just the full register, stored in *regs. We do this, then iterate + // * the list of overrides directly to overwrite the ultimate diagonal + // * entries + // */ + // QMatrix allRegMatr{{1}}; + // int startInd = 0; + // for (int r=0; r 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + // // for reference, determine the values corresponding to each register for all basis states + // std::vector> regVals(1<(numRegs)); + // for (long long int i=0; i<(1<> numQubitsPerReg[r]; + + // if (encoding == TWOS_COMPLEMENT) + // regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); + // } + // } + + // /* the reference diagonal matrix which assumes the qubits are + // * contiguous and strictly increasing between the registers, and hence + // * only depends on the number of qubits in each register. + // */ + // QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); + + // SECTION( "NORM" ) { + + // for (size_t i=0; i 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + + // // choose a random number of overrides (even overriding every amplitude) + // int numOverrides = getRandomInt(0, (1<> regVals(1<(numRegs)); + // for (long long int i=0; i<(1<> numQubitsPerReg[r]; + + // if (encoding == TWOS_COMPLEMENT) + // regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); + // } + // } + + // /* a reference diagonal matrix which assumes the qubits are + // * contiguous and strictly increasing between the registers, and hence + // * only depends on the number of qubits in each register. + // */ + // QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); + + // SECTION( "NORM" ) { + + // for (size_t i=0; i= NUM_QUBITS) - maxNumTargs = NUM_QUBITS - 1; // leave room for min-number of control qubits - - SECTION( "correctness" ) { - - // try all possible numbers of targets and controls - int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); - int maxNumCtrls = NUM_QUBITS - numTargs; - int numCtrls = GENERATE_COPY( range(1,maxNumCtrls+1) ); - - // generate all possible valid qubit arrangements - int* targs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numTargs) ); - int* ctrls = GENERATE_COPY( sublists(range(0,NUM_QUBITS), numCtrls, targs, numTargs) ); - - // for each qubit arrangement, use a new random unitary - QMatrix op = getRandomQMatrix(1 << numTargs); - ComplexMatrixN matr = createComplexMatrixN(numTargs); - toComplexMatrixN(op, matr); + // int numOverrides = -1; + // REQUIRE_THROWS_WITH( applyNamedPhaseFuncOverrides(quregVec, regs, numQubitsPerReg, numRegs, UNSIGNED, NORM, NULL, NULL, numOverrides), Contains("Invalid number of phase function overrides specified") ); + // } + // SECTION( "override indices" ) { + + // // numQubitsPerReg = {2, 3} + // int numOverrides = 3; + // long long int overrideInds[] = {0,0, 0,0, 0,0}; // repetition not checked + // qreal overridePhases[] = {.1, .1, .1}; + + // // first element of overrideInds coordinate is a 2 qubit register + // enum bitEncoding enc = UNSIGNED; + // int badInd = GENERATE(0, 2, 4); + // overrideInds[badInd] = GENERATE( -1, (1<<2) ); + // REQUIRE_THROWS_WITH( applyNamedPhaseFuncOverrides(quregVec, regs, numQubitsPerReg, numRegs, enc, NORM, overrideInds, overridePhases, numOverrides), Contains("Invalid phase function override index, in the UNSIGNED encoding") ); + // overrideInds[badInd] = 0; + + // // second element of overrideInds coordinate is a 3 qubit register + // badInd += 1; + // overrideInds[badInd] = GENERATE( -1, (1<<3) ); + // REQUIRE_THROWS_WITH( applyNamedPhaseFuncOverrides(quregVec, regs, numQubitsPerReg, numRegs, enc, NORM, overrideInds, overridePhases, numOverrides), Contains("Invalid phase function override index, in the UNSIGNED encoding") ); + // overrideInds[badInd] = 0; + // badInd -= 1; + + // enc = TWOS_COMPLEMENT; + // int minInd = -(1<<(numQubitsPerReg[0]-1)); + // int maxInd = (1<<(numQubitsPerReg[0]-1)) - 1; + // overrideInds[badInd] = GENERATE_COPY( minInd-1, maxInd+1 ); + // REQUIRE_THROWS_WITH( applyNamedPhaseFuncOverrides(quregVec, regs, numQubitsPerReg, numRegs, enc, NORM, overrideInds, overridePhases, numOverrides), Contains("Invalid phase function override index, in the TWOS_COMPLEMENT encoding") ); + // overrideInds[badInd] = 0; + + // badInd++; + // minInd = -(1<<(numQubitsPerReg[1]-1)); + // maxInd = (1<<(numQubitsPerReg[1]-1)) -1; + // overrideInds[badInd] = GENERATE_COPY( minInd-1, maxInd+1 ); + // REQUIRE_THROWS_WITH( applyNamedPhaseFuncOverrides(quregVec, regs, numQubitsPerReg, numRegs, enc, NORM, overrideInds, overridePhases, numOverrides), Contains("Invalid phase function override index, in the TWOS_COMPLEMENT encoding") ); + // } + // } + // CLEANUP_TEST( quregVec, quregMatr ); + // } + + + +// applyParamNamedPhaseFunc removed because phase functions are +// completely deprecated, in favour of functions like +// setDiagMatrFromMultiVarFunc and setFullStateDiagMatrFromMultiDimLists + + // /** @sa applyParamNamedPhaseFunc + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "applyParamNamedPhaseFunc", "[operators]" ) { + + // PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); + + // SECTION( "correctness" ) { + + // // try every kind of binary encoding + // enum bitEncoding encoding = GENERATE( UNSIGNED,TWOS_COMPLEMENT ); + + // // try every possible number of registers + // // (between #qubits containing 1, and 1 containing #qubits) + // int numRegs; + // int maxNumRegs = 0; + // if (encoding == UNSIGNED) + // maxNumRegs = NUM_QUBITS; + // if (encoding == TWOS_COMPLEMENT) + // maxNumRegs = NUM_QUBITS/2; // floors + // numRegs = GENERATE_COPY( range(1, maxNumRegs+1) ); + + // // try every possible total number of involved qubits + // int totalNumQubits; + // int minTotalQubits = 0; + // if (encoding == UNSIGNED) + // // each register must contain at least 1 qubit + // minTotalQubits = numRegs; + // if (encoding == TWOS_COMPLEMENT) + // // each register must contain at least 2 qubits + // minTotalQubits = 2*numRegs; + // totalNumQubits = GENERATE_COPY( range(minTotalQubits,NUM_QUBITS+1) ); + + // // Previously, we would try every qubits subset and ordering, via: + // // int* regs = GENERATE_COPY( sublists(range(0,NUM_QUBITS), totalNumQubits) ); + // // Alas, this is too many tests and times out Ubuntu unit testing. So instead, we + // // randomly choose some qubits, 10 times. + // GENERATE( range(0, 10) ); + // VLA(int, regs, totalNumQubits); + // setRandomTargets(regs, totalNumQubits, NUM_QUBITS); + + // // assign each sub-reg its minimum length + // int unallocQubits = totalNumQubits; + // VLA(int, numQubitsPerReg, numRegs); + // for (int i=0; i 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + // /* produce a reference diagonal matrix which assumes the qubits are + // * contiguous and strictly increasing between the registers, and hence + // * only depends on the number of qubits in each register. + // */ + // QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); + + // // determine the values corresponding to each register for all basis states + // std::vector> regVals(1<(numRegs)); + // for (long long int i=0; i<(1<> numQubitsPerReg[r]; + + // if (encoding == TWOS_COMPLEMENT) + // regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); + // } + // } + + // SECTION( "INVERSE_NORM" ) { + + // enum phaseFunc func = INVERSE_NORM; + // qreal divPhase = getRandomReal(-4, 4); + + // for (size_t i=0; i 0) { + // numQubitsPerReg[getRandomInt(0,numRegs)] += 1; + // unallocQubits--; + // } + + // // choose a random number of overrides (even overriding every amplitude) + // int numOverrides = getRandomInt(0, (1<> regVals(1<(numRegs)); + // for (long long int i=0; i<(1<> numQubitsPerReg[r]; + + // if (encoding == TWOS_COMPLEMENT) + // regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); + // } + // } + + // /* produce a reference diagonal matrix which assumes the qubits are + // * contiguous and strictly increasing between the registers, and hence + // * only depends on the number of qubits in each register. + // */ + // QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); + + + // SECTION( "INVERSE_NORM" ) { + + // enum phaseFunc func = INVERSE_NORM; + // qreal divPhase = getRandomReal(-4, 4); + // qreal params[] = {divPhase}; + // int numParams = 1; + + // for (size_t i=0; i 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - - // each register gets a random number of terms in the full phase function - VLA(int, numTermsPerReg, numRegs); - int numTermsTotal = 0; - for (int r=0; r|x1> = exp(i f2[x2])|x2> (x) exp(i f1[x1])|x1> - * and so compute a separate diagonal matrix for each sub-register with - * phases determined only by its own variable, and Kronecker multiply - * them together. The target qubits of the Kronecker product is then - * just the full register, stored in *regs. - */ - QMatrix allRegMatr{{1}}; - int startInd = 0; - - for (int r=0; r 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - - // each register gets a random number of terms in the full phase function - VLA(int, numTermsPerReg, numRegs); - int numTermsTotal = 0; - for (int r=0; r|x1> = exp(i f2[x2])|x2> (x) exp(i f1[x1])|x1> - * and so compute a separate diagonal matrix for each sub-register with - * phases determined only by its own variable, and Kronecker multiply - * them together. The target qubits of the Kronecker product is then - * just the full register, stored in *regs. We do this, then iterate - * the list of overrides directly to overwrite the ultimate diagonal - * entries - */ - QMatrix allRegMatr{{1}}; - int startInd = 0; - for (int r=0; r 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - // for reference, determine the values corresponding to each register for all basis states - std::vector> regVals(1<(numRegs)); - for (long long int i=0; i<(1<> numQubitsPerReg[r]; - - if (encoding == TWOS_COMPLEMENT) - regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); - } - } - - /* the reference diagonal matrix which assumes the qubits are - * contiguous and strictly increasing between the registers, and hence - * only depends on the number of qubits in each register. - */ - QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); - - SECTION( "NORM" ) { - - for (size_t i=0; i 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - - // choose a random number of overrides (even overriding every amplitude) - int numOverrides = getRandomInt(0, (1<> regVals(1<(numRegs)); - for (long long int i=0; i<(1<> numQubitsPerReg[r]; - - if (encoding == TWOS_COMPLEMENT) - regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); - } - } - - /* a reference diagonal matrix which assumes the qubits are - * contiguous and strictly increasing between the registers, and hence - * only depends on the number of qubits in each register. - */ - QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); - - SECTION( "NORM" ) { - - for (size_t i=0; i 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - /* produce a reference diagonal matrix which assumes the qubits are - * contiguous and strictly increasing between the registers, and hence - * only depends on the number of qubits in each register. - */ - QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); - - // determine the values corresponding to each register for all basis states - std::vector> regVals(1<(numRegs)); - for (long long int i=0; i<(1<> numQubitsPerReg[r]; - - if (encoding == TWOS_COMPLEMENT) - regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); - } - } - - SECTION( "INVERSE_NORM" ) { - - enum phaseFunc func = INVERSE_NORM; - qreal divPhase = getRandomReal(-4, 4); - - for (size_t i=0; i 0) { - numQubitsPerReg[getRandomInt(0,numRegs)] += 1; - unallocQubits--; - } - - - // choose a random number of overrides (even overriding every amplitude) - int numOverrides = getRandomInt(0, (1<> regVals(1<(numRegs)); - for (long long int i=0; i<(1<> numQubitsPerReg[r]; - - if (encoding == TWOS_COMPLEMENT) - regVals[i][r] = getTwosComplement(regVals[i][r], numQubitsPerReg[r]); - } - } - - /* produce a reference diagonal matrix which assumes the qubits are - * contiguous and strictly increasing between the registers, and hence - * only depends on the number of qubits in each register. - */ - QMatrix diagMatr = getZeroMatrix(1 << totalNumQubits); - - - SECTION( "INVERSE_NORM" ) { - - enum phaseFunc func = INVERSE_NORM; - qreal divPhase = getRandomReal(-4, 4); - qreal params[] = {divPhase}; - int numParams = 1; - - for (size_t i=0; i= prevFid ); + // prevFid = fid; + // } + // } + // SECTION( "order scaling" ) { + + // // exclude order 1; too few reps for monotonic increase of accuracy + // int reps = GENERATE( 5, 10 ); + + // // accuracy should increase with increasing repetitions + // int orders[] = {1, 2, 4, 6}; + + // qreal prevFid = 0; + // for (int i=0; i<4; i++) { + // initDebugState(vec); + // applyTrotterCircuit(vec, hamil, time, orders[i], reps); + // qreal fid = calcFidelity(vec, vecRef) / fidNorm; - // since unnormalised (initDebugState), max fid is 728359.8336 - qreal fidNorm = 728359.8336; + // REQUIRE( fid >= prevFid ); + // prevFid = fid; + // } + // } + + // destroyPauliHamil(hamil); + // } + // } + // SECTION( "input validation" ) { - SECTION( "absolute" ) { + // SECTION( "repetitions" ) { - // such a high order and reps should yield precise solution - int order = 4; - int reps = 20; - applyTrotterCircuit(vec, hamil, time, 4, 20); - qreal fid = calcFidelity(vec, vecRef) / fidNorm; + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, 1); + // int reps = GENERATE( -1, 0 ); - REQUIRE( fid == Approx(1).epsilon(1E-8) ); - } - SECTION( "repetitions scaling" ) { + // REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, reps), Contains("repetitions must be >=1") ); + + // destroyPauliHamil(hamil); + // } + // SECTION( "order" ) { - // exclude order 1; too few reps for monotonic increase of accuracy - int order = GENERATE( 2, 4, 6 ); + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, 1); + // int order = GENERATE( -1, 0, 3, 5, 7 ); - // accuracy should increase with increasing repetitions - int reps[] = {1, 5, 10}; + // REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, order, 1), Contains("order must be 1, or an even number") ); - qreal prevFid = 0; - for (int i=0; i<3; i++) { - initDebugState(vec); - applyTrotterCircuit(vec, hamil, time, order, reps[i]); - qreal fid = calcFidelity(vec, vecRef) / fidNorm; + // destroyPauliHamil(hamil); + // } + // SECTION( "pauli codes" ) { + + // int numTerms = 3; + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); - REQUIRE( fid >= prevFid ); - prevFid = fid; - } - } - SECTION( "order scaling" ) { + // // make one pauli code wrong + // hamil.pauliCodes[GENERATE_COPY( range(0,numTerms*NUM_QUBITS) )] = (pauliOpType) GENERATE( -1, 4 ); + // REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, 1), Contains("Invalid Pauli code") ); - // exclude order 1; too few reps for monotonic increase of accuracy - int reps = GENERATE( 5, 10 ); + // destroyPauliHamil(hamil); + // } + // SECTION( "matching hamiltonian qubits" ) { - // accuracy should increase with increasing repetitions - int orders[] = {1, 2, 4, 6}; + // PauliHamil hamil = createPauliHamil(NUM_QUBITS + 1, 1); - qreal prevFid = 0; - for (int i=0; i<4; i++) { - initDebugState(vec); - applyTrotterCircuit(vec, hamil, time, orders[i], reps); - qreal fid = calcFidelity(vec, vecRef) / fidNorm; - - REQUIRE( fid >= prevFid ); - prevFid = fid; - } - } - - destroyPauliHamil(hamil); - } - } - SECTION( "input validation" ) { + // REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, 1), Contains("same number of qubits") ); + // REQUIRE_THROWS_WITH( applyTrotterCircuit(mat, hamil, 1, 1, 1), Contains("same number of qubits") ); + + // destroyPauliHamil(hamil); + // } + // } - SECTION( "repetitions" ) { - - PauliHamil hamil = createPauliHamil(NUM_QUBITS, 1); - int reps = GENERATE( -1, 0 ); - - REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, reps), Contains("repetitions must be >=1") ); - - destroyPauliHamil(hamil); - } - SECTION( "order" ) { - - PauliHamil hamil = createPauliHamil(NUM_QUBITS, 1); - int order = GENERATE( -1, 0, 3, 5, 7 ); - - REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, order, 1), Contains("order must be 1, or an even number") ); - - destroyPauliHamil(hamil); - } - SECTION( "pauli codes" ) { - - int numTerms = 3; - PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); - - // make one pauli code wrong - hamil.pauliCodes[GENERATE_COPY( range(0,numTerms*NUM_QUBITS) )] = (pauliOpType) GENERATE( -1, 4 ); - REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, 1), Contains("Invalid Pauli code") ); - - destroyPauliHamil(hamil); - } - SECTION( "matching hamiltonian qubits" ) { - - PauliHamil hamil = createPauliHamil(NUM_QUBITS + 1, 1); - - REQUIRE_THROWS_WITH( applyTrotterCircuit(vec, hamil, 1, 1, 1), Contains("same number of qubits") ); - REQUIRE_THROWS_WITH( applyTrotterCircuit(mat, hamil, 1, 1, 1), Contains("same number of qubits") ); - - destroyPauliHamil(hamil); - } - } - - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); - destroyQureg(vecRef, QUEST_ENV); - destroyQureg(matRef, QUEST_ENV); -} + // destroyQureg(vec); + // destroyQureg(mat); + // destroyQureg(vecRef); + // destroyQureg(matRef); + // } diff --git a/tests/test_state_initialisations.cpp b/tests/test_state_initialisations.cpp index f077e630c..cb488e861 100644 --- a/tests/test_state_initialisations.cpp +++ b/tests/test_state_initialisations.cpp @@ -1,7 +1,11 @@ #include "catch.hpp" -#include "QuEST.h" -#include "utilities.hpp" + +#define INCLUDE_DEPRECATED_FUNCTIONS 1 +#define DISABLE_DEPRECATION_WARNINGS 1 +#include "quest.h" + +#include "test_utilities.hpp" /* allows concise use of Contains in catch's REQUIRE_THROWS_WITH */ using Catch::Matchers::Contains; @@ -14,14 +18,14 @@ using Catch::Matchers::Contains; */ TEST_CASE( "cloneQureg", "[state_initialisations]" ) { - Qureg vec1 = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat1 = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec1 = createForcedQureg(NUM_QUBITS); + Qureg mat1 = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { SECTION( "state-vector" ) { - Qureg vec2 = createQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec2 = createForcedQureg(NUM_QUBITS); // make sure states start differently initDebugState(vec1); @@ -36,11 +40,11 @@ TEST_CASE( "cloneQureg", "[state_initialisations]" ) { // make sure vec1 unaffected REQUIRE( areEqual(vec1, copy1) ); - destroyQureg(vec2, QUEST_ENV); + destroyQureg(vec2); } SECTION( "density-matrix" ) { - Qureg mat2 = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg mat2 = createForcedDensityQureg(NUM_QUBITS); // make sure states start differently initDebugState(mat1); @@ -55,7 +59,7 @@ TEST_CASE( "cloneQureg", "[state_initialisations]" ) { // make sure vec1 unaffected REQUIRE( areEqual(mat1, copy1) ); - destroyQureg(mat2, QUEST_ENV); + destroyQureg(mat2); } } SECTION( "input validation" ) { @@ -67,18 +71,18 @@ TEST_CASE( "cloneQureg", "[state_initialisations]" ) { } SECTION( "qureg dimensions" ) { - Qureg vec3 = createQureg(vec1.numQubitsRepresented + 1, QUEST_ENV); - Qureg mat3 = createDensityQureg(mat1.numQubitsRepresented + 1, QUEST_ENV); + Qureg vec3 = createForcedQureg(vec1.numQubits + 1); + Qureg mat3 = createForcedDensityQureg(mat1.numQubits + 1); REQUIRE_THROWS_WITH( cloneQureg(vec1, vec3), Contains("Dimensions") && Contains("don't match") ); REQUIRE_THROWS_WITH( cloneQureg(mat1, mat3), Contains("Dimensions") && Contains("don't match") ); - destroyQureg(vec3, QUEST_ENV); - destroyQureg(mat3, QUEST_ENV); + destroyQureg(vec3); + destroyQureg(mat3); } } - destroyQureg(vec1, QUEST_ENV); - destroyQureg(mat1, QUEST_ENV); + destroyQureg(vec1); + destroyQureg(mat1); } @@ -89,8 +93,8 @@ TEST_CASE( "cloneQureg", "[state_initialisations]" ) { */ TEST_CASE( "initBlankState", "[state_initialisations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -110,8 +114,8 @@ TEST_CASE( "initBlankState", "[state_initialisations]" ) { // no user validation SUCCEED( ); } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -122,8 +126,8 @@ TEST_CASE( "initBlankState", "[state_initialisations]" ) { */ TEST_CASE( "initClassicalState", "[state_initialisations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -153,8 +157,8 @@ TEST_CASE( "initClassicalState", "[state_initialisations]" ) { REQUIRE_THROWS_WITH( initClassicalState(vec, ind), Contains("Invalid state index") ); } } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -165,8 +169,8 @@ TEST_CASE( "initClassicalState", "[state_initialisations]" ) { */ TEST_CASE( "initPlusState", "[state_initialisations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -198,8 +202,8 @@ TEST_CASE( "initPlusState", "[state_initialisations]" ) { // no user validation SUCCEED( ); } - destroyQureg(vec, QUEST_ENV); - destroyQureg(mat, QUEST_ENV); + destroyQureg(vec); + destroyQureg(mat); } @@ -210,8 +214,8 @@ TEST_CASE( "initPlusState", "[state_initialisations]" ) { */ TEST_CASE( "initPureState", "[state_initialisations]" ) { - Qureg vec1 = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat1 = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec1 = createForcedQureg(NUM_QUBITS); + Qureg mat1 = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -219,7 +223,7 @@ TEST_CASE( "initPureState", "[state_initialisations]" ) { /* state-vector version just performs cloneQureg */ - Qureg vec2 = createQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec2 = createForcedQureg(NUM_QUBITS); // make sure states start differently initDebugState(vec1); @@ -234,7 +238,7 @@ TEST_CASE( "initPureState", "[state_initialisations]" ) { // make sure vec1 was not modified REQUIRE( areEqual(vec1, copy1) ); - destroyQureg(vec2, QUEST_ENV); + destroyQureg(vec2); } SECTION( "density-matrix" ) { @@ -267,26 +271,26 @@ TEST_CASE( "initPureState", "[state_initialisations]" ) { } SECTION( "qureg dimensions" ) { - Qureg vec2 = createQureg(NUM_QUBITS + 1, QUEST_ENV); + Qureg vec2 = createForcedQureg(NUM_QUBITS + 1); REQUIRE_THROWS_WITH( initPureState(vec1, vec2), Contains("Dimensions") && Contains("don't match") ); REQUIRE_THROWS_WITH( initPureState(mat1, vec2), Contains("Dimensions") && Contains("don't match") ); - destroyQureg(vec2, QUEST_ENV); + destroyQureg(vec2); } } - destroyQureg(vec1, QUEST_ENV); - destroyQureg(mat1, QUEST_ENV); + destroyQureg(vec1); + destroyQureg(mat1); } -/** @sa initStateFromAmps +/** @sa initArbitraryPureState * @ingroup unittest * @author Tyson Jones */ -TEST_CASE( "initStateFromAmps", "[state_initialisations]" ) { +TEST_CASE( "initArbitraryPureState", "[state_initialisations]" ) { - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg mat = createDensityQureg(NUM_QUBITS, QUEST_ENV); + Qureg vec = createForcedQureg(NUM_QUBITS); + Qureg mat = createForcedDensityQureg(NUM_QUBITS); SECTION( "correctness" ) { @@ -294,41 +298,28 @@ TEST_CASE( "initStateFromAmps", "[state_initialisations]" ) { // create random (unnormalised) vector QVector vecRef = getRandomQVector(1<= maxInd ) { - r=0; - c++; - } - } + // // try all valid number of amplitudes and offsets + // int startRow = GENERATE_COPY( range(0,maxInd) ); + // int startCol = GENERATE_COPY( range(0,maxInd) ); - REQUIRE( areEqual(matr, matrRef) ); - } - } - SECTION( "input validation" ) { - - SECTION( "start index" ) { - - int badInd = GENERATE_COPY( -1, maxInd ); - int numAmps = 0; - REQUIRE_THROWS_WITH( setDensityAmps(matr, badInd, 0, reals, imags, numAmps), Contains("Invalid amplitude index") ); - REQUIRE_THROWS_WITH( setDensityAmps(matr, 0, badInd, reals, imags, numAmps), Contains("Invalid amplitude index") ); - } - SECTION( "number of amplitudes" ) { - - // independent - int numAmps = GENERATE_COPY( -1, matr.numAmpsTotal+1 ); - REQUIRE_THROWS_WITH( setDensityAmps(matr, 0, 0, reals, imags, numAmps), Contains("Invalid number of amplitudes") ); - - // invalid considering start-index - REQUIRE_THROWS_WITH( setDensityAmps(matr, maxInd-1, maxInd-1, reals, imags, 2), Contains("More amplitudes given than exist") ); - REQUIRE_THROWS_WITH( setDensityAmps(matr, maxInd-1, maxInd-2, reals, imags, maxInd+2), Contains("More amplitudes given than exist") ); - } - SECTION( "state-vector" ) { - - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); - REQUIRE_THROWS_WITH( setDensityAmps(vec, 0, 0, reals, imags, 0), Contains("valid only for density matrices") ); - destroyQureg(vec, QUEST_ENV); - } - } - destroyQureg(matr, QUEST_ENV); -} + // // determine the max number of amps that can be passed from the given start indices + // int numPriorAmps = startRow + startCol*(1 << matr.numQubits); + // int maxNumAmps = matr.numAmps - numPriorAmps; + + // // previously, we tried all possible number of amps, like so: + // // int numAmps = GENERATE_COPY( range(0,maxNumAmps) ); // upper-bound allows all amps specified + // // but this is too many and causes a timeout on Ubuntu. + // // So we instead randomly choose the number of amps, and repeat 10 times. + // int numAmps = getRandomInt(0, maxNumAmps); + // GENERATE_COPY( range(0,10) ); + + // // check both specified and un-specified amplitudes are correctly handled + // initDebugState(matr); + // QMatrix matrRef = toQMatrix(matr); + + // setDensityQuregAmps(matr, startRow, startCol, reals, imags, numAmps); + + // int r=startRow; + // int c=startCol; + // for (int i=0; i= maxInd ) { + // r=0; + // c++; + // } + // } + + // REQUIRE( areEqual(matr, matrRef) ); + // } + // } + // SECTION( "input validation" ) { + + // SECTION( "start index" ) { + + // int badInd = GENERATE_COPY( -1, maxInd ); + // int numAmps = 0; + // REQUIRE_THROWS_WITH( setDensityAmps(matr, badInd, 0, reals, imags, numAmps), Contains("Invalid amplitude index") ); + // REQUIRE_THROWS_WITH( setDensityAmps(matr, 0, badInd, reals, imags, numAmps), Contains("Invalid amplitude index") ); + // } + // SECTION( "number of amplitudes" ) { + + // // independent + // int numAmps = GENERATE_COPY( -1, matr.numAmps+1 ); + // REQUIRE_THROWS_WITH( setDensityAmps(matr, 0, 0, reals, imags, numAmps), Contains("Invalid number of amplitudes") ); + + // // invalid considering start-index + // REQUIRE_THROWS_WITH( setDensityAmps(matr, maxInd-1, maxInd-1, reals, imags, 2), Contains("More amplitudes given than exist") ); + // REQUIRE_THROWS_WITH( setDensityAmps(matr, maxInd-1, maxInd-2, reals, imags, maxInd+2), Contains("More amplitudes given than exist") ); + // } + // SECTION( "state-vector" ) { + + // Qureg vec = createForcedQureg(NUM_QUBITS); + // REQUIRE_THROWS_WITH( setDensityAmps(vec, 0, 0, reals, imags, 0), Contains("valid only for density matrices") ); + // destroyQureg(vec); + // } + // } + // destroyQureg(matr); + // } -/** @sa setQuregToPauliHamil - * @ingroup unittest - * @author Tyson Jones - */ -TEST_CASE( "setQuregToPauliHamil", "[state_initialisations]" ) { - - Qureg rho = createDensityQureg(NUM_QUBITS, QUEST_ENV); - - SECTION( "correctness" ) { - - // too expensive to enumerate all paulis, so try 10x with random ones - GENERATE( range(0,10) ); - - int numTerms = GENERATE( 1, 2, 10, 15, 100, 1000 ); - PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); - setRandomPauliSum(hamil); +// setQuregToPauliHamil removed because replacement setQuregToPauliStrSum +// accepts new type PauliStrSum which has an initialiser incompatible +// with the deprecated PauliHamil type - setQuregToPauliHamil(rho, hamil); - REQUIRE( areEqual(rho, toQMatrix(hamil)) ); + // /** @sa setQuregToPauliHamil + // * @ingroup unittest + // * @author Tyson Jones + // */ + // TEST_CASE( "setQuregToPauliHamil", "[state_initialisations]" ) { - destroyPauliHamil(hamil); - } - SECTION( "input validation" ) { + // Qureg rho = createForcedDensityQureg(NUM_QUBITS); - SECTION( "density-matrix" ) { + // SECTION( "correctness" ) { - PauliHamil hamil = createPauliHamil(NUM_QUBITS, 5); - Qureg vec = createQureg(NUM_QUBITS, QUEST_ENV); + // // too expensive to enumerate all paulis, so try 10x with random ones + // GENERATE( range(0,10) ); - REQUIRE_THROWS_WITH( setQuregToPauliHamil(vec, hamil), Contains("density matrices") ); - - destroyQureg(vec, QUEST_ENV); - destroyPauliHamil(hamil); - } - SECTION( "pauli codes" ) { - - int numTerms = 3; - PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); - - // make one pauli code wrong - hamil.pauliCodes[GENERATE_COPY( range(0,numTerms*NUM_QUBITS) )] = (pauliOpType) GENERATE( -1, 4 ); - REQUIRE_THROWS_WITH( setQuregToPauliHamil(rho, hamil), Contains("Invalid Pauli code") ); - - destroyPauliHamil(hamil); - } - SECTION( "matching hamiltonian qubits" ) { - - int numTerms = 1; - PauliHamil hamil = createPauliHamil(NUM_QUBITS + 1, numTerms); + // int numTerms = GENERATE( 1, 2, 10, 15, 100, 1000 ); + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); + // setRandomPauliSum(hamil); + + // setQuregToPauliHamil(rho, hamil); + // REQUIRE( areEqual(rho, toQMatrix(hamil)) ); - REQUIRE_THROWS_WITH( setQuregToPauliHamil(rho, hamil), Contains("same number of qubits") ); + // destroyPauliHamil(hamil); + // } + // SECTION( "input validation" ) { - destroyPauliHamil(hamil); - } - } - destroyQureg(rho, QUEST_ENV); -} + // SECTION( "density-matrix" ) { + + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, 5); + // Qureg vec = createForcedQureg(NUM_QUBITS); + + // REQUIRE_THROWS_WITH( setQuregToPauliHamil(vec, hamil), Contains("density matrices") ); + + // destroyQureg(vec); + // destroyPauliHamil(hamil); + // } + // SECTION( "pauli codes" ) { + + // int numTerms = 3; + // PauliHamil hamil = createPauliHamil(NUM_QUBITS, numTerms); + + // // make one pauli code wrong + // hamil.pauliCodes[GENERATE_COPY( range(0,numTerms*NUM_QUBITS) )] = (pauliOpType) GENERATE( -1, 4 ); + // REQUIRE_THROWS_WITH( setQuregToPauliHamil(rho, hamil), Contains("Invalid Pauli code") ); + + // destroyPauliHamil(hamil); + // } + // SECTION( "matching hamiltonian qubits" ) { + + // int numTerms = 1; + // PauliHamil hamil = createPauliHamil(NUM_QUBITS + 1, numTerms); + + // REQUIRE_THROWS_WITH( setQuregToPauliHamil(rho, hamil), Contains("same number of qubits") ); + + // destroyPauliHamil(hamil); + // } + // } + // destroyQureg(rho); + // } @@ -611,13 +596,13 @@ TEST_CASE( "setWeightedQureg", "[state_initialisations]" ) { SECTION( "state-vector" ) { // make three random vectors - Qureg vecA = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg vecB = createQureg(NUM_QUBITS, QUEST_ENV); - Qureg vecC = createQureg(NUM_QUBITS, QUEST_ENV); - for (int j=0; j= NUM_QUBITS) maxNumTargs = NUM_QUBITS - 1; // make space for control qubit @@ -364,7 +376,7 @@ TEST_CASE( "controlledMultiQubitUnitary", "[unitaries]" ) { int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); ComplexMatrixN matr = createComplexMatrixN(numTargs); // initially zero, hence not-unitary - VLA(int, targs, numTargs); + int targs[NUM_QUBITS]; for (int i=0; i= 4 ); + REQUIRE( quregVec.numAmpsPerNode >= 4 ); // every test will use a unique random matrix QMatrix op = getRandomUnitary(2); - ComplexMatrix4 matr = toComplexMatrix4(op); + ComplexMatrix4 matr = toComplexMatrix4(op); SECTION( "correctness" ) { @@ -871,7 +882,7 @@ TEST_CASE( "controlledTwoQubitUnitary", "[unitaries]" ) { SECTION( "unitary fits in node" ) { // pretend we have a very limited distributed memory - quregVec.numAmpsPerChunk = 1; + quregVec.numAmpsPerNode = 1; REQUIRE_THROWS_WITH( controlledTwoQubitUnitary(quregVec, 0, 1, 2, matr), Contains("targets too many qubits")); } } @@ -1077,7 +1088,7 @@ TEST_CASE( "multiControlledMultiQubitUnitary", "[unitaries]" ) { PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); // figure out max-num targs (inclusive) allowed by hardware backend - int maxNumTargs = calcLog2(quregVec.numAmpsPerChunk); + int maxNumTargs = calcLog2(quregVec.numAmpsPerNode); if (maxNumTargs >= NUM_QUBITS) maxNumTargs = NUM_QUBITS - 1; // leave room for min-number of control qubits @@ -1189,7 +1200,7 @@ TEST_CASE( "multiControlledMultiQubitUnitary", "[unitaries]" ) { int ctrls[1] = {0}; int numTargs = GENERATE_COPY( range(1,maxNumTargs+1) ); - VLA(int, targs, numTargs); + int targs[NUM_QUBITS]; for (int i=0; i= 4 ); + REQUIRE( quregVec.numAmpsPerNode >= 4 ); // every test will use a unique random matrix QMatrix op = getRandomUnitary(2); - ComplexMatrix4 matr = toComplexMatrix4(op); + ComplexMatrix4 matr = toComplexMatrix4(op); SECTION( "correctness" ) { @@ -1725,7 +1735,7 @@ TEST_CASE( "multiControlledTwoQubitUnitary", "[unitaries]" ) { SECTION( "unitary fits in node" ) { // pretend we have a very limited distributed memory - quregVec.numAmpsPerChunk = 1; + quregVec.numAmpsPerNode = 1; int ctrls[1] = {0}; REQUIRE_THROWS_WITH( multiControlledTwoQubitUnitary(quregVec, ctrls, 1, 1, 2, matr), Contains("targets too many qubits")); } @@ -1745,7 +1755,7 @@ TEST_CASE( "multiControlledUnitary", "[unitaries]" ) { // every test will use a unique random matrix QMatrix op = getRandomUnitary(1); - ComplexMatrix2 matr = toComplexMatrix2(op); + ComplexMatrix2 matr = toComplexMatrix2(op); SECTION( "correctness" ) { @@ -1882,7 +1892,7 @@ TEST_CASE( "multiQubitUnitary", "[unitaries]" ) { PREPARE_TEST( quregVec, quregMatr, refVec, refMatr ); // figure out max-num (inclusive) targs allowed by hardware backend - int maxNumTargs = calcLog2(quregVec.numAmpsPerChunk); + int maxNumTargs = calcLog2(quregVec.numAmpsPerNode); SECTION( "correctness" ) { @@ -1945,7 +1955,7 @@ TEST_CASE( "multiQubitUnitary", "[unitaries]" ) { SECTION( "unitarity" ) { int numTargs = GENERATE_COPY( range(1,maxNumTargs) ); - VLA(int, targs, numTargs); + int targs[NUM_QUBITS]; for (int i=0; i= 4 ); + REQUIRE( quregVec.numAmpsPerNode >= 4 ); // every test will use a unique random matrix QMatrix op = getRandomUnitary(2); - ComplexMatrix4 matr = toComplexMatrix4(op); + ComplexMatrix4 matr = toComplexMatrix4(op); SECTION( "correctness" ) { @@ -2847,8 +2856,9 @@ TEST_CASE( "twoQubitUnitary", "[unitaries]" ) { SECTION( "unitary fits in node" ) { // pretend we have a very limited distributed memory - quregVec.numAmpsPerChunk = 1; - REQUIRE_THROWS_WITH( twoQubitUnitary(quregVec, 0, 1, matr), Contains("targets too many qubits")); + quregVec.numAmpsPerNode = 1; + quregVec.logNumAmpsPerNode = 1; + REQUIRE_THROWS_WITH( twoQubitUnitary(quregVec, 0, 1, matr), Contains("communication buffer") && Contains("cannot simultaneously store") ); } } CLEANUP_TEST( quregVec, quregMatr ); @@ -2866,7 +2876,7 @@ TEST_CASE( "unitary", "[unitaries]" ) { // every test will use a unique random matrix QMatrix op = getRandomUnitary(1); - ComplexMatrix2 matr = toComplexMatrix2(op); + ComplexMatrix2 matr = toComplexMatrix2(op); SECTION( "correctness" ) { diff --git a/tests/utilities.cpp b/tests/test_utilities.cpp similarity index 78% rename from tests/utilities.cpp rename to tests/test_utilities.cpp index df4ad90b0..2fd98ba86 100644 --- a/tests/utilities.cpp +++ b/tests/test_utilities.cpp @@ -4,17 +4,92 @@ * @author Tyson Jones */ -#include "QuEST.h" -#include "utilities.hpp" #include "catch.hpp" + +#define INCLUDE_DEPRECATED_FUNCTIONS 1 +#define DISABLE_DEPRECATION_WARNINGS 1 +#include "quest.h" + +#include "test_utilities.hpp" + #include +#include #include #include +#include + +#if COMPILE_MPI + + #include + + #if (FLOAT_PRECISION == 1) + #define MPI_QCOMP MPI_CXX_FLOAT_COMPLEX + #elif (FLOAT_PRECISION == 2) + #define MPI_QCOMP MPI_CXX_DOUBLE_COMPLEX + #elif (FLOAT_PRECISION == 4) && defined(MPI_CXX_LONG_DOUBLE_COMPLEX) + #define MPI_QCOMP MPI_CXX_LONG_DOUBLE_COMPLEX + #else + #define MPI_QCOMP MPI_C_LONG_DOUBLE_COMPLEX + #endif + + #ifdef MPI_MAX_AMPS_IN_MSG + #undef MPI_MAX_AMPS_IN_MSG + #endif + #define MPI_MAX_AMPS_IN_MSG (1 << 30) + +#endif + +using std::vector; + + + +/* + * forcing use of compiled backends + */ + +Qureg createForcedQureg(int numQubits) { + QuESTEnv env = getQuESTEnv(); + + int isDenseMatr = 0; + return createCustomQureg(numQubits, isDenseMatr, env.isDistributed, env.isGpuAccelerated, env.isMultithreaded); +} + +Qureg createForcedDensityQureg(int numQubits) { + QuESTEnv env = getQuESTEnv(); + + int isDenseMatr = 1; + return createCustomQureg(numQubits, isDenseMatr, env.isDistributed, env.isGpuAccelerated, env.isMultithreaded); +} -#ifdef DISTRIBUTED_MODE -#include + + +/* + * resolve reprecated absReal() + */ + +#ifdef absReal +#undef absReal #endif +// not sure where these will go! Maybe into QuEST v itself +qreal absReal(qreal x) { + return abs(x); // TODO: make precision agnostic +} +qreal absComp(qcomp x) { + return abs(x); // TODO: make precision agnostic +} + + + +/* RNG used for generating random test states, + * independently used from C's rand() which is + * used to generate random test data (e.g. operators) + */ + +static std::mt19937 randomGenerator; + + + /* (don't generate doxygen doc) * * preconditions to the internal unit testing functions are checked using @@ -23,6 +98,8 @@ */ #define DEMAND( cond ) if (!(cond)) FAIL( ); +constexpr qreal REAL_EPS = std::numeric_limits::epsilon(); + QVector operator + (const QVector& v1, const QVector& v2) { DEMAND( v1.size() == v2.size() ); QVector out = v1; @@ -140,9 +217,28 @@ QVector operator * (const QMatrix& m, const QVector& v) { return prod; } +void setRandomTestStateSeeds() { + + // must seed both rand() and this C++ generator (used for shuffling) + + // obtain a seed from hardware + std::random_device cspnrg; + unsigned seed = cspnrg(); + + // broadcast to ensure node consensus +#if COMPILE_MPI + int sendRank = 0; + MPI_Bcast(&seed, 1, MPI_UNSIGNED, sendRank, MPI_COMM_WORLD); +#endif + + // initilise both C (rand()) and C++ (randomGenerator) RNGs + srand(seed); + randomGenerator.seed(seed); +} + void assertQuregAndRefInDebugState(Qureg qureg, QVector ref) { DEMAND( qureg.isDensityMatrix == 0 ); - DEMAND( qureg.numAmpsTotal == (long long int) ref.size() ); + DEMAND( qureg.numAmps == (long long int) ref.size() ); // assert ref is in the debug state (else initDebugState failed) for (size_t i=0; i ctrlsCopy(ctrls, ctrls+numCtrls); - std::vector targsCopy(targs, targs+numTargs); + vector ctrlsCopy(ctrls, ctrls+numCtrls); + vector targsCopy(targs, targs+numTargs); // full-state matrix of qubit swaps QMatrix swaps = getIdentityMatrix(1 << numQubits); @@ -440,7 +536,7 @@ bool areEqual(QVector a, QVector b) { DEMAND( a.size() == b.size() ); for (size_t i=0; i REAL_EPS) + if (std::abs(a[i] - b[i]) > REAL_EPS) return false; return true; } @@ -450,7 +546,7 @@ bool areEqual(QMatrix a, QMatrix b) { for (size_t i=0; i REAL_EPS) + if (std::abs(a[i][j] - b[i][j]) > REAL_EPS) return false; return true; } @@ -512,10 +608,10 @@ QVector getRandomStateVector(int numQb) { return getNormalised(getRandomQVector(1< getRandomProbabilities(int numProbs) { +vector getRandomProbabilities(int numProbs) { // generate random unnormalised scalars - std::vector probs; + vector probs; qreal total = 0; for (int i=0; i probs = getRandomProbabilities(dim); + vector probs = getRandomProbabilities(dim); // add random pure states QMatrix dens = getZeroMatrix(dim); @@ -640,17 +736,17 @@ QMatrix getRandomUnitary(int numQb) { return matrU; } -std::vector getRandomKrausMap(int numQb, int numOps) { +vector getRandomKrausMap(int numQb, int numOps) { DEMAND( numOps >= 1 ); DEMAND( numOps <= 4*numQb*numQb ); // generate random unitaries - std::vector ops; + vector ops; for (int i=0; i weights(numOps); + vector weights(numOps); for (int i=0; i getRandomKrausMap(int numQb, int numOps) { return ops; } -std::vector getRandomOrthonormalVectors(int numQb, int numStates) { +vector getRandomOrthonormalVectors(int numQb, int numStates) { DEMAND( numQb >= 1 ); DEMAND( numStates >= 1); // set of orthonormal vectors - std::vector vecs; + vector vecs; for (int n=0; n getRandomOrthonormalVectors(int numQb, int numStates) { return vecs; } -QMatrix getMixedDensityMatrix(std::vector probs, std::vector states) { +QMatrix getMixedDensityMatrix(vector probs, vector states) { DEMAND( probs.size() == states.size() ); DEMAND( probs.size() >= 1 ); @@ -937,58 +1033,56 @@ void applyReferenceMatrix( bool areEqual(Qureg qureg1, Qureg qureg2, qreal precision) { DEMAND( qureg1.isDensityMatrix == qureg2.isDensityMatrix ); - DEMAND( qureg1.numAmpsTotal == qureg2.numAmpsTotal ); + DEMAND( qureg1.numAmps == qureg2.numAmps ); copyStateFromGPU(qureg1); copyStateFromGPU(qureg2); - syncQuESTEnv(QUEST_ENV); + syncQuESTEnv(); // loop terminates when areEqual = 0 int ampsAgree = 1; - for (long long int i=0; ampsAgree && i precision || imagDif > precision) { + if (absComp(dif) > precision) { ampsAgree = 0; - + // debug char buff[200]; - sprintf(buff, "Disagreement at %lld of (%s) + i(%s):\n\t%s + i(%s) VS %s + i(%s)\n", + snprintf(buff, 200, "Disagreement at %lld of (%s) + i(%s):\n\t%s + i(%s) VS %s + i(%s)\n", startInd+i, - REAL_STRING_FORMAT, REAL_STRING_FORMAT, REAL_STRING_FORMAT, - REAL_STRING_FORMAT, REAL_STRING_FORMAT, REAL_STRING_FORMAT); + QREAL_FORMAT_SPECIFIER, QREAL_FORMAT_SPECIFIER, QREAL_FORMAT_SPECIFIER, + QREAL_FORMAT_SPECIFIER, QREAL_FORMAT_SPECIFIER, QREAL_FORMAT_SPECIFIER); printf(buff, - realDif, imagDif, - qureg.stateVec.real[i], qureg.stateVec.imag[i], + real(dif), imag(dif), + real(qureg.cpuAmps[i]), imag(qureg.cpuAmps[i]), real(vec[startInd+i]), imag(vec[startInd+i])); break; @@ -997,7 +1091,7 @@ bool areEqual(Qureg qureg, QVector vec, qreal precision) { // if one node's partition wasn't equal, all-nodes must report not-equal int allAmpsAgree = ampsAgree; -#ifdef DISTRIBUTED_MODE +#if COMPILE_MPI MPI_Allreduce(&sAgree, &allAmpsAgree, 1, MPI_INT, MPI_LAND, MPI_COMM_WORLD); #endif @@ -1009,37 +1103,38 @@ bool areEqual(Qureg qureg, QVector vec) { bool areEqual(Qureg qureg, QMatrix matr, qreal precision) { DEMAND( qureg.isDensityMatrix ); - DEMAND( (long long int) (matr.size()*matr.size()) == qureg.numAmpsTotal ); + DEMAND( (long long int) (matr.size()*matr.size()) == qureg.numAmps ); - // ensure local qureg.stateVec is up to date + // ensure local qureg amps is up to date copyStateFromGPU(qureg); - syncQuESTEnv(QUEST_ENV); + syncQuESTEnv(); // the starting index in vec of this node's qureg partition. - long long int startInd = qureg.chunkId * qureg.numAmpsPerChunk; + long long int startInd = qureg.rank * qureg.numAmpsPerNode; long long int globalInd, row, col, i; int ampsAgree; // compare each of this node's amplitude to the corresponding matr sub-matrix - for (i=0; i REAL_EPS) return false; - dif = absReal(imag(vec[i]) - imags[i]); + dif = std::abs(imag(vec[i]) - imags[i]); if (dif > REAL_EPS) return false; } @@ -1087,7 +1182,7 @@ bool areEqual(QVector vec, qreal* reals) { for (size_t i=0; i REAL_EPS) return false; } @@ -1095,7 +1190,7 @@ bool areEqual(QVector vec, qreal* reals) { } /* Copies QMatrix into a CompelxMAtrix struct */ -#define macro_copyQMatrix(dest, src) { \ +#define macro_copyQMatrixToDeprecatedComplexMatrix(dest, src) { \ for (size_t i=0; i(matr.cpuElems, matr.cpuElems + matr.numElems); +} + +QVector toQVector(FullStateDiagMatr matr) { + +#if COMPILE_MPI + DEMAND( matr.numElems < MPI_MAX_AMPS_IN_MSG ); #endif - - qreal* fullRe; - qreal* fullIm; - + + vector vec(matr.numElems); + // in distributed mode, give every node the full diagonal operator -#ifdef DISTRIBUTED_MODE - fullRe = (qreal*) malloc(totalElems * sizeof *fullRe); - fullIm = (qreal*) malloc(totalElems * sizeof *fullIm); - - MPI_Allgather( - op.real, op.numElemsPerChunk, MPI_QuEST_REAL, - fullRe, op.numElemsPerChunk, MPI_QuEST_REAL, MPI_COMM_WORLD); - MPI_Allgather( - op.imag, op.numElemsPerChunk, MPI_QuEST_REAL, - fullIm, op.numElemsPerChunk, MPI_QuEST_REAL, MPI_COMM_WORLD); -#else - fullRe = op.real; - fullIm = op.imag; -#endif - - // copy full state vector into a QVector - QVector vec = QVector(totalElems); - for (long long int i=0; i 0) - hamil.pauliCodes[i++] = PAULI_Z; - else - hamil.pauliCodes[i++] = PAULI_I; +void setRandomDiagPauliHamil(PauliHamil hamil, int numQubits) { + for (int n=0; n allQb(numQb); for (int q=0; q &targs, int numQb) { + + setRandomTargets(targs.data(), targs.size(), numQb); +} QMatrix toQMatrix(qreal* coeffs, pauliOpType* paulis, int numQubits, int numTerms) { @@ -1389,9 +1470,11 @@ QMatrix toQMatrix(qreal* coeffs, pauliOpType* paulis, int numQubits, int numTerm // a now 2^numQubits by 2^numQubits Hermitian matrix return pauliSum; } -QMatrix toQMatrix(PauliHamil hamil) { - return toQMatrix(hamil.termCoeffs, hamil.pauliCodes, hamil.numQubits, hamil.numSumTerms); -} + +// QMatrix toQMatrix(PauliHamil hamil) { +// return toQMatrix(hamil.termCoeffs, hamil.pauliCodes, hamil.numQubits, hamil.numSumTerms); +// } + long long int getTwosComplement(long long int decimal, int numBits) { DEMAND( decimal >= 0 ); @@ -1423,76 +1506,76 @@ QMatrix toDiagonalQMatrix(QVector vec) { return mat; } -void setDiagMatrixOverrides(QMatrix &matr, int* numQubitsPerReg, int numRegs, enum bitEncoding encoding, long long int* overrideInds, qreal* overridePhases, int numOverrides) { - DEMAND( (encoding == UNSIGNED || encoding == TWOS_COMPLEMENT) ); - DEMAND( numRegs > 0 ); - DEMAND( numOverrides >= 0 ); +// void setDiagMatrixOverrides(QMatrix &matr, int* numQubitsPerReg, int numRegs, enum bitEncoding encoding, long long int* overrideInds, qreal* overridePhases, int numOverrides) { +// DEMAND( (encoding == UNSIGNED || encoding == TWOS_COMPLEMENT) ); +// DEMAND( numRegs > 0 ); +// DEMAND( numOverrides >= 0 ); - int totalQb = 0; - for (int r=0; r 0 ); - totalQb += numQubitsPerReg[r]; - } - DEMAND( matr.size() == (1 << totalQb) ); +// int totalQb = 0; +// for (int r=0; r 0 ); +// totalQb += numQubitsPerReg[r]; +// } +// DEMAND( matr.size() == (1 << totalQb) ); - // record whether a diagonal index has been already overriden - std::vector hasBeenOverriden(1 << totalQb); - for (int i=0; i<(1 << totalQb); i++) - hasBeenOverriden[i] = 0; +// // record whether a diagonal index has been already overriden +// vector hasBeenOverriden(1 << totalQb); +// for (int i=0; i<(1 << totalQb); i++) +// hasBeenOverriden[i] = 0; - int flatInd = 0; - for (int v=0; v { @@ -1578,11 +1661,11 @@ class SubListGenerator : public Catch::Generators::IGenerator { bool next() override { // offer next permutation of the current combination - if (next_permutation(sublist, sublist+sublen)) + if (std::next_permutation(sublist, sublist+sublen)) return true; // else generate the next combination - if (next_permutation(featured.begin(), featured.end())) { + if (std::next_permutation(featured.begin(), featured.end())) { prepareSublist(); return true; } diff --git a/tests/utilities.hpp b/tests/test_utilities.hpp similarity index 92% rename from tests/utilities.hpp rename to tests/test_utilities.hpp index 2ea37448e..87310007a 100644 --- a/tests/utilities.hpp +++ b/tests/test_utilities.hpp @@ -14,15 +14,14 @@ #ifndef QUEST_TEST_UTILS_H #define QUEST_TEST_UTILS_H -#include "QuEST.h" -#include "QuEST_complex.h" +#include "quest.h" #include "catch.hpp" + +#include #include -/** The single QuESTEnv environment created before the Catch tests begin, - * and destroyed thereafter. - */ -extern QuESTEnv QUEST_ENV; +using std::string; +using std::vector; /** The default number of qubits in the registers created for unit testing * (both statevectors and density matrices). Creation of non-NUM_QUBITS sized @@ -33,24 +32,21 @@ extern QuESTEnv QUEST_ENV; * results from their expected value, due to numerical error; this is especially * apparent for density matrices. */ -#define NUM_QUBITS 5 +#define NUM_QUBITS 4 + +#undef REAL_EPS +static qreal REAL_EPS = 1E2 * DEFAULT_VALIDATION_EPSILON; +//#define REAL_EPS DEFAULT_VALIDATION_EPSILON #ifndef M_PI #define M_PI 3.141592653589793238 #endif -/** This is absolute war against MSVC C++14 which does not permit variable-length - * arrays. We hunted down the previous VLAs with regex: - * ([a-zA-Z0-9]+?) ([a-zA-Z0-9]+?)\[([a-zA-Z0-9]+?)\] - * replacing results with: - * VLA($1, $2, $3) - * We perform this replacement even for non-MSVC compilers, since some dislike - * VLAs of non-POD elemets (like of QMatrix, compiling with NVCC + Clang). - * Eat it, Bill! - */ -#define VLA(type, name, len) \ - std::vector name##_vla_hack_vec(len); \ - type* name = name##_vla_hack_vec.data(); + +// Qureg creators which forcefully enable the environment backends +Qureg createForcedQureg(int numQubits); +Qureg createForcedDensityQureg(int numQubits); + /** A complex square matrix. * Should be initialised with getZeroMatrix(). @@ -63,7 +59,7 @@ extern QuESTEnv QUEST_ENV; * @ingroup testutilities * @author Tyson Jones */ -typedef std::vector> QMatrix; +typedef vector> QMatrix; /** A complex vector, which can be zero-initialised with QVector(numAmps). * These have all the natural linear-algebra operator overloads. @@ -74,7 +70,20 @@ typedef std::vector> QMatrix; * @ingroup testutilities * @author Tyson Jones */ -typedef std::vector QVector; +typedef vector QVector; + +/** Seed the C and C++ RNGs using hardware CSPRNG + * + * @ingroup testutilities + * @author Tyson Jones + */ +void setRandomTestStateSeeds(); + +#ifdef absReal +#undef absReal +#endif +qreal absReal(qreal x); +qreal absComp(qcomp x); /** Asserts the given statevector qureg and reference agree, and are properly initialised in the debug state. * Assertion uses the DEMAND() macro, calling Catch2's FAIL() if unsatisfied, so does not contribute @@ -143,14 +152,21 @@ QVector operator * (const QMatrix& m, const QVector& v); */ QVector toQVector(Qureg qureg); -/** Returns a vector with the same of the full diagonal operator, - * populated with \p op's elements. +/** Returns a vector with the given diagonal's elements. + * In distributed mode, this involves an all-to-all broadcast of \p op. + * + * @ingroup testutilities + * @author Tyson Jones + */ +QVector toQVector(DiagMatr op); + +/** Returns a vector with the given diagonal's elements. * In distributed mode, this involves an all-to-all broadcast of \p op. * * @ingroup testutilities * @author Tyson Jones */ -QVector toQVector(DiagonalOp op); +QVector toQVector(FullStateDiagMatr op); /** Returns an equal-size copy of the given density matrix \p qureg. * In GPU mode, this function involves a copy of \p qureg from GPU memory to RAM. @@ -161,34 +177,26 @@ QVector toQVector(DiagonalOp op); */ QMatrix toQMatrix(Qureg qureg); -/** Returns the matrix (where a=\p alpha, b=\p beta) - * {{a, -conj(b)}, {b, conj(a)}} using the \p qcomp complex type. - * - * @ingroup testutilities - * @author Tyson Jones - */ -QMatrix toQMatrix(Complex alpha, Complex beta); - /** Returns a copy of the given 2-by-2 matrix. * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(ComplexMatrix2 src); +QMatrix toQMatrix(CompMatr1 src); /** Returns a copy of the given 4-by-4 matrix. * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(ComplexMatrix4 src); +QMatrix toQMatrix(CompMatr2 src); -/** Returns a copy of the given 2^\p N-by-2^\p N matrix +/** Returns a copy of the given matrix * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(ComplexMatrixN src); +QMatrix toQMatrix(CompMatr src); /** Returns a 2^\p N-by-2^\p N Hermitian matrix form of the specified * weighted sum of Pauli products @@ -196,61 +204,70 @@ QMatrix toQMatrix(ComplexMatrixN src); * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(qreal* coeffs, pauliOpType* paulis, int numQubits, int numTerms); +QMatrix toQMatrix(qreal* coeffs, PauliStr* paulis, int numQubits, int numTerms); /** Returns a 2^\p N-by-2^\p N Hermitian matrix form of the PauliHamil * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(PauliHamil hamil); +QMatrix toQMatrix(PauliStrSum hamil); -/** Returns a 2^\p N-by-2^\p N complex diagonal matrix form of the DiagonalOp +/** Returns a 2^\p N-by-2^\p N Hermitian Z-basis + * matrix of the given complex-weighted sum of Pauli + * strings, where N is the number of non-Identity + * operators. * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(DiagonalOp op); +QMatrix toQMatrix(PauliStrSum sum); -/** Returns a 2^\p n-by-2^\p n complex diagonal matrix form of the SubDiagonalOp, - * where n = op.numQubits +/** Returns a dense matrix equivalent to the given diagonal * * @ingroup testutilities * @author Tyson Jones */ -QMatrix toQMatrix(SubDiagonalOp op); +QMatrix toQMatrix(DiagMatr matr); -/** Returns a diagonal complex matrix formed by the given vector +/** Returns a dense matrix equivalent to the given diagonal * * @ingroup testutilities - * @author Tyson Jones + * @author Tyson Jones */ -QMatrix toDiagonalQMatrix(QVector vec); +QMatrix toQMatrix(FullStateDiagMatr matr); -/** Returns a \p ComplexMatrix2 copy of QMatix \p qm. +/** Returns a \p CompMatr1 copy of QMatix \p qm. * Demands that \p qm is a 2-by-2 matrix. * * @ingroup testutilities * @author Tyson Jones */ -ComplexMatrix2 toComplexMatrix2(QMatrix qm); +CompMatr1 toCompMatr1(QMatrix qm); -/** Returns a \p ComplexMatrix4 copy of QMatix \p qm. +/** Returns a \p CompMatr2 copy of QMatix \p qm. * Demands that \p qm is a 4-by-4 matrix. * * @ingroup testutilities * @author Tyson Jones */ -ComplexMatrix4 toComplexMatrix4(QMatrix qm); +CompMatr2 toCompMatr2(QMatrix qm); -/** Initialises \p cm with the values of \p qm. - * Demands that \p cm is a previously created ComplexMatrixN instance, with - * the same dimensions as \p qm. - * +/** Populates the ComplexMatrixN with the contents of a QMatrix. In + * GPU-mode, this will then sync the elements ot the matrix's + * persistent GPU memory + * * @ingroup testutilities * @author Tyson Jones */ -void toComplexMatrixN(QMatrix qm, ComplexMatrixN cm); +void toCompMatr(QMatrix qm, CompMatr cm); + +/** Returns a diagonal complex matrix formed by the given vector + * + * @ingroup testutilities + * @author Tyson Jones + */ +QMatrix toDiagonalQMatrix(QVector vec); /** Initialises the state-vector \p qureg to have the same amplitudes as \p vec. * Demands \p qureg is a state-vector of an equal size to \p vec. @@ -473,14 +490,14 @@ QVector getMatrixDiagonal(QMatrix matr); * @ingroup testutilities * @author Tyson Jones */ -std::vector getRandomKrausMap(int numQb, int numOps); +vector getRandomKrausMap(int numQb, int numOps); /** Returns a list of random real scalars, each in [0, 1], which sum to unity. * * @ingroup testutilities * @author Tyson Jones */ -std::vector getRandomProbabilities(int numProbs); +vector getRandomProbabilities(int numProbs); /** Returns a list of random orthonormal complex vectors, from an undisclosed * distribution. @@ -488,7 +505,7 @@ std::vector getRandomProbabilities(int numProbs); * @ingroup testutilities * @author Tyson Jones */ -std::vector getRandomOrthonormalVectors(int numQb, int numStates); +vector getRandomOrthonormalVectors(int numQb, int numStates); /** Returns a mixed density matrix formed from mixing the given pure states, * which are assumed normalised, but not necessarily orthogonal. @@ -496,7 +513,7 @@ std::vector getRandomOrthonormalVectors(int numQb, int numStates); * @ingroup testutilities * @author Tyson Jones */ -QMatrix getMixedDensityMatrix(std::vector probs, std::vector states); +QMatrix getMixedDensityMatrix(vector probs, vector states); /** Returns an L2-normalised copy of \p vec, using Kahan summation for improved accuracy. * @@ -1014,14 +1031,14 @@ unsigned int calcLog2(long unsigned int res); * @ingroup testutilities * @author Tyson Jones */ -void setRandomPauliSum(qreal* coeffs, pauliOpType* codes, int numQubits, int numTerms); +void setRandomPauliSum(qreal* coeffs, PauliStr* codes, int numQubits, int numTerms); /** Populates \p hamil with random coefficients and pauli codes * * @ingroup testutilities * @author Tyson Jones */ -void setRandomPauliSum(PauliHamil hamil); +void setRandomPauliSum(PauliHamil hamil, int numQubits); /** Populates \p hamil with random coefficients and a random amount number of * PAULI_I and PAULI_Z operators. @@ -1029,7 +1046,7 @@ void setRandomPauliSum(PauliHamil hamil); * @ingroup testutilities * @author Tyson Jones */ -void setRandomDiagPauliHamil(PauliHamil hamil); +void setRandomDiagPauliHamil(PauliHamil hamil, int numQubits); /** Populates \p targs with a random selection of \p numTargs elements from [0,\p numQb-1]. * List \p targs does not need to be initialised and its elements are overwritten. @@ -1039,6 +1056,14 @@ void setRandomDiagPauliHamil(PauliHamil hamil); */ void setRandomTargets(int* targs, int numTargs, int numQb); +/** Populates \p targs with a random selection of elements from [0,\p numQb-1]. + * List \p targs does not need to be initialised and its elements are overwritten. + * + * @ingroup testutilities + * @author Tyson Jones + */ +void setRandomTargets(vector &targs, int numQb); + /** Returns the two's complement signed encoding of the unsigned number decimal, * which must be a number between 0 and 2^numBits (exclusive). The returned number * lies in [-2^(numBits-1), 2^(numBits-1)-1] @@ -1057,18 +1082,18 @@ long long int getTwosComplement(long long int decimal, int numBits); */ long long int getUnsigned(long long int twosComp, int numBits); -/** Modifies the given diagonal matrix such that the diagonal elements which - * correspond to the coordinates in overrideInds are replaced with exp(i phase), as - * prescribed by overridePhases. This function assumes that the given registers - * are contiguous, are in order of increasing significance, and that the matrix - * is proportionately sized and structured to act on the space of all registers - * combined. Overrides can be repeated, and only the first encountered for a given - * index will be effected (much like applyMultiVarPhaseFuncOverrides()). - * - * @ingroup testutilities - * @author Tyson Jones - */ -void setDiagMatrixOverrides(QMatrix &matr, int* numQubitsPerReg, int numRegs, enum bitEncoding encoding, long long int* overrideInds, qreal* overridePhases, int numOverrides); +// /** Modifies the given diagonal matrix such that the diagonal elements which +// * correspond to the coordinates in overrideInds are replaced with exp(i phase), as +// * prescribed by overridePhases. This function assumes that the given registers +// * are contiguous, are in order of increasing significance, and that the matrix +// * is proportionately sized and structured to act on the space of all registers +// * combined. Overrides can be repeated, and only the first encountered for a given +// * index will be effected (much like applyMultiVarPhaseFuncOverrides()). +// * +// * @ingroup testutilities +// * @author Tyson Jones +// */ +// void setDiagMatrixOverrides(QMatrix &matr, int* numQubitsPerReg, int numRegs, enum bitEncoding encoding, long long int* overrideInds, qreal* overridePhases, int numOverrides); /** Modifies outFn to be a filename of format prefix_NUM.txt where NUM * is a new unique integer so far. This is useful for getting unique filenames for @@ -1078,7 +1103,7 @@ void setDiagMatrixOverrides(QMatrix &matr, int* numQubitsPerReg, int numRegs, en * @ingroup testutilities * @author Tyson Jones */ -void setUniqueFilename(char* outFn, char* prefix); +void setUniqueFilename(char* outFn, int maxlen, char* prefix); /** Writes contents to the file with filename fn, which is created and/or overwritten. * In distributed mode, the master node writes while the other nodes wait until complete. @@ -1086,7 +1111,7 @@ void setUniqueFilename(char* outFn, char* prefix); * @ingroup testutilities * @author Tyson Jones */ -void writeToFileSynch(char* fn, const string& contents); +void writeToFileSynch(char* fn, const std::string& contents); /** Deletes all files with filename starting with prefix. In distributed mode, the * master node deletes while the other nodes wait until complete. @@ -1249,6 +1274,6 @@ CatchGen sequences(int base, int numDigits); * @ingroup testutilities * @author Tyson Jones */ -CatchGen pauliseqs(int numPaulis); +CatchGen pauliseqs(int numPaulis); #endif // QUEST_TEST_UTILS_H