From 2cb27272c7882c5cf63a0ef047dc37884c113cac Mon Sep 17 00:00:00 2001 From: Chae-Yeun Park Date: Sat, 12 Feb 2022 10:19:51 -0500 Subject: [PATCH] Finalize the LM kernel (#212) * Complete all tests * Complete all gate implementations * Add compile-time tests * Refactor `DynamicDispatcher` and gate implementations. Co-authored-by: Lee James O'Riordan Co-authored-by: Ali Asadi --- .github/CHANGELOG.md | 10 +- .gitignore | 1 + CMakeLists.txt | 10 +- Makefile | 3 +- doc/add_kernel.rst | 84 +- pennylane_lightning/_version.py | 2 +- pennylane_lightning/lightning_qubit.py | 4 - pennylane_lightning/src/.clang-tidy | 2 +- pennylane_lightning/src/CMakeLists.txt | 6 +- .../src/algorithms/AdjointDiff.hpp | 92 +- .../src/algorithms/CMakeLists.txt | 8 +- .../src/algorithms/JacobianTape.hpp | 8 +- pennylane_lightning/src/bindings/Bindings.cpp | 25 +- pennylane_lightning/src/bindings/Bindings.hpp | 61 +- .../src/examples/CMakeLists.txt | 14 +- .../src/examples/ExampleUtil.hpp | 27 + .../src/examples/benchmark_multi_rz.cpp | 77 + .../src/examples/gate_benchmark_oplist.cpp | 78 +- .../src/examples/plot_gate_benchmark.py | 2 + .../src/examples/run_gate_benchmark.sh | 61 +- .../src/gates/AvailableKernels.hpp | 37 + pennylane_lightning/src/gates/CMakeLists.txt | 12 + pennylane_lightning/src/gates/Constant.hpp | 258 +++ .../src/gates/GateImplementationsLM.hpp | 1197 +++++++++++++ .../src/gates/GateImplementationsPI.hpp | 747 +++++++++ .../src/gates/GateOperation.hpp | 85 + pennylane_lightning/src/gates/GateUtil.cpp | 90 + .../IndicesUtil.hpp => gates/GateUtil.hpp} | 46 +- .../src/{simulator => gates}/Gates.hpp | 28 +- pennylane_lightning/src/gates/KernelType.hpp | 37 + .../src/gates/OpToMemberFuncPtr.hpp | 452 +++++ .../src/gates/PauliGenerator.hpp | 58 + .../src/gates/SelectKernel.hpp | 127 ++ .../src/simulator/CMakeLists.txt | 10 +- .../src/simulator/DynamicDispatcher.cpp | 218 +++ .../src/simulator/DynamicDispatcher.hpp | 352 ++-- .../src/simulator/GateOperations.hpp | 168 -- .../src/simulator/GateOperationsLM.hpp | 513 ------ .../src/simulator/GateOperationsPI.hpp | 585 ------- .../src/simulator/IndicesUtil.cpp | 36 - .../src/simulator/KernelType.hpp | 80 - .../src/simulator/Measures.hpp | 15 +- .../src/simulator/SelectGateOps.hpp | 375 ----- .../src/simulator/StateVectorBase.hpp | 362 ++-- .../src/simulator/StateVectorManaged.hpp | 59 +- .../src/simulator/StateVectorRaw.hpp | 34 +- pennylane_lightning/src/tests/CMakeLists.txt | 30 +- .../src/tests/TestAvailableKernels.hpp | 87 + .../src/tests/TestConstant.hpp | 121 ++ pennylane_lightning/src/tests/TestHelpers.hpp | 312 +++- pennylane_lightning/src/tests/TestKernels.hpp | 13 + pennylane_lightning/src/tests/TestMacros.hpp | 6 - .../src/tests/Test_DynamicDispatcher.cpp | 143 +- .../Test_GateImplementations_Generator.cpp | 155 ++ .../Test_GateImplementations_Inverse.cpp | 88 + .../tests/Test_GateImplementations_Matrix.cpp | 968 +++++++++++ .../Test_GateImplementations_Nonparam.cpp | 597 +++++++ .../tests/Test_GateImplementations_Param.cpp | 1480 +++++++++++++++++ .../src/tests/Test_GateOperations.cpp | 14 - .../tests/Test_GateOperations_Nonparam.cpp | 537 ------ .../src/tests/Test_GateOperations_Param.cpp | 329 ---- ...Test_IndicesUtil.cpp => Test_GateUtil.cpp} | 39 +- .../src/tests/Test_Internal.cpp | 118 ++ .../src/tests/Test_OpToMemberFuncPtr.cpp | 252 +++ .../src/tests/Test_SelectGateOps.cpp | 148 -- .../src/tests/Test_StateVectorRaw.cpp | 155 +- pennylane_lightning/src/tests/Test_Util.cpp | 97 +- .../src/tests/compile_time_tests.cpp | 9 + pennylane_lightning/src/util/BitUtil.hpp | 24 +- pennylane_lightning/src/util/ConstantUtil.hpp | 35 +- pennylane_lightning/src/util/Error.hpp | 7 +- .../src/util/LinearAlgebra.hpp | 129 +- pennylane_lightning/src/util/TypeList.hpp | 56 + pennylane_lightning/src/util/Util.hpp | 3 + tests/test_adjoint_jacobian.py | 8 +- tests/test_apply.py | 41 +- 76 files changed, 8646 insertions(+), 3911 deletions(-) create mode 100644 pennylane_lightning/src/examples/ExampleUtil.hpp create mode 100644 pennylane_lightning/src/examples/benchmark_multi_rz.cpp create mode 100644 pennylane_lightning/src/gates/AvailableKernels.hpp create mode 100644 pennylane_lightning/src/gates/CMakeLists.txt create mode 100644 pennylane_lightning/src/gates/Constant.hpp create mode 100644 pennylane_lightning/src/gates/GateImplementationsLM.hpp create mode 100644 pennylane_lightning/src/gates/GateImplementationsPI.hpp create mode 100644 pennylane_lightning/src/gates/GateOperation.hpp create mode 100644 pennylane_lightning/src/gates/GateUtil.cpp rename pennylane_lightning/src/{simulator/IndicesUtil.hpp => gates/GateUtil.hpp} (67%) rename pennylane_lightning/src/{simulator => gates}/Gates.hpp (96%) create mode 100644 pennylane_lightning/src/gates/KernelType.hpp create mode 100644 pennylane_lightning/src/gates/OpToMemberFuncPtr.hpp create mode 100644 pennylane_lightning/src/gates/PauliGenerator.hpp create mode 100644 pennylane_lightning/src/gates/SelectKernel.hpp create mode 100644 pennylane_lightning/src/simulator/DynamicDispatcher.cpp delete mode 100644 pennylane_lightning/src/simulator/GateOperations.hpp delete mode 100644 pennylane_lightning/src/simulator/GateOperationsLM.hpp delete mode 100644 pennylane_lightning/src/simulator/GateOperationsPI.hpp delete mode 100644 pennylane_lightning/src/simulator/IndicesUtil.cpp delete mode 100644 pennylane_lightning/src/simulator/KernelType.hpp delete mode 100644 pennylane_lightning/src/simulator/SelectGateOps.hpp create mode 100644 pennylane_lightning/src/tests/TestAvailableKernels.hpp create mode 100644 pennylane_lightning/src/tests/TestConstant.hpp create mode 100644 pennylane_lightning/src/tests/TestKernels.hpp delete mode 100644 pennylane_lightning/src/tests/TestMacros.hpp create mode 100644 pennylane_lightning/src/tests/Test_GateImplementations_Generator.cpp create mode 100644 pennylane_lightning/src/tests/Test_GateImplementations_Inverse.cpp create mode 100644 pennylane_lightning/src/tests/Test_GateImplementations_Matrix.cpp create mode 100644 pennylane_lightning/src/tests/Test_GateImplementations_Nonparam.cpp create mode 100644 pennylane_lightning/src/tests/Test_GateImplementations_Param.cpp delete mode 100644 pennylane_lightning/src/tests/Test_GateOperations.cpp delete mode 100644 pennylane_lightning/src/tests/Test_GateOperations_Nonparam.cpp delete mode 100644 pennylane_lightning/src/tests/Test_GateOperations_Param.cpp rename pennylane_lightning/src/tests/{Test_IndicesUtil.cpp => Test_GateUtil.cpp} (64%) create mode 100644 pennylane_lightning/src/tests/Test_Internal.cpp create mode 100644 pennylane_lightning/src/tests/Test_OpToMemberFuncPtr.cpp delete mode 100644 pennylane_lightning/src/tests/Test_SelectGateOps.cpp create mode 100644 pennylane_lightning/src/tests/compile_time_tests.cpp create mode 100644 pennylane_lightning/src/util/TypeList.hpp diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 761dde00e7..54551edb16 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -6,8 +6,13 @@ ### Improvements +* Update adjointJacobian and VJP methods. +[(#222)](https://github.com/PennyLaneAI/pennylane-lightning/pull/222) + * Set GitHub workflow to upload wheels to Test PyPI [(#220)](https://github.com/PennyLaneAI/pennylane-lightning/pull/220). +* Finalize the new kernel implementation [(#212)](https://github.com/PennyLaneAI/pennylane-lightning/pull/212). + ### Documentation ### Bug fixes @@ -18,7 +23,7 @@ This release contains contributions from (in alphabetical order): -Chae-Yeun Park +Ali Asadi, Chae-Yeun Park --- @@ -44,9 +49,6 @@ Chae-Yeun Park * Ensure debug info is built into dynamic libraries. [(#201)](https://github.com/PennyLaneAI/pennylane-lightning/pull/201) -* Update adjointJacobian and VJP methods. -[(#222)](https://github.com/PennyLaneAI/pennylane-lightning/pull/222) - ### Documentation ### Bug fixes diff --git a/.gitignore b/.gitignore index 9312da9f18..01d8f85a32 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ venv/ doc/_build/ PennyLane_Lightning.egg-info/ build/ +Build/ BuildBench/ BuildTests/ dist/ diff --git a/CMakeLists.txt b/CMakeLists.txt index c898903a99..89f665e31e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,11 +71,13 @@ target_include_directories(pennylane_lightning INTERFACE "pennylane_lightning/sr ##################################################### pybind11_add_module(lightning_qubit_ops "pennylane_lightning/src/bindings/Bindings.cpp") -target_link_libraries(lightning_qubit_ops PRIVATE lightning_compile_options - lightning_external_libs - lightning_utils +target_link_libraries(lightning_qubit_ops PRIVATE lightning_algorithms + lightning_gates lightning_simulator - lightning_algorithms) + lightning_utils) + +target_link_libraries(lightning_qubit_ops PRIVATE lightning_compile_options + lightning_external_libs) set_target_properties(lightning_qubit_ops PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(lightning_qubit_ops PRIVATE VERSION_INFO=${VERSION_STRING}) diff --git a/Makefile b/Makefile index b2d3cdc218..02556dc3e0 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ docs: clean-docs: $(MAKE) -C doc clean +.PHONY : test-builtin test-suite test-python coverage test-cpp test-builtin: $(PYTHON) -I $(TESTRUNNER) @@ -117,5 +118,5 @@ endif .PHONY: check-tidy check-tidy: rm -rf ./Build - cmake . -BBuild -DENABLE_CLANG_TIDY=ON -DBUILD_TESTS=1 + cmake . -BBuild -DENABLE_CLANG_TIDY=ON -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON cmake --build ./Build diff --git a/doc/add_kernel.rst b/doc/add_kernel.rst index ed6bf198c1..3c365fe62f 100644 --- a/doc/add_kernel.rst +++ b/doc/add_kernel.rst @@ -1,15 +1,22 @@ +.. _lightning_add_gate_implementation: + Adding a gate implementation ############################ -We discuss how one can add another gate implementation in this document. Assume that you want to add a custom ``PauliX`` gate implementation in Pennylane-Lightning. In this case, you may first add a template class as: +We discuss how one can add another gate implementation in this document. Assume that you want to add a custom ``PauliX`` gate implementation in Pennylane-Lightning. In this case, you may first create a file and add a class: .. code-block:: cpp - template + // file: MyGateImplementation.hpp struct MyGateImplementation { - constexpr static implemented_gates = {GateOperations::PauliX}; + public: + constexpr static std::array implemented_gates = { + GateOperation::PauliX + }; // List of implemented gates constexpr static kernel_id = KernelType::Mykernel; // Will be discussed below + constexpr static std::string_view = "MyGateImpl"; // Name of your kernel + template static void applyPauliX(std::complex* data, size_t num_qubits, const std::vector& wires, @@ -17,58 +24,29 @@ We discuss how one can add another gate implementation in this document. Assume /* Write your implementation */ ... } - - static void applyPauliY(std::complex* data, - size_t num_qubits, - const std::vector& wires, - [[maybe_unused]] bool inverse) { - PL_ABORT("MyGateImplementation::applyPauliY is not implemented"); - } - - /* All other gates */ - ... }; -Note that all member functions must be defined to prevent compile errors (this requirement may be deprecated in the near future). - -Then you can add your gate implementation to Pennylane-Lightning by doing followings: +Then you can add your gate implementation to Pennylane-Lightning. This can be done my modifying two files as: .. code-block:: cpp // file: simulator/KernelType.hpp namespace Pennylane { - enum class KernelType { PI, LM, MyKernel /* This is added */, Unknown }; - - namespace Constant { - constexpr std::array available_kernels = { - std::pair{KernelType::PI, "PI"}, - std::pair{KernelType::LM, "LM"}, - /* The following line is added */ - std::pair{KernelType::MyKernel, "MyKernel"}, - }; + enum class KernelType { PI, LM, MyKernel /* This is added */, None }; /* Rest of the file */ + } // namespace Pennylane and .. code-block:: cpp - // file: simulator/SelectGateOps.hpp + // file: simulator/AvailableKernels.hpp namespace Pennylane { - ... - /* Some code */ - - template class SelectGateOps {}; - - template - class SelectGateOps : public GateOperationsPI {}; - template - class SelectGateOps : public GateOperationsLM {}; - - /* Add the following lines */ - template - class SelectGateOps : public MyGateImplementation {}; + using AvailableKernels = Util::TypeList; } // namespace Pennylane @@ -106,10 +84,8 @@ To make your gate implementation default, you need to change ``default_kernel_fo .. code-block:: cpp - // file: simulator/SelectGateOps.hpp - constexpr std::array, - static_cast(GateOperations::END)> - default_kernel_for_ops = { + // file: simulator/Constant.hpp + constexpr std::array default_kernel_for_gates = { std::pair{GateOperations::PauliX, KernelType::LM}, std::pair{GateOperations::PauliY, KernelType::LM}, ... @@ -119,12 +95,26 @@ to .. code-block:: cpp - constexpr std::array, - static_cast(GateOperations::END)> - default_kernel_for_ops = { + constexpr std::array default_kernel_for_gates = { std::pair{GateOperations::PauliX, KernelType::MyKernel}, std::pair{GateOperations::PauliY, KernelType::LM}, ... } -will make your implementation as default kernel for ``PauliX`` gate (for all C++ call as well as for the Python binding). +will make your implementation as default kernel for ``PauliX`` gate (for all C++ calls as well as for the Python binding). + +Gate generators can also be handled in the same way. + +Test your gate implementation +============================= + +To test your own kernel implementations, you can go to ``tests/TestKernels.hpp`` and add your implementation. + +.. code-block:: cpp + + using TestKernels = Pennylane::Util::TypeList; + +It will automatically test your gate implementation. +Note that, in the current implementation, this will test a gate if ``apply + gate name`` is defined even when the gate is not included in ``implemented_gates`` variable. diff --git a/pennylane_lightning/_version.py b/pennylane_lightning/_version.py index b7247672af..6492811e4b 100644 --- a/pennylane_lightning/_version.py +++ b/pennylane_lightning/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.22.0-dev4" +__version__ = "0.22.0-dev5" diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 9bbea07fd9..344870fd61 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -56,10 +56,6 @@ UNSUPPORTED_PARAM_GATES_ADJOINT = ( - "MultiRZ", - "IsingXX", - "IsingYY", - "IsingZZ", "SingleExcitation", "SingleExcitationPlus", "SingleExcitationMinus", diff --git a/pennylane_lightning/src/.clang-tidy b/pennylane_lightning/src/.clang-tidy index c7bfa4f8ed..f015b16a1d 100644 --- a/pennylane_lightning/src/.clang-tidy +++ b/pennylane_lightning/src/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,-llvmlibc-*,modernize-*,-modernize-use-trailing-return-type,clang-analyzer-cplusplus*,openmp-*,performance-*,portability-*,readability-*,hicpp-signed-bitwise' +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,-llvmlibc-*,modernize-*,-modernize-use-trailing-return-type,clang-analyzer-cplusplus*,openmp-*,performance-*,portability-*,readability-*,hicpp-*,-hicpp-no-array-decay,bugprone-suspicious-*,llvm-namespace-comment,' WarningsAsErrors: '*' HeaderFilterRegex: '.*' AnalyzeTemporaryDtors: false diff --git a/pennylane_lightning/src/CMakeLists.txt b/pennylane_lightning/src/CMakeLists.txt index 15ce66b257..b6776ac992 100644 --- a/pennylane_lightning/src/CMakeLists.txt +++ b/pennylane_lightning/src/CMakeLists.txt @@ -5,6 +5,7 @@ project(lightning_components LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) option(ENABLE_WARNINGS "Enable warnings" ON) +option(ENABLE_OPENMP "Enable OpenMP" ON) if(ENABLE_CLANG_TIDY) if(NOT DEFINED CLANG_TIDY_BINARY) @@ -25,8 +26,9 @@ include("${PROJECT_SOURCE_DIR}/../../cmake/process_options.cmake") ############################################################################### # Include all nested sources directories ############################################################################### -set(COMPONENT_SUBDIRS simulator; - algorithms; +set(COMPONENT_SUBDIRS algorithms; + gates; + simulator; util; ) foreach(COMP ${COMPONENT_SUBDIRS}) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 01b4d2bd48..1d84d139b6 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -18,11 +18,11 @@ #include #include #include -#include #include #include #include +#include "DynamicDispatcher.hpp" #include "Error.hpp" #include "JacobianTape.hpp" #include "LinearAlgebra.hpp" @@ -36,70 +36,10 @@ namespace { using namespace Pennylane; using namespace Pennylane::Util; -template -static constexpr auto getP00() -> std::vector> { - return {ONE(), ZERO(), ZERO(), ZERO()}; -} - -template -static constexpr auto getP11() -> std::vector> { - return {ZERO(), ZERO(), ZERO(), ONE()}; -} - -template -void applyGeneratorRX(SVType &sv, const std::vector &wires, - const bool adj = false) { - sv.applyPauliX(wires, adj); -} - -template -void applyGeneratorRY(SVType &sv, const std::vector &wires, - const bool adj = false) { - sv.applyPauliY(wires, adj); -} - -template -void applyGeneratorRZ(SVType &sv, const std::vector &wires, - const bool adj = false) { - sv.applyPauliZ(wires, adj); -} - -template -void applyGeneratorPhaseShift(SVType &sv, const std::vector &wires, - const bool adj = false) { - sv.applyGeneratorPhaseShift(wires, adj); -} - -template -void applyGeneratorCRX(SVType &sv, const std::vector &wires, - [[maybe_unused]] const bool adj = false) { - sv.applyGeneratorCRX(wires, adj); -} - -template -void applyGeneratorCRY(SVType &sv, const std::vector &wires, - [[maybe_unused]] const bool adj = false) { - sv.applyGeneratorCRY(wires, adj); -} - -template -void applyGeneratorCRZ(SVType &sv, const std::vector &wires, - [[maybe_unused]] const bool adj = false) { - sv.applyGeneratorCRZ(wires, adj); -} - -template -void applyGeneratorControlledPhaseShift(SVType &sv, - const std::vector &wires, - const bool adj = false) { - sv.applyGeneratorControlledPhaseShift(wires, adj); -} - } // namespace /// @endcond namespace Pennylane::Algorithms { - /** * @brief Represent the logic for the adjoint Jacobian method of * arXiV:2009.02823 @@ -112,29 +52,6 @@ template class AdjointJacobian { const std::vector &, const bool); // function pointer type - // Holds the mapping from gate labels to associated generator functions. - const std::unordered_map generator_map{ - {"RX", &::applyGeneratorRX>}, - {"RY", &::applyGeneratorRY>}, - {"RZ", &::applyGeneratorRZ>}, - {"PhaseShift", &::applyGeneratorPhaseShift>}, - {"CRX", &::applyGeneratorCRX>}, - {"CRY", &::applyGeneratorCRY>}, - {"CRZ", &::applyGeneratorCRZ>}, - {"ControlledPhaseShift", - &::applyGeneratorControlledPhaseShift>}}; - - // Holds the mappings from gate labels to associated generator coefficients. - const std::unordered_map scaling_factors{ - {"RX", -static_cast(0.5)}, - {"RY", -static_cast(0.5)}, - {"RZ", -static_cast(0.5)}, - {"PhaseShift", static_cast(1)}, - {"CRX", -static_cast(0.5)}, - {"CRY", -static_cast(0.5)}, - {"CRZ", -static_cast(0.5)}, - {"ControlledPhaseShift", static_cast(1)}}; - /** * @brief Utility method to update the Jacobian at a given index by * calculating the overlap between two given states. @@ -365,12 +282,12 @@ template class AdjointJacobian { * @param adj Indicate whether to take the adjoint of the operation. * @return T Generator scaling coefficient. */ - inline auto applyGenerator(StateVectorManaged &sv, + template + inline auto applyGenerator(StateVectorBase &sv, const std::string &op_name, const std::vector &wires, const bool adj) -> T { - generator_map.at(op_name)(sv, wires, adj); - return scaling_factors.at(op_name); + return sv.applyGenerator(op_name, wires, adj); } public: @@ -485,5 +402,4 @@ template class AdjointJacobian { jac = Transpose(jac, jd.getNumParams(), num_observables); } }; // class AdjointJacobian - } // namespace Pennylane::Algorithms diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 3224b90d5c..140b46b0e7 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -5,8 +5,10 @@ set(ALGORITHM_FILES AdjointDiff.hpp AdjointDiff.cpp JacobianProd.hpp JacobianPro add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) target_link_libraries(lightning_algorithms PRIVATE lightning_compile_options - lightning_external_libs) + lightning_external_libs + lightning_gates + lightning_simulator + lightning_utils) -target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) +target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/pennylane_lightning/src/algorithms/JacobianTape.hpp b/pennylane_lightning/src/algorithms/JacobianTape.hpp index dbd2036cc1..ca7d0ac6f7 100644 --- a/pennylane_lightning/src/algorithms/JacobianTape.hpp +++ b/pennylane_lightning/src/algorithms/JacobianTape.hpp @@ -40,7 +40,7 @@ template class ObsDatum { * * @param obs_name Name of each operation of the observable. Tensor product * observables have more than one operation. - * @param obs_params Parameters for a given obserable operation ({} if + * @param obs_params Parameters for a given observable operation ({} if * optional). * @param obs_wires Wires upon which to apply operation. Each observable * operation will be a separate nested list. @@ -278,10 +278,10 @@ template class JacobianData { * calculation. */ JacobianData(size_t num_params, size_t num_elem, std::complex *ps, - const std::vector> &obs, const OpsData &ops, + std::vector> obs, OpsData ops, std::vector trainP) : num_parameters(num_params), num_elements(num_elem), psi(ps), - observables(obs), operations(ops), + observables(std::move(obs)), operations(std::move(ops)), trainableParams(std::move(trainP)) {} /** @@ -359,4 +359,4 @@ template class JacobianData { return !trainableParams.empty(); } }; -} // namespace Pennylane::Algorithms \ No newline at end of file +} // namespace Pennylane::Algorithms diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 2eaa9f5a6c..c7747c6016 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -17,14 +17,17 @@ */ #include "Bindings.hpp" +#include "GateUtil.hpp" +#include "SelectKernel.hpp" + #include "pybind11/pybind11.h" /// @cond DEV namespace { -using Pennylane::StateVectorRaw; -using Pennylane::Internal::implementedGatesForKernel; - using namespace Pennylane::Algorithms; +using namespace Pennylane::Gates; + +using Pennylane::StateVectorRaw; using std::complex; using std::string; @@ -352,20 +355,19 @@ PYBIND11_MODULE(lightning_qubit_ops, // NOLINT: No control over Pybind internals m.def("generateBitPatterns", py::overload_cast &, size_t>( - &IndicesUtil::generateBitPatterns), + &Gates::generateBitPatterns), "Get statevector indices for gate application"); m.def("getIndicesAfterExclusion", py::overload_cast &, size_t>( - &IndicesUtil::getIndicesAfterExclusion), + &Gates::getIndicesAfterExclusion), "Get statevector indices for gate application"); /* Add EXPORTED_KERNELS */ std::vector> exported_kernel_ops; - for (const auto kernel : Constant::kernels_to_pyexport) { - const auto kernel_name = - std::string(lookup(Constant::available_kernels, kernel)); - const auto implemented_gates = implementedGatesForKernel(kernel); + for (const auto kernel : kernels_to_pyexport) { + const auto kernel_name = lookup(kernel_id_name_pairs, kernel); + const auto implemented_gates = implementedGatesForKernel(kernel); for (const auto gate_op : implemented_gates) { const auto gate_name = std::string(lookup(Constant::gate_names, gate_op)); @@ -378,9 +380,8 @@ PYBIND11_MODULE(lightning_qubit_ops, // NOLINT: No control over Pybind internals /* Add DEFAULT_KERNEL_FOR_OPS */ std::map default_kernel_ops_map; for (const auto &[gate_op, name] : Constant::gate_names) { - const auto kernel = lookup(Constant::default_kernel_for_ops, gate_op); - const auto kernel_name = - std::string(lookup(Constant::available_kernels, kernel)); + const auto kernel = lookup(Constant::default_kernel_for_gates, gate_op); + const auto kernel_name = Util::lookup(kernel_id_name_pairs, kernel); default_kernel_ops_map.emplace(std::string(name), kernel_name); } m.attr("DEFAULT_KERNEL_FOR_OPS") = py::cast(default_kernel_ops_map); diff --git a/pennylane_lightning/src/bindings/Bindings.hpp b/pennylane_lightning/src/bindings/Bindings.hpp index e179459673..5d79774ffd 100644 --- a/pennylane_lightning/src/bindings/Bindings.hpp +++ b/pennylane_lightning/src/bindings/Bindings.hpp @@ -18,9 +18,9 @@ */ #pragma once #include "AdjointDiff.hpp" -#include "IndicesUtil.hpp" #include "JacobianProd.hpp" #include "Measures.hpp" +#include "OpToMemberFuncPtr.hpp" #include "StateVectorRaw.hpp" #include "pybind11/complex.h" @@ -88,40 +88,31 @@ void apply(pybind11::array_t> &stateNumpyArray, * @brief Return a specific lambda function for the given kernel and gate * operation * - * We do not expect template paramters kernel and gate_op can be function - * paramters as we want the lambda function to be a stateless. + * We do not expect template parameters kernel and gate_op can be function + * parameters as we want the lambda function to be a stateless. * * @tparam PrecisionT Floating point precision of underlying statevector data * @tparam ParamT Floating point type of gate parameters * @tparam kernel Kernel to register * @tparam gate_op Gate operation */ -template +template constexpr auto getLambdaForKernelGateOp() { - using Pennylane::Internal::callGateOps; - using Pennylane::Internal::GateOpsFuncPtrPairs; namespace py = pybind11; + using namespace Pennylane::Gates; + using GateImplementation = SelectKernel; - static_assert( - array_has_elt(SelectGateOps::implemented_gates, - gate_op), - "The operator to register must be implemented."); + static_assert(array_has_elt(GateImplementation::implemented_gates, gate_op), + "The operator to register must be implemented."); - if constexpr (gate_op != GateOperations::Matrix) { + if constexpr (gate_op != GateOperation::Matrix) { return [](StateVectorRaw &st, const std::vector &wires, bool inverse, const std::vector ¶ms) { - constexpr size_t num_params = - static_lookup(Constant::gate_num_params); - constexpr auto func_ptr = static_lookup( - GateOpsFuncPtrPairs::value); - // The line below is added as static_lookup cannot raise - // exception in GCC 9 - static_assert(func_ptr != nullptr, - "Function pointer for the gate is not " - "included in GateOpsFuncPtrPairs."); + constexpr auto func_ptr = + GateOpToMemberFuncPtr::value; callGateOps(func_ptr, st.getData(), st.getNumQubits(), wires, inverse, params); }; @@ -139,12 +130,13 @@ constexpr auto getLambdaForKernelGateOp() { }; /// @cond DEV -template +template constexpr auto getGateOpLambdaPairsIter() { - if constexpr (gate_idx < - SelectGateOps::implemented_gates.size()) { + using Pennylane::Gates::SelectKernel; + if constexpr (gate_idx < SelectKernel::implemented_gates.size()) { constexpr auto gate_op = - SelectGateOps::implemented_gates[gate_idx]; + SelectKernel::implemented_gates[gate_idx]; return prepend_to_tuple( std::pair{gate_op, getLambdaForKernelGateOp()}, @@ -163,7 +155,7 @@ constexpr auto getGateOpLambdaPairsIter() { * @tparam ParamT Floating point type of gate parameters * @tparam kernel Kernel to register */ -template +template constexpr auto getGateOpLambdaPairs() { return getGateOpLambdaPairsIter(); } @@ -177,18 +169,19 @@ constexpr auto getGateOpLambdaPairs() { * @tparam Kernel Kernel to register * @tparam PyClass Pybind11 class type */ -template +template void registerImplementedGatesForKernel(PyClass &pyclass) { - const auto kernel_name = - std::string(lookup(Constant::available_kernels, kernel)); + using namespace Pennylane::Gates; + const auto kernel_name = std::string(SelectKernel::name); constexpr auto gate_op_lambda_pairs = getGateOpLambdaPairs(); auto registerToPyclass = - [&pyclass, &kernel_name](auto &&gate_op_lambda_pair) -> GateOperations { + [&pyclass, &kernel_name](auto &&gate_op_lambda_pair) -> GateOperation { const auto &[gate_op, func] = gate_op_lambda_pair; - if (gate_op == GateOperations::Matrix) { + if (gate_op == GateOperation::Matrix) { const std::string name = "applyMatrix_" + kernel_name; const std::string doc = "Apply a given matrix to wires."; pyclass.def(name.c_str(), func, doc.c_str()); @@ -213,8 +206,8 @@ void registerImplementedGatesForKernel(PyClass &pyclass) { /// @cond DEV template void registerKernelsToPyexportIter(PyClass &pyclass) { - if constexpr (kernel_idx < Constant::kernels_to_pyexport.size()) { - constexpr auto kernel = Constant::kernels_to_pyexport[kernel_idx]; + if constexpr (kernel_idx < kernels_to_pyexport.size()) { + constexpr auto kernel = kernels_to_pyexport[kernel_idx]; registerImplementedGatesForKernel(pyclass); registerKernelsToPyexportIter( pyclass); diff --git a/pennylane_lightning/src/examples/CMakeLists.txt b/pennylane_lightning/src/examples/CMakeLists.txt index 701d307a05..21bbe56c63 100644 --- a/pennylane_lightning/src/examples/CMakeLists.txt +++ b/pennylane_lightning/src/examples/CMakeLists.txt @@ -14,11 +14,17 @@ project("gate_benchmark" # lightning_compile_options # lightning_external_libs) +add_library(lightning_examples INTERFACE) +target_link_libraries(lightning_examples INTERFACE lightning_compile_options + lightning_external_libs + lightning_gates + lightning_simulator + lightning_utils) + add_executable(gate_benchmark_oplist gate_benchmark_oplist.cpp) -target_link_libraries(gate_benchmark_oplist PRIVATE lightning_utils - lightning_simulator - lightning_compile_options - lightning_external_libs) +target_link_libraries(gate_benchmark_oplist PRIVATE lightning_examples) +add_executable(benchmark_multi_rz benchmark_multi_rz.cpp) +target_link_libraries(benchmark_multi_rz PRIVATE lightning_examples) configure_file("compiler_info.in" "compiler_info.txt") diff --git a/pennylane_lightning/src/examples/ExampleUtil.hpp b/pennylane_lightning/src/examples/ExampleUtil.hpp new file mode 100644 index 0000000000..313b8e3d82 --- /dev/null +++ b/pennylane_lightning/src/examples/ExampleUtil.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include + +template +std::vector generateDistinctWires(RandomEngine &re, size_t num_qubits, + size_t num_wires) { + std::vector v(num_qubits, 0); + std::iota(v.begin(), v.end(), 0); + shuffle(v.begin(), v.end(), re); + return {v.begin(), v.begin() + num_wires}; +} + +template +std::vector generateNeighboringWires(RandomEngine &re, + size_t num_qubits, + size_t num_wires) { + std::vector v; + v.reserve(num_wires); + std::uniform_int_distribution idist(0, num_qubits - 1); + size_t start_idx = idist(re); + for (size_t k = 0; k < num_wires; k++) { + v.emplace_back((start_idx + k) % num_qubits); + } + return v; +} diff --git a/pennylane_lightning/src/examples/benchmark_multi_rz.cpp b/pennylane_lightning/src/examples/benchmark_multi_rz.cpp new file mode 100644 index 0000000000..180e93ba9a --- /dev/null +++ b/pennylane_lightning/src/examples/benchmark_multi_rz.cpp @@ -0,0 +1,77 @@ +#include "ExampleUtil.hpp" +#include "StateVectorManaged.hpp" + +#include +#include +#include +#include + +using namespace Pennylane; +using namespace Pennylane::Gates; + +constexpr uint32_t seed = 1337; + +int main(int argc, char *argv[]) { + using TestType = double; + + if (argc != 5) { // NOLINT(readability-magic-numbers) + std::cout << "Usage: " << argv[0] + << " num_gate_reps num_qubits num_wires kernel" << std::endl; + return 1; + } + + size_t num_gate_reps; + size_t num_qubits; + size_t num_wires; + + try { + num_gate_reps = std::stoi(argv[1]); + num_qubits = std::stoi(argv[2]); + num_wires = std::stoi(argv[3]); + } catch (std::exception &e) { + std::cerr << "Arguments must be integers." << std::endl; + return 1; + } + + std::string_view kernel_name = argv[4]; + KernelType kernel = string_to_kernel(kernel_name); + if (kernel == KernelType::None) { + std::cerr << "Kernel " << kernel_name << " is unknown." << std::endl; + return 1; + } + + std::mt19937 re{seed}; // NOLINT(readability-magic-number) + std::uniform_real_distribution param_dist(-M_PI, M_PI); + + std::vector> wires; + std::vector params; + + wires.reserve(num_gate_reps); + params.reserve(num_gate_reps); + + for (size_t gate_rep = 0; gate_rep < num_gate_reps; gate_rep++) { + wires.emplace_back(generateDistinctWires(re, num_qubits, num_wires)); + params.emplace_back(param_dist(re)); + } + + StateVectorManaged sv{num_qubits}; + + std::chrono::time_point t_start = + std::chrono::high_resolution_clock::now(); + + for (size_t gate_rep = 0; gate_rep < num_gate_reps; gate_rep++) { + sv.applyOperation(kernel, "MultiRZ", wires[gate_rep], false, + {params[gate_rep]}); + } + + std::chrono::time_point t_end = + std::chrono::high_resolution_clock::now(); + const auto walltime = + 0.001 * ((std::chrono::duration_cast( + t_end - t_start)) + .count()); + std::cout << num_qubits << ", " + << walltime / static_cast(num_gate_reps) << std::endl; + + return 0; +} diff --git a/pennylane_lightning/src/examples/gate_benchmark_oplist.cpp b/pennylane_lightning/src/examples/gate_benchmark_oplist.cpp index f64f567ad3..242dc9d97d 100644 --- a/pennylane_lightning/src/examples/gate_benchmark_oplist.cpp +++ b/pennylane_lightning/src/examples/gate_benchmark_oplist.cpp @@ -7,9 +7,13 @@ #include #include +#include "Constant.hpp" +#include "ExampleUtil.hpp" #include "StateVectorManaged.hpp" using namespace Pennylane; +using namespace Pennylane::Gates; +using namespace Pennylane::Util; std::string_view strip(std::string_view str) { auto start = str.find_first_not_of(" \t"); @@ -22,33 +26,20 @@ struct GateDesc { size_t n_params; // number of parameters the gate requires }; -std::vector> +std::vector> parseGateLists(std::string_view arg) { - const static std::map available_gates_wires = { - /* Single-qubit gates */ - {"PauliX", {1, 0}}, - {"PauliY", {1, 0}}, - {"PauliZ", {1, 0}}, - {"Hadamard", {1, 0}}, - {"S", {1, 0}}, - {"T", {1, 0}}, - {"RX", {1, 1}}, - {"RY", {1, 1}}, - {"RZ", {1, 1}}, - {"Rot", {1, 3}}, - {"PhaseShift", {1, 1}}, - /* Two-qubit gates */ - {"CNOT", {2, 0}}, - {"CZ", {2, 0}}, - {"SWAP", {2, 0}}, - {"ControlledPhaseShift", {2, 1}}, - {"CRX", {2, 1}}, - {"CRY", {2, 1}}, - {"CRZ", {2, 1}}, - {"CRot", {2, 3}}, - /* Three-qubit gates */ - {"Toffoli", {3, 0}}, - {"CSWAP", {3, 0}}}; + namespace Constant = Gates::Constant; + std::map available_gates_wires; + + for (const auto &[gate_op, gate_name] : Constant::gate_names) { + if (!array_has_elt(Constant::multi_qubit_gates, gate_op)) { + // We do not support multi qubit gates yet + size_t n_wires = Util::lookup(Constant::gate_wires, gate_op); + size_t n_params = Util::lookup(Constant::gate_num_params, gate_op); + available_gates_wires.emplace(gate_name, + GateDesc{n_wires, n_params}); + } + } if (arg.empty()) { /* @@ -58,7 +49,7 @@ parseGateLists(std::string_view arg) { return {}; } - std::vector> ops; + std::vector> ops; if (auto pos = arg.find_first_of('['); pos != std::string_view::npos) { // arg is a list "[...]" @@ -89,29 +80,6 @@ parseGateLists(std::string_view arg) { } return ops; } - -template -std::vector generateDistinctWires(RandomEngine &re, size_t num_qubits, - size_t num_wires) { - std::vector v(num_qubits, 0); - std::iota(v.begin(), v.end(), 0); - shuffle(v.begin(), v.end(), re); - return {v.begin(), v.begin() + num_wires}; -} - -template -std::vector generateNeighboringWires(RandomEngine &re, - size_t num_qubits, - size_t num_wires) { - std::vector v; - v.reserve(num_wires); - std::uniform_int_distribution idist(0, num_qubits - 1); - size_t start_idx = idist(re); - for (size_t k = 0; k < num_wires; k++) { - v.emplace_back((start_idx + k) % num_qubits); - } - return v; -} /** * @brief Benchmark Pennylane-Lightning for a given gate set * @@ -152,14 +120,14 @@ int main(int argc, char *argv[]) { num_gate_reps = std::stoi(argv[1]); num_qubits = std::stoi(argv[2]); } catch (std::exception &e) { - std::cerr << "Arguements num_gate_reps and num_qubits must be integers." + std::cerr << "Arguments num_gate_reps and num_qubits must be integers." << std::endl; return -1; } std::string_view kernel_name = argv[3]; KernelType kernel = string_to_kernel(kernel_name); - if (kernel == KernelType::Unknown) { + if (kernel == KernelType::None) { std::cerr << "Kernel " << kernel_name << " is unknown." << std::endl; return 1; } @@ -174,7 +142,7 @@ int main(int argc, char *argv[]) { op_list_s = ss.str(); } - std::vector> op_list; + std::vector> op_list; try { op_list = parseGateLists(op_list_s); } catch (std::exception &e) { @@ -204,7 +172,7 @@ int main(int argc, char *argv[]) { auto gen_param = [¶m_dist, &re]() { return param_dist(re); }; for (uint32_t k = 0; k < num_gate_reps; k++) { - auto [op_name, gate_desc] = op_list[gate_dist(re)]; + const auto &[op_name, gate_desc] = op_list[gate_dist(re)]; std::vector gate_params(gate_desc.n_params, 0.0); std::generate(gate_params.begin(), gate_params.end(), gen_param); @@ -218,7 +186,7 @@ int main(int argc, char *argv[]) { random_gate_parameters.emplace_back(std::move(gate_params)); } - // Log genereated sequence if LOG is turned on + // Log generated sequence if LOG is turned on const char *env_p = std::getenv("LOG"); try { if (env_p != nullptr && std::stoi(env_p) != 0) { diff --git a/pennylane_lightning/src/examples/plot_gate_benchmark.py b/pennylane_lightning/src/examples/plot_gate_benchmark.py index 3f3f19f532..0d650071ec 100755 --- a/pennylane_lightning/src/examples/plot_gate_benchmark.py +++ b/pennylane_lightning/src/examples/plot_gate_benchmark.py @@ -8,6 +8,8 @@ import re +plt.rc("font", family="sans-serif") + def parse_result_csv(filepath): n_qubits = [] diff --git a/pennylane_lightning/src/examples/run_gate_benchmark.sh b/pennylane_lightning/src/examples/run_gate_benchmark.sh index a3d4d6ca82..3e310e0f88 100755 --- a/pennylane_lightning/src/examples/run_gate_benchmark.sh +++ b/pennylane_lightning/src/examples/run_gate_benchmark.sh @@ -2,8 +2,8 @@ currdir=$(pwd) -if [ "$#" -ne 2 ]; then - echo "Usage: $0 Kernel Gate" +if [ "$#" -lt 2 ]; then + echo "Usage: $0 Kernel Gate [Number of wires (for MultiRZ)]" exit 1 fi @@ -17,24 +17,39 @@ gate="$2" compiler_info=$( $path_to_csv - -# Generate data -for ((num_qubits=$min_num_qubits; num_qubits<$max_num_qubits+1; num_qubits+=$num_qubits_increment)); do - echo "Gate repetition=$num_gate_reps, num_qubits=$num_qubits, kernel=$kernel, gate=$gate" - $path_to_binary ${num_gate_reps} ${num_qubits} ${kernel} ${gate} >> $path_to_csv -done - -# Plot results -# python_path=$(which python3) -# echo "Plotting results" -# $python_path gate_benchmark_plotter.py $path_to_csv $path_to_compiler_file +if [[ "$gate" != "MultiRZ" ]]; then + # Creating data file + binary_name="./gate_benchmark_oplist" + path_to_binary="$currdir/$binary_name" + + resdir="$currdir/res_${compiler_info}" + mkdir -p $resdir + data_file_name="benchmark_${kernel}_${gate}.csv" + path_to_csv="$resdir/$data_file_name" + echo "Creating $path_to_csv" + echo "Num Qubits, Time (milliseconds)" > $path_to_csv + + # Generate data + for ((num_qubits=$min_num_qubits; num_qubits<$max_num_qubits+1; num_qubits+=$num_qubits_increment)); do + echo "Gate repetition=$num_gate_reps, num_qubits=$num_qubits, kernel=$kernel, gate=$gate" + $path_to_binary ${num_gate_reps} ${num_qubits} ${kernel} ${gate} >> $path_to_csv + done +else + num_wires="$3" + # Creating data file + binary_name="./benchmark_multi_rz" + path_to_binary="$currdir/$binary_name" + + resdir="$currdir/res_${compiler_info}" + mkdir -p $resdir + data_file_name="benchmark_${kernel}_${gate}_${num_wires}.csv" + path_to_csv="$resdir/$data_file_name" + echo "Creating $path_to_csv" + echo "Num Qubits, Time (milliseconds)" > $path_to_csv + + # Generate data + for ((num_qubits=$min_num_qubits; num_qubits<$max_num_qubits+1; num_qubits+=$num_qubits_increment)); do + echo "Gate repetition=$num_gate_reps, num_qubits=$num_qubits, kernel=$kernel, gate=$gate" + $path_to_binary ${num_gate_reps} ${num_qubits} ${num_wires} ${kernel} >> $path_to_csv + done +fi diff --git a/pennylane_lightning/src/gates/AvailableKernels.hpp b/pennylane_lightning/src/gates/AvailableKernels.hpp new file mode 100644 index 0000000000..af06fa536a --- /dev/null +++ b/pennylane_lightning/src/gates/AvailableKernels.hpp @@ -0,0 +1,37 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file AvailableKernels.hpp + * Defines available kernels. Be careful when including this file as + * it also includes all gate implementations. + */ +#pragma once + +#include "GateImplementationsLM.hpp" +#include "GateImplementationsPI.hpp" +#include "TypeList.hpp" + +namespace Pennylane { +/** + * @brief List of all available kernels (gate implementations). + * + * If you want to add another gate implementation, just add it to this type + * list. + * @rst + * See :ref:`lightning_add_gate_implementation` for details. + * @endrst + */ +using AvailableKernels = + Util::TypeList; +} // namespace Pennylane diff --git a/pennylane_lightning/src/gates/CMakeLists.txt b/pennylane_lightning/src/gates/CMakeLists.txt new file mode 100644 index 0000000000..3bc00ff329 --- /dev/null +++ b/pennylane_lightning/src/gates/CMakeLists.txt @@ -0,0 +1,12 @@ +project(lightning_gates) +set(CMAKE_CXX_STANDARD 17) + +set(SIMULATOR_FILES GateUtil.cpp CACHE INTERNAL "" FORCE) + +add_library(lightning_gates STATIC ${SIMULATOR_FILES}) +target_link_libraries(lightning_gates PRIVATE lightning_compile_options + lightning_external_libs + lightning_utils) +target_include_directories(lightning_gates PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_property(TARGET lightning_gates PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/pennylane_lightning/src/gates/Constant.hpp b/pennylane_lightning/src/gates/Constant.hpp new file mode 100644 index 0000000000..38d086875d --- /dev/null +++ b/pennylane_lightning/src/gates/Constant.hpp @@ -0,0 +1,258 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file Constant.hpp + * Defines all constants for statevector + */ +#pragma once + +#include "GateOperation.hpp" +#include "KernelType.hpp" +#include "TypeList.hpp" + +namespace Pennylane::Gates::Constant { +/** + * @brief List of multi-qubit gates + */ +[[maybe_unused]] constexpr std::array multi_qubit_gates{GateOperation::MultiRZ, + GateOperation::Matrix}; +/** + * @brief List of multi-qubit generators + */ +[[maybe_unused]] constexpr std::array multi_qubit_generators{ + GeneratorOperation::MultiRZ, +}; + +/** + * @brief Gate names + */ +[[maybe_unused]] constexpr std::array gate_names = { + std::pair{GateOperation::PauliX, "PauliX"}, + std::pair{GateOperation::PauliY, "PauliY"}, + std::pair{GateOperation::PauliZ, "PauliZ"}, + std::pair{GateOperation::Hadamard, + "Hadamard"}, + std::pair{GateOperation::S, "S"}, + std::pair{GateOperation::T, "T"}, + std::pair{GateOperation::PhaseShift, + "PhaseShift"}, + std::pair{GateOperation::RX, "RX"}, + std::pair{GateOperation::RY, "RY"}, + std::pair{GateOperation::RZ, "RZ"}, + std::pair{GateOperation::Rot, "Rot"}, + std::pair{GateOperation::CNOT, "CNOT"}, + std::pair{GateOperation::CY, "CY"}, + std::pair{GateOperation::CZ, "CZ"}, + std::pair{GateOperation::IsingXX, + "IsingXX"}, + std::pair{GateOperation::IsingYY, + "IsingYY"}, + std::pair{GateOperation::IsingZZ, + "IsingZZ"}, + std::pair{GateOperation::SWAP, "SWAP"}, + std::pair{ + GateOperation::ControlledPhaseShift, "ControlledPhaseShift"}, + std::pair{GateOperation::CRX, "CRX"}, + std::pair{GateOperation::CRY, "CRY"}, + std::pair{GateOperation::CRZ, "CRZ"}, + std::pair{GateOperation::CRot, "CRot"}, + std::pair{GateOperation::Toffoli, + "Toffoli"}, + std::pair{GateOperation::CSWAP, "CSWAP"}, + std::pair{GateOperation::MultiRZ, + "MultiRZ"}, + std::pair{GateOperation::Matrix, "Matrix"}, +}; +/** + * @brief Generator names. + * + * Note that a name of generators must be "Generator" + + * the name of the corresponding gate. + */ +[[maybe_unused]] constexpr std::array generator_names = { + std::pair{ + GeneratorOperation::PhaseShift, "GeneratorPhaseShift"}, + std::pair{GeneratorOperation::RX, + "GeneratorRX"}, + std::pair{GeneratorOperation::RY, + "GeneratorRY"}, + std::pair{GeneratorOperation::RZ, + "GeneratorRZ"}, + std::pair{GeneratorOperation::CRX, + "GeneratorCRX"}, + std::pair{GeneratorOperation::CRY, + "GeneratorCRY"}, + std::pair{GeneratorOperation::CRZ, + "GeneratorCRZ"}, + std::pair{GeneratorOperation::IsingXX, + "GeneratorIsingXX"}, + std::pair{GeneratorOperation::IsingYY, + "GeneratorIsingYY"}, + std::pair{GeneratorOperation::IsingZZ, + "GeneratorIsingZZ"}, + std::pair{ + GeneratorOperation::ControlledPhaseShift, + "GeneratorControlledPhaseShift"}, + std::pair{GeneratorOperation::MultiRZ, + "GeneratorMultiRZ"}, +}; + +/** + * @brief Number of wires for gates besides multi-qubit gates + */ +[[maybe_unused]] constexpr std::array gate_wires = { + std::pair{GateOperation::PauliX, 1}, + std::pair{GateOperation::PauliY, 1}, + std::pair{GateOperation::PauliZ, 1}, + std::pair{GateOperation::Hadamard, 1}, + std::pair{GateOperation::S, 1}, + std::pair{GateOperation::T, 1}, + std::pair{GateOperation::PhaseShift, 1}, + std::pair{GateOperation::RX, 1}, + std::pair{GateOperation::RY, 1}, + std::pair{GateOperation::RZ, 1}, + std::pair{GateOperation::Rot, 1}, + std::pair{GateOperation::CNOT, 2}, + std::pair{GateOperation::CY, 2}, + std::pair{GateOperation::CZ, 2}, + std::pair{GateOperation::SWAP, 2}, + std::pair{GateOperation::IsingXX, 2}, + std::pair{GateOperation::IsingYY, 2}, + std::pair{GateOperation::IsingZZ, 2}, + std::pair{GateOperation::ControlledPhaseShift, 2}, + std::pair{GateOperation::CRX, 2}, + std::pair{GateOperation::CRY, 2}, + std::pair{GateOperation::CRZ, 2}, + std::pair{GateOperation::CRot, 2}, + std::pair{GateOperation::Toffoli, 3}, + std::pair{GateOperation::CSWAP, 3}, +}; + +/** + * @brief Number of wires for generators besides multi-qubit gates + */ +[[maybe_unused]] constexpr std::array generator_wires = { + std::pair{GeneratorOperation::PhaseShift, + 1}, + std::pair{GeneratorOperation::RX, 1}, + std::pair{GeneratorOperation::RY, 1}, + std::pair{GeneratorOperation::RZ, 1}, + std::pair{GeneratorOperation::IsingXX, 2}, + std::pair{GeneratorOperation::IsingYY, 2}, + std::pair{GeneratorOperation::IsingZZ, 2}, + std::pair{GeneratorOperation::CRX, 2}, + std::pair{GeneratorOperation::CRY, 2}, + std::pair{GeneratorOperation::CRZ, 2}, + std::pair{ + GeneratorOperation::ControlledPhaseShift, 2}, +}; + +/** + * @brief Number of parameters for gates + */ +[[maybe_unused]] constexpr std::array gate_num_params = { + std::pair{GateOperation::PauliX, 0}, + std::pair{GateOperation::PauliY, 0}, + std::pair{GateOperation::PauliZ, 0}, + std::pair{GateOperation::Hadamard, 0}, + std::pair{GateOperation::S, 0}, + std::pair{GateOperation::T, 0}, + std::pair{GateOperation::PhaseShift, 1}, + std::pair{GateOperation::RX, 1}, + std::pair{GateOperation::RY, 1}, + std::pair{GateOperation::RZ, 1}, + std::pair{GateOperation::Rot, 3}, + std::pair{GateOperation::CNOT, 0}, + std::pair{GateOperation::CY, 0}, + std::pair{GateOperation::CZ, 0}, + std::pair{GateOperation::SWAP, 0}, + std::pair{GateOperation::IsingXX, 1}, + std::pair{GateOperation::IsingYY, 1}, + std::pair{GateOperation::IsingZZ, 1}, + std::pair{GateOperation::ControlledPhaseShift, 1}, + std::pair{GateOperation::CRX, 1}, + std::pair{GateOperation::CRY, 1}, + std::pair{GateOperation::CRZ, 1}, + std::pair{GateOperation::CRot, 3}, + std::pair{GateOperation::Toffoli, 0}, + std::pair{GateOperation::CSWAP, 0}, + std::pair{GateOperation::MultiRZ, 1}, +}; + +/** + * + * @brief Define which kernel to use for each gate operation. + * + * @rst + * Check + * `this repository + * `_ to see + * the benchmark results for each gate + * @endrst + * + * This value is used for: + * 1. StateVector apply##GATE_NAME methods. The kernel function is statically + * binded to the given kernel and cannot be modified. + * 2. Default kernel functions for DynamicDispatcher. The kernel function is + * dynamically binded and can be changed using DynamicDispatcher singleton + * class. + * 3. For the Python binding. + */ +[[maybe_unused]] constexpr std::array default_kernel_for_gates = { + std::pair{GateOperation::PauliX, KernelType::LM}, + std::pair{GateOperation::PauliY, KernelType::LM}, + std::pair{GateOperation::PauliZ, KernelType::LM}, + std::pair{GateOperation::Hadamard, KernelType::PI}, + std::pair{GateOperation::S, KernelType::LM}, + std::pair{GateOperation::T, KernelType::LM}, + std::pair{GateOperation::RX, KernelType::PI}, + std::pair{GateOperation::RY, KernelType::PI}, + std::pair{GateOperation::RZ, KernelType::LM}, + std::pair{GateOperation::PhaseShift, KernelType::LM}, + std::pair{GateOperation::Rot, KernelType::LM}, + std::pair{GateOperation::ControlledPhaseShift, KernelType::PI}, + std::pair{GateOperation::CNOT, KernelType::LM}, + std::pair{GateOperation::CY, KernelType::PI}, + std::pair{GateOperation::CZ, KernelType::LM}, + std::pair{GateOperation::SWAP, KernelType::LM}, + std::pair{GateOperation::IsingXX, KernelType::LM}, + std::pair{GateOperation::IsingYY, KernelType::LM}, + std::pair{GateOperation::IsingZZ, KernelType::LM}, + std::pair{GateOperation::CRX, KernelType::LM}, + std::pair{GateOperation::CRY, KernelType::LM}, + std::pair{GateOperation::CRZ, KernelType::LM}, + std::pair{GateOperation::CRot, KernelType::PI}, + std::pair{GateOperation::Toffoli, KernelType::PI}, + std::pair{GateOperation::CSWAP, KernelType::PI}, + std::pair{GateOperation::MultiRZ, KernelType::LM}, + std::pair{GateOperation::Matrix, KernelType::PI}, +}; +/** + * @brief Define which kernel to use for each generator operation. + */ +[[maybe_unused]] constexpr std::array default_kernel_for_generators = { + std::pair{GeneratorOperation::PhaseShift, KernelType::PI}, + std::pair{GeneratorOperation::RX, KernelType::LM}, + std::pair{GeneratorOperation::RY, KernelType::LM}, + std::pair{GeneratorOperation::RZ, KernelType::LM}, + std::pair{GeneratorOperation::IsingXX, KernelType::LM}, + std::pair{GeneratorOperation::IsingYY, KernelType::LM}, + std::pair{GeneratorOperation::IsingZZ, KernelType::LM}, + std::pair{GeneratorOperation::CRX, KernelType::PI}, + std::pair{GeneratorOperation::CRY, KernelType::PI}, + std::pair{GeneratorOperation::CRZ, KernelType::PI}, + std::pair{GeneratorOperation::ControlledPhaseShift, KernelType::PI}, + std::pair{GeneratorOperation::MultiRZ, KernelType::LM}, +}; +} // namespace Pennylane::Gates::Constant diff --git a/pennylane_lightning/src/gates/GateImplementationsLM.hpp b/pennylane_lightning/src/gates/GateImplementationsLM.hpp new file mode 100644 index 0000000000..9f227862b2 --- /dev/null +++ b/pennylane_lightning/src/gates/GateImplementationsLM.hpp @@ -0,0 +1,1197 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file + * Defines kernel functions with less memory (and fast) + */ +#pragma once + +#include "BitUtil.hpp" +#include "Error.hpp" +#include "GateOperation.hpp" +#include "Gates.hpp" +#include "KernelType.hpp" +#include "LinearAlgebra.hpp" +#include "PauliGenerator.hpp" + +#include +#include + +namespace Pennylane::Gates { +/** + * @brief A gate operation implementation with less memory. + * + * We use a bitwise operation to calculate the indices where the gate + * applies to on the fly. + * + * @tparam PrecisionT Floating point precision of underlying statevector data + */ +class GateImplementationsLM : public PauliGenerator { + public: + constexpr static KernelType kernel_id = KernelType::LM; + constexpr static std::string_view name = "LM"; + constexpr static uint32_t data_alignment_in_bytes = 1; + + constexpr static std::array implemented_gates = { + GateOperation::PauliX, GateOperation::PauliY, + GateOperation::PauliZ, GateOperation::Hadamard, + GateOperation::S, GateOperation::T, + GateOperation::RX, GateOperation::RY, + GateOperation::RZ, GateOperation::PhaseShift, + GateOperation::Rot, GateOperation::CY, + GateOperation::CZ, GateOperation::CNOT, + GateOperation::SWAP, GateOperation::ControlledPhaseShift, + GateOperation::CRX, GateOperation::CRY, + GateOperation::CRZ, GateOperation::IsingXX, + GateOperation::IsingYY, GateOperation::IsingZZ, + GateOperation::MultiRZ, GateOperation::Matrix}; + + constexpr static std::array implemented_generators = { + GeneratorOperation::RX, GeneratorOperation::RY, + GeneratorOperation::RZ, GeneratorOperation::PhaseShift, + GeneratorOperation::CRX, GeneratorOperation::CRY, + GeneratorOperation::CRZ, GeneratorOperation::IsingXX, + GeneratorOperation::IsingYY, GeneratorOperation::IsingZZ, + GeneratorOperation::MultiRZ, + }; + + private: + /* Alias utility functions */ + static constexpr auto fillLeadingOnes = Util::fillLeadingOnes; + static constexpr auto fillTrailingOnes = Util::fillTrailingOnes; + static constexpr auto bitswap = Util::bitswap; + + /** + * @brief Apply a single qubit gate to the statevector. + * + * @param arr Pointer to the statevector. + * @param num_qubits Number of qubits. + * @param matrix Perfect square matrix in row-major order. + * @param wire A wire the gate applies to. + * @param inverse Indicate whether inverse should be taken. + */ + template + static inline void + applySingleQubitOp(std::complex *arr, size_t num_qubits, + const std::complex *matrix, size_t wire, + bool inverse = false) { + const size_t rev_wire = num_qubits - wire - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + if (inverse) { + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = + ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + const std::complex v0 = arr[i0]; + const std::complex v1 = arr[i1]; + arr[i0] = std::conj(matrix[0B00]) * v0 + + std::conj(matrix[0B10]) * + v1; // NOLINT(readability-magic-numbers) + arr[i1] = std::conj(matrix[0B01]) * v0 + + std::conj(matrix[0B11]) * + v1; // NOLINT(readability-magic-numbers) + } + } else { + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = + ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + const std::complex v0 = arr[i0]; + const std::complex v1 = arr[i1]; + arr[i0] = + matrix[0B00] * v0 + + matrix[0B01] * v1; // NOLINT(readability-magic-numbers) + arr[i1] = + matrix[0B10] * v0 + + matrix[0B11] * v1; // NOLINT(readability-magic-numbers) + } + } + } + /** + * @brief Apply a two qubit gate to the statevector. + * + * @param arr Pointer to the statevector. + * @param num_qubits Number of qubits. + * @param matrix Perfect square matrix in row-major order. + * @param wires Wires the gate applies to. + * @param inverse Indicate whether inverse should be taken. + */ + template + static inline void + applyTwoQubitOp(std::complex *arr, size_t num_qubits, + const std::complex *matrix, + const std::vector &wires, bool inverse = false) { + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + if (inverse) { + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | + (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const std::complex v00 = arr[i00]; + const std::complex v01 = arr[i01]; + const std::complex v10 = arr[i10]; + const std::complex v11 = arr[i11]; + + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i00] = std::conj(matrix[0b0000]) * v00 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b0100]) * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1000]) * v10 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1100]) * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i01] = std::conj(matrix[0b0001]) * v00 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b0101]) * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1001]) * v10 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1101]) * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i10] = std::conj(matrix[0b0010]) * v00 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b0110]) * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1010]) * v10 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1110]) * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i11] = std::conj(matrix[0b0011]) * v00 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b0111]) * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1011]) * v10 + + // NOLINTNEXTLINE(readability-magic-numbers) + std::conj(matrix[0b1111]) * v11; + } + } else { + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | + (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const std::complex v00 = arr[i00]; + const std::complex v01 = arr[i01]; + const std::complex v10 = arr[i10]; + const std::complex v11 = arr[i11]; + + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i00] = matrix[0b0000] * v00 + matrix[0b0001] * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + matrix[0b0010] * v10 + matrix[0b0011] * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i01] = matrix[0b0100] * v00 + matrix[0b0101] * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + matrix[0b0110] * v10 + matrix[0b0111] * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i10] = matrix[0b1000] * v00 + matrix[0b1001] * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + matrix[0b1010] * v10 + matrix[0b1011] * v11; + // NOLINTNEXTLINE(readability-magic-numbers) + arr[i11] = matrix[0b1100] * v00 + matrix[0b1101] * v01 + + // NOLINTNEXTLINE(readability-magic-numbers) + matrix[0b1110] * v10 + matrix[0b1111] * v11; + } + } + } + + public: + template + static void applyMatrix(std::complex *arr, size_t num_qubits, + const std::complex *matrix, + const std::vector &wires, bool inverse) { + assert(num_qubits >= wires.size()); + + switch (wires.size()) { + case 1: + applySingleQubitOp(arr, num_qubits, matrix, wires[0], inverse); + break; + case 2: + applyTwoQubitOp(arr, num_qubits, matrix, wires, inverse); + break; + default: { + size_t dim = 1U << wires.size(); + std::vector indices; + indices.resize(dim); + + for (size_t k = 0; k < Util::exp2(num_qubits); k += dim) { + std::vector> coeffs_in(dim); + std::vector> coeffs_out(dim); + + for (size_t inner_idx = 0; inner_idx < dim; inner_idx++) { + size_t idx = k | inner_idx; + size_t n_wires = wires.size(); + for (size_t pos = 0; pos < n_wires; pos++) { + bitswap(idx, n_wires - pos - 1, + num_qubits - wires[pos] - 1); + } + indices[inner_idx] = idx; + coeffs_in[inner_idx] = arr[idx]; + } + + Util::matrixVecProd( + matrix, coeffs_in.data(), coeffs_out.data(), dim, dim, + inverse ? Trans::Adjoint : Trans::NoTranspose); + + for (size_t inner_idx = 0; inner_idx < dim; inner_idx++) { + arr[indices[inner_idx]] = coeffs_out[inner_idx]; + } + } + } + } + } + + template + static void applyPauliX(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + using Util::fillLeadingOnes, Util::fillTrailingOnes; + + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + std::swap(arr[i0], arr[i1]); + } + } + + template + static void applyPauliY(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + const auto v0 = arr[i0]; + const auto v1 = arr[i1]; + arr[i0] = {std::imag(v1), -std::real(v1)}; + arr[i1] = {-std::imag(v0), std::real(v0)}; + } + } + + template + static void applyPauliZ(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + arr[i1] *= -1; + } + } + + template + static void applyHadamard(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + constexpr auto isqrt2 = Util::INVSQRT2(); + constexpr static std::array, 4> hadamardMat = { + isqrt2, isqrt2, isqrt2, -isqrt2}; + applySingleQubitOp(arr, num_qubits, hadamardMat.data(), wires[0]); + } + + template + static void applyS(std::complex *arr, const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + const std::complex shift = + (inverse) ? -Util::IMAG() : Util::IMAG(); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + arr[i1] *= shift; + } + } + + template + static void applyT(std::complex *arr, const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + const std::complex shift = + (inverse) ? std::conj(std::exp(std::complex( + 0, static_cast(M_PI / 4)))) + : std::exp(std::complex( + 0, static_cast(M_PI / 4))); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + arr[i1] *= shift; + } + } + + template + static void applyPhaseShift(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + using Util::fillLeadingOnes, Util::fillTrailingOnes; + + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + const std::complex s = + inverse ? std::exp(-std::complex(0, angle)) + : std::exp(std::complex(0, angle)); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + arr[i1] *= s; + } + } + + template + static void applyRX(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT js = + (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); + + const std::array, 4> RXMat = { + c, Util::IMAG() * js, Util::IMAG() * js, c}; + applySingleQubitOp(arr, num_qubits, RXMat.data(), wires[0]); + } + + template + static void applyRY(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT s = + (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); + + const std::array, 4> RYMat = {c, -s, s, c}; + applySingleQubitOp(arr, num_qubits, RYMat.data(), wires[0]); + } + + template + static void applyRZ(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t rev_wire_shift = (static_cast(1U) << rev_wire); + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + const size_t i1 = i0 | rev_wire_shift; + arr[i0] *= shifts[0]; + arr[i1] *= shifts[1]; + } + } + + template + static void applyRot(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT phi, ParamT theta, ParamT omega) { + assert(wires.size() == 1); + + const auto rotMat = + (inverse) ? Gates::getRot(-omega, -theta, -phi) + : Gates::getRot(phi, theta, omega); + + applySingleQubitOp(arr, num_qubits, rotMat.data(), wires[0]); + } + + /* Two-qubit gates */ + + template + static void + applyCNOT(std::complex *arr, const size_t num_qubits, + const std::vector &wires, [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + /* This is faster than iterate over all indices */ + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire1_shift | rev_wire0_shift; + + std::swap(arr[i10], arr[i11]); + } + } + + template + static void applyCY(std::complex *arr, const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + /* This is faster than iterate over all indices */ + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire1_shift | rev_wire0_shift; + std::complex v10 = arr[i10]; + arr[i10] = std::complex{std::imag(arr[i11]), + -std::real(arr[i11])}; + arr[i11] = + std::complex{-std::imag(v10), std::real(v10)}; + } + } + + template + static void applyCZ(std::complex *arr, const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + /* This is faster than iterate over all indices */ + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + arr[i11] *= -1; + } + } + + template + static void applySWAP(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + std::swap(arr[i10], arr[i01]); + } + } + + template + static void applyIsingXX(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + using ComplexPrecisionT = std::complex; + using std::imag; + using std::real; + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + const PrecisionT cr = std::cos(angle / 2); + const PrecisionT sj = + inverse ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const ComplexPrecisionT v00 = arr[i00]; + const ComplexPrecisionT v01 = arr[i01]; + const ComplexPrecisionT v10 = arr[i10]; + const ComplexPrecisionT v11 = arr[i11]; + + arr[i00] = ComplexPrecisionT{cr * real(v00) + sj * imag(v11), + cr * imag(v00) - sj * real(v11)}; + arr[i01] = ComplexPrecisionT{cr * real(v01) + sj * imag(v10), + cr * imag(v01) - sj * real(v10)}; + arr[i10] = ComplexPrecisionT{cr * real(v10) + sj * imag(v01), + cr * imag(v10) - sj * real(v01)}; + arr[i11] = ComplexPrecisionT{cr * real(v11) + sj * imag(v00), + cr * imag(v11) - sj * real(v00)}; + } + } + + template + static void applyIsingYY(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + using ComplexPrecisionT = std::complex; + using std::imag; + using std::real; + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + const PrecisionT cr = std::cos(angle / 2); + const PrecisionT sj = + inverse ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const ComplexPrecisionT v00 = arr[i00]; + const ComplexPrecisionT v01 = arr[i01]; + const ComplexPrecisionT v10 = arr[i10]; + const ComplexPrecisionT v11 = arr[i11]; + + arr[i00] = ComplexPrecisionT{cr * real(v00) - sj * imag(v11), + cr * imag(v00) + sj * real(v11)}; + arr[i01] = ComplexPrecisionT{cr * real(v01) + sj * imag(v10), + cr * imag(v01) - sj * real(v10)}; + arr[i10] = ComplexPrecisionT{cr * real(v10) + sj * imag(v01), + cr * imag(v10) - sj * real(v01)}; + arr[i11] = ComplexPrecisionT{cr * real(v11) - sj * imag(v00), + cr * imag(v11) + sj * real(v00)}; + } + } + + template + static void + applyIsingZZ(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, ParamT angle) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + arr[i00] *= shifts[0]; + arr[i01] *= shifts[1]; + arr[i10] *= shifts[1]; + arr[i11] *= shifts[0]; + } + } + + template + static void applyControlledPhaseShift(std::complex *arr, + const size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse, + ParamT angle) { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + const std::complex s = + inverse ? std::exp(-std::complex(0, angle)) + : std::exp(std::complex(0, angle)); + + /* This is faster than iterate over all indices */ + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i11 = i00 | rev_wire1_shift | rev_wire0_shift; + + arr[i11] *= s; + } + } + + template + static void applyCRX(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT js = + (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const std::complex v10 = arr[i10]; + const std::complex v11 = arr[i11]; + + arr[i10] = std::complex{ + c * std::real(v10) + js * std::imag(v11), + c * std::imag(v10) - js * std::real(v11)}; + arr[i11] = std::complex{ + c * std::real(v11) + js * std::imag(v10), + c * std::imag(v11) - js * std::real(v10)}; + } + } + + template + static void applyCRY(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT s = + (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const std::complex v10 = arr[i10]; + const std::complex v11 = arr[i11]; + + arr[i10] = c * v10 + -s * v11; + arr[i11] = s * v10 + c * v11; + } + } + + template + static void applyCRZ(std::complex *arr, const size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + arr[i10] *= shifts[0]; + arr[i11] *= shifts[1]; + } + } + + /* Multi-qubit gates */ + + template + static void applyMultiRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + size_t wires_parity = 0U; + for (size_t wire : wires) { + wires_parity |= + (static_cast(1U) << (num_qubits - wire - 1)); + } + + for (size_t k = 0; k < Util::exp2(num_qubits); k++) { + arr[k] *= shifts[Util::popcount(k & wires_parity) % 2]; + } + } + + /* Define generators */ + + template + [[nodiscard]] static auto + applyGeneratorPhaseShift(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 1); + const size_t rev_wire = num_qubits - wires[0] - 1; + const size_t wire_parity = fillTrailingOnes(rev_wire); + const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); + + for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { + const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); + arr[i0] = std::complex{0.0, 0.0}; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return static_cast(1.0); + } + + template + [[nodiscard]] static auto + applyGeneratorIsingXX(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + std::swap(arr[i00], arr[i11]); + std::swap(arr[i10], arr[i01]); + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorIsingYY(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + const auto v00 = arr[i00]; + arr[i00] = -arr[i11]; + arr[i11] = -v00; + std::swap(arr[i10], arr[i01]); + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + template + [[nodiscard]] static auto + applyGeneratorIsingZZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i10 = i00 | rev_wire1_shift; + + arr[i10] *= -1; + arr[i01] *= -1; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorCRX(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + using ComplexPrecisionT = std::complex; + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + arr[i00] = ComplexPrecisionT{}; + arr[i01] = ComplexPrecisionT{}; + + std::swap(arr[i10], arr[i11]); + } + + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorCRY(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + using ComplexPrecisionT = std::complex; + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i10 = i00 | rev_wire1_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + arr[i00] = ComplexPrecisionT{}; + arr[i01] = ComplexPrecisionT{}; + + const auto v0 = arr[i10]; + + arr[i10] = + ComplexPrecisionT{std::imag(arr[i11]), -std::real(arr[i11])}; + arr[i11] = ComplexPrecisionT{-std::imag(v0), std::real(v0)}; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorCRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + using ComplexPrecisionT = std::complex; + assert(wires.size() == 2); + + const size_t rev_wire0 = num_qubits - wires[1] - 1; + const size_t rev_wire1 = num_qubits - wires[0] - 1; // Control qubit + + const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; + const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; + + const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); + const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); + + const size_t parity_low = fillTrailingOnes(rev_wire_min); + const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); + const size_t parity_middle = + fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); + + for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { + const size_t i00 = ((k << 2U) & parity_high) | + ((k << 1U) & parity_middle) | (k & parity_low); + const size_t i01 = i00 | rev_wire0_shift; + const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; + + arr[i00] = ComplexPrecisionT{}; + arr[i01] = ComplexPrecisionT{}; + arr[i11] *= -1; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorMultiRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + auto wires_parity = static_cast(0U); + for (size_t wire : wires) { + wires_parity |= + (static_cast(1U) << (num_qubits - wire - 1)); + } + + for (size_t k = 0; k < Util::exp2(num_qubits); k++) { + arr[k] *= (2 * int(Util::popcount(k & wires_parity) % 2) - 1); + } + // NOLINTNEXTLINE(readability-magic-numbers) + return static_cast(0.5); + } +}; +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/gates/GateImplementationsPI.hpp b/pennylane_lightning/src/gates/GateImplementationsPI.hpp new file mode 100644 index 0000000000..69a5826efc --- /dev/null +++ b/pennylane_lightning/src/gates/GateImplementationsPI.hpp @@ -0,0 +1,747 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file + * Defines gate operations with precomputed indices + */ +#pragma once + +/// @cond DEV +// Required for compilation with MSVC +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES // for C++ +#endif +/// @endcond + +#include "BitUtil.hpp" +#include "GateOperation.hpp" +#include "GateUtil.hpp" +#include "Gates.hpp" +#include "KernelType.hpp" +#include "LinearAlgebra.hpp" +#include "PauliGenerator.hpp" + +#include +#include + +namespace Pennylane::Gates { +/** + * @brief Kernel functions for gate operations with precomputed indices + * + * For given wires, we first compute the indices the gate applies to and use + * the computed indices to apply the operation. + * + * @tparam PrecisionT Floating point precision of underlying statevector data. + * */ +class GateImplementationsPI : public PauliGenerator { + public: + constexpr static KernelType kernel_id = KernelType::PI; + constexpr static std::string_view name = "PI"; + constexpr static uint32_t data_alignment_in_bytes = 1; + + constexpr static std::array implemented_gates = { + GateOperation::PauliX, GateOperation::PauliY, + GateOperation::PauliZ, GateOperation::Hadamard, + GateOperation::S, GateOperation::T, + GateOperation::RX, GateOperation::RY, + GateOperation::RZ, GateOperation::PhaseShift, + GateOperation::Rot, GateOperation::ControlledPhaseShift, + GateOperation::CNOT, GateOperation::CY, + GateOperation::CZ, GateOperation::SWAP, + GateOperation::IsingXX, GateOperation::IsingYY, + GateOperation::IsingZZ, GateOperation::CRX, + GateOperation::CRY, GateOperation::CRZ, + GateOperation::CRot, GateOperation::Toffoli, + GateOperation::CSWAP, GateOperation::MultiRZ, + GateOperation::Matrix}; + constexpr static std::array implemented_generators = { + GeneratorOperation::RX, GeneratorOperation::RY, + GeneratorOperation::RZ, GeneratorOperation::PhaseShift, + GeneratorOperation::CRX, GeneratorOperation::CRY, + GeneratorOperation::CRZ, GeneratorOperation::ControlledPhaseShift}; + + /** + * @brief Apply a given matrix directly to the statevector. + * + * @param arr Pointer to the statevector. + * @param num_qubits Number of qubits. + * @param matrix Perfect square matrix in row-major order. + * @param wires Wires the gate applies to. + * @param inverse Indicate whether inverse should be taken. + */ + template + static void applyMatrix(std::complex *arr, size_t num_qubits, + const std::complex *matrix, + const std::vector &wires, bool inverse) { + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + std::vector> v(indices.size()); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + // Gather + size_t pos = 0; + for (const size_t &index : indices) { + v[pos] = shiftedState[index]; + pos++; + } + + // Apply + scatter + if (inverse) { + for (size_t i = 0; i < indices.size(); i++) { + size_t index = indices[i]; + shiftedState[index] = 0; + + for (size_t j = 0; j < indices.size(); j++) { + const size_t baseIndex = j * indices.size(); + shiftedState[index] += + std::conj(matrix[baseIndex + i]) * v[j]; + } + } + } else { + for (size_t i = 0; i < indices.size(); i++) { + size_t index = indices[i]; + shiftedState[index] = 0; + + const size_t baseIndex = i * indices.size(); + for (size_t j = 0; j < indices.size(); j++) { + shiftedState[index] += matrix[baseIndex + j] * v[j]; + } + } + } + } + } + + /** + * @brief Apply a given matrix directly to the statevector. + * + * @param arr Pointer to the statevector. + * @param num_qubits Number of qubits. + * @param matrix Perfect square matrix in row-major order. + * @param wires Wires the gate applies to. + * @param inverse Indicate whether inverse should be taken. + */ + template + static void applyMatrix(std::complex *arr, size_t num_qubits, + const std::vector> &matrix, + const std::vector &wires, bool inverse) { + if (matrix.size() != Util::exp2(2 * wires.size())) { + throw std::invalid_argument( + "The size of matrix does not match with the given " + "number of wires"); + } + applyMatrix(arr, num_qubits, matrix.data(), wires, inverse); + } + + /* Single qubit operators */ + template + static void applyPauliX(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::swap(shiftedState[indices[0]], shiftedState[indices[1]]); + } + } + + template + static void applyPauliY(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::complex v0 = shiftedState[indices[0]]; + shiftedState[indices[0]] = + std::complex{shiftedState[indices[1]].imag(), + -shiftedState[indices[1]].real()}; + shiftedState[indices[1]] = + std::complex{-v0.imag(), v0.real()}; + } + } + + template + static void applyPauliZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[1]] = -shiftedState[indices[1]]; + } + } + + template + static void applyHadamard(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + + const std::complex v0 = shiftedState[indices[0]]; + const std::complex v1 = shiftedState[indices[1]]; + + shiftedState[indices[0]] = Util::INVSQRT2() * (v0 + v1); + shiftedState[indices[1]] = Util::INVSQRT2() * (v0 - v1); + } + } + + template + static void applyS(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + const std::complex shift = + (inverse) ? -Util::IMAG() : Util::IMAG(); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[1]] *= shift; + } + } + + template + static void applyT(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const std::complex shift = + (inverse) ? std::conj(std::exp(std::complex( + 0, static_cast(M_PI / 4)))) + : std::exp(std::complex( + 0, static_cast(M_PI / 4))); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[1]] *= shift; + } + } + + /* Single qubit operators with a parameter */ + template + static void applyPhaseShift(std::complex *arr, + size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + const std::complex s = + inverse ? std::conj(std::exp(std::complex(0, angle))) + : std::exp(std::complex(0, angle)); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[1]] *= s; + } + } + + template + static void applyRX(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT js = + (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[0]]; + const std::complex v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = + c * v0 + js * std::complex{-v1.imag(), v1.real()}; + shiftedState[indices[1]] = + js * std::complex{-v0.imag(), v0.real()} + c * v1; + } + } + + template + static void applyRY(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT s = + (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[0]]; + const std::complex v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = c * v0 - s * v1; + shiftedState[indices[1]] = s * v0 + c * v1; + } + } + + template + static void applyRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const std::complex first = + std::complex(std::cos(angle / 2), -std::sin(angle / 2)); + const std::complex second = + std::complex(std::cos(angle / 2), std::sin(angle / 2)); + const std::complex shift1 = + (inverse) ? std::conj(first) : first; + const std::complex shift2 = + (inverse) ? std::conj(second) : second; + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[0]] *= shift1; + shiftedState[indices[1]] *= shift2; + } + } + + template + static void applyRot(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT phi, ParamT theta, ParamT omega) { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const std::vector> rot = + Gates::getRot(phi, theta, omega); + + const std::complex t1 = + (inverse) ? std::conj(rot[0]) : rot[0]; + const std::complex t2 = (inverse) ? -rot[1] : rot[1]; + const std::complex t3 = (inverse) ? -rot[2] : rot[2]; + const std::complex t4 = + (inverse) ? std::conj(rot[3]) : rot[3]; + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[0]]; + const std::complex v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = t1 * v0 + t2 * v1; + shiftedState[indices[1]] = t3 * v0 + t4 * v1; + } + } + + /* Two qubit operators */ + template + static void applyCNOT(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::swap(shiftedState[indices[2]], shiftedState[indices[3]]); + } + } + + template + static void applyCY(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::complex v2 = shiftedState[indices[2]]; + shiftedState[indices[2]] = + std::complex{shiftedState[indices[3]].imag(), + -shiftedState[indices[3]].real()}; + shiftedState[indices[3]] = + std::complex{-v2.imag(), v2.real()}; + } + } + + template + static void applyCZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[3]] *= -1; + } + } + + template + static void applySWAP(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::swap(shiftedState[indices[1]], shiftedState[indices[2]]); + } + } + + /* Two qubit operators with a parameter */ + template + static void applyIsingXX(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + using ComplexPrecisionT = std::complex; + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT cr = std::cos(angle / 2); + const PrecisionT sj = + inverse ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + + const auto v0 = shiftedState[indices[0]]; + const auto v1 = shiftedState[indices[1]]; + const auto v2 = shiftedState[indices[2]]; + const auto v3 = shiftedState[indices[3]]; + + shiftedState[indices[0]] = ComplexPrecisionT{ + cr * real(v0) + sj * imag(v3), cr * imag(v0) - sj * real(v3)}; + shiftedState[indices[1]] = ComplexPrecisionT{ + cr * real(v1) + sj * imag(v2), cr * imag(v1) - sj * real(v2)}; + shiftedState[indices[2]] = ComplexPrecisionT{ + cr * real(v2) + sj * imag(v1), cr * imag(v2) - sj * real(v1)}; + shiftedState[indices[3]] = ComplexPrecisionT{ + cr * real(v3) + sj * imag(v0), cr * imag(v3) - sj * real(v0)}; + } + } + + template + static void applyIsingYY(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + using ComplexPrecisionT = std::complex; + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT cr = std::cos(angle / 2); + const PrecisionT sj = + inverse ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + + const auto v0 = shiftedState[indices[0]]; + const auto v1 = shiftedState[indices[1]]; + const auto v2 = shiftedState[indices[2]]; + const auto v3 = shiftedState[indices[3]]; + + shiftedState[indices[0]] = ComplexPrecisionT{ + cr * real(v0) - sj * imag(v3), cr * imag(v0) + sj * real(v3)}; + shiftedState[indices[1]] = ComplexPrecisionT{ + cr * real(v1) + sj * imag(v2), cr * imag(v1) - sj * real(v2)}; + shiftedState[indices[2]] = ComplexPrecisionT{ + cr * real(v2) + sj * imag(v1), cr * imag(v2) - sj * real(v1)}; + shiftedState[indices[3]] = ComplexPrecisionT{ + cr * real(v3) - sj * imag(v0), cr * imag(v3) + sj * real(v0)}; + } + } + + template + static void applyIsingZZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + + shiftedState[indices[0]] *= shifts[0]; + shiftedState[indices[1]] *= shifts[1]; + shiftedState[indices[2]] *= shifts[1]; + shiftedState[indices[3]] *= shifts[0]; + } + } + + template + static void applyControlledPhaseShift(std::complex *arr, + size_t num_qubits, + const std::vector &wires, + bool inverse, ParamT angle) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const std::complex s = + inverse ? std::conj(std::exp(std::complex(0, angle))) + : std::exp(std::complex(0, angle)); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[3]] *= s; + } + } + + template + static void applyCRX(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT js = + (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[2]]; + const std::complex v1 = shiftedState[indices[3]]; + shiftedState[indices[2]] = + c * v0 + js * std::complex{-v1.imag(), v1.real()}; + shiftedState[indices[3]] = + js * std::complex{-v0.imag(), v0.real()} + c * v1; + } + } + + template + static void applyCRY(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + const PrecisionT c = std::cos(angle / 2); + const PrecisionT s = + (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[2]]; + const std::complex v1 = shiftedState[indices[3]]; + shiftedState[indices[2]] = c * v0 - s * v1; + shiftedState[indices[3]] = s * v0 + c * v1; + } + } + + template + static void applyCRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT angle) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + const std::complex m00 = + (inverse) ? std::complex(std::cos(angle / 2), + std::sin(angle / 2)) + : std::complex(std::cos(angle / 2), + -std::sin(angle / 2)); + const std::complex m11 = + (inverse) ? std::complex(std::cos(angle / 2), + -std::sin(angle / 2)) + : std::complex(std::cos(angle / 2), + std::sin(angle / 2)); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[2]] *= m00; + shiftedState[indices[3]] *= m11; + } + } + + template + static void applyCRot(std::complex *arr, size_t num_qubits, + const std::vector &wires, bool inverse, + ParamT phi, ParamT theta, ParamT omega) { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + const auto rot = Gates::getRot(phi, theta, omega); + + const std::complex t1 = + (inverse) ? std::conj(rot[0]) : rot[0]; + const std::complex t2 = (inverse) ? -rot[1] : rot[1]; + const std::complex t3 = (inverse) ? -rot[2] : rot[2]; + const std::complex t4 = + (inverse) ? std::conj(rot[3]) : rot[3]; + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const std::complex v0 = shiftedState[indices[2]]; + const std::complex v1 = shiftedState[indices[3]]; + shiftedState[indices[2]] = t1 * v0 + t2 * v1; + shiftedState[indices[3]] = t3 * v0 + t4 * v1; + } + } + + /* Three-qubit gate */ + template + static void applyToffoli(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 3); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + // Participating swapped indices + static const size_t op_idx0 = 6; + static const size_t op_idx1 = 7; + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::swap(shiftedState[indices[op_idx0]], + shiftedState[indices[op_idx1]]); + } + } + + template + static void applyCSWAP(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse) { + assert(wires.size() == 3); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + // Participating swapped indices + static const size_t op_idx0 = 5; + static const size_t op_idx1 = 6; + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + std::swap(shiftedState[indices[op_idx0]], + shiftedState[indices[op_idx1]]); + } + } + + /* Multi-qubit gates */ + template + static void applyMultiRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool inverse, ParamT angle) { + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + const std::complex first = + std::complex{std::cos(angle / 2), -std::sin(angle / 2)}; + const std::complex second = + std::complex{std::cos(angle / 2), std::sin(angle / 2)}; + + const std::array, 2> shifts = { + (inverse) ? std::conj(first) : first, + (inverse) ? std::conj(second) : second}; + + // Participating swapped indices + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + for (size_t k = 0; k < indices.size(); k++) { + shiftedState[indices[k]] *= shifts[Util::popcount(k) % 2]; + } + } + } + + /* Gate generators */ + template + [[nodiscard]] static auto + applyGeneratorPhaseShift(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 1); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[0]] = std::complex{0.0, 0.0}; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return static_cast(1.0); + } + + template + [[nodiscard]] static auto + applyGeneratorCRX(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[0]] = Util::ZERO(); + shiftedState[indices[1]] = Util::ZERO(); + + std::swap(shiftedState[indices[2]], shiftedState[indices[3]]); + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorCRY(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + const auto v0 = shiftedState[indices[2]]; + shiftedState[indices[0]] = Util::ZERO(); + shiftedState[indices[1]] = Util::ZERO(); + shiftedState[indices[2]] = + -Util::IMAG() * shiftedState[indices[3]]; + shiftedState[indices[3]] = Util::IMAG() * v0; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto + applyGeneratorCRZ(std::complex *arr, size_t num_qubits, + const std::vector &wires, + [[maybe_unused]] bool adj) -> PrecisionT { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[0]] = Util::ZERO(); + shiftedState[indices[1]] = Util::ZERO(); + shiftedState[indices[3]] *= -1; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + + template + [[nodiscard]] static auto applyGeneratorControlledPhaseShift( + std::complex *arr, size_t num_qubits, + const std::vector &wires, [[maybe_unused]] bool adj) + -> PrecisionT { + assert(wires.size() == 2); + const auto [indices, externalIndices] = GateIndices(wires, num_qubits); + + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = arr + externalIndex; + shiftedState[indices[0]] = 0; + shiftedState[indices[1]] = 0; + shiftedState[indices[2]] = 0; + } + // NOLINTNEXTLINE(readability-magic-numbers) + return static_cast(1); + } +}; +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/gates/GateOperation.hpp b/pennylane_lightning/src/gates/GateOperation.hpp new file mode 100644 index 0000000000..24d17d4406 --- /dev/null +++ b/pennylane_lightning/src/gates/GateOperation.hpp @@ -0,0 +1,85 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file GateOperation.hpp + * Defines possible operations. + */ +#pragma once +#include +#include +#include + +namespace Pennylane::Gates { +/** + * @brief Enum class for all gate operations + */ +enum class GateOperation : uint32_t { + BEGIN = 0, + /* Single-qubit gates */ + PauliX = 0, + PauliY, + PauliZ, + Hadamard, + S, + T, + PhaseShift, + RX, + RY, + RZ, + Rot, + /* Two-qubit gates */ + CNOT, + CY, + CZ, + SWAP, + IsingXX, + IsingYY, + IsingZZ, + ControlledPhaseShift, + CRX, + CRY, + CRZ, + CRot, + /* Three-qubit gates */ + Toffoli, + CSWAP, + /* Mutli-qubit gates */ + MultiRZ, + /* General matrix */ + Matrix, + /* END (placeholder) */ + END +}; +/** + * @brief Enum class of all gate generators + */ +enum class GeneratorOperation : uint32_t { + BEGIN = 0, + /* Gate generators (only used internally for adjoint diff) */ + PhaseShift = 0, + RX, + RY, + RZ, + IsingXX, + IsingYY, + IsingZZ, + CRX, + CRY, + CRZ, + ControlledPhaseShift, + MultiRZ, + /* END (placeholder) */ + END +}; +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/gates/GateUtil.cpp b/pennylane_lightning/src/gates/GateUtil.cpp new file mode 100644 index 0000000000..86db076845 --- /dev/null +++ b/pennylane_lightning/src/gates/GateUtil.cpp @@ -0,0 +1,90 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "GateUtil.hpp" +#include "AvailableKernels.hpp" + +#include "Util.hpp" + +namespace Pennylane::Gates { + +auto getIndicesAfterExclusion(const std::vector &indicesToExclude, + size_t num_qubits) -> std::vector { + std::set indices; + for (size_t i = 0; i < num_qubits; i++) { + indices.emplace(i); + } + for (const size_t &excludedIndex : indicesToExclude) { + indices.erase(excludedIndex); + } + return {indices.begin(), indices.end()}; +} + +auto generateBitPatterns(const std::vector &qubitIndices, + size_t num_qubits) -> std::vector { + std::vector indices; + indices.reserve(Util::exp2(qubitIndices.size())); + indices.emplace_back(0); + + for (auto index_it = qubitIndices.rbegin(); index_it != qubitIndices.rend(); + index_it++) { + const size_t value = Util::maxDecimalForQubit(*index_it, num_qubits); + const size_t currentSize = indices.size(); + for (size_t j = 0; j < currentSize; j++) { + indices.emplace_back(indices[j] + value); + } + } + return indices; +} +} // namespace Pennylane::Gates + +/// @cond DEV +namespace { +template struct ImplementedGates { + constexpr static auto value = OperatorImplementation::implemented_gates; +}; +template struct ImplementedGenerators { + constexpr static auto value = + OperatorImplementation::implemented_generators; +}; + +template class ValueClass> +auto ValueForKernelHelper( + [[maybe_unused]] Pennylane::Gates::KernelType kernel) { + if constexpr (std::is_same_v) { + return std::vector{}; + } else { + if (TypeList::Type::kernel_id == kernel) { + return std::vector( + std::cbegin(ValueClass::value), + std::cend(ValueClass::value)); + } + return ValueForKernelHelper(kernel); + } +} +} // namespace +/// @endcond + +namespace Pennylane::Gates { +auto implementedGatesForKernel(KernelType kernel) + -> std::vector { + return ValueForKernelHelper(kernel); +} +auto implementedGeneratorsForKernel(KernelType kernel) + -> std::vector { + return ValueForKernelHelper(kernel); +} +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/simulator/IndicesUtil.hpp b/pennylane_lightning/src/gates/GateUtil.hpp similarity index 67% rename from pennylane_lightning/src/simulator/IndicesUtil.hpp rename to pennylane_lightning/src/gates/GateUtil.hpp index a38ac9b6e5..860d2ba6d8 100644 --- a/pennylane_lightning/src/simulator/IndicesUtil.hpp +++ b/pennylane_lightning/src/gates/GateUtil.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. /** - * @file IndicesUtil.hpp - * Defines the class representation for quantum state vectors. + * @file + * Defines a non-template utility functions */ #pragma once @@ -22,6 +22,8 @@ #include #include +#include "GateOperation.hpp" +#include "KernelType.hpp" #include "Util.hpp" /** @@ -30,7 +32,7 @@ * for loop is usually better performing, consider to use other options before * using functions below. */ -namespace Pennylane::IndicesUtil { +namespace Pennylane::Gates { /** * @brief Get indices of statevector data not participating in application @@ -65,18 +67,40 @@ auto generateBitPatterns(const std::vector &qubitIndices, * 1000B, 1001B}. For each external index, internal indices are bitstrings for * the wires. In the example, we have internal indices {0000B, 0010B, 0100B, * 0110B}. - * - * @var internal Internal indices. For the given wires with size n_wire, the - * output size is 2^n_wire. - * @var external External indices. For the given wires with size n_wire, the - * output size is 2^(num_qubits - n_wires). */ struct GateIndices { - const std::vector internal; - const std::vector external; + const std::vector internal; /**< Internal indices. + For the given wires with size n_wire, + the output size is 2^n_wire. */ + + const std::vector + external; /**< external External indices. + For the given wires with size n_wire, the + output size is 2^(num_qubits - n_wires). */ + + /** + * @brief Create indices for gates. + */ GateIndices(const std::vector &wires, size_t num_qubits) : internal{generateBitPatterns(wires, num_qubits)}, external{generateBitPatterns( getIndicesAfterExclusion(wires, num_qubits), num_qubits)} {} }; -} // namespace Pennylane::IndicesUtil + +/** + * @brief Return implemented_gates constexpr member variables for a given kernel + * + * This function interfaces the runtime variable kernel with the constant time + * variable implemented_gates + */ +auto implementedGatesForKernel(KernelType kernel) -> std::vector; +/** + * @brief Return implemented_generators constexpr member variables for a given + * kernel + * + * This function interfaces the runtime variable kernel with the constant time + * variable implemented_gates + */ +auto implementedGeneratorsForKernel(KernelType kernel) + -> std::vector; +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/simulator/Gates.hpp b/pennylane_lightning/src/gates/Gates.hpp similarity index 96% rename from pennylane_lightning/src/simulator/Gates.hpp rename to pennylane_lightning/src/gates/Gates.hpp index 0cfe7b4c04..a9141fec48 100644 --- a/pennylane_lightning/src/simulator/Gates.hpp +++ b/pennylane_lightning/src/gates/Gates.hpp @@ -6,12 +6,6 @@ #include "Util.hpp" -/// @cond DEV -namespace { -using namespace Pennylane::Util; -} -/// @endcond - namespace Pennylane::Gates { /** @@ -24,6 +18,7 @@ namespace Pennylane::Gates { */ template static constexpr auto getPauliX() -> std::vector> { + using namespace Util; return {ZERO(), ONE(), ONE(), ZERO()}; } @@ -37,6 +32,7 @@ static constexpr auto getPauliX() -> std::vector> { */ template static constexpr auto getPauliY() -> std::vector> { + using namespace Util; return {ZERO(), -IMAG(), IMAG(), ZERO()}; } @@ -50,6 +46,7 @@ static constexpr auto getPauliY() -> std::vector> { */ template static constexpr auto getPauliZ() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), -ONE()}; } @@ -63,6 +60,7 @@ static constexpr auto getPauliZ() -> std::vector> { */ template static constexpr auto getHadamard() -> std::vector> { + using namespace Util; return {INVSQRT2(), INVSQRT2(), INVSQRT2(), -INVSQRT2()}; } @@ -75,6 +73,7 @@ static constexpr auto getHadamard() -> std::vector> { */ template static constexpr auto getS() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), IMAG()}; } @@ -87,7 +86,9 @@ static constexpr auto getS() -> std::vector> { */ template static constexpr auto getT() -> std::vector> { - return {ONE(), ZERO(), ZERO(), IMAG()}; + using namespace Util; + return {ONE(), ZERO(), ZERO(), + std::complex{INVSQRT2(), INVSQRT2()}}; } /** @@ -100,6 +101,7 @@ static constexpr auto getT() -> std::vector> { */ template static constexpr auto getCNOT() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ONE(), ZERO()}; @@ -115,6 +117,7 @@ static constexpr auto getCNOT() -> std::vector> { */ template static constexpr auto getSWAP() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ONE()}; @@ -130,6 +133,7 @@ static constexpr auto getSWAP() -> std::vector> { */ template static constexpr auto getCZ() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), -ONE()}; @@ -145,6 +149,7 @@ static constexpr auto getCZ() -> std::vector> { */ template static constexpr auto getCSWAP() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), @@ -168,6 +173,7 @@ static constexpr auto getCSWAP() -> std::vector> { */ template static constexpr auto getToffoli() -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), ZERO(), @@ -193,6 +199,7 @@ static constexpr auto getToffoli() -> std::vector> { */ template static auto getPhaseShift(U angle) -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), std::exp(IMAG() * angle)}; } @@ -285,6 +292,7 @@ static auto getRY(const std::vector ¶ms) */ template static auto getRZ(U angle) -> std::vector> { + using namespace Util; return {std::exp(-IMAG() * (angle / 2)), ZERO(), ZERO(), std::exp(IMAG() * (angle / 2))}; } @@ -323,6 +331,7 @@ e^{-i(\phi-\omega)/2}\sin(\theta/2) & e^{i(\phi+\omega)/2}\cos(\theta/2) */ template static auto getRot(U phi, U theta, U omega) -> std::vector> { + using namespace Util; const T c = std::cos(theta / 2); const T s = std::sin(theta / 2); const U p{phi + omega}; @@ -367,6 +376,7 @@ static auto getRot(const std::vector ¶ms) */ template static auto getCRX(U angle) -> std::vector> { + using namespace Util; const std::complex rx{getRX(angle)}; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), rx[0], rx[1], @@ -399,6 +409,7 @@ static auto getCRX(const std::vector ¶ms) */ template static auto getCRY(U angle) -> std::vector> { + using namespace Util; const std::complex ry{getRY(angle)}; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ry[0], ry[1], @@ -431,6 +442,7 @@ static auto getCRY(const std::vector ¶ms) */ template static auto getCRZ(U angle) -> std::vector> { + using namespace Util; const std::complex first = std::exp(-IMAG() * (angle / 2)); const std::complex second = std::exp(IMAG() * (angle / 2)); return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), @@ -461,6 +473,7 @@ row-major format. */ template static auto getCRot(U phi, U theta, U omega) -> std::vector> { + using namespace Util; const std::vector> rot{ std::move(getRot(phi, theta, omega))}; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), @@ -488,6 +501,7 @@ in row-major format. */ template static auto getControlledPhaseShift(U angle) -> std::vector> { + using namespace Util; return {ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), ZERO(), ZERO(), ZERO(), ONE(), ZERO(), diff --git a/pennylane_lightning/src/gates/KernelType.hpp b/pennylane_lightning/src/gates/KernelType.hpp new file mode 100644 index 0000000000..f517cc1f61 --- /dev/null +++ b/pennylane_lightning/src/gates/KernelType.hpp @@ -0,0 +1,37 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file KernelType.hpp + * Defines possible kernel types as enum and define python export. + */ +#pragma once +#include "Error.hpp" +#include "Util.hpp" + +#include + +namespace Pennylane::Gates { +/** + * @brief Define kernel id for each implementation. + */ +enum class KernelType { PI, LM, None }; +} // namespace Pennylane::Gates + +namespace Pennylane { +/** + * @brief List of kernels binds to Python. + */ +[[maybe_unused]] constexpr std::array kernels_to_pyexport = { + Gates::KernelType::PI, Gates::KernelType::LM}; +} // namespace Pennylane diff --git a/pennylane_lightning/src/gates/OpToMemberFuncPtr.hpp b/pennylane_lightning/src/gates/OpToMemberFuncPtr.hpp new file mode 100644 index 0000000000..05808dd364 --- /dev/null +++ b/pennylane_lightning/src/gates/OpToMemberFuncPtr.hpp @@ -0,0 +1,452 @@ +// Copyright 2022 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file + * Defines template classes to extract member function pointers for + * metaprogramming. Also defines some utility functions that calls such + * pointers. + */ + +#pragma once +#include "GateOperation.hpp" +#include "cassert" +#include +#include + +namespace Pennylane::Gates { + +/** + * @brief Return a specific member function pointer for a given gate operation. + * See specialized classes. + */ +template +struct GateOpToMemberFuncPtr { + // raises compile error when used + static_assert( + gate_op != GateOperation::Matrix, + "GateOpToMemberFuncPtr is not defined for GateOperation::Matrix."); + static_assert(gate_op == GateOperation::Matrix, + "GateOpToMemberFuncPtr is not defined for the given gate. " + "When you define a new GateOperation, check that you also " + "have added the corresponding entry in " + "GateOpToMemberFuncPtr."); + constexpr static auto value = nullptr; +}; + +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyPauliX; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyPauliY; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyPauliZ; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyHadamard; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyS; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyT; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyPhaseShift; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyRX; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyRY; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyRZ; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyRot; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCNOT; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCY; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCZ; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applySWAP; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyIsingXX; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyIsingYY; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyIsingZZ; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyControlledPhaseShift; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCRX; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCRY; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCRZ; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCRot; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyToffoli; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyCSWAP; +}; +template +struct GateOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyMultiRZ; +}; + +/** + * @brief Return a specific member function pointer for a given generator + * operation. See specialized classes. + */ +template +struct GeneratorOpToMemberFuncPtr { + // raises compile error when used + static_assert( + sizeof(GateImplementation) == -1, + "GeneratorOpToMemberFuncPtr is not defined for the given generator. " + "When you define a new GeneratorOperation, check that you also " + "have added the corresponding entry in GeneratorOpToMemberFuncPtr."); +}; + +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorRX; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorRY; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorRZ; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorPhaseShift; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorIsingXX; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorIsingYY; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorIsingZZ; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorCRX; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorCRY; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorCRZ; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorControlledPhaseShift< + PrecisionT>; +}; +template +struct GeneratorOpToMemberFuncPtr { + constexpr static auto value = + &GateImplementation::template applyGeneratorMultiRZ; +}; + +/// @cond DEV +namespace Internal { +/** + * @brief Gate operation pointer type for a statevector. See all specialized + * types. + */ +template struct GateMemFuncPtr { + static_assert(num_params < 2 || num_params == 3, + "The given num_params is not supported."); +}; +/** + * @brief Function pointer type for a gate operation without parameters. + */ +template struct GateMemFuncPtr { + using Type = void (SVType::*)(const std::vector &, bool); +}; +/** + * @brief Function pointer type for a gate operation with a single parameter. + */ +template struct GateMemFuncPtr { + using Type = void (SVType::*)(const std::vector &, bool, ParamT); +}; +/** + * @brief Function pointer type for a gate operation with three parameters. + */ +template struct GateMemFuncPtr { + using Type = void (SVType::*)(const std::vector &, bool, ParamT, + ParamT, ParamT); +}; + +/** + * @brief A convenient alias for GateMemFuncPtr. + */ +template +using GateMemFuncPtrT = + typename GateMemFuncPtr::Type; + +/** + * @brief Gate operation pointer type. See all specialized types. + */ +template +struct GateFuncPtr { + static_assert(num_params < 2 || num_params == 3, + "The given num_params is not supported."); +}; + +/** + * @brief Pointer type for a gate operation without parameters. + */ +template +struct GateFuncPtr { + using Type = void (*)(std::complex *, size_t, + const std::vector &, bool); +}; +/** + * @brief Pointer type for a gate operation with a single parameter + */ +template +struct GateFuncPtr { + using Type = void (*)(std::complex *, size_t, + const std::vector &, bool, ParamT); +}; +/** + * @brief Pointer type for a gate operation with three parameters + */ +template +struct GateFuncPtr { + using Type = void (*)(std::complex *, size_t, + const std::vector &, bool, ParamT, ParamT, + ParamT); +}; + +/** + * @brief Pointer type for a generator operation + */ +template struct GeneratorFuncPtr { + using Type = PrecisionT (*)(std::complex *, size_t, + const std::vector &, bool); +}; +} // namespace Internal +/// @endcond + +/** + * @brief Convenient type alias for GateFuncPtr. + */ +template +using GateFuncPtrT = + typename Internal::GateFuncPtr::Type; + +/** + * @brief Convenient type alias for GeneratorFuncPtrT. + */ +template +using GeneratorFuncPtrT = typename Internal::GeneratorFuncPtr::Type; + +/** + * @defgroup Call gate operation with provided arguments + * + * @tparam PrecisionT Floating point type for the state-vector. + * @tparam ParamT Floating point type for the gate parameters. + * @param func Function pointer for the gate operation. + * @param data Data pointer the gate is applied to + * @param num_qubits The number of qubits of the state-vector. + * @param wires Wires the gate applies to. + * @param inverse If true, we apply the inverse of the gate. + * @param params The list of gate parameters. + */ +/// @{ +/** + * @brief Overload for a gate operation without parameters + */ +template +inline void callGateOps(GateFuncPtrT func, + std::complex *data, size_t num_qubits, + const std::vector &wires, bool inverse, + [[maybe_unused]] const std::vector ¶ms) { + assert(params.empty()); + func(data, num_qubits, wires, inverse); +} + +/** + * @brief Overload for a gate operation for a single parameter + */ +template +inline void callGateOps(GateFuncPtrT func, + std::complex *data, size_t num_qubits, + const std::vector &wires, bool inverse, + const std::vector ¶ms) { + assert(params.size() == 1); + func(data, num_qubits, wires, inverse, params[0]); +} + +/** + * @brief Overload for a gate operation for three parameters + */ +template +inline void callGateOps(GateFuncPtrT func, + std::complex *data, size_t num_qubits, + const std::vector &wires, bool inverse, + const std::vector ¶ms) { + assert(params.size() == 3); + func(data, num_qubits, wires, inverse, params[0], params[1], params[2]); +} +/// @} +/** + * @brief Call a generator operation. + * + * @tparam PrecisionT Floating point type for the state-vector. + * @return Scaling factor + */ +template +inline PrecisionT callGeneratorOps(GeneratorFuncPtrT func, + std::complex *data, + size_t num_qubits, + const std::vector &wires, bool adj) { + return func(data, num_qubits, wires, adj); +} +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/gates/PauliGenerator.hpp b/pennylane_lightning/src/gates/PauliGenerator.hpp new file mode 100644 index 0000000000..1611499644 --- /dev/null +++ b/pennylane_lightning/src/gates/PauliGenerator.hpp @@ -0,0 +1,58 @@ +// Copyright 2022 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file PauliGenerator.hpp + * Defines generators for RX, RY, RZ + */ +#pragma once +#include +#include +#include + +namespace Pennylane::Gates { +/** + * @brief Define generators of RX, RY, RZ using the Pauli gates. + * @rst + * A Generator for a unitary operator :math:`U` is :math:`G` such that + * :math:`e^{iGt} = U`. + * @endrst + */ +template class PauliGenerator { + public: + template + [[nodiscard]] static auto + applyGeneratorRX(std::complex *data, size_t num_qubits, + const std::vector &wires, bool adj) -> PrecisionT { + GateImplementation::applyPauliX(data, num_qubits, wires, adj); + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + template + [[nodiscard]] static auto + applyGeneratorRY(std::complex *data, size_t num_qubits, + const std::vector &wires, bool adj) -> PrecisionT { + GateImplementation::applyPauliY(data, num_qubits, wires, adj); + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } + template + [[nodiscard]] static auto + applyGeneratorRZ(std::complex *data, size_t num_qubits, + const std::vector &wires, bool adj) -> PrecisionT { + GateImplementation::applyPauliZ(data, num_qubits, wires, adj); + // NOLINTNEXTLINE(readability-magic-numbers) + return -static_cast(0.5); + } +}; +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/gates/SelectKernel.hpp b/pennylane_lightning/src/gates/SelectKernel.hpp new file mode 100644 index 0000000000..5057ed9b42 --- /dev/null +++ b/pennylane_lightning/src/gates/SelectKernel.hpp @@ -0,0 +1,127 @@ +// Copyright 2022 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file SelectKernel.hpp + * Defines a template class for choosing a Gate operations + */ +#pragma once +#include "AvailableKernels.hpp" +#include "Constant.hpp" +#include "ConstantUtil.hpp" +#include "GateOperation.hpp" +#include "KernelType.hpp" +#include "TypeList.hpp" + +#include +#include +#include + +namespace Pennylane::Gates { +/** + * @brief For lookup from any array of pair whose first elements are + * GateOperation. + * + * As Util::lookup can be used in constexpr context, this function is redundant + * (by the standard). But GCC 9 still does not accept Util::lookup in constexpr + * some cases. + */ +///@{ +template +constexpr auto +static_lookup(const std::array, size> &arr) -> T { + for (size_t idx = 0; idx < size; idx++) { + if (std::get<0>(arr[idx]) == op) { + return std::get<1>(arr[idx]); + } + } + return T{}; +} + +template +constexpr auto +static_lookup(const std::array, size> &arr) + -> T { + for (size_t idx = 0; idx < size; idx++) { + if (std::get<0>(arr[idx]) == op) { + return std::get<1>(arr[idx]); + } + } + return T{}; +} +///@} + +/// @cond DEV +namespace Internal { +template struct KernelIdNamePairsHelper { + constexpr static std::tuple value = Util::prepend_to_tuple( + std::pair{TypeList::Type::kernel_id, TypeList::Type::name}, + KernelIdNamePairsHelper::value); +}; + +template <> struct KernelIdNamePairsHelper { + constexpr static std::tuple value = std::tuple{}; +}; + +} // namespace Internal +/// @endcond + +/** + * @brief Array of kernel_id and name pairs for all available kernels + */ +constexpr static auto kernel_id_name_pairs = Util::tuple_to_array( + Internal::KernelIdNamePairsHelper::value); + +/** + * @brief Return kernel_id for the given kernel_name + * + * @param kernel_name Name of kernel to search + */ +constexpr auto string_to_kernel(const std::string_view kernel_name) + -> KernelType { + return Util::lookup(Util::reverse_pairs(kernel_id_name_pairs), kernel_name); +} + +/// @cond DEV +namespace Internal { +template struct SelectKernelHelper { + using Type = std::conditional_t< + TypeList::Type::kernel_id == kernel, typename TypeList::Type, + typename SelectKernelHelper::Type>; +}; +template struct SelectKernelHelper { + static_assert(Util::array_has_elt(Util::first_elts_of(kernel_id_name_pairs), + kernel), + "The given kernel is not in the list of available kernels."); + using Type = void; +}; +} // namespace Internal +/// @endcond + +/** + * @brief This class chooses a gate implementation at the compile time. + * + * When one adds another gate implementation, one needs to add a key + * in KernelType and assign it to SelectKernel by template specialization. + * + * Even though it is impossible to convert this into a constexpr function, + * one may convert GateOpsFuncPtrPairs into constexpr functions with + * kernel as a parameter (instead of a template parameter). + * + * @tparam kernel Kernel to select + */ +template +using SelectKernel = + typename Internal::SelectKernelHelper::Type; + +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index 03bf6e09d9..ff07211f3a 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -1,12 +1,14 @@ project(lightning_simulator) set(CMAKE_CXX_STANDARD 17) -set(SIMULATOR_FILES IndicesUtil.cpp CACHE INTERNAL "" FORCE) +set(SIMULATOR_FILES DynamicDispatcher.cpp CACHE INTERNAL "" FORCE) add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) -target_link_libraries(lightning_simulator PRIVATE lightning_compile_options - lightning_external_libs) + target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(lightning_simulator PRIVATE lightning_utils) +target_link_libraries(lightning_simulator PRIVATE lightning_compile_options + lightning_external_libs + lightning_gates + lightning_utils) set_property(TARGET lightning_simulator PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/pennylane_lightning/src/simulator/DynamicDispatcher.cpp b/pennylane_lightning/src/simulator/DynamicDispatcher.cpp new file mode 100644 index 0000000000..315b7a102e --- /dev/null +++ b/pennylane_lightning/src/simulator/DynamicDispatcher.cpp @@ -0,0 +1,218 @@ +// Copyright 2022 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file DynamicDispatcher.cpp + * Register all gate and generator implementations + */ +#include "DynamicDispatcher.hpp" +#include "AvailableKernels.hpp" +#include "Constant.hpp" +#include "ConstantUtil.hpp" +#include "GateUtil.hpp" +#include "OpToMemberFuncPtr.hpp" +#include "SelectKernel.hpp" + +using namespace Pennylane; +using namespace Pennylane::Util; + +/// @cond DEV +namespace { +/** + * @brief return a lambda function for the given kernel and gate operation + * + * As we want the lambda function to be stateless, kernel and gate_op are + * template parameters (or the functions can be consteval in C++20). + * In C++20, one also may use a template lambda function instead. + * + * @tparam PrecisionT Floating point precision of underlying statevector data. + * @tparam ParamT Floating point type for parameters> + * @tparam GateImplementation Gate implementation class. + * @tparam gate_op Gate operation to make a functor. + */ +template +constexpr auto gateOpToFunctor() { + return [](std::complex *data, size_t num_qubits, + const std::vector &wires, bool inverse, + const std::vector ¶ms) { + constexpr auto func_ptr = + Gates::GateOpToMemberFuncPtr::value; + assert(params.size() == + Gates::static_lookup(Gates::Constant::gate_num_params)); + Gates::callGateOps(func_ptr, data, num_qubits, wires, inverse, params); + }; +} +/// @endcond + +/// @cond DEV +/** + * @brief Internal recursion function for constructGateOpsFunctorTuple + * + * @return Tuple of gate operations and corresponding GateImplementation member + * function pointers. + */ +template +constexpr auto constructGateOpsFunctorTupleIter() { + if constexpr (gate_idx == GateImplementation::implemented_gates.size()) { + return std::tuple{}; + } else if (gate_idx < GateImplementation::implemented_gates.size()) { + constexpr auto gate_op = + GateImplementation::implemented_gates[gate_idx]; + if constexpr (gate_op == Gates::GateOperation::Matrix) { + /* GateOperation::Matrix is not supported for dynamic dispatch now + */ + return constructGateOpsFunctorTupleIter< + PrecisionT, ParamT, GateImplementation, gate_idx + 1>(); + } else { + return prepend_to_tuple( + std::pair{gate_op, + gateOpToFunctor()}, + constructGateOpsFunctorTupleIter< + PrecisionT, ParamT, GateImplementation, gate_idx + 1>()); + } + } +} +/** + * @brief Internal recursion function for constructGateOpsFunctorTuple + */ +template +constexpr auto constructGeneratorOpsFunctorTupleIter() { + if constexpr (gntr_idx == + GateImplementation::implemented_generators.size()) { + return std::tuple{}; + } else if (gntr_idx < GateImplementation::implemented_generators.size()) { + constexpr auto gntr_op = + GateImplementation::implemented_generators[gntr_idx]; + return prepend_to_tuple( + std::pair{gntr_op, + Gates::GeneratorOpToMemberFuncPtr< + PrecisionT, GateImplementation, gntr_op>::value}, + constructGeneratorOpsFunctorTupleIter< + PrecisionT, GateImplementation, gntr_idx + 1>()); + } +} +/// @endcond + +/** + * @brief Tuple of gate operation and function pointer pairs. + * + * @tparam PrecisionT Floating point precision of underlying statevector data + * @tparam ParamT Floating point type of gate parameters + * @tparam GateImplementation Gate implementation class. + */ +template +constexpr auto gate_op_functor_tuple = constructGateOpsFunctorTupleIter< + PrecisionT, ParamT, GateImplementation, 0>(); + +/** + * @brief Tuple of gate operation and function pointer pairs. + * + * @tparam PrecisionT Floating point precision of underlying statevector data + * @tparam ParamT Floating point type of gate parameters + * @tparam GateImplementation Gate implementation class. + */ +template +constexpr auto generator_op_functor_tuple = + constructGeneratorOpsFunctorTupleIter(); + +/** + * @brief Register all implemented gates for a given kernel + * + * @tparam PrecisionT Floating point precision of underlying statevector data + * @tparam ParamT Floating point type of gate parameters + * @tparam GateImplementation Gate implementation class. + */ +template +void registerAllImplementedGateOps() { + auto &dispatcher = DynamicDispatcher::getInstance(); + + auto registerGateToDispatcher = [&dispatcher]( + const auto &gate_op_func_pair) { + const auto &[gate_op, func] = gate_op_func_pair; + std::string op_name = + std::string(lookup(Gates::Constant::gate_names, gate_op)); + dispatcher.registerGateOperation(op_name, GateImplementation::kernel_id, + func); + return gate_op; + }; + + [[maybe_unused]] const auto registerd_gate_ops = std::apply( + [®isterGateToDispatcher](auto... elt) { + return std::make_tuple(registerGateToDispatcher(elt)...); + }, + gate_op_functor_tuple); +} +/** + * @brief Register all implemented generators for a given kernel + * + * @tparam PrecisionT Floating point precision of underlying statevector data + * @tparam GateImplementation Gate implementation class. + */ +template +void registerAllImplementedGeneratorOps() { + auto &dispatcher = DynamicDispatcher::getInstance(); + + auto registerGeneratorToDispatcher = + [&dispatcher](const auto &gntr_op_func_pair) { + const auto &[gntr_op, func] = gntr_op_func_pair; + std::string op_name = + std::string(lookup(Gates::Constant::generator_names, gntr_op)); + dispatcher.registerGeneratorOperation( + op_name, GateImplementation::kernel_id, func); + return gntr_op; + }; + + [[maybe_unused]] const auto registerd_gate_ops = std::apply( + [®isterGeneratorToDispatcher](auto... elt) { + return std::make_tuple(registerGeneratorToDispatcher(elt)...); + }, + generator_op_functor_tuple); +} + +/// @cond DEV +/** + * @brief Internal function to iterate over all available kernels in + * the compile time + */ +template +void registerKernelIter() { + if constexpr (std::is_same_v) { + return; + } else { + registerAllImplementedGateOps(); + registerAllImplementedGeneratorOps(); + registerKernelIter(); + } +} +/// @endcond +} // namespace + +/// @cond DEV +namespace Pennylane::Internal { +template int registerAllAvailableKernels() { + registerKernelIter(); + return 1; +} +/// @endcond + +// explicit instantiations +template int registerAllAvailableKernels(); +template int registerAllAvailableKernels(); + +} // namespace Pennylane::Internal diff --git a/pennylane_lightning/src/simulator/DynamicDispatcher.hpp b/pennylane_lightning/src/simulator/DynamicDispatcher.hpp index 1868bd04da..83536f9076 100644 --- a/pennylane_lightning/src/simulator/DynamicDispatcher.hpp +++ b/pennylane_lightning/src/simulator/DynamicDispatcher.hpp @@ -19,9 +19,13 @@ #pragma once +#include "Constant.hpp" +#include "ConstantUtil.hpp" #include "Error.hpp" -#include "SelectGateOps.hpp" +#include "GateUtil.hpp" +#include "KernelType.hpp" +#include #include #include #include @@ -29,58 +33,126 @@ #include #include +/// @cond DEV namespace Pennylane::Internal { struct PairHash { - size_t operator()(const std::pair &p) const { + size_t + operator()(const std::pair &p) const { return std::hash()(p.first) ^ std::hash()(static_cast(p.second)); } }; - +/** + * @brief Register all implemented gates for all available kernels. + * + * @tparam PrecisionT Floating point precision of underlying statevector data. + * @tparam ParamT Floating point type for parameters + */ +template int registerAllAvailableKernels(); } // namespace Pennylane::Internal +/// @endcond namespace Pennylane { +/** + * @brief These functions are only used to register kernels to the dynamic + * dispatcher. + */ +template struct registerBeforeMain; + +template <> struct registerBeforeMain { + static inline int dummy = + Internal::registerAllAvailableKernels(); +}; + +template <> struct registerBeforeMain { + static inline int dummy = + Internal::registerAllAvailableKernels(); +}; /** * @brief DynamicDispatcher class * - * This class is used to call a gate operation dynamically + * This class calls a gate/generator operation dynamically */ -template class DynamicDispatcher { +template class DynamicDispatcher { public: - using scalar_type_t = fp_t; - using CFP_t = std::complex; + using CFP_t = std::complex; - using Func = std::function * /*data*/, size_t /*num_qubits*/, + using GateFunc = std::function * /*data*/, size_t /*num_qubits*/, const std::vector & /*wires*/, bool /*inverse*/, - const std::vector & /*params*/)>; + const std::vector & /*params*/)>; + + using GeneratorFunc = PrecisionT (*)(std::complex * /*data*/, + size_t /*num_qubits*/, + const std::vector & /*wires*/, + bool /*adjoint*/); private: std::unordered_map gate_wires_; - std::unordered_map kernel_map_; - std::unordered_map, Func, - Pennylane::Internal::PairHash> + std::unordered_map gate_kernel_map_; + std::unordered_map generator_kernel_map_; + + std::unordered_map, GateFunc, + Internal::PairHash> gates_; + std::unordered_map, GeneratorFunc, + Internal::PairHash> + generators_; + + std::string removeGeneratorPrefix(const std::string &op_name) { + constexpr std::string_view prefix = "Generator"; + // TODO: change to string::starts_with in C++20 + if (op_name.rfind(prefix) != 0) { + return op_name; + } + return op_name.substr(prefix.size()); + } + std::string_view removeGeneratorPrefix(std::string_view op_name) { + constexpr std::string_view prefix = "Generator"; + // TODO: change to string::starts_with in C++20 + if (op_name.rfind(prefix) != 0) { + return op_name; + } + return op_name.substr(prefix.size()); + } + DynamicDispatcher() { - namespace Internal = Pennylane::Internal; - for (const auto &[gate_op, n_wires] : Constant::gate_wires) { - gate_wires_.emplace(lookup(Constant::gate_names, gate_op), n_wires); + using Gates::KernelType; + for (const auto &[gate_op, n_wires] : Gates::Constant::gate_wires) { + gate_wires_.emplace( + Util::lookup(Gates::Constant::gate_names, gate_op), n_wires); } - for (const auto &[gate_op, gate_name] : Constant::gate_names) { - KernelType kernel = - lookup(Constant::default_kernel_for_ops, gate_op); - auto implemented_gates = - Internal::implementedGatesForKernel(kernel); + + for (const auto &[gate_op, gate_name] : Gates::Constant::gate_names) { + KernelType kernel = Util::lookup( + Gates::Constant::default_kernel_for_gates, gate_op); + const auto implemented_gates = implementedGatesForKernel(kernel); if (std::find(std::cbegin(implemented_gates), std::cend(implemented_gates), gate_op) == std::cend(implemented_gates)) { PL_ABORT("Default kernel for " + std::string(gate_name) + " does not implement the gate."); } - kernel_map_.emplace(gate_name, kernel); + gate_kernel_map_.emplace(gate_name, kernel); + } + + for (const auto &[gntr_op, gntr_name] : + Gates::Constant::generator_names) { + KernelType kernel = Util::lookup( + Gates::Constant::default_kernel_for_generators, gntr_op); + const auto implemented_generators = + implementedGeneratorsForKernel(kernel); + if (std::find(std::cbegin(implemented_generators), + std::cend(implemented_generators), + gntr_op) == std::cend(implemented_generators)) { + PL_ABORT("Default kernel for " + std::string(gntr_name) + + " does not implement the generator."); + } + generator_kernel_map_.emplace(removeGeneratorPrefix(gntr_name), + kernel); } } @@ -95,13 +167,27 @@ template class DynamicDispatcher { * kernel */ template - void registerGateOperation(const std::string &op_name, KernelType kernel, - FunctionType &&func) { + void registerGateOperation(const std::string &op_name, + Gates::KernelType kernel, FunctionType &&func) { // TODO: Add mutex when we go to multithreading gates_.emplace(std::make_pair(op_name, kernel), std::forward(func)); } + /** + * @brief Register a new gate generator for the operation. Can pass a custom + * kernel + */ + template + void registerGeneratorOperation(const std::string &op_name, + Gates::KernelType kernel, + FunctionType &&func) { + // TODO: Add mutex when we go to multithreading + generators_.emplace( + std::make_pair(removeGeneratorPrefix(op_name), kernel), + std::forward(func)); + } + /** * @brief Apply a single gate to the state-vector using the given kernel. * @@ -113,22 +199,23 @@ template class DynamicDispatcher { * @param inverse Indicates whether to use inverse of gate. * @param params Optional parameter list for parametric gates. */ - void applyOperation(KernelType kernel, CFP_t *data, size_t num_qubits, - const std::string &op_name, + void applyOperation(Gates::KernelType kernel, CFP_t *data, + size_t num_qubits, const std::string &op_name, const std::vector &wires, bool inverse, - const std::vector ¶ms = {}) const { + const std::vector ¶ms = {}) const { const auto iter = gates_.find(std::make_pair(op_name, kernel)); if (iter == gates_.cend()) { throw std::invalid_argument( "Cannot find a gate with a given name \"" + op_name + "\"."); } - - if (const auto requiredWires = gate_wires_.at(op_name); - requiredWires != wires.size()) { + const auto gate_wire_iter = gate_wires_.find(op_name); + if ((gate_wire_iter != gate_wires_.end()) && + (gate_wire_iter->second != wires.size())) { throw std::invalid_argument( std::string("The supplied gate requires ") + - std::to_string(requiredWires) + " wires, but " + + std::to_string(gate_wire_iter->second) + " wires, but " + std::to_string(wires.size()) + " were supplied."); + // TODO: change to std::format in C++20 } (iter->second)(data, num_qubits, wires, inverse, params); } @@ -143,12 +230,12 @@ template class DynamicDispatcher { * @param inverse Indicates whether to use inverse of gate. * @param params Optional parameter list for parametric gates. */ - inline void applyOperation(CFP_t *data, size_t num_qubits, - const std::string &op_name, - const std::vector &wires, bool inverse, - const std::vector ¶ms = {}) const { - const auto kernel_iter = kernel_map_.find(op_name); - if (kernel_iter == kernel_map_.end()) { + inline void + applyOperation(CFP_t *data, size_t num_qubits, const std::string &op_name, + const std::vector &wires, bool inverse, + const std::vector ¶ms = {}) const { + const auto kernel_iter = gate_kernel_map_.find(op_name); + if (kernel_iter == gate_kernel_map_.end()) { PL_ABORT("Kernel for gate " + op_name + " is not registered."); } @@ -170,7 +257,7 @@ template class DynamicDispatcher { const std::vector &ops, const std::vector> &wires, const std::vector &inverse, - const std::vector> ¶ms) { + const std::vector> ¶ms) { const size_t numOperations = ops.size(); if (numOperations != wires.size() || numOperations != params.size()) { throw std::invalid_argument( @@ -185,7 +272,7 @@ template class DynamicDispatcher { } /** - * @brief Apply multiple (non-paramterized) gates to the state-vector + * @brief Apply multiple (non-parameterized) gates to the state-vector * using a registered kernel * * @param data Pointer to data. @@ -193,7 +280,6 @@ template class DynamicDispatcher { * @param ops List of Gate operation names. * @param wires List of wires to apply each gate to. * @param inverse List of inverses - * @param params List of parameters */ void applyOperations(CFP_t *data, size_t num_qubits, const std::vector &ops, @@ -210,158 +296,50 @@ template class DynamicDispatcher { applyOperation(data, num_qubits, ops[i], wires[i], inverse[i], {}); } } -}; -/******************************************************************************* - * The functions below are only used for register kernels to the dynamic - * dispatcher. - ******************************************************************************/ -namespace Internal { -/** - * @brief return a lambda function for the given kernel and gate operation - * - * As we want the lamba function to be stateless, kernel and gate_op are - * template paramters (or the functions can be consteval in C++20). - * In C++20, one also may use a template lambda function instead. - * - * @tparam PrecisionT Floating point precision of underlying statevector data. - * @tparam ParamT Floating point type for parameters - * @tparam kernel Kernel for the gate operation - * @tparam gate_op Gate operation to make a functor - */ -template -constexpr auto gateOpToFunctor() { - return [](std::complex *data, size_t num_qubits, - const std::vector &wires, bool inverse, - const std::vector ¶ms) { - namespace Internal = Pennylane::Internal; - constexpr size_t num_params = - static_lookup(Constant::gate_num_params); - constexpr auto func_ptr = static_lookup( - Internal::GateOpsFuncPtrPairs::value); - // This line is added as static_lookup cannnot raise exception - // statically in GCC 9. - static_assert(func_ptr != nullptr, - "Function pointer for the gate is not " - "included in GateOpsFuncPtrPairs."); - callGateOps(func_ptr, data, num_qubits, wires, inverse, params); - }; -} - -/// @cond DEV -/** - * @brief Internal recursion function for constructGateOpsFunctorTuple - */ -template -constexpr auto constructGateOpsFunctorTupleIter() { - if constexpr (gate_idx == - SelectGateOps::implemented_gates.size()) { - return std::tuple{}; - } else if (gate_idx < - SelectGateOps::implemented_gates.size()) { - constexpr auto gate_op = - SelectGateOps::implemented_gates[gate_idx]; - if constexpr (gate_op == GateOperations::Matrix) { - /* GateOperations::Matrix is not supported for dynamic dispatch now - */ - return constructGateOpsFunctorTupleIter(); - } else { - return prepend_to_tuple( - std::pair{ - gate_op, - gateOpToFunctor()}, - constructGateOpsFunctorTupleIter()); + /** + * @brief Apply a single generator to the state-vector using the given + * kernel. + * + * @param kernel Kernel to run the gate operation. + * @param data Pointer to data. + * @param num_qubits Number of qubits. + * @param op_name Gate operation name. + * @param wires Wires to apply gate to. + * @param adj Indicates whether to use adjoint of gate. + */ + auto applyGenerator(Gates::KernelType kernel, CFP_t *data, + size_t num_qubits, const std::string &op_name, + const std::vector &wires, bool adj) const + -> PrecisionT { + const auto iter = generators_.find(std::make_pair(op_name, kernel)); + if (iter == generators_.cend()) { + throw std::invalid_argument( + "Cannot find a gate with a given name \"" + op_name + "\"."); } + return (iter->second)(data, num_qubits, wires, adj); } -} -/// @endcond -/** - * @brief Generate a tuple of gate operation and function pointer pairs. - * - * @tparam PrecisionT Floating point precision of underlying statevector data - * @tparam ParamT Floating point type of gate parameters - * @tparam kernel Kernel to construct tuple - */ -template -constexpr auto constructGateOpsFunctorTuple() { - return constructGateOpsFunctorTupleIter(); -}; - -/** - * @brief Register all implemented gates for a given kernel - * - * @tparam PrecisionT Floating point precision of underlying statevector data - * @tparam ParamT Floating point type of gate parameters - * @tparam kernel Kernel to construct tuple - */ -template -void registerAllImplementedGateOps() { - auto &dispatcher = DynamicDispatcher::getInstance(); - - constexpr auto gateFunctorPairs = - constructGateOpsFunctorTuple(); - - auto registerGateToDispatcher = [&dispatcher](auto &&gate_op_func_pair) { - const auto &[gate_op, func] = gate_op_func_pair; - std::string op_name = - std::string(lookup(Constant::gate_names, gate_op)); - dispatcher.registerGateOperation(op_name, kernel, func); - return gate_op; - }; - - [[maybe_unused]] const auto registerd_gate_ops = std::apply( - [®isterGateToDispatcher](auto... elt) { - return std::make_tuple(registerGateToDispatcher(elt)...); - }, - gateFunctorPairs); -} + /** + * @brief Apply a single gate to the state-vector using a registered kernel + * + * @param data Pointer to data. + * @param num_qubits Number of qubits. + * @param op_name Gate operation name. + * @param wires Wires to apply gate to. + * @param adj Indicates whether to use adjoint of gate. + */ + inline auto applyGenerator(CFP_t *data, size_t num_qubits, + const std::string &op_name, + const std::vector &wires, bool adj) const + -> PrecisionT { + const auto kernel_iter = generator_kernel_map_.find(op_name); + if (kernel_iter == generator_kernel_map_.end()) { + PL_ABORT("Kernel for gate " + op_name + " is not registered."); + } -/// @cond DEV -/** - * @brief Internal function to iterate over all available kerenls in - * the compile time - */ -template -void registerKernelIter() { - if constexpr (idx == Constant::available_kernels.size()) { - return; - } else { - registerAllImplementedGateOps( - Constant::available_kernels[idx])>(); - registerKernelIter(); + return applyGenerator(kernel_iter->second, data, num_qubits, op_name, + wires, adj); } -} -/// @endcond - -/** - * @brief Register all implemented gates for all available kernels. - * - * @tparam PrecisionT Floating point precision of underlying statevector data. - * @tparam ParamT Floating point type for parameters - */ -template -auto registerAllAvailableKernels() -> int { - registerKernelIter(); - return 0; -} -} // namespace Internal - -template struct registerBeforeMain {}; - -template <> struct registerBeforeMain { - static inline int dummy = - Internal::registerAllAvailableKernels(); }; - -template <> struct registerBeforeMain { - static inline int dummy = - Internal::registerAllAvailableKernels(); -}; - } // namespace Pennylane diff --git a/pennylane_lightning/src/simulator/GateOperations.hpp b/pennylane_lightning/src/simulator/GateOperations.hpp deleted file mode 100644 index 5cbcc82338..0000000000 --- a/pennylane_lightning/src/simulator/GateOperations.hpp +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2021 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -/** - * @file - * Defines a template class for choosing a Gate operations - */ -#pragma once -#include -#include -#include - -namespace Pennylane { -/** - * @brief enum class for all gate operations - * - * When you add a gate in this enum, please sure that gate_num_params is also - * updated accordingly. - * */ -enum class GateOperations : int { - BEGIN = 0, - /* Single-qubit gates */ - PauliX = 0, - PauliY, - PauliZ, - Hadamard, - S, - T, - PhaseShift, - RX, - RY, - RZ, - Rot, - /* Two-qubit gates */ - CNOT, - CY, - CZ, - SWAP, - ControlledPhaseShift, - CRX, - CRY, - CRZ, - CRot, - /* Three-qubit gates */ - Toffoli, - CSWAP, - /* Gate generators (only used internally for adjoint diff) */ - GeneratorPhaseShift, - GeneratorCRX, - GeneratorCRY, - GeneratorCRZ, - GeneratorControlledPhaseShift, - /* General matrix */ - Matrix, - /* END (placeholder) */ - END -}; -} // namespace Pennylane - -namespace Pennylane::Constant { -constexpr std::array, - static_cast(GateOperations::END)> - gate_names = { - std::pair{GateOperations::PauliX, "PauliX"}, - std::pair{GateOperations::PauliY, "PauliY"}, - std::pair{GateOperations::PauliZ, "PauliZ"}, - std::pair{GateOperations::Hadamard, "Hadamard"}, - std::pair{GateOperations::S, "S"}, - std::pair{GateOperations::T, "T"}, - std::pair{GateOperations::PhaseShift, "PhaseShift"}, - std::pair{GateOperations::RX, "RX"}, - std::pair{GateOperations::RY, "RY"}, - std::pair{GateOperations::RZ, "RZ"}, - std::pair{GateOperations::Rot, "Rot"}, - std::pair{GateOperations::CNOT, "CNOT"}, - std::pair{GateOperations::CY, "CY"}, - std::pair{GateOperations::CZ, "CZ"}, - std::pair{GateOperations::SWAP, "SWAP"}, - std::pair{GateOperations::ControlledPhaseShift, "ControlledPhaseShift"}, - std::pair{GateOperations::CRX, "CRX"}, - std::pair{GateOperations::CRY, "CRY"}, - std::pair{GateOperations::CRZ, "CRZ"}, - std::pair{GateOperations::CRot, "CRot"}, - std::pair{GateOperations::Toffoli, "Toffoli"}, - std::pair{GateOperations::CSWAP, "CSWAP"}, - std::pair{GateOperations::GeneratorPhaseShift, "GeneratrorPhaseShift"}, - std::pair{GateOperations::GeneratorCRX, "GeneratrorCRX"}, - std::pair{GateOperations::GeneratorCRY, "GeneratrorCRY"}, - std::pair{GateOperations::GeneratorCRZ, "GeneratrorCRZ"}, - std::pair{GateOperations::GeneratorControlledPhaseShift, - "GeneratrorControlledPhaseShift"}, - std::pair{GateOperations::Matrix, "Matrix"}, -}; - -constexpr std::array, - static_cast(GateOperations::END) - 1> - gate_wires = { - std::pair{GateOperations::PauliX, 1}, - std::pair{GateOperations::PauliY, 1}, - std::pair{GateOperations::PauliZ, 1}, - std::pair{GateOperations::Hadamard, 1}, - std::pair{GateOperations::S, 1}, - std::pair{GateOperations::T, 1}, - std::pair{GateOperations::PhaseShift, 1}, - std::pair{GateOperations::RX, 1}, - std::pair{GateOperations::RY, 1}, - std::pair{GateOperations::RZ, 1}, - std::pair{GateOperations::Rot, 1}, - std::pair{GateOperations::CNOT, 2}, - std::pair{GateOperations::CY, 2}, - std::pair{GateOperations::CZ, 2}, - std::pair{GateOperations::SWAP, 2}, - std::pair{GateOperations::ControlledPhaseShift, 2}, - std::pair{GateOperations::CRX, 2}, - std::pair{GateOperations::CRY, 2}, - std::pair{GateOperations::CRZ, 2}, - std::pair{GateOperations::CRot, 2}, - std::pair{GateOperations::Toffoli, 3}, - std::pair{GateOperations::CSWAP, 3}, - std::pair{GateOperations::GeneratorPhaseShift, 1}, - std::pair{GateOperations::GeneratorCRX, 2}, - std::pair{GateOperations::GeneratorCRY, 2}, - std::pair{GateOperations::GeneratorCRZ, 2}, - std::pair{GateOperations::GeneratorControlledPhaseShift, 2}, -}; - -constexpr std::array, - static_cast(GateOperations::END) - 1> - gate_num_params = { - std::pair{GateOperations::PauliX, 0}, - std::pair{GateOperations::PauliY, 0}, - std::pair{GateOperations::PauliZ, 0}, - std::pair{GateOperations::Hadamard, 0}, - std::pair{GateOperations::S, 0}, - std::pair{GateOperations::T, 0}, - std::pair{GateOperations::PhaseShift, 1}, - std::pair{GateOperations::RX, 1}, - std::pair{GateOperations::RY, 1}, - std::pair{GateOperations::RZ, 1}, - std::pair{GateOperations::Rot, 3}, - std::pair{GateOperations::CNOT, 0}, - std::pair{GateOperations::CY, 0}, - std::pair{GateOperations::CZ, 0}, - std::pair{GateOperations::SWAP, 0}, - std::pair{GateOperations::ControlledPhaseShift, 1}, - std::pair{GateOperations::CRX, 1}, - std::pair{GateOperations::CRY, 1}, - std::pair{GateOperations::CRZ, 1}, - std::pair{GateOperations::CRot, 3}, - std::pair{GateOperations::Toffoli, 0}, - std::pair{GateOperations::CSWAP, 0}, - std::pair{GateOperations::GeneratorPhaseShift, 0}, - std::pair{GateOperations::GeneratorCRX, 0}, - std::pair{GateOperations::GeneratorCRY, 0}, - std::pair{GateOperations::GeneratorCRZ, 0}, - std::pair{GateOperations::GeneratorControlledPhaseShift, 0}, -}; -} // namespace Pennylane::Constant diff --git a/pennylane_lightning/src/simulator/GateOperationsLM.hpp b/pennylane_lightning/src/simulator/GateOperationsLM.hpp deleted file mode 100644 index 54760dba73..0000000000 --- a/pennylane_lightning/src/simulator/GateOperationsLM.hpp +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2021 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -/** - * @file GateOperationsLM.hpp - * Defines kernel functions with less memory (and fast) - */ -#pragma once - -#include "Error.hpp" -#include "GateOperations.hpp" -#include "Gates.hpp" -#include "KernelType.hpp" -#include "Util.hpp" - -#include -#include -#include - -namespace Pennylane { -/** - * @brief Fill ones from LSB to rev_wire - */ -auto constexpr fillTrailingOnes(size_t pos) -> size_t { - return (pos == 0) ? 0 : (~size_t(0) >> (CHAR_BIT * sizeof(size_t) - pos)); -} -/** - * @brief Fill ones from MSB to pos - */ -auto constexpr fillLeadingOnes(size_t pos) -> size_t { - return (~size_t(0)) << pos; -} - -/** - * @brief A gate operation implementation with less memory. - * - * We use a bitwise operation to calculate the indices where the gate - * applies to on the fly. - * - * @tparam fp_t Floating point precision of underlying statevector data - */ -template class GateOperationsLM { - public: - using scalar_type_t = fp_t; - using CFP_t = std::complex; - - constexpr static KernelType kernel_id = KernelType::LM; - - constexpr static std::array implemented_gates = { - GateOperations::PauliX, - GateOperations::PauliY, - GateOperations::PauliZ, - GateOperations::Hadamard, - GateOperations::S, - GateOperations::T, - GateOperations::RX, - GateOperations::RY, - GateOperations::RZ, - GateOperations::PhaseShift, - GateOperations::Rot, - GateOperations::CZ, - GateOperations::CNOT, - GateOperations::SWAP, - GateOperations::GeneratorPhaseShift}; - - private: - static inline void applySingleQubitOp(CFP_t *arr, size_t num_qubits, - const CFP_t *op_matrix, size_t wire) { - const size_t rev_wire = num_qubits - wire - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - for (size_t n = 0; n < Util::exp2(num_qubits - 1); n++) { - const size_t k = n; - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - const CFP_t v0 = arr[i0]; - const CFP_t v1 = arr[i1]; - arr[i0] = op_matrix[0B00] * v0 + - op_matrix[0B01] * v1; // NOLINT(readability-magic-numbers) - arr[i1] = op_matrix[0B10] * v0 + - op_matrix[0B11] * v1; // NOLINT(readability-magic-numbers) - } - } - - public: - static void applyMatrix(CFP_t *arr, size_t num_qubits, const CFP_t *matrix, - const std::vector &wires, bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(matrix); - static_cast(wires); - static_cast(inverse); - PL_ABORT("Called unimplemented gate operation applyMatrix."); - } - - static void applyPauliX(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - std::swap(arr[i0], arr[i1]); - } - } - - static void applyPauliY(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - const auto v0 = arr[i0]; - const auto v1 = arr[i1]; - arr[i0] = {std::imag(v1), -std::real(v1)}; - arr[i1] = {-std::imag(v0), std::real(v0)}; - } - } - - static void applyPauliZ(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - arr[i1] *= -1; - } - } - - static void applyHadamard(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - constexpr fp_t isqrt2 = Util::INVSQRT2(); - constexpr static std::array hadamardMat = {isqrt2, isqrt2, - isqrt2, -isqrt2}; - applySingleQubitOp(arr, num_qubits, hadamardMat.data(), wires[0]); - } - - static void applyS(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - const CFP_t shift = - (inverse) ? -Util::IMAG() : Util::IMAG(); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - arr[i1] *= shift; - } - } - - static void applyT(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - const CFP_t shift = - (inverse) - ? std::conj(std::exp(CFP_t(0, static_cast(M_PI / 4)))) - : std::exp(CFP_t(0, static_cast(M_PI / 4))); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - arr[i1] *= shift; - } - } - - template - static void applyRX(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - - const fp_t c = std::cos(angle / 2); - const fp_t js = - (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); - - const std::array RXMat = {c, Util::IMAG() * js, - Util::IMAG() * js, c}; - applySingleQubitOp(arr, num_qubits, RXMat.data(), wires[0]); - } - - template - static void applyRY(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - - const fp_t c = std::cos(angle / 2); - const fp_t s = (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); - - const std::array RYMat = {c, -s, s, c}; - applySingleQubitOp(arr, num_qubits, RYMat.data(), wires[0]); - } - - template - static void applyRZ(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - - const CFP_t first = CFP_t{std::cos(angle / 2), -std::sin(angle / 2)}; - const CFP_t second = CFP_t{std::cos(angle / 2), std::sin(angle / 2)}; - - const std::array shifts = { - (inverse) ? std::conj(first) : first, - (inverse) ? std::conj(second) : second}; - const size_t rev_wire = num_qubits - wires[0] - 1; - - for (size_t k = 0; k < Util::exp2(num_qubits); k++) { - arr[k] *= shifts[(k >> rev_wire) & 1U]; - } - } - - template - static void applyPhaseShift(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t rev_wire_shift = (static_cast(1U) << rev_wire); - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - const CFP_t s = - inverse ? std::exp(-CFP_t(0, angle)) : std::exp(CFP_t(0, angle)); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - const size_t i1 = i0 | rev_wire_shift; - arr[i1] *= s; - } - } - - template - static void applyRot(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t phi, Param_t theta, Param_t omega) { - assert(wires.size() == 1); - - const auto rotMat = (inverse) - ? Gates::getRot(-omega, -theta, -phi) - : Gates::getRot(phi, theta, omega); - - applySingleQubitOp(arr, num_qubits, rotMat.data(), wires[0]); - } - - static void applyCNOT(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - - const size_t rev_wire0 = num_qubits - wires[1] - 1; - const size_t rev_wire1 = num_qubits - wires[0] - 1; // Controll qubit - - const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; - const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; - - const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); - const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); - - const size_t parity_low = fillTrailingOnes(rev_wire_min); - const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); - const size_t parity_middle = - fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); - - /* This is faster than iterate over all indices */ - for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { - const size_t i00 = ((k << 2U) & parity_high) | - ((k << 1U) & parity_middle) | (k & parity_low); - const size_t i10 = i00 | rev_wire1_shift; - const size_t i11 = i00 | rev_wire1_shift | rev_wire0_shift; - - std::swap(arr[i10], arr[i11]); - } - } - - static void applyCY(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyCY is not implemented yet"); - } - - static void applyCZ(CFP_t *arr, const size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - - const size_t rev_wire0 = num_qubits - wires[1] - 1; - const size_t rev_wire1 = num_qubits - wires[0] - 1; // Controll qubit - - const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; - const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; - - const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); - const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); - - const size_t parity_low = fillTrailingOnes(rev_wire_min); - const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); - const size_t parity_middle = - fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); - - /* This is faster than iterate over all indices */ - for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { - const size_t i00 = ((k << 2U) & parity_high) | - ((k << 1U) & parity_middle) | (k & parity_low); - const size_t i11 = i00 | rev_wire0_shift | rev_wire1_shift; - arr[i11] *= -1; - } - } - - static void applySWAP(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - - const size_t rev_wire0 = num_qubits - wires[1] - 1; - const size_t rev_wire1 = num_qubits - wires[0] - 1; // Controll qubit - - const size_t rev_wire0_shift = static_cast(1U) << rev_wire0; - const size_t rev_wire1_shift = static_cast(1U) << rev_wire1; - - const size_t rev_wire_min = std::min(rev_wire0, rev_wire1); - const size_t rev_wire_max = std::max(rev_wire0, rev_wire1); - - const size_t parity_low = fillTrailingOnes(rev_wire_min); - const size_t parity_high = fillLeadingOnes(rev_wire_max + 1); - const size_t parity_middle = - fillLeadingOnes(rev_wire_min + 1) & fillTrailingOnes(rev_wire_max); - - for (size_t k = 0; k < Util::exp2(num_qubits - 2); k++) { - const size_t i00 = ((k << 2U) & parity_high) | - ((k << 1U) & parity_middle) | (k & parity_low); - const size_t i10 = i00 | rev_wire1_shift; - const size_t i01 = i00 | rev_wire0_shift; - std::swap(arr[i10], arr[i01]); - } - } - - template - static void applyControlledPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse, - Param_t angle) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - static_cast(angle); - PL_ABORT("GaterOperationsLM::applyControlledPhaseShift is not " - "implemented yet"); - } - - template - static void applyCRX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse, Param_t angle) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - static_cast(angle); - PL_ABORT("GaterOperationsLM::applyCRX is not implemented yet"); - } - - template - static void applyCRY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse, Param_t angle) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - static_cast(angle); - PL_ABORT("GaterOperationsLM::applyCRY is not implemented yet"); - } - - template - static void applyCRZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse, Param_t angle) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - static_cast(angle); - PL_ABORT("GaterOperationsLM::applyCRZ is not implemented yet"); - } - - template - static void applyCRot(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse, Param_t phi, - Param_t theta, Param_t omega) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - static_cast(phi); - static_cast(theta); - static_cast(omega); - PL_ABORT("GaterOperationsLM::applyCRot is not implemented yet"); - } - - static void applyToffoli(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyTofolli is not implemented yet"); - } - - static void applyCSWAP(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyCSWAP is not implemented yet"); - } - - static void applyGeneratorPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const size_t rev_wire = num_qubits - wires[0] - 1; - const size_t wire_parity = fillTrailingOnes(rev_wire); - const size_t wire_parity_inv = fillLeadingOnes(rev_wire + 1); - - for (size_t k = 0; k < Util::exp2(num_qubits - 1); k++) { - const size_t i0 = ((k << 1U) & wire_parity_inv) | (wire_parity & k); - arr[i0] = Util::ZERO(); - } - } - - static void applyGeneratorCRX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyGeneratorCRX is not implemented yet"); - } - - static void applyGeneratorCRY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyGeneratorCRY is not implemented yet"); - } - static void applyGeneratorCRZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyGeneratorCRZ is not implemented yet"); - } - static void - applyGeneratorControlledPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - static_cast(arr); - static_cast(num_qubits); - static_cast(wires); - PL_ABORT("GaterOperationsLM::applyGeneratorControlledPhaseShift is not " - "implemented yet"); - } -}; - -} // namespace Pennylane diff --git a/pennylane_lightning/src/simulator/GateOperationsPI.hpp b/pennylane_lightning/src/simulator/GateOperationsPI.hpp deleted file mode 100644 index 91aa2bd857..0000000000 --- a/pennylane_lightning/src/simulator/GateOperationsPI.hpp +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright 2021 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -/** - * @file GateOperationsPI.hpp - * Defines gate operations with precomputed indicies - */ -#pragma once - -/// @cond DEV -// Required for compilation with MSVC -#ifndef _USE_MATH_DEFINES -#define _USE_MATH_DEFINES // for C++ -#endif -/// @endcond - -#include "GateOperations.hpp" -#include "Gates.hpp" -#include "IndicesUtil.hpp" -#include "KernelType.hpp" - -#include -#include - -namespace Pennylane { -/** - * @brief Kernel functions for gate operations with precomputed indices - * - * For given wires, we first compute the indices the gate applies to and use - * the computed indices to apply the operation. - * - * @tparam fp_t Floating point precision of underlying statevector data. - * */ -template class GateOperationsPI { - private: - using GateIndices = IndicesUtil::GateIndices; - - public: - using scalar_type_t = fp_t; - using CFP_t = std::complex; - - constexpr static KernelType kernel_id = KernelType::PI; - - constexpr static std::array implemented_gates = { - GateOperations::PauliX, - GateOperations::PauliY, - GateOperations::PauliZ, - GateOperations::Hadamard, - GateOperations::S, - GateOperations::T, - GateOperations::RX, - GateOperations::RY, - GateOperations::RZ, - GateOperations::PhaseShift, - GateOperations::Rot, - GateOperations::ControlledPhaseShift, - GateOperations::CNOT, - GateOperations::CY, - GateOperations::CZ, - GateOperations::SWAP, - GateOperations::CRX, - GateOperations::CRY, - GateOperations::CRZ, - GateOperations::CRot, - GateOperations::Toffoli, - GateOperations::CSWAP, - GateOperations::Matrix, - GateOperations::GeneratorPhaseShift, - GateOperations::GeneratorCRX, - GateOperations::GeneratorCRY, - GateOperations::GeneratorCRZ, - GateOperations::GeneratorControlledPhaseShift}; - - /** - * @brief Apply a given matrix directly to the statevector. - * - * @param matrix Perfect square matrix in row-major order. - * @param indices Internal indices participating in the operation. - * @param externalIndices External indices unaffected by the operation. - * @param inverse Indicate whether inverse should be taken. - */ - static void applyMatrix(CFP_t *arr, size_t num_qubits, const CFP_t *matrix, - const std::vector &wires, bool inverse) { - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - std::vector v(indices.size()); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - // Gather - size_t pos = 0; - for (const size_t &index : indices) { - v[pos] = shiftedState[index]; - pos++; - } - - // Apply + scatter - if (inverse) { - for (size_t i = 0; i < indices.size(); i++) { - size_t index = indices[i]; - shiftedState[index] = 0; - - for (size_t j = 0; j < indices.size(); j++) { - const size_t baseIndex = j * indices.size(); - shiftedState[index] += - std::conj(matrix[baseIndex + i]) * v[j]; - } - } - } else { - for (size_t i = 0; i < indices.size(); i++) { - size_t index = indices[i]; - shiftedState[index] = 0; - - const size_t baseIndex = i * indices.size(); - for (size_t j = 0; j < indices.size(); j++) { - shiftedState[index] += matrix[baseIndex + j] * v[j]; - } - } - } - } - } - - /** - * @brief Apply a given matrix directly to the statevector. - * - * @param matrix Perfect square matrix in row-major order. - * @param indices Internal indices participating in the operation. - * @param externalIndices External indices unaffected by the operation. - * @param inverse Indicate whether inverse should be taken. - */ - static void applyMatrix(CFP_t *arr, size_t num_qubits, - const std::vector &matrix, - const std::vector &wires, bool inverse) { - if (matrix.size() != Util::exp2(2 * wires.size())) { - throw std::invalid_argument( - "The size of matrix does not match with the given " - "number of wires"); - } - applyMatrix(arr, num_qubits, matrix.data(), wires, inverse); - } - - /* Single qubit operators */ - static void applyPauliX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - std::swap(shiftedState[indices[0]], shiftedState[indices[1]]); - } - } - - static void applyPauliY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - CFP_t v0 = shiftedState[indices[0]]; - shiftedState[indices[0]] = CFP_t{shiftedState[indices[1]].imag(), - -shiftedState[indices[1]].real()}; - shiftedState[indices[1]] = CFP_t{-v0.imag(), v0.real()}; - } - } - - static void applyPauliZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[1]] = -shiftedState[indices[1]]; - } - } - - static void applyHadamard(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - - const CFP_t v0 = shiftedState[indices[0]]; - const CFP_t v1 = shiftedState[indices[1]]; - - shiftedState[indices[0]] = Util::INVSQRT2() * (v0 + v1); - shiftedState[indices[1]] = Util::INVSQRT2() * (v0 - v1); - } - } - - static void applyS(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - const CFP_t shift = - (inverse) ? -Util::IMAG() : Util::IMAG(); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[1]] *= shift; - } - } - - static void applyT(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const CFP_t shift = - (inverse) - ? std::conj(std::exp(CFP_t(0, static_cast(M_PI / 4)))) - : std::exp(CFP_t(0, static_cast(M_PI / 4))); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[1]] *= shift; - } - } - - /* Single qubit operators with a parameter */ - template - static void applyRX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const fp_t c = std::cos(angle / 2); - const fp_t js = - (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[0]]; - const CFP_t v1 = shiftedState[indices[1]]; - shiftedState[indices[0]] = - c * v0 + js * CFP_t{-v1.imag(), v1.real()}; - shiftedState[indices[1]] = - js * CFP_t{-v0.imag(), v0.real()} + c * v1; - } - } - - template - static void applyRY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const fp_t c = std::cos(angle / 2); - const fp_t s = (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[0]]; - const CFP_t v1 = shiftedState[indices[1]]; - shiftedState[indices[0]] = c * v0 - s * v1; - shiftedState[indices[1]] = s * v0 + c * v1; - } - } - - template - static void applyRZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const CFP_t first = CFP_t(std::cos(angle / 2), -std::sin(angle / 2)); - const CFP_t second = CFP_t(std::cos(angle / 2), std::sin(angle / 2)); - const CFP_t shift1 = (inverse) ? std::conj(first) : first; - const CFP_t shift2 = (inverse) ? std::conj(second) : second; - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[0]] *= shift1; - shiftedState[indices[1]] *= shift2; - } - } - - template - static void applyPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - const CFP_t s = inverse ? std::conj(std::exp(CFP_t(0, angle))) - : std::exp(CFP_t(0, angle)); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[1]] *= s; - } - } - - template - static void applyRot(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t phi, Param_t theta, Param_t omega) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const std::vector rot = Gates::getRot(phi, theta, omega); - - const CFP_t t1 = (inverse) ? std::conj(rot[0]) : rot[0]; - const CFP_t t2 = (inverse) ? -rot[1] : rot[1]; - const CFP_t t3 = (inverse) ? -rot[2] : rot[2]; - const CFP_t t4 = (inverse) ? std::conj(rot[3]) : rot[3]; - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[0]]; - const CFP_t v1 = shiftedState[indices[1]]; - shiftedState[indices[0]] = t1 * v0 + t2 * v1; - shiftedState[indices[1]] = t3 * v0 + t4 * v1; - } - } - - /* Two qubit operators */ - static void applyCNOT(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - std::swap(shiftedState[indices[2]], shiftedState[indices[3]]); - } - } - - static void applySWAP(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - std::swap(shiftedState[indices[1]], shiftedState[indices[2]]); - } - } - - static void applyCY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - CFP_t v2 = shiftedState[indices[2]]; - shiftedState[indices[2]] = CFP_t{shiftedState[indices[3]].imag(), - -shiftedState[indices[3]].real()}; - shiftedState[indices[3]] = CFP_t{-v2.imag(), v2.real()}; - } - } - - static void applyCZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[3]] *= -1; - } - } - - /* Two qubit operators with a parameter */ - template - static void applyControlledPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - bool inverse, Param_t angle) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const CFP_t s = inverse ? std::conj(std::exp(CFP_t(0, angle))) - : std::exp(CFP_t(0, angle)); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[3]] *= s; - } - } - - template - static void applyCRX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const fp_t c = std::cos(angle / 2); - const fp_t js = - (inverse) ? -std::sin(-angle / 2) : std::sin(-angle / 2); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[2]]; - const CFP_t v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = - c * v0 + js * CFP_t{-v1.imag(), v1.real()}; - shiftedState[indices[3]] = - js * CFP_t{-v0.imag(), v0.real()} + c * v1; - } - } - - template - static void applyCRY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - const fp_t c = std::cos(angle / 2); - const fp_t s = (inverse) ? -std::sin(angle / 2) : std::sin(angle / 2); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[2]]; - const CFP_t v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = c * v0 - s * v1; - shiftedState[indices[3]] = s * v0 + c * v1; - } - } - - template - static void applyCRZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t angle) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - const CFP_t m00 = - (inverse) ? CFP_t(std::cos(angle / 2), std::sin(angle / 2)) - : CFP_t(std::cos(angle / 2), -std::sin(angle / 2)); - const CFP_t m11 = (inverse) - ? CFP_t(std::cos(angle / 2), -std::sin(angle / 2)) - : CFP_t(std::cos(angle / 2), std::sin(angle / 2)); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[2]] *= m00; - shiftedState[indices[3]] *= m11; - } - } - - template - static void applyCRot(CFP_t *arr, size_t num_qubits, - const std::vector &wires, bool inverse, - Param_t phi, Param_t theta, Param_t omega) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - const auto rot = Gates::getRot(phi, theta, omega); - - const CFP_t t1 = (inverse) ? std::conj(rot[0]) : rot[0]; - const CFP_t t2 = (inverse) ? -rot[1] : rot[1]; - const CFP_t t3 = (inverse) ? -rot[2] : rot[2]; - const CFP_t t4 = (inverse) ? std::conj(rot[3]) : rot[3]; - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const CFP_t v0 = shiftedState[indices[2]]; - const CFP_t v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = t1 * v0 + t2 * v1; - shiftedState[indices[3]] = t3 * v0 + t4 * v1; - } - } - - /* Three-qubit gate */ - static void applyToffoli(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 3); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - // Participating swapped indices - static const size_t op_idx0 = 6; - static const size_t op_idx1 = 7; - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - std::swap(shiftedState[indices[op_idx0]], - shiftedState[indices[op_idx1]]); - } - } - - static void applyCSWAP(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool inverse) { - assert(wires.size() == 3); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - // Participating swapped indices - static const size_t op_idx0 = 5; - static const size_t op_idx1 = 6; - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - std::swap(shiftedState[indices[op_idx0]], - shiftedState[indices[op_idx1]]); - } - } - - /* Gate generators */ - static void applyGeneratorPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool adj) { - assert(wires.size() == 1); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[0]] = CFP_t{0.0, 0.0}; - } - } - - static void applyGeneratorCRX(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool adj) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[0]] = Util::ZERO(); - shiftedState[indices[1]] = Util::ZERO(); - - std::swap(shiftedState[indices[2]], shiftedState[indices[3]]); - } - } - - static void applyGeneratorCRY(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool adj) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - const auto v0 = shiftedState[indices[2]]; - shiftedState[indices[0]] = Util::ZERO(); - shiftedState[indices[1]] = Util::ZERO(); - shiftedState[indices[2]] = - -Util::IMAG() * shiftedState[indices[3]]; - shiftedState[indices[3]] = Util::IMAG() * v0; - } - } - - static void applyGeneratorCRZ(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool adj) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[0]] = Util::ZERO(); - shiftedState[indices[1]] = Util::ZERO(); - shiftedState[indices[3]] *= -1; - } - } - - static void - applyGeneratorControlledPhaseShift(CFP_t *arr, size_t num_qubits, - const std::vector &wires, - [[maybe_unused]] bool adj) { - assert(wires.size() == 2); - const auto [indices, externalIndices] = GateIndices(wires, num_qubits); - - for (const size_t &externalIndex : externalIndices) { - CFP_t *shiftedState = arr + externalIndex; - shiftedState[indices[0]] = 0; - shiftedState[indices[1]] = 0; - shiftedState[indices[2]] = 0; - } - } -}; -} // namespace Pennylane diff --git a/pennylane_lightning/src/simulator/IndicesUtil.cpp b/pennylane_lightning/src/simulator/IndicesUtil.cpp deleted file mode 100644 index 5973c10b93..0000000000 --- a/pennylane_lightning/src/simulator/IndicesUtil.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "IndicesUtil.hpp" - -#include "Util.hpp" - -namespace Pennylane::IndicesUtil { - -auto getIndicesAfterExclusion(const std::vector &indicesToExclude, - size_t num_qubits) -> std::vector { - std::set indices; - for (size_t i = 0; i < num_qubits; i++) { - indices.emplace(i); - } - for (const size_t &excludedIndex : indicesToExclude) { - indices.erase(excludedIndex); - } - return {indices.begin(), indices.end()}; -} - -auto generateBitPatterns(const std::vector &qubitIndices, - size_t num_qubits) -> std::vector { - std::vector indices; - indices.reserve(Util::exp2(qubitIndices.size())); - indices.emplace_back(0); - - for (auto index_it = qubitIndices.rbegin(); index_it != qubitIndices.rend(); - index_it++) { - const size_t value = Util::maxDecimalForQubit(*index_it, num_qubits); - const size_t currentSize = indices.size(); - for (size_t j = 0; j < currentSize; j++) { - indices.emplace_back(indices[j] + value); - } - } - return indices; -} - -} // namespace Pennylane::IndicesUtil diff --git a/pennylane_lightning/src/simulator/KernelType.hpp b/pennylane_lightning/src/simulator/KernelType.hpp deleted file mode 100644 index 687bee091b..0000000000 --- a/pennylane_lightning/src/simulator/KernelType.hpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -/** - * @file - * Defines possible kernel types as enum and define python export. - */ -#pragma once -#include "Error.hpp" -#include "Util.hpp" - -#include -#include -#include -#include - -namespace Pennylane { -enum class KernelType { PI, LM, Unknown }; - -namespace Constant { -constexpr std::array available_kernels = { - std::pair{KernelType::LM, "LM"}, - std::pair{KernelType::PI, "PI"}, -}; - -[[maybe_unused]] constexpr std::array kernels_to_pyexport = {KernelType::PI, - KernelType::LM}; -} // namespace Constant - -constexpr auto string_to_kernel(std::string_view str) -> KernelType { - for (const auto &[k, v] : Constant::available_kernels) { - if (v == str) { - return k; - } - } - // TODO: Throw exception or call PL_ABORT in GCC >= 9 - return KernelType::Unknown; -} - -/// @cond DEV -/******************************************************************************* - * The functions below are only used in the compile time to check - * internal consistency. - ******************************************************************************/ -constexpr auto is_available_kernel(KernelType kernel) -> bool { - // TODO: change to constexpr std::any_of in C++20 - // NOLINTNEXTLINE (readability-use-anyofallof) - for (const auto &[avail_kernel, avail_kernel_name] : - Constant::available_kernels) { - if (kernel == avail_kernel) { - return true; - } - } - return false; -} - -constexpr auto check_kernels_to_pyexport() -> bool { - // TODO: change to constexpr std::any_of in C++20 - // NOLINTNEXTLINE (readability-use-anyofallof) - for (const auto &kernel : Constant::kernels_to_pyexport) { - if (!is_available_kernel(kernel)) { - return false; - } - } - return true; -} -static_assert(check_kernels_to_pyexport(), - "Some of Kernels in Python export is not available."); -/// @endcond -} // namespace Pennylane diff --git a/pennylane_lightning/src/simulator/Measures.hpp b/pennylane_lightning/src/simulator/Measures.hpp index 194b7447be..26208b6ba1 100644 --- a/pennylane_lightning/src/simulator/Measures.hpp +++ b/pennylane_lightning/src/simulator/Measures.hpp @@ -90,11 +90,10 @@ class Measures { size_t num_qubits = original_statevector.getNumQubits(); const std::vector all_indices = - IndicesUtil::generateBitPatterns(sorted_wires, num_qubits); - const std::vector all_offsets = - IndicesUtil::generateBitPatterns( - IndicesUtil::getIndicesAfterExclusion(sorted_wires, num_qubits), - num_qubits); + Gates::generateBitPatterns(sorted_wires, num_qubits); + const std::vector all_offsets = Gates::generateBitPatterns( + Gates::getIndicesAfterExclusion(sorted_wires, num_qubits), + num_qubits); std::vector probabilities(all_indices.size(), 0); @@ -105,8 +104,8 @@ class Measures { } ind_probs++; } - // Transposing the probabilites tensor with the indices determined at - // the begining. + // Transposing the probabilities tensor with the indices determined at + // the beginning. if (wires != sorted_wires) { probabilities = Util::transpose_state_tensor(probabilities, sorted_ind_wires); @@ -209,7 +208,7 @@ class Measures { /** * @brief Variance of an observable. * - * @param operation Square matrix in row-major order. + * @param matrix Square matrix in row-major order. * @param wires Wires where to apply the operator. * @return Floating point with the variance of the observables. */ diff --git a/pennylane_lightning/src/simulator/SelectGateOps.hpp b/pennylane_lightning/src/simulator/SelectGateOps.hpp deleted file mode 100644 index 3dc33a366b..0000000000 --- a/pennylane_lightning/src/simulator/SelectGateOps.hpp +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2021 Xanadu Quantum Technologies Inc. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -/** - * @file SelectGateOps.hpp - * Defines a template class for choosing a Gate operations - */ -#pragma once - -#include "ConstantUtil.hpp" -#include "GateOperations.hpp" -#include "GateOperationsLM.hpp" -#include "GateOperationsPI.hpp" -#include "KernelType.hpp" -#include "Macros.hpp" - -#include -#include -#include - -namespace Pennylane { - -namespace Constant { -/** - * @brief Define which kernel to use for each operation - * - * This value is used for: - * 1) StateVector apply##GATE_NAME methods. The kernel function is statically - * binded to the given kernel and cannot be modified. 2) Default kernel - * functions of StateVector applyOperation(opName, ...) methods. The kernel - * function is dynamically binded and can be changed using DynamicDispatcher - * singleton class. 3) Python binding. - * - * TODO: Change to constexpr - * Map(https://www.youtube.com/watch?v=INn3xa4pMfg&list=WL&index=9) in C++20? - */ -constexpr std::array, - static_cast(GateOperations::END)> - default_kernel_for_ops = { - std::pair{GateOperations::PauliX, KernelType::LM}, - std::pair{GateOperations::PauliY, KernelType::LM}, - std::pair{GateOperations::PauliZ, KernelType::LM}, - std::pair{GateOperations::Hadamard, KernelType::PI}, - std::pair{GateOperations::S, KernelType::LM}, - std::pair{GateOperations::T, KernelType::LM}, - std::pair{GateOperations::RX, KernelType::PI}, - std::pair{GateOperations::RY, KernelType::PI}, - std::pair{GateOperations::RZ, KernelType::LM}, - std::pair{GateOperations::PhaseShift, KernelType::LM}, - std::pair{GateOperations::Rot, KernelType::LM}, - std::pair{GateOperations::ControlledPhaseShift, KernelType::PI}, - std::pair{GateOperations::CNOT, KernelType::PI}, - std::pair{GateOperations::CY, KernelType::PI}, - std::pair{GateOperations::CZ, KernelType::PI}, - std::pair{GateOperations::SWAP, KernelType::PI}, - std::pair{GateOperations::CRX, KernelType::PI}, - std::pair{GateOperations::CRY, KernelType::PI}, - std::pair{GateOperations::CRZ, KernelType::PI}, - std::pair{GateOperations::CRot, KernelType::PI}, - std::pair{GateOperations::Toffoli, KernelType::PI}, - std::pair{GateOperations::CSWAP, KernelType::PI}, - std::pair{GateOperations::Matrix, KernelType::PI}, - std::pair{GateOperations::GeneratorPhaseShift, KernelType::PI}, - std::pair{GateOperations::GeneratorCRX, KernelType::PI}, - std::pair{GateOperations::GeneratorCRY, KernelType::PI}, - std::pair{GateOperations::GeneratorCRZ, KernelType::PI}, - std::pair{GateOperations::GeneratorControlledPhaseShift, - KernelType::PI}, -}; -} // namespace Constant - -/** - * @brief For lookup from any array of pair whose first elements are - * GateOperations. - * - * As Util::lookup can be used in constexpr context, this function is redundant - * (by the standard). But GCC 9 still does not accept Util::lookup in constexpr - * some cases. - */ -template -constexpr auto -static_lookup(const std::array, size> &arr) -> T { - for (size_t idx = 0; idx < size; idx++) { - if (std::get<0>(arr[idx]) == op) { - return std::get<1>(arr[idx]); - } - } - return T{}; -} - -/** - * @brief This class chooses a gate implementation at the compile time. - * - * When one adds another gate implementation, one needs to add a key - * in KernelType and assign it to SelectGateOps by template specialization. - * - * Even though it is impossible to convert this into a constexpr function, - * one may convert GateOpsFuncPtrPairs into constexpr functions with - * kernel as a parameter (instead of a template prameter). - * - * @tparam fp_t Floating point precision of underlying statevector data. - * @tparam kernel Kernel to select - */ -template class SelectGateOps {}; - -template -class SelectGateOps : public GateOperationsPI {}; -template -class SelectGateOps : public GateOperationsLM {}; - -} // namespace Pennylane - -namespace Pennylane::Internal { - -/** - * @brief Gate operation pointer type. See all specialized types. - */ -template -struct GateFuncPtr { - static_assert(num_params < 2 || num_params == 3, - "The given num_params is not supported."); -}; - -/** - * @brief Pointer type for a gate operation without parameters. - */ -template -struct GateFuncPtr { - using type = void (*)(std::complex *, size_t, - const std::vector &, bool); -}; -/** - * @brief Pointer type for a gate operation with a single parameter - */ -template -struct GateFuncPtr { - using type = void (*)(std::complex *, size_t, - const std::vector &, bool, ParamT); -}; -/** - * @brief Pointer type for a gate operation with three paramters - */ -template -struct GateFuncPtr { - using type = void (*)(std::complex *, size_t, - const std::vector &, bool, ParamT, ParamT, - ParamT); -}; - -/** - * @brief Convinient type alias for GateFuncPtr. See GateFuncPtr for details. - */ -template -using GateFuncPtrT = typename GateFuncPtr::type; - -/** - * @brief List of all gate operation and funciont pointer pairs for the given - * num_params. See specializations for details. - */ -template -struct GateOpsFuncPtrPairs { - static_assert(num_params < 2 || num_params == 3, - "The given num_params is not supported."); -}; - -/** - * @brief List of all gate operation and funciont pointer pairs without - * parameters. - */ -template -struct GateOpsFuncPtrPairs { - constexpr static std::array value = { - std::pair{GateOperations::PauliX, - &SelectGateOps::applyPauliX}, - std::pair{GateOperations::PauliY, - &SelectGateOps::applyPauliY}, - std::pair{GateOperations::PauliZ, - &SelectGateOps::applyPauliZ}, - std::pair{GateOperations::Hadamard, - &SelectGateOps::applyHadamard}, - std::pair{GateOperations::S, - &SelectGateOps::applyS}, - std::pair{GateOperations::T, - &SelectGateOps::applyT}, - std::pair{GateOperations::CNOT, - &SelectGateOps::applyCNOT}, - std::pair{GateOperations::CY, - &SelectGateOps::applyCY}, - std::pair{GateOperations::CZ, - &SelectGateOps::applyCZ}, - std::pair{GateOperations::SWAP, - &SelectGateOps::applySWAP}, - std::pair{GateOperations::Toffoli, - &SelectGateOps::applyToffoli}, - std::pair{GateOperations::CSWAP, - &SelectGateOps::applyCSWAP}, - std::pair{GateOperations::GeneratorPhaseShift, - &SelectGateOps::applyGeneratorPhaseShift}, - std::pair{GateOperations::GeneratorCRX, - &SelectGateOps::applyGeneratorCRX}, - std::pair{GateOperations::GeneratorCRY, - &SelectGateOps::applyGeneratorCRY}, - std::pair{GateOperations::GeneratorCRZ, - &SelectGateOps::applyGeneratorCRZ}, - std::pair{GateOperations::GeneratorControlledPhaseShift, - &SelectGateOps::applyGeneratorControlledPhaseShift}}; -}; - -/** - * @brief List of all gate operation and funciont pointer pairs with a single - * paramter. - */ -template -struct GateOpsFuncPtrPairs { - constexpr static std::array value = { - std::pair{GateOperations::RX, - &SelectGateOps::template applyRX}, - std::pair{GateOperations::RY, - &SelectGateOps::template applyRY}, - std::pair{GateOperations::RZ, - &SelectGateOps::template applyRZ}, - std::pair{GateOperations::PhaseShift, - &SelectGateOps::template applyPhaseShift}, - std::pair{ - GateOperations::CRX, - &SelectGateOps::template applyCRX}, - std::pair{ - GateOperations::CRY, - &SelectGateOps::template applyCRY}, - std::pair{ - GateOperations::CRZ, - &SelectGateOps::template applyCRZ}, - std::pair{GateOperations::ControlledPhaseShift, - &SelectGateOps:: - template applyControlledPhaseShift}}; -}; - -/** - * @brief List of all gate operation and funciont pointer pairs with three - * paramters. - */ -template -struct GateOpsFuncPtrPairs { - constexpr static std::array value = { - std::pair{ - GateOperations::Rot, - &SelectGateOps::template applyRot}, - std::pair{ - GateOperations::CRot, - &SelectGateOps::template applyCRot}}; -}; - -/** - * @defgroup Call gate operation with provided arguments - * - * @tparam fp_t floating point type for the state-vector - * @tparam ParamT floating point type for the gate paramters - * @param func Function pointer for the gate operation - * @param num_qubits The number of qubits of the state-vector - * @param wires Wires the gate applies to - * @param inverse If true, we apply the inverse of the gate - * @param params The list of gate paramters - */ -/// @{ -/** - * @brief Overload for a gate operation without parameters - */ -template -inline void callGateOps(GateFuncPtrT func, - std::complex *data, size_t num_qubits, - const std::vector &wires, bool inverse, - [[maybe_unused]] const std::vector ¶ms) { - assert(params.empty()); - func(data, num_qubits, wires, inverse); -} - -/** - * @brief Overload for a gate operation for a single paramter - */ -template -inline void callGateOps(GateFuncPtrT func, - std::complex *data, size_t num_qubits, - const std::vector &wires, bool inverse, - const std::vector ¶ms) { - assert(params.size() == 1); - func(data, num_qubits, wires, inverse, params[0]); -} - -/** - * @brief Overload for a gate operation for three paramters - */ -template -inline void callGateOps(GateFuncPtrT func, - std::complex *data, size_t num_qubits, - const std::vector &wires, bool inverse, - const std::vector ¶ms) { - assert(params.size() == 3); - func(data, num_qubits, wires, inverse, params[0], params[1], params[2]); -} -/// @} -/// @cond DEV -template -std::vector implementedGatesForKernelIter(KernelType kernel) { - if constexpr (idx == Constant::available_kernels.size()) { - return {}; - } else if (kernel == std::get<0>(Constant::available_kernels[idx])) { - const auto &arr = - SelectGateOps(Constant::available_kernels[idx])>:: - implemented_gates; - return std::vector(arr.begin(), arr.end()); - } else { - return implementedGatesForKernelIter(kernel); - } -} -/// @endcond - -/** - * @brief Return implemented_gates constexpr member variables for a given kernel - * - * This function interfaces the runtime variable kernel with the constant time - * variable implemented_gates - * - * TODO: Change to constexpr function in C++20 - */ -template -auto implementedGatesForKernel(KernelType kernel) - -> std::vector { - return implementedGatesForKernelIter(kernel); -} - -/******************************************************************** - * Functions below are only used in a compile time to check - * consistency. - ********************************************************************/ -template -constexpr auto check_default_kernels_are_available() -> bool { - // TODO: change to constexpr std::all_of in C++20 - // which is not constexpr in C++17. - // NOLINTNEXTLINE (readability-use-anyofallof) - for (const auto &[gate_op, kernel] : Constant::default_kernel_for_ops) { - if (!is_available_kernel(kernel)) { - return false; - } - } - return true; -} - -static_assert(check_default_kernels_are_available(), - "default_kernel_for_ops contains an unavailable kernel"); -static_assert(count_unique(first_elts_of(Constant::default_kernel_for_ops)) == - static_cast(GateOperations::END), - "All gate operations must be defined in default_kernel_for_ops"); - -} // namespace Pennylane::Internal - -/** - * @brief A hash function for GateOperations type - */ -template <> struct std::hash { - size_t operator()(Pennylane::GateOperations gate_operation) const { - return std::hash()(static_cast(gate_operation)); - } -}; diff --git a/pennylane_lightning/src/simulator/StateVectorBase.hpp b/pennylane_lightning/src/simulator/StateVectorBase.hpp index 74229d5b1a..7976525820 100644 --- a/pennylane_lightning/src/simulator/StateVectorBase.hpp +++ b/pennylane_lightning/src/simulator/StateVectorBase.hpp @@ -18,10 +18,10 @@ #pragma once +#include "Constant.hpp" #include "DynamicDispatcher.hpp" #include "Error.hpp" -#include "Gates.hpp" -#include "SelectGateOps.hpp" +#include "SelectKernel.hpp" #include "Util.hpp" /// @cond DEV @@ -35,9 +35,7 @@ #include #include #include -#include #include -#include #include #include @@ -45,32 +43,46 @@ * @brief This macro defines methods for State-vector class. The kernel template * argument choose the kernel to run. */ -#define PENNYLANE_STATEVECTOR_DEFINE_OPS(GATE_NAME) \ - template \ +#define PENNYLANE_STATEVECTOR_DEFINE_GATE(GATE_NAME) \ + template \ inline void apply##GATE_NAME##_(const std::vector &wires, \ bool inverse, Ts &&...args) { \ auto *arr = getData(); \ - static_assert(static_lookup( \ - Constant::gate_num_params) == sizeof...(Ts), \ + static_assert(Gates::static_lookup( \ + Gates::Constant::gate_num_params) == sizeof...(Ts), \ "The provided number of parameters for gate " #GATE_NAME \ " is wrong."); \ - static_assert( \ - array_has_elt(SelectGateOps::implemented_gates, \ - GateOperations::GATE_NAME), \ - "The kernel does not implement the gate."); \ - SelectGateOps::apply##GATE_NAME( \ + static_assert(Util::array_has_elt( \ + Gates::SelectKernel::implemented_gates, \ + Gates::GateOperation::GATE_NAME), \ + "The kernel does not implement the gate."); \ + Gates::SelectKernel::apply##GATE_NAME( \ arr, num_qubits_, wires, inverse, std::forward(args)...); \ } -#define PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GATE_NAME) \ +#define PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(GATE_NAME) \ template \ inline void apply##GATE_NAME(const std::vector &wires, \ bool inverse, Ts &&...args) { \ - constexpr auto kernel = static_lookup( \ - Constant::default_kernel_for_ops); \ + constexpr auto kernel = \ + Gates::static_lookup( \ + Gates::Constant::default_kernel_for_gates); \ apply##GATE_NAME##_(wires, inverse, \ std::forward(args)...); \ } +#define PENNYLANE_STATEVECTOR_DEFINE_GENERATOR(GENERATOR_NAME) \ + template \ + inline void applyGenerator##GENERATOR_NAME##_( \ + const std::vector &wires, bool adj) { \ + auto *arr = getData(); \ + static_assert(Util::array_has_elt( \ + Gates::SelectKernel::implemented_generators, \ + Gates::GeneratorOperation::GENERATOR_NAME), \ + "The kernel does not implement the gate generator."); \ + SelectKernel::applyGenerator##GENERATOR_NAME(arr, num_qubits_, \ + wires, adj); \ + } namespace Pennylane { @@ -83,23 +95,21 @@ namespace Pennylane { * 64-bit (128-bit `complex`) floating point representation. * As this is the base class, we do not add default template arguments. * - * @tparam fp_t Floating point precision of underlying statevector data. + * @tparam PrecisionT Floating point precision of underlying statevector data. * @tparam Derived Type of a derived class */ -template class StateVectorBase { +template class StateVectorBase { public: - using scalar_type_t = fp_t; /** * @brief StateVector complex precision type. */ - using CFP_t = std::complex; + using ComplexPrecisionT = std::complex; private: size_t num_qubits_{0}; protected: - StateVectorBase() = default; - StateVectorBase(size_t num_qubits) : num_qubits_{num_qubits} {} + explicit StateVectorBase(size_t num_qubits) : num_qubits_{num_qubits} {} /** * @brief Redefine the number of qubits in the statevector and number of @@ -123,11 +133,11 @@ template class StateVectorBase { return static_cast(Util::exp2(num_qubits_)); } - [[nodiscard]] inline auto getData() -> CFP_t * { + [[nodiscard]] inline auto getData() -> ComplexPrecisionT * { return static_cast(this)->getData(); } - [[nodiscard]] inline auto getData() const -> const CFP_t * { + [[nodiscard]] inline auto getData() const -> const ComplexPrecisionT * { return static_cast(this)->getData(); } @@ -139,12 +149,12 @@ template class StateVectorBase { * @return bool */ template - bool operator==(const StateVectorBase &rhs) { + bool operator==(const StateVectorBase &rhs) { if (num_qubits_ != rhs.getNumQubits()) { return false; } - const CFP_t *data1 = getData(); - const CFP_t *data2 = rhs.getData(); + const ComplexPrecisionT *data1 = getData(); + const ComplexPrecisionT *data2 = rhs.getData(); for (size_t k = 0; k < getLength(); k++) { if (data1[k] != data2[k]) { return false; @@ -162,11 +172,11 @@ template class StateVectorBase { * @param inverse Indicates whether to use inverse of gate. * @param params Optional parameter list for parametric gates. */ - void applyOperation(KernelType kernel, const std::string &opName, + void applyOperation(Gates::KernelType kernel, const std::string &opName, const std::vector &wires, bool inverse = false, - const std::vector ¶ms = {}) { + const std::vector ¶ms = {}) { auto *arr = getData(); - DynamicDispatcher::getInstance().applyOperation( + DynamicDispatcher::getInstance().applyOperation( kernel, arr, num_qubits_, opName, wires, inverse, params); } @@ -180,9 +190,9 @@ template class StateVectorBase { */ void applyOperation(const std::string &opName, const std::vector &wires, bool inverse = false, - const std::vector ¶ms = {}) { + const std::vector ¶ms = {}) { auto *arr = getData(); - DynamicDispatcher::getInstance().applyOperation( + DynamicDispatcher::getInstance().applyOperation( arr, num_qubits_, opName, wires, inverse, params); } @@ -197,9 +207,9 @@ template class StateVectorBase { void applyOperations(const std::vector &ops, const std::vector> &wires, const std::vector &inverse, - const std::vector> ¶ms) { + const std::vector> ¶ms) { auto *arr = getData(); - DynamicDispatcher::getInstance().applyOperations( + DynamicDispatcher::getInstance().applyOperations( arr, num_qubits_, ops, wires, inverse, params); } @@ -214,53 +224,104 @@ template class StateVectorBase { const std::vector> &wires, const std::vector &inverse) { auto *arr = getData(); - DynamicDispatcher::getInstance().applyOperations( + DynamicDispatcher::getInstance().applyOperations( arr, num_qubits_, ops, wires, inverse); } + /** + * @brief Apply a single generator to the state-vector using a given kernel. + * + * @param kernel Kernel to run the operation. + * @param opName Name of gate to apply. + * @param wires Wires to apply gate to. + * @param adj Indicates whether to use adjoint of operator. + */ + [[nodiscard]] inline auto applyGenerator(Gates::KernelType kernel, + const std::string &opName, + const std::vector &wires, + bool adj = false) -> PrecisionT { + auto *arr = getData(); + return DynamicDispatcher::getInstance().applyGenerator( + kernel, arr, num_qubits_, opName, wires, adj); + } + + /** + * @brief Apply a single generator to the state-vector. + * + * @param opName Name of gate to apply. + * @param wires Wires the gate applies to. + * @param adj Indicates whether to use adjoint of operator. + */ + [[nodiscard]] auto applyGenerator(const std::string &opName, + const std::vector &wires, + bool adj = false) -> PrecisionT { + auto *arr = getData(); + return DynamicDispatcher::getInstance().applyGenerator( + arr, num_qubits_, opName, wires, adj); + } + /** * @brief Apply a given matrix directly to the statevector read directly * from numpy data. Data can be in 1D or 2D format. * * @param matrix Pointer to the array data. + * @param wires Wires the gate applies to. * @param inverse Indicate whether inverse should be taken. */ - template - inline void applyMatrix_(const CFP_t *matrix, + template + inline void applyMatrix_(const ComplexPrecisionT *matrix, const std::vector &wires, bool inverse = false) { auto *arr = getData(); - SelectGateOps::applyMatrix(arr, num_qubits_, matrix, + Gates::SelectKernel::applyMatrix(arr, num_qubits_, matrix, wires, inverse); } - template - inline void applyMatrix_(const std::vector &matrix, + template + inline void applyMatrix_(const std::vector &matrix, const std::vector &wires, bool inverse = false) { auto *arr = getData(); - SelectGateOps::applyMatrix(arr, num_qubits_, matrix, + Gates::SelectKernel::applyMatrix(arr, num_qubits_, matrix, wires, inverse); } - inline void applyMatrix(const CFP_t *matrix, + /** + * @brief Apply a given matrix directly to the statevector read directly + * from numpy data. Data can be in 1D or 2D format. + * + * @param matrix Pointer to the array data. + * @param wires Wires to apply gate to. + * @param inverse Indicate whether inverse should be taken. + */ + inline void applyMatrix(const ComplexPrecisionT *matrix, const std::vector &wires, bool inverse = false) { - constexpr auto kernel = static_lookup( - Constant::default_kernel_for_ops); + namespace Constant = Gates::Constant; + using Gates::GateOperation; + using Gates::SelectKernel; + using Gates::static_lookup; + + constexpr auto kernel = static_lookup( + Constant::default_kernel_for_gates); static_assert( - array_has_elt(SelectGateOps::implemented_gates, - GateOperations::Matrix), + Util::array_has_elt(SelectKernel::implemented_gates, + GateOperation::Matrix), "The default kernel for applyMatrix does not implement it."); applyMatrix_(matrix, wires, inverse); } - inline void applyMatrix(const std::vector &matrix, + inline void applyMatrix(const std::vector &matrix, const std::vector &wires, bool inverse = false) { - constexpr auto kernel = static_lookup( - Constant::default_kernel_for_ops); + namespace Constant = Gates::Constant; + using Gates::GateOperation; + using Gates::SelectKernel; + using Gates::static_lookup; + + constexpr auto kernel = static_lookup( + Constant::default_kernel_for_gates); static_assert( - array_has_elt(SelectGateOps::implemented_gates, - GateOperations::Matrix), + Util::array_has_elt(SelectKernel::implemented_gates, + GateOperation::Matrix), "The default kernel for applyMatrix does not implement it."); applyMatrix_(matrix, wires, inverse); } @@ -271,13 +332,13 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(PauliX) + PENNYLANE_STATEVECTOR_DEFINE_GATE(PauliX) /** * @brief Apply PauliX gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(PauliX) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(PauliX) /** * @brief Apply PauliY gate operation to given indices of statevector. @@ -285,13 +346,13 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(PauliY) + PENNYLANE_STATEVECTOR_DEFINE_GATE(PauliY) /** * @brief Apply PauliY gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(PauliY) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(PauliY) /** * @brief Apply PauliZ gate operation to given indices of statevector. @@ -299,12 +360,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(PauliZ) + PENNYLANE_STATEVECTOR_DEFINE_GATE(PauliZ) /** * @brief Apply PauliZ gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(PauliZ) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(PauliZ) /** * @brief Apply Hadamard gate operation to given indices of statevector. @@ -312,12 +373,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(Hadamard) + PENNYLANE_STATEVECTOR_DEFINE_GATE(Hadamard) /** * @brief Apply Hadamard gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(Hadamard) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(Hadamard) /** * @brief Apply S gate operation to given indices of statevector. @@ -325,24 +386,25 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(S) + PENNYLANE_STATEVECTOR_DEFINE_GATE(S) /** * @brief Apply S gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(S) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(S) /** * @brief Apply T gate operation to given indices of statevector. * + * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(T) + PENNYLANE_STATEVECTOR_DEFINE_GATE(T) /** * @brief Apply T gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(T) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(T) /** * @brief Apply RX gate operation to given indices of statevector. @@ -351,12 +413,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(RX) + PENNYLANE_STATEVECTOR_DEFINE_GATE(RX) /** * @brief Apply RX gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(RX) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(RX) /** * @brief Apply RY gate operation to given indices of statevector. @@ -365,12 +427,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(RY) + PENNYLANE_STATEVECTOR_DEFINE_GATE(RY) /** * @brief Apply RY gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(RY) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(RY) /** * @brief Apply RZ gate operation to given indices of statevector. @@ -379,12 +441,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(RZ) + PENNYLANE_STATEVECTOR_DEFINE_GATE(RZ) /** * @brief Apply RZ gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(RZ) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(RZ) /** * @brief Apply phase shift gate operation to given indices of statevector. @@ -393,12 +455,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Phase shift angle. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(PhaseShift) + PENNYLANE_STATEVECTOR_DEFINE_GATE(PhaseShift) /** * @brief Apply PhaseShift gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(PhaseShift) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(PhaseShift) /* * @brief Apply Rot gate \f$RZ(\omega)RY(\theta)RZ(\phi)\f$ to given indices @@ -410,12 +472,12 @@ template class StateVectorBase { * @param theta Gate rotation parameter \f$\theta\f$. * @param omega Gate rotation parameter \f$\omega\f$. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(Rot) + PENNYLANE_STATEVECTOR_DEFINE_GATE(Rot) /** * @brief Apply Rot gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(Rot) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(Rot) /** * @brief Apply controlled phase shift gate operation to given indices of @@ -425,12 +487,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Phase shift angle. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(ControlledPhaseShift) + PENNYLANE_STATEVECTOR_DEFINE_GATE(ControlledPhaseShift) /** * @brief Apply controlled phase shift gate operation using a kernel given - * in default_kernel_for_ops + * in default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(ControlledPhaseShift) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(ControlledPhaseShift) /** * @brief Apply CNOT (CX) gate to given indices of statevector. @@ -438,12 +500,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CNOT) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CNOT) /** * @brief Apply CNOT gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CNOT) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CNOT) /** * @brief Apply CY gate to given indices of statevector. @@ -451,12 +513,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CY) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CY) /** * @brief Apply CY gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CY) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CY) /** * @brief Apply CZ gate to given indices of statevector. @@ -464,12 +526,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CZ) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CZ) /** * @brief Apply CZ gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CZ) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CZ) /** * @brief Apply SWAP gate to given indices of statevector. @@ -477,12 +539,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(SWAP) + PENNYLANE_STATEVECTOR_DEFINE_GATE(SWAP) /** * @brief Apply SWAP gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(SWAP) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(SWAP) /** * @brief Apply CRX gate to given indices of statevector. @@ -491,12 +553,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CRX) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CRX) /** * @brief Apply CRX gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CRX) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CRX) /** * @brief Apply CRY gate to given indices of statevector. @@ -505,12 +567,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CRY) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CRY) /** * @brief Apply CRY gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CRY) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CRY) /** * @brief Apply CRZ gate to given indices of statevector. @@ -519,12 +581,12 @@ template class StateVectorBase { * @param inverse Take adjoint of given operation. * @param angle Rotation angle of gate. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CRZ) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CRZ) /** * @brief Apply CRZ gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CRZ) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CRZ) /** * @brief Apply CRot gate (controlled \f$RZ(\omega)RY(\theta)RZ(\phi)\f$) to @@ -536,12 +598,12 @@ template class StateVectorBase { * @param theta Gate rotation parameter \f$\theta\f$. * @param omega Gate rotation parameter \f$\omega\f$. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CRot) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CRot) /** * @brief Apply CRot gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CRot) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CRot) /** * @brief Apply Toffoli (CCX) gate to given indices of statevector. @@ -549,12 +611,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(Toffoli) + PENNYLANE_STATEVECTOR_DEFINE_GATE(Toffoli) /** * @brief Apply Toffoli gate operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(Toffoli) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(Toffoli) /** * @brief Apply CSWAP gate to given indices of statevector. @@ -562,78 +624,12 @@ template class StateVectorBase { * @param wires Wires to apply gate to. * @param inverse Take adjoint of given operation. */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(CSWAP) + PENNYLANE_STATEVECTOR_DEFINE_GATE(CSWAP) /** * @brief Apply CSWAP gate operation using a kernel given in - * default_kernel_for_ops - */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(CSWAP) - - /** - * @brief Apply PhaseShift generator to given indices of statevector. - * - * @param wires Wires to apply gate to. - * @param inverse Take adjoint of given operation. - */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(GeneratorPhaseShift) - /** - * @brief Apply PhaseShift generator operation using a kernel given in - * default_kernel_for_ops - */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GeneratorPhaseShift) - - /** - * @brief Apply CRX generator to given indices of statevector. - * - * @param wires Wires to apply gate to. - * @param inverse Take adjoint of given operation. - */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(GeneratorCRX) - /** - * @brief Apply CRX generator operation using a kernel given in - * default_kernel_for_ops - */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GeneratorCRX) - - /** - * @brief Apply CRY generator to given indices of statevector. - * - * @param wires Wires to apply gate to. - * @param inverse Take adjoint of given operation. - */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(GeneratorCRY) - /** - * @brief Apply CRY generator opertation using a kernel given in - * default_kernel_for_ops - */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GeneratorCRY) - - /** - * @brief Apply CRZ generator to given indices of statevector. - * - * @param wires Wires to apply gate to. - * @param inverse Take adjoint of given operation. - */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(GeneratorCRZ) - /** - * @brief Apply CRZ generator operation using a kernel given in - * default_kernel_for_ops - */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GeneratorCRZ) - - /** - * @brief Apply controlled phase shift generator to given indices of - * statevector. - * - * @param wires Wires to apply gate to. - * @param inverse Take adjoint of given operation. - */ - PENNYLANE_STATEVECTOR_DEFINE_OPS(GeneratorControlledPhaseShift) - /** - * @brief Apply controlled phase shift operation using a kernel given in - * default_kernel_for_ops + * default_kernel_for_gates */ - PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_OPS(GeneratorControlledPhaseShift) + PENNYLANE_STATEVECTOR_DEFINE_DEFAULT_GATE(CSWAP) }; /** diff --git a/pennylane_lightning/src/simulator/StateVectorManaged.hpp b/pennylane_lightning/src/simulator/StateVectorManaged.hpp index 29a8b23e1f..a1317e75d5 100644 --- a/pennylane_lightning/src/simulator/StateVectorManaged.hpp +++ b/pennylane_lightning/src/simulator/StateVectorManaged.hpp @@ -22,66 +22,79 @@ namespace Pennylane { * * This class is only internally used in C++ code. * - * @tparam fp_t + * @tparam PrecisionT */ -template +template class StateVectorManaged - : public StateVectorBase> { + : public StateVectorBase> { public: - using scalar_type_t = fp_t; - using CFP_t = std::complex; + using ComplexPrecisionT = std::complex; private: - using BaseType = StateVectorBase; + using BaseType = StateVectorBase; - std::vector data_; + std::vector data_; public: - StateVectorManaged() : StateVectorBase() {} + StateVectorManaged() : StateVectorBase() {} explicit StateVectorManaged(size_t num_qubits) : BaseType(num_qubits), - data_(static_cast(Util::exp2(num_qubits)), CFP_t{0, 0}) { + data_(static_cast(Util::exp2(num_qubits)), + ComplexPrecisionT{0, 0}) { data_[0] = {1, 0}; } template - StateVectorManaged(const StateVectorBase &other) + explicit StateVectorManaged( + const StateVectorBase &other) : BaseType(other.getNumQubits()), data_{other.getData(), other.getData() + other.getLength()} {} - StateVectorManaged(const std::vector &other_data) + explicit StateVectorManaged( + const std::vector &other_data) : BaseType(Util::log2(other_data.size())), data_{other_data} { PL_ABORT_IF_NOT(Util::isPerfectPowerOf2(other_data.size()), "The size of provided data must be a power of 2."); } - StateVectorManaged(const CFP_t *other_data, size_t other_size) + StateVectorManaged(const ComplexPrecisionT *other_data, size_t other_size) : BaseType(Util::log2(other_size)), data_{other_data, other_data + other_size} { PL_ABORT_IF_NOT(Util::isPerfectPowerOf2(other_size), "The size of provided data must be a power of 2."); } - StateVectorManaged(const StateVectorManaged &other) = default; - StateVectorManaged(StateVectorManaged &&other) noexcept = default; + StateVectorManaged(const StateVectorManaged &other) = default; + StateVectorManaged(StateVectorManaged &&other) noexcept = + default; - auto operator=(const StateVectorManaged &other) - -> StateVectorManaged & = default; - auto operator=(StateVectorManaged &&other) noexcept - -> StateVectorManaged & = default; + ~StateVectorManaged() = default; - auto getDataVector() -> std::vector & { return data_; } - [[nodiscard]] auto getDataVector() const -> const std::vector & { + auto operator=(const StateVectorManaged &other) + -> StateVectorManaged & = default; + auto operator=(StateVectorManaged &&other) noexcept + -> StateVectorManaged & = default; + + auto getDataVector() -> std::vector & { return data_; } + [[nodiscard]] auto getDataVector() const + -> const std::vector & { return data_; } - [[nodiscard]] auto getData() -> CFP_t * { return data_.data(); } + [[nodiscard]] auto getData() -> ComplexPrecisionT * { return data_.data(); } - [[nodiscard]] auto getData() const -> const CFP_t * { return data_.data(); } + [[nodiscard]] auto getData() const -> const ComplexPrecisionT * { + return data_.data(); + } - void updateData(const std::vector &new_data) { + /** + * @brief Update data of the class to new_data + * + * @param new_data std::vector contains data. + */ + void updateData(const std::vector &new_data) { PL_ABORT_IF_NOT(data_.size() == new_data.size(), "New data must be the same size as old data.") std::copy(new_data.begin(), new_data.end(), data_.begin()); diff --git a/pennylane_lightning/src/simulator/StateVectorRaw.hpp b/pennylane_lightning/src/simulator/StateVectorRaw.hpp index 55a03fa0f8..f25b2c2151 100644 --- a/pennylane_lightning/src/simulator/StateVectorRaw.hpp +++ b/pennylane_lightning/src/simulator/StateVectorRaw.hpp @@ -17,18 +17,13 @@ */ #pragma once -#include #include -#include -#include #include -#include #include #include #include "BitUtil.hpp" #include "Error.hpp" -#include "Gates.hpp" #include "StateVectorBase.hpp" #include @@ -46,16 +41,17 @@ namespace Pennylane { * `complex`) or 64-bit (128-bit `complex`) floating point * representation. * - * @tparam fp_t Floating point precision of underlying statevector data. + * @tparam PrecisionT Floating point precision of underlying statevector data. */ -template -class StateVectorRaw : public StateVectorBase> { +template +class StateVectorRaw + : public StateVectorBase> { public: - using Base = StateVectorBase>; - using CFP_t = std::complex; + using Base = StateVectorBase>; + using ComplexPrecisionT = std::complex; private: - CFP_t *data_; + ComplexPrecisionT *data_; size_t length_; public: @@ -65,8 +61,8 @@ class StateVectorRaw : public StateVectorBase> { * @param data Raw data pointer. * @param length The size of the data, i.e. 2^(number of qubits). */ - StateVectorRaw(CFP_t *data, size_t length) - : StateVectorBase>( + StateVectorRaw(ComplexPrecisionT *data, size_t length) + : StateVectorBase>( Util::log2PerfectPower(length)), data_{data}, length_(length) { // check length is perfect power of 2 @@ -84,19 +80,21 @@ class StateVectorRaw : public StateVectorBase> { auto operator=(const StateVectorRaw &) -> StateVectorRaw & = default; auto operator=(StateVectorRaw &&) noexcept -> StateVectorRaw & = default; + ~StateVectorRaw() = default; + /** * @brief Get the underlying data pointer. * - * @return const CFP_t* Pointer to statevector data. + * @return const ComplexPrecisionT* Pointer to statevector data. */ - [[nodiscard]] auto getData() const -> CFP_t * { return data_; } + [[nodiscard]] auto getData() const -> ComplexPrecisionT * { return data_; } /** * @brief Get the underlying data pointer. * - * @return CFP_t* Pointer to statevector data. + * @return ComplexPrecisionT* Pointer to statevector data. */ - auto getData() -> CFP_t * { return data_; } + auto getData() -> ComplexPrecisionT * { return data_; } /** * @brief Redefine statevector data pointer. @@ -104,7 +102,7 @@ class StateVectorRaw : public StateVectorBase> { * @param data New raw data pointer. * @param length The size of the data, i.e. 2^(number of qubits). */ - void setData(CFP_t *data, size_t length) { + void setData(ComplexPrecisionT *data, size_t length) { if (!Util::isPerfectPowerOf2(length)) { PL_ABORT("The length of the array for StateVector must be " "a perfect power of 2. But " + diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index 0dea61a10c..17babaf9ee 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -34,8 +34,13 @@ include(Catch) ################################################################################ add_library(lightning_tests_dependency INTERFACE) -target_link_libraries(lightning_tests_dependency INTERFACE lightning_simulator lightning_algorithms lightning_utils Catch2::Catch2) -target_compile_options(lightning_tests_dependency INTERFACE "-Wno-unused;") +target_link_libraries(lightning_tests_dependency INTERFACE lightning_algorithms + lightning_gates + lightning_simulator + lightning_utils + Catch2::Catch2) + +target_compile_options(lightning_tests_dependency INTERFACE "-Wall;-Wno-unused;") target_sources(lightning_tests_dependency INTERFACE runner_main.cpp) if(ENABLE_NATIVE) @@ -47,14 +52,21 @@ endif() # Define targets ################################################################################ +add_executable(compile_time_tests compile_time_tests.cpp) +target_link_libraries(compile_time_tests lightning_gates lightning_utils) + set(TEST_SOURCES Test_AdjDiff.cpp - Test_Bindings.cpp +# Test_Bindings.cpp Test_DynamicDispatcher.cpp - Test_GateOperations_Nonparam.cpp - Test_GateOperations_Param.cpp - Test_IndicesUtil.cpp + Test_GateImplementations_Generator.cpp + Test_GateImplementations_Inverse.cpp + Test_GateImplementations_Matrix.cpp + Test_GateImplementations_Nonparam.cpp + Test_GateImplementations_Param.cpp + Test_GateUtil.cpp + Test_Internal.cpp Test_Measures.cpp - Test_SelectGateOps.cpp + Test_OpToMemberFuncPtr.cpp Test_StateVectorManaged.cpp Test_StateVectorRaw.cpp Test_Util.cpp @@ -65,3 +77,7 @@ target_link_libraries(runner PRIVATE lightning_tests_dependency lightning_compile_options lightning_external_libs) catch_discover_tests(runner) + +# We build compile time tests before the runtime tests as build error messages +# are horrible if compile time constants are not well defined. +add_dependencies(runner compile_time_tests) diff --git a/pennylane_lightning/src/tests/TestAvailableKernels.hpp b/pennylane_lightning/src/tests/TestAvailableKernels.hpp new file mode 100644 index 0000000000..4170856b96 --- /dev/null +++ b/pennylane_lightning/src/tests/TestAvailableKernels.hpp @@ -0,0 +1,87 @@ +#include "AvailableKernels.hpp" +#include "Constant.hpp" +#include "KernelType.hpp" +#include "SelectKernel.hpp" +#include "Util.hpp" + +namespace Pennylane::Gates { + +// Define some utility function +template +constexpr auto is_available_kernel_helper(KernelType kernel) -> bool { + if (TypeList::Type::kernel_id == kernel) { + return true; + } + return is_available_kernel_helper(kernel); +} +template <> +constexpr auto +is_available_kernel_helper([[maybe_unused]] KernelType kernel) -> bool { + return false; +} +/** + * @brief Check the given kernel is in AvailableKernels. + */ +constexpr auto is_available_kernel(KernelType kernel) -> bool { + return is_available_kernel_helper(kernel); +} + +template +constexpr auto +check_kernels_are_available(const std::array &arr) -> bool { + // TODO: change to constexpr std::all_of in C++20 + // which is not constexpr in C++17. + // NOLINTNEXTLINE (readability-use-anyofallof) + for (const auto &kernel : arr) { + if (!is_available_kernel(kernel)) { + return false; + } + } + return true; +} + +/******************************************************************************* + * Check all kernels in kernels_to_pyexport are available + ******************************************************************************/ + +constexpr auto check_kernels_to_pyexport() -> bool { + // TODO: change to constexpr std::any_of in C++20 + // NOLINTNEXTLINE (readability-use-anyofallof) + for (const auto &kernel : kernels_to_pyexport) { + if (!is_available_kernel(kernel)) { + return false; + } + } + return true; +} +static_assert(check_kernels_to_pyexport(), + "Some of Kernels in Python export is not available."); + +/******************************************************************************* + * Check each element in kernelIdNamesPairs is unique + ******************************************************************************/ + +static_assert(Util::count_unique(Util::first_elts_of(kernel_id_name_pairs)) == + Util::length(), + "Kernel ids must be distinct."); + +static_assert(Util::count_unique(Util::second_elts_of(kernel_id_name_pairs)) == + Util::length(), + "Kernel names must be distinct."); + +/******************************************************************************* + * Check all kernels in default_kernel_for_gates are available + ******************************************************************************/ + +static_assert(check_kernels_are_available( + Util::second_elts_of(Constant::default_kernel_for_gates)), + "default_kernel_for_gates contains an unavailable kernel"); + +/******************************************************************************* + * Check all kernels in default_kernel_for_generators are available + ******************************************************************************/ + +static_assert(check_kernels_are_available(Util::second_elts_of( + Constant::default_kernel_for_generators)), + "default_kernel_for_gates contains an unavailable kernel"); +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/tests/TestConstant.hpp b/pennylane_lightning/src/tests/TestConstant.hpp new file mode 100644 index 0000000000..4d20e25af2 --- /dev/null +++ b/pennylane_lightning/src/tests/TestConstant.hpp @@ -0,0 +1,121 @@ +#include "Constant.hpp" +#include "GateOperation.hpp" +#include "Util.hpp" + +namespace Pennylane::Gates { + +template +constexpr auto are_mutually_disjoint(const std::array &arr1, + const std::array &arr2) -> bool { + // TODO: change to sort and compare in C++20 (std::sort will be constexpr) + // NOLINTNEXTLINE(readability-use-anyofallof) + for (const T &elt : arr2) { + if (Util::array_has_elt(arr1, elt)) { + return false; + } + } + return true; +} + +/******************************************************************************* + * Check gate_names is well defined + ******************************************************************************/ + +static_assert(Constant::gate_names.size() == + static_cast(GateOperation::END), + "Constant gate_names must be defined for all gate operations."); +static_assert(Util::count_unique(Util::first_elts_of(Constant::gate_names)) == + Constant::gate_names.size(), + "First elements of gate_names must be distinct."); +static_assert(Util::count_unique(Util::second_elts_of(Constant::gate_names)) == + Constant::gate_names.size(), + "Second elements of gate_names must be distinct."); + +/******************************************************************************* + * Check generator_names is well defined + ******************************************************************************/ + +constexpr auto check_generator_names_starts_with() -> bool { + // TODO: change constexpr std::any_of, string_view::starts_with in C++20 + // NOLINTNEXTLINE(readability-use-anyofallof) + for (const auto &[gntr_op, gntr_name] : Constant::generator_names) { + if (gntr_name.substr(0, 9) != "Generator") { + return false; + } + } + return true; +} + +static_assert( + Constant::generator_names.size() == + static_cast(GeneratorOperation::END), + "Constant generator_names must be defined for all generator operations."); +static_assert( + Util::count_unique(Util::first_elts_of(Constant::generator_names)) == + Constant::generator_names.size(), + "First elements of generator_names must be distinct."); +static_assert( + Util::count_unique(Util::second_elts_of(Constant::generator_names)) == + Constant::generator_names.size(), + "Second elements of generator_names must be distinct."); +static_assert(check_generator_names_starts_with(), + "Names of generators must start with \"Generator\""); + +/******************************************************************************* + * Check gate_wires is well defined + ******************************************************************************/ + +static_assert(Constant::gate_wires.size() == + static_cast(GateOperation::END) - + Constant::multi_qubit_gates.size(), + "Constant gate_wires must be defined for all gate operations " + "acting on a fixed number of qubits."); +static_assert( + are_mutually_disjoint(Util::first_elts_of(Constant::gate_wires), + Constant::multi_qubit_gates), + "Constant gate_wires must not define values for multi-qubit gates."); +static_assert(Util::count_unique(Util::first_elts_of(Constant::gate_wires)) == + Constant::gate_wires.size(), + "First elements of gate_wires must be distinct."); + +/******************************************************************************* + * Check generator_wires is well defined + ******************************************************************************/ + +static_assert( + Constant::generator_wires.size() == + static_cast(GeneratorOperation::END) - + Constant::multi_qubit_generators.size(), + "Constant generator_wires must be defined for all generator operations " + "acting on a fixed number of qubits."); +static_assert( + are_mutually_disjoint(Util::first_elts_of(Constant::generator_wires), + Constant::multi_qubit_generators), + "Constant generator_wires must not define values for multi-qubit " + "generators."); +static_assert( + Util::count_unique(Util::first_elts_of(Constant::generator_wires)) == + Constant::generator_wires.size(), + "First elements of generator_wires must be distinct."); + +/******************************************************************************* + * Check default_kernel_for_gates are defined for all gates + ******************************************************************************/ + +static_assert( + Util::count_unique( + Util::first_elts_of(Constant::default_kernel_for_gates)) == + static_cast(GateOperation::END), + "Constant default_kernel_for_gates must be defined for all gates."); + +/******************************************************************************* + * Check default_kernel_for_generators are defined for all generators + ******************************************************************************/ + +static_assert(Util::count_unique(Util::first_elts_of( + Constant::default_kernel_for_generators)) == + static_cast(GeneratorOperation::END), + "Constant default_kernel_for_generators must be defined for all " + "generators."); + +} // namespace Pennylane::Gates diff --git a/pennylane_lightning/src/tests/TestHelpers.hpp b/pennylane_lightning/src/tests/TestHelpers.hpp index 068d9c52c8..74faeeb5ce 100644 --- a/pennylane_lightning/src/tests/TestHelpers.hpp +++ b/pennylane_lightning/src/tests/TestHelpers.hpp @@ -1,12 +1,99 @@ #include #include #include +#include +#include #include -#include "GateOperations.hpp" +#include "Constant.hpp" +#include "ConstantUtil.hpp" +#include "Error.hpp" +#include "GateOperation.hpp" +#include "LinearAlgebra.hpp" +#include "Util.hpp" #include +namespace Pennylane { +template struct remove_complex { using type = T; }; +template struct remove_complex> { + using type = T; +}; +template using remove_complex_t = typename remove_complex::type; + +template struct is_complex : std::false_type {}; + +template struct is_complex> : std::true_type {}; + +template constexpr bool is_complex_v = is_complex::value; + +template struct PLApprox { + const std::vector &comp_; + + explicit PLApprox(const std::vector &comp) : comp_{comp} {} + + remove_complex_t margin_{}; + remove_complex_t epsilon_ = std::numeric_limits::epsilon() * 100; + + template + [[nodiscard]] bool compare(const std::vector &lhs) const { + if (lhs.size() != comp_.size()) { + return false; + } + + for (size_t i = 0; i < lhs.size(); i++) { + if constexpr (is_complex_v) { + if (lhs[i].real() != Approx(comp_[i].real()) + .epsilon(epsilon_) + .margin(margin_) || + lhs[i].imag() != Approx(comp_[i].imag()) + .epsilon(epsilon_) + .margin(margin_)) { + return false; + } + } else { + if (lhs[i] != + Approx(comp_[i]).epsilon(epsilon_).margin(margin_)) { + return false; + } + } + } + return true; + } + [[nodiscard]] std::string describe() const { + std::ostringstream ss; + ss << "is Approx to {"; + for (const auto &elt : comp_) { + ss << elt << ", "; + } + ss << "}" << std::endl; + return ss.str(); + } + PLApprox &epsilon(remove_complex_t eps) { + epsilon_ = eps; + return *this; + } + PLApprox &margin(remove_complex_t m) { + margin_ = m; + return *this; + } +}; +template +std::ostream &operator<<(std::ostream &os, const PLApprox &approx) { + os << approx.describe(); + return os; +} +template +bool operator==(const std::vector &lhs, + const PLApprox &rhs) { + return rhs.compare(lhs); +} +template +bool operator!=(const std::vector &lhs, + const PLApprox &rhs) { + return !rhs.compare(lhs); +} + /** * @brief Utility function to compare complex statevector data. * @@ -16,22 +103,14 @@ * @return true Data are approximately equal. * @return false Data are not approximately equal. */ -template -inline bool isApproxEqual( - const std::vector &data1, const std::vector &data2, - const typename Data_t::value_type eps = - std::numeric_limits::epsilon() * 100) { - if (data1.size() != data2.size()) { - return false; - } - - for (size_t i = 0; i < data1.size(); i++) { - if (data1[i].real() != Approx(data2[i].real()).epsilon(eps) || - data1[i].imag() != Approx(data2[i].imag()).epsilon(eps)) { - return false; - } - } - return true; +template +inline bool +isApproxEqual(const std::vector &data1, + const std::vector &data2, + const typename Data_t::value_type eps = + std::numeric_limits::epsilon() * + 100) { + return data1 == PLApprox(data2); } /** @@ -69,44 +148,211 @@ void scaleVector(std::vector> &data, [scalar](const std::complex &c) { return c * scalar; }); } +/** + * @brief Multiplies every value in a dataset by a given complex scalar value. + * + * @tparam Data_t Precision of complex data type. Supports float and double + * data. + * @param data Data to be scaled. + * @param scalar Scalar value. + */ +template +void scaleVector(std::vector> &data, Data_t scalar) { + std::transform( + data.begin(), data.end(), data.begin(), + [scalar](const std::complex &c) { return c * scalar; }); +} + /** * @brief create |0>^N */ -template -auto create_zero_state(size_t num_qubits) -> std::vector> { - std::vector> res(1U << num_qubits, {0.0, 0.0}); - res[0] = std::complex{1.0, 0.0}; +template +auto createZeroState(size_t num_qubits) + -> std::vector> { + std::vector> res(1U << num_qubits, {0.0, 0.0}); + res[0] = std::complex{1.0, 0.0}; return res; } /** * @brief create |+>^N */ -template -auto create_plus_state(size_t num_qubits) -> std::vector> { - std::vector> res(1U << num_qubits, {1.0, 0.0}); +template +auto createPlusState(size_t num_qubits) + -> std::vector> { + std::vector> res(1U << num_qubits, {1.0, 0.0}); for (auto &elt : res) { elt /= std::sqrt(1U << num_qubits); } return res; } +/** + * @brief Calculate the squared norm of a vector + */ +template +auto squaredNorm(const std::complex *data, size_t data_size) + -> PrecisionT { + return std::transform_reduce( + data, data + data_size, PrecisionT{}, std::plus(), + static_cast &)>( + &std::norm)); +} + /** * @brief create a random state */ -template -auto create_random_state(RandomEngine &re, size_t num_qubits) - -> std::vector> { - std::vector> res(1U << num_qubits, {0.0, 0.0}); - std::uniform_real_distribution dist; +template +auto createRandomState(RandomEngine &re, size_t num_qubits) + -> std::vector> { + std::vector> res(1U << num_qubits, {0.0, 0.0}); + std::uniform_real_distribution dist; for (size_t idx = 0; idx < (1U << num_qubits); idx++) { res[idx] = {dist(re), dist(re)}; } - fp_t squared_norm = std::transform_reduce( - std::cbegin(res), std::cend(res), fp_t{}, std::plus(), - static_cast &)>(&std::norm)); + scaleVector(res, std::complex{1.0, 0.0} / + std::sqrt(squaredNorm(res.data(), res.size()))); + return res; +} + +/** + * @brief Create an arbitrary product state in X- or Z-basis. + * + * Example: createProductState("+01") will produce |+01> state. + */ +template auto createProductState(std::string_view str) { + using Pennylane::Util::INVSQRT2; + std::vector> st; + st.resize(1U << str.length()); + + std::vector zero{1.0, 0.0}; + std::vector one{0.0, 1.0}; + + std::vector plus{INVSQRT2(), + INVSQRT2()}; + std::vector minus{INVSQRT2(), + -INVSQRT2()}; + + for (size_t k = 0; k < (1U << str.length()); k++) { + PrecisionT elt = 1.0; + for (size_t n = 0; n < str.length(); n++) { + char c = str[n]; + const size_t wire = str.length() - 1 - n; + switch (c) { + case '0': + elt *= zero[(k >> wire) & 1U]; + break; + case '1': + elt *= one[(k >> wire) & 1U]; + break; + case '+': + elt *= plus[(k >> wire) & 1U]; + break; + case '-': + elt *= minus[(k >> wire) & 1U]; + break; + default: + PL_ABORT("Unknown character in the argument."); + } + } + st[k] = elt; + } + return st; +} + +inline auto createWires(Gates::GateOperation op) -> std::vector { + if (Pennylane::Util::array_has_elt(Gates::Constant::multi_qubit_gates, + op)) { + // if multi-qubit gates + return {0, 1, 2}; + } + switch (Pennylane::Util::lookup(Gates::Constant::gate_wires, op)) { + case 1: + return {0}; + case 2: + return {0, 1}; + case 3: + return {0, 1, 2}; + default: + PL_ABORT("The number of wires for a given gate is unknown."); + } + return {}; +} + +template +auto createParams(Gates::GateOperation op) -> std::vector { + switch (Pennylane::Util::lookup(Gates::Constant::gate_num_params, op)) { + case 0: + return {}; + case 1: + return {0.312}; + case 3: + return {0.128, -0.563, 1.414}; + default: + PL_ABORT("The number of parameters for a given gate is unknown."); + } + return {}; +} +/** + * @brief Generate random unitary matrix + * + * @return Generated unitary matrix in row-major format + */ +template +auto randomUnitary(RandomEngine &re, size_t num_qubits) + -> std::vector> { + using ComplexPrecisionT = std::complex; + const size_t dim = (1U << num_qubits); + std::vector res(dim * dim, ComplexPrecisionT{}); + + std::normal_distribution dist; + + auto generator = [&dist, &re]() -> ComplexPrecisionT { + return ComplexPrecisionT{dist(re), dist(re)}; + }; + + std::generate(res.begin(), res.end(), generator); + + // Simple algorithm to make rows orthogonal with Gram-Schmidt + // This algorithm is unstable but works for a small matrix. + // Use QR decomposition when we have LAPACK support. + + for (size_t row2 = 0; row2 < dim; row2++) { + ComplexPrecisionT *row2_p = res.data() + row2 * dim; + for (size_t row1 = 0; row1 < row2; row1++) { + const ComplexPrecisionT *row1_p = res.data() + row1 * dim; + ComplexPrecisionT dot12 = Util::innerProdC(row1_p, row2_p, dim); + ComplexPrecisionT dot11 = squaredNorm(row1_p, dim); + + // orthogonalize row2 + std::transform( + row2_p, row2_p + dim, row1_p, row2_p, + [scale = dot12 / dot11](auto &elt2, const auto &elt1) { + return elt2 - scale * elt1; + }); + } + } + + // Normalize each row + for (size_t row = 0; row < dim; row++) { + ComplexPrecisionT *row_p = res.data() + row * dim; + PrecisionT norm2 = std::sqrt(squaredNorm(row_p, dim)); - scaleVector(res, std::complex{1.0, 0.0} / std::sqrt(squared_norm)); + // noramlize row2 + std::transform(row_p, row_p + dim, row_p, [norm2](const auto c) { + return (static_cast(1.0) / norm2) * c; + }); + } return res; } + +template struct PrecisionToName; + +template <> struct PrecisionToName { + constexpr static auto value = "float"; +}; +template <> struct PrecisionToName { + constexpr static auto value = "double"; +}; +} // namespace Pennylane diff --git a/pennylane_lightning/src/tests/TestKernels.hpp b/pennylane_lightning/src/tests/TestKernels.hpp new file mode 100644 index 0000000000..e9b9cfa785 --- /dev/null +++ b/pennylane_lightning/src/tests/TestKernels.hpp @@ -0,0 +1,13 @@ +#pragma once +/** + * @brief We define test kernels. Note that kernels not registered to + * AvailableKernels can be also tested by adding it to here. + */ +#include "GateImplementationsLM.hpp" +#include "GateImplementationsPI.hpp" + +#include "TypeList.hpp" + +using TestKernels = + Pennylane::Util::TypeList; diff --git a/pennylane_lightning/src/tests/TestMacros.hpp b/pennylane_lightning/src/tests/TestMacros.hpp deleted file mode 100644 index b2c9552348..0000000000 --- a/pennylane_lightning/src/tests/TestMacros.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -/** - * Change this when a kernel is added - */ -#define ALL_GATE_OPERATIONS (GateOperationsPI, GateOperationsLM) diff --git a/pennylane_lightning/src/tests/Test_DynamicDispatcher.cpp b/pennylane_lightning/src/tests/Test_DynamicDispatcher.cpp index d04b399961..26aa3bde27 100644 --- a/pennylane_lightning/src/tests/Test_DynamicDispatcher.cpp +++ b/pennylane_lightning/src/tests/Test_DynamicDispatcher.cpp @@ -10,132 +10,110 @@ #include #include "DynamicDispatcher.hpp" -#include "Gates.hpp" +#include "OpToMemberFuncPtr.hpp" +#include "SelectKernel.hpp" #include "Util.hpp" #include "TestHelpers.hpp" using namespace Pennylane; +using namespace Pennylane::Gates; +namespace Constant = Pennylane::Gates::Constant; -using Pennylane::Internal::callGateOps; -using Pennylane::Internal::GateOpsFuncPtrPairs; +using Pennylane::Gates::callGateOps; /** * @file This file contains tests for DynamicDispatcher class + * + * We just check DynamicDispacther calls the correct functuion by comparing + * the result from it with that of the direct call. */ - -std::vector createWires(GateOperations op) { - switch (lookup(Constant::gate_wires, op)) { - case 1: - return {0}; - case 2: - return {0, 1}; - case 3: - return {0, 1, 2}; - default: - break; - } - PL_ABORT("The number of wires for a given gate is unset."); - return {}; -} - -template -std::vector createParams(GateOperations op) { - switch (lookup(Constant::gate_num_params, op)) { - case 0: - return {}; - case 1: - return {0.312}; - case 3: - return {0.128, -0.563, 1.414}; - default: - break; - } - PL_ABORT("The number of wires for a given gate is unset."); - return {}; -} - -template +template struct testDispatchForKernel { - template + template < + GateOperation gate_op, class RandomEngine, + std::enable_if_t< + Util::array_has_elt(GateImplementation::implemented_gates, gate_op), + bool> = true> static void test(RandomEngine &re, size_t num_qubits) { using CFP_t = std::complex; - std::vector ini_st = - create_random_state(re, num_qubits); + const std::vector ini_st = + createRandomState(re, num_qubits); std::vector expected = ini_st; const auto wires = createWires(gate_op); const auto params = createParams(gate_op); - constexpr size_t num_params = - static_lookup(Constant::gate_num_params); - - auto gate_func = static_lookup( - GateOpsFuncPtrPairs::value); - + // We first calculate expected directly calling a static member function + // in the GateImplementation + auto gate_func = + GateOpToMemberFuncPtr::value; callGateOps(gate_func, expected.data(), num_qubits, wires, false, params); + // and compare it to the dynamic dispatcher + auto test_st = ini_st; const auto gate_name = std::string(static_lookup(Constant::gate_names)); - - auto test_st = ini_st; - if constexpr (array_has_elt( - SelectGateOps::implemented_gates, - gate_op)) { - DynamicDispatcher::getInstance().applyOperation( - kernel, test_st.data(), num_qubits, gate_name, wires, false, - params); - REQUIRE(isApproxEqual(test_st, expected)); - } else { - REQUIRE_THROWS( - DynamicDispatcher::getInstance().applyOperation( - kernel, test_st.data(), num_qubits, gate_name.c_str(), - wires, false, params)); - } + DynamicDispatcher::getInstance().applyOperation( + GateImplementation::kernel_id, test_st.data(), num_qubits, + gate_name, wires, false, params); + REQUIRE(test_st == expected); } + + template < + GateOperation gate_op, class RandomEngine, + std::enable_if_t = true> + static void test(RandomEngine &re, size_t num_qubits) { + } // Do nothing if not implemented }; -template +template constexpr void testAllGatesForKernelIter(RandomEngine &re, size_t max_num_qubits) { - if constexpr (idx < static_cast(GateOperations::END) - 1) { - constexpr auto gate_op = static_cast(idx); - for (size_t num_qubits = static_lookup(Constant::gate_wires); - num_qubits <= max_num_qubits; num_qubits++) { - testDispatchForKernel::template test(re, - num_qubits); + if constexpr (idx < static_cast(GateOperation::END)) { + constexpr auto gate_op = static_cast(idx); + + if constexpr (gate_op != GateOperation::Matrix) { // ignore Matrix + for (size_t num_qubits = 3; num_qubits <= max_num_qubits; + num_qubits++) { + testDispatchForKernel:: + template test(re, num_qubits); + } } - testAllGatesForKernelIter( - re, max_num_qubits); + testAllGatesForKernelIter(re, max_num_qubits); } } -template void testAllGatesForKernel(RandomEngine &re, size_t max_num_qubits) { - testAllGatesForKernelIter(re, - max_num_qubits); + testAllGatesForKernelIter( + re, max_num_qubits); } -template +template void testAllKernelsIter(RandomEngine &re, size_t max_num_qubits) { - if constexpr (idx < Constant::available_kernels.size()) { - testAllGatesForKernel(Constant::available_kernels[idx])>( + if constexpr (!std::is_same_v) { + testAllGatesForKernel( re, max_num_qubits); - testAllKernelsIter( - re, max_num_qubits); + testAllKernelsIter(re, max_num_qubits); } } template void testAllKernels(RandomEngine &re, size_t max_num_qubits) { - testAllKernelsIter(re, max_num_qubits); + testAllKernelsIter( + re, max_num_qubits); } TEMPLATE_TEST_CASE("DynamicDispatcher::applyOperation", "[DynamicDispatcher]", @@ -144,8 +122,7 @@ TEMPLATE_TEST_CASE("DynamicDispatcher::applyOperation", "[DynamicDispatcher]", constexpr size_t max_num_qubits = 6; - testAllGatesForKernel(re, - max_num_qubits); + testAllKernels(re, max_num_qubits); } // DynamicDispatcher::appyMatrix? diff --git a/pennylane_lightning/src/tests/Test_GateImplementations_Generator.cpp b/pennylane_lightning/src/tests/Test_GateImplementations_Generator.cpp new file mode 100644 index 0000000000..000e97f879 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_GateImplementations_Generator.cpp @@ -0,0 +1,155 @@ +#include "OpToMemberFuncPtr.hpp" +#include "SelectKernel.hpp" +#include "TestHelpers.hpp" +#include "TestKernels.hpp" +#include "Util.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * @file Test_GateImplementations_Generator.cpp + * + * This file tests gate generators. To be specific, we test whether each + * generator satisfies + * @rst + * :math:`I*G |\psi> = \parital{U(\theta)}/\partial{\theta}_{\theta=0} |\psi>` + * @endrst + */ +using namespace Pennylane; +using namespace Pennylane::Gates; + +/** + * @brief As clang does not support constexpr string_view::remove_prefix yet. + */ +constexpr std::string_view remove_prefix(const std::string_view &str, + size_t len) { + return std::string_view(str.data() + len, str.length() - len); +} + +constexpr auto gate_name_to_ops = Util::reverse_pairs(Constant::gate_names); + +template +constexpr auto findGateOpForGenerator() -> GateOperation { + constexpr auto gntr_name = + remove_prefix(static_lookup(Constant::generator_names), 9); + + for (const auto &[gate_op, gate_name] : Constant::gate_names) { + if (gate_name == gntr_name) { + return gate_op; + } + } + return GateOperation{}; +} + +template constexpr auto generatorGatePairsIter() { + if constexpr (gntr_idx < Constant::generator_names.size()) { + constexpr auto gntr_op = + std::get<0>(Constant::generator_names[gntr_idx]); + constexpr auto gate_op = findGateOpForGenerator(); + + return Util::prepend_to_tuple(std::pair{gntr_op, gate_op}, + generatorGatePairsIter()); + } else { + return std::tuple{}; + } +} + +/** + * @brief Array of all generator operations with the corresponding gate + * operations. + */ +constexpr static auto generator_gate_pairs = + Util::tuple_to_array(generatorGatePairsIter<0>()); + +template +void testGeneratorForGate(RandomEngine &re, size_t num_qubits) { + using ComplexPrecisionT = std::complex; + constexpr auto I = Util::IMAG(); + + constexpr ParamT eps = 1e-4; // For finite difference + + constexpr auto gate_op = static_lookup(generator_gate_pairs); + constexpr auto gate_name = static_lookup(Constant::gate_names); + + DYNAMIC_SECTION("Test generator of " << gate_name << " for kernel " + << GateImplementation::name) { + const auto wires = createWires(gate_op); + const auto ini_st = createRandomState(re, num_qubits); + + auto gntr_func = + GeneratorOpToMemberFuncPtr::value; + auto gate_func = + GateOpToMemberFuncPtr::value; + + /* Apply generator to gntr_st */ + auto gntr_st = ini_st; + PrecisionT scale = gntr_func(gntr_st.data(), num_qubits, wires, false); + scaleVector(gntr_st, I * scale); + + /* Compute the derivative of the unitary gate applied to ini_st using + * finite difference */ + + auto diff_st_1 = ini_st; + auto diff_st_2 = ini_st; + + gate_func(diff_st_1.data(), num_qubits, wires, false, eps); + gate_func(diff_st_2.data(), num_qubits, wires, false, -eps); + + std::vector gate_der_st(1U << num_qubits); + + std::transform( + diff_st_1.cbegin(), diff_st_1.cend(), diff_st_2.cbegin(), + gate_der_st.begin(), + [](ComplexPrecisionT a, ComplexPrecisionT b) { return a - b; }); + + scaleVector(gate_der_st, static_cast(0.5) / eps); + + REQUIRE(gntr_st == PLApprox(gate_der_st).margin(1e-3)); + } +} +template +void testAllGeneratorForKernel(RandomEngine &re, size_t num_qubits) { + if constexpr (gntr_idx < + GateImplementation::implemented_generators.size()) { + constexpr auto gntr_op = + GateImplementation::implemented_generators[gntr_idx]; + testGeneratorForGate( + re, num_qubits); + testAllGeneratorForKernel(re, num_qubits); + } +} + +template +void testAllGeneratorsAndKernels(RandomEngine &re, size_t num_qubits) { + if constexpr (!std::is_same_v) { + using GateImplementation = typename TypeList::Type; + testAllGeneratorForKernel( + re, num_qubits); + testAllGeneratorsAndKernels(re, num_qubits); + } +} + +TEMPLATE_TEST_CASE("Test all generators of all kernels", + "[GateImplementations_Generator]", float, double) { + using PrecisionT = TestType; + using ParamT = TestType; + + std::mt19937 re{1337}; + + testAllGeneratorsAndKernels(re, 4); +} diff --git a/pennylane_lightning/src/tests/Test_GateImplementations_Inverse.cpp b/pennylane_lightning/src/tests/Test_GateImplementations_Inverse.cpp new file mode 100644 index 0000000000..18988e80eb --- /dev/null +++ b/pennylane_lightning/src/tests/Test_GateImplementations_Inverse.cpp @@ -0,0 +1,88 @@ +#include "OpToMemberFuncPtr.hpp" +#include "SelectKernel.hpp" +#include "TestHelpers.hpp" +#include "TestKernels.hpp" +#include "Util.hpp" + +#include + +#include +#include +#include + +/** + * @file + * We test inverse of each gate operation here. For all gates in + * implemented_gates, we test wether the state after applying an operation and + * its inverse is the same as the initial state. + * + * Note the we only test generators only when it is included in + * constexpr member variable implemented_generators. + */ + +using namespace Pennylane; +using namespace Pennylane::Gates; + +template +void testInverseKernelGate(RandomEngine &re, size_t num_qubits) { + if constexpr (gate_op != GateOperation::Matrix) { + constexpr auto gate_name = static_lookup(Constant::gate_names); + DYNAMIC_SECTION("Test inverse of " << gate_name << " for kernel " + << GateImplementation::name) { + const auto ini_st = createRandomState(re, num_qubits); + + auto st = ini_st; + + const auto func_ptr = + GateOpToMemberFuncPtr::value; + + const auto wires = createWires(gate_op); + const auto params = createParams(gate_op); + + callGateOps(func_ptr, st.data(), num_qubits, wires, false, params); + callGateOps(func_ptr, st.data(), num_qubits, wires, true, params); + + REQUIRE(st == PLApprox(ini_st).margin(1e-7)); + } + } +} + +template +void testKernelInversesIter(RandomEngine &re, size_t num_qubits) { + if constexpr (gate_idx < GateImplementation::implemented_gates.size()) { + testInverseKernelGate( + re, num_qubits); + testKernelInversesIter(re, num_qubits); + } +} + +template +void testKernelInverses(RandomEngine &re, size_t num_qubits) { + testKernelInversesIter( + re, num_qubits); +} + +template +void testKernels(RandomEngine &re, size_t num_qubits) { + if constexpr (!std::is_same_v) { + using GateImplementation = typename TypeList::Type; + testKernelInverses(re, + num_qubits); + testKernels(re, + num_qubits); + } +} + +TEMPLATE_TEST_CASE("Test inverse of gate implementations", + "[GateImplementations_Inverse]", float, double) { + std::mt19937 re(1337); + + testKernels(re, 5); +} diff --git a/pennylane_lightning/src/tests/Test_GateImplementations_Matrix.cpp b/pennylane_lightning/src/tests/Test_GateImplementations_Matrix.cpp new file mode 100644 index 0000000000..d3290160f4 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_GateImplementations_Matrix.cpp @@ -0,0 +1,968 @@ +#include "AvailableKernels.hpp" +#include "SelectKernel.hpp" +#include "TestHelpers.hpp" +#include "Util.hpp" + +#include + +using namespace Pennylane; + +template +using ApplyMatrixType = void (*)(std::complex *, size_t, + const std::complex *, + const std::vector &, bool); + +template +struct IsApplyMatrixDefined { + constexpr static bool value = false; +}; +template +struct IsApplyMatrixDefined< + PrecisionT, GateImplementation, + std::enable_if_t< + std::is_pointer_v>( + &GateImplementation::template applyMatrix))>>> { + constexpr static bool value = true; +}; + +template +void testApplyMatrix() { + using ComplexPrecisionT = std::complex; + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix0 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {0}; + std::vector matrix{ + ComplexPrecisionT{-0.6709485262524046, -0.6304426335363695}, + ComplexPrecisionT{-0.14885403153998722, 0.3608498832392019}, + ComplexPrecisionT{-0.2376311670004963, 0.3096798175687841}, + ComplexPrecisionT{-0.8818365947322423, -0.26456390390903695}, + }; + + std::vector expected{ + ComplexPrecisionT{-0.088159230256, -0.200310710166}, + ComplexPrecisionT{-0.092298136103, -0.239992165702}, + ComplexPrecisionT{-0.222261050476, -0.172156102614}, + ComplexPrecisionT{-0.028641644551, -0.151772474905}, + ComplexPrecisionT{-0.041128255234, -0.051213774079}, + ComplexPrecisionT{-0.023306864430, -0.139832054875}, + ComplexPrecisionT{-0.034436430308, 0.030470593143}, + ComplexPrecisionT{-0.235253129822, -0.147654159822}, + ComplexPrecisionT{-0.136553865781, -0.046844610803}, + ComplexPrecisionT{-0.208578251022, -0.150162656683}, + ComplexPrecisionT{-0.351228795803, -0.163875086963}, + ComplexPrecisionT{-0.025554644471, -0.259011641330}, + ComplexPrecisionT{-0.202335094297, -0.146984720225}, + ComplexPrecisionT{-0.046497880559, -0.236870070744}, + ComplexPrecisionT{-0.285599324363, -0.224949005764}, + ComplexPrecisionT{-0.317311776750, -0.144580799156}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {1}; + std::vector matrix{ + ComplexPrecisionT{-0.06456334151703813, -0.46701592144475385}, + ComplexPrecisionT{-0.7849162862155974, -0.40203747049594296}, + ComplexPrecisionT{0.35001199887831924, 0.8094561783632672}, + ComplexPrecisionT{-0.4618134467695434, -0.09487168351817299}, + }; + + std::vector expected{ + ComplexPrecisionT{0.042236556846, -0.211840395533}, + ComplexPrecisionT{0.108556009215, -0.248396142329}, + ComplexPrecisionT{0.077192593353, -0.222692634399}, + ComplexPrecisionT{-0.118547567659, -0.207571660592}, + ComplexPrecisionT{-0.054636543376, 0.156024360645}, + ComplexPrecisionT{-0.093651051458, 0.173046696421}, + ComplexPrecisionT{0.010487272302, 0.231867409698}, + ComplexPrecisionT{-0.231889585995, 0.021414192236}, + ComplexPrecisionT{-0.094804784349, -0.198463810478}, + ComplexPrecisionT{0.104863870690, -0.279893530935}, + ComplexPrecisionT{-0.151908242161, -0.395747235633}, + ComplexPrecisionT{-0.058577814605, -0.273974623210}, + ComplexPrecisionT{-0.113602984277, 0.018046788174}, + ComplexPrecisionT{-0.070369209805, 0.063602025384}, + ComplexPrecisionT{-0.152841460397, 0.225969197890}, + ComplexPrecisionT{-0.316053740116, 0.024024286121}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix2 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {2}; + std::vector matrix{ + ComplexPrecisionT{-0.09868517256862797, 0.1346373537372914}, + ComplexPrecisionT{-0.6272437275794093, 0.7607228969250281}, + ComplexPrecisionT{0.5083047843569023, 0.8448433380773042}, + ComplexPrecisionT{-0.07776920594546943, -0.14770893985433542}, + }; + + std::vector expected{ + ComplexPrecisionT{-0.313651523675, 0.158015857153}, + ComplexPrecisionT{-0.190830404547, -0.056031178288}, + ComplexPrecisionT{-0.012743088406, 0.186575564154}, + ComplexPrecisionT{-0.042189526065, 0.268035298797}, + ComplexPrecisionT{-0.097632230188, -0.063021774411}, + ComplexPrecisionT{-0.264297959806, 0.152692968180}, + ComplexPrecisionT{-0.043259258448, 0.098579615666}, + ComplexPrecisionT{-0.115061048825, 0.063129899779}, + ComplexPrecisionT{-0.339269297035, 0.151091393323}, + ComplexPrecisionT{-0.253672211262, -0.122958027429}, + ComplexPrecisionT{-0.020955943769, 0.036078832827}, + ComplexPrecisionT{-0.012472454359, 0.175959514614}, + ComplexPrecisionT{-0.338334782053, 0.185015137716}, + ComplexPrecisionT{-0.338472277486, 0.119068450947}, + ComplexPrecisionT{0.014757040712, 0.164711383267}, + ComplexPrecisionT{-0.163054937984, 0.106682609797}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix0,1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {0, 1}; + std::vector matrix{ + ComplexPrecisionT{-0.010948839478141403, -0.4261536209511877}, + ComplexPrecisionT{-0.6522252885639435, -0.2941022724640708}, + ComplexPrecisionT{0.26225131765405274, 0.4236139177304751}, + ComplexPrecisionT{-0.12618692657772357, 0.20550327298620602}, + ComplexPrecisionT{-0.3628656010611667, 0.12279634811005145}, + ComplexPrecisionT{-0.299785993479099, 0.08052108649543024}, + ComplexPrecisionT{-0.3268760347664654, 0.29288358376151247}, + ComplexPrecisionT{0.06355368774014421, -0.7484828109139796}, + ComplexPrecisionT{-0.42696246581802144, 0.5312298019756412}, + ComplexPrecisionT{0.19082021416756584, -0.3848180554033438}, + ComplexPrecisionT{0.2905316308379382, 0.3567658763647916}, + ComplexPrecisionT{0.3051329918715622, 0.21495115396751993}, + ComplexPrecisionT{-0.04349597374383496, 0.45291155567640123}, + ComplexPrecisionT{-0.4540435198052925, 0.03313504573634055}, + ComplexPrecisionT{-0.5735449843600084, -0.1360234950731129}, + ComplexPrecisionT{-0.14650781658884487, 0.46562323100514574}, + }; + + std::vector expected{ + ComplexPrecisionT{-0.010522294886, -0.097227414150}, + ComplexPrecisionT{0.025218372075, -0.118918153811}, + ComplexPrecisionT{0.027356493011, 0.038593767440}, + ComplexPrecisionT{-0.260209778025, -0.029664187740}, + ComplexPrecisionT{-0.074148286001, -0.197175492308}, + ComplexPrecisionT{-0.047445065528, -0.120432480588}, + ComplexPrecisionT{-0.162531426347, -0.232103748235}, + ComplexPrecisionT{-0.080908114074, -0.339097688508}, + ComplexPrecisionT{-0.065452006637, 0.167593688919}, + ComplexPrecisionT{-0.174290686562, 0.218180044463}, + ComplexPrecisionT{-0.040769815236, 0.391540527230}, + ComplexPrecisionT{-0.032888113119, 0.062559768276}, + ComplexPrecisionT{-0.209674188196, 0.072947117389}, + ComplexPrecisionT{-0.305237717975, -0.088612881006}, + ComplexPrecisionT{-0.345917435397, 0.079914065740}, + ComplexPrecisionT{-0.304373922289, -0.050696747855}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix1,3 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {1, 3}; + std::vector matrix{ + ComplexPrecisionT{-0.4945444988183558, -0.11776474515763265}, + ComplexPrecisionT{-0.29362382883961335, 0.4309563356559181}, + ComplexPrecisionT{0.38642000389978287, -0.11761080702891993}, + ComplexPrecisionT{-0.4625432352960688, 0.3041708755288573}, + ComplexPrecisionT{-0.01670164516238861, -0.06290137757928804}, + ComplexPrecisionT{-0.5303475445655139, -0.281210965038839}, + ComplexPrecisionT{0.3210907020450156, -0.4908226494303132}, + ComplexPrecisionT{0.5371130944965893, 0.054034789251905496}, + ComplexPrecisionT{-0.6374608763985821, 0.1283980517316574}, + ComplexPrecisionT{0.11623580942989631, -0.5772052807682595}, + ComplexPrecisionT{-0.2801100593795251, -0.25593219011143187}, + ComplexPrecisionT{-0.24178695440574766, -0.16750226628447634}, + ComplexPrecisionT{0.43526551626023624, 0.35358616624711525}, + ComplexPrecisionT{0.12383614509048962, -0.07550807002819623}, + ComplexPrecisionT{0.3807245050274032, -0.45158286780318524}, + ComplexPrecisionT{-0.47191404042553947, -0.3047996016248278}, + }; + + std::vector expected{ + ComplexPrecisionT{-0.264205950272, -0.078825796270}, + ComplexPrecisionT{0.033605397647, -0.097442075257}, + ComplexPrecisionT{-0.350166020754, -0.037125900500}, + ComplexPrecisionT{0.220722295888, -0.022059951504}, + ComplexPrecisionT{0.020457336820, -0.248270874284}, + ComplexPrecisionT{0.181621494746, 0.067111057534}, + ComplexPrecisionT{-0.136891895494, -0.142700100623}, + ComplexPrecisionT{0.037867646910, 0.084010926977}, + ComplexPrecisionT{-0.139979818310, -0.092901195560}, + ComplexPrecisionT{0.096552234651, -0.070334396489}, + ComplexPrecisionT{-0.305840219133, -0.139674837753}, + ComplexPrecisionT{0.376774144027, -0.191209037401}, + ComplexPrecisionT{0.038354787323, -0.247322773715}, + ComplexPrecisionT{0.202764286721, -0.117020408763}, + ComplexPrecisionT{-0.180698521324, -0.259571676988}, + ComplexPrecisionT{0.197697750266, -0.048932641006}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix1,2,3 - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {1, 2, 3}; + std::vector matrix{ + ComplexPrecisionT{-0.14601911598243822, -0.18655250647340088}, + ComplexPrecisionT{-0.03917826201290317, -0.031161687050443518}, + ComplexPrecisionT{0.11497626236175404, 0.38310733543366354}, + ComplexPrecisionT{-0.0929691815340695, 0.1219804125497268}, + ComplexPrecisionT{0.07306514883467692, 0.017445444816725875}, + ComplexPrecisionT{-0.27330866098918355, -0.6007032759764033}, + ComplexPrecisionT{0.4530754397715841, -0.08267189625512258}, + ComplexPrecisionT{0.32125201986075, -0.036845158875036116}, + ComplexPrecisionT{0.032317572838307884, 0.02292755555300329}, + ComplexPrecisionT{-0.18775945295623664, -0.060215004737844156}, + ComplexPrecisionT{-0.3093351335745536, -0.2061961962889725}, + ComplexPrecisionT{0.4216087567144761, 0.010534488410902099}, + ComplexPrecisionT{0.2769943541718527, -0.26016137877135465}, + ComplexPrecisionT{0.18727884147867532, 0.02830415812286322}, + ComplexPrecisionT{0.3367562196770689, -0.5250999173939218}, + ComplexPrecisionT{0.05770014289220745, 0.26595514845958573}, + ComplexPrecisionT{0.37885720163317027, 0.3110931426403546}, + ComplexPrecisionT{0.13436510737129648, -0.4083415934958021}, + ComplexPrecisionT{-0.5443665467635203, 0.2458343977310266}, + ComplexPrecisionT{-0.050346912365833024, 0.08709833123617361}, + ComplexPrecisionT{0.11505259829552131, 0.010155858056939438}, + ComplexPrecisionT{-0.2930849061531229, 0.019339259194141145}, + ComplexPrecisionT{0.011825409829453282, 0.011597907736881019}, + ComplexPrecisionT{-0.10565527258356637, -0.3113689446440079}, + ComplexPrecisionT{0.0273191284561944, -0.2479498526173881}, + ComplexPrecisionT{-0.5528072425836249, -0.06114469689935285}, + ComplexPrecisionT{-0.20560364740746587, -0.3800208994544297}, + ComplexPrecisionT{-0.008236143958221483, 0.3017421511504845}, + ComplexPrecisionT{0.04817188123334976, 0.08550951191632741}, + ComplexPrecisionT{-0.24081054643565586, -0.3412671345149831}, + ComplexPrecisionT{-0.38913538197001885, 0.09288402897806938}, + ComplexPrecisionT{-0.07937578245883717, 0.013979426755633685}, + ComplexPrecisionT{0.22246583652015395, -0.18276674810033927}, + ComplexPrecisionT{0.22376666162382491, 0.2995723155125488}, + ComplexPrecisionT{-0.1727191441070097, -0.03880522034607489}, + ComplexPrecisionT{0.075780203819001, 0.2818783673816625}, + ComplexPrecisionT{-0.6161322400651016, 0.26067347179217193}, + ComplexPrecisionT{-0.021161519614267765, -0.08430919051054794}, + ComplexPrecisionT{0.1676500381348944, -0.30645601624407504}, + ComplexPrecisionT{-0.28858251997285883, 0.018089595494883842}, + ComplexPrecisionT{-0.19590767481842053, -0.12844366632033652}, + ComplexPrecisionT{0.18707834504831794, -0.1363932722670649}, + ComplexPrecisionT{-0.07224221779769334, -0.11267803536286894}, + ComplexPrecisionT{-0.23897684826459387, -0.39609971967853685}, + ComplexPrecisionT{-0.0032110880452929555, -0.29294331305690136}, + ComplexPrecisionT{-0.3188741682462722, -0.17338979346647143}, + ComplexPrecisionT{0.08194395032821632, -0.002944814673179825}, + ComplexPrecisionT{-0.5695791830944521, 0.33299548924055095}, + ComplexPrecisionT{-0.4983660307441444, -0.4222358493977972}, + ComplexPrecisionT{0.05533914327048402, -0.42575842134560576}, + ComplexPrecisionT{-0.2187623521182678, -0.03087596187054778}, + ComplexPrecisionT{0.11278255885846857, 0.07075886163492914}, + ComplexPrecisionT{-0.3054684775292515, -0.1739796870866232}, + ComplexPrecisionT{0.14151567663565712, 0.20399935744127418}, + ComplexPrecisionT{0.06720165377364941, 0.07543463072363207}, + ComplexPrecisionT{0.08019665306716581, -0.3473013434358584}, + ComplexPrecisionT{-0.2600167605995786, -0.08795704036197827}, + ComplexPrecisionT{0.125680477777759, 0.266342700305046}, + ComplexPrecisionT{-0.1586772594600269, 0.187360909108502}, + ComplexPrecisionT{-0.4653314704208982, 0.4048609954619629}, + ComplexPrecisionT{0.39992560380733094, -0.10029244177901954}, + ComplexPrecisionT{0.2533527906886461, 0.05222114898540775}, + ComplexPrecisionT{-0.15840033949128557, -0.2727320427534386}, + ComplexPrecisionT{-0.21590866323269536, -0.1191163626522938}, + }; + + std::vector expected{ + ComplexPrecisionT{0.140662010561, 0.048572894309}, + ComplexPrecisionT{0.021854656046, 0.079393113826}, + ComplexPrecisionT{-0.069686446335, -0.072998481807}, + ComplexPrecisionT{-0.126819464879, -0.378029601439}, + ComplexPrecisionT{-0.134716141061, 0.034463497732}, + ComplexPrecisionT{-0.054908086720, -0.157073288683}, + ComplexPrecisionT{0.005790250878, -0.346174821950}, + ComplexPrecisionT{-0.204004872847, 0.019278209615}, + ComplexPrecisionT{0.336927171579, 0.074028686268}, + ComplexPrecisionT{0.170822794112, -0.062115684096}, + ComplexPrecisionT{-0.133087934403, -0.164577625932}, + ComplexPrecisionT{-0.240412977475, -0.331519081061}, + ComplexPrecisionT{-0.228436573919, -0.063017646940}, + ComplexPrecisionT{-0.016556534913, -0.258822480482}, + ComplexPrecisionT{-0.012416037504, -0.214182329161}, + ComplexPrecisionT{-0.204751961090, -0.130791666115}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Matrix0,1,2,3 - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.203377341216, 0.132238554262}, + ComplexPrecisionT{0.216290940442, 0.203109511967}, + ComplexPrecisionT{0.290374372568, 0.123095338906}, + ComplexPrecisionT{0.040762810130, 0.153237600777}, + ComplexPrecisionT{0.062445212079, 0.106020046388}, + ComplexPrecisionT{0.041489260594, 0.149813636657}, + ComplexPrecisionT{0.002100244854, 0.099744848045}, + ComplexPrecisionT{0.281559630427, 0.083376695381}, + ComplexPrecisionT{0.073652349575, 0.066811372960}, + ComplexPrecisionT{0.150797357980, 0.146266222503}, + ComplexPrecisionT{0.324043781913, 0.157417591307}, + ComplexPrecisionT{0.040556496061, 0.254572386140}, + ComplexPrecisionT{0.204954964152, 0.098550445557}, + ComplexPrecisionT{0.056681743348, 0.225803880189}, + ComplexPrecisionT{0.327486634260, 0.130699704247}, + ComplexPrecisionT{0.299805387808, 0.150417378569}, + }; + + const std::vector wires = {0, 1, 2, 3}; + std::vector matrix{ + ComplexPrecisionT{-0.0811773464755885, -0.19682208345860647}, + ComplexPrecisionT{-0.06700740455243999, -0.04561583597315822}, + ComplexPrecisionT{0.46656064487776394, -0.20461647097832134}, + ComplexPrecisionT{0.010256952837273701, 0.139986239661604}, + ComplexPrecisionT{0.09467881261623481, -0.03336335480963736}, + ComplexPrecisionT{0.15936811587631455, 0.057315624600284346}, + ComplexPrecisionT{0.17978576553142844, 0.15678425145109015}, + ComplexPrecisionT{0.18055277474308914, -0.1165523390317797}, + ComplexPrecisionT{0.012281901962826983, 0.18007645868598343}, + ComplexPrecisionT{0.03284274273649668, 0.10947017370431167}, + ComplexPrecisionT{0.3432582684584454, -0.06867050921150765}, + ComplexPrecisionT{-0.10561876215199961, 0.07949429539442246}, + ComplexPrecisionT{0.19701386373457638, 0.16981005094001442}, + ComplexPrecisionT{0.13534591755792408, 0.28066932476781636}, + ComplexPrecisionT{0.1837983164589069, -0.23569832312461225}, + ComplexPrecisionT{-0.27499236539110833, -0.10791973216625947}, + ComplexPrecisionT{0.1834816172494385, -0.034827779112586055}, + ComplexPrecisionT{-0.0009230068403018204, -0.029512495868369862}, + ComplexPrecisionT{-0.05901102512885115, -0.15390444461487718}, + ComplexPrecisionT{0.05688461261840959, -0.03889302125105607}, + ComplexPrecisionT{0.29577734654421267, -0.13100455290792246}, + ComplexPrecisionT{0.3391919314190099, -0.2243138830097492}, + ComplexPrecisionT{0.16273296736459222, -0.2670701510726166}, + ComplexPrecisionT{-0.03336090672169414, 0.2455179411098639}, + ComplexPrecisionT{-0.3787491481884647, -0.11947116040140429}, + ComplexPrecisionT{-0.24173307291148638, -0.2213075797523435}, + ComplexPrecisionT{0.036982779372098394, -0.03544917593630506}, + ComplexPrecisionT{0.08741852442523015, 0.03010491205939035}, + ComplexPrecisionT{-0.010647043925551675, 0.37808566195485366}, + ComplexPrecisionT{-0.010545077863258843, -0.24546296648810367}, + ComplexPrecisionT{0.1316231994514309, -0.06760672518348157}, + ComplexPrecisionT{0.016131973166901414, -0.03503746492310948}, + ComplexPrecisionT{-0.06492487598324831, -0.1748331563535617}, + ComplexPrecisionT{0.031460705076800655, 0.11334377057699434}, + ComplexPrecisionT{0.03441902155646885, 0.22408157859399114}, + ComplexPrecisionT{0.0371280750051708, -0.1804040960738852}, + ComplexPrecisionT{0.09535644451753002, -0.025614187155079796}, + ComplexPrecisionT{-0.09216255279186078, 0.05633363422606912}, + ComplexPrecisionT{-0.11634815903744167, -0.23538903164817157}, + ComplexPrecisionT{-0.3039552254656059, -0.20552815005438066}, + ComplexPrecisionT{-0.27859216491701827, 0.10853204476017964}, + ComplexPrecisionT{0.24626461042473888, -0.1518650171214598}, + ComplexPrecisionT{0.4642911826057222, -0.11812007476730525}, + ComplexPrecisionT{0.36313614431900554, -0.14249281948146095}, + ComplexPrecisionT{-0.06380575042422873, -0.23288959902354697}, + ComplexPrecisionT{0.044361592585855265, -0.019957902264200526}, + ComplexPrecisionT{0.030605075870618798, -0.058098104432514286}, + ComplexPrecisionT{-0.014272674386900611, -0.14520609081384306}, + ComplexPrecisionT{0.23277420359739226, -0.14947000483002895}, + ComplexPrecisionT{-0.16342330755739196, 0.22676118594557015}, + ComplexPrecisionT{-0.20398531466518455, -0.28009454020028807}, + ComplexPrecisionT{-0.07735297055263991, 0.10081191135850101}, + ComplexPrecisionT{0.022873612354914773, -0.03126243586780419}, + ComplexPrecisionT{0.08881705356321942, -0.004514840514670637}, + ComplexPrecisionT{-0.05757185774631672, -0.13015459704290877}, + ComplexPrecisionT{-0.32070853617502854, -0.07803053060891867}, + ComplexPrecisionT{-0.07706926458785131, 0.05207299820519886}, + ComplexPrecisionT{0.2503902572322073, 0.09099135737552513}, + ComplexPrecisionT{-0.16383113911767921, 0.2973647186513588}, + ComplexPrecisionT{-0.27819268180170403, -0.30236394856248}, + ComplexPrecisionT{-0.12373346518281966, 0.03229258305240953}, + ComplexPrecisionT{-0.14477119991828383, 0.1959246121959782}, + ComplexPrecisionT{-0.045491424991441154, -0.032367020566301676}, + ComplexPrecisionT{-0.34719732324226155, -0.08638402027658323}, + ComplexPrecisionT{-0.0831137914503245, -0.38306179315772587}, + ComplexPrecisionT{0.33896013112876805, -0.08708321371958307}, + ComplexPrecisionT{0.08295290236493533, -0.12849960294752283}, + ComplexPrecisionT{0.3062034236838223, -0.16407337320867357}, + ComplexPrecisionT{-0.2525913492428881, 0.01270242036804025}, + ComplexPrecisionT{0.07387523797228966, 0.13236037063441145}, + ComplexPrecisionT{-0.10627680904019554, -0.1304826951782252}, + ComplexPrecisionT{-0.079498759063908, -0.10575816891195548}, + ComplexPrecisionT{0.12636058586101778, -0.3562639492682855}, + ComplexPrecisionT{-0.21351837585905967, -0.05490031570511319}, + ComplexPrecisionT{-0.08211886411050803, -0.014650442132510554}, + ComplexPrecisionT{0.1075526548380377, -0.09958704526495876}, + ComplexPrecisionT{0.1547843699970367, 0.18454687141005055}, + ComplexPrecisionT{-0.06315418425117045, 0.19385000729237473}, + ComplexPrecisionT{-0.18942009592159434, 0.23211726033840208}, + ComplexPrecisionT{-0.08358478431285932, -0.17542289710060982}, + ComplexPrecisionT{0.07414365811858471, 0.002553326538433997}, + ComplexPrecisionT{-0.12301246391656774, 0.08015352317475764}, + ComplexPrecisionT{-0.01987269429433799, 0.1569301408474106}, + ComplexPrecisionT{-0.12706461080431816, -0.12379705115137626}, + ComplexPrecisionT{-0.2758787728864479, 0.0035494406457515885}, + ComplexPrecisionT{-0.00984562112886961, -0.02481667008233526}, + ComplexPrecisionT{-0.084916806161764, 0.002014985096033997}, + ComplexPrecisionT{-0.04810808029083434, -0.0559974655611716}, + ComplexPrecisionT{-0.19559161234187358, -0.356244470773283}, + ComplexPrecisionT{-0.051130798099170774, 0.3205688860172856}, + ComplexPrecisionT{0.18301730588702717, -0.35462514629054254}, + ComplexPrecisionT{-0.29345027995280915, 0.3010185851398186}, + ComplexPrecisionT{-0.2572348744363236, 0.12734173180635364}, + ComplexPrecisionT{-0.07183902584877759, -0.08131174943117606}, + ComplexPrecisionT{-0.2170702641465722, -0.2294241732024488}, + ComplexPrecisionT{-0.1333742246142454, -0.09338397402993287}, + ComplexPrecisionT{0.2793032171967457, 0.24834849095463785}, + ComplexPrecisionT{-0.08087034521910429, 0.017228399318073954}, + ComplexPrecisionT{0.2736672517309202, -0.13557890045311372}, + ComplexPrecisionT{0.016893918658642156, -0.15032601746949797}, + ComplexPrecisionT{-0.4906452426501637, 0.16391653370174594}, + ComplexPrecisionT{0.05457620687567622, 0.060399646900382575}, + ComplexPrecisionT{-0.04607801770469559, -0.11126264743946929}, + ComplexPrecisionT{-0.046762700213043976, 0.2605200559933763}, + ComplexPrecisionT{0.03720080407993604, 0.12764613070765587}, + ComplexPrecisionT{0.06752680234074637, -0.003965034003632684}, + ComplexPrecisionT{0.28187184016252287, 0.26497003409635334}, + ComplexPrecisionT{0.05337188694517632, -0.08820558091508003}, + ComplexPrecisionT{0.22213111054177023, 0.051232875330921135}, + ComplexPrecisionT{0.08329146371445284, -0.3063899155813947}, + ComplexPrecisionT{-0.1661041551303006, 0.04669729713417685}, + ComplexPrecisionT{-0.07012401396611247, 0.09229606742901898}, + ComplexPrecisionT{-0.03268230910789402, -0.07891702877461022}, + ComplexPrecisionT{-0.6088891510900916, 0.010869529571772913}, + ComplexPrecisionT{0.181327487214985, -0.10980293958533298}, + ComplexPrecisionT{-0.06910826525269803, -0.002123158429041397}, + ComplexPrecisionT{0.17226502926121215, -0.0163737379577749}, + ComplexPrecisionT{-0.29034764218037307, 0.0010825500957120766}, + ComplexPrecisionT{-0.39606935617644395, -0.11929012808517281}, + ComplexPrecisionT{0.031140622944890596, -0.08978846977992205}, + ComplexPrecisionT{0.09829720632173958, -0.15419259877086008}, + ComplexPrecisionT{-0.16979247786848609, -0.20104559788220375}, + ComplexPrecisionT{0.022771727354803907, 0.07376310045777855}, + ComplexPrecisionT{0.10248457555947008, 0.0959848862914514}, + ComplexPrecisionT{-0.08500603537774072, 0.19979163374724185}, + ComplexPrecisionT{0.16183991510732185, 0.08834832692616706}, + ComplexPrecisionT{-0.07405592956498913, 0.17113318238915956}, + ComplexPrecisionT{-0.010865837528670281, 0.18262669089522315}, + ComplexPrecisionT{0.3007340238878014, 0.01228269465380516}, + ComplexPrecisionT{0.14841870857595918, 0.04052914054163879}, + ComplexPrecisionT{0.07979188546811528, 0.03448057959133652}, + ComplexPrecisionT{-0.08878394458437894, -0.2555136336296365}, + ComplexPrecisionT{0.05550210679913066, -0.13359457037788244}, + ComplexPrecisionT{-0.24170895206097853, -0.5657605420017089}, + ComplexPrecisionT{0.1366326110342102, 0.39911030119908275}, + ComplexPrecisionT{-0.057513002498134125, -0.056872501379940044}, + ComplexPrecisionT{0.12028326294930897, -0.17837416458259414}, + ComplexPrecisionT{0.12424194113702836, 0.024058926963841928}, + ComplexPrecisionT{0.053139541530483975, -0.05945051420622338}, + ComplexPrecisionT{0.11364580087339703, -0.21140604493359308}, + ComplexPrecisionT{-0.023179998082468196, 0.12855784626080657}, + ComplexPrecisionT{0.18447197799514206, 0.059283465491411}, + ComplexPrecisionT{0.02268886826432022, 0.14991864893901055}, + ComplexPrecisionT{-0.11684281491031034, 0.05965071504188227}, + ComplexPrecisionT{0.1644084622807226, 0.2504871714298593}, + ComplexPrecisionT{0.13677900982607485, 0.045751657613712624}, + ComplexPrecisionT{0.07637224308536966, 0.043949816587819344}, + ComplexPrecisionT{-0.15232748233545576, 0.39490236818664404}, + ComplexPrecisionT{-0.07620965894681739, -0.00044015958182986453}, + ComplexPrecisionT{0.015228807982348392, -0.03989508573959658}, + ComplexPrecisionT{-0.06025106319147798, -0.10646056582772014}, + ComplexPrecisionT{0.08404620644271446, 0.10988557291824569}, + ComplexPrecisionT{-0.24019669759696022, -0.32677139387437215}, + ComplexPrecisionT{-0.1818352969905923, -0.104919596974696}, + ComplexPrecisionT{0.05869841366621599, -0.041398191449316835}, + ComplexPrecisionT{0.145282212607238, -0.1924313363943447}, + ComplexPrecisionT{0.11060376483178569, -0.26643308349138073}, + ComplexPrecisionT{0.033457421541339696, 0.45989190483425835}, + ComplexPrecisionT{-0.11299501544002627, -0.1701839390369943}, + ComplexPrecisionT{-0.027076760012979297, 0.22500799348689948}, + ComplexPrecisionT{-0.12738473942173661, -0.2324834516745204}, + ComplexPrecisionT{-0.19309725929725693, 0.05397935052707938}, + ComplexPrecisionT{-0.1580199221099269, 0.02650400948671369}, + ComplexPrecisionT{-0.3243602236537279, -0.1222811623098372}, + ComplexPrecisionT{-0.3667586083634349, 0.009310446080170115}, + ComplexPrecisionT{0.319516408449643, -0.06325831841303045}, + ComplexPrecisionT{0.08903851358406503, 0.035815693787163774}, + ComplexPrecisionT{0.26343384552070426, -0.06458983496775506}, + ComplexPrecisionT{-0.015982429855605745, 0.021963169842707644}, + ComplexPrecisionT{0.1857171521950028, -0.18246521253046694}, + ComplexPrecisionT{-0.059754779213136586, 0.017676478816029573}, + ComplexPrecisionT{0.09776213545785536, -0.21762015291541528}, + ComplexPrecisionT{-0.1021998970995581, 0.17743735401036495}, + ComplexPrecisionT{0.204067980539901, 0.16726678221372981}, + ComplexPrecisionT{-0.0644818679085489, -0.1447270248131951}, + ComplexPrecisionT{0.39083699741955674, -0.03858837033831454}, + ComplexPrecisionT{0.0846216954671219, -0.12110810943095418}, + ComplexPrecisionT{-0.08992012094040822, -0.17734766719453254}, + ComplexPrecisionT{-0.04125741492613475, -0.1839167873488144}, + ComplexPrecisionT{-0.12059699651731241, 0.14869580875265737}, + ComplexPrecisionT{-0.2937373017282166, -0.22981405150756443}, + ComplexPrecisionT{0.08463640092878551, -0.11016426157266608}, + ComplexPrecisionT{0.01308530686553121, 0.3559206665166033}, + ComplexPrecisionT{-0.1331583159634414, -0.10956805576318897}, + ComplexPrecisionT{-0.1644759222332063, 0.09866893124404766}, + ComplexPrecisionT{-0.1900211227067346, -0.3658017080024861}, + ComplexPrecisionT{0.017273241356383206, 0.09372965786434638}, + ComplexPrecisionT{0.0773103659755795, 0.3489293973682007}, + ComplexPrecisionT{-0.08102734189288471, -0.33217362924578586}, + ComplexPrecisionT{-0.11464128747877839, -0.05875281079321294}, + ComplexPrecisionT{-0.014051241439616557, 0.1544645442809333}, + ComplexPrecisionT{-0.1123948350954748, -0.19432517027831833}, + ComplexPrecisionT{0.0330691662270549, -0.04919564002605478}, + ComplexPrecisionT{-0.1503779515321153, 0.31786458703670245}, + ComplexPrecisionT{0.20056990324454096, 0.1850109516828582}, + ComplexPrecisionT{0.2668695146420536, -0.43908623087339876}, + ComplexPrecisionT{0.011925951393575142, 0.17761991713542086}, + ComplexPrecisionT{0.005498164983530092, -0.002242630083480047}, + ComplexPrecisionT{0.17886013938198958, 0.10872382704642089}, + ComplexPrecisionT{0.08132470444007044, -0.09749221481303638}, + ComplexPrecisionT{-0.1737463651573049, 0.033299982055501054}, + ComplexPrecisionT{-0.18488118424023303, -0.3108837738476596}, + ComplexPrecisionT{-0.27113768160711277, 0.05256567395241565}, + ComplexPrecisionT{-0.19544234063361635, -0.01048324904429301}, + ComplexPrecisionT{0.0945949165859533, -0.2172498114363532}, + ComplexPrecisionT{-0.10985875532895437, 0.06386245713299038}, + ComplexPrecisionT{-0.025338698108748416, -0.23532608763027604}, + ComplexPrecisionT{-0.0916460338613978, 0.17111255669108807}, + ComplexPrecisionT{0.006942232587543371, 0.0449604620457603}, + ComplexPrecisionT{0.3550406307789137, -0.02112983773334885}, + ComplexPrecisionT{-0.03139393148937276, -0.12538378880991538}, + ComplexPrecisionT{-0.050516685188229764, 0.01574122670219121}, + ComplexPrecisionT{-0.1481888835160628, 0.13072587703855404}, + ComplexPrecisionT{-0.07986143820660621, 0.016012176121866912}, + ComplexPrecisionT{-0.32768187389890996, -0.015327242028981312}, + ComplexPrecisionT{0.2558846540681018, -0.3436385199742097}, + ComplexPrecisionT{-0.05851615752958035, -0.005701707401036289}, + ComplexPrecisionT{0.019780986059743003, -0.12316275013093546}, + ComplexPrecisionT{-0.09991905974897695, 0.14752267178535067}, + ComplexPrecisionT{0.1291642581984909, 0.023163549022428606}, + ComplexPrecisionT{-0.25784163164652146, 0.15076651333162944}, + ComplexPrecisionT{0.03232346409394317, -0.22680289177803026}, + ComplexPrecisionT{0.3358286037483417, -0.291011759563723}, + ComplexPrecisionT{-0.2598827120355048, 0.20093990553213004}, + ComplexPrecisionT{0.0182311108231829, -0.07832433364549145}, + ComplexPrecisionT{0.025583973820756032, -0.023411273206879912}, + ComplexPrecisionT{-0.1654037078192205, -0.16284081930646588}, + ComplexPrecisionT{0.0019996979156362543, -0.10309761662934443}, + ComplexPrecisionT{0.16916605119355627, 0.18306162041718788}, + ComplexPrecisionT{-0.2559127189443478, 0.09222694309162274}, + ComplexPrecisionT{-0.09919028489941913, 0.18613169533463675}, + ComplexPrecisionT{0.07508213832551346, 0.41694487884583353}, + ComplexPrecisionT{0.023972824357252787, -0.06599789836959391}, + ComplexPrecisionT{0.016773306946815114, -0.03939241313465466}, + ComplexPrecisionT{-0.08664317188243345, 0.12563189408611994}, + ComplexPrecisionT{0.21026595312801138, 0.08176642742039242}, + ComplexPrecisionT{0.0093028207667191, -0.06279692109628966}, + ComplexPrecisionT{0.2203850153337698, 0.020898065538940646}, + ComplexPrecisionT{-0.20583392564574995, -0.43652451614186233}, + ComplexPrecisionT{-0.08855804365999222, -0.4392951412878797}, + ComplexPrecisionT{0.07533134195667648, 0.4606082840651167}, + ComplexPrecisionT{0.052524607577778604, -0.023110444690372434}, + ComplexPrecisionT{-0.30000883176081444, -0.20888279683044061}, + ComplexPrecisionT{0.07247961742346525, -0.2320002192036349}, + ComplexPrecisionT{-0.053018678053434604, 0.01691219103432881}, + ComplexPrecisionT{-0.2399217664380185, 0.16673537527457852}, + ComplexPrecisionT{0.0916908309563268, -0.04065242875621084}, + ComplexPrecisionT{0.13288223871943822, -0.1449122646529056}, + ComplexPrecisionT{-0.1440177353834986, 0.20921513414099863}, + ComplexPrecisionT{-0.019495585563170055, -0.22503492052552065}, + ComplexPrecisionT{0.17343212478867365, -0.1494555146156185}, + ComplexPrecisionT{-0.12018617624189008, 0.056351189991110794}, + ComplexPrecisionT{-0.0016827409660573889, 0.25808153364327724}, + ComplexPrecisionT{-0.04741488633872766, 0.36934530803669297}, + ComplexPrecisionT{-0.002345825594843698, 0.16427813741863367}, + ComplexPrecisionT{0.023944547993878525, -0.17484976236216634}, + }; + + std::vector expected{ + ComplexPrecisionT{0.264749262466, -0.016918881870}, + ComplexPrecisionT{0.201841741500, 0.074598631106}, + ComplexPrecisionT{0.260163664155, -0.025732021493}, + ComplexPrecisionT{-0.321148080521, -0.224394633392}, + ComplexPrecisionT{0.057117880510, -0.098204010637}, + ComplexPrecisionT{-0.199622088528, -0.357183553512}, + ComplexPrecisionT{0.142361283082, 0.304993936113}, + ComplexPrecisionT{-0.167235905884, -0.079450883404}, + ComplexPrecisionT{0.223735534585, 0.156113785239}, + ComplexPrecisionT{-0.039724326748, 0.074784370874}, + ComplexPrecisionT{0.128903821272, -0.052607394218}, + ComplexPrecisionT{-0.100102973432, -0.369701144889}, + ComplexPrecisionT{-0.076618826943, -0.113689447069}, + ComplexPrecisionT{0.137136222122, -0.081190249787}, + ComplexPrecisionT{-0.054059628740, -0.174640023638}, + ComplexPrecisionT{-0.073454475362, -0.053685843736}, + }; + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} + +template +void testApplyMatrixForKernels() { + if constexpr (!std::is_same_v) { + using GateImplementation = typename TypeList::Type; + + if constexpr (IsApplyMatrixDefined::value) { + testApplyMatrix(); + } else { + SUCCEED("Member function applyMatrix is not defined in kernel" + << GateImplementation::name); + } + testApplyMatrixForKernels(); + } +} + +TEMPLATE_TEST_CASE("GateImplementation::applyMatrix, inverse = false", + "[GateImplementations_Matrix]", float, double) { + using PrecisionT = TestType; + + testApplyMatrixForKernels(); +} + +template +void testApplyMatrixInverse() { + using ComplexPrecisionT = std::complex; + + std::mt19937 re{1337}; + const int num_qubits = 4; + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {0} - " + << PrecisionToName::value) { + const std::vector wires{0}; + + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {1} - " + << PrecisionToName::value) { + const std::vector wires{1}; + + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {2} - " + << PrecisionToName::value) { + const std::vector wires{2}; + + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {3} - " + << PrecisionToName::value) { + const std::vector wires{3}; + + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {0,1} - " + << PrecisionToName::value) { + const std::vector wires{0, 1}; + + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {1,2} - " + << PrecisionToName::value) { + const std::vector wires{1, 2}; + + const auto ini_st = createRandomState(re, num_qubits); + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {1,3} - " + << PrecisionToName::value) { + const std::vector wires{1, 3}; + + const auto ini_st = createRandomState(re, num_qubits); + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {1,2,3} - " + << PrecisionToName::value) { + const std::vector wires{1, 2, 3}; + + const auto ini_st = createRandomState(re, num_qubits); + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", wires = {0,1,2,3} - " + << PrecisionToName::value) { + const std::vector wires{0, 1, 2, 3}; + const auto ini_st = createRandomState(re, num_qubits); + + const auto matrix = randomUnitary(re, wires.size()); + + auto st = ini_st; + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, false); + GateImplementation::applyMatrix(st.data(), num_qubits, matrix.data(), + wires, true); + + REQUIRE(st == PLApprox(ini_st).margin(1e-5)); + } +} + +template +void testApplyMatrixInverseForKernels() { + if constexpr (!std::is_same_v) { + using GateImplementation = typename TypeList::Type; + if constexpr (IsApplyMatrixDefined::value) { + testApplyMatrixInverse(); + } else { + SUCCEED("Member function applyMatrix is not defined in kernel" + << GateImplementation::name); + } + testApplyMatrixInverseForKernels(); + } +} + +TEMPLATE_TEST_CASE("GateImplementation::applyMatrix, inverse = true", + "[GateImplementations_Matrix]", float, double) { + using PrecisionT = TestType; + + testApplyMatrixInverseForKernels(); +} diff --git a/pennylane_lightning/src/tests/Test_GateImplementations_Nonparam.cpp b/pennylane_lightning/src/tests/Test_GateImplementations_Nonparam.cpp new file mode 100644 index 0000000000..e2ca59f791 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_GateImplementations_Nonparam.cpp @@ -0,0 +1,597 @@ +#include "TestHelpers.hpp" +#include "TestKernels.hpp" +#include "Util.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * @file Test_GateImplementations_Nonparam.cpp + * + * This file contains tests for non-parameterized gates. List of such + * gates are [PauliX, PauliY, PauliZ, Hadamard, S, T, CNOT, SWAP, CZ, Toffoli, + * CSWAP]. + */ +using namespace Pennylane; + +namespace { +using std::vector; +} + +/** + * @brief Run test suit only when the gate is defined + */ +#define PENNYLANE_RUN_TEST(GATE_NAME) \ + template \ + struct Apply##GATE_NAME##IsDefined { \ + constexpr static bool value = false; \ + }; \ + template \ + struct Apply##GATE_NAME##IsDefined< \ + PrecisionT, GateImplementation, \ + std::enable_if_t)>>> { \ + constexpr static bool value = true; \ + }; \ + template \ + void testApply##GATE_NAME##ForKernels() { \ + if constexpr (!std::is_same_v) { \ + using GateImplementation = typename TypeList::Type; \ + if constexpr (Apply##GATE_NAME##IsDefined< \ + PrecisionT, GateImplementation>::value) { \ + testApply##GATE_NAME(); \ + } else { \ + SUCCEED("Member function apply" #GATE_NAME \ + " is not defined for kernel " \ + << GateImplementation::name); \ + } \ + testApply##GATE_NAME##ForKernels(); \ + } \ + } \ + TEMPLATE_TEST_CASE("GateImplementation::apply" #GATE_NAME, \ + "[GateImplementations_Nonparam]", float, double) { \ + using PrecisionT = TestType; \ + testApply##GATE_NAME##ForKernels(); \ + } \ + static_assert(true, "Require semicolon") + +/******************************************************************************* + * Single-qubit gates + ******************************************************************************/ +template +void testApplyPauliX() { + const size_t num_qubits = 3; + for (size_t index = 0; index < num_qubits; index++) { + auto st = createZeroState(num_qubits); + CHECK(st[0] == Util::ONE()); + + GateImplementation::applyPauliX(st.data(), num_qubits, {index}, false); + CHECK(st[0] == Util::ZERO()); + CHECK(st[0b1 << (num_qubits - index - 1)] == Util::ONE()); + } +} +PENNYLANE_RUN_TEST(PauliX); + +template +void testApplyPauliY() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + constexpr ComplexPrecisionT p = + Util::ConstMult(static_cast(0.5), + Util::ConstMult(Util::INVSQRT2(), + Util::IMAG())); + constexpr ComplexPrecisionT m = Util::ConstMult(-1, p); + + const std::vector> expected_results = { + {m, m, m, m, p, p, p, p}, + {m, m, p, p, m, m, p, p}, + {m, p, m, p, m, p, m, p}}; + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + + GateImplementation::applyPauliY(st.data(), num_qubits, {index}, false); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(PauliY); + +template +void testApplyPauliZ() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + constexpr ComplexPrecisionT p(static_cast(0.5) * + Util::INVSQRT2()); + constexpr ComplexPrecisionT m(Util::ConstMult(-1, p)); + + const std::vector> expected_results = { + {p, p, p, p, m, m, m, m}, + {p, p, m, m, p, p, m, m}, + {p, m, p, m, p, m, p, m}}; + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + GateImplementation::applyPauliZ(st.data(), num_qubits, {index}, false); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(PauliZ); + +template +void testApplyHadamard() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + for (size_t index = 0; index < num_qubits; index++) { + auto st = createZeroState(num_qubits); + + CHECK(st[0] == ComplexPrecisionT{1, 0}); + GateImplementation::applyHadamard(st.data(), num_qubits, {index}, + false); + + ComplexPrecisionT expected(1 / std::sqrt(2), 0); + CHECK(expected.real() == Approx(st[0].real())); + CHECK(expected.imag() == Approx(st[0].imag())); + + CHECK(expected.real() == + Approx(st[0b1 << (num_qubits - index - 1)].real())); + CHECK(expected.imag() == + Approx(st[0b1 << (num_qubits - index - 1)].imag())); + } +} +PENNYLANE_RUN_TEST(Hadamard); + +template void testApplyS() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + constexpr ComplexPrecisionT r(static_cast(0.5) * + Util::INVSQRT2()); + constexpr ComplexPrecisionT i(Util::ConstMult(r, Util::IMAG())); + + const std::vector> expected_results = { + {r, r, r, r, i, i, i, i}, + {r, r, i, i, r, r, i, i}, + {r, i, r, i, r, i, r, i}}; + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + + GateImplementation::applyS(st.data(), num_qubits, {index}, false); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(S); + +template void testApplyT() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + // Test using |+++> state + + ComplexPrecisionT r(1.0 / (2.0 * std::sqrt(2)), 0); + ComplexPrecisionT i(1.0 / 4, 1.0 / 4); + + const std::vector> expected_results = { + {r, r, r, r, i, i, i, i}, + {r, r, i, i, r, r, i, i}, + {r, i, r, i, r, i, r, i}}; + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + + GateImplementation::applyT(st.data(), num_qubits, {index}, false); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(T); +/******************************************************************************* + * Two-qubit gates + ******************************************************************************/ + +template void testApplyCNOT() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + auto st = createZeroState(num_qubits); + + // Test using |+00> state to generate 3-qubit GHZ state + GateImplementation::applyHadamard(st.data(), num_qubits, {0}, false); + + for (size_t index = 1; index < num_qubits; index++) { + GateImplementation::applyCNOT(st.data(), num_qubits, {index - 1, index}, + false); + } + CHECK(st.front() == Util::INVSQRT2()); + CHECK(st.back() == Util::INVSQRT2()); +} +PENNYLANE_RUN_TEST(CNOT); + +// NOLINTNEXTLINE: Avoiding complexity errors +template void testApplyCY() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + auto ini_st = createZeroState(num_qubits); + + // Test using |+10> state + GateImplementation::applyHadamard(ini_st.data(), num_qubits, {0}, false); + GateImplementation::applyPauliX(ini_st.data(), num_qubits, {1}, false); + + CHECK(ini_st == std::vector{ + Util::ZERO(), Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}); + + DYNAMIC_SECTION(GateImplementation::name + << ", CY 0,1 |+10> -> i|100> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(0, -1 / sqrt(2)), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + auto sv01 = ini_st; + GateImplementation::applyCY(sv01.data(), num_qubits, {0, 1}, false); + CHECK(sv01 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CY 0,2 |+10> -> |010> + i |111> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0.0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(0.0, 1 / sqrt(2))}; + + auto sv02 = ini_st; + + GateImplementation::applyCY(sv02.data(), num_qubits, {0, 2}, false); + CHECK(sv02 == expected); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CY 1,2 |+10> -> i|+11> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(0.0, 1.0 / sqrt(2)), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(0.0, 1 / sqrt(2))}; + + auto sv12 = ini_st; + + GateImplementation::applyCY(sv12.data(), num_qubits, {1, 2}, false); + CHECK(sv12 == expected); + } +} +PENNYLANE_RUN_TEST(CY); + +// NOLINTNEXTLINE: Avoiding complexity errors +template void testApplyCZ() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + auto ini_st = createZeroState(num_qubits); + + // Test using |+10> state + GateImplementation::applyHadamard(ini_st.data(), num_qubits, {0}, false); + GateImplementation::applyPauliX(ini_st.data(), num_qubits, {1}, false); + + auto st = ini_st; + CHECK(st == std::vector{ + Util::ZERO(), Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}); + + DYNAMIC_SECTION(GateImplementation::name + << ", CZ0,1 |+10> -> |-10> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(-1 / sqrt(2), 0), + Util::ZERO()}; + + auto sv01 = ini_st; + auto sv10 = ini_st; + + GateImplementation::applyCZ(sv01.data(), num_qubits, {0, 1}, false); + GateImplementation::applyCZ(sv10.data(), num_qubits, {1, 0}, false); + + CHECK(sv01 == expected); + CHECK(sv10 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CZ0,2 |+10> -> |+10> - " + << PrecisionToName::value) { + const std::vector &expected{ini_st}; + + auto sv02 = ini_st; + auto sv20 = ini_st; + + GateImplementation::applyCZ(sv02.data(), num_qubits, {0, 2}, false); + GateImplementation::applyCZ(sv20.data(), num_qubits, {2, 0}, false); + + CHECK(sv02 == expected); + CHECK(sv20 == expected); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CZ1,2 |+10> -> |+10> - " + << PrecisionToName::value) { + const std::vector &expected{ini_st}; + + auto sv12 = ini_st; + auto sv21 = ini_st; + + GateImplementation::applyCZ(sv12.data(), num_qubits, {1, 2}, false); + GateImplementation::applyCZ(sv21.data(), num_qubits, {2, 1}, false); + + CHECK(sv12 == expected); + CHECK(sv21 == expected); + } +} +PENNYLANE_RUN_TEST(CZ); + +// NOLINTNEXTLINE: Avoiding complexity errors +template void testApplySWAP() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + auto ini_st = createZeroState(num_qubits); + + // Test using |+10> state + GateImplementation::applyHadamard(ini_st.data(), num_qubits, {0}, false); + GateImplementation::applyPauliX(ini_st.data(), num_qubits, {1}, false); + + CHECK(ini_st == std::vector{ + Util::ZERO(), Util::ZERO(), + Util::INVSQRT2(), Util::ZERO(), + Util::ZERO(), Util::ZERO(), + Util::INVSQRT2(), + Util::ZERO()}); + + DYNAMIC_SECTION(GateImplementation::name + << ", SWAP0,1 |+10> -> |1+0> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}; + auto sv01 = ini_st; + auto sv10 = ini_st; + + GateImplementation::applySWAP(sv01.data(), num_qubits, {0, 1}, false); + GateImplementation::applySWAP(sv10.data(), num_qubits, {1, 0}, false); + + CHECK(sv01 == expected); + CHECK(sv10 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", SWAP0,2 |+10> -> |01+> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + auto sv02 = ini_st; + auto sv20 = ini_st; + + GateImplementation::applySWAP(sv02.data(), num_qubits, {0, 2}, false); + GateImplementation::applySWAP(sv20.data(), num_qubits, {2, 0}, false); + + CHECK(sv02 == expected); + CHECK(sv20 == expected); + } + DYNAMIC_SECTION(GateImplementation::name + << ", SWAP1,2 |+10> -> |+01> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + + auto sv12 = ini_st; + auto sv21 = ini_st; + + GateImplementation::applySWAP(sv12.data(), num_qubits, {1, 2}, false); + GateImplementation::applySWAP(sv21.data(), num_qubits, {2, 1}, false); + + CHECK(sv12 == expected); + CHECK(sv21 == expected); + } +} +PENNYLANE_RUN_TEST(SWAP); + +/******************************************************************************* + * Three-qubit gates + ******************************************************************************/ +template +void testApplyToffoli() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + auto ini_st = createZeroState(num_qubits); + + // Test using |+10> state + GateImplementation::applyHadamard(ini_st.data(), num_qubits, {0}, false); + GateImplementation::applyPauliX(ini_st.data(), num_qubits, {1}, false); + + DYNAMIC_SECTION(GateImplementation::name + << ", Toffoli 0,1,2 |+10> -> |010> + |111> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; + + auto sv012 = ini_st; + + GateImplementation::applyToffoli(sv012.data(), num_qubits, {0, 1, 2}, + false); + + CHECK(sv012 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Toffoli 1,0,2 |+10> -> |010> + |111> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; + + auto sv102 = ini_st; + + GateImplementation::applyToffoli(sv102.data(), num_qubits, {1, 0, 2}, + false); + + CHECK(sv102 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Toffoli 0,2,1 |+10> -> |+10> - " + << PrecisionToName::value) { + const auto &expected = ini_st; + + auto sv021 = ini_st; + + GateImplementation::applyToffoli(sv021.data(), num_qubits, {0, 2, 1}, + false); + + CHECK(sv021 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", Toffoli 1,2,0 |+10> -> |+10> - " + << PrecisionToName::value) { + const auto &expected = ini_st; + + auto sv120 = ini_st; + GateImplementation::applyToffoli(sv120.data(), num_qubits, {1, 2, 0}, + false); + CHECK(sv120 == expected); + } +} +PENNYLANE_RUN_TEST(Toffoli); + +template void testApplyCSWAP() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + auto ini_st = createZeroState(num_qubits); + + // Test using |+10> state + GateImplementation::applyHadamard(ini_st.data(), num_qubits, {0}, false); + GateImplementation::applyPauliX(ini_st.data(), num_qubits, {1}, false); + + DYNAMIC_SECTION(GateImplementation::name + << ", CSWAP 0,1,2 |+10> -> |010> + |101> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + + auto sv012 = ini_st; + GateImplementation::applyCSWAP(sv012.data(), num_qubits, {0, 1, 2}, + false); + CHECK(sv012 == expected); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CSWAP 1,0,2 |+10> -> |01+> - " + << PrecisionToName::value) { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + auto sv102 = ini_st; + GateImplementation::applyCSWAP(sv102.data(), num_qubits, {1, 0, 2}, + false); + CHECK(sv102 == expected); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CSWAP 2,1,0 |+10> -> |+10> - " + << PrecisionToName::value) { + const auto &expected = ini_st; + + auto sv210 = ini_st; + GateImplementation::applyCSWAP(sv210.data(), num_qubits, {2, 1, 0}, + false); + CHECK(sv210 == expected); + } +} +PENNYLANE_RUN_TEST(CSWAP); diff --git a/pennylane_lightning/src/tests/Test_GateImplementations_Param.cpp b/pennylane_lightning/src/tests/Test_GateImplementations_Param.cpp new file mode 100644 index 0000000000..cc6f687e11 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_GateImplementations_Param.cpp @@ -0,0 +1,1480 @@ +#include "TestHelpers.hpp" +#include "TestKernels.hpp" +#include "Util.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * @file This file contains tests for parameterized gates. List of such gates is + * [RX, RY, RZ, PhaseShift, Rot, ControlledPhaseShift, CRX, CRY, CRZ, CRot] + */ + +using namespace Pennylane; +/** + * @brief Run test suit only when the gate is defined + */ +#define PENNYLANE_RUN_TEST(GATE_NAME) \ + template \ + struct Apply##GATE_NAME##IsDefined { \ + constexpr static bool value = false; \ + }; \ + template \ + struct Apply##GATE_NAME##IsDefined< \ + PrecisionT, ParamT, GateImplementation, \ + std::enable_if_t)>>> { \ + constexpr static bool value = true; \ + }; \ + template \ + void testApply##GATE_NAME##ForKernels() { \ + if constexpr (!std::is_same_v) { \ + using GateImplementation = typename TypeList::Type; \ + if constexpr (Apply##GATE_NAME##IsDefined< \ + PrecisionT, ParamT, \ + GateImplementation>::value) { \ + testApply##GATE_NAME(); \ + } else { \ + SUCCEED("Member function apply" #GATE_NAME \ + " is not defined for kernel " \ + << GateImplementation::name); \ + } \ + testApply##GATE_NAME##ForKernels(); \ + } \ + } \ + TEMPLATE_TEST_CASE("GateImplementation::apply" #GATE_NAME, \ + "[GateImplementations_Param]", float, double) { \ + using PrecisionT = TestType; \ + using ParamT = TestType; \ + testApply##GATE_NAME##ForKernels(); \ + } \ + static_assert(true, "Require semicolon") + +/******************************************************************************* + * Single-qubit gates + ******************************************************************************/ + +template +void testApplyPhaseShift() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + // Test using |+++> state + + const std::vector angles{0.3, 0.8, 2.4}; + const ComplexPrecisionT coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> ps_data; + ps_data.reserve(angles.size()); + for (auto &a : angles) { + ps_data.push_back(Gates::getPhaseShift(a)); + } + + std::vector> expected_results = { + {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], + ps_data[0][3], ps_data[0][3], ps_data[0][3], ps_data[0][3]}, + { + ps_data[1][0], + ps_data[1][0], + ps_data[1][3], + ps_data[1][3], + ps_data[1][0], + ps_data[1][0], + ps_data[1][3], + ps_data[1][3], + }, + {ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3], + ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + + GateImplementation::applyPhaseShift(st.data(), num_qubits, {index}, + false, {angles[index]}); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(PhaseShift); + +template +void testApplyRX() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 1; + + const std::vector angles{{0.1}, {0.6}}; + std::vector> expected_results{ + std::vector{{0.9987502603949663, 0.0}, + {0.0, -0.04997916927067834}}, + std::vector{{0.9553364891256061, 0.0}, + {0, -0.2955202066613395}}, + std::vector{{0.49757104789172696, 0.0}, + {0, -0.867423225594017}}}; + + for (size_t index = 0; index < angles.size(); index++) { + auto st = createZeroState(num_qubits); + + GateImplementation::applyRX(st.data(), num_qubits, {0}, false, + {angles[index]}); + + CHECK(st == PLApprox(expected_results[index]).epsilon(1e-7)); + } +} +PENNYLANE_RUN_TEST(RX); + +template +void testApplyRY() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 1; + + const std::vector angles{0.2, 0.7, 2.9}; + std::vector> expected_results{ + std::vector{ + {0.8731983044562817, 0.04786268954660339}, + {0.0876120655431924, -0.47703040785184303}}, + std::vector{ + {0.8243771119105122, 0.16439396602553008}, + {0.3009211363333468, -0.45035926880694604}}, + std::vector{ + {0.10575112905629831, 0.47593196040758534}, + {0.8711876098966215, -0.0577721051072477}}}; + std::vector> expected_results_adj{ + std::vector{ + {0.8731983044562817, -0.04786268954660339}, + {-0.0876120655431924, -0.47703040785184303}}, + std::vector{ + {0.8243771119105122, -0.16439396602553008}, + {-0.3009211363333468, -0.45035926880694604}}, + std::vector{ + {0.10575112905629831, -0.47593196040758534}, + {-0.8711876098966215, -0.0577721051072477}}}; + + const std::vector init_state{ + {0.8775825618903728, 0.0}, {0.0, -0.47942553860420306}}; + DYNAMIC_SECTION(GateImplementation::name + << ", RY - " << PrecisionToName::value) { + for (size_t index = 0; index < angles.size(); index++) { + auto st = init_state; + GateImplementation::applyRY(st.data(), num_qubits, {0}, false, + {angles[index]}); + CHECK(st == PLApprox(expected_results[index]).epsilon(1e-5)); + } + } +} +PENNYLANE_RUN_TEST(RY); + +template +void testApplyRZ() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + // Test using |+++> state + + const std::vector angles{0.2, 0.7, 2.9}; + const ComplexPrecisionT coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> rz_data; + rz_data.reserve(angles.size()); + for (auto &a : angles) { + rz_data.push_back(Gates::getRZ(a)); + } + + std::vector> expected_results = { + {rz_data[0][0], rz_data[0][0], rz_data[0][0], rz_data[0][0], + rz_data[0][3], rz_data[0][3], rz_data[0][3], rz_data[0][3]}, + { + rz_data[1][0], + rz_data[1][0], + rz_data[1][3], + rz_data[1][3], + rz_data[1][0], + rz_data[1][0], + rz_data[1][3], + rz_data[1][3], + }, + {rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3], + rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createPlusState(num_qubits); + + GateImplementation::applyRZ(st.data(), num_qubits, {index}, false, + {angles[index]}); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(RZ); + +template +void testApplyRot() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + auto ini_st = createZeroState(num_qubits); + + const std::vector> angles{ + std::vector{0.3, 0.8, 2.4}, + std::vector{0.5, 1.1, 3.0}, + std::vector{2.3, 0.1, 0.4}}; + + std::vector> expected_results{ + std::vector(0b1 << num_qubits), + std::vector(0b1 << num_qubits), + std::vector(0b1 << num_qubits)}; + + for (size_t i = 0; i < angles.size(); i++) { + const auto rot_mat = + Gates::getRot(angles[i][0], angles[i][1], angles[i][2]); + expected_results[i][0] = rot_mat[0]; + expected_results[i][0b1 << (num_qubits - i - 1)] = rot_mat[2]; + } + + for (size_t index = 0; index < num_qubits; index++) { + auto st = createZeroState(num_qubits); + GateImplementation::applyRot(st.data(), num_qubits, {index}, false, + angles[index][0], angles[index][1], + angles[index][2]); + + CHECK(st == PLApprox(expected_results[index])); + } +} +PENNYLANE_RUN_TEST(Rot); + +/******************************************************************************* + * Two-qubit gates + ******************************************************************************/ +template +void testApplyIsingXX() { + using ComplexPrecisionT = std::complex; + using std::cos; + using std::sin; + + DYNAMIC_SECTION(GateImplementation::name + << ", IsingXX0,1 |000> -> a|000> + b|110> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createZeroState(num_qubits); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingXX(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingXX0,1 |100> -> a|100> + b|010> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("100"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingXX(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingXX0,1 |010> -> a|010> + b|100> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("010"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingXX(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingXX0,1 |110> -> a|110> + b|000> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("110"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingXX(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingXX0,2 - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + std::vector ini_st{ + ComplexPrecisionT{0.125681356503, 0.252712197380}, + ComplexPrecisionT{0.262591068130, 0.370189000494}, + ComplexPrecisionT{0.129300299863, 0.371057794075}, + ComplexPrecisionT{0.392248682814, 0.195795523118}, + ComplexPrecisionT{0.303908059240, 0.082981563244}, + ComplexPrecisionT{0.189140284321, 0.179512645957}, + ComplexPrecisionT{0.173146612336, 0.092249594834}, + ComplexPrecisionT{0.298857179897, 0.269627836165}, + }; + const std::vector wires = {0, 2}; + const ParamT angle = 0.267030328057308; + std::vector expected{ + ComplexPrecisionT{0.148459317603, 0.225284945157}, + ComplexPrecisionT{0.271300438716, 0.326438461763}, + ComplexPrecisionT{0.164042082006, 0.327971890339}, + ComplexPrecisionT{0.401037861022, 0.171003883572}, + ComplexPrecisionT{0.350482432141, 0.047287216587}, + ComplexPrecisionT{0.221097705423, 0.161184442326}, + ComplexPrecisionT{0.197669694288, 0.039212892562}, + ComplexPrecisionT{0.345592157995, 0.250015865318}, + }; + + auto st = ini_st; + GateImplementation::applyIsingXX(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(IsingXX); + +template +void testApplyIsingYY() { + using ComplexPrecisionT = std::complex; + using std::cos; + using std::sin; + + DYNAMIC_SECTION(GateImplementation::name + << ", IsingYY0,1 |000> -> a|000> + b|110> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createZeroState(num_qubits); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingYY(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingYY0,1 |100> -> a|100> + b|010> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("100"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingYY(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingYY0,1 |010> -> a|010> + b|100> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("010"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingYY(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingYY0,1 |110> -> a|110> + b|000> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("110"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingYY(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingYY0,1 - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.276522701942, 0.192601873155}, + ComplexPrecisionT{0.035951282872, 0.224882549474}, + ComplexPrecisionT{0.142578003191, 0.016769549184}, + ComplexPrecisionT{0.207510965432, 0.068085008177}, + ComplexPrecisionT{0.231177902264, 0.039974505646}, + ComplexPrecisionT{0.038587049391, 0.058503643276}, + ComplexPrecisionT{0.023121176451, 0.294843178966}, + ComplexPrecisionT{0.297936734810, 0.061981734524}, + ComplexPrecisionT{0.140961289031, 0.061129422308}, + ComplexPrecisionT{0.204531438234, 0.159178277448}, + ComplexPrecisionT{0.143828437747, 0.031972463787}, + ComplexPrecisionT{0.291528706380, 0.138875986482}, + ComplexPrecisionT{0.297088897520, 0.179914971203}, + ComplexPrecisionT{0.032991360504, 0.024025500927}, + ComplexPrecisionT{0.121553926676, 0.263606060346}, + ComplexPrecisionT{0.177173454285, 0.267447421480}, + }; + + const std::vector wires = {0, 1}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.245211756573, 0.236421160261}, + ComplexPrecisionT{0.031781919269, 0.227277526275}, + ComplexPrecisionT{0.099890674345, 0.035451505339}, + ComplexPrecisionT{0.163438308608, 0.094785319724}, + ComplexPrecisionT{0.237868187763, 0.017588203228}, + ComplexPrecisionT{0.062849689541, 0.026015566111}, + ComplexPrecisionT{0.027807906892, 0.268916455494}, + ComplexPrecisionT{0.315895675672, 0.015934827233}, + ComplexPrecisionT{0.145460308037, 0.024469450691}, + ComplexPrecisionT{0.211137338769, 0.151250126997}, + ComplexPrecisionT{0.187891084547, 0.027991919467}, + ComplexPrecisionT{0.297618553419, 0.090899723116}, + ComplexPrecisionT{0.263557070771, 0.220692990352}, + ComplexPrecisionT{-0.002348824386, 0.029319431141}, + ComplexPrecisionT{0.117472403735, 0.282557065430}, + ComplexPrecisionT{0.164443742376, 0.296440286247}, + }; + + auto st = ini_st; + GateImplementation::applyIsingYY(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(IsingYY); + +template +void testApplyIsingZZ() { + using ComplexPrecisionT = std::complex; + using std::cos; + using std::sin; + + DYNAMIC_SECTION(GateImplementation::name + << ", IsingZZ0,1 |000> -> |000> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createZeroState(num_qubits); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{cos(angle / 2), -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingZZ(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingZZ0,1 |100> -> |100> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("100"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingZZ(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", IsingZZ0,1 |010> -> |010> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("010"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingZZ(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", IsingZZ0,1 |110> -> |110> - " + << PrecisionToName::value) { + const size_t num_qubits = 3; + const auto ini_st = createProductState("110"); + ParamT angle = 0.312; + + const std::vector expected_results{ + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{0.0, 0.0}, + ComplexPrecisionT{cos(angle / 2), -sin(angle / 2)}, + ComplexPrecisionT{0.0, 0.0}, + }; + + auto st = ini_st; + GateImplementation::applyIsingZZ(st.data(), num_qubits, {0, 1}, false, + angle); + REQUIRE(st == PLApprox(expected_results).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", IsingZZ0,1 - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.267462841882, 0.010768564798}, + ComplexPrecisionT{0.228575129706, 0.010564590956}, + ComplexPrecisionT{0.099492749900, 0.260849823392}, + ComplexPrecisionT{0.093690204310, 0.189847108173}, + ComplexPrecisionT{0.033390732374, 0.203836830144}, + ComplexPrecisionT{0.226979395737, 0.081852150975}, + ComplexPrecisionT{0.031235505729, 0.176933497281}, + ComplexPrecisionT{0.294287602843, 0.145156781198}, + ComplexPrecisionT{0.152742706049, 0.111628061129}, + ComplexPrecisionT{0.012553863703, 0.120027860480}, + ComplexPrecisionT{0.237156555364, 0.154658769755}, + ComplexPrecisionT{0.117001120872, 0.228059505033}, + ComplexPrecisionT{0.041495873225, 0.065934827444}, + ComplexPrecisionT{0.089653239407, 0.221581340372}, + ComplexPrecisionT{0.217892322429, 0.291261296999}, + ComplexPrecisionT{0.292993251871, 0.186570798697}, + }; + + const std::vector wires = {0, 1}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.265888039508, -0.030917377350}, + ComplexPrecisionT{0.227440863156, -0.025076966901}, + ComplexPrecisionT{0.138812299373, 0.242224241539}, + ComplexPrecisionT{0.122048663851, 0.172985266764}, + ComplexPrecisionT{0.001315529800, 0.206549421962}, + ComplexPrecisionT{0.211505899280, 0.116123534558}, + ComplexPrecisionT{0.003366392733, 0.179637932181}, + ComplexPrecisionT{0.268161243812, 0.189116978698}, + ComplexPrecisionT{0.133544466595, 0.134003857126}, + ComplexPrecisionT{-0.006247074818, 0.120520790080}, + ComplexPrecisionT{0.210247652980, 0.189627242850}, + ComplexPrecisionT{0.080147179284, 0.243468334233}, + ComplexPrecisionT{0.051236139067, 0.058687025978}, + ComplexPrecisionT{0.122991206449, 0.204961354585}, + ComplexPrecisionT{0.260499076094, 0.253870909435}, + ComplexPrecisionT{0.318422472324, 0.138783420076}, + }; + + auto st = ini_st; + GateImplementation::applyIsingZZ(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(IsingZZ); + +template +void testApplyControlledPhaseShift() { + using ComplexPrecisionT = std::complex; + + const size_t num_qubits = 3; + + // Test using |+++> state + auto ini_st = createPlusState(num_qubits); + + const std::vector angles{0.3, 2.4}; + const ComplexPrecisionT coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> ps_data; + ps_data.reserve(angles.size()); + for (auto &a : angles) { + ps_data.push_back(Gates::getPhaseShift(a)); + } + + std::vector> expected_results = { + {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], + ps_data[0][0], ps_data[0][0], ps_data[0][3], ps_data[0][3]}, + {ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3], + ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + auto st = ini_st; + + GateImplementation::applyControlledPhaseShift(st.data(), num_qubits, {0, 1}, + false, angles[0]); + CAPTURE(st); + CHECK(st == PLApprox(expected_results[0])); +} +PENNYLANE_RUN_TEST(ControlledPhaseShift); + +template +void testApplyCRX() { + using ComplexPrecisionT = std::complex; + DYNAMIC_SECTION(GateImplementation::name + << ", CRX0,1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.188018120185, 0.267344585187}, + ComplexPrecisionT{0.172684792903, 0.187465336044}, + ComplexPrecisionT{0.218892658302, 0.241508557821}, + ComplexPrecisionT{0.107094509452, 0.233123916768}, + ComplexPrecisionT{0.144398681914, 0.102112687699}, + ComplexPrecisionT{0.266641428689, 0.096286886834}, + ComplexPrecisionT{0.037126289559, 0.047222166486}, + ComplexPrecisionT{0.136865047634, 0.203178369592}, + ComplexPrecisionT{0.001562711889, 0.224933454573}, + ComplexPrecisionT{0.009933412610, 0.080866505038}, + ComplexPrecisionT{0.000948295069, 0.280652963863}, + ComplexPrecisionT{0.109817299553, 0.150776413412}, + ComplexPrecisionT{0.297480913626, 0.232588348025}, + ComplexPrecisionT{0.247386444054, 0.077608200535}, + ComplexPrecisionT{0.192650977126, 0.054764192471}, + ComplexPrecisionT{0.033093927690, 0.243038790593}, + }; + + const std::vector wires = {0, 1}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.188018120185, 0.267344585187}, + ComplexPrecisionT{0.172684792903, 0.187465336044}, + ComplexPrecisionT{0.218892658302, 0.241508557821}, + ComplexPrecisionT{0.107094509452, 0.233123916768}, + ComplexPrecisionT{0.144398681914, 0.102112687699}, + ComplexPrecisionT{0.266641428689, 0.096286886834}, + ComplexPrecisionT{0.037126289559, 0.047222166486}, + ComplexPrecisionT{0.136865047634, 0.203178369592}, + ComplexPrecisionT{0.037680529583, 0.175982985869}, + ComplexPrecisionT{0.021870621269, 0.041448569986}, + ComplexPrecisionT{0.009445384485, 0.247313095111}, + ComplexPrecisionT{0.146244209335, 0.143803745197}, + ComplexPrecisionT{0.328815969263, 0.229521152393}, + ComplexPrecisionT{0.256946415396, 0.075122442730}, + ComplexPrecisionT{0.233916049255, 0.053951837341}, + ComplexPrecisionT{0.056117891609, 0.223025389250}, + }; + + auto st = ini_st; + GateImplementation::applyCRX(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CRX0,2 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.052996853820, 0.268704529517}, + ComplexPrecisionT{0.082642978242, 0.195193762273}, + ComplexPrecisionT{0.275869474800, 0.221416497403}, + ComplexPrecisionT{0.198695648566, 0.006071386515}, + ComplexPrecisionT{0.067983147697, 0.276232498024}, + ComplexPrecisionT{0.136067312263, 0.055703741794}, + ComplexPrecisionT{0.157173013237, 0.279061453647}, + ComplexPrecisionT{0.104219108364, 0.247711145514}, + ComplexPrecisionT{0.176998514444, 0.152305581694}, + ComplexPrecisionT{0.055177054767, 0.009344289143}, + ComplexPrecisionT{0.047003532929, 0.014270464770}, + ComplexPrecisionT{0.067602001658, 0.237978418468}, + ComplexPrecisionT{0.191357285454, 0.247486891611}, + ComplexPrecisionT{0.059014417923, 0.240820754268}, + ComplexPrecisionT{0.017675906958, 0.280795663824}, + ComplexPrecisionT{0.149294381068, 0.236647612943}, + }; + + const std::vector wires = {0, 2}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.052996853820, 0.268704529517}, + ComplexPrecisionT{0.082642978242, 0.195193762273}, + ComplexPrecisionT{0.275869474800, 0.221416497403}, + ComplexPrecisionT{0.198695648566, 0.006071386515}, + ComplexPrecisionT{0.067983147697, 0.276232498024}, + ComplexPrecisionT{0.136067312263, 0.055703741794}, + ComplexPrecisionT{0.157173013237, 0.279061453647}, + ComplexPrecisionT{0.104219108364, 0.247711145514}, + ComplexPrecisionT{0.177066334766, 0.143153236251}, + ComplexPrecisionT{0.091481259734, -0.001272371824}, + ComplexPrecisionT{0.070096171606, -0.013402737499}, + ComplexPrecisionT{0.068232891172, 0.226515814342}, + ComplexPrecisionT{0.232660238337, 0.241735302419}, + ComplexPrecisionT{0.095065259834, 0.214700810780}, + ComplexPrecisionT{0.055912814010, 0.247655060549}, + ComplexPrecisionT{0.184897295154, 0.224604965678}, + }; + + auto st = ini_st; + GateImplementation::applyCRX(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CRX1,3 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.192438300910, 0.082027221475}, + ComplexPrecisionT{0.217147770013, 0.101186506864}, + ComplexPrecisionT{0.172631211937, 0.036301903892}, + ComplexPrecisionT{0.006532319481, 0.086171029910}, + ComplexPrecisionT{0.042291498813, 0.282934641945}, + ComplexPrecisionT{0.231739267944, 0.188873888944}, + ComplexPrecisionT{0.278594048803, 0.306941867941}, + ComplexPrecisionT{0.126901023080, 0.220266540060}, + ComplexPrecisionT{0.229998291616, 0.200076737619}, + ComplexPrecisionT{0.016698938983, 0.160673755090}, + ComplexPrecisionT{0.123754272868, 0.123889666882}, + ComplexPrecisionT{0.128913058161, 0.104905508280}, + ComplexPrecisionT{0.004957334386, 0.000151477546}, + ComplexPrecisionT{0.286109480550, 0.287939421742}, + ComplexPrecisionT{0.180882613126, 0.180408714716}, + ComplexPrecisionT{0.169404192357, 0.128550443286}, + }; + + const std::vector wires = {1, 3}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.192438300910, 0.082027221475}, + ComplexPrecisionT{0.217147770013, 0.101186506864}, + ComplexPrecisionT{0.172631211937, 0.036301903892}, + ComplexPrecisionT{0.006532319481, 0.086171029910}, + ComplexPrecisionT{0.071122903322, 0.243493995118}, + ComplexPrecisionT{0.272884177375, 0.180009581467}, + ComplexPrecisionT{0.309433364794, 0.283498205063}, + ComplexPrecisionT{0.173048974802, 0.174307158347}, + ComplexPrecisionT{0.229998291616, 0.200076737619}, + ComplexPrecisionT{0.016698938983, 0.160673755090}, + ComplexPrecisionT{0.123754272868, 0.123889666882}, + ComplexPrecisionT{0.128913058161, 0.104905508280}, + ComplexPrecisionT{0.049633717487, -0.044302629247}, + ComplexPrecisionT{0.282658689673, 0.283672663198}, + ComplexPrecisionT{0.198658723032, 0.151897953530}, + ComplexPrecisionT{0.195376806318, 0.098886035231}, + }; + + auto st = ini_st; + GateImplementation::applyCRX(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(CRX); + +template +void testApplyCRY() { + using ComplexPrecisionT = std::complex; + + DYNAMIC_SECTION(GateImplementation::name + << ", CRY0,1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.024509081663, 0.005606762650}, + ComplexPrecisionT{0.261792037054, 0.259257414596}, + ComplexPrecisionT{0.168380715455, 0.096012484887}, + ComplexPrecisionT{0.169761107379, 0.042890935442}, + ComplexPrecisionT{0.012169527484, 0.082631086139}, + ComplexPrecisionT{0.155790166500, 0.292998574950}, + ComplexPrecisionT{0.150529463310, 0.282021216715}, + ComplexPrecisionT{0.097100202708, 0.134938013786}, + ComplexPrecisionT{0.062640753523, 0.251735121160}, + ComplexPrecisionT{0.121654204141, 0.116964600258}, + ComplexPrecisionT{0.152865184550, 0.084800955456}, + ComplexPrecisionT{0.300145205424, 0.101098965771}, + ComplexPrecisionT{0.288274703880, 0.038180155037}, + ComplexPrecisionT{0.041378441702, 0.206525491532}, + ComplexPrecisionT{0.033201995261, 0.096777018650}, + ComplexPrecisionT{0.303210250465, 0.300817738868}, + }; + + const std::vector wires = {0, 1}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.024509081663, 0.005606762650}, + ComplexPrecisionT{0.261792037054, 0.259257414596}, + ComplexPrecisionT{0.168380715455, 0.096012484887}, + ComplexPrecisionT{0.169761107379, 0.042890935442}, + ComplexPrecisionT{0.012169527484, 0.082631086139}, + ComplexPrecisionT{0.155790166500, 0.292998574950}, + ComplexPrecisionT{0.150529463310, 0.282021216715}, + ComplexPrecisionT{0.097100202708, 0.134938013786}, + ComplexPrecisionT{0.017091411508, 0.242746239557}, + ComplexPrecisionT{0.113748028260, 0.083456799483}, + ComplexPrecisionT{0.145850361424, 0.068735133269}, + ComplexPrecisionT{0.249391258812, 0.053133825802}, + ComplexPrecisionT{0.294506455875, 0.076828111036}, + ComplexPrecisionT{0.059777143539, 0.222190141515}, + ComplexPrecisionT{0.056549175144, 0.108777179774}, + ComplexPrecisionT{0.346161234622, 0.312872353290}, + }; + + auto st = ini_st; + GateImplementation::applyCRY(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CRY0,2 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.102619838050, 0.054477528511}, + ComplexPrecisionT{0.202715827962, 0.019268690848}, + ComplexPrecisionT{0.009985085718, 0.046864154650}, + ComplexPrecisionT{0.095353410397, 0.178365407785}, + ComplexPrecisionT{0.265491448756, 0.075474015573}, + ComplexPrecisionT{0.155542525434, 0.336145304405}, + ComplexPrecisionT{0.264473386058, 0.073102790542}, + ComplexPrecisionT{0.275654487087, 0.027356694914}, + ComplexPrecisionT{0.040156237615, 0.323407814320}, + ComplexPrecisionT{0.111584643322, 0.148005654537}, + ComplexPrecisionT{0.143440399478, 0.139829784016}, + ComplexPrecisionT{0.104105862006, 0.036845342185}, + ComplexPrecisionT{0.254859090295, 0.077839069459}, + ComplexPrecisionT{0.166580751989, 0.081673415646}, + ComplexPrecisionT{0.322693919290, 0.244062536913}, + ComplexPrecisionT{0.203101217204, 0.182142660415}, + }; + + const std::vector wires = {0, 2}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.102619838050, 0.054477528511}, + ComplexPrecisionT{0.202715827962, 0.019268690848}, + ComplexPrecisionT{0.009985085718, 0.046864154650}, + ComplexPrecisionT{0.095353410397, 0.178365407785}, + ComplexPrecisionT{0.265491448756, 0.075474015573}, + ComplexPrecisionT{0.155542525434, 0.336145304405}, + ComplexPrecisionT{0.264473386058, 0.073102790542}, + ComplexPrecisionT{0.275654487087, 0.027356694914}, + ComplexPrecisionT{0.017382553849, 0.297755483640}, + ComplexPrecisionT{0.094054909639, 0.140483782705}, + ComplexPrecisionT{0.147937549133, 0.188379019063}, + ComplexPrecisionT{0.120178355382, 0.059393264033}, + ComplexPrecisionT{0.201627929216, 0.038974326513}, + ComplexPrecisionT{0.133002468018, 0.052382480362}, + ComplexPrecisionT{0.358372291916, 0.253192504889}, + ComplexPrecisionT{0.226516213248, 0.192620277535}, + }; + + auto st = ini_st; + GateImplementation::applyCRY(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CRY1,3 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.058899496683, 0.031397556785}, + ComplexPrecisionT{0.069961513798, 0.130434904124}, + ComplexPrecisionT{0.217689437802, 0.274984586300}, + ComplexPrecisionT{0.306390652950, 0.298990481245}, + ComplexPrecisionT{0.209944539032, 0.220900665872}, + ComplexPrecisionT{0.003587823096, 0.069341448987}, + ComplexPrecisionT{0.114578641694, 0.136714993752}, + ComplexPrecisionT{0.131460200149, 0.288466810023}, + ComplexPrecisionT{0.153891247725, 0.128222510215}, + ComplexPrecisionT{0.161391493466, 0.264248676428}, + ComplexPrecisionT{0.102366240850, 0.123871730768}, + ComplexPrecisionT{0.094155009506, 0.178235083697}, + ComplexPrecisionT{0.137480035766, 0.038860712805}, + ComplexPrecisionT{0.181542539134, 0.186931324992}, + ComplexPrecisionT{0.130801257167, 0.165524479895}, + ComplexPrecisionT{0.303475658073, 0.099907724058}, + }; + + const std::vector wires = {1, 3}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.058899496683, 0.031397556785}, + ComplexPrecisionT{0.069961513798, 0.130434904124}, + ComplexPrecisionT{0.217689437802, 0.274984586300}, + ComplexPrecisionT{0.306390652950, 0.298990481245}, + ComplexPrecisionT{0.206837677400, 0.207444748683}, + ComplexPrecisionT{0.036162925095, 0.102820314015}, + ComplexPrecisionT{0.092762561137, 0.090236295654}, + ComplexPrecisionT{0.147665692045, 0.306204998241}, + ComplexPrecisionT{0.153891247725, 0.128222510215}, + ComplexPrecisionT{0.161391493466, 0.264248676428}, + ComplexPrecisionT{0.102366240850, 0.123871730768}, + ComplexPrecisionT{0.094155009506, 0.178235083697}, + ComplexPrecisionT{0.107604661198, 0.009345661471}, + ComplexPrecisionT{0.200698008554, 0.190699066265}, + ComplexPrecisionT{0.082062476397, 0.147991992696}, + ComplexPrecisionT{0.320112783074, 0.124411723198}, + }; + + auto st = ini_st; + GateImplementation::applyCRY(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} + +PENNYLANE_RUN_TEST(CRY); + +template +void testApplyCRZ() { + using ComplexPrecisionT = std::complex; + + DYNAMIC_SECTION(GateImplementation::name + << ", CRZ0,1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.264968228755, 0.059389110312}, + ComplexPrecisionT{0.004927738580, 0.117198819444}, + ComplexPrecisionT{0.192517901751, 0.061524928233}, + ComplexPrecisionT{0.285160768924, 0.013212111581}, + ComplexPrecisionT{0.278645646186, 0.212116779981}, + ComplexPrecisionT{0.171786665640, 0.141260537212}, + ComplexPrecisionT{0.199480649113, 0.218261452113}, + ComplexPrecisionT{0.071007710848, 0.294720535623}, + ComplexPrecisionT{0.169589173252, 0.010528306669}, + ComplexPrecisionT{0.061973371011, 0.033143783035}, + ComplexPrecisionT{0.177570977662, 0.116785656786}, + ComplexPrecisionT{0.070266502325, 0.084338553411}, + ComplexPrecisionT{0.053744021753, 0.146932844792}, + ComplexPrecisionT{0.254428637803, 0.138916780809}, + ComplexPrecisionT{0.260354050166, 0.267004004472}, + ComplexPrecisionT{0.008910554792, 0.316282675508}, + }; + + const std::vector wires = {0, 1}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.264968228755, 0.059389110312}, + ComplexPrecisionT{0.004927738580, 0.117198819444}, + ComplexPrecisionT{0.192517901751, 0.061524928233}, + ComplexPrecisionT{0.285160768924, 0.013212111581}, + ComplexPrecisionT{0.278645646186, 0.212116779981}, + ComplexPrecisionT{0.171786665640, 0.141260537212}, + ComplexPrecisionT{0.199480649113, 0.218261452113}, + ComplexPrecisionT{0.071007710848, 0.294720535623}, + ComplexPrecisionT{0.169165556003, -0.015948278519}, + ComplexPrecisionT{0.066370291483, 0.023112625918}, + ComplexPrecisionT{0.193559430151, 0.087778634862}, + ComplexPrecisionT{0.082516747253, 0.072397233118}, + ComplexPrecisionT{0.030262722499, 0.153498691785}, + ComplexPrecisionT{0.229755796458, 0.176759943762}, + ComplexPrecisionT{0.215708594452, 0.304212379961}, + ComplexPrecisionT{-0.040337866447, 0.313826361773}, + }; + + auto st = ini_st; + GateImplementation::applyCRZ(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CRZ0,2 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.148770394604, 0.083378238599}, + ComplexPrecisionT{0.274356796683, 0.083823071640}, + ComplexPrecisionT{0.028016616540, 0.165919229565}, + ComplexPrecisionT{0.123329104424, 0.295826835858}, + ComplexPrecisionT{0.222343815006, 0.093160444663}, + ComplexPrecisionT{0.288857659956, 0.138646598905}, + ComplexPrecisionT{0.199272938656, 0.123099916175}, + ComplexPrecisionT{0.182062963782, 0.098622669183}, + ComplexPrecisionT{0.270467177482, 0.282942493365}, + ComplexPrecisionT{0.147717133688, 0.038580110182}, + ComplexPrecisionT{0.279040367487, 0.114344708857}, + ComplexPrecisionT{0.229917326705, 0.222777886314}, + ComplexPrecisionT{0.047595071834, 0.026542458656}, + ComplexPrecisionT{0.133654136834, 0.275281854777}, + ComplexPrecisionT{0.126723771272, 0.071649311030}, + ComplexPrecisionT{0.040467231551, 0.098358909396}, + }; + + const std::vector wires = {0, 2}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.148770394604, 0.083378238599}, + ComplexPrecisionT{0.274356796683, 0.083823071640}, + ComplexPrecisionT{0.028016616540, 0.165919229565}, + ComplexPrecisionT{0.123329104424, 0.295826835858}, + ComplexPrecisionT{0.222343815006, 0.093160444663}, + ComplexPrecisionT{0.288857659956, 0.138646598905}, + ComplexPrecisionT{0.199272938656, 0.123099916175}, + ComplexPrecisionT{0.182062963782, 0.098622669183}, + ComplexPrecisionT{0.311143020471, 0.237484672050}, + ComplexPrecisionT{0.151917469671, 0.015161098089}, + ComplexPrecisionT{0.257886371956, 0.156310134957}, + ComplexPrecisionT{0.192512799579, 0.255794420869}, + ComplexPrecisionT{0.051140958142, 0.018825391755}, + ComplexPrecisionT{0.174801129192, 0.251173432304}, + ComplexPrecisionT{0.114052908458, 0.090468071985}, + ComplexPrecisionT{0.024693993739, 0.103451817578}, + }; + + auto st = ini_st; + GateImplementation::applyCRZ(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CRZ1,3 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.190769680625, 0.287992363388}, + ComplexPrecisionT{0.098068639739, 0.098569855389}, + ComplexPrecisionT{0.037728060139, 0.188330976218}, + ComplexPrecisionT{0.091809561053, 0.200107659880}, + ComplexPrecisionT{0.299856248683, 0.162326250675}, + ComplexPrecisionT{0.064700651300, 0.038667789709}, + ComplexPrecisionT{0.119630787356, 0.257575730461}, + ComplexPrecisionT{0.061392768321, 0.055938727834}, + ComplexPrecisionT{0.052661991695, 0.274401532393}, + ComplexPrecisionT{0.238974614805, 0.213527036406}, + ComplexPrecisionT{0.163750665141, 0.107235582319}, + ComplexPrecisionT{0.260992375359, 0.008326988206}, + ComplexPrecisionT{0.240406501616, 0.032737802983}, + ComplexPrecisionT{0.152754313527, 0.107245249982}, + ComplexPrecisionT{0.162638949527, 0.306372397719}, + ComplexPrecisionT{0.231663044710, 0.107293515032}, + }; + + const std::vector wires = {1, 3}; + const ParamT angle = 0.312; + + std::vector expected{ + ComplexPrecisionT{0.190769680625, 0.287992363388}, + ComplexPrecisionT{0.098068639739, 0.098569855389}, + ComplexPrecisionT{0.037728060139, 0.188330976218}, + ComplexPrecisionT{0.091809561053, 0.200107659880}, + ComplexPrecisionT{0.321435301661, 0.113766991605}, + ComplexPrecisionT{0.057907230634, 0.048250646420}, + ComplexPrecisionT{0.158197104346, 0.235861099766}, + ComplexPrecisionT{0.051956164721, 0.064797918341}, + ComplexPrecisionT{0.052661991695, 0.274401532393}, + ComplexPrecisionT{0.238974614805, 0.213527036406}, + ComplexPrecisionT{0.163750665141, 0.107235582319}, + ComplexPrecisionT{0.260992375359, 0.008326988206}, + ComplexPrecisionT{0.242573571004, -0.005011228787}, + ComplexPrecisionT{0.134236881868, 0.129676071390}, + ComplexPrecisionT{0.208264445871, 0.277383118761}, + ComplexPrecisionT{0.212179898392, 0.141983644728}, + }; + + auto st = ini_st; + GateImplementation::applyCRZ(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(CRZ); + +template +void testApplyCRot() { + using ComplexPrecisionT = std::complex; + const size_t num_qubits = 3; + + const auto ini_st = createZeroState(num_qubits); + + const std::vector angles{0.3, 0.8, 2.4}; + + std::vector expected_results(8); + const auto rot_mat = + Gates::getRot(angles[0], angles[1], angles[2]); + expected_results[0b1 << (num_qubits - 1)] = rot_mat[0]; + expected_results[(0b1 << num_qubits) - 2] = rot_mat[2]; + + DYNAMIC_SECTION(GateImplementation::name + << ", CRot0,1 |000> -> |000> - " + << PrecisionToName::value) { + auto st = createZeroState(num_qubits); + GateImplementation::applyCRot(st.data(), num_qubits, {0, 1}, false, + angles[0], angles[1], angles[2]); + + CHECK(st == PLApprox(ini_st)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", CRot0,1 |100> -> |1>(a|0>+b|1>)|0> - " + << PrecisionToName::value) { + auto st = createZeroState(num_qubits); + GateImplementation::applyPauliX(st.data(), num_qubits, {0}, false); + + GateImplementation::applyCRot(st.data(), num_qubits, {0, 1}, false, + angles[0], angles[1], angles[2]); + + CHECK(st == PLApprox(expected_results)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", CRot0,1 - " << PrecisionToName::value) { + const size_t num_qubits = 4; + + std::vector ini_st{ + ComplexPrecisionT{0.234734234199, 0.088957328814}, + ComplexPrecisionT{0.065109443398, 0.284054307559}, + ComplexPrecisionT{0.272603451516, 0.101758170511}, + ComplexPrecisionT{0.049922391489, 0.280849666080}, + ComplexPrecisionT{0.012676439023, 0.283581988298}, + ComplexPrecisionT{0.074837215146, 0.119865583718}, + ComplexPrecisionT{0.220666349215, 0.083019197512}, + ComplexPrecisionT{0.228645004012, 0.109144153614}, + ComplexPrecisionT{0.186515011731, 0.009044330588}, + ComplexPrecisionT{0.268705684298, 0.278878779206}, + ComplexPrecisionT{0.007225255939, 0.104466710409}, + ComplexPrecisionT{0.092186772555, 0.167323294042}, + ComplexPrecisionT{0.198642540305, 0.317101356672}, + ComplexPrecisionT{0.061416756317, 0.014463767792}, + ComplexPrecisionT{0.109767506116, 0.244842265274}, + ComplexPrecisionT{0.044108879936, 0.124327196075}, + }; + + const std::vector wires = {0, 1}; + const ParamT phi = 0.128; + const ParamT theta = -0.563; + const ParamT omega = 1.414; + + std::vector expected{ + ComplexPrecisionT{0.234734234199, 0.088957328814}, + ComplexPrecisionT{0.065109443398, 0.284054307559}, + ComplexPrecisionT{0.272603451516, 0.101758170511}, + ComplexPrecisionT{0.049922391489, 0.280849666080}, + ComplexPrecisionT{0.012676439023, 0.283581988298}, + ComplexPrecisionT{0.074837215146, 0.119865583718}, + ComplexPrecisionT{0.220666349215, 0.083019197512}, + ComplexPrecisionT{0.228645004012, 0.109144153614}, + ComplexPrecisionT{0.231541411002, -0.081215269214}, + ComplexPrecisionT{0.387885772871, 0.005250582985}, + ComplexPrecisionT{0.140096879751, 0.103289147066}, + ComplexPrecisionT{0.206040689190, 0.073864544104}, + ComplexPrecisionT{-0.115373527531, 0.318376165756}, + ComplexPrecisionT{0.019345803102, -0.055678858513}, + ComplexPrecisionT{-0.072480957773, 0.217744954736}, + ComplexPrecisionT{-0.045461901445, 0.062632338099}, + }; + + auto st = ini_st; + GateImplementation::applyCRot(st.data(), num_qubits, wires, false, phi, + theta, omega); + REQUIRE(st == PLApprox(expected).margin(1e-5)); + } +} +PENNYLANE_RUN_TEST(CRot); + +/******************************************************************************* + * Multi-qubit gates + ******************************************************************************/ +template +void testApplyMultiRZ() { + using ComplexPrecisionT = std::complex; + + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ0 |++++> - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + const ParamT angle = M_PI; + auto st = createPlusState(num_qubits); + + std::vector expected{ + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + }; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, {0}, false, + angle); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ0 |++++> - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + const ParamT angle = M_PI; + auto st = createPlusState(num_qubits); + + std::vector expected{ + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + }; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, {0}, false, + angle); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ01 |++++> - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + const ParamT angle = M_PI; + auto st = createPlusState(num_qubits); + + std::vector expected{ + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + }; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, {0, 1}, false, + angle); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ012 |++++> - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + const ParamT angle = M_PI; + auto st = createPlusState(num_qubits); + + std::vector expected{ + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, +0.25}, + }; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, {0, 1, 2}, + false, angle); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ0123 |++++> - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + const ParamT angle = M_PI; + auto st = createPlusState(num_qubits); + + std::vector expected{ + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, -0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, -0.25}, ComplexPrecisionT{0, +0.25}, + ComplexPrecisionT{0, +0.25}, ComplexPrecisionT{0, -0.25}, + }; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, {0, 1, 2, 3}, + false, angle); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + + DYNAMIC_SECTION(GateImplementation::name + << ", MultiRZ013 - " + << PrecisionToName::value) { + const size_t num_qubits = 4; + std::vector ini_st{ + ComplexPrecisionT{0.029963367200, 0.181037777550}, + ComplexPrecisionT{0.070992796807, 0.263183826811}, + ComplexPrecisionT{0.086883003918, 0.090811332201}, + ComplexPrecisionT{0.156989157753, 0.153911449950}, + ComplexPrecisionT{0.193120178047, 0.257383787598}, + ComplexPrecisionT{0.262262890778, 0.163282579388}, + ComplexPrecisionT{0.110853627976, 0.247870990381}, + ComplexPrecisionT{0.202098107411, 0.160525183734}, + ComplexPrecisionT{0.025750679341, 0.172601520950}, + ComplexPrecisionT{0.235737282225, 0.008347360496}, + ComplexPrecisionT{0.085757778150, 0.248516366527}, + ComplexPrecisionT{0.047549845173, 0.223003660220}, + ComplexPrecisionT{0.086414423346, 0.250866254986}, + ComplexPrecisionT{0.112429154107, 0.111787742027}, + ComplexPrecisionT{0.240562329064, 0.010449374903}, + ComplexPrecisionT{0.267984502939, 0.236708607552}, + }; + const std::vector wires = {0, 1, 3}; + const ParamT angle = 0.6746272767672288; + std::vector expected{ + ComplexPrecisionT{0.088189897518, 0.160919303534}, + ComplexPrecisionT{-0.020109410195, 0.271847963971}, + ComplexPrecisionT{0.112041208417, 0.056939635075}, + ComplexPrecisionT{0.097204863997, 0.197194179664}, + ComplexPrecisionT{0.097055284752, 0.306793234914}, + ComplexPrecisionT{0.301522534529, 0.067284365065}, + ComplexPrecisionT{0.022572982655, 0.270590123918}, + ComplexPrecisionT{0.243835640173, 0.084594090888}, + ComplexPrecisionT{-0.032823490356, 0.171397202432}, + ComplexPrecisionT{0.225215396328, -0.070141071525}, + ComplexPrecisionT{-0.001322233373, 0.262893576650}, + ComplexPrecisionT{0.118674074836, 0.194699985129}, + ComplexPrecisionT{0.164569740491, 0.208130081842}, + ComplexPrecisionT{0.069096925107, 0.142696982805}, + ComplexPrecisionT{0.230464206558, -0.069754376895}, + ComplexPrecisionT{0.174543309361, 0.312059756876}, + }; + + auto st = ini_st; + + GateImplementation::applyMultiRZ(st.data(), num_qubits, wires, false, + angle); + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } +} +PENNYLANE_RUN_TEST(MultiRZ); diff --git a/pennylane_lightning/src/tests/Test_GateOperations.cpp b/pennylane_lightning/src/tests/Test_GateOperations.cpp deleted file mode 100644 index e2c89f73e5..0000000000 --- a/pennylane_lightning/src/tests/Test_GateOperations.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - -#include "GateOperations.hpp" -#include "Util.hpp" - -#include - -TEST_CASE("Constant::gate_names is well defined", "GateOperations") { - static_assert(count_unique(Util::first_elts_of(Constant::gate_names)), - "Gate operations in gate_names are not distinct."); - - static_assert(count_unique(Util::second_elts_of(Constant::gate_names)), - "Gate names in gate_names are not distinct."); -} diff --git a/pennylane_lightning/src/tests/Test_GateOperations_Nonparam.cpp b/pennylane_lightning/src/tests/Test_GateOperations_Nonparam.cpp deleted file mode 100644 index 14e346e6ed..0000000000 --- a/pennylane_lightning/src/tests/Test_GateOperations_Nonparam.cpp +++ /dev/null @@ -1,537 +0,0 @@ -#include "ConstantUtil.hpp" -#include "GateOperationsLM.hpp" -#include "GateOperationsPI.hpp" -#include "Gates.hpp" -#include "TestHelpers.hpp" -#include "TestMacros.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -/** - * @file This file contains tests for non-parameterized gates. List of such - * gates are [PauliX, PauliY, PauliZ, Hadamard, S, T, CNOT, SWAP, CZ, Toffoli, - * CSWAP]. - */ -using namespace Pennylane; - -namespace { -using std::vector; -} - -/******************************************************************************* - * Single-qubit gates - ******************************************************************************/ - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyPauliX", - "[GateOperations_Nonparam],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_zero_state(num_qubits); - CHECK(st[0] == Util::ONE()); - - TestType::applyPauliX(st.data(), num_qubits, {index}, false); - CHECK(st[0] == Util::ZERO()); - CHECK(st[0b1 << (num_qubits - index - 1)] == Util::ONE()); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyPauliY", - "[GateOperations_Nonparam],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - constexpr CFP_t p = Util::ConstMult( - static_cast(0.5), - Util::ConstMult(Util::INVSQRT2(), Util::IMAG())); - constexpr CFP_t m = Util::ConstMult(-1, p); - - const std::vector> expected_results = { - {m, m, m, m, p, p, p, p}, - {m, m, p, p, m, m, p, p}, - {m, p, m, p, m, p, m, p}}; - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - - TestType::applyPauliY(st.data(), num_qubits, {index}, false); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyPauliZ", - "[GateOperations_Nonparam],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - constexpr CFP_t p(static_cast(0.5) * Util::INVSQRT2()); - constexpr CFP_t m(Util::ConstMult(-1, p)); - - const std::vector> expected_results = { - {p, p, p, p, m, m, m, m}, - {p, p, m, m, p, p, m, m}, - {p, m, p, m, p, m, p, m}}; - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - TestType::applyPauliZ(st.data(), num_qubits, {index}, false); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyHadamard", - "[GateOperations_Nonparam],[single-qubit]", - (GateOperationsLM, GateOperationsPI), - (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_zero_state(num_qubits); - - CHECK(st[0] == CFP_t{1, 0}); - TestType::applyHadamard(st.data(), num_qubits, {index}, false); - - CFP_t expected(1 / std::sqrt(2), 0); - CHECK(expected.real() == Approx(st[0].real())); - CHECK(expected.imag() == Approx(st[0].imag())); - - CHECK(expected.real() == - Approx(st[0b1 << (num_qubits - index - 1)].real())); - CHECK(expected.imag() == - Approx(st[0b1 << (num_qubits - index - 1)].imag())); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyS", - "[GateOperations_Nonparam],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - constexpr CFP_t r(static_cast(0.5) * Util::INVSQRT2()); - constexpr CFP_t i(Util::ConstMult(r, Util::IMAG())); - - const std::vector> expected_results = { - {r, r, r, r, i, i, i, i}, - {r, r, i, i, r, r, i, i}, - {r, i, r, i, r, i, r, i}}; - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - - TestType::applyS(st.data(), num_qubits, {index}, false); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyT", - "[GateOperations_Nonparam],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - // Test using |+++> state - - CFP_t r(1.0 / (2.0 * std::sqrt(2)), 0); - CFP_t i(1.0 / 4, 1.0 / 4); - - const std::vector> expected_results = { - {r, r, r, r, i, i, i, i}, - {r, r, i, i, r, r, i, i}, - {r, i, r, i, r, i, r, i}}; - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - - TestType::applyT(st.data(), num_qubits, {index}, false); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -/******************************************************************************* - * Two-qubit gates - ******************************************************************************/ - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyCNOT", - "[GateOperations_Nonparam],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - auto st = create_zero_state(num_qubits); - - // Test using |+00> state to generate 3-qubit GHZ state - TestType::applyHadamard(st.data(), num_qubits, {0}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::CNOT)) { - for (size_t index = 1; index < num_qubits; index++) { - TestType::applyCNOT(st.data(), num_qubits, {index - 1, index}, - false); - } - CHECK(st.front() == Util::INVSQRT2()); - CHECK(st.back() == Util::INVSQRT2()); - } -} - -// NOLINTNEXTLINE: Avoiding complexity errors -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applySWAP", - "[GateOperations_Nonparam],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - auto ini_st = create_zero_state(num_qubits); - - // Test using |+10> state - TestType::applyHadamard(ini_st.data(), num_qubits, {0}, false); - TestType::applyPauliX(ini_st.data(), num_qubits, {1}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::SWAP)) { - CHECK(ini_st == - std::vector{Util::ZERO(), Util::ZERO(), - Util::INVSQRT2(), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - Util::INVSQRT2(), Util::ZERO()}); - - SECTION("SWAP0,1 |+10> -> |1+0>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO()}; - auto sv01 = ini_st; - auto sv10 = ini_st; - - TestType::applySWAP(sv01.data(), num_qubits, {0, 1}, false); - TestType::applySWAP(sv10.data(), num_qubits, {1, 0}, false); - - CHECK(sv01 == expected); - CHECK(sv10 == expected); - } - - SECTION("SWAP0,2 |+10> -> |01+>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO()}; - - auto sv02 = ini_st; - auto sv20 = ini_st; - - TestType::applySWAP(sv02.data(), num_qubits, {0, 2}, false); - TestType::applySWAP(sv20.data(), num_qubits, {2, 0}, false); - - CHECK(sv02 == expected); - CHECK(sv20 == expected); - } - SECTION("SWAP1,2 |+10> -> |+01>") { - std::vector expected{ - Util::ZERO(), std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), Util::ZERO()}; - - auto sv12 = ini_st; - auto sv21 = ini_st; - - TestType::applySWAP(sv12.data(), num_qubits, {1, 2}, false); - TestType::applySWAP(sv21.data(), num_qubits, {2, 1}, false); - - CHECK(sv12 == expected); - CHECK(sv21 == expected); - } - } -} - -// NOLINTNEXTLINE: Avoiding complexity errors -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyCY", - "[GateOperations_Nonparam],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - auto ini_st = create_zero_state(num_qubits); - - // Test using |+10> state - TestType::applyHadamard(ini_st.data(), num_qubits, {0}, false); - TestType::applyPauliX(ini_st.data(), num_qubits, {1}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::CY)) { - CHECK(ini_st == - std::vector{ - Util::ZERO(), Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), Util::ZERO()}); - - SECTION("CY 0,1 |+10> -> i|100>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - std::complex(0, -1 / sqrt(2)), - Util::ZERO(), - Util::ZERO(), - Util::ZERO()}; - - auto sv01 = ini_st; - TestType::applyCY(sv01.data(), num_qubits, {0, 1}, false); - CHECK(sv01 == expected); - } - - SECTION("CY 0,2 |+10> -> |010> + i |111>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0.0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - std::complex(0.0, 1 / sqrt(2))}; - - auto sv02 = ini_st; - - TestType::applyCY(sv02.data(), num_qubits, {0, 2}, false); - CHECK(sv02 == expected); - } - SECTION("CY 1,2 |+10> -> i|+11>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - Util::ZERO(), std::complex(0.0, 1.0 / sqrt(2)), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), std::complex(0.0, 1 / sqrt(2))}; - - auto sv12 = ini_st; - - TestType::applyCY(sv12.data(), num_qubits, {1, 2}, false); - CHECK(sv12 == expected); - } - } -} - -// NOLINTNEXTLINE: Avoiding complexity errors -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyCZ", - "[GateOperations_Nonparam],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - auto ini_st = create_zero_state(num_qubits); - - // Test using |+10> state - TestType::applyHadamard(ini_st.data(), num_qubits, {0}, false); - TestType::applyPauliX(ini_st.data(), num_qubits, {1}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::CZ)) { - auto st = ini_st; - CHECK(st == std::vector{Util::ZERO(), Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO()}); - - SECTION("CZ0,1 |+10> -> |-10>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - std::complex(-1 / sqrt(2), 0), - Util::ZERO()}; - - auto sv01 = ini_st; - auto sv10 = ini_st; - - TestType::applyCZ(sv01.data(), num_qubits, {0, 1}, false); - TestType::applyCZ(sv10.data(), num_qubits, {1, 0}, false); - - CHECK(sv01 == expected); - CHECK(sv10 == expected); - } - - SECTION("CZ0,2 |+10> -> |+10>") { - const std::vector &expected{ini_st}; - - auto sv02 = ini_st; - auto sv20 = ini_st; - - TestType::applyCZ(sv02.data(), num_qubits, {0, 2}, false); - TestType::applyCZ(sv20.data(), num_qubits, {2, 0}, false); - - CHECK(sv02 == expected); - CHECK(sv20 == expected); - } - SECTION("CZ1,2 |+10> -> |+10>") { - const std::vector &expected{ini_st}; - - auto sv12 = ini_st; - auto sv21 = ini_st; - - TestType::applyCZ(sv12.data(), num_qubits, {1, 2}, false); - TestType::applyCZ(sv21.data(), num_qubits, {2, 1}, false); - - CHECK(sv12 == expected); - CHECK(sv21 == expected); - } - } -} - -/******************************************************************************* - * Three-qubit gates - ******************************************************************************/ - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyToffoli", - "[GateOperations_Nonparam],[three-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - auto ini_st = create_zero_state(num_qubits); - - // Test using |+10> state - TestType::applyHadamard(ini_st.data(), num_qubits, {0}, false); - TestType::applyPauliX(ini_st.data(), num_qubits, {1}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::Toffoli)) { - SECTION("Toffoli 0,1,2 |+10> -> |010> + |111>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0)}; - - auto sv012 = ini_st; - - TestType::applyToffoli(sv012.data(), num_qubits, {0, 1, 2}, false); - - CHECK(sv012 == expected); - } - - SECTION("Toffoli 1,0,2 |+10> -> |010> + |111>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0)}; - - auto sv102 = ini_st; - - TestType::applyToffoli(sv102.data(), num_qubits, {1, 0, 2}, false); - - CHECK(sv102 == expected); - } - - SECTION("Toffoli 0,2,1 |+10> -> |+10>") { - const auto &expected = ini_st; - - auto sv021 = ini_st; - - TestType::applyToffoli(sv021.data(), num_qubits, {0, 2, 1}, false); - - CHECK(sv021 == expected); - } - - SECTION("Toffoli 1,2,0 |+10> -> |+10>") { - const auto &expected = ini_st; - - auto sv120 = ini_st; - TestType::applyToffoli(sv120.data(), num_qubits, {1, 2, 0}, false); - CHECK(sv120 == expected); - } - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyCSWAP", - "[GateOperations_Nonparam],[three-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - auto ini_st = create_zero_state(num_qubits); - - // Test using |+10> state - TestType::applyHadamard(ini_st.data(), num_qubits, {0}, false); - TestType::applyPauliX(ini_st.data(), num_qubits, {1}, false); - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::CSWAP)) { - SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO()}; - - auto sv012 = ini_st; - TestType::applyCSWAP(sv012.data(), num_qubits, {0, 1, 2}, false); - CHECK(sv012 == expected); - } - - SECTION("CSWAP 1,0,2 |+10> -> |01+>") { - std::vector expected{Util::ZERO(), - Util::ZERO(), - std::complex(1.0 / sqrt(2), 0), - std::complex(1.0 / sqrt(2), 0), - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - Util::ZERO()}; - - auto sv102 = ini_st; - TestType::applyCSWAP(sv102.data(), num_qubits, {1, 0, 2}, false); - CHECK(sv102 == expected); - } - SECTION("CSWAP 2,1,0 |+10> -> |+10>") { - const auto &expected = ini_st; - - auto sv210 = ini_st; - TestType::applyCSWAP(sv210.data(), num_qubits, {2, 1, 0}, false); - CHECK(sv210 == expected); - } - } -} diff --git a/pennylane_lightning/src/tests/Test_GateOperations_Param.cpp b/pennylane_lightning/src/tests/Test_GateOperations_Param.cpp deleted file mode 100644 index 078b544fc4..0000000000 --- a/pennylane_lightning/src/tests/Test_GateOperations_Param.cpp +++ /dev/null @@ -1,329 +0,0 @@ -#include "ConstantUtil.hpp" -#include "GateOperationsLM.hpp" -#include "GateOperationsPI.hpp" -#include "Gates.hpp" -#include "TestHelpers.hpp" -#include "TestMacros.hpp" -#include "Util.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -/** - * @file This file contains tests for parameterized gates. List of such gates is - * [RX, RY, RZ, PhaseShift, Rot, ControlledPhaseShift, CRX, CRY, CRZ, CRot] - */ - -using namespace Pennylane; - -/******************************************************************************* - * Single-qubit gates - ******************************************************************************/ - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyRX", - "[GateOperations_Param],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 1; - - const std::vector angles{{0.1}, {0.6}}; - std::vector> expected_results{ - std::vector{{0.9987502603949663, 0.0}, - {0.0, -0.04997916927067834}}, - std::vector{{0.9553364891256061, 0.0}, {0, -0.2955202066613395}}, - std::vector{{0.49757104789172696, 0.0}, - {0, -0.867423225594017}}}; - - std::vector> expected_results_adj{ - std::vector{{0.9987502603949663, 0.0}, - {0.0, 0.04997916927067834}}, - std::vector{{0.9553364891256061, 0.0}, {0, 0.2955202066613395}}, - std::vector{{0.49757104789172696, 0.0}, {0, 0.867423225594017}}}; - - SECTION("adj = false") { - for (size_t index = 0; index < angles.size(); index++) { - auto st = create_zero_state(num_qubits); - - TestType::applyRX(st.data(), num_qubits, {0}, false, - {angles[index]}); - - CHECK(isApproxEqual(st, expected_results[index], 1e-7)); - } - } - SECTION("adj = true") { - for (size_t index = 0; index < angles.size(); index++) { - auto st = create_zero_state(num_qubits); - - TestType::applyRX(st.data(), num_qubits, {0}, true, - {angles[index]}); - - CHECK(isApproxEqual(st, expected_results_adj[index], 1e-7)); - } - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyRY", - "[GateOperations_Param],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 1; - - const std::vector angles{0.2, 0.7, 2.9}; - std::vector> expected_results{ - std::vector{{0.8731983044562817, 0.04786268954660339}, - {0.0876120655431924, -0.47703040785184303}}, - std::vector{{0.8243771119105122, 0.16439396602553008}, - {0.3009211363333468, -0.45035926880694604}}, - std::vector{{0.10575112905629831, 0.47593196040758534}, - {0.8711876098966215, -0.0577721051072477}}}; - std::vector> expected_results_adj{ - std::vector{{0.8731983044562817, -0.04786268954660339}, - {-0.0876120655431924, -0.47703040785184303}}, - std::vector{{0.8243771119105122, -0.16439396602553008}, - {-0.3009211363333468, -0.45035926880694604}}, - std::vector{{0.10575112905629831, -0.47593196040758534}, - {-0.8711876098966215, -0.0577721051072477}}}; - - const std::vector init_state{{0.8775825618903728, 0.0}, - {0.0, -0.47942553860420306}}; - SECTION("adj = false") { - for (size_t index = 0; index < angles.size(); index++) { - auto st = init_state; - TestType::applyRY(st.data(), num_qubits, {0}, false, - {angles[index]}); - CHECK(isApproxEqual(st, expected_results[index], 1e-5)); - } - } - SECTION("adj = true") { - for (size_t index = 0; index < angles.size(); index++) { - auto st = init_state; - - TestType::applyRY(st.data(), num_qubits, {0}, true, - {angles[index]}); - - CHECK(isApproxEqual(st, expected_results_adj[index], 1e-5)); - } - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyRZ", - "[GateOperations_Param],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - // Test using |+++> state - - const std::vector angles{0.2, 0.7, 2.9}; - const CFP_t coef(1.0 / (2 * std::sqrt(2)), 0); - - std::vector> rz_data; - rz_data.reserve(angles.size()); - for (auto &a : angles) { - rz_data.push_back(Gates::getRZ(a)); - } - - std::vector> expected_results = { - {rz_data[0][0], rz_data[0][0], rz_data[0][0], rz_data[0][0], - rz_data[0][3], rz_data[0][3], rz_data[0][3], rz_data[0][3]}, - { - rz_data[1][0], - rz_data[1][0], - rz_data[1][3], - rz_data[1][3], - rz_data[1][0], - rz_data[1][0], - rz_data[1][3], - rz_data[1][3], - }, - {rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3], - rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3]}}; - - for (auto &vec : expected_results) { - scaleVector(vec, coef); - } - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - - TestType::applyRZ(st.data(), num_qubits, {index}, false, - {angles[index]}); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyPhaseShift", - "[GateOperations_Param],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - // Test using |+++> state - - const std::vector angles{0.3, 0.8, 2.4}; - const CFP_t coef(1.0 / (2 * std::sqrt(2)), 0); - - std::vector> ps_data; - ps_data.reserve(angles.size()); - for (auto &a : angles) { - ps_data.push_back(Gates::getPhaseShift(a)); - } - - std::vector> expected_results = { - {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], - ps_data[0][3], ps_data[0][3], ps_data[0][3], ps_data[0][3]}, - { - ps_data[1][0], - ps_data[1][0], - ps_data[1][3], - ps_data[1][3], - ps_data[1][0], - ps_data[1][0], - ps_data[1][3], - ps_data[1][3], - }, - {ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3], - ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3]}}; - - for (auto &vec : expected_results) { - scaleVector(vec, coef); - } - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_plus_state(num_qubits); - - TestType::applyPhaseShift(st.data(), num_qubits, {index}, false, - {angles[index]}); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyRot", - "[GateOperations_Param],[single-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - auto ini_st = create_zero_state(num_qubits); - - const std::vector> angles{ - std::vector{0.3, 0.8, 2.4}, std::vector{0.5, 1.1, 3.0}, - std::vector{2.3, 0.1, 0.4}}; - - std::vector> expected_results{ - std::vector(0b1 << num_qubits), - std::vector(0b1 << num_qubits), - std::vector(0b1 << num_qubits)}; - - for (size_t i = 0; i < angles.size(); i++) { - const auto rot_mat = - Gates::getRot(angles[i][0], angles[i][1], angles[i][2]); - expected_results[i][0] = rot_mat[0]; - expected_results[i][0b1 << (num_qubits - i - 1)] = rot_mat[2]; - } - - for (size_t index = 0; index < num_qubits; index++) { - auto st = create_zero_state(num_qubits); - TestType::applyRot(st.data(), num_qubits, {index}, false, - angles[index][0], angles[index][1], - angles[index][2]); - - CHECK(isApproxEqual(st, expected_results[index])); - } -} - -/******************************************************************************* - * Two-qubit gates - ******************************************************************************/ - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyControlledPhaseShift", - "[GateOperations_Param],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - - const size_t num_qubits = 3; - - // Test using |+++> state - auto ini_st = create_plus_state(num_qubits); - - const std::vector angles{0.3, 2.4}; - const CFP_t coef(1.0 / (2 * std::sqrt(2)), 0); - - std::vector> ps_data; - ps_data.reserve(angles.size()); - for (auto &a : angles) { - ps_data.push_back(Gates::getPhaseShift(a)); - } - - std::vector> expected_results = { - {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], - ps_data[0][0], ps_data[0][0], ps_data[0][3], ps_data[0][3]}, - {ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3], - ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3]}}; - - for (auto &vec : expected_results) { - scaleVector(vec, coef); - } - - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::ControlledPhaseShift)) { - auto st = ini_st; - - TestType::applyControlledPhaseShift(st.data(), num_qubits, {0, 1}, - false, {angles[0]}); - CAPTURE(st); - CHECK(isApproxEqual(st, expected_results[0])); - } -} - -TEMPLATE_PRODUCT_TEST_CASE("GateOperations::applyCRot", - "[GateOperations_Param],[two-qubit]", - ALL_GATE_OPERATIONS, (float, double)) { - using fp_t = typename TestType::scalar_type_t; - using CFP_t = typename TestType::CFP_t; - const size_t num_qubits = 3; - - auto ini_st = create_zero_state(num_qubits); - - const std::vector angles{0.3, 0.8, 2.4}; - - std::vector expected_results(8); - const auto rot_mat = Gates::getRot(angles[0], angles[1], angles[2]); - expected_results[0b1 << (num_qubits - 1)] = rot_mat[0]; - expected_results[(0b1 << num_qubits) - 2] = rot_mat[2]; - - // correct this when LM kernels are fully developed - if constexpr (array_has_elt(TestType::implemented_gates, - GateOperations::CRot)) { - SECTION("CRot0,1 |000> -> |000>") { - auto st = create_zero_state(num_qubits); - TestType::applyCRot(st.data(), num_qubits, {0, 1}, false, angles[0], - angles[1], angles[2]); - - CHECK(isApproxEqual(st, ini_st)); - } - SECTION("CRot0,1 |100> -> |1>(a|0>+b|1>)|0>") { - auto st = create_zero_state(num_qubits); - TestType::applyPauliX(st.data(), num_qubits, {0}, false); - - TestType::applyCRot(st.data(), num_qubits, {0, 1}, false, angles[0], - angles[1], angles[2]); - - CHECK(isApproxEqual(st, expected_results)); - } - } -} diff --git a/pennylane_lightning/src/tests/Test_IndicesUtil.cpp b/pennylane_lightning/src/tests/Test_GateUtil.cpp similarity index 64% rename from pennylane_lightning/src/tests/Test_IndicesUtil.cpp rename to pennylane_lightning/src/tests/Test_GateUtil.cpp index 1501808b48..15642d4303 100644 --- a/pennylane_lightning/src/tests/Test_IndicesUtil.cpp +++ b/pennylane_lightning/src/tests/Test_GateUtil.cpp @@ -1,5 +1,5 @@ +#include "GateUtil.hpp" #include "Gates.hpp" -#include "IndicesUtil.hpp" #include "Util.hpp" #include "TestHelpers.hpp" @@ -15,43 +15,39 @@ #include using namespace Pennylane; +using namespace Pennylane::Gates; -TEST_CASE("IndicesUtil::generateBitPatterns", "[IndicesUtil]") { +TEST_CASE("generateBitPatterns", "[IndicesUtil]") { const size_t num_qubits = 4; SECTION("Qubit indices {}") { - auto bit_pattern = IndicesUtil::generateBitPatterns({}, num_qubits); + auto bit_pattern = generateBitPatterns({}, num_qubits); CHECK(bit_pattern == std::vector{0}); } SECTION("Qubit indices {i}") { for (size_t i = 0; i < num_qubits; i++) { std::vector expected{0, 0b1UL << (num_qubits - i - 1)}; - auto bit_pattern = - IndicesUtil::generateBitPatterns({i}, num_qubits); + auto bit_pattern = generateBitPatterns({i}, num_qubits); CHECK(bit_pattern == expected); } } SECTION("Qubit indices {i,i+1,i+2}") { std::vector expected_123{0, 1, 2, 3, 4, 5, 6, 7}; std::vector expected_012{0, 2, 4, 6, 8, 10, 12, 14}; - auto bit_pattern_123 = - IndicesUtil::generateBitPatterns({1, 2, 3}, num_qubits); - auto bit_pattern_012 = - IndicesUtil::generateBitPatterns({0, 1, 2}, num_qubits); + auto bit_pattern_123 = generateBitPatterns({1, 2, 3}, num_qubits); + auto bit_pattern_012 = generateBitPatterns({0, 1, 2}, num_qubits); CHECK(bit_pattern_123 == expected_123); CHECK(bit_pattern_012 == expected_012); } SECTION("Qubit indices {0,2,3}") { std::vector expected{0, 1, 2, 3, 8, 9, 10, 11}; - auto bit_pattern = - IndicesUtil::generateBitPatterns({0, 2, 3}, num_qubits); + auto bit_pattern = generateBitPatterns({0, 2, 3}, num_qubits); CHECK(bit_pattern == expected); } SECTION("Qubit indices {3,1,0}") { std::vector expected{0, 8, 4, 12, 1, 9, 5, 13}; - auto bit_pattern = - IndicesUtil::generateBitPatterns({3, 1, 0}, num_qubits); + auto bit_pattern = generateBitPatterns({3, 1, 0}, num_qubits); CHECK(bit_pattern == expected); } } @@ -60,7 +56,7 @@ TEST_CASE("StateVector::getIndicesAfterExclusion", "[StateVector_Nonparam]") { const size_t num_qubits = 4; SECTION("Qubit indices {}") { std::vector expected{0, 1, 2, 3}; - auto indices = IndicesUtil::getIndicesAfterExclusion({}, num_qubits); + auto indices = getIndicesAfterExclusion({}, num_qubits); CHECK(indices == expected); } SECTION("Qubit indices {i}") { @@ -68,33 +64,28 @@ TEST_CASE("StateVector::getIndicesAfterExclusion", "[StateVector_Nonparam]") { std::vector expected{0, 1, 2, 3}; expected.erase(expected.begin() + i); - auto indices = - IndicesUtil::getIndicesAfterExclusion({i}, num_qubits); + auto indices = getIndicesAfterExclusion({i}, num_qubits); CHECK(indices == expected); } } SECTION("Qubit indices {i,i+1,i+2}") { std::vector expected_123{0}; std::vector expected_012{3}; - auto indices_123 = - IndicesUtil::getIndicesAfterExclusion({1, 2, 3}, num_qubits); - auto indices_012 = - IndicesUtil::getIndicesAfterExclusion({0, 1, 2}, num_qubits); + auto indices_123 = getIndicesAfterExclusion({1, 2, 3}, num_qubits); + auto indices_012 = getIndicesAfterExclusion({0, 1, 2}, num_qubits); CHECK(indices_123 == expected_123); CHECK(indices_012 == expected_012); } SECTION("Qubit indices {0,2,3}") { std::vector expected{1}; - auto indices = - IndicesUtil::getIndicesAfterExclusion({0, 2, 3}, num_qubits); + auto indices = getIndicesAfterExclusion({0, 2, 3}, num_qubits); CHECK(indices == expected); } SECTION("Qubit indices {3,1,0}") { std::vector expected{2}; - auto indices = - IndicesUtil::getIndicesAfterExclusion({3, 1, 0}, num_qubits); + auto indices = getIndicesAfterExclusion({3, 1, 0}, num_qubits); CHECK(indices == expected); } diff --git a/pennylane_lightning/src/tests/Test_Internal.cpp b/pennylane_lightning/src/tests/Test_Internal.cpp new file mode 100644 index 0000000000..4020a393c6 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_Internal.cpp @@ -0,0 +1,118 @@ +#include "GateImplementationsPI.hpp" +#include "TestHelpers.hpp" + +#include + +#include + +/** + * We test internal functions for test suite. + */ + +using namespace Pennylane; +using Pennylane::Gates::GateImplementationsPI; + +TEMPLATE_TEST_CASE("Approx", "[Test_Internal]", float, double) { + using PrecisionT = TestType; + using ComplexPrecisionT = std::complex; + + SECTION("vector{1.0, 1.0*I} approx vector{1.0001, 0.9999*I} with margin " + "0.00015") { + const std::vector test1{ + ComplexPrecisionT{1.0, 0.0}, + ComplexPrecisionT{0.0, 1.0}, + }; + const std::vector test2{ + ComplexPrecisionT{1.0001, 0.0}, + ComplexPrecisionT{0.0, 0.9999}, + }; + REQUIRE(test1 == PLApprox(test2).margin(0.00015)); + } + SECTION("vector{1.0, 1.0*I} does not approx vector{1.0002, 0.9998*I} with " + "margin 0.00015") { + const std::vector test1{ + ComplexPrecisionT{1.0, 0.0}, + ComplexPrecisionT{0.0, 1.0}, + }; + const std::vector test2{ + ComplexPrecisionT{1.0002, 0.0}, + ComplexPrecisionT{0.0, 0.9998}, + }; + REQUIRE(test1 != PLApprox(test2).margin(0.00015)); + } + SECTION("vector{1.0, 1.0*I} does not approx vector{1.0I, 1.0} with margin " + "0.00015") { + const std::vector test1{ + ComplexPrecisionT{1.0, 0.0}, + ComplexPrecisionT{0.0, 1.0}, + }; + const std::vector test2{ + ComplexPrecisionT{0.0, 1.0}, + ComplexPrecisionT{1.0, 0.0}, + }; + REQUIRE(test1 != PLApprox(test2).margin(0.00015)); + } +} + +TEMPLATE_TEST_CASE("createProductState", "[Test_Internal]", float, double) { + using PrecisionT = TestType; + + SECTION("createProductState(\"+-0\") == |+-0> ") { + const auto st = createProductState("+-0"); + + auto expected = createZeroState(3); + GateImplementationsPI::applyHadamard(expected.data(), 3, {0}, false); + + GateImplementationsPI::applyPauliX(expected.data(), 3, {1}, false); + GateImplementationsPI::applyHadamard(expected.data(), 3, {1}, false); + + REQUIRE(st == PLApprox(expected).margin(1e-7)); + } + SECTION("createProductState(\"+-0\") == |+-1> ") { + const auto st = createProductState("+-0"); + + auto expected = createZeroState(3); + GateImplementationsPI::applyHadamard(expected.data(), 3, {0}, false); + + GateImplementationsPI::applyPauliX(expected.data(), 3, {1}, false); + GateImplementationsPI::applyHadamard(expected.data(), 3, {1}, false); + + GateImplementationsPI::applyPauliX(expected.data(), 3, {2}, false); + + REQUIRE(st != PLApprox(expected).margin(1e-7)); + } +} + +/** + * @brief Test randomUnitary is correct + */ +TEMPLATE_TEST_CASE("randomUnitary", "[Test_Internal]", float, double) { + using PrecisionT = TestType; + using ComplexPrecisionT = std::complex; + + std::mt19937 re{1337}; + + for (size_t num_qubits = 1; num_qubits <= 5; num_qubits++) { + const size_t dim = (1U << num_qubits); + const auto unitary = randomUnitary(re, num_qubits); + + std::vector> unitary_dagger = + Util::Transpose(unitary, dim, dim); + std::transform( + unitary_dagger.begin(), unitary_dagger.end(), + unitary_dagger.begin(), + [](const std::complex &v) { return std::conj(v); }); + + std::vector> mat(dim * dim); + Util::matrixMatProd(unitary.data(), unitary_dagger.data(), mat.data(), + dim, dim, dim); + + std::vector> identity( + dim * dim, std::complex{}); + for (size_t i = 0; i < dim; i++) { + identity[i * dim + i] = std::complex{1.0, 0.0}; + } + + REQUIRE(mat == PLApprox(identity).margin(1e-5)); + } +} diff --git a/pennylane_lightning/src/tests/Test_OpToMemberFuncPtr.cpp b/pennylane_lightning/src/tests/Test_OpToMemberFuncPtr.cpp new file mode 100644 index 0000000000..78d9c7bbac --- /dev/null +++ b/pennylane_lightning/src/tests/Test_OpToMemberFuncPtr.cpp @@ -0,0 +1,252 @@ +#include "OpToMemberFuncPtr.hpp" +#include "SelectKernel.hpp" +#include "Util.hpp" + +#include + +using namespace Pennylane; +using namespace Pennylane::Gates; + +template +constexpr auto +allGateOpsHelper([[maybe_unused]] std::integer_sequence dummy) { + return std::make_tuple(static_cast(I)...); +} + +template constexpr auto allGateOps() { + return Util::tuple_to_array(allGateOpsHelper( + std::make_integer_sequence(EnumClass::END)>{})); +}; +template +constexpr bool testAllGatesImplementedIter() { + if constexpr (gate_idx < static_cast(GateOperation::END)) { + constexpr auto gate_op = static_cast(gate_idx); + if constexpr (gate_op != GateOperation::Matrix) { + if (GateOpToMemberFuncPtr::value == nullptr) { + return false; + } + } + return testAllGatesImplementedIter(); + } else { + return true; + } +} +template +constexpr bool testAllGatesImplemeted() { + return testAllGatesImplementedIter(); +} + +#define PENNYLANE_TESTS_DEFINE_GATE_OP_PARAM0(GATE_NAME) \ + template \ + static void apply##GATE_NAME( \ + std::complex *arr, size_t num_qubits, \ + const std::vector &wires, bool inverse) { \ + static_cast(arr); \ + static_cast(num_qubits); \ + static_cast(wires); \ + static_cast(inverse); \ + } +#define PENNYLANE_TESTS_DEFINE_GATE_OP_PARAM1(GATE_NAME) \ + template \ + static void apply##GATE_NAME( \ + std::complex *arr, size_t num_qubits, \ + const std::vector &wires, bool inverse, ParamT) { \ + static_cast(arr); \ + static_cast(num_qubits); \ + static_cast(wires); \ + static_cast(inverse); \ + } +#define PENNYLANE_TESTS_DEFINE_GATE_OP_PARAM3(GATE_NAME) \ + template \ + static void apply##GATE_NAME(std::complex *arr, \ + size_t num_qubits, \ + const std::vector &wires, \ + bool inverse, ParamT, ParamT, ParamT) { \ + static_cast(arr); \ + static_cast(num_qubits); \ + static_cast(wires); \ + static_cast(inverse); \ + } +#define PENNYLANE_TESTS_DEFINE_GATE_OP(GATE_NAME, NUM_PARAMS) \ + PENNYLANE_TESTS_DEFINE_GATE_OP_PARAM##NUM_PARAMS(GATE_NAME) + +#define PENNYLANE_TESTS_DEFINE_GENERATOR_OP(GENERATOR_NAME) \ + template \ + static PrecisionT applyGenerator##GENERATOR_NAME( \ + std::complex *arr, size_t num_qubits, \ + const std::vector &wires, bool adj) { \ + static_cast(arr); \ + static_cast(num_qubits); \ + static_cast(wires); \ + static_cast(adj); \ + return PrecisionT{}; \ + } + +/** + * @brief This class defines all dummy gates and generators to check + * consistency of OpToMemberFuncPtr + */ +class DummyImplementation { + public: + constexpr static std::array implemented_gates = allGateOps(); + constexpr static std::array implemented_generators = + allGateOps(); + constexpr static std::string_view name = "Dummy"; + + template + static void applyMatrix(std::complex *arr, size_t num_qubits, + const std::complex *matrix, + const std::vector &wires, bool inverse) { + static_cast(arr); + static_cast(num_qubits); + static_cast(matrix); + static_cast(inverse); + } + + PENNYLANE_TESTS_DEFINE_GATE_OP(PauliX, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(PauliY, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(PauliZ, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(Hadamard, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(S, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(T, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(PhaseShift, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(RX, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(RY, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(RZ, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(Rot, 3) + PENNYLANE_TESTS_DEFINE_GATE_OP(CNOT, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(CY, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(CZ, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(SWAP, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(ControlledPhaseShift, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(IsingXX, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(IsingYY, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(IsingZZ, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(CRX, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(CRY, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(CRZ, 1) + PENNYLANE_TESTS_DEFINE_GATE_OP(CRot, 3) + PENNYLANE_TESTS_DEFINE_GATE_OP(Toffoli, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(CSWAP, 0) + PENNYLANE_TESTS_DEFINE_GATE_OP(MultiRZ, 1) + + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(PhaseShift) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(RX) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(RY) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(RZ) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(IsingXX) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(IsingYY) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(IsingZZ) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(CRX) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(CRY) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(CRZ) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(ControlledPhaseShift) + PENNYLANE_TESTS_DEFINE_GENERATOR_OP(MultiRZ) +}; + +static_assert(testAllGatesImplemeted(), + "DummyImplementation must define all gate operations."); + +struct ImplementedGates { + constexpr static auto value = DummyImplementation::implemented_gates; + constexpr static std::array ignore_list = { + GateOperation::Matrix}; + + template + constexpr static auto func_ptr = + GateOpToMemberFuncPtr::value; +}; +struct ImplementedGenerators { + constexpr static auto value = DummyImplementation::implemented_generators; + constexpr static std::array ignore_list = {}; + + template + constexpr static auto func_ptr = + GeneratorOpToMemberFuncPtr::value; +}; + +template +constexpr auto opFuncPtrPairsIter() { + if constexpr (op_idx < ValueClass::value.size()) { + constexpr auto op = ValueClass::value[op_idx]; + if constexpr (Util::array_has_elt(ValueClass::ignore_list, op)) { + return opFuncPtrPairsIter(); + } else { + const auto elt = std::pair{ + op, ValueClass::template func_ptr}; + return Util::prepend_to_tuple( + elt, opFuncPtrPairsIter()); + } + } else { + return std::tuple{}; + } +}; + +/** + * @brief Pairs of all implemented gate operations and the corresponding + * function pointers. + */ +template +constexpr static auto gate_op_func_ptr_pairs = + opFuncPtrPairsIter(); + +template +constexpr static auto generator_op_func_ptr_pairs = + opFuncPtrPairsIter(); + +template +constexpr auto gateOpFuncPtrPairsWithNumParamsIter() { + if constexpr (tuple_idx < + std::tuple_size_v< + decltype(gate_op_func_ptr_pairs)>) { + constexpr auto elt = + std::get(gate_op_func_ptr_pairs); + if constexpr (static_lookup(Constant::gate_num_params) == + num_params) { + return Util::prepend_to_tuple( + elt, gateOpFuncPtrPairsWithNumParamsIter< + PrecisionT, ParamT, num_params, tuple_idx + 1>()); + } else { + return gateOpFuncPtrPairsWithNumParamsIter< + PrecisionT, ParamT, num_params, tuple_idx + 1>(); + } + } else { + return std::tuple{}; + } +} + +template +constexpr auto gate_op_func_ptr_with_params = Util::tuple_to_array( + gateOpFuncPtrPairsWithNumParamsIter()); + +template +constexpr auto generator_op_func_ptr = + Util::tuple_to_array(generator_op_func_ptr_pairs); + +template +auto testUniqueness(const std::array, size> &pairs) { + REQUIRE(Util::count_unique(Util::first_elts_of(pairs)) == pairs.size()); + REQUIRE(Util::count_unique(Util::second_elts_of(pairs)) == pairs.size()); +} + +TEMPLATE_TEST_CASE("GateOpToMemberFuncPtr", "[GateOpToMemberFuncPtr]", float, + double) { + // TODO: This can be done in compile time + testUniqueness(gate_op_func_ptr_with_params); + testUniqueness(gate_op_func_ptr_with_params); + testUniqueness(gate_op_func_ptr_with_params); +} +TEMPLATE_TEST_CASE("GeneratorOpToMemberFuncPtr", "[GeneratorOpToMemberFuncPtr]", + float, double) { + // TODO: This can be done in compile time + testUniqueness(generator_op_func_ptr); +} diff --git a/pennylane_lightning/src/tests/Test_SelectGateOps.cpp b/pennylane_lightning/src/tests/Test_SelectGateOps.cpp deleted file mode 100644 index affa494dbf..0000000000 --- a/pennylane_lightning/src/tests/Test_SelectGateOps.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "SelectGateOps.hpp" -#include "TestHelpers.hpp" -#include "Util.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace Pennylane; - -template -std::enable_if_t::kernel_id == kernel, void> -checkKernelId() { - static_assert(SelectGateOps::kernel_id == kernel); -} - -template -std::enable_if_t::kernel_id != kernel, void> -checkKernelId() { - static_assert(SelectGateOps::kernel_id == kernel); -} - -template void checkAllAvailableKernelsIter() { - if constexpr (idx == Constant::available_kernels.size()) { - // do nothing - } else { - checkKernelId(Constant::available_kernels[idx])>(); - checkAllAvailableKernelsIter(); - } -} - -template void checkAllAvailableKernels() { - checkAllAvailableKernelsIter(); -} - -TEMPLATE_TEST_CASE("SelectGateOps", "[SelectGateOps]", float, double) { - SECTION( - "Check all available gate implementations have correct kernel ids.") { - checkAllAvailableKernels(); - } - - SECTION("Check all gate operations have default kernels") { - static_assert( - count_unique(first_elts_of(Constant::default_kernel_for_ops)) == - static_cast(GateOperations::END)); - } -} - -template -constexpr size_t count_elt(int begin, int end, UnaryPredicate p) { - size_t count = 0; - for (int i = begin; i < end; i++) { - if (p(i)) { - count++; - } - } - return count; -} - -template struct CountGatesWithNumParamsIter { - constexpr static size_t value = - CountGatesWithNumParamsIter::value + - int(static_lookup(idx)>( - Constant::gate_num_params) == num_params); -}; - -template struct CountGatesWithNumParamsIter { - constexpr static size_t value = 0; -}; - -template struct CountGatesWithNumParams { - constexpr static size_t value = CountGatesWithNumParamsIter< - num_params, static_cast(GateOperations::END) - 1>::value; -}; - -/** - * TODO: replace all CountGatewWithNumParams template struct to below constexpr - * function - * */ -constexpr static size_t countGatesWithNumParams(size_t num_params) { - size_t cnt = 0; - for (size_t idx = 0; idx < static_cast(GateOperations::END) - 1; - idx++) { - const auto gate_op = static_cast(idx); - if (gate_op == GateOperations::Matrix) { - continue; - } - if (lookup(Constant::gate_num_params, gate_op) == num_params) { - cnt++; - } - } - return cnt; -} - -template -void testGateFuncPtrPair() { - // TODO: This also can be done in compile time - std::set gate_ops_set; - for (const auto &[gate_op, func] : - Pennylane::Internal::GateOpsFuncPtrPairs::value) { - gate_ops_set.emplace(gate_op); - } - - REQUIRE(gate_ops_set.size() == - count_elt( - static_cast(GateOperations::BEGIN), - static_cast(GateOperations::END) - 1, /*Besides matrix*/ - [](int v) { - return lookup(Constant::gate_num_params, - static_cast(v)) == num_params; - })); -} - -template -void testGateFuncPtrPairIter() { - using Pennylane::Internal::GateOpsFuncPtrPairs; - if constexpr (kernel_idx < Constant::available_kernels.size()) { - const auto kernel = - std::get<0>(Constant::available_kernels[kernel_idx]); - - static_assert( - count_unique(Util::first_elts_of( - GateOpsFuncPtrPairs::value)) == - CountGatesWithNumParams::value, - "Gate operations in GateOpsFuncPtrPairs are not distinct"); - REQUIRE( - count_unique(Util::second_elts_of( - GateOpsFuncPtrPairs::value)) == - CountGatesWithNumParams::value); - } -} - -TEMPLATE_TEST_CASE("GateOpsFuncPtrPairs", "[GateOpsFuncPtrPairs]", float, - double) { - testGateFuncPtrPairIter(); - testGateFuncPtrPairIter(); - // The following line must not be compiled - // testGateFuncPtrPairIter(); - testGateFuncPtrPairIter(); - REQUIRE(true); -} diff --git a/pennylane_lightning/src/tests/Test_StateVectorRaw.cpp b/pennylane_lightning/src/tests/Test_StateVectorRaw.cpp index 65065e6924..4700c74881 100644 --- a/pennylane_lightning/src/tests/Test_StateVectorRaw.cpp +++ b/pennylane_lightning/src/tests/Test_StateVectorRaw.cpp @@ -10,8 +10,6 @@ using namespace Pennylane; -constexpr auto referenceKernel = KernelType::PI; - std::mt19937_64 re{1337}; TEMPLATE_TEST_CASE("StateVectorRaw::StateVectorRaw", "[StateVectorRaw]", float, @@ -20,7 +18,7 @@ TEMPLATE_TEST_CASE("StateVectorRaw::StateVectorRaw", "[StateVectorRaw]", float, SECTION("StateVectorRaw {std::complex*, size_t}") { const size_t num_qubits = 4; - auto st_data = create_random_state(re, num_qubits); + auto st_data = createRandomState(re, num_qubits); StateVectorRaw sv(st_data.data(), st_data.size()); REQUIRE(sv.getNumQubits() == 4); @@ -37,160 +35,13 @@ TEMPLATE_TEST_CASE("StateVectorRaw::setData", "[StateVectorRaw]", float, double) { using fp_t = TestType; - auto st_data = create_random_state(re, 4); + auto st_data = createRandomState(re, 4); StateVectorRaw sv(st_data.data(), st_data.size()); - auto st_data2 = create_random_state(re, 8); + auto st_data2 = createRandomState(re, 8); sv.setData(st_data2.data(), st_data2.size()); REQUIRE(sv.getNumQubits() == 8); REQUIRE(sv.getData() == st_data2.data()); REQUIRE(sv.getLength() == (1U << 8)); } - -#define PENNYLANE_TEST_STATIC_DISPATCH(GATE_NAME) \ - template struct TestGateOps##GATE_NAME { \ - static void test() { \ - static_assert( \ - (num_params != 2) || (num_params > 3), \ - "Unsupported number of parameters for gate " #GATE_NAME "."); \ - } \ - }; \ - template struct TestGateOps##GATE_NAME { \ - constexpr static GateOperations op = GateOperations::GATE_NAME; \ - template \ - static std::enable_if_t< \ - array_has_elt(SelectGateOps::implemented_gates, op), \ - void> \ - test() { \ - const size_t num_qubits = 4; \ - auto ini_st = create_random_state(re, num_qubits); \ - auto num_wires = lookup(Constant::gate_wires, op); \ - std::vector wires; \ - wires.resize(num_wires); \ - std::iota(wires.begin(), wires.end(), 0); \ - auto expected = ini_st; \ - SelectGateOps::apply##GATE_NAME( \ - expected.data(), num_qubits, wires, false); \ - auto sv_data = ini_st; \ - StateVectorRaw sv(sv_data.data(), sv_data.size()); \ - sv.apply##GATE_NAME(wires, false); \ - REQUIRE(isApproxEqual(sv_data, expected)); \ - } \ - template \ - static std::enable_if_t< \ - !array_has_elt(SelectGateOps::implemented_gates, \ - op), \ - void> \ - test() { /* do nothing */ \ - } \ - }; \ - template struct TestGateOps##GATE_NAME { \ - constexpr static GateOperations op = GateOperations::GATE_NAME; \ - constexpr static std::array params = {0.312}; \ - template \ - static std::enable_if_t< \ - array_has_elt(SelectGateOps::implemented_gates, op), \ - void> \ - test() { \ - const size_t num_qubits = 4; \ - auto ini_st = create_random_state(re, num_qubits); \ - auto num_wires = lookup(Constant::gate_wires, op); \ - std::vector wires; \ - wires.resize(num_wires); \ - std::iota(wires.begin(), wires.end(), 0); \ - auto expected = ini_st; \ - SelectGateOps::template apply##GATE_NAME< \ - fp_t>(expected.data(), num_qubits, wires, false, params[0]); \ - auto sv_data = ini_st; \ - StateVectorRaw sv(sv_data.data(), sv_data.size()); \ - sv.apply##GATE_NAME(wires, false, params[0]); \ - REQUIRE(isApproxEqual(sv_data, expected)); \ - } \ - template \ - static std::enable_if_t< \ - !array_has_elt(SelectGateOps::implemented_gates, \ - op), \ - void> \ - test() { /* do nothing */ \ - } \ - }; \ - template struct TestGateOps##GATE_NAME { \ - constexpr static GateOperations op = GateOperations::GATE_NAME; \ - constexpr static std::array params = {0.128, -0.563, 1.414}; \ - template \ - static std::enable_if_t< \ - array_has_elt(SelectGateOps::implemented_gates, op), \ - void> \ - test() { \ - const size_t num_qubits = 4; \ - auto ini_st = create_random_state(re, num_qubits); \ - auto num_wires = lookup(Constant::gate_wires, op); \ - std::vector wires; \ - wires.resize(num_wires); \ - std::iota(wires.begin(), wires.end(), 0); \ - auto expected = ini_st; \ - SelectGateOps::template apply##GATE_NAME< \ - fp_t>(expected.data(), num_qubits, wires, false, params[0], \ - params[1], params[2]); \ - auto sv_data = ini_st; \ - StateVectorRaw sv(sv_data.data(), sv_data.size()); \ - sv.apply##GATE_NAME(wires, false, params[0], params[1], \ - params[2]); \ - REQUIRE(isApproxEqual(sv_data, expected)); \ - } \ - template \ - static std::enable_if_t< \ - !array_has_elt(SelectGateOps::implemented_gates, \ - op), \ - void> \ - test() { /* do nothing */ \ - } \ - }; \ - template \ - void testStateVectorApply##GATE_NAME##Iter() { \ - if constexpr (idx < Constant::available_kernels.size()) { \ - constexpr auto kernel = \ - std::get<0>(Constant::available_kernels[idx]); \ - TestGateOps##GATE_NAME< \ - fp_t, \ - static_lookup( \ - Constant::gate_num_params)>::template test(); \ - testStateVectorApply##GATE_NAME##Iter(); \ - } \ - } \ - template void testStateVectorApply##GATE_NAME() { \ - testStateVectorApply##GATE_NAME##Iter(); \ - } \ - TEMPLATE_TEST_CASE("StateVectorRaw::" #GATE_NAME, "[StateVectorRaw]", \ - float, double) { \ - using fp_t = TestType; \ - testStateVectorApply##GATE_NAME(); \ - } - -PENNYLANE_TEST_STATIC_DISPATCH(PauliX) -PENNYLANE_TEST_STATIC_DISPATCH(PauliY) -PENNYLANE_TEST_STATIC_DISPATCH(PauliZ) -PENNYLANE_TEST_STATIC_DISPATCH(Hadamard) -PENNYLANE_TEST_STATIC_DISPATCH(S) -PENNYLANE_TEST_STATIC_DISPATCH(T) -PENNYLANE_TEST_STATIC_DISPATCH(PhaseShift) -PENNYLANE_TEST_STATIC_DISPATCH(RX) -PENNYLANE_TEST_STATIC_DISPATCH(RY) -PENNYLANE_TEST_STATIC_DISPATCH(RZ) -PENNYLANE_TEST_STATIC_DISPATCH(Rot) -PENNYLANE_TEST_STATIC_DISPATCH(CNOT) -PENNYLANE_TEST_STATIC_DISPATCH(CZ) -PENNYLANE_TEST_STATIC_DISPATCH(SWAP) -PENNYLANE_TEST_STATIC_DISPATCH(ControlledPhaseShift) -PENNYLANE_TEST_STATIC_DISPATCH(CRX) -PENNYLANE_TEST_STATIC_DISPATCH(CRY) -PENNYLANE_TEST_STATIC_DISPATCH(CRZ) -PENNYLANE_TEST_STATIC_DISPATCH(CRot) -PENNYLANE_TEST_STATIC_DISPATCH(Toffoli) -PENNYLANE_TEST_STATIC_DISPATCH(CSWAP) -PENNYLANE_TEST_STATIC_DISPATCH(GeneratorPhaseShift) -PENNYLANE_TEST_STATIC_DISPATCH(GeneratorCRX) -PENNYLANE_TEST_STATIC_DISPATCH(GeneratorCRY) -PENNYLANE_TEST_STATIC_DISPATCH(GeneratorCRZ) -PENNYLANE_TEST_STATIC_DISPATCH(GeneratorControlledPhaseShift) diff --git a/pennylane_lightning/src/tests/Test_Util.cpp b/pennylane_lightning/src/tests/Test_Util.cpp index c2de38e057..a4cc61a429 100644 --- a/pennylane_lightning/src/tests/Test_Util.cpp +++ b/pennylane_lightning/src/tests/Test_Util.cpp @@ -157,9 +157,8 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, Util::matrixVecProd(mat, v_in, m, m); CAPTURE(v_out); CAPTURE(v_expected); - for (size_t i = 0; i < m; i++) { - CHECK(isApproxEqual(v_out[i], v_expected[i])); - } + + CHECK(v_out == PLApprox(v_expected).margin(1e-7)); } } SECTION("Random Complex") { @@ -184,9 +183,8 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, std::vector> v_out = Util::matrixVecProd(mat, v_in, 4, 4); CAPTURE(v_out); - for (size_t i = 0; i < 4; i++) { - CHECK(isApproxEqual(v_out[i], v_out[i])); - } + + CHECK(v_out == PLApprox(v_expected).margin(1e-7)); } SECTION("Invalid Arguments") { using namespace Catch::Matchers; @@ -212,11 +210,11 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, std::vector v_expected(m, m); std::vector v_out = Util::vecMatrixProd(v_in, mat, m, m); + CAPTURE(v_out); CAPTURE(v_expected); - for (size_t i = 0; i < m; i++) { - CHECK(v_out[i] == v_expected[i]); - } + + CHECK(v_out == PLApprox(v_expected).margin(1e-7)); } } SECTION("Zero Vector") { @@ -226,11 +224,11 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, std::vector v_expected(m, 0); std::vector v_out = Util::vecMatrixProd(v_in, mat, m, m); + CAPTURE(v_out); CAPTURE(v_expected); - for (size_t i = 0; i < m; i++) { - CHECK(v_out[i] == v_expected[i]); - } + + CHECK(v_out == PLApprox(v_expected).margin(1e-7)); } } SECTION("Random Matrix") { @@ -239,11 +237,11 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, 0.4, -0.7, 1.2, -0.5, -0.6, 0.7}; std::vector v_expected{0.6, -3.2, 6.8}; std::vector v_out = Util::vecMatrixProd(v_in, mat, 4, 3); + CAPTURE(v_out); CAPTURE(v_expected); - for (size_t i = 0; i < 3; i++) { - CHECK(std::abs(v_out[i] - v_expected[i]) < 0.000001); - } + + CHECK(v_out == PLApprox(v_expected).margin(1e-7)); } } SECTION("Transpose") { @@ -255,11 +253,11 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, } std::vector> mat_t = Util::Transpose(mat, m, m); + CAPTURE(mat_t); CAPTURE(mat); - for (size_t i = 0; i < m * m; i++) { - CHECK(isApproxEqual(mat[i], mat_t[i])); - } + + CHECK(mat_t == PLApprox(mat).margin(1e-7)); } } SECTION("Random Complex") { @@ -283,11 +281,11 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, {0.868736, 0.760685}, {0.659472, 0.232696}}; std::vector> mat_t = Util::Transpose(mat, 4, 4); + CAPTURE(mat_t); CAPTURE(mat_t_exp); - for (size_t i = 0; i < 16; i++) { - CHECK(isApproxEqual(mat_t[i], mat_t_exp[i])); - } + + CHECK(mat_t == PLApprox(mat_t_exp)); } SECTION("Invalid Arguments") { using namespace Catch::Matchers; @@ -306,13 +304,13 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, std::vector> m_right(m * m, {1, 1}); std::vector> m_out_exp( m * m, {0, static_cast(2 * m)}); - std::vector> m_out = - Util::matrixMatProd(m_left, m_right, m, m, m, true); + std::vector> m_out = Util::matrixMatProd( + m_left, m_right, m, m, m, Trans::Transpose); + CAPTURE(m_out); CAPTURE(m_out_exp); - for (size_t i = 0; i < m * m; i++) { - CHECK(isApproxEqual(m_out[i], m_out_exp[i])); - } + + CHECK(m_out == PLApprox(m_out_exp)); } } SECTION("Random Complex") { @@ -360,19 +358,17 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, {0.845207296911400, 1.843583823364000}, {-0.482010055957000, 2.062995137499000}, {-0.524094900662100, 1.815727577737900}}; - std::vector> m_out_1 = - Util::matrixMatProd(m_left, m_right_tp, 4, 4, 4, true); - std::vector> m_out_2 = - Util::matrixMatProd(m_left, m_right, 4, 4, 4, false); + std::vector> m_out_1 = Util::matrixMatProd( + m_left, m_right_tp, 4, 4, 4, Trans::Transpose); + std::vector> m_out_2 = Util::matrixMatProd( + m_left, m_right, 4, 4, 4, Trans::NoTranspose); + CAPTURE(m_out_1); CAPTURE(m_out_2); CAPTURE(m_out_exp); - for (size_t i = 0; i < 16; i++) { - CHECK(isApproxEqual(m_out_1[i], m_out_2[i])); - } - for (size_t i = 0; i < 16; i++) { - CHECK(isApproxEqual(m_out_1[i], m_out_exp[i])); - } + + CHECK(m_out_1 == PLApprox(m_out_2)); + CHECK(m_out_1 == PLApprox(m_out_exp)); } SECTION("Random complex non-square") { const size_t m = 4; @@ -445,9 +441,7 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util][LinearAlgebra]", float, const auto m_out = Util::matrixMatProd(mat1, mat2, m, n, k); - for (size_t i = 0; i < (m * n); i++) { - CHECK(isApproxEqual(m_out[i], expected[i])); - } + CHECK(m_out == PLApprox(expected)); } SECTION("Invalid Arguments") { using namespace Catch::Matchers; @@ -574,3 +568,28 @@ TEST_CASE("Utility bit operations", "[Util][BitUtil]") { } } } + +TEST_CASE("Utility array and tuples", "[Util]") { + std::array, 5> test_pairs{ + std::pair(0, "Zero"), std::pair(1, "One"), std::pair(2, "Two"), + std::pair(3, "Three"), std::pair(4, "Four"), + }; + + REQUIRE(Util::reverse_pairs(test_pairs) == + std::array{ + std::pair("Zero", 0), + std::pair("One", 1), + std::pair("Two", 2), + std::pair("Three", 3), + std::pair("Four", 4), + }); + + REQUIRE(Util::reverse_pairs(test_pairs) != + std::array{ + std::pair("Zero", 0), + std::pair("One", 1), + std::pair("Two", 0), + std::pair("Three", 3), + std::pair("Four", 4), + }); +} diff --git a/pennylane_lightning/src/tests/compile_time_tests.cpp b/pennylane_lightning/src/tests/compile_time_tests.cpp new file mode 100644 index 0000000000..338cf6091d --- /dev/null +++ b/pennylane_lightning/src/tests/compile_time_tests.cpp @@ -0,0 +1,9 @@ +#include "TestAvailableKernels.hpp" +#include "TestConstant.hpp" + +/** + * This is a dummy file for some compile time tests. + * See included files to see checks. + */ + +int main() { return 0; } diff --git a/pennylane_lightning/src/util/BitUtil.hpp b/pennylane_lightning/src/util/BitUtil.hpp index e98575a4d0..8b7251ddc3 100644 --- a/pennylane_lightning/src/util/BitUtil.hpp +++ b/pennylane_lightning/src/util/BitUtil.hpp @@ -16,6 +16,7 @@ * Contains uncategorised utility functions. */ #pragma once +#include #include #include @@ -147,12 +148,12 @@ inline auto popcount(unsigned long val) -> size_t { ///@} /** - * @brief Faster log2 when the value is the perferct power of 2. + * @brief Faster log2 when the value is the perfect power of 2. * * If the value is the perfect power of 2, using a system provided bit operation * is much faster than std::log2 * - * TODO: change to std::countr_zero in C++20 + * TODO: change to std::count_zero in C++20 */ ///@{ #if defined(_MSC_VER) @@ -179,5 +180,24 @@ inline auto log2PerfectPower(unsigned long val) -> size_t { inline auto isPerfectPowerOf2(size_t value) -> bool { return popcount(value) == 1; } +/** + * @brief Fill ones from LSB to rev_wire + */ +inline auto constexpr fillTrailingOnes(size_t pos) -> size_t { + return (pos == 0) ? 0 : (~size_t(0) >> (CHAR_BIT * sizeof(size_t) - pos)); +} +/** + * @brief Fill ones from MSB to pos + */ +inline auto constexpr fillLeadingOnes(size_t pos) -> size_t { + return (~size_t(0)) << pos; +} +/** + * @brief Swap bits in i-th and j-th position in place + */ +inline void constexpr bitswap(size_t bits, const size_t i, const size_t j) { + size_t x = ((bits >> i) ^ (bits >> j)) & 1U; + bits ^= ((x << i) | (x << j)); +} } // namespace Pennylane::Util diff --git a/pennylane_lightning/src/util/ConstantUtil.hpp b/pennylane_lightning/src/util/ConstantUtil.hpp index 5d26d8d57a..532b49ee01 100644 --- a/pennylane_lightning/src/util/ConstantUtil.hpp +++ b/pennylane_lightning/src/util/ConstantUtil.hpp @@ -21,6 +21,8 @@ #include #include +#include "Util.hpp" + namespace Pennylane::Util { /** * @brief Lookup key in array of pairs. For a constexpr map-like behavior. @@ -146,7 +148,7 @@ prepend_to_tuple_helper(T &&elt, Tuple &&t, /// @endcond /** - * @brief Prepent an element to a tuple + * @brief Prepend an element to a tuple * @tparam T Type of element * @tparam Tuple Type of the tuple (usually std::tuple) * @@ -171,9 +173,38 @@ constexpr auto prepend_to_tuple(T &&elt, Tuple &&t) { * @tparam Tuple Type of the tuple. * @param tuple Tuple to transform */ -template constexpr auto tuple_to_array(Tuple &&tuple) { +template constexpr auto tuple_to_array(Tuple &&tuple) { + using T = std::tuple_element_t<0, remove_cvref_t>; return std::apply( [](auto... n) { return std::array{n...}; }, std::forward(tuple)); } + +/// @cond DEV +namespace Internal { +/** + * @brief Helper function for prepend_to_tuple + */ +template +constexpr auto +reverse_pairs_helper(const std::array, size> &arr, + [[maybe_unused]] std::index_sequence dummy) { + return std::array{std::pair{arr[I].second, arr[I].first}...}; +} +} // namespace Internal +/// @endcond + +/** + * @brief Swap positions of elements in each pair + * + * @tparam T Type of first elements + * @tparam U Type of second elements + * @tparam size Size of the array + */ +template +constexpr auto reverse_pairs(const std::array, size> &arr) + -> std::array, size> { + return Internal::reverse_pairs_helper(arr, + std::make_index_sequence{}); +} } // namespace Pennylane::Util diff --git a/pennylane_lightning/src/util/Error.hpp b/pennylane_lightning/src/util/Error.hpp index 0550a9243d..f405d43196 100644 --- a/pennylane_lightning/src/util/Error.hpp +++ b/pennylane_lightning/src/util/Error.hpp @@ -75,10 +75,15 @@ class LightningException : public std::exception { * * @param err_msg Error message explaining the exception condition. */ - explicit LightningException(std::string err_msg) noexcept : err_msg{std::move(err_msg)} {} + LightningException(const LightningException &) = default; + LightningException(LightningException &&) noexcept = default; + + auto operator=(const LightningException &) -> LightningException & = delete; + auto operator=(LightningException &&) -> LightningException & = delete; + /** * @brief Destroys the `%LightningException` object. */ diff --git a/pennylane_lightning/src/util/LinearAlgebra.hpp b/pennylane_lightning/src/util/LinearAlgebra.hpp index 6cd1d87c39..40ea4292a8 100644 --- a/pennylane_lightning/src/util/LinearAlgebra.hpp +++ b/pennylane_lightning/src/util/LinearAlgebra.hpp @@ -46,6 +46,13 @@ using CBLAS_LAYOUT = enum CBLAS_LAYOUT { #endif #endif /// @endcond +// + +enum class Trans : int { + NoTranspose = CblasNoTrans, + Transpose = CblasTrans, + Adjoint = CblasConjTrans +}; namespace Pennylane::Util { /** @@ -124,7 +131,7 @@ inline auto innerProd(const std::complex *v1, const std::complex *v2, /** * @brief Calculates the inner-product using OpenMP. - * with the the first dataset conjugated. + * with the first dataset conjugated. * * @tparam T Floating point precision type. * @tparam NTERMS Number of terms proceeds by each thread @@ -233,37 +240,55 @@ inline auto innerProdC(const std::vector> &v1, * @param v_out Pre-allocated complex data array to store the result. * @param m Number of rows of `mat`. * @param n Number of columns of `mat`. - * @param transpose If `true`, considers transposed version of `mat`. + * @param transpose Whether use a transposed version of `m_right`. * row-wise. */ template -inline static void omp_matrixVecProd(const std::complex *mat, - const std::complex *v_in, - std::complex *v_out, size_t m, size_t n, - bool transpose = false) { +inline static void +omp_matrixVecProd(const std::complex *mat, const std::complex *v_in, + std::complex *v_out, size_t m, size_t n, Trans transpose) { if (!v_out) { return; } - if (transpose) { + size_t row; + size_t col; + + { + switch (transpose) { + case Trans::NoTranspose: #if defined(_OPENMP) -#pragma omp parallel for default(none) shared(mat, v_in, v_out) \ - firstprivate(m, n) +#pragma omp parallel for default(none) private(row, col) firstprivate(m, n) \ + shared(v_out, mat, v_in) #endif - for (size_t row = 0; row < m; row++) { - for (size_t col = 0; col < n; col++) { - v_out[row] += mat[col * m + row] * v_in[col]; + for (row = 0; row < m; row++) { + for (col = 0; col < n; col++) { + v_out[row] += mat[row * n + col] * v_in[col]; + } } - } - } else { + break; + case Trans::Transpose: #if defined(_OPENMP) -#pragma omp parallel for default(none) shared(mat, v_in, v_out) \ - firstprivate(m, n) +#pragma omp parallel for default(none) private(row, col) firstprivate(m, n) \ + shared(v_out, mat, v_in) #endif - for (size_t row = 0; row < m; row++) { - for (size_t col = 0; col < n; col++) { - v_out[row] += mat[row * n + col] * v_in[col]; + for (row = 0; row < m; row++) { + for (col = 0; col < n; col++) { + v_out[row] += mat[col * m + row] * v_in[col]; + } + } + break; + case Trans::Adjoint: +#if defined(_OPENMP) +#pragma omp parallel for default(none) private(row, col) firstprivate(m, n) \ + shared(v_out, mat, v_in) +#endif + for (row = 0; row < m; row++) { + for (col = 0; col < n; col++) { + v_out[row] += std::conj(mat[col * m + row]) * v_in[col]; + } } + break; } } } @@ -277,12 +302,13 @@ inline static void omp_matrixVecProd(const std::complex *mat, * @param v_out Pre-allocated complex data array to store the result. * @param m Number of rows of `mat`. * @param n Number of columns of `mat`. - * @param transpose If `true`, considers transposed version of `mat`. + * @param transpose Whether use a transposed version of `m_right`. */ template inline void matrixVecProd(const std::complex *mat, const std::complex *v_in, std::complex *v_out, - size_t m, size_t n, bool transpose = false) { + size_t m, size_t n, + Trans transpose = Trans::NoTranspose) { if (!v_out) { return; } @@ -290,7 +316,7 @@ inline void matrixVecProd(const std::complex *mat, if constexpr (USE_CBLAS) { constexpr std::complex co{1, 0}; constexpr std::complex cz{0, 0}; - const auto tr = (transpose) ? CblasTrans : CblasNoTrans; + const auto tr = static_cast(transpose); if constexpr (std::is_same_v) { cblas_cgemv(CblasRowMajor, tr, m, n, &co, mat, m, v_in, 1, &cz, v_out, 1); @@ -313,7 +339,7 @@ inline void matrixVecProd(const std::complex *mat, template inline auto matrixVecProd(const std::vector> mat, const std::vector> v_in, size_t m, - size_t n, bool transpose = false) + size_t n, Trans transpose = Trans::NoTranspose) -> std::vector> { if (mat.size() != m * n) { throw std::invalid_argument( @@ -333,7 +359,7 @@ inline auto matrixVecProd(const std::vector> mat, * using blocking and Cache-optimized techniques. * * @tparam T Floating point precision type. - * @tparam BLOCKSIZE Size of submatrices in the blocking techinque. + * @tparam BLOCKSIZE Size of submatrices in the blocking technique. * @param mat Data array repr. a flatten (row-wise) matrix m * n. * @param mat_t Pre-allocated data array to store the transpose of `mat`. * @param m Number of rows of `mat`. @@ -436,7 +462,7 @@ inline static void CFTranspose(const std::complex *mat, * @return mat transpose of shape n * m. */ template -inline auto Transpose(const std::vector> mat, size_t m, +inline auto Transpose(const std::vector> &mat, size_t m, size_t n) -> std::vector> { if (mat.size() != m * n) { throw std::invalid_argument( @@ -459,7 +485,7 @@ inline auto Transpose(const std::vector> mat, size_t m, * @return mat transpose of shape n * m. */ template -inline auto Transpose(const std::vector mat, size_t m, size_t n) +inline auto Transpose(const std::vector &mat, size_t m, size_t n) -> std::vector { if (mat.size() != m * n) { throw std::invalid_argument( @@ -517,7 +543,7 @@ inline void vecMatrixProd(const T *v_in, const T *mat, T *v_out, size_t m, } /** - * @brief Calculates the vactor-matrix product using the best available method. + * @brief Calculates the vector-matrix product using the best available method. * * @see inline void vecMatrixProd(const T *v_in, * const T *mat, T *v_out, size_t m, size_t n) @@ -540,7 +566,7 @@ inline auto vecMatrixProd(const std::vector &v_in, const std::vector &mat, } /** - * @brief Calculates the vactor-matrix product using the best available method. + * @brief Calculates the vector-matrix product using the best available method. * * @see inline void vecMatrixProd(const T *v_in, const T *mat, T *v_out, size_t * m, size_t n) @@ -573,20 +599,24 @@ inline void vecMatrixProd(std::vector &v_out, const std::vector &v_in, * @param m Number of rows of `m_left`. * @param n Number of columns of `m_right`. * @param k Number of rows of `m_right`. - * @param transpose If `true`, requires transposed version of `m_right`. + * @param transpose Whether use a transposed version of `m_right`. * * @note Consider transpose=true, to get a better performance. * To transpose a matrix efficiently, check Util::Transpose */ -template // NOLINT(readability-magic-numbers) + +template +// NOLINTNEXTLINE(readability-function-cognitive-complexity) inline void omp_matrixMatProd(const std::complex *m_left, const std::complex *m_right, std::complex *m_out, size_t m, size_t n, - size_t k, bool transpose = false) { + size_t k, Trans transpose) { if (!m_out) { return; } - if (transpose) { + + switch (transpose) { + case Trans::Transpose: #if defined(_OPENMP) #pragma omp parallel for default(none) shared(m_left, m_right, m_out) \ firstprivate(m, n, k) @@ -599,7 +629,22 @@ inline void omp_matrixMatProd(const std::complex *m_left, } } } - } else { + break; + case Trans::Adjoint: +#if defined(_OPENMP) +#pragma omp parallel for default(none) shared(m_left, m_right, m_out) \ + firstprivate(m, n, k) +#endif + for (size_t row = 0; row < m; row++) { + for (size_t col = 0; col < n; col++) { + for (size_t blk = 0; blk < k; blk++) { + m_out[row * n + col] += m_left[row * k + blk] * + std::conj(m_right[col * n + blk]); + } + } + } + break; + case Trans::NoTranspose: #if defined(_OPENMP) #pragma omp parallel for default(none) shared(m_left, m_right, m_out) \ firstprivate(m, n, k) @@ -624,11 +669,12 @@ inline void omp_matrixMatProd(const std::complex *m_left, } } } + break; } } /** - * @brief Calculates matrix-matrix product using the best avaiable method. + * @brief Calculates matrix-matrix product using the best available method. * * @tparam T Floating point precision type. * @param m_left Row-wise flatten matrix of shape m * k. @@ -637,7 +683,7 @@ inline void omp_matrixMatProd(const std::complex *m_left, * @param m Number of rows of `m_left`. * @param n Number of columns of `m_right`. * @param k Number of rows of `m_right`. - * @param transpose If `true`, requires transposed version of `m_right`. + * @param transpose Whether use a transposed version of `m_right`. * * @note Consider transpose=true, to get a better performance. * To transpose a matrix efficiently, check Util::Transpose @@ -646,20 +692,22 @@ template inline void matrixMatProd(const std::complex *m_left, const std::complex *m_right, std::complex *m_out, size_t m, size_t n, size_t k, - bool transpose = false) { + Trans transpose = Trans::NoTranspose) { if (!m_out) { return; } if constexpr (USE_CBLAS) { constexpr std::complex co{1, 0}; constexpr std::complex cz{0, 0}; - const auto tr = (transpose) ? CblasTrans : CblasNoTrans; + const auto tr = static_cast(transpose); if constexpr (std::is_same_v) { cblas_cgemm(CblasRowMajor, CblasNoTrans, tr, m, n, k, &co, m_left, - k, m_right, transpose ? k : n, &cz, m_out, n); + k, m_right, (transpose != Trans::NoTranspose) ? k : n, + &cz, m_out, n); } else if constexpr (std::is_same_v) { cblas_zgemm(CblasRowMajor, CblasNoTrans, tr, m, n, k, &co, m_left, - k, m_right, transpose ? k : n, &cz, m_out, n); + k, m_right, (transpose != Trans::NoTranspose) ? k : n, + &cz, m_out, n); } } else { omp_matrixMatProd(m_left, m_right, m_out, m, n, k, transpose); @@ -679,7 +727,8 @@ inline void matrixMatProd(const std::complex *m_left, template inline auto matrixMatProd(const std::vector> m_left, const std::vector> m_right, size_t m, - size_t n, size_t k, bool transpose = false) + size_t n, size_t k, + Trans transpose = Trans::NoTranspose) -> std::vector> { if (m_left.size() != m * k) { throw std::invalid_argument( diff --git a/pennylane_lightning/src/util/TypeList.hpp b/pennylane_lightning/src/util/TypeList.hpp new file mode 100644 index 0000000000..e288bd80a5 --- /dev/null +++ b/pennylane_lightning/src/util/TypeList.hpp @@ -0,0 +1,56 @@ +// Copyright 2022 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @file TypeList.hpp + * Define type list + */ +#pragma once + +#include +#include + +namespace Pennylane::Util { +template struct TypeNode { + using Type = T; + using Next = TypeNode; +}; + +template struct TypeNode { + using Type = T; + using Next = void; +}; + +/** + * @brief Define type list + */ +template using TypeList = TypeNode; + +template struct getNthType { + static_assert(!std::is_same_v, + "The given n is larger than the length of the typelist."); + using Type = getNthType; +}; + +template struct getNthType { + using Type = typename TypeList::Type; +}; + +template constexpr size_t length() { + if constexpr (std::is_same_v) { + return 0; + } else { + return 1 + length(); + } +} +} // namespace Pennylane::Util diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index 8b51db6577..1efc0b24e8 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -347,4 +347,7 @@ template struct remove_cvref { using type = std::remove_cv_t>; }; +// type alias +template using remove_cvref_t = typename remove_cvref::type; + } // namespace Pennylane::Util diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 6eb78ec866..210800e236 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -632,12 +632,12 @@ def circuit_ansatz(params, wires): qml.PhaseShift(params[6], wires=wires[0]).inv() qml.Rot(params[6], params[7], params[8], wires=wires[0]) # # qml.Rot(params[8], params[8], params[9], wires=wires[1]).inv() - # qml.MultiRZ(params[11], wires=[wires[0], wires[1]]) + qml.MultiRZ(params[11], wires=[wires[0], wires[1]]) # # qml.PauliRot(params[12], "XXYZ", wires=[wires[0], wires[1], wires[2], wires[3]]) qml.CPhase(params[12], wires=[wires[3], wires[2]]) - # qml.IsingXX(params[13], wires=[wires[1], wires[0]]) - # qml.IsingYY(params[14], wires=[wires[3], wires[2]]) - # qml.IsingZZ(params[14], wires=[wires[2], wires[1]]) + qml.IsingXX(params[13], wires=[wires[1], wires[0]]) + qml.IsingYY(params[14], wires=[wires[3], wires[2]]) + qml.IsingZZ(params[14], wires=[wires[2], wires[1]]) qml.U1(params[15], wires=wires[0]) qml.U2(params[16], params[17], wires=wires[0]) qml.U3(params[18], params[19], params[20], wires=wires[1]) diff --git a/tests/test_apply.py b/tests/test_apply.py index 5d456872fe..b982e8c283 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -254,6 +254,14 @@ def test_apply_operation_state_preparation( [1 / 2 - 1j / 2, 1 / 2 + 1j / 2], [math.pi / 2], ), + (qml.MultiRZ, [1, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0], [math.pi / 2]), + (qml.MultiRZ, [0, 1], [0, 1j], [math.pi]), + ( + qml.MultiRZ, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 - 1j / 2, 1 / 2 + 1j / 2], + [math.pi / 2], + ), (qml.Rot, [1, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0], [math.pi / 2, 0, 0]), (qml.Rot, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)], [0, math.pi / 2, 0]), ( @@ -294,6 +302,34 @@ def test_apply_operation_single_wire_with_parameters( """ operation,input,expected_output,par """ test_data_two_wires_with_parameters = [ + (qml.IsingXX, [1, 0, 0, 0], [1 / math.sqrt(2), 0, 0, -1j / math.sqrt(2)], [math.pi / 2]), + ( + qml.IsingXX, + [0, 1 / math.sqrt(2), 0, 1 / math.sqrt(2)], + [-0.5j, 0.5, -0.5j, 0.5], + [math.pi / 2], + ), + (qml.IsingYY, [1, 0, 0, 0], [1 / math.sqrt(2), 0, 0, 1j / math.sqrt(2)], [math.pi / 2]), + ( + qml.IsingYY, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 + 0.5j, 0, 0, 0.5 + 0.5j], + [math.pi / 2], + ), + (qml.IsingZZ, [1, 0, 0, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0, 0, 0], [math.pi / 2]), + ( + qml.IsingZZ, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 - 0.5j, 0, 0, 0.5 - 0.5j], + [math.pi / 2], + ), + (qml.MultiRZ, [1, 0, 0, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0, 0, 0], [math.pi / 2]), + ( + qml.MultiRZ, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 - 0.5j, 0, 0, 0.5 - 0.5j], + [math.pi / 2], + ), (qml.CRX, [0, 1, 0, 0], [0, 1, 0, 0], [math.pi / 2]), (qml.CRX, [0, 0, 0, 1], [0, 0, -1j, 0], [math.pi]), ( @@ -620,11 +656,6 @@ def test_load_default_qubit_device_with_valid_kernel(self): @pytest.mark.skipif(not lq._CPP_BINARY_AVAILABLE, reason="Lightning binary required") def test_load_default_qubit_device_with_invalid_kernel(self): """Test that lightning.qubit raises error for unsupported gate/kernel pair.""" - # This line is only for current implementation - with pytest.raises( - ValueError, match=f"The given kernel LM does not implement Matrix gate." - ): - dev = qml.device("lightning.qubit", kernel_for_ops={"Matrix": "LM"}, wires=2) for gate in ["PauliX", "CRot", "CSWAP", "Matrix"]: with pytest.raises(