From 5448c8aaa47afada2ddbc379d65d1718bdd7352f Mon Sep 17 00:00:00 2001 From: Shuli Shu <31480676+multiphaseCFD@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:31:40 -0500 Subject: [PATCH] Add exact tensor network C++ backend to LT (#977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Before submitting Please complete the following checklist when submitting a PR: - [ ] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [ ] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [ ] Ensure that the test suite passes, by running `make test`. - [x] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [x] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** [sc-72879] **Description of the Change:** **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** --------- Co-authored-by: ringo-but-quantum Co-authored-by: Luis Alfredo Nuñez Meneses Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .github/CHANGELOG.md | 10 +- CMakeLists.txt | 3 +- pennylane_lightning/core/_version.py | 2 +- .../lightning_tensor/tncuda/CMakeLists.txt | 2 +- .../lightning_tensor/tncuda/ExactTNCuda.cpp | 19 + .../lightning_tensor/tncuda/ExactTNCuda.hpp | 70 + .../lightning_tensor/tncuda/MPSTNCuda.hpp | 324 +--- .../tncuda/{TNCudaBase.hpp => TNCuda.hpp} | 594 +++++-- .../tncuda/base/TNCudaBase.hpp | 168 ++ .../tncuda/base/TensornetBase.hpp | 76 - .../tncuda/base/tests/CMakeLists.txt | 22 +- ...tensornetBase.cpp => Tests_TNCudaBase.cpp} | 8 +- ...=> runner_lightning_tensor_TNCudaBase.cpp} | 0 .../tncuda/bindings/LTensorTNCudaBindings.hpp | 4 +- .../tncuda/gates/tests/CMakeLists.txt | 13 +- .../gates/tests/Test_MPSTNCuda_NonParam.cpp | 713 -------- .../gates/tests/Test_MPSTNCuda_Param.cpp | 1161 ------------- .../tncuda/gates/tests/Test_TNCuda_MPO.cpp | 231 +++ .../gates/tests/Test_TNCuda_NonParam.cpp | 689 ++++++++ .../tncuda/gates/tests/Test_TNCuda_Param.cpp | 1451 +++++++++++++++++ .../tncuda/measurements/tests/CMakeLists.txt | 20 +- ...Cuda_Expval.cpp => Test_TNCuda_Expval.cpp} | 279 ++-- ...da_Measure.cpp => Test_TNCuda_Measure.cpp} | 81 +- ..._MPSTNCuda_Var.cpp => Test_TNCuda_Var.cpp} | 185 ++- .../tncuda/observables/ObservablesTNCuda.cpp | 16 + .../observables/ObservablesTNCudaOperator.cpp | 3 + .../tncuda/tests/Tests_MPSTNCuda.cpp | 12 +- .../tncuda/utils/CMakeLists.txt | 5 +- .../tncuda/utils/tn/CMakeLists.txt | 9 + .../tncuda/utils/tn/TestHelpersTNCuda.hpp | 72 + 30 files changed, 3516 insertions(+), 2726 deletions(-) create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.hpp rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/{TNCudaBase.hpp => TNCuda.hpp} (53%) create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TNCudaBase.hpp delete mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TensornetBase.hpp rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/{Tests_tensornetBase.cpp => Tests_TNCudaBase.cpp} (87%) rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/{runner_lightning_tensor_tensornetBase.cpp => runner_lightning_tensor_TNCudaBase.cpp} (100%) delete mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_NonParam.cpp delete mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_Param.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_MPO.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_NonParam.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_Param.cpp rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/{Test_MPSTNCuda_Expval.cpp => Test_TNCuda_Expval.cpp} (52%) rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/{Test_MPSTNCuda_Measure.cpp => Test_TNCuda_Measure.cpp} (63%) rename pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/{Test_MPSTNCuda_Var.cpp => Test_TNCuda_Var.cpp} (61%) create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/CMakeLists.txt create mode 100644 pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/TestHelpersTNCuda.hpp diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index c09d84436e..0159bcd875 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,8 +2,8 @@ ### New features since last release -* Add native N-controlled gate/matrix operations and adjoint support to `lightning.kokkos`. - [(#950)](https://github.com/PennyLaneAI/pennylane-lightning/pull/950) +* Add Exact Tensor Network C++ backend to `lightning.tensor`. + [(#977)](https://github.com/PennyLaneAI/pennylane-lightning/pull/977) * Add native N-controlled generators and adjoint support to `lightning.gpu`'s single-GPU backend. [(#970)](https://github.com/PennyLaneAI/pennylane-lightning/pull/970) @@ -12,6 +12,9 @@ [(#960)](https://github.com/PennyLaneAI/pennylane-lightning/pull/960) [(#999)](https://github.com/PennyLaneAI/pennylane-lightning/pull/999) +* Add native N-controlled gate/matrix operations and adjoint support to `lightning.kokkos`. + [(#950)](https://github.com/PennyLaneAI/pennylane-lightning/pull/950) + * Add native N-controlled gates support to `lightning.gpu`'s single-GPU backend. [(#938)](https://github.com/PennyLaneAI/pennylane-lightning/pull/938) @@ -55,6 +58,9 @@ * Update Kokkos version support to 4.4.1 and enable Lightning-Kokkos[CUDA] C++ tests on CI. [(#1000)](https://github.com/PennyLaneAI/pennylane-lightning/pull/1000) +* Add C++ unit tests for Exact Tensor Network backends. + [(#998)](https://github.com/PennyLaneAI/pennylane-lightning/pull/998) + * Add native BLAS support to the C++ layer via dynamic `scipy-openblas32` loading. [(#995)](https://github.com/PennyLaneAI/pennylane-lightning/pull/995) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1445b01844..6f5da046ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,9 +72,8 @@ endif() foreach(BACKEND ${PL_BACKEND}) message(STATUS "PL_BACKEND: ${BACKEND}") if("${BACKEND}" STREQUAL "lightning_tensor") - set(PL_TENSOR_METHOD "mps" CACHE STRING "PennyLane LightningTensor MPS simulator.") set(PL_TENSOR_BACKEND "cutensornet" CACHE STRING "PennyLane LightningTensor backed by cutensornet") - set(PL_TENSOR "${PL_BACKEND}_${PL_TENSOR_METHOD}_${PL_TENSOR_BACKEND}") + set(PL_TENSOR "${PL_BACKEND}_${PL_TENSOR_BACKEND}") endif() endforeach() diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 8eb04fefb5..c4d8cd5027 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.40.0-dev30" +__version__ = "0.40.0-dev31" diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt index 49630b536f..72d396319f 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt @@ -17,7 +17,7 @@ include("${pennylane_lightning_SOURCE_DIR}/cmake/support_pltensortncuda.cmake") findCUDATK(lightning_external_libs) findCutensornet(lightning_external_libs) -set(LTENSOR_MPS_FILES MPSTNCuda.cpp CACHE INTERNAL "" FORCE) +set(LTENSOR_MPS_FILES MPSTNCuda.cpp ExactTNCuda.cpp MPOTNCuda.cpp CACHE INTERNAL "" FORCE) add_library(${PL_BACKEND} STATIC ${LTENSOR_MPS_FILES}) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.cpp new file mode 100644 index 0000000000..e89a27b7d1 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.cpp @@ -0,0 +1,19 @@ +// Copyright 2024 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 "ExactTNCuda.hpp" + +// explicit instantiation +template class Pennylane::LightningTensor::TNCuda::ExactTNCuda; +template class Pennylane::LightningTensor::TNCuda::ExactTNCuda; diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.hpp new file mode 100644 index 0000000000..0afc1f23a4 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/ExactTNCuda.hpp @@ -0,0 +1,70 @@ +// Copyright 2024 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 ExactTNCuda.hpp + * ExactTN class with cuTensorNet backend. Note that current implementation only + * support the open boundary condition. + */ + +#pragma once + +#include + +#include "DevTag.hpp" +#include "TNCuda.hpp" +#include "TNCudaBase.hpp" +#include "TensorCuda.hpp" +#include "Util.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningGPU; +using namespace Pennylane::LightningTensor::TNCuda; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +/// @endcond + +namespace Pennylane::LightningTensor::TNCuda { + +/** + * @brief Managed memory Exact Tensor Network class using cutensornet high-level + * APIs. + * + * @tparam Precision Floating-point precision type. + */ + +template +class ExactTNCuda final : public TNCuda> { + private: + using BaseType = TNCuda; + + public: + constexpr static auto method = "exacttn"; + + using CFP_t = decltype(cuUtil::getCudaType(Precision{})); + using ComplexT = std::complex; + using PrecisionT = Precision; + + public: + ExactTNCuda() = delete; + + explicit ExactTNCuda(std::size_t numQubits) : BaseType(numQubits) {} + + explicit ExactTNCuda(std::size_t numQubits, DevTag dev_tag) + : BaseType(numQubits, dev_tag) {} + + ~ExactTNCuda() = default; +}; +} // namespace Pennylane::LightningTensor::TNCuda diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp index dd37b3c994..d2c77eeea2 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp @@ -30,9 +30,9 @@ #include "DataBuffer.hpp" #include "DevTag.hpp" #include "MPOTNCuda.hpp" +#include "TNCuda.hpp" #include "TNCudaBase.hpp" #include "TensorCuda.hpp" -#include "TensornetBase.hpp" #include "Util.hpp" #include "cuda_helpers.hpp" #include "tncudaError.hpp" @@ -56,28 +56,19 @@ namespace Pennylane::LightningTensor::TNCuda { * @tparam Precision Floating-point precision type. */ -// TODO check if CRTP is required by the end of project. template -class MPSTNCuda final : public TNCudaBase> { +class MPSTNCuda final : public TNCuda> { private: - using BaseType = TNCudaBase; + using BaseType = TNCuda; MPSStatus MPSInitialized_ = MPSStatus::MPSInitNotSet; - const std::size_t maxBondDim_; - const std::vector bondDims_; - const std::vector> sitesModes_; - const std::vector> sitesExtents_; - const std::vector> sitesExtents_int64_; - - std::vector> tensors_; - - std::vector> tensors_out_; - std::vector>> mpos_; std::vector mpo_ids_; public: + constexpr static auto method = "mps"; + using CFP_t = decltype(cuUtil::getCudaType(Precision{})); using ComplexT = std::complex; using PrecisionT = Precision; @@ -85,31 +76,13 @@ class MPSTNCuda final : public TNCudaBase> { public: MPSTNCuda() = delete; - // TODO: Add method to the constructor to allow users to select methods at - // runtime in the C++ layer explicit MPSTNCuda(const std::size_t numQubits, const std::size_t maxBondDim) - : BaseType(numQubits), maxBondDim_(maxBondDim), - bondDims_(setBondDims_()), sitesModes_(setSitesModes_()), - sitesExtents_(setSitesExtents_()), - sitesExtents_int64_(setSitesExtents_int64_()) { - initTensors_(); - reset(); - appendInitialMPSState_(); - } + : BaseType(numQubits, maxBondDim) {} - // TODO: Add method to the constructor to allow users to select methods at - // runtime in the C++ layer explicit MPSTNCuda(const std::size_t numQubits, const std::size_t maxBondDim, DevTag dev_tag) - : BaseType(numQubits, dev_tag), maxBondDim_(maxBondDim), - bondDims_(setBondDims_()), sitesModes_(setSitesModes_()), - sitesExtents_(setSitesExtents_()), - sitesExtents_int64_(setSitesExtents_int64_()) { - initTensors_(); - reset(); - appendInitialMPSState_(); - } + : BaseType(numQubits, dev_tag, maxBondDim) {} ~MPSTNCuda() = default; @@ -119,123 +92,18 @@ class MPSTNCuda final : public TNCudaBase> { * @return std::size_t */ [[nodiscard]] auto getMaxBondDim() const -> std::size_t { - return maxBondDim_; + return BaseType::maxBondDim_; }; /** - * @brief Get a vector of pointers to extents of each site. - * - * @return std::vector Note int64_t const* is - * required by cutensornet backend. - */ - [[nodiscard]] auto getSitesExtentsPtr() -> std::vector { - std::vector sitesExtentsPtr_int64( - BaseType::getNumQubits()); - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - sitesExtentsPtr_int64[i] = sitesExtents_int64_[i].data(); - } - return sitesExtentsPtr_int64; - } - - /** - * @brief Get a vector of pointers to tensor data of each site. - * - * @return std::vector - */ - [[nodiscard]] auto getTensorsDataPtr() -> std::vector { - std::vector tensorsDataPtr(BaseType::getNumQubits()); - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - tensorsDataPtr[i] = reinterpret_cast( - tensors_[i].getDataBuffer().getData()); - } - return tensorsDataPtr; - } - - /** - * @brief Get a vector of pointers to tensor data of each site. - * - * @return std::vector - */ - [[nodiscard]] auto getTensorsOutDataPtr() -> std::vector { - std::vector tensorsOutDataPtr(BaseType::getNumQubits()); - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - tensorsOutDataPtr[i] = tensors_out_[i].getDataBuffer().getData(); - } - return tensorsOutDataPtr; - } - - /** - * @brief Set current quantum state as zero state. - */ - void reset() { - const std::vector zeroState(BaseType::getNumQubits(), 0); - setBasisState(zeroState); - } - - /** - * @brief Update the ith MPS site data. + * @brief Get the bond dimensions. * - * @param site_idx Index of the MPS site. - * @param host_data Pointer to the data on host. - * @param host_data_size Length of the data. + * @return std::vector */ - void updateMPSSiteData(const std::size_t site_idx, - const ComplexT *host_data, - std::size_t host_data_size) { - PL_ABORT_IF_NOT( - site_idx < BaseType::getNumQubits(), - "The site index should be less than the number of qubits."); - - const std::size_t idx = BaseType::getNumQubits() - site_idx - 1; - PL_ABORT_IF_NOT( - host_data_size == tensors_[idx].getDataBuffer().getLength(), - "The length of the host data should match its copy on the device."); - - tensors_[idx].getDataBuffer().zeroInit(); - - tensors_[idx].getDataBuffer().CopyHostDataToGpu(host_data, - host_data_size); + [[nodiscard]] auto getBondDims(std::size_t idx) const -> std::size_t { + return BaseType::bondDims_[idx]; } - /** - * @brief Update quantum state with a basis state. - * NOTE: This API assumes the bond vector is a standard basis vector - * ([1,0,0,......]) and current implementation only works for qubit systems. - * @param basisState Vector representation of a basis state. - */ - void setBasisState(const std::vector &basisState) { - PL_ABORT_IF(BaseType::getNumQubits() != basisState.size(), - "The size of a basis state should be equal to the number " - "of qubits."); - - bool allZeroOrOne = std::all_of( - basisState.begin(), basisState.end(), - [](std::size_t bitVal) { return bitVal == 0 || bitVal == 1; }); - - PL_ABORT_IF_NOT(allZeroOrOne, - "Please ensure all elements of a basis state should be " - "either 0 or 1."); - - CFP_t value_cu = cuUtil::complexToCu(ComplexT{1.0, 0.0}); - - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - tensors_[i].getDataBuffer().zeroInit(); - std::size_t target = 0; - std::size_t idx = BaseType::getNumQubits() - std::size_t{1} - i; - - // Rightmost site - if (i == 0) { - target = basisState[idx]; - } else { - target = basisState[idx] == 0 ? 0 : bondDims_[i - 1]; - } - - PL_CUDA_IS_SUCCESS( - cudaMemcpy(&tensors_[i].getDataBuffer().getData()[target], - &value_cu, sizeof(CFP_t), cudaMemcpyHostToDevice)); - } - }; - /** * @brief Apply an MPO operator with the gate's MPO decomposition data * provided by the user to the compute graph. @@ -321,7 +189,7 @@ class MPSTNCuda final : public TNCudaBase> { /* cutensornetBoundaryCondition_t */ CUTENSORNET_BOUNDARY_CONDITION_OPEN, /* const int64_t *const extentsOut[] */ - getSitesExtentsPtr().data(), + BaseType::getSitesExtentsPtr().data(), /*strides=*/nullptr)); // Optional: SVD @@ -367,8 +235,8 @@ class MPSTNCuda final : public TNCudaBase> { /* std::size_t */ sizeof(mpo_attribute))); BaseType::computeState( - const_cast(getSitesExtentsPtr().data()), - reinterpret_cast(getTensorsOutDataPtr().data())); + const_cast(BaseType::getSitesExtentsPtr().data()), + reinterpret_cast(BaseType::getTensorsOutDataPtr().data())); // TODO: This is a dummy tensor update to allow multiple calls to the // `append_mps_final_state` method as well as appending additional @@ -385,167 +253,5 @@ class MPSTNCuda final : public TNCudaBase> { // BaseType::dummy_tensor_update(); } - - /** - * @brief Get the full state vector representation of a MPS quantum state. - * Note that users/developers should be responsible to ensure that there is - * sufficient memory on the host to store the full state vector. - * - * @param res Pointer to the host memory to store the full state vector - * @param res_length Length of the result vector - */ - void getData(ComplexT *res, const std::size_t res_length) { - PL_ABORT_IF(log2(res_length) != BaseType::getNumQubits(), - "The size of the result vector should be equal to the " - "dimension of the quantum state."); - - std::size_t avail_gpu_memory = getFreeMemorySize(); - - PL_ABORT_IF(log2(avail_gpu_memory) < BaseType::getNumQubits(), - "State tensor size exceeds the available GPU memory!"); - BaseType::get_state_tensor(res); - } - - /** - * @brief Get the full state vector representation of a MPS quantum state. - * - * - * @return std::vector Full state vector representation of MPS - * quantum state on host - */ - auto getDataVector() -> std::vector { - std::size_t length = std::size_t{1} << BaseType::getNumQubits(); - std::vector results(length); - - getData(results.data(), results.size()); - - return results; - } - - private: - /** - * @brief Return bondDims to the member initializer - * NOTE: This method only works for the open boundary condition - * @return std::vector - */ - std::vector setBondDims_() { - std::vector localBondDims(BaseType::getNumQubits() - 1, - maxBondDim_); - - const std::size_t ubDim = log2(maxBondDim_); - for (std::size_t i = 0; i < localBondDims.size(); i++) { - const std::size_t bondDim = - std::min(i + 1, BaseType::getNumQubits() - i - 1); - - if (bondDim <= ubDim) { - localBondDims[i] = std::size_t{1} << bondDim; - } - } - return localBondDims; - } - - /** - * @brief Return siteModes to the member initializer - * NOTE: This method only works for the open boundary condition - * @return std::vector> - */ - std::vector> setSitesModes_() { - std::vector> localSitesModes; - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - std::vector localSiteModes; - if (i == 0) { - // Leftmost site (state mode, shared mode) - localSiteModes = - std::vector({i, i + BaseType::getNumQubits()}); - } else if (i == BaseType::getNumQubits() - 1) { - // Rightmost site (shared mode, state mode) - localSiteModes = std::vector( - {i + BaseType::getNumQubits() - 1, i}); - } else { - // Interior sites (state mode, state mode, shared mode) - localSiteModes = - std::vector({i + BaseType::getNumQubits() - 1, - i, i + BaseType::getNumQubits()}); - } - localSitesModes.push_back(std::move(localSiteModes)); - } - return localSitesModes; - } - - /** - * @brief Return sitesExtents to the member initializer - * NOTE: This method only works for the open boundary condition - * @return std::vector> - */ - std::vector> setSitesExtents_() { - std::vector> localSitesExtents; - - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - std::vector localSiteExtents; - if (i == 0) { - // Leftmost site (state mode, shared mode) - localSiteExtents = std::vector( - {BaseType::getQubitDims()[i], bondDims_[i]}); - } else if (i == BaseType::getNumQubits() - 1) { - // Rightmost site (shared mode, state mode) - localSiteExtents = std::vector( - {bondDims_[i - 1], BaseType::getQubitDims()[i]}); - } else { - // Interior sites (state mode, state mode, shared mode) - localSiteExtents = std::vector( - {bondDims_[i - 1], BaseType::getQubitDims()[i], - bondDims_[i]}); - } - localSitesExtents.push_back(std::move(localSiteExtents)); - } - return localSitesExtents; - } - - /** - * @brief Return siteExtents_int64 to the member initializer - * NOTE: This method only works for the open boundary condition - * @return std::vector> - */ - std::vector> setSitesExtents_int64_() { - std::vector> localSitesExtents_int64; - - for (const auto &siteExtents : sitesExtents_) { - localSitesExtents_int64.push_back( - std::move(Pennylane::Util::cast_vector( - siteExtents))); - } - return localSitesExtents_int64; - } - - /** - * @brief The tensors init helper function for ctor. - */ - void initTensors_() { - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - // construct mps tensors reprensentation - tensors_.emplace_back(sitesModes_[i].size(), sitesModes_[i], - sitesExtents_[i], BaseType::getDevTag()); - - tensors_out_.emplace_back(sitesModes_[i].size(), sitesModes_[i], - sitesExtents_[i], BaseType::getDevTag()); - } - } - - /** - * @brief Append initial MPS sites to the compute graph with data provided - * by a user - * - */ - void appendInitialMPSState_() { - PL_CUTENSORNET_IS_SUCCESS(cutensornetStateInitializeMPS( - /*const cutensornetHandle_t */ BaseType::getTNCudaHandle(), - /*cutensornetState_t*/ BaseType::getQuantumState(), - /*cutensornetBoundaryCondition_t */ - CUTENSORNET_BOUNDARY_CONDITION_OPEN, - /*const int64_t *const* */ getSitesExtentsPtr().data(), - /*const int64_t *const* */ nullptr, - /*void ** */ - reinterpret_cast(getTensorsDataPtr().data()))); - } }; } // namespace Pennylane::LightningTensor::TNCuda diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCuda.hpp similarity index 53% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCuda.hpp index 2337f7f704..f268af0474 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCuda.hpp @@ -13,8 +13,9 @@ // limitations under the License. /** - * @file TNCudaBase.hpp - * Base class for cuTensorNet-backed tensor networks. + * @file TNCuda.hpp + * Base class for cuTensorNet-backed tensor networks (for common APIs + * of MPS and ExactTN). */ #pragma once @@ -22,20 +23,17 @@ #include #include #include -#include #include #include -#include #include "LinearAlg.hpp" +#include "TNCudaBase.hpp" #include "TNCudaGateCache.hpp" #include "TensorBase.hpp" #include "TensorCuda.hpp" -#include "TensornetBase.hpp" -#include "cuda_helpers.hpp" -#include "tncudaError.hpp" -#include "tncuda_helpers.hpp" + +#include "Util.hpp" /// @cond DEV namespace { @@ -49,144 +47,209 @@ using namespace Pennylane::LightningTensor::TNCuda::Util; namespace Pennylane::LightningTensor::TNCuda { /** - * @brief CRTP-enabled base class for cuTensorNet backends. + * @brief CRTP-enabled base class for cuTensorNet backends. This class stores + * both tensor data and gate data supported by both ExactTN and MPS. * * @tparam PrecisionT Floating point precision. * @tparam Derived Derived class to instantiate using CRTP. */ template -class TNCudaBase : public TensornetBase { +class TNCuda : public TNCudaBase { private: using CFP_t = decltype(cuUtil::getCudaType(PrecisionT{})); using ComplexT = std::complex; - using BaseType = TensornetBase; - SharedTNCudaHandle handle_; + using BaseType = TNCudaBase; + + protected: + // Note both maxBondDim_ and bondDims_ are used for both MPS and Exact + // Tensor Network. Per Exact Tensor Network, maxBondDim_ is 1 and bondDims_ + // is {1}. Per Exact Tensor Network, setting bondDims_ allows call to + // appendInitialMPSState_() to append the initial state to the Exact Tensor + // Network state. + const std::size_t + maxBondDim_; // maxBondDim_ default is 1 for Exact Tensor Network + const std::vector + bondDims_; // bondDims_ default is {1} for Exact Tensor Network + + private: + const std::vector> sitesModes_; + const std::vector> sitesExtents_; + const std::vector> sitesExtents_int64_; + SharedCublasCaller cublascaller_; - cudaDataType_t typeData_; - DevTag dev_tag_; - cutensornetComputeType_t typeCompute_; - cutensornetState_t quantumState_; - cutensornetStatePurity_t purity_ = - CUTENSORNET_STATE_PURITY_PURE; // Only supports pure tensor network - // states as v24.03 std::shared_ptr> gate_cache_; std::set gate_ids_; std::vector identiy_gate_ids_; + std::vector> tensors_; + std::vector> tensors_out_; + public: - TNCudaBase() = delete; + TNCuda() = delete; - // TODO: Add method to the constructor to all user to select methods at - // runtime in the C++ layer - explicit TNCudaBase(const std::size_t numQubits, int device_id = 0, - cudaStream_t stream_id = 0) - : BaseType(numQubits), handle_(make_shared_tncuda_handle()), + explicit TNCuda(std::size_t numQubits, std::size_t maxBondDim = 1) + : BaseType(numQubits), maxBondDim_(maxBondDim), + bondDims_(setBondDims_()), sitesModes_(setSitesModes_()), + sitesExtents_(setSitesExtents_()), + sitesExtents_int64_(setSitesExtents_int64_()), cublascaller_(make_shared_cublas_caller()), - dev_tag_({device_id, stream_id}), - gate_cache_(std::make_shared>(dev_tag_)) { - // TODO this code block could be moved to base class and need to revisit - // when working on copy ctor - PL_ABORT_IF(numQubits < 2, - "The number of qubits should be greater than 1."); - - if constexpr (std::is_same_v) { - typeData_ = CUDA_C_64F; - typeCompute_ = CUTENSORNET_COMPUTE_64F; - } else { - typeData_ = CUDA_C_32F; - typeCompute_ = CUTENSORNET_COMPUTE_32F; - } + gate_cache_(std::make_shared>( + BaseType::getDevTag())) { + initTensors_(); + appendInitialMPSState_( + getSitesExtentsPtr() + .data()); // This API works for Exact Tensor Network as well, + // given sitesExtents_ settings meet the requirement + // of cutensornet. + } - PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateState( - /* const cutensornetHandle_t */ handle_.get(), - /* cutensornetStatePurity_t */ purity_, - /* int32_t numStateModes */ - static_cast(BaseType::getNumQubits()), - /* const int64_t *stateModeExtents */ - reinterpret_cast(BaseType::getQubitDims().data()), - /* cudaDataType_t */ typeData_, - /* cutensornetState_t * */ &quantumState_)); + explicit TNCuda(const std::size_t numQubits, DevTag dev_tag, + const std::size_t maxBondDim = 1) + : BaseType(numQubits, dev_tag.getDeviceID(), dev_tag.getStreamID()), + maxBondDim_(maxBondDim), bondDims_(setBondDims_()), + sitesModes_(setSitesModes_()), sitesExtents_(setSitesExtents_()), + sitesExtents_int64_(setSitesExtents_int64_()), + cublascaller_(make_shared_cublas_caller()), + gate_cache_(std::make_shared>( + BaseType::getDevTag())) { + initTensors_(); + appendInitialMPSState_( + getSitesExtentsPtr() + .data()); // This API works for Exact Tensor Network as well, + // given sitesExtents_ settings meet the requirement + // of cutensornet. } - // TODO: Add method to the constructor to all user to select methods at - // runtime in the C++ layer - explicit TNCudaBase(const std::size_t numQubits, DevTag dev_tag) - : BaseType(numQubits), handle_(make_shared_tncuda_handle()), - cublascaller_(make_shared_cublas_caller()), dev_tag_(dev_tag), - gate_cache_(std::make_shared>(dev_tag_)) { - // TODO this code block could be moved to base class and need to revisit - // when working on copy ctor - PL_ABORT_IF(numQubits < 2, - "The number of qubits should be greater than 1."); - if constexpr (std::is_same_v) { - typeData_ = CUDA_C_64F; - typeCompute_ = CUTENSORNET_COMPUTE_64F; - } else { - typeData_ = CUDA_C_32F; - typeCompute_ = CUTENSORNET_COMPUTE_32F; - } + ~TNCuda() = default; - PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateState( - /* const cutensornetHandle_t */ handle_.get(), - /* cutensornetStatePurity_t */ purity_, - /* int32_t numStateModes */ - static_cast(BaseType::getNumQubits()), - /* const int64_t *stateModeExtents */ - reinterpret_cast(BaseType::getQubitDims().data()), - /* cudaDataType_t */ typeData_, - /* cutensornetState_t * */ &quantumState_)); + /** + * @brief Get the method of a derived class object. + * + * @return std::string + */ + [[nodiscard]] auto getMethod() const -> std::string { + return Derived::method; } - ~TNCudaBase() { - PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroyState(quantumState_)); + /** + * @brief Access the CublasCaller the object is using. + * + * @return a reference to the object's CublasCaller object. + */ + auto getCublasCaller() const -> const CublasCaller & { + return *cublascaller_; } /** - * @brief Get the CUDA data type. + * @brief Get the full state vector representation of a quantum state. + * Note that users/developers should be responsible to ensure that there is + * sufficient memory on the host to store the full state vector. * - * @return cudaDataType_t + * @param res Pointer to the host memory to store the full state vector + * @param res_length Length of the result vector */ - [[nodiscard]] auto getCudaDataType() const -> cudaDataType_t { - return typeData_; + void getData(ComplexT *res, const std::size_t res_length) { + PL_ABORT_IF_NOT(res_length == + Pennylane::Util::exp2(BaseType::getNumQubits()), + "The size of the result vector should be equal to the " + "dimension of the quantum state."); + + std::size_t avail_gpu_memory = getFreeMemorySize(); + + PL_ABORT_IF(avail_gpu_memory < + Pennylane::Util::exp2(BaseType::getNumQubits()) * + sizeof(ComplexT), + "State tensor size exceeds the available GPU memory!"); + get_state_tensor(res); } /** - * @brief Get the cutensornet handle that the object is using. + * @brief Get the full state vector representation of a quantum state. * - * @return cutensornetHandle_t + * @return std::vector Full state vector representation of the + * quantum state on host */ - [[nodiscard]] auto getTNCudaHandle() const -> cutensornetHandle_t { - return handle_.get(); + auto getDataVector() -> std::vector { + std::size_t length = Pennylane::Util::exp2(BaseType::getNumQubits()); + std::vector results(length); + + getData(results.data(), results.size()); + + return results; } /** - * @brief Access the CublasCaller the object is using. - * - * @return a reference to the object's CublasCaller object. + * @brief Set current quantum state as zero state. */ - auto getCublasCaller() const -> const CublasCaller & { - return *cublascaller_; + void reset() { + const std::vector zeroState(BaseType::getNumQubits(), 0); + setBasisState(zeroState); } /** - * @brief Get the quantum state pointer. - * - * @return cutensornetState_t + * @brief Update quantum state with a basis state. + * NOTE: This API assumes the bond vector is a standard basis vector + * ([1,0,0,......]) and current implementation only works for qubit systems. + * @param basisState Vector representation of a basis state. */ - [[nodiscard]] auto getQuantumState() const -> cutensornetState_t { - return quantumState_; + void setBasisState(const std::vector &basisState) { + PL_ABORT_IF(BaseType::getNumQubits() != basisState.size(), + "The size of a basis state should be equal to the number " + "of qubits."); + + bool allZeroOrOne = std::all_of( + basisState.begin(), basisState.end(), + [](std::size_t bitVal) { return bitVal == 0 || bitVal == 1; }); + + PL_ABORT_IF_NOT(allZeroOrOne, + "Please ensure all elements of a basis state should be " + "either 0 or 1."); + + CFP_t value_cu = cuUtil::complexToCu(ComplexT{1.0, 0.0}); + + tensors_[0].getDataBuffer().zeroInit(); + std::size_t idx = BaseType::getNumQubits() - std::size_t{1}; + std::size_t target = basisState[idx]; + PL_CUDA_IS_SUCCESS( + cudaMemcpy(&tensors_[0].getDataBuffer().getData()[target], + &value_cu, sizeof(CFP_t), cudaMemcpyHostToDevice)); + + for (std::size_t i = 1; i < BaseType::getNumQubits(); i++) { + tensors_[i].getDataBuffer().zeroInit(); + + idx = BaseType::getNumQubits() - std::size_t{1} - i; + target = basisState[idx] == 0 ? 0 : bondDims_[i - 1]; + + PL_CUDA_IS_SUCCESS( + cudaMemcpy(&tensors_[i].getDataBuffer().getData()[target], + &value_cu, sizeof(CFP_t), cudaMemcpyHostToDevice)); + } }; /** - * @brief Get device and Cuda stream information (device ID and the - * associated Cuda stream ID). + * @brief Update the ith tensor data. * - * @return DevTag + * @param site_idx Index of the site. + * @param host_data Pointer to the data on host. + * @param host_data_size Length of the data. */ - [[nodiscard]] auto getDevTag() const -> const DevTag & { - return dev_tag_; + void updateSiteData(const std::size_t site_idx, const ComplexT *host_data, + std::size_t host_data_size) { + PL_ABORT_IF_NOT( + site_idx < BaseType::getNumQubits(), + "The site index should be less than the number of qubits."); + + const std::size_t idx = BaseType::getNumQubits() - site_idx - 1; + PL_ABORT_IF_NOT( + host_data_size == tensors_[idx].getDataBuffer().getLength(), + "The length of the host data should match its copy on the device."); + + tensors_[idx].getDataBuffer().zeroInit(); + + tensors_[idx].getDataBuffer().CopyHostDataToGpu(host_data, + host_data_size); } /** @@ -304,8 +367,8 @@ class TNCudaBase : public TensornetBase { targetWires, BaseType::getNumQubits()); PL_CUTENSORNET_IS_SUCCESS(cutensornetStateApplyControlledTensorOperator( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), /* int32_t numControlModes */ controlled_wires.size(), /* const int32_t * stateControlModes */ controlledModes.data(), /* const int64_t *stateControlValues*/ @@ -342,11 +405,8 @@ class TNCudaBase : public TensornetBase { bool adjoint = false, const std::vector ¶ms = {0.0}, const std::vector &gate_matrix = {}) { - // TODO: Need to revisit this line of code for the exact TN backend. - // We should be able to turn on/ skip this check based on the backend, - // if(getMethod() == "mps") { ... } PL_ABORT_IF( - wires.size() > 2, + wires.size() > 2 && getMethod() == "mps", "Unsupported gate: MPS method only supports 1, 2-wires gates"); auto &&par = (params.empty()) ? std::vector{0.0} : params; @@ -373,8 +433,8 @@ class TNCudaBase : public TensornetBase { // conjugated. `adjoint` in the following API is not equivalent to // `inverse` in the lightning context PL_CUTENSORNET_IS_SUCCESS(cutensornetStateApplyTensorOperator( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), /* int32_t numStateModes */ stateModes.size(), /* const int32_t * stateModes */ stateModes.data(), /* void * */ @@ -411,7 +471,8 @@ class TNCudaBase : public TensornetBase { const std::size_t length = std::size_t{1} << BaseType::getNumQubits(); - DataBuffer d_output_tensor(length, getDevTag(), true); + DataBuffer d_output_tensor(length, BaseType::getDevTag(), + true); get_accessor_(d_output_tensor.getData(), length, projected_modes, projected_mode_values, numHyperSamples); @@ -438,6 +499,108 @@ class TNCudaBase : public TensornetBase { projected_mode_values, numHyperSamples); } + protected: + /** + * @brief Get a vector of pointers to tensor data of each site. + * + * @return std::vector + */ + [[nodiscard]] auto getTensorsOutDataPtr() -> std::vector { + std::vector tensorsOutDataPtr(BaseType::getNumQubits()); + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + tensorsOutDataPtr[i] = tensors_out_[i].getDataBuffer().getData(); + } + return tensorsOutDataPtr; + } + + /** + * @brief Get a vector of pointers to extents of each site. + * + * @return std::vector Note int64_t const* is + * required by cutensornet backend. + */ + [[nodiscard]] auto getSitesExtentsPtr() -> std::vector { + std::vector sitesExtentsPtr_int64( + BaseType::getNumQubits()); + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + sitesExtentsPtr_int64[i] = sitesExtents_int64_[i].data(); + } + return sitesExtentsPtr_int64; + } + + /** + * @brief Dummy tensor operator update to allow multiple calls of + * appendMPSFinalize. This is a workaround to avoid the issue of the + * cutensornet library not allowing multiple calls of appendMPSFinalize. + * + * This function either appends a new `Identity` gate to the graph when the + * gate cache is empty or update the existing gate operator by itself. + */ + void dummy_tensor_update() { + if (identiy_gate_ids_.empty()) { + applyOperation("Identity", {0}, false); + } + + PL_CUTENSORNET_IS_SUCCESS(cutensornetStateUpdateTensorOperator( + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), + /* int64_t tensorId*/ + static_cast(identiy_gate_ids_.front()), + /* void* */ + static_cast( + gate_cache_->get_gate_device_ptr(identiy_gate_ids_.front())), + /* int32_t unitary*/ 1)); + } + + /** + * @brief Save quantumState information to data provided by a user + * + * @param extentsPtr Pointer to extents provided by a user + * @param tensorPtr Pointer to tensors provided by a user + */ + void computeState(int64_t **extentsPtr, void **tensorPtr) { + cutensornetWorkspaceDescriptor_t workDesc; + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateWorkspaceDescriptor( + BaseType::getTNCudaHandle(), &workDesc)); + + // TODO we assign half (magic number is) of free memory size to the + // maximum memory usage. + const std::size_t scratchSize = cuUtil::getFreeMemorySize() / 2; + + PL_CUTENSORNET_IS_SUCCESS(cutensornetStatePrepare( + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), + /* std::size_t maxWorkspaceSizeDevice */ scratchSize, + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* cudaStream_t unused as of v24.03*/ 0x0)); + + std::size_t worksize = + getWorkSpaceMemorySize(BaseType::getTNCudaHandle(), workDesc); + + PL_ABORT_IF(worksize > scratchSize, + "Insufficient workspace size on Device!"); + + const std::size_t d_scratch_length = worksize / sizeof(std::size_t); + DataBuffer d_scratch(d_scratch_length, + BaseType::getDevTag(), true); + + setWorkSpaceMemory(BaseType::getTNCudaHandle(), workDesc, + reinterpret_cast(d_scratch.getData()), + worksize); + + PL_CUTENSORNET_IS_SUCCESS(cutensornetStateCompute( + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* int64_t * */ extentsPtr, + /* int64_t *stridesOut */ nullptr, + /* void * */ tensorPtr, + /* cudaStream_t */ BaseType::getDevTag().getStreamID())); + + PL_CUTENSORNET_IS_SUCCESS( + cutensornetDestroyWorkspaceDescriptor(workDesc)); + } + private: /** * @brief Get accessor of a state tensor @@ -456,8 +619,8 @@ class TNCudaBase : public TensornetBase { const int32_t numHyperSamples = 1) const { cutensornetStateAccessor_t accessor; PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateAccessor( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), /* int32_t numProjectedModes */ static_cast(projected_modes.size()), /* const int32_t *projectedModes */ projected_modes.data(), @@ -468,7 +631,7 @@ class TNCudaBase : public TensornetBase { const cutensornetAccessorAttributes_t accessor_attribute = CUTENSORNET_ACCESSOR_CONFIG_NUM_HYPER_SAMPLES; PL_CUTENSORNET_IS_SUCCESS(cutensornetAccessorConfigure( - /* const cutensornetHandle_t */ getTNCudaHandle(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), /* cutensornetStateAccessor_t */ accessor, /* cutensornetAccessorAttributes_t */ accessor_attribute, /* const void * */ &numHyperSamples, @@ -476,15 +639,15 @@ class TNCudaBase : public TensornetBase { // prepare the computation cutensornetWorkspaceDescriptor_t workDesc; - PL_CUTENSORNET_IS_SUCCESS( - cutensornetCreateWorkspaceDescriptor(getTNCudaHandle(), &workDesc)); + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateWorkspaceDescriptor( + BaseType::getTNCudaHandle(), &workDesc)); // TODO we assign half (magic number is) of free memory size to the // maximum memory usage. const std::size_t scratchSize = cuUtil::getFreeMemorySize() / 2; PL_CUTENSORNET_IS_SUCCESS(cutensornetAccessorPrepare( - /* const cutensornetHandle_t */ getTNCudaHandle(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), /* cutensornetStateAccessor_t*/ accessor, /* std::size_t */ scratchSize, /* cutensornetWorkspaceDescriptor_t */ workDesc, @@ -492,23 +655,23 @@ class TNCudaBase : public TensornetBase { // Allocate workspace buffer std::size_t worksize = - getWorkSpaceMemorySize(getTNCudaHandle(), workDesc); + getWorkSpaceMemorySize(BaseType::getTNCudaHandle(), workDesc); PL_ABORT_IF(worksize > scratchSize, "Insufficient workspace size on Device!"); const std::size_t d_scratch_length = worksize / sizeof(std::size_t); - DataBuffer d_scratch(d_scratch_length, getDevTag(), - true); + DataBuffer d_scratch(d_scratch_length, + BaseType::getDevTag(), true); - setWorkSpaceMemory(getTNCudaHandle(), workDesc, + setWorkSpaceMemory(BaseType::getTNCudaHandle(), workDesc, reinterpret_cast(d_scratch.getData()), worksize); // compute the specified slice of the quantum circuit amplitudes tensor ComplexT stateNorm2{0.0, 0.0}; PL_CUTENSORNET_IS_SUCCESS(cutensornetAccessorCompute( - /* const cutensornetHandle_t */ getTNCudaHandle(), + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), /* cutensornetStateAccessor_t */ accessor, /* const int64_t * projectedModeValues */ projected_mode_values.data(), @@ -518,84 +681,161 @@ class TNCudaBase : public TensornetBase { /* void *stateNorm */ static_cast(&stateNorm2), /* cudaStream_t cudaStream */ 0x0)); - PL_CUDA_IS_SUCCESS(cudaStreamSynchronize(getDevTag().getStreamID())); + PL_CUDA_IS_SUCCESS( + cudaStreamSynchronize(BaseType::getDevTag().getStreamID())); + + const ComplexT scale_scalar = ComplexT{1.0, 0.0} / stateNorm2; + + CFP_t scale_scalar_cu{scale_scalar.real(), scale_scalar.imag()}; + + scaleC_CUDA( + scale_scalar_cu, tensor_data, tensor_data_size, + BaseType::getDevTag().getDeviceID(), + BaseType::getDevTag().getStreamID(), getCublasCaller()); PL_CUTENSORNET_IS_SUCCESS( cutensornetDestroyWorkspaceDescriptor(workDesc)); PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroyAccessor(accessor)); } - protected: /** - * @brief Dummy tensor operator update to allow multiple calls of - * appendMPSFinalize. This is a workaround to avoid the issue of the - * cutensornet library not allowing multiple calls of appendMPSFinalize. + * @brief Append initial MPS sites to the compute graph with data provided + * by a user * - * This function either appends a new `Identity` gate to the graph when the - * gate cache is empty or update the existing gate operator by itself. */ - void dummy_tensor_update() { - if (identiy_gate_ids_.empty()) { - applyOperation("Identity", {0}, false); - } - - PL_CUTENSORNET_IS_SUCCESS(cutensornetStateUpdateTensorOperator( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), - /* int64_t tensorId*/ - static_cast(identiy_gate_ids_.front()), - /* void* */ - static_cast( - gate_cache_->get_gate_device_ptr(identiy_gate_ids_.front())), - /* int32_t unitary*/ 1)); + void appendInitialMPSState_(const int64_t *const *extentsPtr) { + PL_CUTENSORNET_IS_SUCCESS(cutensornetStateInitializeMPS( + /*const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /*cutensornetState_t*/ BaseType::getQuantumState(), + /*cutensornetBoundaryCondition_t */ + CUTENSORNET_BOUNDARY_CONDITION_OPEN, + /*const int64_t *const* */ extentsPtr, + /*const int64_t *const* */ nullptr, + /*void ** */ + reinterpret_cast(getTensorsDataPtr_().data()))); } /** - * @brief Save quantumState information to data provided by a user + * @brief Get a vector of pointers to tensor data of each site. * - * @param tensorPtr Pointer to tensors provided by a user + * @return std::vector */ - void computeState(int64_t **extentsPtr, void **tensorPtr) { - cutensornetWorkspaceDescriptor_t workDesc; - PL_CUTENSORNET_IS_SUCCESS( - cutensornetCreateWorkspaceDescriptor(getTNCudaHandle(), &workDesc)); - - // TODO we assign half (magic number is) of free memory size to the - // maximum memory usage. - const std::size_t scratchSize = cuUtil::getFreeMemorySize() / 2; - - PL_CUTENSORNET_IS_SUCCESS(cutensornetStatePrepare( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), - /* std::size_t maxWorkspaceSizeDevice */ scratchSize, - /* cutensornetWorkspaceDescriptor_t */ workDesc, - /* cudaStream_t unused as of v24.03*/ 0x0)); + [[nodiscard]] auto getTensorsDataPtr_() -> std::vector { + std::vector tensorsDataPtr(BaseType::getNumQubits()); + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + tensorsDataPtr[i] = reinterpret_cast( + tensors_[i].getDataBuffer().getData()); + } + return tensorsDataPtr; + } - std::size_t worksize = - getWorkSpaceMemorySize(getTNCudaHandle(), workDesc); + /** + * @brief Return bondDims to the member initializer + * NOTE: This method only works for the open boundary condition + * @return std::vector + */ + std::vector setBondDims_() { + std::vector localBondDims(BaseType::getNumQubits() - 1, + maxBondDim_); + + const std::size_t ubDim = log2(maxBondDim_); + for (std::size_t i = 0; i < localBondDims.size(); i++) { + const std::size_t bondDim = + std::min(i + 1, BaseType::getNumQubits() - i - 1); + + if (bondDim <= ubDim) { + localBondDims[i] = std::size_t{1} << bondDim; + } + } + return localBondDims; + } - PL_ABORT_IF(worksize > scratchSize, - "Insufficient workspace size on Device!"); + /** + * @brief Return siteModes to the member initializer + * NOTE: This method only works for the open boundary condition + * @return std::vector> + */ + std::vector> setSitesModes_() { + std::vector> localSitesModes; + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + std::vector localSiteModes; + if (i == 0) { + // Leftmost site (state mode, shared mode) + localSiteModes = + std::vector({i, i + BaseType::getNumQubits()}); + } else if (i == BaseType::getNumQubits() - 1) { + // Rightmost site (shared mode, state mode) + localSiteModes = std::vector( + {i + BaseType::getNumQubits() - 1, i}); + } else { + // Interior sites (state mode, state mode, shared mode) + localSiteModes = + std::vector({i + BaseType::getNumQubits() - 1, + i, i + BaseType::getNumQubits()}); + } + localSitesModes.push_back(std::move(localSiteModes)); + } + return localSitesModes; + } - const std::size_t d_scratch_length = worksize / sizeof(std::size_t); - DataBuffer d_scratch(d_scratch_length, getDevTag(), - true); + /** + * @brief Return sitesExtents to the member initializer + * NOTE: This method only works for the open boundary condition + * @return std::vector> + */ + std::vector> setSitesExtents_() { + std::vector> localSitesExtents; + + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + std::vector localSiteExtents; + if (i == 0) { + // Leftmost site (state mode, shared mode) + localSiteExtents = std::vector( + {BaseType::getQubitDims()[i], bondDims_[i]}); + } else if (i == BaseType::getNumQubits() - 1) { + // Rightmost site (shared mode, state mode) + localSiteExtents = std::vector( + {bondDims_[i - 1], BaseType::getQubitDims()[i]}); + } else { + // Interior sites (state mode, state mode, shared mode) + localSiteExtents = std::vector( + {bondDims_[i - 1], BaseType::getQubitDims()[i], + bondDims_[i]}); + } + localSitesExtents.push_back(std::move(localSiteExtents)); + } + return localSitesExtents; + } - setWorkSpaceMemory(getTNCudaHandle(), workDesc, - reinterpret_cast(d_scratch.getData()), - worksize); + /** + * @brief Return siteExtents_int64 to the member initializer + * NOTE: This method only works for the open boundary condition + * @return std::vector> + */ + std::vector> setSitesExtents_int64_() { + std::vector> localSitesExtents_int64; - PL_CUTENSORNET_IS_SUCCESS(cutensornetStateCompute( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetState_t */ getQuantumState(), - /* cutensornetWorkspaceDescriptor_t */ workDesc, - /* int64_t * */ extentsPtr, - /* int64_t *stridesOut */ nullptr, - /* void * */ tensorPtr, - /* cudaStream_t */ getDevTag().getStreamID())); + for (const auto &siteExtents : sitesExtents_) { + localSitesExtents_int64.push_back( + std::move(Pennylane::Util::cast_vector( + siteExtents))); + } + return localSitesExtents_int64; + } - PL_CUTENSORNET_IS_SUCCESS( - cutensornetDestroyWorkspaceDescriptor(workDesc)); + /** + * @brief The tensors init helper function for ctor. + */ + void initTensors_() { + for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { + // construct mps tensors reprensentation + tensors_.emplace_back(sitesModes_[i].size(), sitesModes_[i], + sitesExtents_[i], BaseType::getDevTag()); + + tensors_out_.emplace_back(sitesModes_[i].size(), sitesModes_[i], + sitesExtents_[i], BaseType::getDevTag()); + } + reset(); } }; } // namespace Pennylane::LightningTensor::TNCuda diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TNCudaBase.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TNCudaBase.hpp new file mode 100644 index 0000000000..eaa132f2cb --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TNCudaBase.hpp @@ -0,0 +1,168 @@ +// Copyright 2024 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 TNCudaBase.hpp + * Base class for all cuTensorNet backends. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "Error.hpp" +#include "cuda_helpers.hpp" +#include "tncudaError.hpp" +#include "tncuda_helpers.hpp" + +/// @cond DEV +namespace { +namespace cuUtil = Pennylane::LightningGPU::Util; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +///@endcond + +namespace Pennylane::LightningTensor::TNCuda { +/** + * @brief CRTP-enabled base class for cutensornet. + * + * @tparam PrecisionT Floating point precision. + * @tparam Derived Derived class to instantiate using CRTP. + */ +template class TNCudaBase { + private: + DevTag dev_tag_; + + std::size_t numQubits_; + std::vector qubitDims_; + + cudaDataType_t typeData_; + cutensornetComputeType_t typeCompute_; + + SharedTNCudaHandle handle_; + + cutensornetState_t quantumState_; + cutensornetStatePurity_t purity_ = + CUTENSORNET_STATE_PURITY_PURE; // Only supports pure tensor network + // states as v24.03 + + public: + TNCudaBase() = delete; + + explicit TNCudaBase(const std::size_t numQubits, int device_id = 0, + cudaStream_t stream_id = 0) + : dev_tag_({device_id, stream_id}), numQubits_(numQubits) { + PL_ABORT_IF(numQubits < 2, + "The number of qubits should be greater than 1."); + + // Ensure device is set before creating the state + dev_tag_.refresh(); + + qubitDims_ = std::vector(numQubits, std::size_t{2}); + + if constexpr (std::is_same_v) { + typeData_ = CUDA_C_64F; + typeCompute_ = CUTENSORNET_COMPUTE_64F; + } else { + typeData_ = CUDA_C_32F; + typeCompute_ = CUTENSORNET_COMPUTE_32F; + } + + handle_ = make_shared_tncuda_handle(); + + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateState( + /* const cutensornetHandle_t */ handle_.get(), + /* cutensornetStatePurity_t */ purity_, + /* int32_t numStateModes */ + static_cast(getNumQubits()), + /* const int64_t *stateModeExtents */ + reinterpret_cast(getQubitDims().data()), + /* cudaDataType_t */ typeData_, + /* cutensornetState_t * */ &quantumState_)); + } + + ~TNCudaBase() { + PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroyState(quantumState_)); + } + + /** + * @brief Get dimension of each qubit + * + * @return const std::vector & + */ + [[nodiscard]] auto getQubitDims() const + -> const std::vector & { + return qubitDims_; + }; + + /** + * @brief Get dimension of each qubit + * + * @return std::vector & + */ + [[nodiscard]] auto getQubitDims() -> std::vector & { + return qubitDims_; + }; + + /** + * @brief Get the number of qubits of the simulated system. + * + * @return std::size_t + */ + [[nodiscard]] auto getNumQubits() const -> std::size_t { + return numQubits_; + }; + + /** + * @brief Get the CUDA data type. + * + * @return cudaDataType_t + */ + [[nodiscard]] auto getCudaDataType() const -> cudaDataType_t { + return typeData_; + } + + /** + * @brief Get the cutensornet handle that the object is using. + * + * @return cutensornetHandle_t + */ + [[nodiscard]] auto getTNCudaHandle() const -> cutensornetHandle_t { + return handle_.get(); + } + + /** + * @brief Get the quantum state pointer. + * + * @return cutensornetState_t + */ + [[nodiscard]] auto getQuantumState() const -> cutensornetState_t { + return quantumState_; + }; + + /** + * @brief Get device and Cuda stream information (device ID and the + * associated Cuda stream ID). + * + * @return DevTag + */ + [[nodiscard]] auto getDevTag() const -> const DevTag & { + return dev_tag_; + } +}; +} // namespace Pennylane::LightningTensor::TNCuda diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TensornetBase.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TensornetBase.hpp deleted file mode 100644 index c5ad342619..0000000000 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/TensornetBase.hpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2024 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 TensornetBase.hpp - * Base class for all cuTensorNet backends. - */ - -#pragma once - -#include - -#include "Error.hpp" - -namespace Pennylane::LightningTensor::TNCuda { -/** - * @brief CRTP-enabled base class for cutensornet. - * - * @tparam PrecisionT Floating point precision. - * @tparam Derived Derived class to instantiate using CRTP. - */ -template class TensornetBase { - private: - std::size_t numQubits_; - std::vector qubitDims_; - - public: - TensornetBase() = delete; - - explicit TensornetBase(const std::size_t numQubits) - : numQubits_(numQubits) { - qubitDims_ = std::vector(numQubits, std::size_t{2}); - } - - ~TensornetBase() = default; - - /** - * @brief Get dimension of each qubit - * - * @return const std::vector & - */ - [[nodiscard]] auto getQubitDims() const - -> const std::vector & { - return qubitDims_; - }; - - /** - * @brief Get dimension of each qubit - * - * @return std::vector & - */ - [[nodiscard]] auto getQubitDims() -> std::vector & { - return qubitDims_; - }; - - /** - * @brief Get the number of qubits of the simulated system. - * - * @return std::size_t - */ - [[nodiscard]] auto getNumQubits() const -> std::size_t { - return numQubits_; - }; -}; -} // namespace Pennylane::LightningTensor::TNCuda diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/CMakeLists.txt index e6a24c19dd..cef85b2079 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(tensornet_base_tests) +project(tncuda_base_tests) # Default build type for test code is Debug if(NOT CMAKE_BUILD_TYPE) @@ -14,25 +14,25 @@ FetchAndIncludeCatch() # Define library ################################################################################ -add_library(tensornet_base_tests INTERFACE) -target_link_libraries(tensornet_base_tests INTERFACE Catch2::Catch2) +add_library(tncuda_base_tests INTERFACE) +target_link_libraries(tncuda_base_tests INTERFACE Catch2::Catch2) -ProcessTestOptions(tensornet_base_tests) +ProcessTestOptions(tncuda_base_tests) # Create dependency on the dynamically defined simulator/backend target. -target_link_libraries(tensornet_base_tests INTERFACE ${PL_BACKEND} ${PL_BACKEND}_tncuda_utils) +target_link_libraries(tncuda_base_tests INTERFACE ${PL_BACKEND} ${PL_BACKEND}_tncuda_utils) -target_sources(tensornet_base_tests INTERFACE runner_lightning_tensor_tensornetBase.cpp) +target_sources(tncuda_base_tests INTERFACE runner_lightning_tensor_TNCudaBase.cpp) ################################################################################ # Define targets ################################################################################ -set(TEST_SOURCES Tests_tensornetBase.cpp +set(TEST_SOURCES Tests_TNCudaBase.cpp ) -add_executable(tensornet_base_test_runner ${TEST_SOURCES}) -target_link_libraries(tensornet_base_test_runner PRIVATE tensornet_base_tests) +add_executable(tncuda_base_test_runner ${TEST_SOURCES}) +target_link_libraries(tncuda_base_test_runner PRIVATE tncuda_base_tests) -catch_discover_tests(tensornet_base_test_runner) +catch_discover_tests(tncuda_base_test_runner) -install(TARGETS tensornet_base_test_runner DESTINATION bin) +install(TARGETS tncuda_base_test_runner DESTINATION bin) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_tensornetBase.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_TNCudaBase.cpp similarity index 87% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_tensornetBase.cpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_TNCudaBase.cpp index 8edf472afb..a05159d642 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_tensornetBase.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/Tests_TNCudaBase.cpp @@ -27,7 +27,7 @@ using namespace Pennylane::LightningTensor::TNCuda::Util; * Tests for functionality defined in the MPSBase class. */ -template void testTensornetBase() { +template void testTNCudaBase() { if constexpr (!std::is_same_v) { using MPS_T = typename TypeList::Type; @@ -43,10 +43,10 @@ template void testTensornetBase() { REQUIRE(mps_state.getNumQubits() == 4); REQUIRE(mps_state.getQubitDims() == qubitDims); } - testTensornetBase(); + testTNCudaBase(); } } -TEST_CASE("testTensornetBase", "[TensornetBase]") { - testTensornetBase(); +TEST_CASE("testTNCudaBase", "[TNCudaBase]") { + testTNCudaBase(); } diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/runner_lightning_tensor_tensornetBase.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/runner_lightning_tensor_TNCudaBase.cpp similarity index 100% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/runner_lightning_tensor_tensornetBase.cpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/base/tests/runner_lightning_tensor_TNCudaBase.cpp diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/bindings/LTensorTNCudaBindings.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/bindings/LTensorTNCudaBindings.hpp index 2e7825bbb6..d80c254f0e 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/bindings/LTensorTNCudaBindings.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/bindings/LTensorTNCudaBindings.hpp @@ -138,8 +138,8 @@ void registerBackendClassSpecificBindings(PyClass &pyclass) { py::buffer_info numpyArrayInfo = tensors[idx].request(); auto *data_ptr = static_cast *>( numpyArrayInfo.ptr); - tensor_network.updateMPSSiteData(idx, data_ptr, - tensors[idx].size()); + tensor_network.updateSiteData(idx, data_ptr, + tensors[idx].size()); } }, "Pass MPS site data to the C++ backend.") diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/CMakeLists.txt index 88591dde0a..0522b049da 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/CMakeLists.txt @@ -15,10 +15,10 @@ FetchAndIncludeCatch() ################################################################################ add_library(${PL_BACKEND}_gates_tests INTERFACE) -target_link_libraries(${PL_BACKEND}_gates_tests INTERFACE Catch2::Catch2 - ${PL_BACKEND}_gates - ${PL_BACKEND} - ) +target_link_libraries(${PL_BACKEND}_gates_tests INTERFACE Catch2::Catch2 + ${PL_BACKEND}_gates + ${PL_BACKEND} + ${PL_BACKEND}_tncuda_utils) ProcessTestOptions(${PL_BACKEND}_gates_tests) @@ -27,8 +27,9 @@ target_sources(${PL_BACKEND}_gates_tests INTERFACE runner_${PL_BACKEND}_gates.cp ################################################################################ # Define targets ################################################################################ -set(TEST_SOURCES Test_MPSTNCuda_NonParam.cpp - Test_MPSTNCuda_Param.cpp) +set(TEST_SOURCES Test_TNCuda_NonParam.cpp + Test_TNCuda_Param.cpp + Test_TNCuda_MPO.cpp) add_executable(${PL_BACKEND}_gates_test_runner ${TEST_SOURCES}) target_link_libraries(${PL_BACKEND}_gates_test_runner PRIVATE ${PL_BACKEND}_gates_tests) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_NonParam.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_NonParam.cpp deleted file mode 100644 index b58fa3bbec..0000000000 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_NonParam.cpp +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright 2024 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 -#include - -#include - -#include "DevTag.hpp" -#include "MPSTNCuda.hpp" -#include "TNCudaGateCache.hpp" - -#include "TestHelpers.hpp" - -/// @cond DEV -namespace { -using namespace Pennylane::LightningGPU; -using namespace Pennylane::LightningTensor; -using namespace Pennylane::LightningTensor::TNCuda::Gates; -using namespace Pennylane::Util; -namespace cuUtil = Pennylane::LightningGPU::Util; -} // namespace -/// @endcond - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::Identity", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("Hadamard", {index}, inverse); - - mps_state.applyOperation("Identity", {index}, inverse); - cp_t expected(1.0 / std::sqrt(2), 0); - - auto results = mps_state.getDataVector(); - - CHECK(expected.real() == - Approx(results[0b1 << ((num_qubits - 1 - index))].real())); - CHECK(expected.imag() == - Approx(results[0b1 << ((num_qubits - index - 1))].imag())); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::Hadamard", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.append_mps_final_state(); - - mps_state.applyOperation("Hadamard", {index}, inverse); - - mps_state.append_mps_final_state(); - - mps_state.applyOperation("Identity", {index}, inverse); - - // Test for multiple final states appendings - mps_state.append_mps_final_state(); - - cp_t expected(1.0 / std::sqrt(2), 0); - - auto results = mps_state.getDataVector(); - - CHECK(expected.real() == - Approx(results[0b1 << ((num_qubits - 1 - index))].real())); - CHECK(expected.imag() == - Approx(results[0b1 << ((num_qubits - index - 1))].imag())); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::PauliX", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("PauliX", {index}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results[0] == cuUtil::ZERO>()); - CHECK(results[0b1 << (num_qubits - index - 1)] == - cuUtil::ONE>()); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::applyOperation-gatematrix", - "[MPSTNCuda_Nonparam]", float, double) { - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - std::vector> gate_matrix = { - cuUtil::ZERO>(), - cuUtil::ONE>(), - cuUtil::ONE>(), - cuUtil::ZERO>()}; - - mps_state.applyOperation("applyMatrix", {index}, false, {}, - gate_matrix); - - auto results = mps_state.getDataVector(); - - CHECK(results[0] == cuUtil::ZERO>()); - CHECK(results[0b1 << (num_qubits - index - 1)] == - cuUtil::ONE>()); - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::PauliY", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const cp_t p = cuUtil::ConstMult( - std::complex(0.5, 0.0), - cuUtil::ConstMult(cuUtil::INVSQRT2>(), - cuUtil::IMAG>())); - const cp_t m = cuUtil::ConstMult(std::complex(-1, 0), 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}}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("PauliY", {index}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::PauliZ", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const cp_t p(static_cast(0.5) * - cuUtil::INVSQRT2>()); - const cp_t m(cuUtil::ConstMult(cp_t{-1.0, 0.0}, 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}}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("PauliZ", {index}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::S", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - cp_t r(std::complex(0.5, 0.0) * - cuUtil::INVSQRT2>()); - cp_t i(cuUtil::ConstMult(r, cuUtil::IMAG>())); - - if (inverse) { - i = conj(i); - } - - 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}}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("S", {index}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::T", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - cp_t r(1.0 / (2.0 * std::sqrt(2)), 0); - cp_t i(1.0 / 4, 1.0 / 4); - - if (inverse) { - i = conj(i); - } - - 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}}; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("T", {index}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CNOT", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply adjacent wire indices") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "CNOT", "CNOT"}, - {{0}, {0, 1}, {1, 2}}, - {false, inverse, inverse}); - - auto results = mps_state.getDataVector(); - - CHECK(results.front() == - cuUtil::INVSQRT2>()); - CHECK(results.back() == cuUtil::INVSQRT2>()); - } - - SECTION("Apply non-adjacent wire indices") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("Hadamard", {0}, false); - mps_state.applyOperation("CNOT", {0, 2}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results[0] == cuUtil::INVSQRT2>()); - CHECK(results[5] == cuUtil::INVSQRT2>()); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::SWAP", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply adjacent wire indices") { - std::vector expected{cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>()}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("SWAP", {0, 1}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected)); - } - - SECTION("Apply non-adjacent wire indices") { - std::vector expected{cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>()}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("SWAP", {0, 2}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected)); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CY", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply adjacent wire indices") { - std::vector expected_results{ - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>(), - std::complex(0, -1 / sqrt(2)), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>()}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("CY", {0, 1}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - - SECTION("Apply non-adjacent wire indices") { - std::vector expected_results{ - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0.0), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(0.0, 1.0 / sqrt(2))}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("CY", {0, 2}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CZ", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Apply adjacent wire indices") { - std::vector expected_results{ - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(-1 / sqrt(2), 0), - cuUtil::ZERO>()}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("CZ", {0, 1}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - - SECTION("Apply non-adjacent wire indices") { - std::vector expected_results{ - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - cuUtil::ZERO>(), - std::complex(1.0 / sqrt(2), 0), - cuUtil::ZERO>()}; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, - {false, false}); - - mps_state.applyOperation("CZ", {0, 2}, inverse); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Non_Param_Gates::2+_wires", - "[MPSTNCuda_Nonparam]", float, double) { - const bool inverse = GENERATE(false, true); - { - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("Toffoli gate") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - REQUIRE_THROWS_AS( - mps_state.applyOperation("Toffoli", {0, 1, 2}, inverse), - LightningException); - } - - SECTION("CSWAP gate") { - std::size_t num_qubits = 4; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - REQUIRE_THROWS_AS( - mps_state.applyOperation("CSWAP", {0, 1, 2}, inverse), - LightningException); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::applyControlledOperation non-param " - "one-qubit with controls", - "[MPSTNCuda]", float, double) { - using PrecisionT = TestType; - using ComplexT = std::complex; - const int num_qubits = 4; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const auto margin = PrecisionT{1e-5}; - const std::size_t control = GENERATE(0, 1, 2, 3); - const std::size_t wire = GENERATE(0, 1, 2, 3); - - MPSTNCuda mps_state0{num_qubits, maxExtent, dev_tag}; - MPSTNCuda mps_state1{num_qubits, maxExtent, dev_tag}; - - DYNAMIC_SECTION("Controlled gates with base operation - " - << "controls = {" << control << "} " - << ", wires = {" << wire << "} - " - << PrecisionToName::value) { - if (control != wire) { - mps_state0.applyControlledOperation( - "PauliX", std::vector{control}, - std::vector{true}, std::vector{wire}); - - mps_state1.applyOperation( - "CNOT", std::vector{control, wire}, false); - - REQUIRE(mps_state0.getDataVector() == - approx(mps_state1.getDataVector()).margin(margin)); - } - } - - DYNAMIC_SECTION("Controlled gates with a target matrix - " - << "controls = {" << control << "} " - << ", wires = {" << wire << "} - " - << PrecisionToName::value) { - if (control != wire) { - std::vector gate_matrix = { - ComplexT{0.0, 0.0}, ComplexT{1.0, 0.0}, ComplexT{1.0, 0.0}, - ComplexT{0.0, 0.0}}; - mps_state0.applyControlledOperation( - "applyControlledGates", std::vector{control}, - std::vector{true}, std::vector{wire}, false, - {}, gate_matrix); - - mps_state1.applyOperation( - "CNOT", std::vector{control, wire}, false); - - REQUIRE(mps_state0.getDataVector() == - approx(mps_state1.getDataVector()).margin(margin)); - } - } - - SECTION("Throw exception for 1+ target wires gates") { - REQUIRE_THROWS_AS(mps_state0.applyControlledOperation( - "CSWAP", {0}, {true, true}, {1, 2}), - LightningException); - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::applyMPO::2+_wires", "[MPSTNCuda_NonParam]", - float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t maxExtent = 2; - std::size_t max_mpo_bond = 16; - DevTag dev_tag{0, 0}; - - std::vector> mpo_cnot( - 2, std::vector(16, {0.0, 0.0})); - - // in-order decomposition of the cnot operator - // data from scipy decompose in the lightning.tensor python layer - mpo_cnot[0][0] = {1.0, 0.0}; - mpo_cnot[0][3] = {-1.0, 0.0}; - mpo_cnot[0][9] = {1.0, 0.0}; - mpo_cnot[0][10] = {-1.0, 0.0}; - - mpo_cnot[1][0] = {1.0, 0.0}; - mpo_cnot[1][7] = {-1.0, 0.0}; - mpo_cnot[1][10] = {1.0, 0.0}; - mpo_cnot[1][13] = {-1.0, 0.0}; - - std::vector> mpo_cswap; - mpo_cswap.emplace_back(std::vector(16, {0.0, 0.0})); - mpo_cswap.emplace_back(std::vector(64, {0.0, 0.0})); - mpo_cswap.emplace_back(std::vector(16, {0.0, 0.0})); - - mpo_cswap[0][0] = {-1.5811388300841898, 0.0}; - mpo_cswap[0][2] = {0.7071067811865475, 0.0}; - mpo_cswap[0][5] = {-1.0, 0.0}; - mpo_cswap[0][9] = mpo_cswap[0][0]; - mpo_cswap[0][11] = -mpo_cswap[0][2]; - mpo_cswap[0][14] = {1.0, 0.0}; - - mpo_cswap[1][0] = {-0.413452607315265, 0.0}; - mpo_cswap[1][1] = {0.6979762349196628, 0.0}; - mpo_cswap[1][7] = {0.9870874576374964, 0.0}; - mpo_cswap[1][8] = {0.5736348503222318, 0.0}; - mpo_cswap[1][9] = {0.11326595025589799, 0.0}; - mpo_cswap[1][15] = {0.16018224300696726, 0.0}; - mpo_cswap[1][34] = -mpo_cswap[1][7]; - mpo_cswap[1][36] = mpo_cswap[1][0]; - mpo_cswap[1][37] = -mpo_cswap[1][1]; - mpo_cswap[1][42] = -mpo_cswap[1][15]; - mpo_cswap[1][44] = mpo_cswap[1][8]; - mpo_cswap[1][45] = -mpo_cswap[1][9]; - - mpo_cswap[2][0] = mpo_cswap[1][15]; - mpo_cswap[2][1] = -mpo_cswap[1][7]; - mpo_cswap[2][7] = {1.0, 0.0}; - mpo_cswap[2][10] = {-1.0, 0.0}; - mpo_cswap[2][12] = -mpo_cswap[2][1]; - mpo_cswap[2][13] = mpo_cswap[2][0]; - - SECTION("Target at wire indices") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, - {false, false, false}); - - mps_state.applyOperation("CNOT", {0, 1}, inverse); - - mps_state_mpo.applyMPOOperation(mpo_cnot, {0, 1}, max_mpo_bond); - - auto ref = mps_state.getDataVector(); - auto res = mps_state_mpo.getDataVector(); - - CHECK(res == Pennylane::Util::approx(ref)); - } - - SECTION("Target at non-adjacent wire indices") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, - {false, false, false}); - - mps_state.applyOperation("CNOT", {0, 2}, inverse); - - mps_state_mpo.applyMPOOperation(mpo_cnot, {0, 2}, max_mpo_bond); - - auto ref = mps_state.getDataVector(); - auto res = mps_state_mpo.getDataVector(); - - CHECK(res == Pennylane::Util::approx(ref)); - } - - SECTION("Tests for 3-wire MPOs") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, - {false, false, false}); - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state_mpo.applyMPOOperation(mpo_cswap, {0, 1, 2}, max_mpo_bond); - - auto res = mps_state_mpo.getDataVector(); - auto ref = mps_state.getDataVector(); - - CHECK(res == Pennylane::Util::approx(ref)); - } - } -} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_Param.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_Param.cpp deleted file mode 100644 index c5caa31715..0000000000 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_MPSTNCuda_Param.cpp +++ /dev/null @@ -1,1161 +0,0 @@ -// Copyright 2024 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 -#include - -#include - -#include "DevTag.hpp" -#include "Gates.hpp" -#include "MPSTNCuda.hpp" -#include "TNCudaGateCache.hpp" -#include "TestHelpers.hpp" - -/// @cond DEV -namespace { -using namespace Pennylane::LightningGPU; -using namespace Pennylane::LightningTensor; -using namespace Pennylane::LightningTensor::TNCuda::Gates; -using namespace Pennylane::Util; -using namespace Pennylane; -namespace cuUtil = Pennylane::LightningGPU::Util; -} // namespace -/// @endcond - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::PhaseShift", "[MPSTNCuda_Param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 0.8, 2.4}; - const TestType sign = (inverse) ? -1.0 : 1.0; - const cp_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( - Pennylane::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); - } - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("PhaseShift", {index}, inverse, - {sign * angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::RX", "[MPSTNCuda_Param]", float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 0.8, 2.4}; - - // Results from default.qubit - std::vector results = {{0.34958337, -0.05283436}, - {0.32564424, -0.13768018}, - {0.12811281, -0.32952558}}; - - for (auto &val : results) { - val = inverse ? std::conj(val) : val; - } - - std::vector> expected_results{ - std::vector(std::size_t{1} << num_qubits, results[0]), - std::vector(std::size_t{1} << num_qubits, results[1]), - std::vector(std::size_t{1} << num_qubits, results[2]), - }; - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("RX", {index}, inverse, {angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::RY", "[MPSTNCuda_Nonparam]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 0.8, 2.4}; - - // Results from default.qubit - std::vector> expected_results{{{0.29674901, 0}, - {0.29674901, 0}, - {0.29674901, 0}, - {0.29674901, 0}, - {0.40241773, 0}, - {0.40241773, 0}, - {0.40241773, 0}, - {0.40241773, 0}}, - {{0.18796406, 0}, - {0.18796406, 0}, - {0.46332441, 0}, - {0.46332441, 0}, - {0.18796406, 0}, - {0.18796406, 0}, - {0.46332441, 0}, - {0.46332441, 0}}, - {{-0.20141277, 0}, - {0.45763839, 0}, - {-0.20141277, 0}, - {0.45763839, 0}, - {-0.20141277, 0}, - {0.45763839, 0}, - {-0.20141277, 0}, - {0.45763839, 0}}}; - - if (inverse) { - std::swap(expected_results[0][4], expected_results[0][0]); - std::swap(expected_results[0][5], expected_results[0][1]); - std::swap(expected_results[0][6], expected_results[0][2]); - std::swap(expected_results[0][7], expected_results[0][3]); - - std::swap(expected_results[1][2], expected_results[1][0]); - std::swap(expected_results[1][3], expected_results[1][1]); - std::swap(expected_results[1][6], expected_results[1][4]); - std::swap(expected_results[1][7], expected_results[1][5]); - - std::swap(expected_results[2][1], expected_results[2][0]); - std::swap(expected_results[2][3], expected_results[2][2]); - std::swap(expected_results[2][5], expected_results[2][4]); - std::swap(expected_results[2][7], expected_results[2][6]); - } - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("RY", {index}, inverse, {angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::RZ", "[MPSTNCuda_Param]", float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 0.8, 2.4}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - {{0.34958337, -0.05283436}, - {0.34958337, -0.05283436}, - {0.34958337, -0.05283436}, - {0.34958337, -0.05283436}, - {0.34958337, 0.05283436}, - {0.34958337, 0.05283436}, - {0.34958337, 0.05283436}, - {0.34958337, 0.05283436}}, - - {{0.32564424, -0.13768018}, - {0.32564424, -0.13768018}, - {0.32564424, 0.13768018}, - {0.32564424, 0.13768018}, - {0.32564424, -0.13768018}, - {0.32564424, -0.13768018}, - {0.32564424, 0.13768018}, - {0.32564424, 0.13768018}}, - - {{0.12811281, -0.32952558}, - {0.12811281, 0.32952558}, - {0.12811281, -0.32952558}, - {0.12811281, 0.32952558}, - {0.12811281, -0.32952558}, - {0.12811281, 0.32952558}, - {0.12811281, -0.32952558}, - {0.12811281, 0.32952558}}}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("RZ", {index}, inverse, {angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::ControlledPhaseShift", - "[MPSTNCuda_Param]", float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 2.4}; - const TestType sign = (inverse) ? -1.0 : 1.0; - const cp_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( - Pennylane::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][0], - ps_data[1][0], ps_data[1][3], ps_data[1][0], ps_data[1][3]}}; - - for (auto &vec : expected_results) { - scaleVector(vec, coef); - } - - SECTION("Apply adjacent wire indices") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("ControlledPhaseShift", {0, 1}, inverse, - {sign * angles[0]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wire indices") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("ControlledPhaseShift", {0, 2}, inverse, - {sign * angles[1]}); - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::Rot", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - 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 (std::size_t i = 0; i < angles.size(); i++) { - const auto rot_mat = - Pennylane::Gates::getRot( - angles[i][0], angles[i][1], angles[i][2]); - expected_results[i][0] = - inverse ? std::conj(rot_mat[0]) : rot_mat[0]; - expected_results[i][0b1 << (num_qubits - i - 1)] = - inverse ? -rot_mat[2] : rot_mat[2]; - } - - SECTION("Apply at different wire indices") { - const std::size_t index = GENERATE(0, 1, 2); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("Rot", {index}, inverse, angles[index]); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CRot", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = - std::vector{0.3, 0.8, 2.4}; - - std::vector expected_results = - std::vector(0b1 << num_qubits); - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("CRot", {0, 1}, inverse, angles); - - expected_results[0] = cp_t{1, 0}; - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("CRot", {0, 2}, inverse, angles); - - expected_results[0] = cp_t{1, 0}; - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results)); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::IsingXX", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles{0.3, 0.8}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits), - std::vector(1 << num_qubits), - std::vector(1 << num_qubits), - std::vector(1 << num_qubits)}; - - expected_results[0][0] = {0.9887710779360422, 0.0}; - expected_results[0][6] = {0.0, -0.14943813247359922}; - - expected_results[1][0] = {0.9210609940028851, 0.0}; - expected_results[1][6] = {0.0, -0.3894183423086505}; - - expected_results[2][0] = {0.9887710779360422, 0.0}; - expected_results[2][5] = {0.0, -0.14943813247359922}; - - expected_results[3][0] = {0.9210609940028851, 0.0}; - expected_results[3][5] = {0.0, -0.3894183423086505}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - const std::size_t index = GENERATE(0, 1); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("IsingXX", {0, 1}, inverse, - {angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[index])); - } - - SECTION("Apply non-adjacent wires") { - const std::size_t index = GENERATE(0, 1); - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperation("IsingXX", {0, 2}, inverse, - {angles[index]}); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx( - expected_results[index + angles.size()])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::IsingXY", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.35355339, 0.0}), - std::vector(1 << num_qubits, {0.35355339, 0.0}), - }; - - expected_results[0][2] = {0.34958337, 0.05283436}; - expected_results[0][3] = {0.34958337, 0.05283436}; - expected_results[0][4] = {0.34958337, 0.05283436}; - expected_results[0][5] = {0.34958337, 0.05283436}; - - expected_results[1][1] = {0.34958337, 0.05283436}; - expected_results[1][3] = {0.34958337, 0.05283436}; - expected_results[1][4] = {0.34958337, 0.05283436}; - expected_results[1][6] = {0.34958337, 0.05283436}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("IsingXY", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - mps_state.applyOperation("IsingXY", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::IsingYY", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - }; - - expected_results[0][2] = {0.34958337, -0.05283436}; - expected_results[0][3] = {0.34958337, -0.05283436}; - expected_results[0][4] = {0.34958337, -0.05283436}; - expected_results[0][5] = {0.34958337, -0.05283436}; - - expected_results[1][1] = {0.34958337, -0.05283436}; - expected_results[1][3] = {0.34958337, -0.05283436}; - expected_results[1][4] = {0.34958337, -0.05283436}; - expected_results[1][6] = {0.34958337, -0.05283436}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("IsingYY", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("IsingYY", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::IsingZZ", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - }; - - expected_results[0][0] = {0.34958337, -0.05283436}; - expected_results[0][1] = {0.34958337, -0.05283436}; - expected_results[0][6] = {0.34958337, -0.05283436}; - expected_results[0][7] = {0.34958337, -0.05283436}; - - expected_results[1][0] = {0.34958337, -0.05283436}; - expected_results[1][2] = {0.34958337, -0.05283436}; - expected_results[1][5] = {0.34958337, -0.05283436}; - expected_results[1][7] = {0.34958337, -0.05283436}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("IsingZZ", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("IsingZZ", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CRX", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.35355339, 0.0}), - std::vector(1 << num_qubits, {0.35355339, 0.0}), - }; - - expected_results[0][4] = {0.34958337, -0.05283436}; - expected_results[0][5] = {0.34958337, -0.05283436}; - expected_results[0][6] = {0.34958337, -0.05283436}; - expected_results[0][7] = {0.34958337, -0.05283436}; - - expected_results[1][4] = {0.34958337, -0.05283436}; - expected_results[1][5] = {0.34958337, -0.05283436}; - expected_results[1][6] = {0.34958337, -0.05283436}; - expected_results[1][7] = {0.34958337, -0.05283436}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("CRX", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("CRX", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CRY", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.35355339, 0.0}), - std::vector(1 << num_qubits, {0.35355339, 0.0}), - }; - - expected_results[0][4] = {0.29674901, 0.0}; - expected_results[0][5] = {0.29674901, 0.0}; - expected_results[0][6] = {0.40241773, 0.0}; - expected_results[0][7] = {0.40241773, 0.0}; - - if (inverse) { - std::swap(expected_results[0][4], expected_results[0][6]); - std::swap(expected_results[0][5], expected_results[0][7]); - } - - expected_results[1][4] = {0.29674901, 0.0}; - expected_results[1][5] = {0.40241773, 0.0}; - expected_results[1][6] = {0.29674901, 0.0}; - expected_results[1][7] = {0.40241773, 0.0}; - - if (inverse) { - std::swap(expected_results[1][4], expected_results[1][5]); - std::swap(expected_results[1][6], expected_results[1][7]); - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("CRY", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("CRY", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::CRZ", "[MPSTNCuda_param]", float, - double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.35355339, 0.0}), - std::vector(1 << num_qubits, {0.35355339, 0.0}), - }; - - expected_results[0][4] = {0.34958337, -0.05283436}; - expected_results[0][5] = {0.34958337, -0.05283436}; - expected_results[0][6] = {0.34958337, 0.05283436}; - expected_results[0][7] = {0.34958337, 0.05283436}; - - expected_results[1][4] = {0.34958337, -0.05283436}; - expected_results[1][5] = {0.34958337, 0.05283436}; - expected_results[1][6] = {0.34958337, -0.05283436}; - expected_results[1][7] = {0.34958337, 0.05283436}; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("CRZ", {0, 1}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - mps_state.applyOperation("CRZ", {0, 2}, inverse, angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::SingleExcitation", "[MPSTNCuda_param]", - float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.35355339, 0.0}), - std::vector(1 << num_qubits, {0.35355339, 0.0}), - }; - - expected_results[0][2] = {0.29674901, 0.0}; - expected_results[0][3] = {0.29674901, 0.0}; - expected_results[0][4] = {0.40241773, 0.0}; - expected_results[0][5] = {0.40241773, 0.0}; - - expected_results[1][1] = {0.29674901, 0.0}; - expected_results[1][3] = {0.29674901, 0.0}; - expected_results[1][4] = {0.40241773, 0.0}; - expected_results[1][6] = {0.40241773, 0.0}; - - if (inverse) { - std::swap(expected_results[0][2], expected_results[0][4]); - std::swap(expected_results[0][3], expected_results[0][5]); - std::swap(expected_results[1][1], expected_results[1][6]); - std::swap(expected_results[1][3], expected_results[1][4]); - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitation", {0, 1}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitation", {0, 2}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::SingleExcitationMinus", - "[MPSTNCuda_param]", float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.34958337, -0.05283436}), - std::vector(1 << num_qubits, {0.34958337, -0.05283436}), - }; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - expected_results[0][2] = {0.29674901, 0.0}; - expected_results[0][3] = {0.29674901, 0.0}; - expected_results[0][4] = {0.40241773, 0.0}; - expected_results[0][5] = {0.40241773, 0.0}; - - expected_results[1][1] = {0.29674901, 0.0}; - expected_results[1][3] = {0.29674901, 0.0}; - expected_results[1][4] = {0.40241773, 0.0}; - expected_results[1][6] = {0.40241773, 0.0}; - - if (inverse) { - std::swap(expected_results[0][2], expected_results[0][4]); - std::swap(expected_results[0][3], expected_results[0][5]); - std::swap(expected_results[1][1], expected_results[1][6]); - std::swap(expected_results[1][3], expected_results[1][4]); - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitationMinus", {0, 1}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitationMinus", {0, 2}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Gates::SingleExcitationPlus", - "[MPSTNCuda_param]", float, double) { - const bool inverse = GENERATE(false, true); - { - using cp_t = std::complex; - std::size_t num_qubits = 3; - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - const std::vector angles = {0.3}; - - // Results collected from `default.qubit` - std::vector> expected_results{ - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - std::vector(1 << num_qubits, {0.34958337, 0.05283436}), - }; - - for (auto &vec : expected_results) { - for (auto &val : vec) { - val = inverse ? std::conj(val) : val; - } - } - - expected_results[0][2] = {0.29674901, 0.0}; - expected_results[0][3] = {0.29674901, 0.0}; - expected_results[0][4] = {0.40241773, 0.0}; - expected_results[0][5] = {0.40241773, 0.0}; - - expected_results[1][1] = {0.29674901, 0.0}; - expected_results[1][3] = {0.29674901, 0.0}; - expected_results[1][4] = {0.40241773, 0.0}; - expected_results[1][6] = {0.40241773, 0.0}; - - if (inverse) { - std::swap(expected_results[0][2], expected_results[0][4]); - std::swap(expected_results[0][3], expected_results[0][5]); - std::swap(expected_results[1][1], expected_results[1][6]); - std::swap(expected_results[1][3], expected_results[1][4]); - } - - SECTION("Apply adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - mps_state.reset(); - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitationPlus", {0, 1}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[0])); - } - - SECTION("Apply non-adjacent wires") { - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - mps_state.applyOperation("SingleExcitationPlus", {0, 2}, inverse, - angles); - - auto results = mps_state.getDataVector(); - - CHECK(results == Pennylane::Util::approx(expected_results[1])); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::Param_Gates::2+_wires", "[MPSTNCuda_Param]", - float, double) { - const bool inverse = GENERATE(false, true); - { - std::size_t maxExtent = 2; - DevTag dev_tag{0, 0}; - - SECTION("DoubleExcitation gate") { - std::size_t num_qubits = 5; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - REQUIRE_THROWS_AS(mps_state.applyOperation("DoubleExcitation", - {0, 1, 2, 3}, inverse), - LightningException); - } - - SECTION("DoubleExcitationMinus gate") { - std::size_t num_qubits = 5; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - REQUIRE_THROWS_AS(mps_state.applyOperation("DoubleExcitationMinus", - {0, 1, 2, 3}, inverse), - LightningException); - } - - SECTION("DoubleExcitationPlus gate") { - std::size_t num_qubits = 5; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - REQUIRE_THROWS_AS(mps_state.applyOperation("DoubleExcitationPlus", - {0, 1, 2, 3}, inverse), - LightningException); - } - } -} - -TEMPLATE_TEST_CASE("MPSTNCuda::applyMPO::SingleExcitation", "[MPSTNCuda_Param]", - float, double) { - using cp_t = std::complex; - std::size_t maxExtent = 2; - std::size_t max_mpo_bond = 4; - DevTag dev_tag{0, 0}; - - std::vector> mpo_single_excitation( - 2, std::vector(16, {0.0, 0.0})); - - // in-order decomposition of the cnot operator - // data from scipy decompose in the lightning.tensor python layer - mpo_single_excitation[0][0] = {-1.40627352, 0.0}; - mpo_single_excitation[0][3] = {-0.14943813, 0.0}; - mpo_single_excitation[0][6] = {0.00794005, 0.0}; - mpo_single_excitation[0][9] = {-1.40627352, 0.0}; - mpo_single_excitation[0][12] = {-0.14943813, 0.0}; - mpo_single_excitation[0][15] = {-0.00794005, 0.0}; - - mpo_single_excitation[1][0] = {-0.707106781, 0.0}; - mpo_single_excitation[1][3] = {0.707106781, 0.0}; - mpo_single_excitation[1][6] = {1.0, 0.0}; - mpo_single_excitation[1][9] = {-1.0, 0.0}; - mpo_single_excitation[1][12] = {-0.707106781, 0.0}; - mpo_single_excitation[1][15] = {-0.707106781, 0.0}; - - SECTION("Target at wire indices") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitation", {0, 1}, false, {0.3}); - - mps_state_mpo.applyMPOOperation(mpo_single_excitation, {0, 1}, - max_mpo_bond); - - auto ref = mps_state.getDataVector(); - auto res = mps_state_mpo.getDataVector(); - - CHECK(res == Pennylane::Util::approx(ref)); - } - - SECTION("Target at non-adjacent wire indices") { - std::size_t num_qubits = 3; - - MPSTNCuda mps_state{num_qubits, maxExtent, dev_tag}; - - MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; - - mps_state.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, - {{0}, {1}, {2}}, {false, false, false}); - - mps_state.applyOperation("SingleExcitation", {0, 2}, false, {0.3}); - - mps_state_mpo.applyMPOOperation(mpo_single_excitation, {0, 2}, - max_mpo_bond); - - auto ref = mps_state.getDataVector(); - auto res = mps_state_mpo.getDataVector(); - - CHECK(res == Pennylane::Util::approx(ref)); - } -} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_MPO.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_MPO.cpp new file mode 100644 index 0000000000..ff79a770dc --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_MPO.cpp @@ -0,0 +1,231 @@ +// Copyright 2024 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 +#include + +#include + +#include "DevTag.hpp" +#include "MPSTNCuda.hpp" +#include "TNCudaGateCache.hpp" + +#include "TestHelpers.hpp" +#include "TestHelpersTNCuda.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningGPU; +using namespace Pennylane::LightningTensor; +using namespace Pennylane::LightningTensor::TNCuda::Gates; +using namespace Pennylane::Util; +namespace cuUtil = Pennylane::LightningGPU::Util; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +/// @endcond + +TEMPLATE_LIST_TEST_CASE("TNCuda::applyMPO::2+_wires", "[TNCuda_Nonparam]", + TestMPSBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using PrecisionT = typename TNDevice_T::PrecisionT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + constexpr std::size_t max_mpo_bond = 16; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + std::vector> mpo_cnot(2, + std::vector(16, {0.0, 0.0})); + + // in-order decomposition of the cnot operator + // data from scipy decompose in the lightning.tensor python layer + // TODO: this is a temporary solution, it will be removed once SVD + // decomposition is implemented in the C++ layer + mpo_cnot[0][0] = {1.0, 0.0}; + mpo_cnot[0][3] = {-1.0, 0.0}; + mpo_cnot[0][9] = {1.0, 0.0}; + mpo_cnot[0][10] = {-1.0, 0.0}; + + mpo_cnot[1][0] = {1.0, 0.0}; + mpo_cnot[1][7] = {-1.0, 0.0}; + mpo_cnot[1][10] = {1.0, 0.0}; + mpo_cnot[1][13] = {-1.0, 0.0}; + + std::vector> mpo_cswap; + mpo_cswap.emplace_back(std::vector(16, {0.0, 0.0})); + mpo_cswap.emplace_back(std::vector(64, {0.0, 0.0})); + mpo_cswap.emplace_back(std::vector(16, {0.0, 0.0})); + + mpo_cswap[0][0] = {-1.5811388300841898, 0.0}; + mpo_cswap[0][2] = {0.7071067811865475, 0.0}; + mpo_cswap[0][5] = {-1.0, 0.0}; + mpo_cswap[0][9] = mpo_cswap[0][0]; + mpo_cswap[0][11] = -mpo_cswap[0][2]; + mpo_cswap[0][14] = {1.0, 0.0}; + + mpo_cswap[1][0] = {-0.413452607315265, 0.0}; + mpo_cswap[1][1] = {0.6979762349196628, 0.0}; + mpo_cswap[1][7] = {0.9870874576374964, 0.0}; + mpo_cswap[1][8] = {0.5736348503222318, 0.0}; + mpo_cswap[1][9] = {0.11326595025589799, 0.0}; + mpo_cswap[1][15] = {0.16018224300696726, 0.0}; + mpo_cswap[1][34] = -mpo_cswap[1][7]; + mpo_cswap[1][36] = mpo_cswap[1][0]; + mpo_cswap[1][37] = -mpo_cswap[1][1]; + mpo_cswap[1][42] = -mpo_cswap[1][15]; + mpo_cswap[1][44] = mpo_cswap[1][8]; + mpo_cswap[1][45] = -mpo_cswap[1][9]; + + mpo_cswap[2][0] = mpo_cswap[1][15]; + mpo_cswap[2][1] = -mpo_cswap[1][7]; + mpo_cswap[2][7] = {1.0, 0.0}; + mpo_cswap[2][10] = {-1.0, 0.0}; + mpo_cswap[2][12] = -mpo_cswap[2][1]; + mpo_cswap[2][13] = mpo_cswap[2][0]; + + SECTION("Target at wire indices") { + MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("CNOT", {0, 1}, inverse); + + mps_state_mpo.applyMPOOperation(mpo_cnot, {0, 1}, max_mpo_bond); + + auto ref = tn_state->getDataVector(); + auto res = mps_state_mpo.getDataVector(); + + CHECK(res == Pennylane::Util::approx(ref)); + } + + SECTION("Target at non-adjacent wire indices") { + MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("CNOT", {0, 2}, inverse); + + mps_state_mpo.applyMPOOperation(mpo_cnot, {0, 2}, max_mpo_bond); + + auto ref = tn_state->getDataVector(); + auto res = mps_state_mpo.getDataVector(); + + CHECK(res == Pennylane::Util::approx(ref)); + } + + SECTION("Tests for 3-wire MPOs") { + MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; + + mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + mps_state_mpo.applyMPOOperation(mpo_cswap, {0, 1, 2}, max_mpo_bond); + + auto res = mps_state_mpo.getDataVector(); + auto ref = tn_state->getDataVector(); + + CHECK(res == Pennylane::Util::approx(ref)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::applyMPO::SingleExcitation", "[TNCuda_Param]", + TestMPSBackends) { + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + constexpr std::size_t max_mpo_bond = 4; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + std::vector> mpo_single_excitation( + 2, std::vector(16, {0.0, 0.0})); + + // in-order decomposition of the cnot operator + // data from scipy decompose in the lightning.tensor python layer + // TODO: this is a temporary solution, it will be removed once SVD + // decomposition is implemented in the C++ layer + + mpo_single_excitation[0][0] = {-1.40627352, 0.0}; + mpo_single_excitation[0][3] = {-0.14943813, 0.0}; + mpo_single_excitation[0][6] = {0.00794005, 0.0}; + mpo_single_excitation[0][9] = {-1.40627352, 0.0}; + mpo_single_excitation[0][12] = {-0.14943813, 0.0}; + mpo_single_excitation[0][15] = {-0.00794005, 0.0}; + + mpo_single_excitation[1][0] = {-0.707106781, 0.0}; + mpo_single_excitation[1][3] = {0.707106781, 0.0}; + mpo_single_excitation[1][6] = {1.0, 0.0}; + mpo_single_excitation[1][9] = {-1.0, 0.0}; + mpo_single_excitation[1][12] = {-0.707106781, 0.0}; + mpo_single_excitation[1][15] = {-0.707106781, 0.0}; + + SECTION("Target at wire indices") { + MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitation", {0, 1}, false, {0.3}); + + mps_state_mpo.applyMPOOperation(mpo_single_excitation, {0, 1}, + max_mpo_bond); + + auto ref = tn_state->getDataVector(); + auto res = mps_state_mpo.getDataVector(); + + CHECK(res == Pennylane::Util::approx(ref)); + } + + SECTION("Target at non-adjacent wire indices") { + MPSTNCuda mps_state_mpo{num_qubits, maxExtent, dev_tag}; + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + mps_state_mpo.applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitation", {0, 2}, false, {0.3}); + + mps_state_mpo.applyMPOOperation(mpo_single_excitation, {0, 2}, + max_mpo_bond); + + auto ref = tn_state->getDataVector(); + auto res = mps_state_mpo.getDataVector(); + + CHECK(res == Pennylane::Util::approx(ref)); + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_NonParam.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_NonParam.cpp new file mode 100644 index 0000000000..d0443775ca --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_NonParam.cpp @@ -0,0 +1,689 @@ +// Copyright 2024 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 +#include + +#include + +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" +#include "MPSTNCuda.hpp" +#include "TNCudaGateCache.hpp" + +#include "TestHelpers.hpp" +#include "TestHelpersTNCuda.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningGPU; +using namespace Pennylane::LightningTensor; +using namespace Pennylane::LightningTensor::TNCuda::Gates; +using namespace Pennylane::Util; +namespace cuUtil = Pennylane::LightningGPU::Util; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +/// @endcond + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::Identity", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperation("Hadamard", {index}, inverse); + + tn_state->applyOperation("Identity", {index}, inverse); + auto expected = cuUtil::INVSQRT2(); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(expected == Pennylane::Util::approx( + results[0b1 << ((num_qubits - 1 - index))])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::Hadamard", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state_append_mps_final_state(tn_state); + + tn_state->applyOperation("Hadamard", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + tn_state->applyOperation("Identity", {index}, inverse); + + // Test for multiple final states appendings + tn_state_append_mps_final_state(tn_state); + + cp_t expected(1.0 / std::sqrt(2), 0); + + auto results = tn_state->getDataVector(); + + CHECK(expected == Pennylane::Util::approx( + results[0b1 << ((num_qubits - 1 - index))])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::PauliX", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperation("PauliX", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results[0] == cuUtil::ZERO()); + CHECK(results[0b1 << (num_qubits - index - 1)] == cuUtil::ONE()); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::applyOperation-gatematrix", + "[TNCuda_Nonparam]", TestTNBackends) { + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + std::vector gate_matrix = { + cuUtil::ZERO(), cuUtil::ONE(), cuUtil::ONE(), + cuUtil::ZERO()}; + + tn_state->applyOperation("applyMatrix", {index}, false, {}, + gate_matrix); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results[0] == cuUtil::ZERO()); + CHECK(results[0b1 << (num_qubits - index - 1)] == cuUtil::ONE()); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::PauliY", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const cp_t p = cuUtil::ConstMult( + cp_t(0.5, 0.0), + cuUtil::ConstMult(cuUtil::INVSQRT2(), cuUtil::IMAG())); + const cp_t m = cuUtil::ConstMult(cp_t(-1, 0), 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}}; + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("PauliY", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::PauliZ", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const cp_t p(cp_t(0.5, 0.0) * cuUtil::INVSQRT2()); + const cp_t m(cuUtil::ConstMult(cp_t{-1.0, 0.0}, 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}}; + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("PauliZ", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::S", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + cp_t r(cp_t(0.5, 0.0) * cuUtil::INVSQRT2()); + cp_t i(cuUtil::ConstMult(r, cuUtil::IMAG())); + + if (inverse) { + i = std::conj(i); + } + + 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}}; + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("S", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::T", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + std::size_t num_qubits = 3; + std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + cp_t r(1.0 / (2.0 * std::sqrt(2)), 0); + cp_t i(1.0 / 4, 1.0 / 4); + + if (inverse) { + i = conj(i); + } + + 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}}; + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("T", {index}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[index]).margin(1e-8)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CNOT", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + std::size_t num_qubits = 3; + std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply adjacent wire indices") { + tn_state->applyOperations({"Hadamard", "CNOT", "CNOT"}, + {{0}, {0, 1}, {1, 2}}, + {false, inverse, inverse}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results.front() == + Pennylane::Util::approx(cuUtil::INVSQRT2()).epsilon(1e-5)); + CHECK(results.back() == + Pennylane::Util::approx(cuUtil::INVSQRT2()).epsilon(1e-5)); + } + + SECTION("Apply non-adjacent wire indices") { + tn_state->applyOperation("Hadamard", {0}, false); + tn_state->applyOperation("CNOT", {0, 2}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results[0] == + Pennylane::Util::approx(cuUtil::INVSQRT2()).epsilon(1e-5)); + CHECK(results[5] == + Pennylane::Util::approx(cuUtil::INVSQRT2()).epsilon(1e-5)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::SWAP", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply adjacent wire indices") { + std::vector expected{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + cp_t(1.0 / sqrt(2), 0), cuUtil::ZERO(), + cp_t(1.0 / sqrt(2), 0), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("SWAP", {0, 1}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected)); + } + + SECTION("Apply non-adjacent wire indices") { + std::vector expected{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::INVSQRT2(), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("SWAP", {0, 2}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected).margin(1e-5)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CY", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + -cuUtil::INVSQRT2IMAG(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CY", {0, 1}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results).margin(1e-5)); + } + + SECTION("Apply non-adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::INVSQRT2IMAG()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CY", {0, 2}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CZ", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + SECTION("Apply adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + -cuUtil::INVSQRT2(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CZ", {0, 1}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + std::for_each(results.begin(), results.end(), + [](cp_t &val) { val += cuUtil::ONE(); }); + std::for_each(expected_results.begin(), expected_results.end(), + [](cp_t &val) { val += cuUtil::ONE(); }); + + CHECK(expected_results == + Pennylane::Util::approx(results).margin(1e-8)); + } + + SECTION("Apply non-adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CZ", {0, 2}, inverse); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } +} + +TEMPLATE_LIST_TEST_CASE("ExactTNCuda::Gates::CSWAP", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state; + + if constexpr (std::is_same_v> || + std::is_same_v>) { + SECTION("CSWAP gate") { + // Create the object for MPSTNCuda + tn_state = + std::make_unique(num_qubits, maxExtent, dev_tag); + REQUIRE_THROWS_AS( + tn_state->applyOperation("CSWAP", {0, 1, 2}, inverse), + LightningException); + } + } else { + // Create the object for ExactTNCuda + tn_state = std::make_unique(num_qubits, dev_tag); + + SECTION("Apply adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::INVSQRT2(), + cuUtil::ZERO(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CSWAP", {0, 1, 2}, inverse); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } + + SECTION("Apply non-adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cp_t(1.0 / sqrt(2), 0), cp_t(1.0 / sqrt(2), 0), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CSWAP", {1, 0, 2}, inverse); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } + } +} + +TEMPLATE_LIST_TEST_CASE("ExactTNCuda::Gates::Toffoli", "[TNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state; + + if constexpr (std::is_same_v> || + std::is_same_v>) { + SECTION("Toffoli gate") { + std::size_t num_qubits = 3; + // Create the object for MPSTNCuda + tn_state = std::make_unique(num_qubits, maxExtent); + + REQUIRE_THROWS_AS( + tn_state->applyOperation("Toffoli", {0, 1, 2}, inverse), + LightningException); + } + } else { + // Create the object for ExactTNCuda + tn_state = std::make_unique(num_qubits, dev_tag); + + SECTION("Apply adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::INVSQRT2(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::INVSQRT2()}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("Toffoli", {0, 1, 2}, inverse); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } + + SECTION("Apply non-adjacent wire indices") { + std::vector expected_results{ + cuUtil::ZERO(), cuUtil::ZERO(), + cp_t(1.0 / sqrt(2), 0), cuUtil::ZERO(), + cuUtil::ZERO(), cuUtil::ZERO(), + cuUtil::ZERO(), cp_t(1.0 / sqrt(2), 0)}; + + tn_state->applyOperations({"Hadamard", "PauliX"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("Toffoli", {1, 0, 2}, inverse); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::applyControlledOperation non-param " + "one-qubit with controls", + "[TNCuda]", TestTNBackends) { + using TNDevice_T = TestType; + using ComplexT = typename TNDevice_T::ComplexT; + using PrecisionT = typename TNDevice_T::PrecisionT; + + constexpr int num_qubits = 4; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state0 = + createTNState(num_qubits, maxExtent); + std::unique_ptr tn_state1 = + createTNState(num_qubits, maxExtent); + + const auto margin = PrecisionT{1e-5}; + const std::size_t control = GENERATE(0, 1, 2, 3); + const std::size_t wire = GENERATE(0, 1, 2, 3); + + DYNAMIC_SECTION("Controlled gates with base operation - " + << "controls = {" << control << "} " + << ", wires = {" << wire << "} - " + << PrecisionToName::value) { + if (control != wire) { + tn_state0->applyControlledOperation( + "PauliX", std::vector{control}, + std::vector{true}, std::vector{wire}); + + tn_state1->applyOperation( + "CNOT", std::vector{control, wire}, false); + + REQUIRE(tn_state0->getDataVector() == + approx(tn_state1->getDataVector()).margin(margin)); + } + } + + DYNAMIC_SECTION("Controlled gates with a target matrix - " + << "controls = {" << control << "} " + << ", wires = {" << wire << "} - " + << PrecisionToName::value) { + if (control != wire) { + std::vector gate_matrix = { + ComplexT{0.0, 0.0}, ComplexT{1.0, 0.0}, ComplexT{1.0, 0.0}, + ComplexT{0.0, 0.0}}; + tn_state0->applyControlledOperation( + "applyControlledGates", std::vector{control}, + std::vector{true}, std::vector{wire}, false, + {}, gate_matrix); + + tn_state1->applyOperation( + "CNOT", std::vector{control, wire}, false); + + REQUIRE(tn_state0->getDataVector() == + approx(tn_state1->getDataVector()).margin(margin)); + } + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_Param.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_Param.cpp new file mode 100644 index 0000000000..739b56f683 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/gates/tests/Test_TNCuda_Param.cpp @@ -0,0 +1,1451 @@ +// Copyright 2024 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 +#include + +#include + +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" +#include "Gates.hpp" +#include "MPSTNCuda.hpp" +#include "TNCudaGateCache.hpp" + +#include "TestHelpers.hpp" +#include "TestHelpersTNCuda.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningGPU; +using namespace Pennylane::LightningTensor; +using namespace Pennylane::LightningTensor::TNCuda::Gates; +using namespace Pennylane::Util; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +/// @endcond + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::PhaseShift", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 0.8, 2.4}; + const Precision_T sign = (inverse) ? -1.0 : 1.0; + const cp_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( + Pennylane::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); + } + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("PhaseShift", {index}, inverse, + {sign * angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[index]).margin(1e-5)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::RX", "[TNCuda_Param]", TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 0.8, 2.4}; + + // Results from default.qubit + std::vector results = {{0.34958337, -0.05283436}, + {0.32564424, -0.13768018}, + {0.12811281, -0.32952558}}; + + for (auto &val : results) { + val = inverse ? std::conj(val) : val; + } + + std::vector> expected_results{ + std::vector(std::size_t{1} << num_qubits, results[0]), + std::vector(std::size_t{1} << num_qubits, results[1]), + std::vector(std::size_t{1} << num_qubits, results[2]), + }; + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("RX", {index}, inverse, {angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +/* python code to get the reference values for a single parameter +import pennylane as qml + +qubits = 3 +dev = qml.device('default.qubit', wires=qubits) + +gate = qml.RY + +invert = False +gate = qml.adjoint(gate) if invert else gate +wires=0 + +@qml.qnode(dev) +def circuit(): + [qml.H(i) for i in range(qubits)] + gate(0.3, wires=wires) + return qml.state() + +result = circuit() +[print(f"{i:3d} : ",r) for i,r in enumerate(result)] +*/ + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::RY", "[MPSTNCuda_Nonparam]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 0.8, 2.4}; + + // Results from default.qubit + std::vector> expected_results{{{0.29674901, 0}, + {0.29674901, 0}, + {0.29674901, 0}, + {0.29674901, 0}, + {0.40241773, 0}, + {0.40241773, 0}, + {0.40241773, 0}, + {0.40241773, 0}}, + {{0.18796406, 0}, + {0.18796406, 0}, + {0.46332441, 0}, + {0.46332441, 0}, + {0.18796406, 0}, + {0.18796406, 0}, + {0.46332441, 0}, + {0.46332441, 0}}, + {{-0.20141277, 0}, + {0.45763839, 0}, + {-0.20141277, 0}, + {0.45763839, 0}, + {-0.20141277, 0}, + {0.45763839, 0}, + {-0.20141277, 0}, + {0.45763839, 0}}}; + + if (inverse) { + std::swap(expected_results[0][4], expected_results[0][0]); + std::swap(expected_results[0][5], expected_results[0][1]); + std::swap(expected_results[0][6], expected_results[0][2]); + std::swap(expected_results[0][7], expected_results[0][3]); + + std::swap(expected_results[1][2], expected_results[1][0]); + std::swap(expected_results[1][3], expected_results[1][1]); + std::swap(expected_results[1][6], expected_results[1][4]); + std::swap(expected_results[1][7], expected_results[1][5]); + + std::swap(expected_results[2][1], expected_results[2][0]); + std::swap(expected_results[2][3], expected_results[2][2]); + std::swap(expected_results[2][5], expected_results[2][4]); + std::swap(expected_results[2][7], expected_results[2][6]); + } + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("RY", {index}, inverse, {angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::RZ", "[TNCuda_Param]", TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 0.8, 2.4}; + + // Results collected from `default.qubit` + std::vector> expected_results{{{0.34958337, -0.05283436}, + {0.34958337, -0.05283436}, + {0.34958337, -0.05283436}, + {0.34958337, -0.05283436}, + {0.34958337, 0.05283436}, + {0.34958337, 0.05283436}, + {0.34958337, 0.05283436}, + {0.34958337, 0.05283436}}, + + {{0.32564424, -0.13768018}, + {0.32564424, -0.13768018}, + {0.32564424, 0.13768018}, + {0.32564424, 0.13768018}, + {0.32564424, -0.13768018}, + {0.32564424, -0.13768018}, + {0.32564424, 0.13768018}, + {0.32564424, 0.13768018}}, + + {{0.12811281, -0.32952558}, + {0.12811281, 0.32952558}, + {0.12811281, -0.32952558}, + {0.12811281, 0.32952558}, + {0.12811281, -0.32952558}, + {0.12811281, 0.32952558}, + {0.12811281, -0.32952558}, + {0.12811281, 0.32952558}}}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("RZ", {index}, inverse, {angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::ControlledPhaseShift", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 2.4}; + const Precision_T sign = (inverse) ? -1.0 : 1.0; + const cp_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( + Pennylane::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][0], + ps_data[1][0], ps_data[1][3], ps_data[1][0], ps_data[1][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + SECTION("Apply adjacent wire indices") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + tn_state->applyOperation("ControlledPhaseShift", {0, 1}, inverse, + {sign * angles[0]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-6)); + } + + SECTION("Apply non-adjacent wire indices") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("ControlledPhaseShift", {0, 2}, inverse, + {sign * angles[1]}); + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-6)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::Rot", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + 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 (std::size_t i = 0; i < angles.size(); i++) { + const auto rot_mat = + Pennylane::Gates::getRot( + angles[i][0], angles[i][1], angles[i][2]); + expected_results[i][0] = inverse ? std::conj(rot_mat[0]) : rot_mat[0]; + expected_results[i][0b1 << (num_qubits - i - 1)] = + inverse ? -rot_mat[2] : rot_mat[2]; + } + + SECTION("Apply at different wire indices") { + const std::size_t index = GENERATE(0, 1, 2); + + tn_state->applyOperation("Rot", {index}, inverse, angles[index]); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CRot", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = + std::vector{0.3, 0.8, 2.4}; + + std::vector expected_results = std::vector(0b1 << num_qubits); + + SECTION("Apply adjacent wires") { + tn_state->applyOperation("CRot", {0, 1}, inverse, angles); + + expected_results[0] = cp_t{1, 0}; + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperation("CRot", {0, 2}, inverse, angles); + + expected_results[0] = cp_t{1, 0}; + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results)); + } +} + +/* python code to get the reference values for a single parameter with 2 target +wires import pennylane as qml + +qubits = 3 +dev = qml.device('default.qubit', wires=qubits) + +gate = qml.IsingXX + +invert = False +gate = qml.adjoint(gate) if invert else gate +adjacent = True +wires = [0, 1] if adjacent else [0,2] + +@qml.qnode(dev) +def circuit(): + # [qml.H(i) for i in range(qubits)] + gate(0.3, wires=wires) + return qml.state() + +result = circuit() +[print(f"{i:3d} : ",r) for i,r in enumerate(result)] +*/ + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::IsingXX", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles{0.3, 0.8}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits), std::vector(1 << num_qubits), + std::vector(1 << num_qubits), std::vector(1 << num_qubits)}; + + expected_results[0][0] = {0.9887710779360422, 0.0}; + expected_results[0][6] = {0.0, -0.14943813247359922}; + + expected_results[1][0] = {0.9210609940028851, 0.0}; + expected_results[1][6] = {0.0, -0.3894183423086505}; + + expected_results[2][0] = {0.9887710779360422, 0.0}; + expected_results[2][5] = {0.0, -0.14943813247359922}; + + expected_results[3][0] = {0.9210609940028851, 0.0}; + expected_results[3][5] = {0.0, -0.3894183423086505}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + const std::size_t index = GENERATE(0, 1); + + tn_state->applyOperation("IsingXX", {0, 1}, inverse, {angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[index])); + } + + SECTION("Apply non-adjacent wires") { + const std::size_t index = GENERATE(0, 1); + + tn_state->applyOperation("IsingXX", {0, 2}, inverse, {angles[index]}); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[index + angles.size()])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::IsingXY", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.35355339, 0.0}), + std::vector(1 << num_qubits, {0.35355339, 0.0}), + }; + + expected_results[0][2] = {0.34958337, 0.05283436}; + expected_results[0][3] = {0.34958337, 0.05283436}; + expected_results[0][4] = {0.34958337, 0.05283436}; + expected_results[0][5] = {0.34958337, 0.05283436}; + + expected_results[1][1] = {0.34958337, 0.05283436}; + expected_results[1][3] = {0.34958337, 0.05283436}; + expected_results[1][4] = {0.34958337, 0.05283436}; + expected_results[1][6] = {0.34958337, 0.05283436}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("IsingXY", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-6)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + tn_state->applyOperation("IsingXY", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-6)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::IsingYY", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + }; + + expected_results[0][2] = {0.34958337, -0.05283436}; + expected_results[0][3] = {0.34958337, -0.05283436}; + expected_results[0][4] = {0.34958337, -0.05283436}; + expected_results[0][5] = {0.34958337, -0.05283436}; + + expected_results[1][1] = {0.34958337, -0.05283436}; + expected_results[1][3] = {0.34958337, -0.05283436}; + expected_results[1][4] = {0.34958337, -0.05283436}; + expected_results[1][6] = {0.34958337, -0.05283436}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("IsingYY", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("IsingYY", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::IsingZZ", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + }; + + expected_results[0][0] = {0.34958337, -0.05283436}; + expected_results[0][1] = {0.34958337, -0.05283436}; + expected_results[0][6] = {0.34958337, -0.05283436}; + expected_results[0][7] = {0.34958337, -0.05283436}; + + expected_results[1][0] = {0.34958337, -0.05283436}; + expected_results[1][2] = {0.34958337, -0.05283436}; + expected_results[1][5] = {0.34958337, -0.05283436}; + expected_results[1][7] = {0.34958337, -0.05283436}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("IsingZZ", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("IsingZZ", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } +} + +/* python code to get the reference values for a single parameter with 2 target +for CRX import pennylane as qml + +qubits = 3 +dev = qml.device('default.qubit', wires=qubits) + +gate = qml.CRX + +invert = False +gate = qml.adjoint(gate) if invert else gate +adjacent = True +wires = [0,1] if adjacent else [0,2] + +@qml.qnode(dev) +def circuit(): + [qml.H(i) for i in range(qubits-1)] + gate(0.3, wires=wires) + + return qml.state() + +result = circuit() +[print(f"{i:3d} : ",r) for i,r in enumerate(result)] +*/ +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CRX", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.0, 0.0}), + std::vector(1 << num_qubits, {0.0, 0.0}), + }; + + // adjacent wires + expected_results[0][0] = {0.5, 0.0}; + expected_results[0][2] = {0.5, 0.0}; + expected_results[0][4] = {0.49438553, -0.07471906}; + expected_results[0][6] = {0.49438553, -0.07471906}; + // non - adjacent wires + expected_results[1][0] = {0.5, 0.0}; + expected_results[1][2] = {0.5, 0.0}; + expected_results[1][4] = {0.49438553, 0.0}; + expected_results[1][6] = {0.49438553, 0.0}; + expected_results[1][5] = {0.0, -0.07471906}; + expected_results[1][7] = {0.0, -0.07471906}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CRX", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-6)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard"}, {{0}, {1}}, + {false, false}); + + tn_state->applyOperation("CRX", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-6)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CRY", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.35355339, 0.0}), + std::vector(1 << num_qubits, {0.35355339, 0.0}), + }; + + expected_results[0][4] = {0.29674901, 0.0}; + expected_results[0][5] = {0.29674901, 0.0}; + expected_results[0][6] = {0.40241773, 0.0}; + expected_results[0][7] = {0.40241773, 0.0}; + + if (inverse) { + std::swap(expected_results[0][4], expected_results[0][6]); + std::swap(expected_results[0][5], expected_results[0][7]); + } + + expected_results[1][4] = {0.29674901, 0.0}; + expected_results[1][5] = {0.40241773, 0.0}; + expected_results[1][6] = {0.29674901, 0.0}; + expected_results[1][7] = {0.40241773, 0.0}; + + if (inverse) { + std::swap(expected_results[1][4], expected_results[1][5]); + std::swap(expected_results[1][6], expected_results[1][7]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("CRY", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("CRY", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::CRZ", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.35355339, 0.0}), + std::vector(1 << num_qubits, {0.35355339, 0.0}), + }; + + expected_results[0][4] = {0.34958337, -0.05283436}; + expected_results[0][5] = {0.34958337, -0.05283436}; + expected_results[0][6] = {0.34958337, 0.05283436}; + expected_results[0][7] = {0.34958337, 0.05283436}; + + expected_results[1][4] = {0.34958337, -0.05283436}; + expected_results[1][5] = {0.34958337, 0.05283436}; + expected_results[1][6] = {0.34958337, -0.05283436}; + expected_results[1][7] = {0.34958337, 0.05283436}; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("CRZ", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-6)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + tn_state->applyOperation("CRZ", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-6)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::SingleExcitation", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.35355339, 0.0}), + std::vector(1 << num_qubits, {0.35355339, 0.0}), + }; + + expected_results[0][2] = {0.29674901, 0.0}; + expected_results[0][3] = {0.29674901, 0.0}; + expected_results[0][4] = {0.40241773, 0.0}; + expected_results[0][5] = {0.40241773, 0.0}; + + expected_results[1][1] = {0.29674901, 0.0}; + expected_results[1][3] = {0.29674901, 0.0}; + expected_results[1][4] = {0.40241773, 0.0}; + expected_results[1][6] = {0.40241773, 0.0}; + + if (inverse) { + std::swap(expected_results[0][2], expected_results[0][4]); + std::swap(expected_results[0][3], expected_results[0][5]); + std::swap(expected_results[1][1], expected_results[1][6]); + std::swap(expected_results[1][3], expected_results[1][4]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitation", {0, 1}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitation", {0, 2}, inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::SingleExcitationMinus", + "[TNCuda_Param]", TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.34958337, -0.05283436}), + std::vector(1 << num_qubits, {0.34958337, -0.05283436}), + }; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + expected_results[0][2] = {0.29674901, 0.0}; + expected_results[0][3] = {0.29674901, 0.0}; + expected_results[0][4] = {0.40241773, 0.0}; + expected_results[0][5] = {0.40241773, 0.0}; + + expected_results[1][1] = {0.29674901, 0.0}; + expected_results[1][3] = {0.29674901, 0.0}; + expected_results[1][4] = {0.40241773, 0.0}; + expected_results[1][6] = {0.40241773, 0.0}; + + if (inverse) { + std::swap(expected_results[0][2], expected_results[0][4]); + std::swap(expected_results[0][3], expected_results[0][5]); + std::swap(expected_results[1][1], expected_results[1][6]); + std::swap(expected_results[1][3], expected_results[1][4]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitationMinus", {0, 1}, inverse, + angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-7)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitationMinus", {0, 2}, inverse, + angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-7)); + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::SingleExcitationPlus", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 3; + constexpr std::size_t maxExtent = 2; + + std::unique_ptr tn_state = + createTNState(num_qubits, maxExtent); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + std::vector(1 << num_qubits, {0.34958337, 0.05283436}), + }; + + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = inverse ? std::conj(val) : val; + } + } + + expected_results[0][2] = {0.29674901, 0.0}; + expected_results[0][3] = {0.29674901, 0.0}; + expected_results[0][4] = {0.40241773, 0.0}; + expected_results[0][5] = {0.40241773, 0.0}; + + expected_results[1][1] = {0.29674901, 0.0}; + expected_results[1][3] = {0.29674901, 0.0}; + expected_results[1][4] = {0.40241773, 0.0}; + expected_results[1][6] = {0.40241773, 0.0}; + + if (inverse) { + std::swap(expected_results[0][2], expected_results[0][4]); + std::swap(expected_results[0][3], expected_results[0][5]); + std::swap(expected_results[1][1], expected_results[1][6]); + std::swap(expected_results[1][3], expected_results[1][4]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + + tn_state->applyOperation("SingleExcitationPlus", {0, 1}, inverse, + angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[0]).margin(1e-5)); + } + + SECTION("Apply non-adjacent wires") { + tn_state->applyOperations({"Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}}, {false, false, false}); + tn_state->applyOperation("SingleExcitationPlus", {0, 2}, inverse, + angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == + Pennylane::Util::approx(expected_results[1]).margin(1e-5)); + } +} + +/* python code to get the reference values for a single parameter with 4 target + wires + + + import pennylane as qml + + qubits = 5 + dev = qml.device('default.qubit', wires=qubits) + + gate = qml.DoubleExcitationPlus + + invert = True + gate = qml.adjoint(gate) if invert else gate + adjacent = True + wires = [0,1,2,3] if adjacent else [0,1,3,4] + + @qml.qnode(dev) + def circuit(): + [qml.H(i) for i in range(qubits)] + gate(0.3, wires=wires) + + return qml.state() + result = circuit() + [print(f"{i:3d} : ",r) for i,r in enumerate(result)] +*/ + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::DoubleExcitation", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 5; + constexpr std::size_t maxExtent = 2; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state; + + if constexpr (std::is_same_v> || + std::is_same_v>) { + // Create the object for MPSTNCuda + tn_state = std::make_unique(num_qubits, maxExtent, dev_tag); + + REQUIRE_THROWS_AS( + tn_state->applyOperation("DoubleExcitation", {0, 1, 2, 3}, inverse), + LightningException); + } else { + // Create the object for ExactTNCuda + tn_state = std::make_unique(num_qubits, dev_tag); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.17677669, 0.0}), + std::vector(1 << num_qubits, {0.17677669, 0.0}), + }; + + expected_results[0][6] = {0.14837450, 0.0}; + expected_results[0][7] = {0.14837450, 0.0}; + expected_results[0][24] = {0.20120886, 0.0}; + expected_results[0][25] = {0.20120886, 0.0}; + + expected_results[1][3] = {0.14837450, 0.0}; + expected_results[1][7] = {0.14837450, 0.0}; + expected_results[1][24] = {0.20120886, 0.0}; + expected_results[1][28] = {0.20120886, 0.0}; + + if (inverse) { + std::swap(expected_results[0][6], expected_results[0][24]); + std::swap(expected_results[0][7], expected_results[0][25]); + std::swap(expected_results[1][3], expected_results[1][24]); + std::swap(expected_results[1][7], expected_results[1][28]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + + tn_state->applyOperation("DoubleExcitation", {0, 1, 2, 3}, inverse, + angles); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + tn_state->applyOperation("DoubleExcitation", {0, 1, 3, 4}, inverse, + angles); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::DoubleExcitationMinus", + "[TNCuda_Param]", TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 5; + constexpr std::size_t maxExtent = 2; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state; + + if constexpr (std::is_same_v> || + std::is_same_v>) { + // Create the object for MPSTNCuda + tn_state = std::make_unique(num_qubits, maxExtent, dev_tag); + + REQUIRE_THROWS_AS(tn_state->applyOperation("DoubleMinusExcitationMinus", + {0, 1, 2, 3}, inverse), + LightningException); + } else { + // Create the object for ExactTNCuda + tn_state = std::make_unique(num_qubits, dev_tag); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.17479168, -0.02641717}), + std::vector(1 << num_qubits, {0.17479168, -0.02641717}), + + }; + + expected_results[0][6] = {0.14837450, 0.0}; + expected_results[0][7] = {0.14837450, 0.0}; + expected_results[0][24] = {0.20120886, 0.0}; + expected_results[0][25] = {0.20120886, 0.0}; + + expected_results[1][3] = {0.14837450, 0.0}; + expected_results[1][7] = {0.14837450, 0.0}; + expected_results[1][24] = {0.20120886, 0.0}; + expected_results[1][28] = {0.20120886, 0.0}; + + if (inverse) { + for (size_t i = 0; i < expected_results[0].size(); i++) { + expected_results[0][i] = std::conj(expected_results[0][i]); + expected_results[1][i] = std::conj(expected_results[1][i]); + } + + std::swap(expected_results[0][6], expected_results[0][24]); + std::swap(expected_results[0][7], expected_results[0][25]); + std::swap(expected_results[1][3], expected_results[1][24]); + std::swap(expected_results[1][7], expected_results[1][28]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + + tn_state->applyOperation("DoubleExcitationMinus", {0, 1, 2, 3}, + inverse, angles); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + tn_state->applyOperation("DoubleExcitationMinus", {0, 1, 3, 4}, + inverse, angles); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } + } +} + +TEMPLATE_LIST_TEST_CASE("TNCuda::Gates::DoubleExcitationPlus", "[TNCuda_Param]", + TestTNBackends) { + const bool inverse = GENERATE(false, true); + using TNDevice_T = TestType; + using cp_t = typename TNDevice_T::ComplexT; + using Precision_T = typename TNDevice_T::PrecisionT; + constexpr std::size_t num_qubits = 5; + constexpr std::size_t maxExtent = 2; + DevTag dev_tag{0, 0}; + + std::unique_ptr tn_state; + + if constexpr (std::is_same_v> || + std::is_same_v>) { + // Create the object for MPSTNCuda + tn_state = std::make_unique(num_qubits, maxExtent, dev_tag); + + REQUIRE_THROWS_AS(tn_state->applyOperation("DoubleMinusExcitationPlus", + {0, 1, 2, 3}, inverse), + LightningException); + } else { + // Create the object for ExactTNCuda + tn_state = std::make_unique(num_qubits, dev_tag); + + const std::vector angles = {0.3}; + + // Results collected from `default.qubit` + std::vector> expected_results{ + std::vector(1 << num_qubits, {0.17479168, 0.02641717}), + std::vector(1 << num_qubits, {0.17479168, 0.02641717}), + + }; + + expected_results[0][6] = {0.14837450, 0.0}; + expected_results[0][7] = {0.14837450, 0.0}; + expected_results[0][24] = {0.20120886, 0.0}; + expected_results[0][25] = {0.20120886, 0.0}; + + expected_results[1][3] = {0.14837450, 0.0}; + expected_results[1][7] = {0.14837450, 0.0}; + expected_results[1][24] = {0.20120886, 0.0}; + expected_results[1][28] = {0.20120886, 0.0}; + + if (inverse) { + for (auto &vec : expected_results) { + for (auto &val : vec) { + val = std::conj(val); + } + } + + std::swap(expected_results[0][6], expected_results[0][24]); + std::swap(expected_results[0][7], expected_results[0][25]); + std::swap(expected_results[1][3], expected_results[1][24]); + std::swap(expected_results[1][7], expected_results[1][28]); + } + + SECTION("Apply adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + + tn_state->applyOperation("DoubleExcitationPlus", {0, 1, 2, 3}, + inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[0])); + } + + SECTION("Apply non-adjacent wires") { + tn_state->reset(); + + tn_state->applyOperations( + {"Hadamard", "Hadamard", "Hadamard", "Hadamard", "Hadamard"}, + {{0}, {1}, {2}, {3}, {4}}, {false, false, false, false, false}); + tn_state->applyOperation("DoubleExcitationPlus", {0, 1, 3, 4}, + inverse, angles); + + tn_state_append_mps_final_state(tn_state); + + auto results = tn_state->getDataVector(); + + CHECK(results == Pennylane::Util::approx(expected_results[1])); + } + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/CMakeLists.txt index e18e336449..bafc8c6fac 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/CMakeLists.txt @@ -15,13 +15,13 @@ FetchAndIncludeCatch() ################################################################################ add_library(${PL_BACKEND}_measurements_tests INTERFACE) -target_link_libraries(${PL_BACKEND}_measurements_tests INTERFACE Catch2::Catch2 - ${PL_BACKEND}_gates - ${PL_BACKEND}_utils - ${PL_BACKEND}_measurements - ${PL_BACKEND}_observables - ${PL_BACKEND} - ) +target_link_libraries(${PL_BACKEND}_measurements_tests INTERFACE Catch2::Catch2 + ${PL_BACKEND} + ${PL_BACKEND}_gates + ${PL_BACKEND}_utils + ${PL_BACKEND}_measurements + ${PL_BACKEND}_observables + ${PL_BACKEND}_tncuda_utils) ProcessTestOptions(${PL_BACKEND}_measurements_tests) @@ -30,9 +30,9 @@ target_sources(${PL_BACKEND}_measurements_tests INTERFACE runner_${PL_BACKEND}_m ################################################################################ # Define targets ################################################################################ -set(TEST_SOURCES Test_MPSTNCuda_Expval.cpp - Test_MPSTNCuda_Var.cpp - Test_MPSTNCuda_Measure.cpp) +set(TEST_SOURCES Test_TNCuda_Expval.cpp + Test_TNCuda_Var.cpp + Test_TNCuda_Measure.cpp) add_executable(${PL_BACKEND}_measurements_test_runner ${TEST_SOURCES}) target_link_libraries(${PL_BACKEND}_measurements_test_runner PRIVATE ${PL_BACKEND}_measurements_tests) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Expval.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Expval.cpp similarity index 52% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Expval.cpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Expval.cpp index 21b809a301..87bc06e35a 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Expval.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Expval.cpp @@ -22,80 +22,92 @@ #include +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" #include "MPSTNCuda.hpp" #include "MeasurementsTNCuda.hpp" #include "TNCudaGateCache.hpp" #include "cuda_helpers.hpp" +#include "TestHelpersTNCuda.hpp" + /// @cond DEV namespace { using namespace Pennylane::LightningTensor::TNCuda::Measures; using namespace Pennylane::LightningTensor::TNCuda::Observables; +using namespace Pennylane::LightningTensor::TNCuda::Util; + } // namespace /// @endcond -TEMPLATE_TEST_CASE("[Identity]", "[MPSTNCuda_Expval]", float, double) { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; - auto ONE = TestType(1); +TEMPLATE_LIST_TEST_CASE("[Identity]", "[TNCuda_Expval]", TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; + auto ONE = PrecisionT(1); - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); SECTION("Using expval") { - mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + tn_state->applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, {{0}, {0, 1}, {1, 2}}, {{false}, {false}, {false}}); - mps_state.append_mps_final_state(); + + tn_state_append_mps_final_state(tn_state); + auto ob = NamedObsT("Identity", {0}); auto res = measure.expval(ob); CHECK(res == Approx(ONE)); } } -TEMPLATE_TEST_CASE("[PauliX]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[PauliX]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); - auto ZERO = TestType(0); - auto ONE = TestType(1); + auto ZERO = PrecisionT(0); + auto ONE = PrecisionT(1); SECTION("Using expval") { - mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + tn_state->applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, {{0}, {0, 1}, {1, 2}}, {{false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliX", {0}); auto res = measure.expval(ob); CHECK(res == ZERO); } SECTION("Using expval: Plus states") { - mps_state.applyOperations( + tn_state->applyOperations( {{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliX", {0}); auto res = measure.expval(ob); CHECK(res == Approx(ONE)); } SECTION("Using expval: Minus states") { - mps_state.applyOperations( + tn_state->applyOperations( {{"PauliX"}, {"Hadamard"}, {"PauliX"}, @@ -104,7 +116,7 @@ TEMPLATE_TEST_CASE("[PauliX]", "[MPSTNCuda_Expval]", float, double) { {"Hadamard"}}, {{0}, {0}, {1}, {1}, {2}, {2}}, {{false}, {false}, {false}, {false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliX", {0}); auto res = measure.expval(ob); CHECK(res == -Approx(ONE)); @@ -112,25 +124,27 @@ TEMPLATE_TEST_CASE("[PauliX]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("[PauliY]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[PauliY]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); - auto ZERO = TestType(0); - auto ONE = TestType(1); - auto PI = TestType(M_PI); + auto ZERO = PrecisionT(0); + auto ONE = PrecisionT(1); + auto PI = PrecisionT(M_PI); SECTION("Using expval") { - mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + tn_state->applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, {{0}, {0, 1}, {1, 2}}, {{false}, {false}, {false}}); auto ob = NamedObsT("PauliY", {0}); @@ -139,7 +153,7 @@ TEMPLATE_TEST_CASE("[PauliY]", "[MPSTNCuda_Expval]", float, double) { } SECTION("Using expval: Plus i states") { - mps_state.applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, + tn_state->applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}, {{-PI / 2}, {-PI / 2}, {-PI / 2}}); auto ob = NamedObsT("PauliY", {0}); @@ -148,7 +162,7 @@ TEMPLATE_TEST_CASE("[PauliY]", "[MPSTNCuda_Expval]", float, double) { } SECTION("Using expval: Minus i states") { - mps_state.applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, + tn_state->applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}, {{PI / 2}, {PI / 2}, {PI / 2}}); auto ob = NamedObsT("PauliY", {0}); @@ -158,24 +172,25 @@ TEMPLATE_TEST_CASE("[PauliY]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("[PauliZ]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[PauliZ]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using PrecisionT = TensorNetT::PrecisionT; - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + using NamedObsT = NamedObsTNCuda; + + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); SECTION("Using expval") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}, {{0.5}, {}, {}}); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); auto ob = NamedObsT("PauliZ", {0}); auto res = m.expval(ob); PrecisionT ref = 0.8775825618903724; @@ -185,7 +200,7 @@ TEMPLATE_TEST_CASE("[PauliZ]", "[MPSTNCuda_Expval]", float, double) { SECTION("Using expval mps with cutoff") { double cutoff = GENERATE(1e-1, 1e-2); std::string cutoff_mode = GENERATE("rel", "abs"); - mps_state.applyOperations( + tn_state->applyOperations( {{"Hadamard"}, {"Hadamard"}, {"Hadamard"}, @@ -195,8 +210,13 @@ TEMPLATE_TEST_CASE("[PauliZ]", "[MPSTNCuda_Expval]", float, double) { {{0}, {1}, {2}, {0, 1}, {1, 2}, {0, 2}}, {{false}, {false}, {false}, {false}, {false}, {false}}, {{}, {}, {}, {0.5}, {0.6}, {0.7}}); - mps_state.append_mps_final_state(cutoff, cutoff_mode); - auto m = MeasurementsTNCuda(mps_state); + + if constexpr (std::is_same_v> || + std::is_same_v>) { + tn_state->append_mps_final_state(cutoff, cutoff_mode); + } + + auto m = MeasurementsTNCuda(*tn_state); auto ob = NamedObsT("PauliZ", {0}); auto res = m.expval(ob); // ref is from default.qubit @@ -208,32 +228,34 @@ TEMPLATE_TEST_CASE("[PauliZ]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("[Hadamard]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[Hadamard]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; - - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; - TensorNetT mps_state{num_qubits, maxBondDim}; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - auto measure = MeasurementsTNCuda(mps_state); + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto INVSQRT2 = TestType(0.707106781186547524401); + auto measure = MeasurementsTNCuda(*tn_state); - auto ONE = TestType(1); + auto ONE = PrecisionT(1); // NOTE: Following tests show that the current design can be measured // multiple times with different observables SECTION("Using expval") { - mps_state.applyOperation("PauliX", {0}); - mps_state.append_mps_final_state(); + tn_state->applyOperation("PauliX", {0}); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("Hadamard", {0}); auto res = measure.expval(ob); - CHECK(res == Approx(-INVSQRT2).epsilon(1e-7)); + CHECK( + res == + Approx(-Pennylane::Util::INVSQRT2()).epsilon(1e-7)); auto ob1 = NamedObsT("Identity", {0}); auto res1 = measure.expval(ob1); @@ -242,23 +264,26 @@ TEMPLATE_TEST_CASE("[Hadamard]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("[Parametric_obs]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[Parametric_obs]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); - auto ONE = TestType(1); + auto measure = MeasurementsTNCuda(*tn_state); + + auto ONE = PrecisionT(1); SECTION("Using expval") { - mps_state.applyOperation("PauliX", {0}); - mps_state.append_mps_final_state(); + tn_state->applyOperation("PauliX", {0}); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("RX", {0}, {0}); auto res = measure.expval(ob); @@ -267,48 +292,50 @@ TEMPLATE_TEST_CASE("[Parametric_obs]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("[Hermitian]", "[MPSTNCuda_Expval]", float, double) { +TEMPLATE_LIST_TEST_CASE("[Hermitian]", "[TNCuda_Expval]", TestTNBackends) { { - using TensorNetT = MPSTNCuda; - using ComplexT = typename TensorNetT::ComplexT; - using HermitianObsT = HermitianObsTNCuda; + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using ComplexT = typename TNDeviceT::ComplexT; + using HermitianObsT = HermitianObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); - auto ZERO = TestType(0); - auto ONE = TestType(1); + auto ZERO = PrecisionT(0); + auto ONE = PrecisionT(1); std::vector mat = { {0.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}}; SECTION("Using expval") { - mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + tn_state->applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, {{0}, {0, 1}, {1, 2}}, {{false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = HermitianObsT(mat, std::vector{0}); auto res = measure.expval(ob); CHECK(res == ZERO); } SECTION("Using expval: Plus states") { - mps_state.applyOperations( + tn_state->applyOperations( {{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = HermitianObsT(mat, {0}); auto res = measure.expval(ob); CHECK(res == Approx(ONE)); } SECTION("Using expval: Minus states") { - mps_state.applyOperations( + tn_state->applyOperations( {{"PauliX"}, {"Hadamard"}, {"PauliX"}, @@ -317,7 +344,7 @@ TEMPLATE_TEST_CASE("[Hermitian]", "[MPSTNCuda_Expval]", float, double) { {"Hadamard"}}, {{0}, {0}, {1}, {1}, {2}, {2}}, {{false}, {false}, {false}, {false}, {false}, {false}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = HermitianObsT(mat, {0}); auto res = measure.expval(ob); CHECK(res == -Approx(ONE)); @@ -325,24 +352,25 @@ TEMPLATE_TEST_CASE("[Hermitian]", "[MPSTNCuda_Expval]", float, double) { } } -TEMPLATE_TEST_CASE("Test expectation value of TensorProdObs", - "[MPSTNCuda_Expval]", float, double) { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; - using TensorProdObsT = TensorProdObsTNCuda; - auto ZERO = TestType(0); - auto INVSQRT2 = TestType(0.707106781186547524401); +TEMPLATE_LIST_TEST_CASE("Test expectation value of TensorProdObs", + "[TNCuda_Expval]", TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; + auto ZERO = PrecisionT(0); SECTION("Using XZ") { std::size_t bondDim = GENERATE(2); std::size_t num_qubits = 3; std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + tn_state->applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); auto X0 = std::make_shared("PauliX", std::vector{0}); @@ -359,12 +387,13 @@ TEMPLATE_TEST_CASE("Test expectation value of TensorProdObs", std::size_t num_qubits = 3; std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + tn_state->applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); auto H0 = std::make_shared("Hadamard", std::vector{0}); @@ -375,34 +404,36 @@ TEMPLATE_TEST_CASE("Test expectation value of TensorProdObs", auto ob = TensorProdObsT::create({H0, H1, H2}); auto res = m.expval(*ob); - CHECK(res == Approx(INVSQRT2 / 2)); + CHECK(res == Approx(Pennylane::Util::INVSQRT2() / 2)); } } -TEMPLATE_TEST_CASE("Test expectation value of HamiltonianObs", - "[MPSTNCuda_Expval]", float, double) { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; - using HamiltonianObsT = HamiltonianTNCuda; - auto ONE = TestType(1); +TEMPLATE_LIST_TEST_CASE("Test expectation value of HamiltonianObs", + "[TNCuda_Expval]", TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; + using HamiltonianObsT = HamiltonianTNCuda; + auto ONE = PrecisionT(1); SECTION("Using XZ") { - std::size_t bondDim = GENERATE(2); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + tn_state->applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); auto X0 = std::make_shared("PauliX", std::vector{0}); auto Z1 = std::make_shared("PauliZ", std::vector{1}); - auto ob = HamiltonianObsT::create({TestType{1}, TestType{1}}, {X0, Z1}); + auto ob = HamiltonianObsT::create({ONE, ONE}, {X0, Z1}); auto res = m.expval(*ob); CHECK(res == Approx(ONE)); } diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Measure.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Measure.cpp similarity index 63% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Measure.cpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Measure.cpp index 2284c46dc8..12fe3cc601 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Measure.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Measure.cpp @@ -23,12 +23,16 @@ #include +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" #include "MPSTNCuda.hpp" #include "MeasurementsTNCuda.hpp" #include "TNCudaGateCache.hpp" #include "TestHelpers.hpp" #include "cuda_helpers.hpp" +#include "TestHelpersTNCuda.hpp" + /// @cond DEV namespace { using namespace Pennylane::LightningTensor::TNCuda::Measures; @@ -38,12 +42,14 @@ using namespace Pennylane::Util; } // namespace /// @endcond -TEMPLATE_TEST_CASE("Probabilities", "[Measures]", float, double) { - using TensorNetT = MPSTNCuda; +TEMPLATE_LIST_TEST_CASE("Probabilities", "[Measures]", TestTNBackends) { + using TNDevice_T = TestType; + using PrecisionT = typename TNDevice_T::PrecisionT; SECTION("Looping over different wire configurations:") { // Probabilities calculated with Pennylane default.qubit: - std::vector, std::vector>> + const std::vector< + std::pair, std::vector>> input = { {{0, 1, 2}, {0.65473791, 0.08501576, 0.02690407, 0.00349341, 0.19540418, @@ -56,20 +62,21 @@ TEMPLATE_TEST_CASE("Probabilities", "[Measures]", float, double) { {{2}, {0.88507558, 0.11492442}}}; // data from default.qubit // Defining the State Vector that will be measured. - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RX"}, {"RY"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}, {2}, {2}}, {{false}, {false}, {false}, {false}, {false}, {false}}, {{0.5}, {0.5}, {0.2}, {0.2}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); for (const auto &term : input) { auto probabilities = measure.probs(term.first); @@ -80,67 +87,71 @@ TEMPLATE_TEST_CASE("Probabilities", "[Measures]", float, double) { SECTION("Test TNCudaOperator ctor failures") { // Defining the State Vector that will be measured. - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations({{"RX"}, {"RY"}}, {{0}, {0}}, + tn_state->applyOperations({{"RX"}, {"RY"}}, {{0}, {0}}, {{false}, {false}}, {{0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); REQUIRE_THROWS_AS(measure.probs({2, 1}), LightningException); } SECTION("Test excessive projected wires failure") { // Defining the State Vector that will be measured. - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 100; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 100; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); REQUIRE_THROWS_AS(measure.probs({0, 1, 2, 3}), LightningException); } } -TEMPLATE_TEST_CASE("Samples", "[Measures]", float, double) { - using TensorNetT = MPSTNCuda; +TEMPLATE_LIST_TEST_CASE("Samples", "[TNCUDA_Measures]", TestTNBackends) { + using TNDevice_T = TestType; + using PrecisionT = typename TNDevice_T::PrecisionT; SECTION("Looping over different wire configurations:") { // Probabilities calculated with Pennylane default.qubit: - std::vector expected_probabilities = { + const std::vector expected_probabilities = { 0.67078706, 0.03062806, 0.0870997, 0.00397696, 0.17564072, 0.00801973, 0.02280642, 0.00104134}; // Defining the State Vector that will be measured. - std::size_t bondDim = GENERATE(4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RX"}, {"RY"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}, {2}, {2}}, {{false}, {false}, {false}, {false}, {false}, {false}}, {{0.5}, {0.5}, {0.2}, {0.2}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); - std::size_t num_samples = 100000; + const std::size_t num_samples = 100000; const std::vector wires = {0, 1, 2}; auto samples = measure.generate_samples(wires, num_samples); auto counts = samples_to_decimal(samples, num_qubits, num_samples); // compute estimated probabilities from histogram - std::vector probabilities(counts.size()); + std::vector probabilities(counts.size()); for (std::size_t i = 0; i < counts.size(); i++) { - probabilities[i] = counts[i] / static_cast(num_samples); + probabilities[i] = counts[i] / static_cast(num_samples); } // compare estimated probabilities to real probabilities diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Var.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Var.cpp similarity index 61% rename from pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Var.cpp rename to pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Var.cpp index 6e7f209b18..337653fe8a 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Var.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_TNCuda_Var.cpp @@ -23,11 +23,15 @@ #include +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" #include "MPSTNCuda.hpp" #include "MeasurementsTNCuda.hpp" #include "TNCudaGateCache.hpp" #include "cuda_helpers.hpp" +#include "TestHelpersTNCuda.hpp" + /// @cond DEV namespace { using namespace Pennylane::LightningTensor::TNCuda::Measures; @@ -35,95 +39,100 @@ using namespace Pennylane::LightningTensor::TNCuda::Observables; } // namespace /// @endcond -TEMPLATE_TEST_CASE("Test variance of NamedObs", "[MPSTNCuda_Var]", float, - double) { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; +TEMPLATE_LIST_TEST_CASE("Test variance of NamedObs", "[TNCuda_Var]", + TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 2; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 2; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); SECTION("var(Identity[0])") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("Identity", {0}); auto res = measure.var(ob); - auto expected = TestType(0); + auto expected = PrecisionT(0); CHECK(res == Approx(expected).margin(1e-7)); } SECTION("var(PauliX[0])") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliX", {0}); auto res = measure.var(ob); - auto expected = TestType(0.75722220); + auto expected = PrecisionT(0.75722220); CHECK(res == Approx(expected)); } SECTION("var(PauliY[0])") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliY", {0}); auto res = measure.var(ob); - auto expected = TestType(0.58498357); + auto expected = PrecisionT(0.58498357); CHECK(res == Approx(expected)); } SECTION("var(PauliZ[1])") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("PauliZ", {1}); auto res = measure.var(ob); - auto expected = TestType(0.40686720); + auto expected = PrecisionT(0.40686720); CHECK(res == Approx(expected)); } SECTION("var(Hadamard[1])") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto ob = NamedObsT("Hadamard", {1}); auto res = measure.var(ob); - auto expected = TestType(0.29089449); + auto expected = PrecisionT(0.29089449); CHECK(res == Approx(expected)); } } -TEMPLATE_TEST_CASE("Test variance of HermitianObs", "[MPSTNCuda_Var]", float, - double) { - using TensorNetT = MPSTNCuda; - using ComplexT = typename TensorNetT::ComplexT; - using HermitianObsT = HermitianObsTNCuda; +TEMPLATE_LIST_TEST_CASE("Test variance of HermitianObs", "[TNCuda_Var]", + TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using ComplexT = typename TNDeviceT::ComplexT; + using HermitianObsT = HermitianObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}, {2}, {2}}, {{false}, {false}, {false}, {false}, {false}, {false}}, {{0.7}, {0.7}, {0.5}, {0.5}, {0.3}, {0.3}}); - mps_state.append_mps_final_state(); + + tn_state_append_mps_final_state(tn_state); SECTION("Target at 1 wire") { std::vector matrix = { @@ -131,31 +140,33 @@ TEMPLATE_TEST_CASE("Test variance of HermitianObs", "[MPSTNCuda_Var]", float, auto ob = HermitianObsT(matrix, {0}); auto res = measure.var(ob); - auto expected = TestType(1.8499002); // from default.qubit + auto expected = PrecisionT(1.8499002); // from default.qubit CHECK(res == Approx(expected)); } } -TEMPLATE_TEST_CASE("Test variance of TensorProdObs", "[MPSTNCuda_Var]", float, - double) { - using TensorNetT = MPSTNCuda; - using NamedObsT = NamedObsTNCuda; - using TensorProdObsT = TensorProdObsTNCuda; +TEMPLATE_LIST_TEST_CASE("Test variance of TensorProdObs", "[TNCuda_Var]", + TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using NamedObsT = NamedObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - auto measure = MeasurementsTNCuda(mps_state); + auto measure = MeasurementsTNCuda(*tn_state); SECTION("Using var") { - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.5}, {0.5}, {0.2}, {0.2}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); auto X0 = std::make_shared("PauliX", std::vector{0}); @@ -164,30 +175,32 @@ TEMPLATE_TEST_CASE("Test variance of TensorProdObs", "[MPSTNCuda_Var]", float, auto ob = TensorProdObsT::create({X0, Z1}); auto res = measure.var(*ob); - auto expected = TestType(0.83667953); + auto expected = PrecisionT(0.83667953); CHECK(expected == Approx(res)); } } -TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, - double) { - using TensorNetT = MPSTNCuda; - using ComplexT = typename TensorNetT::ComplexT; - using NamedObsT = NamedObsTNCuda; - using HermitianObsT = HermitianObsTNCuda; - using TensorProdObsT = TensorProdObsTNCuda; - using HamiltonianObsT = HamiltonianTNCuda; +TEMPLATE_LIST_TEST_CASE("Test var value of HamiltonianObs", "[TNCuda_Var]", + TestTNBackends) { + using TNDeviceT = TestType; + using PrecisionT = typename TNDeviceT::PrecisionT; + using ComplexT = typename TNDeviceT::ComplexT; + using NamedObsT = NamedObsTNCuda; + using HermitianObsT = HermitianObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; + using HamiltonianObsT = HamiltonianTNCuda; SECTION("Using TensorProd") { - std::size_t bondDim = GENERATE(2, 3, 4, 5); + const std::size_t bondDim = GENERATE(2, 3, 4, 5); constexpr std::size_t num_qubits = 5; constexpr std::size_t num_paulis = 5; constexpr std::size_t num_obs_terms = 6; - std::size_t maxBondDim = bondDim; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, @@ -219,9 +232,9 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, {0.2}, {0.5}, {0.5}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); std::array paulis = { "Identity", "PauliX", "PauliY", "PauliZ", "Hadamard"}; @@ -254,10 +267,10 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, obs_tensor_prod[0] = obs_tensor_prod[1]; - std::initializer_list coeffs{0.3, 0.5, 0.3, 0.5, 0.3, 0.5}; - std::initializer_list>> - obs{obs_tensor_prod[0], obs_tensor_prod[1], obs_tensor_prod[2], - obs_tensor_prod[3], obs_tensor_prod[4], obs_tensor_prod[5]}; + std::initializer_list coeffs{0.3, 0.5, 0.3, 0.5, 0.3, 0.5}; + std::initializer_list>> obs{ + obs_tensor_prod[0], obs_tensor_prod[1], obs_tensor_prod[2], + obs_tensor_prod[3], obs_tensor_prod[4], obs_tensor_prod[5]}; auto ob = HamiltonianObsT::create(coeffs, obs); @@ -266,19 +279,20 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, } SECTION("Using 1 Hermitian") { - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.5}, {0.5}, {0.2}, {0.2}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); std::vector matrix = { {0.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}}; @@ -297,7 +311,7 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, auto obs0 = TensorProdObsT::create({Z0, Z1}); auto obs1 = TensorProdObsT::create({Herm0, Herm1}); - auto ob = HamiltonianObsT::create({TestType{0.3}, TestType{0.5}}, + auto ob = HamiltonianObsT::create({PrecisionT{0.3}, PrecisionT{0.5}}, {obs0, obs1}); auto res = m.var(*ob); @@ -305,19 +319,20 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, } SECTION("Using 2 Hermitians") { - std::size_t bondDim = GENERATE(2, 3, 4, 5); - std::size_t num_qubits = 3; - std::size_t maxBondDim = bondDim; + const std::size_t bondDim = GENERATE(2, 3, 4, 5); + constexpr std::size_t num_qubits = 3; + const std::size_t maxBondDim = bondDim; - TensorNetT mps_state{num_qubits, maxBondDim}; + std::unique_ptr tn_state = + createTNState(num_qubits, maxBondDim); - mps_state.applyOperations( + tn_state->applyOperations( {{"RX"}, {"RY"}, {"RX"}, {"RY"}}, {{0}, {0}, {1}, {1}}, {{false}, {false}, {false}, {false}}, {{0.5}, {0.5}, {0.2}, {0.2}}); - mps_state.append_mps_final_state(); + tn_state_append_mps_final_state(tn_state); - auto m = MeasurementsTNCuda(mps_state); + auto m = MeasurementsTNCuda(*tn_state); std::vector matrix = { {0.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}}; @@ -330,8 +345,8 @@ TEMPLATE_TEST_CASE("Test var value of HamiltonianObs", "[MPSTNCuda_Var]", float, auto Z1 = std::make_shared(matrix_z, std::vector{1}); - auto ob = - HamiltonianObsT::create({TestType{0.3}, TestType{0.5}}, {X0, Z1}); + auto ob = HamiltonianObsT::create({PrecisionT{0.3}, PrecisionT{0.5}}, + {X0, Z1}); auto res = m.var(*ob); CHECK(res == Approx(0.09341363)); diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.cpp index db8a150f68..3c04640b52 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ObservablesTNCuda.hpp" +#include "ExactTNCuda.hpp" #include "MPSTNCuda.hpp" using namespace Pennylane::LightningTensor::TNCuda::Observables; @@ -31,3 +32,18 @@ template class TensorProdObsTNCuda>; template class HamiltonianTNCuda>; template class HamiltonianTNCuda>; + +template class ObservableTNCuda>; +template class ObservableTNCuda>; + +template class NamedObsTNCuda>; +template class NamedObsTNCuda>; + +template class HermitianObsTNCuda>; +template class HermitianObsTNCuda>; + +template class TensorProdObsTNCuda>; +template class TensorProdObsTNCuda>; + +template class HamiltonianTNCuda>; +template class HamiltonianTNCuda>; diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.cpp index 2b83508a9a..c3063177d6 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.cpp @@ -13,9 +13,12 @@ // limitations under the License. #include "ObservablesTNCudaOperator.hpp" +#include "ExactTNCuda.hpp" #include "MPSTNCuda.hpp" using namespace Pennylane::LightningTensor::TNCuda; template class Observables::ObservableTNCudaOperator>; template class Observables::ObservableTNCudaOperator>; +template class Observables::ObservableTNCudaOperator>; +template class Observables::ObservableTNCudaOperator>; diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/tests/Tests_MPSTNCuda.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/tests/Tests_MPSTNCuda.cpp index 1a03366669..b75c272697 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/tests/Tests_MPSTNCuda.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/tests/Tests_MPSTNCuda.cpp @@ -70,8 +70,8 @@ TEMPLATE_TEST_CASE("MPSTNCuda::setIthMPSSite", "[MPSTNCuda]", float, double) { std::vector> site_data(1, {0.0, 0.0}); REQUIRE_THROWS_WITH( - mps_state.updateMPSSiteData(siteIdx, site_data.data(), - site_data.size()), + mps_state.updateSiteData(siteIdx, site_data.data(), + site_data.size()), Catch::Matchers::Contains( "The site index should be less than the number of qubits.")); } @@ -86,8 +86,8 @@ TEMPLATE_TEST_CASE("MPSTNCuda::setIthMPSSite", "[MPSTNCuda]", float, double) { std::vector> site_data(1, {0.0, 0.0}); REQUIRE_THROWS_WITH( - mps_state.updateMPSSiteData(siteIdx, site_data.data(), - site_data.size()), + mps_state.updateSiteData(siteIdx, site_data.data(), + site_data.size()), Catch::Matchers::Contains("The length of the host data should " "match its copy on the device.")); } @@ -106,8 +106,8 @@ TEMPLATE_TEST_CASE("MPSTNCuda::setIthMPSSite", "[MPSTNCuda]", float, double) { site0_data[2] = {1.0, 0.0}; site1_data[1] = {1.0, 0.0}; - mps_state.updateMPSSiteData(0, site0_data.data(), site0_data.size()); - mps_state.updateMPSSiteData(1, site1_data.data(), site1_data.size()); + mps_state.updateSiteData(0, site0_data.data(), site0_data.size()); + mps_state.updateSiteData(1, site1_data.data(), site1_data.size()); auto results = mps_state.getDataVector(); diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/CMakeLists.txt index 544ed32e87..1e0695acaa 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/CMakeLists.txt @@ -6,10 +6,13 @@ add_library(${PL_BACKEND}_tncuda_utils INTERFACE) set(TNCUDA_UTILS_ADDED FALSE CACHE BOOL "Add tncuda_utils header files") foreach(BACKEND ${PL_BACKEND}) - if("${PL_TENSOR_METHOD}" STREQUAL "mps" AND NOT TNCUDA_UTILS_ADDED) + if("${PL_TENSOR_BACKEND}" STREQUAL "cutensornet" AND NOT TNCUDA_UTILS_ADDED) add_subdirectory(mps) target_include_directories(${PL_BACKEND}_tncuda_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/mps) target_link_libraries(${PL_BACKEND}_tncuda_utils INTERFACE mps_tncuda_utils) + add_subdirectory(tn) + target_include_directories(${PL_BACKEND}_tncuda_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/tn) + target_link_libraries(${PL_BACKEND}_tncuda_utils INTERFACE tncuda_test_utils) set(TNCUDA_UTILS_ADDED TRUE) endif() endforeach() diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/CMakeLists.txt new file mode 100644 index 0000000000..bd86b4e3a5 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(tncuda_test_utils LANGUAGES CXX) + +add_library(tncuda_test_utils INTERFACE) + +target_include_directories(tncuda_test_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(tncuda_test_utils INTERFACE lightning_utils lightning_compile_options lightning_external_libs) + +set_property(TARGET tncuda_test_utils PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/TestHelpersTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/TestHelpersTNCuda.hpp new file mode 100644 index 0000000000..66f38a1e27 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/utils/tn/TestHelpersTNCuda.hpp @@ -0,0 +1,72 @@ +// Copyright 2024 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. +#pragma once +/** + * @file + * This file defines the necessary functionality to test over LTensor. + */ +#include "DevTag.hpp" +#include "ExactTNCuda.hpp" +#include "MPSTNCuda.hpp" +#include "TypeList.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningTensor::TNCuda; +} // namespace +/// @endcond + +namespace Pennylane::LightningTensor::TNCuda::Util { +template struct MPSToName; + +template <> struct MPSToName> { + constexpr static auto name = "MPSTNCuda"; +}; +template <> struct MPSToName> { + constexpr static auto name = "MPSTNCuda"; +}; + +template +std::unique_ptr createTNState(std::size_t num_qubits, + std::size_t maxExtent) { + DevTag dev_tag{0, 0}; + if constexpr (std::is_same_v> || + std::is_same_v>) { + // Create the object for MPSTNCuda + return std::make_unique(num_qubits, maxExtent, dev_tag); + } else { + // Create the object for ExactTNCuda + return std::make_unique(num_qubits, dev_tag); + } +} + +template +inline void +tn_state_append_mps_final_state(std::unique_ptr const &tn_state) { + if constexpr (std::is_same_v> || + std::is_same_v>) { + PL_ABORT_IF( + tn_state->getMethod() != "mps", + "The method append_mps_final_state is exclusive of MPS TensorNet"); + tn_state->append_mps_final_state(); + } +} + +using TestTNBackends = + Pennylane::Util::TypeList, MPSTNCuda, + ExactTNCuda, ExactTNCuda>; +using TestMPSBackends = + Pennylane::Util::TypeList, MPSTNCuda>; + +} // namespace Pennylane::LightningTensor::TNCuda::Util