diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 6d6ef1a4c..562c799d7 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1,6 +1,8 @@ # Release 0.37.0-dev ### New features since last release +* Add `observable` and `expval` support to `cutensornet` backed `lightning.tensor` C++ layer. + [(#728)](https://github.com/PennyLaneAI/pennylane-lightning/pull/728) * Add gate support to `cutensornet` backed `lightning.tensor` C++ layer. [(#718)](https://github.com/PennyLaneAI/pennylane-lightning/pull/718) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 3f4945295..2a0c6b409 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.37.0-dev25" +__version__ = "0.37.0-dev26" diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/base/TensorBase.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/base/TensorBase.hpp index 963b0a72b..8fe9be541 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/base/TensorBase.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/base/TensorBase.hpp @@ -48,6 +48,18 @@ template class TensorBase { length_ = std::accumulate(extents.begin(), extents.end(), std::size_t{1}, std::multiplies<>()); } + /** + * @brief Construct a tensor object with given extents. + * + * @param extents Extents of a tensor object. + */ + explicit TensorBase(const std::vector &extents) + : rank_(extents.size()), + modes_(std::move(std::vector(rank_, std::size_t{0}))), + extents_(std::move(extents)) { + length_ = std::accumulate(extents_.begin(), extents_.end(), + std::size_t{1}, std::multiplies<>()); + } ~TensorBase() {} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/tncuda/TensorCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/tncuda/TensorCuda.hpp index 8b8ec9451..25b9642e4 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/tncuda/TensorCuda.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tensor/tncuda/TensorCuda.hpp @@ -48,6 +48,15 @@ class TensorCuda final : public TensorBase> { using BaseType = TensorBase; using CFP_t = decltype(cuUtil::getCudaType(PrecisionT{})); + /** + * @brief Construct a new TensorCuda object. + * + * @param rank Tensor rank. + * @param modes Tensor modes. + * @param extents Tensor extents. + * @param dev_tag Device tag. + * @param device_alloc If true, allocate memory on device. + */ explicit TensorCuda(const std::size_t rank, const std::vector &modes, const std::vector &extents, @@ -56,6 +65,24 @@ class TensorCuda final : public TensorBase> { data_buffer_{std::make_shared>( BaseType::getLength(), dev_tag, device_alloc)} {} + /** + * @brief Construct a new TensorCuda object from a host data. + * + * @param extents Tensor extents. + * @param host_tensor Host tensor data. + * @param dev_tag Device tag. + * @param device_alloc If true, allocate memory on device. + */ + explicit TensorCuda(const std::vector &extents, + const std::vector &host_tensor, + const DevTag &dev_tag, bool device_alloc = true) + : TensorBase>(extents), + data_buffer_{std::make_shared>( + BaseType::getLength(), dev_tag, device_alloc)} { + data_buffer_->CopyHostDataToGpu(host_tensor.data(), + BaseType::getLength()); + } + TensorCuda() = delete; ~TensorCuda() = default; 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 d7cb580f9..270629ef6 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/CMakeLists.txt @@ -59,6 +59,8 @@ endif() ############################################################################### set(COMPONENT_SUBDIRS base gates + measurements + observables utils ) foreach(COMP ${COMPONENT_SUBDIRS}) 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 8d705ac5d..d1e484b3d 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/MPSTNCuda.hpp @@ -32,6 +32,7 @@ #include "TNCudaBase.hpp" #include "TensorCuda.hpp" #include "TensornetBase.hpp" +#include "Util.hpp" #include "cuda_helpers.hpp" #include "tncudaError.hpp" #include "tncuda_helpers.hpp" @@ -61,6 +62,7 @@ class MPSTNCuda final : public TNCudaBase> { using BaseType = TNCudaBase; MPSStatus MPSInitialized_ = MPSStatus::MPSInitNotSet; + MPSStatus MPSFinalized_ = MPSStatus::MPSFinalizedNotSet; const std::size_t maxBondDim_; @@ -70,9 +72,12 @@ class MPSTNCuda final : public TNCudaBase> { std::vector> tensors_; + std::vector> tensors_out_; + public: using CFP_t = decltype(cuUtil::getCudaType(Precision{})); using ComplexT = std::complex; + using PrecisionT = Precision; public: MPSTNCuda() = delete; @@ -133,6 +138,19 @@ class MPSTNCuda final : public TNCudaBase> { 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. */ @@ -160,8 +178,7 @@ class MPSTNCuda final : public TNCudaBase> { "Please ensure all elements of a basis state should be " "either 0 or 1."); - CFP_t value_cu = - Pennylane::LightningGPU::Util::complexToCu({1.0, 0.0}); + 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(); @@ -186,6 +203,39 @@ class MPSTNCuda final : public TNCudaBase> { } }; + /** + * @brief Get final state of the quantum circuit. + */ + void get_final_state() { + if (MPSFinalized_ == MPSStatus::MPSFinalizedNotSet) { + MPSFinalized_ = MPSStatus::MPSFinalizedSet; + PL_CUTENSORNET_IS_SUCCESS(cutensornetStateFinalizeMPS( + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), + /* cutensornetBoundaryCondition_t */ + CUTENSORNET_BOUNDARY_CONDITION_OPEN, + /* const int64_t *const extentsOut[] */ + getSitesExtentsPtr().data(), + /*strides=*/nullptr)); + } + + // Optional: SVD + cutensornetTensorSVDAlgo_t algo = + CUTENSORNET_TENSOR_SVD_ALGO_GESVDJ; // default + + PL_CUTENSORNET_IS_SUCCESS(cutensornetStateConfigure( + /* const cutensornetHandle_t */ BaseType::getTNCudaHandle(), + /* cutensornetState_t */ BaseType::getQuantumState(), + /* cutensornetStateAttributes_t */ + CUTENSORNET_STATE_CONFIG_MPS_SVD_ALGO, + /* const void * */ &algo, + /* size_t */ sizeof(algo))); + + BaseType::computeState( + const_cast(getSitesExtentsPtr().data()), + reinterpret_cast(getTensorsOutDataPtr().data())); + } + /** * @brief Get the full state vector representation of a MPS quantum state. * @@ -208,7 +258,7 @@ class MPSTNCuda final : public TNCudaBase> { void *output_tensorPtr[] = { static_cast(output_tensor.getDataBuffer().getData())}; - this->computeState(output_tensorPtr); + BaseType::computeState(nullptr, output_tensorPtr); std::vector results(output_extent.front()); output_tensor.CopyGpuDataToHost(results.data(), results.size()); @@ -281,16 +331,10 @@ class MPSTNCuda final : public TNCudaBase> { std::vector> setSitesExtents_int64_() { std::vector> localSitesExtents_int64; - for (std::size_t i = 0; i < BaseType::getNumQubits(); i++) { - // Convert datatype of sitesExtents to int64 as required by - // cutensornet backend - std::vector siteExtents_int64(sitesExtents_[i].size()); - std::transform(sitesExtents_[i].begin(), sitesExtents_[i].end(), - siteExtents_int64.begin(), [](std::size_t x) { - return static_cast(x); - }); - - localSitesExtents_int64.push_back(std::move(siteExtents_int64)); + for (const auto &siteExtents : sitesExtents_) { + localSitesExtents_int64.push_back( + std::move(Pennylane::Util::cast_vector( + siteExtents))); } return localSitesExtents_int64; } @@ -303,6 +347,9 @@ class MPSTNCuda final : public TNCudaBase> { // 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()); } } diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp index cff82d81e..14feb9e6f 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/TNCudaBase.hpp @@ -127,6 +127,15 @@ class TNCudaBase : public TensornetBase { PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroyState(quantumState_)); } + /** + * @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. * @@ -181,8 +190,7 @@ class TNCudaBase : public TensornetBase { "Invalid arguments: number of operations, wires and inverses" "must all be equal"); for (std::size_t i = 0; i < numOperations; i++) { - this->applyOperation(ops[i], ops_wires[i], ops_adjoint[i], - ops_params[i]); + applyOperation(ops[i], ops_wires[i], ops_adjoint[i], ops_params[i]); } } @@ -209,7 +217,7 @@ class TNCudaBase : public TensornetBase { "Invalid arguments: number of operations, wires and inverses" "must all be equal"); for (std::size_t i = 0; i < numOperations; i++) { - this->applyOperation(ops[i], ops_wires[i], ops_adjoint[i], {}); + applyOperation(ops[i], ops_wires[i], ops_adjoint[i], {}); } } @@ -232,11 +240,10 @@ class TNCudaBase : public TensornetBase { DataBuffer dummy_device_data( Pennylane::Util::exp2(wires.size()), getDevTag()); int64_t id; - std::vector stateModes(wires.size()); - std::transform( - wires.begin(), wires.end(), stateModes.begin(), [&](std::size_t x) { - return static_cast(BaseType::getNumQubits() - 1 - x); - }); + + std::vector stateModes = + cuUtil::NormalizeCastIndices( + wires, BaseType::getNumQubits()); // TODO: Need changes to support to the controlled gate tensor API once // the API is finalized in cutensornet lib. @@ -256,12 +263,9 @@ class TNCudaBase : public TensornetBase { /* const int32_t unitary */ 1, /* int64_t * */ &id)); if (!gate_matrix.empty()) { - std::vector matrix_cu(gate_matrix.size()); - std::transform(gate_matrix.begin(), gate_matrix.end(), - matrix_cu.begin(), [](const ComplexT &x) { - return cuUtil::complexToCu(x); - }); auto gate_key = std::make_pair(opName, par); + std::vector matrix_cu = + cuUtil::complexToCu(gate_matrix); gate_cache_->add_gate(static_cast(id), gate_key, matrix_cu); } else { @@ -278,54 +282,12 @@ class TNCudaBase : public TensornetBase { } protected: - /** - * @brief Returns the workspace size. - * - * @return std::size_t - */ - std::size_t - getWorkSpaceMemorySize(cutensornetWorkspaceDescriptor_t &workDesc) { - int64_t worksize{0}; - - PL_CUTENSORNET_IS_SUCCESS(cutensornetWorkspaceGetMemorySize( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetWorkspaceDescriptor_t */ workDesc, - /* cutensornetWorksizePref_t */ - CUTENSORNET_WORKSIZE_PREF_RECOMMENDED, - /* cutensornetMemspace_t*/ CUTENSORNET_MEMSPACE_DEVICE, - /* cutensornetWorkspaceKind_t */ CUTENSORNET_WORKSPACE_SCRATCH, - /* int64_t * */ &worksize)); - - // Ensure data is aligned by 256 bytes - worksize += int64_t{256} - worksize % int64_t{256}; - - return static_cast(worksize); - } - - /** - * @brief Set memory for a workspace. - * - * @param workDesc cutensornet work space descriptor - * @param scratchPtr Pointer to scratch memory - * @param worksize Memory size of a work space - */ - void setWorkSpaceMemory(cutensornetWorkspaceDescriptor_t &workDesc, - void *scratchPtr, std::size_t &worksize) { - PL_CUTENSORNET_IS_SUCCESS(cutensornetWorkspaceSetMemory( - /* const cutensornetHandle_t */ getTNCudaHandle(), - /* cutensornetWorkspaceDescriptor_t */ workDesc, - /* cutensornetMemspace_t*/ CUTENSORNET_MEMSPACE_DEVICE, - /* cutensornetWorkspaceKind_t */ CUTENSORNET_WORKSPACE_SCRATCH, - /* void *const */ scratchPtr, - /* int64_t */ static_cast(worksize))); - } - /** * @brief Save quantumState information to data provided by a user * * @param tensorPtr Pointer to tensors provided by a user */ - void computeState(void **tensorPtr) { + void computeState(int64_t **extentsPtr, void **tensorPtr) { cutensornetWorkspaceDescriptor_t workDesc; PL_CUTENSORNET_IS_SUCCESS( cutensornetCreateWorkspaceDescriptor(getTNCudaHandle(), &workDesc)); @@ -341,7 +303,8 @@ class TNCudaBase : public TensornetBase { /* cutensornetWorkspaceDescriptor_t */ workDesc, /* cudaStream_t unused in v24.03*/ 0x0)); - std::size_t worksize = getWorkSpaceMemorySize(workDesc); + std::size_t worksize = + getWorkSpaceMemorySize(getTNCudaHandle(), workDesc); PL_ABORT_IF(worksize > scratchSize, "Insufficient workspace size on Device!"); @@ -350,14 +313,15 @@ class TNCudaBase : public TensornetBase { DataBuffer d_scratch(d_scratch_length, getDevTag(), true); - setWorkSpaceMemory( - workDesc, reinterpret_cast(d_scratch.getData()), worksize); + setWorkSpaceMemory(getTNCudaHandle(), workDesc, + reinterpret_cast(d_scratch.getData()), + worksize); PL_CUTENSORNET_IS_SUCCESS(cutensornetStateCompute( /* const cutensornetHandle_t */ getTNCudaHandle(), /* cutensornetState_t */ getQuantumState(), /* cutensornetWorkspaceDescriptor_t */ workDesc, - /* int64_t * */ nullptr, + /* int64_t * */ extentsPtr, /* int64_t *stridesOut */ nullptr, /* void * */ tensorPtr, /* cudaStream_t */ getDevTag().getStreamID())); diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/CMakeLists.txt new file mode 100644 index 000000000..6cc35ae80 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.20) + +project(${PL_BACKEND}_measurements LANGUAGES CXX) + +add_library(${PL_BACKEND}_measurements INTERFACE) + +target_include_directories(${PL_BACKEND}_measurements INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(${PL_BACKEND}_measurements INTERFACE lightning_compile_options + lightning_external_libs + ${PL_TENSOR} + ${PL_BACKEND}_utils + ${PL_BACKEND}_observables + ) + +if (BUILD_TESTS) + enable_testing() + add_subdirectory("tests") +endif() diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/MeasurementsTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/MeasurementsTNCuda.hpp new file mode 100644 index 000000000..c544c067d --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/MeasurementsTNCuda.hpp @@ -0,0 +1,141 @@ +// 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 + * Defines a class for the measurement of observables in quantum states + * represented by a Lightning Tensor class. + */ + +#pragma once + +#include +#include +#include + +#include "MPSTNCuda.hpp" +#include "ObservablesTNCuda.hpp" +#include "ObservablesTNCudaOperator.hpp" + +#include "tncuda_helpers.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningTensor::TNCuda; +using namespace Pennylane::LightningTensor::TNCuda::Observables; +using namespace Pennylane::LightningTensor::TNCuda::Util; +} // namespace +/// @endcond + +namespace Pennylane::LightningTensor::TNCuda::Measures { +/** + * @brief ObservablesTNCuda's Measurement Class. + * + * This class couples with a state tensor to perform measurements. + * Observables are defined in the observable class. + * + * @tparam StateTensorT type of the state tensor to be measured. + */ +template class MeasurementsTNCuda { + private: + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + + StateTensorT &state_tensor_; + + public: + explicit MeasurementsTNCuda(StateTensorT &state_tensor) + : state_tensor_(state_tensor){}; + + /** + * @brief Calculate expectation value for a general Observable. + * + * @param obs An Observable object. + * @param numHyperSamples Number of hyper samples to use in the calculation + * and is default as 1. + * + * @return Expectation value with respect to the given observable. + */ + auto expval(ObservableTNCuda &obs, + const int32_t numHyperSamples = 1) -> PrecisionT { + auto tnoperator = + ObservableTNCudaOperator(state_tensor_, obs); + + ComplexT expectation_val{0.0, 0.0}; + ComplexT state_norm2{0.0, 0.0}; + + cutensornetStateExpectation_t expectation; + + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateExpectation( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetState_t */ state_tensor_.getQuantumState(), + /* cutensornetNetworkOperator_t */ tnoperator.getTNOperator(), + /* cutensornetStateExpectation_t * */ &expectation)); + + PL_CUTENSORNET_IS_SUCCESS(cutensornetExpectationConfigure( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetStateExpectation_t */ expectation, + /* cutensornetExpectationAttributes_t */ + CUTENSORNET_EXPECTATION_CONFIG_NUM_HYPER_SAMPLES, + /* const void * */ &numHyperSamples, + /* size_t */ sizeof(numHyperSamples))); + + cutensornetWorkspaceDescriptor_t workDesc; + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateWorkspaceDescriptor( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetWorkspaceDescriptor_t * */ &workDesc)); + + const std::size_t scratchSize = cuUtil::getFreeMemorySize() / 2; + + // Prepare the specified quantum circuit expectation value for + // computation + PL_CUTENSORNET_IS_SUCCESS(cutensornetExpectationPrepare( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetStateExpectation_t */ expectation, + /* size_t maxWorkspaceSizeDevice */ scratchSize, + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* cudaStream_t [unused] */ 0x0)); + + std::size_t worksize = + getWorkSpaceMemorySize(state_tensor_.getTNCudaHandle(), workDesc); + + PL_ABORT_IF(worksize > scratchSize, + "Insufficient workspace size on Device.\n"); + + const std::size_t d_scratch_length = worksize / sizeof(size_t) + 1; + DataBuffer d_scratch(d_scratch_length, + state_tensor_.getDevTag(), true); + + setWorkSpaceMemory(state_tensor_.getTNCudaHandle(), workDesc, + reinterpret_cast(d_scratch.getData()), + worksize); + + PL_CUTENSORNET_IS_SUCCESS(cutensornetExpectationCompute( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetStateExpectation_t */ expectation, + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* void* */ static_cast(&expectation_val), + /* void* */ static_cast(&state_norm2), + /* cudaStream_t unused */ 0x0)); + + expectation_val /= state_norm2; + + PL_CUTENSORNET_IS_SUCCESS( + cutensornetDestroyWorkspaceDescriptor(workDesc)); + PL_CUTENSORNET_IS_SUCCESS(cutensornetDestroyExpectation(expectation)); + + return expectation_val.real(); + } +}; +} // namespace Pennylane::LightningTensor::TNCuda::Measures 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 new file mode 100644 index 000000000..e4ae473d0 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.20) + +project(${PL_BACKEND}_measurements_tests) + +# Default build type for test code is Debug +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +include("${pennylane_lightning_SOURCE_DIR}/cmake/support_tests.cmake") +FetchAndIncludeCatch() + +################################################################################ +# Define library +################################################################################ + +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_TENSOR} + ) + +ProcessTestOptions(${PL_BACKEND}_measurements_tests) + +target_sources(${PL_BACKEND}_measurements_tests INTERFACE runner_${PL_BACKEND}_measurements.cpp) + +################################################################################ +# Define targets +################################################################################ +set(TEST_SOURCES Test_MPSTNCuda_Expval.cpp) + +add_executable(${PL_BACKEND}_measurements_test_runner ${TEST_SOURCES}) +target_link_libraries(${PL_BACKEND}_measurements_test_runner PRIVATE ${PL_BACKEND}_measurements_tests) +catch_discover_tests(${PL_BACKEND}_measurements_test_runner) + +install(TARGETS ${PL_BACKEND}_measurements_test_runner DESTINATION bin) 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_MPSTNCuda_Expval.cpp new file mode 100644 index 000000000..33eee9fa1 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/Test_MPSTNCuda_Expval.cpp @@ -0,0 +1,385 @@ +// 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 +#include +#include +#include + +#include + +#include "MPSTNCuda.hpp" +#include "MeasurementsTNCuda.hpp" +#include "TNCudaGateCache.hpp" +#include "cuda_helpers.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningTensor::TNCuda::Measures; +using namespace Pennylane::LightningTensor::TNCuda::Observables; +} // namespace +/// @endcond + +TEMPLATE_TEST_CASE("[Identity]", "[MPSTNCuda_Expval]", float, double) { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + auto ONE = TestType(1); + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + + SECTION("Using expval") { + mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + {{0}, {0, 1}, {1, 2}}, + {{false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = NamedObsT("Identity", {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(ONE)); + } +} + +TEMPLATE_TEST_CASE("[PauliX]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + + auto ZERO = TestType(0); + auto ONE = TestType(1); + + SECTION("Using expval") { + mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + {{0}, {0, 1}, {1, 2}}, + {{false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = NamedObsT("PauliX", {0}); + auto res = measure.expval(ob); + CHECK(res == ZERO); + } + + SECTION("Using expval: Plus states") { + mps_state.applyOperations( + {{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, + {{false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = NamedObsT("PauliX", {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(ONE)); + } + + SECTION("Using expval: Minus states") { + mps_state.applyOperations( + {{"PauliX"}, + {"Hadamard"}, + {"PauliX"}, + {"Hadamard"}, + {"PauliX"}, + {"Hadamard"}}, + {{0}, {0}, {1}, {1}, {2}, {2}}, + {{false}, {false}, {false}, {false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = NamedObsT("PauliX", {0}); + auto res = measure.expval(ob); + CHECK(res == -Approx(ONE)); + } + } +} + +TEMPLATE_TEST_CASE("[PauliY]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + + auto ZERO = TestType(0); + auto ONE = TestType(1); + auto PI = TestType(M_PI); + + SECTION("Using expval") { + mps_state.applyOperations({{"Hadamard"}, {"CNOT"}, {"CNOT"}}, + {{0}, {0, 1}, {1, 2}}, + {{false}, {false}, {false}}); + auto ob = NamedObsT("PauliY", {0}); + auto res = measure.expval(ob); + CHECK(res == ZERO); + } + + SECTION("Using expval: Plus i states") { + mps_state.applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, + {{false}, {false}, {false}}, + {{-PI / 2}, {-PI / 2}, {-PI / 2}}); + auto ob = NamedObsT("PauliY", {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(ONE)); + } + + SECTION("Using expval: Minus i states") { + mps_state.applyOperations({{"RX"}, {"RX"}, {"RX"}}, {{0}, {1}, {2}}, + {{false}, {false}, {false}}, + {{PI / 2}, {PI / 2}, {PI / 2}}); + auto ob = NamedObsT("PauliY", {0}); + auto res = measure.expval(ob); + CHECK(res == -Approx(ONE)); + } + } +} + +TEMPLATE_TEST_CASE("[PauliZ]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using PrecisionT = StateTensorT::PrecisionT; + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + SECTION("Using expval") { + mps_state.applyOperations( + {{"RX"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, + {{false}, {false}, {false}}, {{0.5}, {}, {}}); + auto m = MeasurementsTNCuda(mps_state); + auto ob = NamedObsT("PauliZ", {0}); + auto res = m.expval(ob); + PrecisionT ref = 0.8775825618903724; + REQUIRE(res == Approx(ref).margin(1e-6)); + } + } +} + +TEMPLATE_TEST_CASE("[Hadamard]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + + auto INVSQRT2 = TestType(0.707106781186547524401); + + auto ONE = TestType(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.get_final_state(); + + auto ob = NamedObsT("Hadamard", {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(-INVSQRT2).epsilon(1e-7)); + + auto ob1 = NamedObsT("Identity", {0}); + auto res1 = measure.expval(ob1); + CHECK(res1 == Approx(ONE)); + } + } +} + +TEMPLATE_TEST_CASE("[Parametric_obs]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + auto ONE = TestType(1); + + SECTION("Using expval") { + mps_state.applyOperation("PauliX", {0}); + mps_state.get_final_state(); + + auto ob = NamedObsT("RX", {0}, {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(ONE).epsilon(1e-7)); + } + } +} + +TEMPLATE_TEST_CASE("[Hermitian]", "[MPSTNCuda_Expval]", float, double) { + { + using StateTensorT = MPSTNCuda; + using ComplexT = typename StateTensorT::ComplexT; + using HermitianObsT = HermitianObsTNCuda; + + std::size_t bondDim = GENERATE(2, 3, 4, 5); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + auto measure = MeasurementsTNCuda(mps_state); + + auto ZERO = TestType(0); + auto ONE = TestType(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"}}, + {{0}, {0, 1}, {1, 2}}, + {{false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = HermitianObsT(mat, std::vector{0}); + auto res = measure.expval(ob); + CHECK(res == ZERO); + } + + SECTION("Using expval: Plus states") { + mps_state.applyOperations( + {{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, + {{false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = HermitianObsT(mat, {0}); + auto res = measure.expval(ob); + CHECK(res == Approx(ONE)); + } + + SECTION("Using expval: Minus states") { + mps_state.applyOperations( + {{"PauliX"}, + {"Hadamard"}, + {"PauliX"}, + {"Hadamard"}, + {"PauliX"}, + {"Hadamard"}}, + {{0}, {0}, {1}, {1}, {2}, {2}}, + {{false}, {false}, {false}, {false}, {false}, {false}}); + mps_state.get_final_state(); + auto ob = HermitianObsT(mat, {0}); + auto res = measure.expval(ob); + CHECK(res == -Approx(ONE)); + } + } +} + +TEMPLATE_TEST_CASE("Test expectation value of TensorProdObs", + "[MPSTNCuda_Expval]", float, double) { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; + auto ZERO = TestType(0); + auto INVSQRT2 = TestType(0.707106781186547524401); + SECTION("Using XZ") { + std::size_t bondDim = GENERATE(2); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + auto m = MeasurementsTNCuda(mps_state); + + auto X0 = + std::make_shared("PauliX", std::vector{0}); + auto Z1 = + std::make_shared("PauliZ", std::vector{1}); + + auto ob = TensorProdObsT::create({X0, Z1}); + auto res = m.expval(*ob); + CHECK(res == Approx(ZERO)); + } + + SECTION("Using HHH") { + std::size_t bondDim = GENERATE(2); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + auto m = MeasurementsTNCuda(mps_state); + + auto H0 = std::make_shared("Hadamard", + std::vector{0}); + auto H1 = std::make_shared("Hadamard", + std::vector{1}); + auto H2 = std::make_shared("Hadamard", + std::vector{2}); + + auto ob = TensorProdObsT::create({H0, H1, H2}); + auto res = m.expval(*ob); + CHECK(res == Approx(INVSQRT2 / 2)); + } +} + +TEMPLATE_TEST_CASE("Test expectation value of HamiltonianObs", + "[MPSTNCuda_Expval]", float, double) { + using StateTensorT = MPSTNCuda; + using NamedObsT = NamedObsTNCuda; + using HamiltonianObsT = HamiltonianTNCuda; + auto ONE = TestType(1); + SECTION("Using XZ") { + std::size_t bondDim = GENERATE(2); + std::size_t num_qubits = 3; + std::size_t maxBondDim = bondDim; + + StateTensorT mps_state{num_qubits, maxBondDim}; + + mps_state.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + auto m = MeasurementsTNCuda(mps_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 res = m.expval(*ob); + CHECK(res == Approx(ONE)); + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/runner_lightning_tensor_measurements.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/runner_lightning_tensor_measurements.cpp new file mode 100644 index 000000000..4ed06df1f --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/measurements/tests/runner_lightning_tensor_measurements.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/CMakeLists.txt new file mode 100644 index 000000000..27f40b9f0 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.20) + +project(${PL_BACKEND}_observables LANGUAGES CXX) + +set(OBSERVABLES_FILES ObservablesTNCudaOperator.cpp ObservablesTNCuda.cpp CACHE INTERNAL "" FORCE) + +add_library(${PL_BACKEND}_observables STATIC ${OBSERVABLES_FILES}) + +target_include_directories(${PL_BACKEND}_observables INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(${PL_BACKEND}_observables PRIVATE lightning_compile_options + lightning_external_libs + ) + +target_link_libraries(${PL_BACKEND}_observables PUBLIC lightning_utils + lightning_gates + lightning_observables + ${PL_BACKEND}_utils + ${PL_BACKEND}_gates + ${PL_TENSOR} + ) + +set_property(TARGET ${PL_BACKEND}_observables PROPERTY POSITION_INDEPENDENT_CODE ON) + +if (BUILD_TESTS) + enable_testing() + add_subdirectory("tests") +endif() 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 new file mode 100644 index 000000000..db8a150f6 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.cpp @@ -0,0 +1,33 @@ +// 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 "ObservablesTNCuda.hpp" +#include "MPSTNCuda.hpp" + +using namespace Pennylane::LightningTensor::TNCuda::Observables; + +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/ObservablesTNCuda.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp new file mode 100644 index 000000000..8a9afd36c --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCuda.hpp @@ -0,0 +1,541 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. and contributors. + +// 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 + +#include +#include +#include + +#include "Constant.hpp" +#include "ConstantUtil.hpp" // lookup +#include "Util.hpp" + +#include "cuda_helpers.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::Util; +using namespace Pennylane::LightningGPU::Util; + +template using vector1D = std::vector; +template using vector2D = std::vector>; +template using vector3D = std::vector>; +} // namespace +/// @endcond + +namespace Pennylane::LightningTensor::TNCuda::Observables { +/** + * @brief A base class for observable classes of cutensornet backends. + * + * We note that the observable classes for cutensornet backends have the same + * user interface as the observable classes for the statevector backends across + * the lightning ecosystem. + * + * @tparam StateTensorT State tensor class. + */ +template class ObservableTNCuda { + public: + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using MetaDataT = std::tuple, + std::vector>; // name, params, matrix + + protected: + vector1D coeffs_; // coefficients of each term + vector1D numTensors_; // number of tensors in each term + vector2D + numStateModes_; // number of state modes of each tensor in each term + vector3D + stateModes_; // state modes of each tensor in each term + vector2D metaData_; // meta data of each tensor in each term + + protected: + ObservableTNCuda() = default; + ObservableTNCuda(const ObservableTNCuda &) = default; + ObservableTNCuda(ObservableTNCuda &&) noexcept = default; + ObservableTNCuda &operator=(const ObservableTNCuda &) = default; + ObservableTNCuda &operator=(ObservableTNCuda &&) noexcept = default; + + private: + /** + * @brief Polymorphic function comparing this to another Observable + * object. + * + * @param other Instance of subclass of ObservableTNCuda to + * compare. + */ + [[nodiscard]] virtual bool + isEqual(const ObservableTNCuda &other) const = 0; + + public: + virtual ~ObservableTNCuda() = default; + + /** + * @brief Get the name of the observable + */ + [[nodiscard]] virtual auto getObsName() const -> std::string = 0; + + /** + * @brief Get the wires the observable applies to. + */ + [[nodiscard]] virtual auto getWires() const -> std::vector = 0; + + /** + * @brief Get the number of tensors in each term (For non-Hamiltonian + * observables, the size of std::vector return is 1). + */ + [[nodiscard]] auto getNumTensors() const -> const vector1D & { + return numTensors_; + } + + /** + * @brief Get the number of state modes of each tensor in each term. + */ + [[nodiscard]] auto getNumStateModes() const + -> const vector2D & { + return numStateModes_; + } + + /** + * @brief Get the state modes of each tensor in each term. + */ + [[nodiscard]] auto getStateModes() const -> const vector3D & { + return stateModes_; + } + + /** + * @brief Get the meta data of each tensor in each term on host. + */ + [[nodiscard]] auto getMetaData() const -> const vector2D & { + return metaData_; + } + + /** + * @brief Get the coefficients of a observable. + */ + [[nodiscard]] auto getCoeffs() const -> const vector1D & { + return coeffs_; + }; + + /** + * @brief Test whether this object is equal to another object + */ + [[nodiscard]] auto + operator==(const ObservableTNCuda &other) const -> bool { + return typeid(*this) == typeid(other) && isEqual(other); + } + + /** + * @brief Test whether this object is different from another object. + */ + [[nodiscard]] auto + operator!=(const ObservableTNCuda &other) const -> bool { + return !(*this == other); + } +}; + +/** + * @brief Named observables (PauliX, PauliY, PauliZ, etc.) + * + * @tparam StateTensorT State tensor class. + */ +template +class NamedObsTNCuda : public ObservableTNCuda { + public: + using BaseType = ObservableTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + + private: + std::string obs_name_; + std::vector wires_; + std::vector params_; + + [[nodiscard]] auto + isEqual(const ObservableTNCuda &other) const + -> bool override { + const auto &other_cast = + static_cast &>(other); + + return (obs_name_ == other_cast.obs_name_) && + (wires_ == other_cast.wires_) && (params_ == other_cast.params_); + } + + public: + /** + * @brief Construct a NamedObsTNCuda object, representing a given + * observable. + * + * @param obs_name Name of the observable. + * @param wires Argument to construct wires. + * @param params Argument to construct parameters + */ + NamedObsTNCuda(std::string obs_name, std::vector wires, + std::vector params = {}) + : obs_name_{obs_name}, wires_{wires}, params_{params} { + BaseType::coeffs_.emplace_back(PrecisionT{1.0}); + BaseType::numTensors_.emplace_back(std::size_t{1}); + BaseType::numStateModes_.emplace_back( + vector1D{wires_.size()}); + BaseType::stateModes_.emplace_back(vector2D{wires_}); + + BaseType::metaData_.push_back( + {std::make_tuple(obs_name, params_, std::vector{})}); + } + + [[nodiscard]] auto getObsName() const -> std::string override { + using Pennylane::Util::operator<<; + std::ostringstream obs_stream; + obs_stream << obs_name_ << wires_; + return obs_stream.str(); + } + + [[nodiscard]] auto getWires() const -> std::vector override { + return wires_; + } +}; + +/** + * @brief Hermitian observables + * + * @tparam StateTensorT State tensor class. + */ +template +class HermitianObsTNCuda : public ObservableTNCuda { + public: + using BaseType = ObservableTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using MatrixT = std::vector; + + private: + inline static const MatrixHasher mh; + MatrixT matrix_; + std::vector wires_; + + [[nodiscard]] auto + isEqual(const ObservableTNCuda &other) const + -> bool override { + const auto &other_cast = + static_cast &>(other); + + return (matrix_ == other_cast.matrix_) && (wires_ == other_cast.wires_); + } + + public: + /** + * @brief Construct a HermitianObs object, representing a given observable. + * + * @param matrix Matrix in row major format. + * @param wires Wires the observable applies to. + */ + HermitianObsTNCuda(MatrixT matrix, std::vector wires) + : matrix_{std::move(matrix)}, wires_{std::move(wires)} { + PL_ASSERT(matrix_.size() == Pennylane::Util::exp2(2 * wires_.size())); + BaseType::coeffs_.emplace_back(PrecisionT{1.0}); + BaseType::numTensors_.emplace_back(std::size_t{1}); + BaseType::numStateModes_.emplace_back( + vector1D{wires_.size()}); + BaseType::stateModes_.emplace_back(vector2D{wires_}); + + BaseType::metaData_.push_back( + {std::make_tuple("Hermitian", std::vector{}, matrix_)}); + } + + [[nodiscard]] auto getObsName() const -> std::string override { + // To avoid collisions on cached GPU data, use matrix elements to + // uniquely identify Hermitian + // TODO: Replace with a performant hash function + std::ostringstream obs_stream; + obs_stream << "Hermitian" << mh(matrix_); + return obs_stream.str(); + } + + [[nodiscard]] auto getWires() const -> std::vector override { + return wires_; + } +}; + +/** + * @brief Tensor product of observables. + * + * @tparam StateTensorT State tensor class. + */ +template +class TensorProdObsTNCuda : public ObservableTNCuda { + public: + using BaseType = ObservableTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using MetaDataT = BaseType::MetaDataT; + + private: + std::vector>> obs_; + std::vector all_wires_; + + [[nodiscard]] auto + isEqual(const ObservableTNCuda &other) const + -> bool override { + const auto &other_cast = + static_cast &>(other); + + if (obs_.size() != other_cast.obs_.size()) { + return false; + } + + for (size_t i = 0; i < obs_.size(); i++) { + if (*obs_[i] != *other_cast.obs_[i]) { + return false; + } + } + return true; + } + + public: + /** + * @brief Create a tensor product of observables + * + * @param arg Arguments perfect-forwarded to vector of observables. + */ + template + explicit TensorProdObsTNCuda(Ts &&...arg) : obs_{std::forward(arg)...} { + if (obs_.size() == 1 && + obs_[0]->getObsName().find('@') != std::string::npos) { + // This would prevent the misuse of this constructor for creating + // TensorProdObs(TensorProdObs). + PL_ABORT("A new TensorProdObs observable cannot be created " + "from a single TensorProdObs."); + } + + for (const auto &ob : obs_) { + PL_ABORT_IF(ob->getObsName().find("Hamiltonian") != + std::string::npos, + "A TensorProdObs observable cannot be created from a " + "Hamiltonian."); + } + + BaseType::coeffs_.push_back(PrecisionT{1.0}); + BaseType::numTensors_.push_back(obs_.size()); + + vector1D numStateModesLocal; + vector2D stateModesLocal; + vector1D dataLocal; + + for (const auto &ob : obs_) { + numStateModesLocal.insert(numStateModesLocal.end(), + ob->getNumStateModes().front().begin(), + ob->getNumStateModes().front().end()); + + stateModesLocal.insert(stateModesLocal.end(), + ob->getStateModes().front().begin(), + ob->getStateModes().front().end()); + + dataLocal.insert(dataLocal.end(), ob->getMetaData().front().begin(), + ob->getMetaData().front().end()); + } + + BaseType::numStateModes_.emplace_back(numStateModesLocal); + BaseType::stateModes_.emplace_back(stateModesLocal); + BaseType::metaData_.emplace_back(dataLocal); + + std::unordered_set wires; + for (const auto &ob : obs_) { + const auto ob_wires = ob->getWires(); + for (const auto wire : ob_wires) { + PL_ABORT_IF(wires.contains(wire), + "All wires in observables must be disjoint."); + wires.insert(wire); + } + } + all_wires_ = std::vector(wires.begin(), wires.end()); + std::sort(all_wires_.begin(), all_wires_.end()); + } + + /** + * @brief Convenient wrapper for the constructor as the constructor does not + * convert the std::shared_ptr with a derived class correctly. + * + * This function is useful as std::make_shared does not handle + * brace-enclosed initializer list correctly. + * + * @param obs List of observables + * @return std::shared_ptr> + */ + static auto create( + std::initializer_list>> + obs) -> std::shared_ptr> { + return std::shared_ptr>{ + new TensorProdObsTNCuda(std::move(obs))}; + } + + /** + * @brief Convenient wrapper for the constructor as the constructor does not + * convert the std::shared_ptr with a derived class correctly. + * + * This function is useful as std::make_shared does not handle + * brace-enclosed initializer list correctly. + * + * @param obs List of observables + * @return std::shared_ptr> + */ + static auto + create(std::vector>> obs) + -> std::shared_ptr> { + return std::shared_ptr>{ + new TensorProdObsTNCuda(std::move(obs))}; + } + + /** + * @brief Get the number of operations in the observable. + * + * @return std::size_t + */ + [[nodiscard]] auto getSize() const -> std::size_t { return obs_.size(); } + + /** + * @brief Get the wires for each observable operation. + * + * @return const std::vector + */ + [[nodiscard]] auto getWires() const -> std::vector override { + return all_wires_; + } + + /** + * @brief Get the name of the observable + */ + [[nodiscard]] auto getObsName() const -> std::string override { + using Pennylane::Util::operator<<; + std::ostringstream obs_stream; + const auto obs_size = obs_.size(); + for (size_t idx = 0; idx < obs_size; idx++) { + obs_stream << obs_[idx]->getObsName(); + if (idx != obs_size - 1) { + obs_stream << " @ "; + } + } + return obs_stream.str(); + } +}; + +/** + * @brief Hamiltonian representation as a sum of observables. + * + * @tparam StateTensorT State tensor class. + */ +template +class HamiltonianTNCuda : public ObservableTNCuda { + public: + using BaseType = ObservableTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + + private: + std::vector coeffs_ham_; + std::vector>> obs_; + + [[nodiscard]] bool + isEqual(const ObservableTNCuda &other) const override { + const auto &other_cast = + static_cast &>(other); + + if (coeffs_ham_ != other_cast.coeffs_ham_) { + return false; + } + + for (size_t i = 0; i < obs_.size(); i++) { + if (*obs_[i] != *other_cast.obs_[i]) { + return false; + } + } + return true; + } + + public: + /** + * @brief Create a Hamiltonian from coefficients and observables + * + * @param coeffs Arguments to construct coefficients + * @param obs Arguments to construct observables + */ + template + HamiltonianTNCuda(T1 &&coeffs, T2 &&obs) + : coeffs_ham_{std::forward(coeffs)}, obs_{std::forward(obs)} { + BaseType::coeffs_ = coeffs_ham_; + PL_ASSERT(BaseType::coeffs_.size() == obs_.size()); + + for (std::size_t term_idx = 0; term_idx < BaseType::coeffs_.size(); + term_idx++) { + auto ob = obs_[term_idx]; + // This is aligned with statevector backends + PL_ABORT_IF(ob->getObsName().find("Hamiltonian") != + std::string::npos, + "A Hamiltonian observable cannot be created from " + "another Hamiltonian."); + BaseType::numTensors_.emplace_back(ob->getNumTensors().front()); + BaseType::numStateModes_.emplace_back( + ob->getNumStateModes().front()); + BaseType::stateModes_.emplace_back(ob->getStateModes().front()); + BaseType::metaData_.emplace_back(ob->getMetaData().front()); + } + } + + /** + * @brief Convenient wrapper for the constructor as the constructor does not + * convert the std::shared_ptr with a derived class correctly. + * + * This function is useful as std::make_shared does not handle + * brace-enclosed initializer list correctly. + * + * @param coeffs Arguments to construct coefficients + * @param obs Arguments to construct observables + * @return std::shared_ptr> + */ + static auto create( + std::initializer_list coeffs, + std::initializer_list>> + obs) -> std::shared_ptr> { + return std::shared_ptr>( + new HamiltonianTNCuda{std::move(coeffs), + std::move(obs)}); + } + + [[nodiscard]] auto getWires() const -> std::vector override { + std::unordered_set wires; + + for (const auto &ob : obs_) { + const auto ob_wires = ob->getWires(); + wires.insert(ob_wires.begin(), ob_wires.end()); + } + auto all_wires = std::vector(wires.begin(), wires.end()); + std::sort(all_wires.begin(), all_wires.end()); + return all_wires; + } + + [[nodiscard]] auto getObsName() const -> std::string override { + using Pennylane::Util::operator<<; + std::ostringstream ss; + ss << "Hamiltonian: { 'coeffs' : " << BaseType::coeffs_ + << ", 'observables' : ["; + const auto term_size = BaseType::coeffs_.size(); + for (size_t t = 0; t < term_size; t++) { + ss << obs_[t]->getObsName(); + if (t != term_size - 1) { + ss << ", "; + } + } + ss << "]}"; + return ss.str(); + } +}; +} // namespace Pennylane::LightningTensor::TNCuda::Observables 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 new file mode 100644 index 000000000..2b83508a9 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.cpp @@ -0,0 +1,21 @@ +// 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 "ObservablesTNCudaOperator.hpp" +#include "MPSTNCuda.hpp" + +using namespace Pennylane::LightningTensor::TNCuda; + +template class Observables::ObservableTNCudaOperator>; +template class Observables::ObservableTNCudaOperator>; diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.hpp new file mode 100644 index 000000000..763ef77f8 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/ObservablesTNCudaOperator.hpp @@ -0,0 +1,295 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. and contributors. + +// 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 + +#include +#include +#include + +#include "ObservablesTNCuda.hpp" +#include "TensorCuda.hpp" +#include "Util.hpp" +#include "cuGates_host.hpp" +#include "tncudaError.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::Util; +using namespace Pennylane::LightningTensor; +using namespace Pennylane::LightningTensor::TNCuda; + +template using vector1D = std::vector; + +template using vector2D = std::vector>; + +template using vector3D = std::vector>; +} // namespace +/// @endcond + +namespace Pennylane::LightningTensor::TNCuda::Observables { + +// Current design allows multiple measurements to be performed for a +// given circuit. +/** + * @brief ObservableTNCudaOperator Class. + * + * This class creates `custatenetTensorNetwork` from + * `ObservablesTNCuda` objects for measurement purpose. Since the NamedObs, + * HermitianObs, TensorProdObs and Hamiltionian objects can be encapsulated in a + * `cutensornetNetworkOperator_t` instance, only one ObservableTNCudaOperator + * class is designed here. Note that a `cutensornetNetworkOperator_t` object can + * only be created and destroyed by creating a new ObservableTNCudaOperator + * object, which ensures its lifetime is aligned with that of data associated to + * it. + * + * @tparam StateTensorT State tensor class. + */ +template class ObservableTNCudaOperator { + public: + using PrecisionT = typename StateTensorT::PrecisionT; + using CFP_t = typename StateTensorT::CFP_t; + using ComplexT = typename StateTensorT::ComplexT; + using obs_key = + std::tuple, std::size_t>; + + private: + cutensornetNetworkOperator_t obsOperator_{ + nullptr}; // cutensornetNetworkOperator operator + + const StateTensorT &state_tensor_; // quantum state to be measured + + const std::size_t numObsTerms_; // number of observable terms + vector1D coeffs_; // coefficients for each term + vector1D numTensors_; // number of tensors in each term + + vector2D + numModes_; // number of state modes of each tensor in each term + + vector3D modes_; // modes for each tensor in each term + + vector2D + modesPtr_; // pointers for modes of each tensor in each term + + vector2D + tensorDataPtr_; // pointers for each tensor data in each term + + std::vector ids_; // ids for each term in the graph + + private: + /** + * @brief Hasher for observable key. + */ + struct ObsKeyHasher { + std::size_t operator()( + const std::tuple, std::size_t> + &obsKey) const { + std::size_t hash_val = + std::hash{}(std::get<0>(obsKey)); + for (const auto ¶m : std::get<1>(obsKey)) { + hash_val ^= std::hash{}(param); + } + hash_val ^= std::hash{}(std::get<2>(obsKey)); + return hash_val; + } + }; + + /** + * @brief Cache for observable data on device. + */ + std::unordered_map, ObsKeyHasher> + device_obs_cache_; + + /** + * @brief Add an observable numerical value to the cached map, indexed by + * the name, parameters and hash value(default as 0 for named observables). + * + * @param obs_name String representing the name of the given observable. + * @param obs_param Vector of parameter values. `{}` if non-parametric + * gate. + */ + void add_obs_(const std::string &obs_name, + [[maybe_unused]] std::vector &obs_param = {}) { + auto obsKey = std::make_tuple(obs_name, obs_param, std::size_t{0}); + + auto &gateMap = + cuGates::DynamicGateDataAccess::getInstance(); + + add_obs_(obsKey, gateMap.getGateData(obs_name, obs_param)); + } + + /** + * @brief Add observable numerical value to the cache map, the name, + * parameters and hash value(default as 0 for named observables). + * + * @param obsKey obs_key tuple representing the name, parameters and hash + * value(default as 0 for named observables). + * @param obs_data_host Vector of complex floating point values + * representing the observable data on host. + */ + void add_obs_(const obs_key &obsKey, + const std::vector &obs_data_host) { + const std::size_t rank = Pennylane::Util::log2(obs_data_host.size()); + auto modes = std::vector(rank, 0); + auto extents = std::vector(rank, 2); + + auto &&tensor = TensorCuda(rank, modes, extents, + state_tensor_.getDevTag()); + + device_obs_cache_.emplace(std::piecewise_construct, + std::forward_as_tuple(obsKey), + std::forward_as_tuple(std::move(tensor))); + + device_obs_cache_.at(obsKey).getDataBuffer().CopyHostDataToGpu( + obs_data_host.data(), obs_data_host.size()); + } + + /** + * @brief Returns a pointer to the GPU device memory where the observable is + * stored. + * + * @param obsKey The key of observable tensor operator. + * @return const CFP_t* Pointer to gate values on device. + */ + const CFP_t *get_obs_device_ptr_(const obs_key &obsKey) { + return device_obs_cache_.at(obsKey).getDataBuffer().getData(); + } + + public: + ObservableTNCudaOperator(const StateTensorT &state_tensor, + ObservableTNCuda &obs) + : state_tensor_{state_tensor}, + numObsTerms_(obs.getNumTensors().size()) { + PL_CUTENSORNET_IS_SUCCESS(cutensornetCreateNetworkOperator( + /* const cutensornetHandle_t */ state_tensor.getTNCudaHandle(), + /* int32_t */ static_cast(state_tensor.getNumQubits()), + /* const int64_t stateModeExtents */ + reinterpret_cast( + const_cast(state_tensor.getQubitDims().data())), + /* cudaDataType_t */ state_tensor.getCudaDataType(), + /* cutensornetNetworkOperator_t */ &obsOperator_)); + + numTensors_ = obs.getNumTensors(); // number of tensors in each term + + for (std::size_t term_idx = 0; term_idx < numObsTerms_; term_idx++) { + auto coeff = cuDoubleComplex{ + static_cast(obs.getCoeffs()[term_idx]), 0.0}; + auto numTensors = numTensors_[term_idx]; + + coeffs_.emplace_back(coeff); + + // number of state modes of each tensor in each term + numModes_.emplace_back(cast_vector( + obs.getNumStateModes()[term_idx])); + + // modes initialization + vector2D modes_per_term; + for (std::size_t tensor_idx = 0; tensor_idx < numTensors; + tensor_idx++) { + modes_per_term.emplace_back( + cuUtil::NormalizeCastIndices( + obs.getStateModes()[term_idx][tensor_idx], + state_tensor.getNumQubits())); + } + modes_.emplace_back(modes_per_term); + + // modes pointer initialization + vector1D modesPtrPerTerm; + for (std::size_t tensor_idx = 0; tensor_idx < modes_.back().size(); + tensor_idx++) { + modesPtrPerTerm.emplace_back(modes_.back()[tensor_idx].data()); + } + modesPtr_.emplace_back(modesPtrPerTerm); + + // tensor data initialization + vector1D tensorDataPtrPerTerm_; + for (std::size_t tensor_idx = 0; tensor_idx < numTensors; + tensor_idx++) { + auto metaData = obs.getMetaData()[term_idx][tensor_idx]; + + auto obsName = std::get<0>(metaData); + auto param = std::get<1>(metaData); + auto hermitianMatrix = std::get<2>(metaData); + std::size_t hash_val = 0; + + if (!hermitianMatrix.empty()) { + hash_val = MatrixHasher()(hermitianMatrix); + } + + auto obsKey = std::make_tuple(obsName, param, hash_val); + + if (device_obs_cache_.find(obsKey) == device_obs_cache_.end()) { + if (hermitianMatrix.empty()) { + add_obs_(obsName, param); + } else { + auto hermitianMatrix_cu = + cuUtil::complexToCu(hermitianMatrix); + add_obs_(obsKey, hermitianMatrix_cu); + } + } + tensorDataPtrPerTerm_.emplace_back(get_obs_device_ptr_(obsKey)); + } + + tensorDataPtr_.emplace_back(tensorDataPtrPerTerm_); + + appendTNOperator_(coeff, numTensors, numModes_.back().data(), + modesPtr_.back().data(), + tensorDataPtr_.back().data()); + } + } + + ~ObservableTNCudaOperator() { + PL_CUTENSORNET_IS_SUCCESS( + cutensornetDestroyNetworkOperator(obsOperator_)); + } + + /** + * @brief Get the `cutensornetNetworkOperator_t` object. + * + * @return cutensornetNetworkOperator_t + */ + [[nodiscard]] auto getTNOperator() const -> cutensornetNetworkOperator_t { + return obsOperator_; + } + + private: + /** + * @brief Append a product of tensors to the `cutensornetNetworkOperator_t` + * + * @param coeff Coefficient of the product. + * @param numTensors Number of tensors in the product. + * @param numStateModes Number of state modes of each tensor in the product. + * @param stateModes State modes of each tensor in the product. + * @param tensorDataPtr Pointer to the data of each tensor in the product. + */ + void appendTNOperator_(const cuDoubleComplex &coeff, + const std::size_t numTensors, + const int32_t *numStateModes, + const int32_t **stateModes, + const void **tensorDataPtr) { + int64_t id; + PL_CUTENSORNET_IS_SUCCESS(cutensornetNetworkOperatorAppendProduct( + /* const cutensornetHandle_t */ state_tensor_.getTNCudaHandle(), + /* cutensornetNetworkOperator_t */ getTNOperator(), + /* cuDoubleComplex coefficient*/ coeff, + /* int32_t numTensors */ static_cast(numTensors), + /* const int32_t numStateModes[] */ numStateModes, + /* const int32_t *stateModes[] */ stateModes, + /* const int64_t *tensorModeStrides[] */ nullptr, + /* const void *tensorData[] */ tensorDataPtr, + /* int64_t* */ &id)); + ids_.push_back(id); + } +}; +} // namespace Pennylane::LightningTensor::TNCuda::Observables diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/CMakeLists.txt new file mode 100644 index 000000000..330fa62d1 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.20) + +project(${PL_BACKEND}_observables_tests) + +# Default build type for test code is Debug +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +include("${pennylane_lightning_SOURCE_DIR}/cmake/support_tests.cmake") +FetchAndIncludeCatch() + +################################################################################ +# Define library +################################################################################ + +add_library(${PL_BACKEND}_observables_tests INTERFACE) +target_link_libraries(${PL_BACKEND}_observables_tests INTERFACE Catch2::Catch2 + ${PL_BACKEND}_gates + ${PL_BACKEND}_observables + ${PL_TENSOR} + ) + +ProcessTestOptions(${PL_BACKEND}_observables_tests) + +target_sources(${PL_BACKEND}_observables_tests INTERFACE runner_${PL_BACKEND}_observables.cpp) + +################################################################################ +# Define targets +################################################################################ +set(TEST_SOURCES Test_Observables_TNCuda.cpp) + +add_executable(${PL_BACKEND}_observables_test_runner ${TEST_SOURCES}) +target_link_libraries(${PL_BACKEND}_observables_test_runner PRIVATE ${PL_BACKEND}_observables_tests) +catch_discover_tests(${PL_BACKEND}_observables_test_runner) + +install(TARGETS ${PL_BACKEND}_observables_test_runner DESTINATION bin) diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp new file mode 100644 index 000000000..3cfd6a1e4 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/Test_Observables_TNCuda.cpp @@ -0,0 +1,467 @@ +// 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 "MPSTNCuda.hpp" +#include "ObservablesTNCuda.hpp" + +#include "TestHelpers.hpp" + +/// @cond DEV +namespace { +using namespace Pennylane::LightningTensor::TNCuda::Observables; +using Pennylane::Util::LightningException; +} // namespace +/// @endcond + +TEMPLATE_PRODUCT_TEST_CASE("NamedObs", "[Observables]", (MPSTNCuda), + (float, double)) { + using StateTensorT = TestType; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using NamedObsT = NamedObsTNCuda; + + SECTION("Test get obs name") { + auto obs = NamedObsT("PauliX", {0}); + auto obs_metaData = obs.getMetaData()[0][0]; + CHECK(obs.getObsName() == "PauliX[0]"); + CHECK(obs.getWires() == std::vector{0}); + CHECK(obs.getNumTensors() == std::vector{1}); + CHECK(obs.getNumStateModes()[0] == std::vector{1}); + CHECK(obs.getStateModes()[0][0] == std::vector{0}); + CHECK(obs.getCoeffs() == std::vector{1.0}); + CHECK(std::get<0>(obs_metaData) == "PauliX"); + CHECK(std::get<1>(obs_metaData) == std::vector{}); + CHECK(std::get<2>(obs_metaData) == std::vector{}); + } + + SECTION("Comparing objects names") { + auto ob1 = NamedObsT("PauliX", {0}); + auto ob2 = NamedObsT("PauliX", {0}); + auto ob3 = NamedObsT("PauliZ", {0}); + + REQUIRE(ob1 == ob2); + REQUIRE(ob2 != ob3); + REQUIRE(ob1 != ob3); + } + + SECTION("Comparing objects wires") { + auto ob1 = NamedObsT("PauliY", {0}); + auto ob2 = NamedObsT("PauliY", {0}); + auto ob3 = NamedObsT("PauliY", {1}); + + REQUIRE(ob1 == ob2); + REQUIRE(ob2 != ob3); + REQUIRE(ob1 != ob3); + } + + SECTION("Comparing objects parameters") { + auto ob1 = NamedObsT("RZ", {0}, {0.4}); + auto ob2 = NamedObsT("RZ", {0}, {0.4}); + auto ob3 = NamedObsT("RZ", {0}, {0.1}); + + REQUIRE(ob1 == ob2); + REQUIRE(ob2 != ob3); + REQUIRE(ob1 != ob3); + } +} + +TEMPLATE_TEST_CASE("[Hermitian]", "[Observables]", float, double) { + using StateTensorT = MPSTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using HermitianObsT = HermitianObsTNCuda; + + SECTION("HermitianObs only accepts correct arguments") { + auto ob1 = + HermitianObsT{std::vector{0.0, 0.0, 0.0, 0.0}, {0}}; + auto ob2 = HermitianObsT{std::vector(16, ComplexT{}), {0, 1}}; + REQUIRE_THROWS_AS( + HermitianObsT(std::vector{0.0, 0.0, 0.0}, {0}), + LightningException); + REQUIRE_THROWS_AS( + HermitianObsT(std::vector{0.0, 0.0, 0.0, 0.0, 0.0}, + {0, 1}), + LightningException); + } + + SECTION("Test get obs name") { + std::vector mat = { + {0.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}}; + auto obs = HermitianObsT(mat, std::vector{0}); + auto obs_metaData = obs.getMetaData()[0][0]; + std::ostringstream res; + res << "Hermitian" << MatrixHasher()(mat); + CHECK(obs.getObsName() == res.str()); + CHECK(obs.getWires() == std::vector{0}); + + CHECK(obs.getNumTensors() == std::vector{1}); + CHECK(obs.getNumStateModes()[0] == std::vector{1}); + CHECK(obs.getStateModes()[0][0] == std::vector{0}); + CHECK(obs.getCoeffs() == std::vector{1.0}); + CHECK(std::get<0>(obs_metaData) == "Hermitian"); + CHECK(std::get<1>(obs_metaData) == std::vector{}); + CHECK(std::get<2>(obs_metaData) == mat); + } + + SECTION("Comparing objects matrices") { + auto ob1 = + HermitianObsT{std::vector{1.0, 0.0, 0.0, 0.0}, {0}}; + auto ob2 = + HermitianObsT{std::vector{1.0, 0.0, 0.0, 0.0}, {0}}; + auto ob3 = + HermitianObsT{std::vector{0.0, 1.0, 0.0, 0.0}, {0}}; + REQUIRE(ob1 == ob2); + REQUIRE(ob1 != ob3); + REQUIRE(ob2 != ob3); + } + + SECTION("Comparing objects wires") { + auto ob1 = + HermitianObsT{std::vector{1.0, 0.0, -1.0, 0.0}, {0}}; + auto ob2 = + HermitianObsT{std::vector{1.0, 0.0, -1.0, 0.0}, {0}}; + auto ob3 = + HermitianObsT{std::vector{1.0, 0.0, -1.0, 0.0}, {1}}; + REQUIRE(ob1 == ob2); + REQUIRE(ob1 != ob3); + REQUIRE(ob2 != ob3); + } +} + +TEMPLATE_TEST_CASE("[TensorProd]", "[Observables]", float, double) { + { + using StateTensorT = MPSTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using NamedObsT = NamedObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; + using HermitianObsT = HermitianObsTNCuda; + + SECTION("Overlapping wires throw an exception") { + auto ob1 = std::make_shared( + std::vector(16, ComplexT{0.0, 0.0}), + std::vector{0, 1}); + auto ob2_1 = std::make_shared( + "PauliX", std::vector{1}); + auto ob2_2 = std::make_shared( + "PauliZ", std::vector{2}); + auto ob2 = TensorProdObsT::create({ob2_1, ob2_2}); + + REQUIRE_THROWS_AS(TensorProdObsT::create({ob1, ob2}), + LightningException); + } + + SECTION("Constructing an observable with non-overlapping wires ") { + auto ob1 = std::make_shared( + std::vector(16, ComplexT{0.0, 0.0}), + std::vector{0, 1}); + auto ob2_1 = std::make_shared( + "PauliX", std::vector{2}); + auto ob2_2 = std::make_shared( + "PauliZ", std::vector{3}); + auto ob2 = TensorProdObsT::create({ob2_1, ob2_2}); + + REQUIRE_NOTHROW(TensorProdObsT::create({ob1, ob2})); + } + + SECTION("Constructing an invalid TensorProd(TensorProd)") { + auto ob2_1 = std::make_shared( + "PauliX", std::vector{2}); + auto ob2_2 = std::make_shared( + "PauliZ", std::vector{3}); + auto ob2 = TensorProdObsT::create({ob2_1, ob2_2}); + + REQUIRE_THROWS_AS(TensorProdObsT::create({ob2}), + LightningException); + } + + SECTION("getObsName") { + auto ob = + TensorProdObsT(std::make_shared( + "PauliX", std::vector{0}), + std::make_shared( + "PauliZ", std::vector{1})); + REQUIRE(ob.getObsName() == "PauliX[0] @ PauliZ[1]"); + CHECK(ob.getNumTensors() == std::vector{2}); + CHECK(ob.getNumStateModes()[0] == std::vector{1, 1}); + CHECK(ob.getStateModes()[0][0] == std::vector{0}); + CHECK(ob.getStateModes()[0][1] == std::vector{1}); + CHECK(ob.getCoeffs() == std::vector{1.0}); + + CHECK(std::get<0>(ob.getMetaData()[0][0]) == "PauliX"); + CHECK(std::get<0>(ob.getMetaData()[0][1]) == "PauliZ"); + CHECK(std::get<1>(ob.getMetaData()[0][0]) == + std::vector{}); + CHECK(std::get<1>(ob.getMetaData()[0][1]) == + std::vector{}); + CHECK(std::get<2>(ob.getMetaData()[0][0]) == + std::vector{}); + CHECK(std::get<2>(ob.getMetaData()[0][1]) == + std::vector{}); + } + + SECTION("Compare tensor product observables") { + auto ob1 = + TensorProdObsT{std::make_shared( + "PauliX", std::vector{0}), + std::make_shared( + "PauliZ", std::vector{1})}; + auto ob2 = + TensorProdObsT{std::make_shared( + "PauliX", std::vector{0}), + std::make_shared( + "PauliZ", std::vector{1})}; + auto ob3 = + TensorProdObsT{std::make_shared( + "PauliX", std::vector{0}), + std::make_shared( + "PauliZ", std::vector{2})}; + auto ob4 = + TensorProdObsT{std::make_shared( + "PauliZ", std::vector{0}), + std::make_shared( + "PauliZ", std::vector{1})}; + + auto ob5 = TensorProdObsT{std::make_shared( + "PauliZ", std::vector{0})}; + + REQUIRE(ob1 == ob2); + REQUIRE(ob1 != ob3); + REQUIRE(ob1 != ob4); + REQUIRE(ob1 != ob5); + } + } +} + +TEMPLATE_TEST_CASE("[Hamiltonian]", "[Observables]", float, double) { + { + using StateTensorT = MPSTNCuda; + using PrecisionT = typename StateTensorT::PrecisionT; + using ComplexT = typename StateTensorT::ComplexT; + using NamedObsT = NamedObsTNCuda; + using TensorProdObsT = TensorProdObsTNCuda; + using HamiltonianT = HamiltonianTNCuda; + + const auto h = PrecisionT{0.809}; // half of the golden ratio + + auto zz = std::make_shared( + std::make_shared("PauliZ", std::vector{0}), + std::make_shared("PauliZ", std::vector{1})); + + auto x1 = + std::make_shared("PauliX", std::vector{0}); + auto x2 = + std::make_shared("PauliX", std::vector{1}); + + SECTION("Hamiltonian constructor only accepts valid arguments") { + REQUIRE_NOTHROW( + HamiltonianT::create({PrecisionT{1.0}, h, h}, {zz, x1, x2})); + + REQUIRE_THROWS_AS( + HamiltonianT::create({PrecisionT{1.0}, h}, {zz, x1, x2}), + LightningException); + + SECTION("getObsName") { + auto X0 = std::make_shared( + "PauliX", std::vector{0}); + auto Z2 = std::make_shared( + "PauliZ", std::vector{2}); + + REQUIRE( + HamiltonianT::create({0.3, 0.5}, {X0, Z2})->getObsName() == + "Hamiltonian: { 'coeffs' : [0.3, 0.5], " + "'observables' : [PauliX[0], PauliZ[2]]}"); + } + + SECTION("Compare Hamiltonians") { + auto X0 = std::make_shared( + "PauliX", std::vector{0}); + auto X1 = std::make_shared( + "PauliX", std::vector{1}); + auto X2 = std::make_shared( + "PauliX", std::vector{2}); + + auto Y0 = std::make_shared( + "PauliY", std::vector{0}); + auto Y1 = std::make_shared( + "PauliY", std::vector{1}); + auto Y2 = std::make_shared( + "PauliY", std::vector{2}); + + auto Z0 = std::make_shared( + "PauliZ", std::vector{0}); + auto Z1 = std::make_shared( + "PauliZ", std::vector{1}); + auto Z2 = std::make_shared( + "PauliZ", std::vector{2}); + + auto ham1 = HamiltonianT::create( + {0.8, 0.5, 0.7}, + { + std::make_shared(X0, Y1, Z2), + std::make_shared(Z0, X1, Y2), + std::make_shared(Y0, Z1, X2), + }); + + auto ham2 = HamiltonianT::create( + {0.8, 0.5, 0.7}, + { + std::make_shared(X0, Y1, Z2), + std::make_shared(Z0, X1, Y2), + std::make_shared(Y0, Z1, X2), + }); + + auto ham3 = HamiltonianT::create( + {0.8, 0.5, 0.642}, + { + std::make_shared(X0, Y1, Z2), + std::make_shared(Z0, X1, Y2), + std::make_shared(Y0, Z1, X2), + }); + + auto ham4 = HamiltonianT::create( + {0.8, 0.5}, + { + std::make_shared(X0, Y1, Z2), + std::make_shared(Z0, X1, Y2), + }); + + auto ham5 = HamiltonianT::create( + {0.8, 0.5, 0.7}, + { + std::make_shared(X0, Y1, Z2), + std::make_shared(Z0, X1, Y2), + std::make_shared(Y0, Z1, Y2), + }); + + REQUIRE(*ham1 == *ham2); + REQUIRE(*ham1 != *ham3); + REQUIRE(*ham2 != *ham3); + REQUIRE(*ham2 != *ham4); + REQUIRE(*ham1 != *ham5); + + REQUIRE(ham5->getWires() == std::vector{0, 1, 2}); + REQUIRE(ham5->getCoeffs() == + std::vector{0.8, 0.5, 0.7}); + CHECK(ham5->getNumTensors() == + std::vector{3, 3, 3}); + CHECK(ham5->getNumStateModes()[0] == + std::vector{1, 1, 1}); + CHECK(ham5->getNumStateModes()[1] == + std::vector{1, 1, 1}); + CHECK(ham5->getNumStateModes()[2] == + std::vector{1, 1, 1}); + + CHECK(ham5->getStateModes()[0][0] == + std::vector{0}); + CHECK(ham5->getStateModes()[0][1] == + std::vector{1}); + CHECK(ham5->getStateModes()[0][2] == + std::vector{2}); + + CHECK(ham5->getStateModes()[1][0] == + std::vector{0}); + CHECK(ham5->getStateModes()[1][1] == + std::vector{1}); + CHECK(ham5->getStateModes()[1][2] == + std::vector{2}); + + CHECK(ham5->getStateModes()[2][0] == + std::vector{0}); + CHECK(ham5->getStateModes()[2][1] == + std::vector{1}); + CHECK(ham5->getStateModes()[2][2] == + std::vector{2}); + + CHECK(std::get<0>(ham5->getMetaData()[0][0]) == "PauliX"); + CHECK(std::get<0>(ham5->getMetaData()[1][0]) == "PauliZ"); + CHECK(std::get<0>(ham5->getMetaData()[2][0]) == "PauliY"); + + CHECK(std::get<0>(ham5->getMetaData()[0][1]) == "PauliY"); + CHECK(std::get<0>(ham5->getMetaData()[1][1]) == "PauliX"); + CHECK(std::get<0>(ham5->getMetaData()[2][1]) == "PauliZ"); + + CHECK(std::get<0>(ham5->getMetaData()[0][2]) == "PauliZ"); + CHECK(std::get<0>(ham5->getMetaData()[1][2]) == "PauliY"); + CHECK(std::get<0>(ham5->getMetaData()[2][2]) == "PauliY"); + } + + SECTION("getWires") { + auto Z0 = std::make_shared( + "PauliZ", std::vector{0}); + auto Z5 = std::make_shared( + "PauliZ", std::vector{5}); + auto Z9 = std::make_shared( + "PauliZ", std::vector{9}); + + auto ham = HamiltonianT::create({0.8, 0.5, 0.7}, {Z0, Z5, Z9}); + + REQUIRE(ham->getWires() == std::vector{0, 5, 9}); + + CHECK(ham->getCoeffs() == + std::vector{0.8, 0.5, 0.7}); + + CHECK(ham->getNumTensors() == + std::vector{1, 1, 1}); + CHECK(ham->getNumStateModes()[0] == + std::vector{1}); + CHECK(ham->getNumStateModes()[1] == + std::vector{1}); + CHECK(ham->getNumStateModes()[2] == + std::vector{1}); + CHECK(ham->getStateModes()[0][0] == + std::vector{0}); + CHECK(ham->getStateModes()[1][0] == + std::vector{5}); + CHECK(ham->getStateModes()[2][0] == + std::vector{9}); + + CHECK(std::get<0>(ham->getMetaData()[0][0]) == "PauliZ"); + CHECK(std::get<0>(ham->getMetaData()[1][0]) == "PauliZ"); + CHECK(std::get<0>(ham->getMetaData()[2][0]) == "PauliZ"); + CHECK(std::get<1>(ham->getMetaData()[0][0]) == + std::vector{}); + CHECK(std::get<1>(ham->getMetaData()[1][0]) == + std::vector{}); + CHECK(std::get<1>(ham->getMetaData()[2][0]) == + std::vector{}); + CHECK(std::get<2>(ham->getMetaData()[0][0]) == + std::vector{}); + CHECK(std::get<2>(ham->getMetaData()[1][0]) == + std::vector{}); + CHECK(std::get<2>(ham->getMetaData()[2][0]) == + std::vector{}); + } + + SECTION("Throw Errors") { + auto X0 = std::make_shared( + "PauliX", std::vector{0}); + auto Z1 = std::make_shared( + "PauliZ", std::vector{1}); + + auto ob0 = HamiltonianT::create({TestType(0.5)}, {X0}); + + auto ob1 = HamiltonianT::create({TestType(0.5)}, {Z1}); + + REQUIRE_THROWS_AS( + HamiltonianT::create({TestType(0.5), TestType(0.5)}, + {ob0, ob1}), + LightningException); + } + } + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/runner_lightning_tensor_observables.cpp b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/runner_lightning_tensor_observables.cpp new file mode 100644 index 000000000..4ed06df1f --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/tncuda/observables/tests/runner_lightning_tensor_observables.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/pennylane_lightning/core/src/simulators/lightning_tensor/utils/tncuda_utils/tncuda_helpers.hpp b/pennylane_lightning/core/src/simulators/lightning_tensor/utils/tncuda_utils/tncuda_helpers.hpp index 0c635d181..b126a63d8 100644 --- a/pennylane_lightning/core/src/simulators/lightning_tensor/utils/tncuda_utils/tncuda_helpers.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_tensor/utils/tncuda_utils/tncuda_helpers.hpp @@ -29,6 +29,8 @@ enum class MPSStatus : uint32_t { BEGIN = 0, MPSInitNotSet = 0, MPSInitSet, + MPSFinalizedNotSet, + MPSFinalizedSet, END }; @@ -53,4 +55,53 @@ inline SharedTNCudaHandle make_shared_tncuda_handle() { PL_CUTENSORNET_IS_SUCCESS(cutensornetCreate(&h)); return {h, TNCudaHandleDeleter()}; } + +/** + * @brief Returns the workspace size. + * + * @param tncuda_handle cutensornetHandle_t + * @param workDesc cutensornetWorkspaceDescriptor_t + * + * @return std::size_t + */ +inline std::size_t +getWorkSpaceMemorySize(const cutensornetHandle_t &tncuda_handle, + cutensornetWorkspaceDescriptor_t &workDesc) { + int64_t worksize{0}; + + PL_CUTENSORNET_IS_SUCCESS(cutensornetWorkspaceGetMemorySize( + /* const cutensornetHandle_t */ tncuda_handle, + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* cutensornetWorksizePref_t */ + CUTENSORNET_WORKSIZE_PREF_RECOMMENDED, + /* cutensornetMemspace_t*/ CUTENSORNET_MEMSPACE_DEVICE, + /* cutensornetWorkspaceKind_t */ CUTENSORNET_WORKSPACE_SCRATCH, + /* int64_t * */ &worksize)); + + // Ensure data is aligned by 256 bytes + worksize += int64_t{256} - worksize % int64_t{256}; + + return static_cast(worksize); +} + +/** + * @brief Set memory for a workspace. + * + * @param tncuda_handle cutensornetHandle_t + * @param workDesc cutensornet work space descriptor + * @param scratchPtr Pointer to scratch memory + * @param worksize Memory size of a work space + */ +inline void setWorkSpaceMemory(const cutensornetHandle_t &tncuda_handle, + cutensornetWorkspaceDescriptor_t &workDesc, + void *scratchPtr, std::size_t &worksize) { + PL_CUTENSORNET_IS_SUCCESS(cutensornetWorkspaceSetMemory( + /* const cutensornetHandle_t */ tncuda_handle, + /* cutensornetWorkspaceDescriptor_t */ workDesc, + /* cutensornetMemspace_t*/ CUTENSORNET_MEMSPACE_DEVICE, + /* cutensornetWorkspaceKind_t */ CUTENSORNET_WORKSPACE_SCRATCH, + /* void *const */ scratchPtr, + /* int64_t */ static_cast(worksize))); +} + } // namespace Pennylane::LightningTensor::TNCuda::Util diff --git a/pennylane_lightning/core/src/utils/Util.hpp b/pennylane_lightning/core/src/utils/Util.hpp index 20af810ca..22a12d408 100644 --- a/pennylane_lightning/core/src/utils/Util.hpp +++ b/pennylane_lightning/core/src/utils/Util.hpp @@ -18,6 +18,7 @@ */ #pragma once +#include #include #include #include @@ -562,4 +563,12 @@ bool is_Hermitian(size_t n, std::size_t lda, return true; } +template +std::vector cast_vector(const std::vector &vec) { + std::vector result(vec.size()); + std::transform(vec.begin(), vec.end(), result.begin(), + [&](T0 x) { return static_cast(x); }); + return result; +} + } // namespace Pennylane::Util diff --git a/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp b/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp index 0daab8e6f..4b6da353d 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp @@ -138,19 +138,35 @@ inline static constexpr auto cuToComplex(CFP_t a) /** * @brief Utility to convert std::complex types to cuComplex types * - * @tparam CFP_t std::complex types. + * @tparam ComplexT std::complex types. * @param a A std::complex type. * @return cuComplex converted a */ -template > -inline static constexpr auto complexToCu(CFP_t a) { - if constexpr (std::is_same_v>) { +template > +inline static constexpr auto complexToCu(ComplexT a) { + if constexpr (std::is_same_v>) { return make_cuDoubleComplex(a.real(), a.imag()); } else { return make_cuFloatComplex(a.real(), a.imag()); } } +/** + * @brief Utility to convert a vector of std::complex types to cuComplex types + * + * @tparam ComplexT std::complex types. + * @param vec A std::vector type. + * @return a vector of cuComplex converted vec + */ +template > +inline auto complexToCu(const std::vector &vec) { + using cuCFP_t = decltype(complexToCu(ComplexT{})); + std::vector cast_vector(vec.size()); + std::transform(vec.begin(), vec.end(), cast_vector.begin(), + [&](ComplexT x) { return complexToCu(x); }); + return cast_vector; +} + /** * @brief Compile-time scalar complex times complex. * @@ -365,4 +381,24 @@ struct MatrixHasher { } }; +/** + * @brief Normalize/Cast the index ordering to match PennyLane. + * + * @tparam IndexTypeIn Integer value type. + * @tparam IndexTypeOut Integer value type. + * @param indices Given indices to transform. + * @param num_qubits Number of qubits. + */ +template +inline auto NormalizeCastIndices(const std::vector &indices, + const std::size_t &num_qubits) + -> std::vector { + std::vector t_indices(indices.size()); + std::transform(indices.begin(), indices.end(), t_indices.begin(), + [&](IndexTypeIn i) { + return static_cast(num_qubits - 1 - i); + }); + return t_indices; +} + } // namespace Pennylane::LightningGPU::Util