From b9b6e1c309f8b63dca8c5e3d5443d070f5154dcc Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:10:12 -0400 Subject: [PATCH] Add Catalyst specific Lightning Kokkos class (#770) **Context:** Catalyst needs to build a class wrapping the Lightning Kokkos class. Here we are moving the logic from Catalyst to the Lightning repository. **Description of the Change:** Moving code from Catalyst to this repository and updating the build system to build against Catalyst. **Benefits:** Catalyst wheels will build faster. **Possible Drawbacks:** Our build system now relies on headers coming from Catalyst. It is unclear if this may cause a deadlock in the future because of our cyclic dependencies (PennyLane, PennyLane Lightning, Catalyst). Chances are small because we don't rely on Catalyst wheels or build it from scratch. **Usage:** This PR offers three ways to configure the CMake building system: 1. Providing a local source path for Catalyst. Example: ```bash cmake -BBuildTests -G Ninja \ -DCMAKE_INSTALL_PREFIX=$path_to_kokkos \ -DCATALYST_SRC_PATH=$path_to_catalyst \ -DCMAKE_BUILD_TYPE=Debug \ -DBUILD_TESTS=ON \ -DENABLE_WARNINGS=ON \ -DPL_BACKEND=lightning_kokkos ``` 3. Providing a GIT TAG. Example with the main branch: ```bash cmake -BBuildTests -G Ninja \ -DCMAKE_INSTALL_PREFIX=$path_to_kokkos \ -DCATALYST_GIT_TAG=main \ -DCMAKE_BUILD_TYPE=Debug \ -DBUILD_TESTS=ON \ -DENABLE_WARNINGS=ON \ -DPL_BACKEND=lightning_kokkos ``` 4. If the dev/user doesn't say anything the configuration will default to get headers from Catalyst GitHub main branch. Example: ```bash cmake -BBuildTests -G Ninja \ -DCMAKE_INSTALL_PREFIX=$path_to_kokkos \ -DCMAKE_BUILD_TYPE=Debug \ -DBUILD_TESTS=ON \ -DENABLE_WARNINGS=ON \ -DPL_BACKEND=lightning_kokkos ``` Check out [ADR 76](https://github.com/PennyLaneAI/adrs/pull/76) for alternative approaches. [sc-59312] --------- Co-authored-by: ringo-but-quantum Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .github/CHANGELOG.md | 4 + pennylane_lightning/core/_version.py | 2 +- .../lightning_kokkos/CMakeLists.txt | 1 + .../lightning_kokkos/catalyst/CMakeLists.txt | 92 + .../catalyst/LightningKokkosObsManager.hpp | 208 ++ .../catalyst/LightningKokkosSimulator.cpp | 580 ++++++ .../catalyst/LightningKokkosSimulator.hpp | 170 ++ .../catalyst/tests/CMakeLists.txt | 39 + .../tests/Test_LightningKokkosGradient.cpp | 298 +++ .../tests/Test_LightningKokkosMeasures.cpp | 1752 +++++++++++++++++ .../tests/Test_LightningKokkosSimulator.cpp | 662 +++++++ .../runner_lightning_kokkos_catalyst.cpp | 2 + 12 files changed, 3809 insertions(+), 1 deletion(-) create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/CMakeLists.txt create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosObsManager.hpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/CMakeLists.txt create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosGradient.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosSimulator.cpp create mode 100644 pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/runner_lightning_kokkos_catalyst.cpp diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 38893190ab..8139ce1c79 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -5,6 +5,8 @@ ### Breaking changes ### Improvements +* Add a Catalyst-specific wrapping class for Lightning Kokkos. + [(#770)](https://github.com/PennyLaneAI/pennylane-lightning/pull/770) ### Documentation @@ -14,6 +16,8 @@ This release contains contributions from (in alphabetical order): +Amintor Dusko + --- # Release 0.37.0 diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index ff1cbc44d8..1264db5e26 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.38.0-dev0" +__version__ = "0.38.0-dev1" diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_kokkos/CMakeLists.txt index 0f8b60c909..76f4874b2c 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/CMakeLists.txt +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/CMakeLists.txt @@ -70,6 +70,7 @@ endif() ############################################################################### set(COMPONENT_SUBDIRS algorithms bindings + catalyst gates measurements observables diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/CMakeLists.txt new file mode 100644 index 0000000000..cdf0570904 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.20) + +project(lightning_kokkos_catalyst LANGUAGES CXX) + +set(LK_CATALYST_FILES LightningKokkosSimulator.cpp CACHE INTERNAL "") +add_library(lightning_kokkos_catalyst SHARED ${LK_CATALYST_FILES}) + +include(FetchContent) + +if(LIGHTNING_CATALYST_SRC_PATH) + if(NOT IS_ABSOLUTE ${LIGHTNING_CATALYST_SRC_PATH}) + message(FATAL_ERROR " LIGHTNING_CATALYST_SRC_PATH=${LIGHTNING_CATALYST_SRC_PATH} must be set to an absolute path") + endif() + if(CATALYST_GIT_TAG) + message(WARN " Setting `LIGHTNING_CATALYST_SRC_PATH=${LIGHTNING_CATALYST_SRC_PATH}` overrides `CATALYST_GIT_TAG=${CATALYST_GIT_TAG}`") + endif() + + # Acquire local git hash and use for CATALYST_GIT_TAG + execute_process(COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${LIGHTNING_CATALYST_SRC_PATH} + OUTPUT_VARIABLE CATALYST_GIT_TAG + ) + message(INFO " Building against local Catalyst - path: ${LIGHTNING_CATALYST_SRC_PATH} - GIT TAG: ${CATALYST_GIT_TAG}") + + target_include_directories(lightning_kokkos_catalyst PUBLIC ${LIGHTNING_CATALYST_SRC_PATH}/runtime/lib/backend/common) + target_include_directories(lightning_kokkos_catalyst PUBLIC ${LIGHTNING_CATALYST_SRC_PATH}/runtime/include) + +else() + if(NOT CATALYST_GIT_TAG) + set(CATALYST_GIT_TAG "main" CACHE STRING "GIT_TAG value to build Catalyst") + endif() + message(INFO " Building against Catalyst GIT TAG ${CATALYST_GIT_TAG}") + + # Fetching /lib/backend/common hpp headers + set(LIB_BACKEND_COMMON_HEADERS CacheManager.hpp + QubitManager.hpp + Utils.hpp + ) + + foreach(HEADER ${LIB_BACKEND_COMMON_HEADERS}) + string(REGEX REPLACE "\\.[^.]*$" "" HEADER_NAME ${HEADER}) + FetchContent_Declare( + ${HEADER_NAME} + URL https://raw.githubusercontent.com/PennyLaneAI/catalyst/${CATALYST_GIT_TAG}/runtime/lib/backend/common/${HEADER} + DOWNLOAD_NO_EXTRACT True + SOURCE_DIR include + ) + + FetchContent_MakeAvailable(${HEADER_NAME}) + endforeach() + + # Fetching include hpp headers + set(INCLUDE_HEADERS DataView.hpp + Exception.hpp + QuantumDevice.hpp + RuntimeCAPI.h + Types.h + ) + + foreach(HEADER ${INCLUDE_HEADERS}) + string(REGEX REPLACE "\\.[^.]*$" "" HEADER_NAME ${HEADER}) + FetchContent_Declare( + ${HEADER_NAME} + URL https://raw.githubusercontent.com/PennyLaneAI/catalyst/${CATALYST_GIT_TAG}/runtime/include/${HEADER} + DOWNLOAD_NO_EXTRACT True + SOURCE_DIR include + ) + + FetchContent_MakeAvailable(${HEADER_NAME}) + endforeach() + + target_include_directories(lightning_kokkos_catalyst PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include) + +endif() + +target_include_directories(lightning_kokkos_catalyst INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(lightning_kokkos_catalyst PUBLIC lightning_compile_options + lightning_external_libs + lightning_base + lightning_gates + lightning_utils + lightning_kokkos + lightning_kokkos_algorithms + lightning_kokkos_gates + lightning_kokkos_measurements + lightning_kokkos_utils +) + +if (BUILD_TESTS) + enable_testing() + add_subdirectory("tests") +endif() diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosObsManager.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosObsManager.hpp new file mode 100644 index 0000000000..387c4e282a --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosObsManager.hpp @@ -0,0 +1,208 @@ +// Copyright 2023-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 + +#include +#include +#include +#include + +#include "Types.h" +#include "Utils.hpp" + +#include "ObservablesKokkos.hpp" + +namespace Catalyst::Runtime::Simulator { + +/** + * @brief The LightningKokkosObsManager caches observables of a program at + * runtime and maps each one to a const unique index (`int64_t`) in the scope of + * the global context manager. + */ +template class LightningKokkosObsManager final { + private: + using StateVectorT = + Pennylane::LightningKokkos::StateVectorKokkos; + using ObservableT = Pennylane::Observables::Observable; + using ObservablePairType = std::pair, ObsType>; + std::vector observables_{}; + + public: + LightningKokkosObsManager() = default; + ~LightningKokkosObsManager() = default; + + LightningKokkosObsManager(const LightningKokkosObsManager &) = delete; + LightningKokkosObsManager & + operator=(const LightningKokkosObsManager &) = delete; + LightningKokkosObsManager(LightningKokkosObsManager &&) = delete; + LightningKokkosObsManager &operator=(LightningKokkosObsManager &&) = delete; + + /** + * @brief A helper function to clear constructed observables in the program. + */ + void clear() { this->observables_.clear(); } + + /** + * @brief Check the validity of observable keys. + * + * @param obsKeys The vector of observable keys + * @return bool + */ + [[nodiscard]] auto + isValidObservables(const std::vector &obsKeys) const -> bool { + return std::all_of(obsKeys.begin(), obsKeys.end(), [this](auto i) { + return (i >= 0 && + static_cast(i) < this->observables_.size()); + }); + } + + /** + * @brief Get the constructed observable instance. + * + * @param key The observable key + * @return std::shared_ptr + */ + [[nodiscard]] auto getObservable(ObsIdType key) + -> std::shared_ptr { + RT_FAIL_IF(!this->isValidObservables({key}), "Invalid observable key"); + return std::get<0>(this->observables_[key]); + } + + /** + * @brief Get the number of observables. + * + * @return size_t + */ + [[nodiscard]] auto numObservables() const -> size_t { + return this->observables_.size(); + } + + /** + * @brief Create and cache a new NamedObs instance. + * + * @param obsId The named observable id of type ObsId + * @param wires The vector of wires the observable acts on + * @return ObsIdType + */ + [[nodiscard]] auto createNamedObs(ObsId obsId, + const std::vector &wires) + -> ObsIdType { + auto &&obs_str = std::string( + Lightning::lookup_obs( + Lightning::simulator_observable_support, obsId)); + + this->observables_.push_back(std::make_pair( + std::make_shared>(obs_str, wires), + ObsType::Basic)); + return static_cast(this->observables_.size() - 1); + } + + /** + * @brief Create and cache a new HermitianObs instance. + * + * @param matrix The row-wise Hermitian matrix + * @param wires The vector of wires the observable acts on + * @return ObsIdType + */ + [[nodiscard]] auto + createHermitianObs(const std::vector> &matrix, + const std::vector &wires) -> ObsIdType { + std::vector> matrix_k; + matrix_k.reserve(matrix.size()); + for (const auto &elem : matrix) { + matrix_k.push_back(static_cast>(elem)); + } + + this->observables_.push_back(std::make_pair( + std::make_shared>( + Pennylane::LightningKokkos::Observables::HermitianObs< + StateVectorT>{matrix_k, wires}), + ObsType::Basic)); + + return static_cast(this->observables_.size() - 1); + } + + /** + * @brief Create and cache a new TensorProd instance. + * + * @param obsKeys The vector of observable keys + * @return ObsIdType + */ + [[nodiscard]] auto + createTensorProdObs(const std::vector &obsKeys) -> ObsIdType { + const auto key_size = obsKeys.size(); + const auto obs_size = this->observables_.size(); + + std::vector> obs_vec; + obs_vec.reserve(key_size); + + for (const auto &key : obsKeys) { + RT_FAIL_IF(static_cast(key) >= obs_size || key < 0, + "Invalid observable key"); + + auto &&[obs, type] = this->observables_[key]; + obs_vec.push_back(obs); + } + + this->observables_.push_back(std::make_pair( + Pennylane::LightningKokkos::Observables::TensorProdObs< + StateVectorT>::create(obs_vec), + ObsType::TensorProd)); + + return static_cast(obs_size); + } + + /** + * @brief Create and cache a new HamiltonianObs instance. + * + * @param coeffs The vector of coefficients + * @param obsKeys The vector of observable keys + * @return ObsIdType + */ + [[nodiscard]] auto + createHamiltonianObs(const std::vector &coeffs, + const std::vector &obsKeys) -> ObsIdType { + const auto key_size = obsKeys.size(); + const auto obs_size = this->observables_.size(); + + RT_FAIL_IF( + key_size != coeffs.size(), + "Incompatible list of observables and coefficients; " + "Number of observables and number of coefficients must be equal"); + + std::vector> obs_vec; + obs_vec.reserve(key_size); + + for (auto key : obsKeys) { + RT_FAIL_IF(static_cast(key) >= obs_size || key < 0, + "Invalid observable key"); + + auto &&[obs, type] = this->observables_[key]; + obs_vec.push_back(obs); + } + + this->observables_.push_back(std::make_pair( + std::make_shared>( + Pennylane::LightningKokkos::Observables::Hamiltonian< + StateVectorT>(coeffs, std::move(obs_vec))), + ObsType::Hamiltonian)); + + return static_cast(obs_size); + } +}; +} // namespace Catalyst::Runtime::Simulator diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp new file mode 100644 index 0000000000..6fa4089d8b --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp @@ -0,0 +1,580 @@ +// Copyright 2022-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 "LightningKokkosSimulator.hpp" + +namespace Catalyst::Runtime::Simulator { + +auto LightningKokkosSimulator::AllocateQubit() -> QubitIdType { + const std::size_t num_qubits = this->device_sv->getNumQubits(); + + if (!num_qubits) { + this->device_sv = std::make_unique(1); + return this->qubit_manager.Allocate(num_qubits); + } + + std::vector> data = + this->device_sv->getDataVector(); + const std::size_t dsize = data.size(); + data.resize(dsize << 1UL); + + auto src = data.begin(); + std::advance(src, dsize - 1); + + for (auto dst = data.end() - 2; src != data.begin(); + std::advance(src, -1), std::advance(dst, -2)) { + *dst = std::move(*src); + *src = Kokkos::complex(.0, .0); + } + + this->device_sv = std::make_unique(data); + return this->qubit_manager.Allocate(num_qubits); +} + +auto LightningKokkosSimulator::AllocateQubits(std::size_t num_qubits) + -> std::vector { + if (!num_qubits) { + return {}; + } + + // at the first call when num_qubits == 0 + if (!this->GetNumQubits()) { + this->device_sv = std::make_unique(num_qubits); + return this->qubit_manager.AllocateRange(0, num_qubits); + } + + std::vector result(num_qubits); + std::generate_n(result.begin(), num_qubits, + [this]() { return AllocateQubit(); }); + return result; +} + +void LightningKokkosSimulator::ReleaseQubit(QubitIdType q) { + this->qubit_manager.Release(q); +} + +void LightningKokkosSimulator::ReleaseAllQubits() { + this->qubit_manager.ReleaseAll(); + this->device_sv = std::make_unique(0); // reset the device +} + +auto LightningKokkosSimulator::GetNumQubits() const -> std::size_t { + return this->device_sv->getNumQubits(); +} + +void LightningKokkosSimulator::StartTapeRecording() { + RT_FAIL_IF(this->tape_recording, "Cannot re-activate the cache manager"); + this->tape_recording = true; + this->cache_manager.Reset(); +} + +void LightningKokkosSimulator::StopTapeRecording() { + RT_FAIL_IF(!this->tape_recording, + "Cannot stop an already stopped cache manager"); + this->tape_recording = false; +} + +auto LightningKokkosSimulator::CacheManagerInfo() + -> std::tuple, std::vector> { + return {this->cache_manager.getNumOperations(), + this->cache_manager.getNumObservables(), + this->cache_manager.getNumParams(), + this->cache_manager.getOperationsNames(), + this->cache_manager.getObservablesKeys()}; +} + +void LightningKokkosSimulator::SetDeviceShots(std::size_t shots) { + this->device_shots = shots; +} + +auto LightningKokkosSimulator::GetDeviceShots() const -> std::size_t { + return this->device_shots; +} + +/// LCOV_EXCL_START +void LightningKokkosSimulator::PrintState() { + using std::cout; + using std::endl; + using UnmanagedComplexHostView = + Kokkos::View *, Kokkos::HostSpace, + Kokkos::MemoryTraits>; + + const std::size_t num_qubits = this->device_sv->getNumQubits(); + const std::size_t size = Pennylane::Util::exp2(num_qubits); + + std::vector> state(size, {0.0, 0.0}); + auto *state_kptr = + reinterpret_cast *>(state.data()); + auto device_data = this->device_sv->getView(); + Kokkos::deep_copy(UnmanagedComplexHostView(state_kptr, size), device_data); + + std::size_t idx = 0; + cout << "*** State-Vector of Size " << size << " ***" << endl; + cout << "["; + for (; idx < size - 1; idx++) { + cout << state[idx] << ", "; + } + cout << state[idx] << "]" << endl; +} +/// LCOV_EXCL_STOP + +auto LightningKokkosSimulator::Zero() const -> Result { + return const_cast(&GLOBAL_RESULT_FALSE_CONST); +} + +auto LightningKokkosSimulator::One() const -> Result { + return const_cast(&GLOBAL_RESULT_TRUE_CONST); +} + +void LightningKokkosSimulator::NamedOperation( + const std::string &name, const std::vector ¶ms, + const std::vector &wires, bool inverse, + const std::vector &controlled_wires, + const std::vector &controlled_values) { + RT_FAIL_IF(!controlled_wires.empty() || !controlled_values.empty(), + "LightningKokkos does not support native quantum control."); + + // Check the validity of number of qubits and parameters + RT_FAIL_IF(!isValidQubits(wires), "Given wires do not refer to qubits"); + RT_FAIL_IF(!isValidQubits(controlled_wires), + "Given controlled wires do not refer to qubits"); + + // Convert wires to device wires + auto &&dev_wires = getDeviceWires(wires); + + // Update the state-vector + this->device_sv->applyOperation(name, dev_wires, inverse, params); + + // Update tape caching if required + if (this->tape_recording) { + this->cache_manager.addOperation(name, params, dev_wires, inverse, {}, + {/*controlled_wires*/}, + {/*controlled_values*/}); + } +} + +void LightningKokkosSimulator::MatrixOperation( + const std::vector> &matrix, + const std::vector &wires, bool inverse, + const std::vector &controlled_wires, + const std::vector &controlled_values) { + using UnmanagedComplexHostView = + Kokkos::View *, Kokkos::HostSpace, + Kokkos::MemoryTraits>; + + // TODO: Remove when controlled wires API is supported + RT_FAIL_IF( + !controlled_wires.empty() || !controlled_values.empty(), + "LightningKokkos device does not support native quantum control."); + RT_FAIL_IF(!isValidQubits(wires), "Given wires do not refer to qubits"); + RT_FAIL_IF(!isValidQubits(controlled_wires), + "Given controlled wires do not refer to qubits"); + + // Convert wires to device wires + auto &&dev_wires = getDeviceWires(wires); + + std::vector> matrix_kok; + matrix_kok.resize(matrix.size()); + std::transform( + matrix.begin(), matrix.end(), matrix_kok.begin(), + [](auto c) { return static_cast>(c); }); + + Kokkos::View *> gate_matrix("gate_matrix", + matrix_kok.size()); + Kokkos::deep_copy(gate_matrix, UnmanagedComplexHostView(matrix_kok.data(), + matrix_kok.size())); + + // Update the state-vector + this->device_sv->applyMultiQubitOp(gate_matrix, dev_wires, inverse); + + // Update tape caching if required + if (this->tape_recording) { + this->cache_manager.addOperation("QubitUnitary", {}, dev_wires, inverse, + matrix_kok, {/*controlled_wires*/}, + {/*controlled_values*/}); + } +} + +auto LightningKokkosSimulator::Observable( + ObsId id, const std::vector> &matrix, + const std::vector &wires) -> ObsIdType { + RT_FAIL_IF(wires.size() > this->GetNumQubits(), "Invalid number of wires"); + RT_FAIL_IF(!isValidQubits(wires), "Invalid given wires"); + + auto &&dev_wires = getDeviceWires(wires); + + if (id == ObsId::Hermitian) { + return this->obs_manager.createHermitianObs(matrix, dev_wires); + } + + return this->obs_manager.createNamedObs(id, dev_wires); +} + +auto LightningKokkosSimulator::TensorObservable( + const std::vector &obs) -> ObsIdType { + return this->obs_manager.createTensorProdObs(obs); +} + +auto LightningKokkosSimulator::HamiltonianObservable( + const std::vector &coeffs, const std::vector &obs) + -> ObsIdType { + return this->obs_manager.createHamiltonianObs(coeffs, obs); +} + +auto LightningKokkosSimulator::Expval(ObsIdType obsKey) -> double { + RT_FAIL_IF(!this->obs_manager.isValidObservables({obsKey}), + "Invalid key for cached observables"); + + // update tape caching + if (this->tape_recording) { + cache_manager.addObservable(obsKey, MeasurementsT::Expval); + } + + auto &&obs = this->obs_manager.getObservable(obsKey); + + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + + return device_shots ? m.expval(*obs, device_shots, {}) : m.expval(*obs); +} + +auto LightningKokkosSimulator::Var(ObsIdType obsKey) -> double { + RT_FAIL_IF(!this->obs_manager.isValidObservables({obsKey}), + "Invalid key for cached observables"); + + // update tape caching + if (this->tape_recording) { + this->cache_manager.addObservable(obsKey, MeasurementsT::Var); + } + + auto &&obs = this->obs_manager.getObservable(obsKey); + + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + + return device_shots ? m.var(*obs, device_shots) : m.var(*obs); +} + +void LightningKokkosSimulator::State(DataView, 1> &state) { + using UnmanagedComplexHostView = + Kokkos::View *, Kokkos::HostSpace, + Kokkos::MemoryTraits>; + + const std::size_t num_qubits = this->device_sv->getNumQubits(); + const std::size_t size = Pennylane::Util::exp2(num_qubits); + RT_FAIL_IF(state.size() != size, + "Invalid size for the pre-allocated state vector"); + + // create a temporary buffer to copy the underlying state-vector to + std::vector> buffer(size); + auto *state_kptr = + reinterpret_cast *>(buffer.data()); + + // copy data from device to host + auto device_data = this->device_sv->getView(); + Kokkos::deep_copy(UnmanagedComplexHostView(state_kptr, size), device_data); + + // move data to state leveraging MemRefIter + std::move(buffer.begin(), buffer.end(), state.begin()); +} + +void LightningKokkosSimulator::Probs(DataView &probs) { + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + auto &&dv_probs = device_shots ? m.probs(device_shots) : m.probs(); + + RT_FAIL_IF(probs.size() != dv_probs.size(), + "Invalid size for the pre-allocated probabilities"); + + std::move(dv_probs.begin(), dv_probs.end(), probs.begin()); +} + +void LightningKokkosSimulator::PartialProbs( + DataView &probs, const std::vector &wires) { + const std::size_t numWires = wires.size(); + const std::size_t numQubits = this->GetNumQubits(); + + RT_FAIL_IF(numWires > numQubits, "Invalid number of wires"); + RT_FAIL_IF(!isValidQubits(wires), "Invalid given wires to measure"); + + auto dev_wires = getDeviceWires(wires); + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + auto &&dv_probs = + device_shots ? m.probs(dev_wires, device_shots) : m.probs(dev_wires); + + RT_FAIL_IF(probs.size() != dv_probs.size(), + "Invalid size for the pre-allocated partial-probabilities"); + + std::move(dv_probs.begin(), dv_probs.end(), probs.begin()); +} + +void LightningKokkosSimulator::Sample(DataView &samples, + std::size_t shots) { + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + // PL-Lightning-Kokkos generates samples using the alias method. + // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling + auto li_samples = m.generate_samples(shots); + + RT_FAIL_IF(samples.size() != li_samples.size(), + "Invalid size for the pre-allocated samples"); + + const std::size_t numQubits = this->GetNumQubits(); + + // The lightning samples are layed out as a single vector of size + // shots*qubits, where each element represents a single bit. The + // corresponding shape is (shots, qubits). Gather the desired bits + // corresponding to the input wires into a bitstring. + auto samplesIter = samples.begin(); + for (std::size_t shot = 0; shot < shots; shot++) { + for (std::size_t wire = 0; wire < numQubits; wire++) { + *(samplesIter++) = + static_cast(li_samples[shot * numQubits + wire]); + } + } +} +void LightningKokkosSimulator::PartialSample( + DataView &samples, const std::vector &wires, + std::size_t shots) { + const std::size_t numWires = wires.size(); + const std::size_t numQubits = this->GetNumQubits(); + + RT_FAIL_IF(numWires > numQubits, "Invalid number of wires"); + RT_FAIL_IF(!isValidQubits(wires), "Invalid given wires to measure"); + RT_FAIL_IF(samples.size() != shots * numWires, + "Invalid size for the pre-allocated partial-samples"); + + // get device wires + auto &&dev_wires = getDeviceWires(wires); + + // generate_samples is a member function of the MeasuresKokkos class. + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + + // PL-Lightning-Kokkos generates samples using the alias method. + // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling + auto li_samples = m.generate_samples(shots); + + // The lightning samples are layed out as a single vector of size + // shots*qubits, where each element represents a single bit. The + // corresponding shape is (shots, qubits). Gather the desired bits + // corresponding to the input wires into a bitstring. + auto samplesIter = samples.begin(); + for (std::size_t shot = 0; shot < shots; shot++) { + for (auto wire : dev_wires) { + *(samplesIter++) = + static_cast(li_samples[shot * numQubits + wire]); + } + } +} + +void LightningKokkosSimulator::Counts(DataView &eigvals, + DataView &counts, + std::size_t shots) { + const std::size_t numQubits = this->GetNumQubits(); + const std::size_t numElements = 1U << numQubits; + + RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, + "Invalid size for the pre-allocated counts"); + + // generate_samples is a member function of the MeasuresKokkos class. + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + + // PL-Lightning-Kokkos generates samples using the alias method. + // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling + auto li_samples = m.generate_samples(shots); + + // Fill the eigenvalues with the integer representation of the corresponding + // computational basis bitstring. In the future, eigenvalues can also be + // obtained from an observable, hence the bitstring integer is stored as a + // double. + std::iota(eigvals.begin(), eigvals.end(), 0); + std::fill(counts.begin(), counts.end(), 0); + + // The lightning samples are layed out as a single vector of size + // shots*qubits, where each element represents a single bit. The + // corresponding shape is (shots, qubits). Gather the bits of all qubits + // into a bitstring. + for (std::size_t shot = 0; shot < shots; shot++) { + std::bitset basisState; + std::size_t idx = numQubits; + for (std::size_t wire = 0; wire < numQubits; wire++) { + basisState[--idx] = li_samples[shot * numQubits + wire]; + } + counts(static_cast(basisState.to_ulong())) += 1; + } +} + +void LightningKokkosSimulator::PartialCounts( + DataView &eigvals, DataView &counts, + const std::vector &wires, std::size_t shots) { + const std::size_t numWires = wires.size(); + const std::size_t numQubits = this->GetNumQubits(); + const std::size_t numElements = 1U << numWires; + + RT_FAIL_IF(numWires > numQubits, "Invalid number of wires"); + RT_FAIL_IF(!isValidQubits(wires), "Invalid given wires to measure"); + RT_FAIL_IF((eigvals.size() != numElements || counts.size() != numElements), + "Invalid size for the pre-allocated partial-counts"); + + // get device wires + auto &&dev_wires = getDeviceWires(wires); + + // generate_samples is a member function of the MeasuresKokkos class. + Pennylane::LightningKokkos::Measures::Measurements m{ + *(this->device_sv)}; + + // PL-Lightning-Kokkos generates samples using the alias method. + // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling + auto li_samples = m.generate_samples(shots); + + // Fill the eigenvalues with the integer representation of the corresponding + // computational basis bitstring. In the future, eigenvalues can also be + // obtained from an observable, hence the bitstring integer is stored as a + // double. + std::iota(eigvals.begin(), eigvals.end(), 0); + std::fill(counts.begin(), counts.end(), 0); + + // The lightning samples are layed out as a single vector of size + // shots*qubits, where each element represents a single bit. The + // corresponding shape is (shots, qubits). Gather the desired bits + // corresponding to the input wires into a bitstring. + for (std::size_t shot = 0; shot < shots; shot++) { + std::bitset basisState; + std::size_t idx = dev_wires.size(); + for (auto wire : dev_wires) { + basisState[--idx] = li_samples[shot * numQubits + wire]; + } + counts(static_cast(basisState.to_ulong())) += 1; + } +} + +auto LightningKokkosSimulator::Measure(QubitIdType wire, + std::optional postselect) + -> Result { + // get a measurement + std::vector wires = {reinterpret_cast(wire)}; + + std::vector probs(1U << wires.size()); + DataView buffer_view(probs); + auto device_shots = GetDeviceShots(); + SetDeviceShots(0); + PartialProbs(buffer_view, wires); + SetDeviceShots(device_shots); + + // It represents the measured result, true for 1, false for 0 + bool mres = Lightning::simulateDraw(probs, postselect); + auto dev_wires = getDeviceWires(wires); + this->device_sv->collapse(dev_wires[0], mres ? 1 : 0); + return mres ? this->One() : this->Zero(); +} + +void LightningKokkosSimulator::Gradient( + std::vector> &gradients, + const std::vector &trainParams) { + const bool tp_empty = trainParams.empty(); + const std::size_t num_observables = this->cache_manager.getNumObservables(); + const std::size_t num_params = this->cache_manager.getNumParams(); + const std::size_t num_train_params = + tp_empty ? num_params : trainParams.size(); + const std::size_t jac_size = + num_train_params * this->cache_manager.getNumObservables(); + + if (!jac_size) { + return; + } + + RT_FAIL_IF(gradients.size() != num_observables, + "Invalid number of pre-allocated gradients"); + + auto &&obs_callees = this->cache_manager.getObservablesCallees(); + bool is_valid_measurements = + std::all_of(obs_callees.begin(), obs_callees.end(), + [](const auto &m) { return m == MeasurementsT::Expval; }); + RT_FAIL_IF( + !is_valid_measurements, + "Unsupported measurements to compute gradient; " + "Adjoint differentiation method only supports expectation return type"); + + // Create OpsData + auto &&ops_names = this->cache_manager.getOperationsNames(); + auto &&ops_params = this->cache_manager.getOperationsParameters(); + auto &&ops_wires = this->cache_manager.getOperationsWires(); + auto &&ops_inverses = this->cache_manager.getOperationsInverses(); + auto &&ops_matrices = this->cache_manager.getOperationsMatrices(); + auto &&ops_controlled_wires = + this->cache_manager.getOperationsControlledWires(); + auto &&ops_controlled_values = + this->cache_manager.getOperationsControlledValues(); + + const auto &&ops = Pennylane::Algorithms::OpsData( + ops_names, ops_params, ops_wires, ops_inverses, ops_matrices, + ops_controlled_wires, ops_controlled_values); + + // Create the vector of observables + auto &&obs_keys = this->cache_manager.getObservablesKeys(); + std::vector< + std::shared_ptr>> + obs_vec; + obs_vec.reserve(obs_keys.size()); + for (auto idx : obs_keys) { + obs_vec.emplace_back(this->obs_manager.getObservable(idx)); + } + + std::vector all_params; + if (tp_empty) { + all_params.reserve(num_params); + for (std::size_t i = 0; i < num_params; i++) { + all_params.push_back(i); + } + } + + auto &&state = this->device_sv->getDataVector(); + + // construct the Jacobian data + Pennylane::Algorithms::JacobianData tape{ + num_params, state.size(), state.data(), + obs_vec, ops, tp_empty ? all_params : trainParams}; + + Pennylane::LightningKokkos::Algorithms::AdjointJacobian adj; + std::vector jacobian(jac_size, 0); + adj.adjointJacobian(std::span{jacobian}, tape, + /* ref_data */ *this->device_sv, + /* apply_operations */ false); + + std::vector cur_buffer(num_train_params); + auto begin_loc_iter = jacobian.begin(); + for (std::size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + RT_ASSERT(begin_loc_iter != jacobian.end()); + RT_ASSERT(num_train_params <= gradients[obs_idx].size()); + std::move(begin_loc_iter, begin_loc_iter + num_train_params, + cur_buffer.begin()); + std::move(cur_buffer.begin(), cur_buffer.end(), + gradients[obs_idx].begin()); + begin_loc_iter += num_train_params; + } +} + +} // namespace Catalyst::Runtime::Simulator + +/// LCOV_EXCL_START +GENERATE_DEVICE_FACTORY(LightningKokkosSimulator, + Catalyst::Runtime::Simulator::LightningKokkosSimulator); +/// LCOV_EXCL_STOP diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp new file mode 100644 index 0000000000..36b4b5891e --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp @@ -0,0 +1,170 @@ +// Copyright 2022-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 LightningKokkosSimulator.hpp + */ + +#pragma once + +#define __device_lightning_kokkos + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AdjointJacobianKokkos.hpp" +#include "MeasurementsKokkos.hpp" +#include "StateVectorKokkos.hpp" + +#include "CacheManager.hpp" +#include "Exception.hpp" +#include "LightningKokkosObsManager.hpp" +#include "QuantumDevice.hpp" +#include "QubitManager.hpp" +#include "Utils.hpp" + +namespace Catalyst::Runtime::Simulator { +/** + * @brief Kokkos state vector class wrapper for Catalyst. + * This class inherits from the QuantumDevice class defined in Catalyst. + * More info: + * https://github.com/PennyLaneAI/catalyst/blob/main/runtime/include/QuantumDevice.hpp + * + */ +class LightningKokkosSimulator final : public Catalyst::Runtime::QuantumDevice { + private: + using StateVectorT = Pennylane::LightningKokkos::StateVectorKokkos; + + // static constants for RESULT values + static constexpr bool GLOBAL_RESULT_TRUE_CONST = true; + static constexpr bool GLOBAL_RESULT_FALSE_CONST = false; + + Catalyst::Runtime::QubitManager qubit_manager{}; + Catalyst::Runtime::CacheManager> cache_manager{}; + bool tape_recording{false}; + + std::size_t device_shots; + + std::unique_ptr device_sv = std::make_unique(0); + LightningKokkosObsManager obs_manager{}; + + inline auto isValidQubit(QubitIdType wire) -> bool { + return this->qubit_manager.isValidQubitId(wire); + } + + inline auto isValidQubits(const std::vector &wires) -> bool { + return std::all_of(wires.begin(), wires.end(), [this](QubitIdType w) { + return this->isValidQubit(w); + }); + } + + inline auto isValidQubits(std::size_t numWires, const QubitIdType *wires) + -> bool { + return std::all_of(wires, wires + numWires, [this](QubitIdType w) { + return this->isValidQubit(w); + }); + } + + inline auto getDeviceWires(const std::vector &wires) + -> std::vector { + std::vector res; + res.reserve(wires.size()); + std::transform( + wires.begin(), wires.end(), std::back_inserter(res), + [this](auto w) { return this->qubit_manager.getDeviceId(w); }); + return res; + } + + public: + explicit LightningKokkosSimulator(const std::string &kwargs = "{}") { + auto &&args = Catalyst::Runtime::parse_kwargs(kwargs); + device_shots = args.contains("shots") + ? static_cast(std::stoll(args["shots"])) + : 0; + } + ~LightningKokkosSimulator() = default; + + LightningKokkosSimulator(const LightningKokkosSimulator &) = delete; + LightningKokkosSimulator & + operator=(const LightningKokkosSimulator &) = delete; + LightningKokkosSimulator(LightningKokkosSimulator &&) = delete; + LightningKokkosSimulator &operator=(LightningKokkosSimulator &&) = delete; + + auto AllocateQubit() -> QubitIdType override; + auto AllocateQubits(size_t num_qubits) -> std::vector override; + void ReleaseQubit(QubitIdType q) override; + void ReleaseAllQubits() override; + [[nodiscard]] auto GetNumQubits() const -> size_t override; + void StartTapeRecording() override; + void StopTapeRecording() override; + void SetDeviceShots(size_t shots) override; + [[nodiscard]] auto GetDeviceShots() const -> size_t override; + void PrintState() override; + [[nodiscard]] auto Zero() const -> Result override; + [[nodiscard]] auto One() const -> Result override; + + void + NamedOperation(const std::string &name, const std::vector ¶ms, + const std::vector &wires, bool inverse = false, + const std::vector &controlled_wires = {}, + const std::vector &controlled_values = {}) override; + using Catalyst::Runtime::QuantumDevice::MatrixOperation; + void + MatrixOperation(const std::vector> &matrix, + const std::vector &wires, bool inverse = false, + const std::vector &controlled_wires = {}, + const std::vector &controlled_values = {}) override; + auto Observable(ObsId id, const std::vector> &matrix, + const std::vector &wires) + -> ObsIdType override; + auto TensorObservable(const std::vector &obs) + -> ObsIdType override; + auto HamiltonianObservable(const std::vector &coeffs, + const std::vector &obs) + -> ObsIdType override; + auto Expval(ObsIdType obsKey) -> double override; + auto Var(ObsIdType obsKey) -> double override; + void State(DataView, 1> &state) override; + void Probs(DataView &probs) override; + void PartialProbs(DataView &probs, + const std::vector &wires) override; + void Sample(DataView &samples, size_t shots) override; + void PartialSample(DataView &samples, + const std::vector &wires, + size_t shots) override; + void Counts(DataView &eigvals, DataView &counts, + size_t shots) override; + void PartialCounts(DataView &eigvals, + DataView &counts, + const std::vector &wires, + size_t shots) override; + auto Measure(QubitIdType wire, + std::optional postselect = std::nullopt) + -> Result override; + void Gradient(std::vector> &gradients, + const std::vector &trainParams) override; + + auto CacheManagerInfo() + -> std::tuple, std::vector>; +}; + +} // namespace Catalyst::Runtime::Simulator diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/CMakeLists.txt b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/CMakeLists.txt new file mode 100644 index 0000000000..636d8c10b6 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.20) + +project(lightning_kokkos_catalyst_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(lightning_kokkos_catalyst_tests INTERFACE) +target_link_libraries(lightning_kokkos_catalyst_tests INTERFACE Catch2::Catch2 + lightning_kokkos_catalyst +) + +ProcessTestOptions(lightning_kokkos_catalyst_tests) + +target_sources(lightning_kokkos_catalyst_tests INTERFACE runner_lightning_kokkos_catalyst.cpp) + +################################################################################ +# Define targets +################################################################################ +set(TEST_SOURCES Test_LightningKokkosSimulator.cpp + Test_LightningKokkosMeasures.cpp + Test_LightningKokkosGradient.cpp +) + +add_executable(lightning_kokkos_catalyst_tests_runner ${TEST_SOURCES}) +target_link_libraries(lightning_kokkos_catalyst_tests_runner PRIVATE lightning_kokkos_catalyst_tests) + +catch_discover_tests(lightning_kokkos_catalyst_tests_runner) + +install(TARGETS lightning_kokkos_catalyst_tests_runner DESTINATION bin) diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosGradient.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosGradient.cpp new file mode 100644 index 0000000000..1b924ebb7c --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosGradient.cpp @@ -0,0 +1,298 @@ +// Copyright 2022-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 "LightningKokkosSimulator.hpp" +#include "catch2/catch.hpp" + +/// @cond DEV +namespace { +// MemRef type definition (Helper) +template struct MemRefT { + T *data_allocated; + T *data_aligned; + std::size_t offset; + std::size_t sizes[R]; + std::size_t strides[R]; +}; +using namespace Catalyst::Runtime::Simulator; +using LKSimulator = LightningKokkosSimulator; +} // namespace +/// @endcond + +TEST_CASE("Zero qubits. Zero parameters", "[Gradient]") { + std::unique_ptr LKsim = std::make_unique(); + + std::vector> gradients; + std::vector Qs = LKsim->AllocateQubits(0); + REQUIRE_NOTHROW(LKsim->Gradient(gradients, {})); +} + +TEST_CASE("Test Gradient with zero number of obs", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + std::vector buffer(1); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0}; + + const auto q = sim->AllocateQubit(); + + sim->StartTapeRecording(); + + sim->NamedOperation("S", {}, {q}, false); + sim->NamedOperation("T", {}, {q}, false); + + REQUIRE_NOTHROW(sim->Gradient(gradients, trainParams)); + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with Var", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + std::vector buffer(1); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0}; + + const auto q = sim->AllocateQubit(); + + sim->StartTapeRecording(); + + sim->NamedOperation("RX", {-M_PI / 7}, {q}, false); + auto pz = sim->Observable(ObsId::PauliZ, {}, {q}); + sim->Var(pz); + + REQUIRE_THROWS_WITH( + sim->Gradient(gradients, trainParams), + Catch::Contains("Unsupported measurements to compute gradient")); + + REQUIRE_THROWS_WITH( + sim->Gradient(gradients, {}), + Catch::Contains("Unsupported measurements to compute gradient")); + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with Op=RX, Obs=Z", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + std::vector buffer(1); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0}; + + const auto q = sim->AllocateQubit(); + + sim->StartTapeRecording(); + + sim->NamedOperation("RX", {-M_PI / 7}, {q}, false); + auto obs = sim->Observable(ObsId::PauliZ, {}, {q}); + sim->Expval(obs); + + sim->Gradient(gradients, trainParams); + CHECK(-sin(-M_PI / 7) == Approx(buffer[0]).margin(1e-5)); + + // Update buffer + buffer[0] = 0.0; + + sim->Gradient(gradients, {}); + CHECK(-sin(-M_PI / 7) == Approx(buffer[0]).margin(1e-5)); + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with Op=RX, Obs=Hermitian", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + std::vector buffer(1); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0}; + + constexpr double expected{0.2169418696}; + + const auto q = sim->AllocateQubit(); + + sim->StartTapeRecording(); + + sim->NamedOperation("RX", {-M_PI / 7}, {q}, false); + + std::vector> mat{ + {1.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}}; + + auto obs = sim->Observable(ObsId::Hermitian, mat, {q}); + + sim->Expval(obs); + + sim->Gradient(gradients, trainParams); + CHECK(expected == Approx(buffer[0]).margin(1e-5)); + + // Update buffer + buffer[0] = 0.0; + + sim->Gradient(gradients, {}); + CHECK(expected == Approx(buffer[0]).margin(1e-5)); + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with Op=[RX,RX,RX,CZ], Obs=[Z,Z,Z]", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + constexpr std::size_t num_parms = 3; + + std::vector buffer_p0(num_parms); + std::vector buffer_p1(num_parms); + std::vector buffer_p2(num_parms); + std::vector> gradients; + gradients.emplace_back(buffer_p0); + gradients.emplace_back(buffer_p1); + gradients.emplace_back(buffer_p2); + + const std::vector trainParams{0, 1, 2}; + + const std::vector param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; + const std::vector expected{-sin(param[0]), -sin(param[1]), + -sin(param[2])}; + + const auto Qs = sim->AllocateQubits(num_parms); + + sim->StartTapeRecording(); + + sim->NamedOperation("RX", {param[0]}, {Qs[0]}, false); + sim->NamedOperation("RX", {param[1]}, {Qs[1]}, false); + sim->NamedOperation("RX", {param[2]}, {Qs[2]}, false); + sim->NamedOperation("CZ", {}, {Qs[0], Qs[2]}, false); + + std::vector> mat{ + {1.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}}; + + auto obs0 = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + auto obs1 = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + auto obs2 = sim->Observable(ObsId::PauliZ, {}, {Qs[2]}); + + sim->Expval(obs0); + sim->Expval(obs1); + sim->Expval(obs2); + + sim->Gradient(gradients, trainParams); + CHECK(expected[0] == Approx(buffer_p0[0]).margin(1e-5)); + CHECK(expected[1] == Approx(buffer_p1[1]).margin(1e-5)); + CHECK(expected[2] == Approx(buffer_p2[2]).margin(1e-5)); + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with Op=Mixed, Obs=Hamiltonian([Z@Z, H], {0.2, 0.6})", + "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + constexpr std::size_t num_parms = 6; + + std::vector buffer(num_parms); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0, 1, 2, 3, 4, 5}; + + const std::vector param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; + const std::vector expected{0.0, -0.2493761627, 0.0, + 0.0, -0.1175570505, 0.0}; + + const auto Qs = sim->AllocateQubits(3); + + sim->StartTapeRecording(); + + sim->NamedOperation("RZ", {param[0]}, {Qs[0]}, false); + sim->NamedOperation("RY", {param[1]}, {Qs[0]}, false); + sim->NamedOperation("RZ", {param[2]}, {Qs[0]}, false); + sim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + sim->NamedOperation("CNOT", {}, {Qs[1], Qs[2]}, false); + sim->NamedOperation("RZ", {param[0]}, {Qs[1]}, false); + sim->NamedOperation("RY", {param[1]}, {Qs[1]}, false); + sim->NamedOperation("RZ", {param[2]}, {Qs[1]}, false); + + std::vector> mat{ + {1.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}}; + + auto obs0 = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + auto obs1 = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + auto obs2 = sim->TensorObservable({obs0, obs1}); + auto obs3 = sim->Observable(ObsId::Hadamard, {}, {Qs[2]}); + auto obs4 = sim->HamiltonianObservable({0.2, 0.6}, {obs2, obs3}); + + sim->Expval(obs4); + + sim->Gradient(gradients, trainParams); + + for (std::size_t i = 0; i < num_parms; i++) { + CAPTURE(i); + CHECK(expected[i] == Approx(buffer[i]).margin(1e-5)); + buffer[i] = 0.0; + } + + sim->Gradient(gradients, {}); + + for (std::size_t i = 0; i < num_parms; i++) { + CAPTURE(i); + CHECK(expected[i] == Approx(buffer[i]).margin(1e-5)); + } + + sim->StopTapeRecording(); +} + +TEST_CASE("Test Gradient with QubitUnitary", "[Gradient]") { + std::unique_ptr sim = std::make_unique(); + + std::vector buffer(1); + std::vector> gradients; + gradients.emplace_back(buffer); + + const std::vector trainParams{0}; + + constexpr double expected{-0.8611041863}; + + const std::vector> matrix{ + {-0.6709485262524046, -0.6304426335363695}, + {-0.14885403153998722, 0.3608498832392019}, + {-0.2376311670004963, 0.3096798175687841}, + {-0.8818365947322423, -0.26456390390903695}, + }; + + const auto Qs = sim->AllocateQubits(1); + + sim->StartTapeRecording(); + + sim->NamedOperation("RX", {-M_PI / 7}, {Qs[0]}, false); + sim->MatrixOperation(matrix, {Qs[0]}, false); + + auto obs = sim->Observable(ObsId::PauliY, {}, {Qs[0]}); + sim->Expval(obs); + + sim->Gradient(gradients, trainParams); + CHECK(expected == Approx(buffer[0]).margin(1e-5)); + + // Update buffer + buffer[0] = 0.0; + + sim->Gradient(gradients, {}); + CHECK(expected == Approx(buffer[0]).margin(1e-5)); + + sim->StopTapeRecording(); +} diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp new file mode 100644 index 0000000000..527c8fddb9 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp @@ -0,0 +1,1752 @@ +// Copyright 2022-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 "CacheManager.hpp" +#include "LightningKokkosSimulator.hpp" +#include "QuantumDevice.hpp" +#include "Types.h" +#include "Utils.hpp" +#include "catch2/catch.hpp" +#include "cmath" + +/// @cond DEV +namespace { +// MemRef type definition (Helper) +template struct MemRefT { + T *data_allocated; + T *data_aligned; + std::size_t offset; + std::size_t sizes[R]; + std::size_t strides[R]; +}; +using namespace Catalyst::Runtime::Simulator; +using LKSimulator = LightningKokkosSimulator; +} // namespace +/// @endcond + +TEST_CASE("NameObs test with invalid number of wires", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + REQUIRE_THROWS_WITH(sim->Observable(ObsId::PauliX, {}, {1}), + Catch::Contains("Invalid number of wires")); +} + +TEST_CASE("NameObs test with invalid given wires for NamedObs", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + sim->AllocateQubit(); + + REQUIRE_THROWS_WITH(sim->Observable(ObsId::PauliX, {}, {1}), + Catch::Contains("Invalid given wires")); +} + +TEST_CASE("HermitianObs test with invalid number of wires", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + REQUIRE_THROWS_WITH(sim->Observable(ObsId::Hermitian, {}, {1}), + Catch::Contains("Invalid number of wires")); +} + +TEST_CASE("HermitianObs test with invalid given wires for HermitianObs", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + sim->AllocateQubit(); + + REQUIRE_THROWS_WITH(sim->Observable(ObsId::Hermitian, {}, {1}), + Catch::Contains("Invalid given wires")); +} + +TEST_CASE("Check an unsupported observable", "[Measures]") { + REQUIRE_THROWS_WITH( + Lightning::lookup_obs( + Lightning::simulator_observable_support, static_cast(10)), + Catch::Contains( + "The given observable is not supported by the simulator")); +} + +TEST_CASE("Measurement collapse test with 2 wires", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + constexpr std::size_t n = 2; + std::vector Qs = sim->AllocateQubits(n); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + auto m = sim->Measure(Qs[0]); + std::vector> state(1U << sim->GetNumQubits()); + DataView, 1> view(state); + sim->State(view); + + // LCOV_EXCL_START + // This is conditional over the measurement result + if (*m) { + CHECK(pow(std::abs(std::real(state[2])), 2) + + pow(std::abs(std::imag(state[2])), 2) == + Approx(1.0).margin(1e-5)); + } else { + CHECK(pow(std::abs(std::real(state[0])), 2) + + pow(std::abs(std::imag(state[0])), 2) == + Approx(1.0).margin(1e-5)); + } + // LCOV_EXCL_STOP +} + +TEST_CASE("Measurement collapse concrete logical qubit difference", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + constexpr std::size_t n = 1; + // The first time an array is allocated, logical and concrete qubits + // are the same. + std::vector Qs = sim->AllocateQubits(n); + sim->ReleaseAllQubits(); + + // Now in this the concrete qubits are shifted by n. + Qs = sim->AllocateQubits(n); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->Measure(Qs[0]); + std::vector> state(1U << sim->GetNumQubits()); + DataView, 1> view(state); + sim->State(view); + + // LCOV_EXCL_START + bool is_zero = pow(std::abs(std::real(state[0])), 2) + + pow(std::abs(std::imag(state[0])), 2) == + Approx(1.0).margin(1e-5); + bool is_one = pow(std::abs(std::real(state[1])), 2) + + pow(std::abs(std::imag(state[1])), 2) == + Approx(1.0).margin(1e-5); + bool is_valid = is_zero ^ is_one; + CHECK(is_valid); + // LCOV_EXCL_STOP +} + +TEST_CASE("Mid-circuit measurement naive test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + intptr_t q; + + q = sim->AllocateQubit(); + + sim->NamedOperation("PauliX", {}, {q}, false); + + auto m = sim->Measure(q); + + CHECK(*m); +} + +TEST_CASE("Mid-circuit measurement test with postselect = 0", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + intptr_t q; + + q = sim->AllocateQubit(); + + sim->NamedOperation("Hadamard", {}, {q}, false); + + auto m = sim->Measure(q, 0); + + CHECK(*m == 0); +} + +TEST_CASE("Mid-circuit measurement test with postselect = 1", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + intptr_t q; + + q = sim->AllocateQubit(); + + sim->NamedOperation("Hadamard", {}, {q}, false); + + auto m = sim->Measure(q, 1); + + CHECK(*m == 1); +} + +TEST_CASE("Mid-circuit measurement test with invalid postselect value", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + intptr_t q; + + q = sim->AllocateQubit(); + + sim->NamedOperation("Hadamard", {}, {q}, false); + + REQUIRE_THROWS_WITH(sim->Measure(q, 2), + Catch::Contains("Invalid postselect value")); +} + +TEST_CASE("Expval(ObsT) test with invalid key for cached observables", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + REQUIRE_THROWS_WITH(sim->Expval(0), + Catch::Contains("Invalid key for cached observables")); +} + +TEST_CASE("Expval(NamedObs) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + CHECK(sim->Expval(px) == Approx(1.0).margin(1e-5)); + CHECK(sim->Expval(py) == Approx(.0).margin(1e-5)); + CHECK(sim->Expval(pz) == Approx(-1.0).margin(1e-5)); +} + +TEST_CASE("Expval(NamedObs) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + CHECK(sim->Expval(px) == Approx(0.0).margin(5e-2)); + CHECK(sim->Expval(py) == Approx(0.0).margin(5e-2)); + CHECK(sim->Expval(pz) == Approx(-1.0).margin(5e-2)); +} + +TEST_CASE("Expval(HermitianObs) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + + CHECK(sim->Expval(h1) == Approx(.0).margin(1e-5)); + CHECK(sim->Expval(h2) == Approx(.0).margin(1e-5)); +} + +TEST_CASE("Expval(HermitianObs) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); + +#ifndef PL_USE_LAPACK + REQUIRE_THROWS_WITH( + sim->Expval(h1), + Catch::Contains( + "Hermitian observables with shot measurement are not supported")); +#else + CHECK(sim->Expval(h1) == Approx(0.0).margin(1e-5)); +#endif +} + +TEST_CASE("Var(HermitianObs) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); +#ifndef PL_USE_LAPACK + REQUIRE_THROWS_WITH( + sim->Var(h1), + Catch::Contains( + "Hermitian observables with shot measurement are not supported")); +#else + CHECK(sim->Var(h1) == Approx(0.0).margin(1e-5)); +#endif +} + +TEST_CASE("Expval(TensorProd(NamedObs)) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpx = sim->TensorObservable({px}); + ObsIdType tpy = sim->TensorObservable({py}); + ObsIdType tpz = sim->TensorObservable({pz}); + + CHECK(sim->Expval(tpx) == Approx(1.0).margin(1e-5)); + CHECK(sim->Expval(tpy) == Approx(.0).margin(1e-5)); + CHECK(sim->Expval(tpz) == Approx(-1.0).margin(1e-5)); +} + +TEST_CASE("Expval(TensorProd(NamedObs)) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpx = sim->TensorObservable({px}); + ObsIdType tpy = sim->TensorObservable({py}); + ObsIdType tpz = sim->TensorObservable({pz}); + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + CHECK(sim->Expval(tpx) == Approx(1.0).margin(5e-2)); + CHECK(sim->Expval(tpy) == Approx(.0).margin(5e-2)); + CHECK(sim->Expval(tpz) == Approx(-1.0).margin(5e-2)); +} + +TEST_CASE("Expval(TensorProd(NamedObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpxy = sim->TensorObservable({px, py}); + ObsIdType tpxz = sim->TensorObservable({px, pz}); + + REQUIRE_THROWS_WITH( + sim->TensorObservable({px, py, pz}), + Catch::Contains("All wires in observables must be disjoint.")); + + CHECK(sim->Expval(tpxy) == Approx(0.0).margin(1e-5)); + CHECK(sim->Expval(tpxz) == Approx(-1.0).margin(1e-5)); +} + +TEST_CASE("Expval(TensorProd(NamedObs[])) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + std::unique_ptr sim0 = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + Qs.push_back(sim0->AllocateQubit()); + } + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + sim0->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim0->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim0->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType tpxy = sim->TensorObservable({px, py}); + + ObsIdType px0 = sim0->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py0 = sim0->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType tpxy0 = sim0->TensorObservable({px0, py0}); + + CHECK(sim->Expval(tpxy) == Approx(sim0->Expval(tpxy0)).margin(5e-2)); +} + +TEST_CASE("Expval(TensorProd(HermitianObs))", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tph1 = sim->TensorObservable({h1}); + ObsIdType tph2 = sim->TensorObservable({h2}); + + CHECK(sim->Expval(tph1) == Approx(.0).margin(1e-5)); + CHECK(sim->Expval(tph2) == Approx(.0).margin(1e-5)); +} + +TEST_CASE("Expval(TensorProd(HermitianObs[]))", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(4, {1.0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tp = sim->TensorObservable({h1, h2}); + + CHECK(sim->Expval(tp) == Approx(.0).margin(1e-5)); +} + +TEST_CASE("Expval(TensorProd(Obs[]))", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tp = sim->TensorObservable({px, h, pz}); + + CHECK(sim->Expval(tp) == Approx(-3.0).margin(1e-5)); +} + +TEST_CASE("Expval(Tensor(Hamiltonian(NamedObs[]), NamedObs)) test", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + ObsIdType hxy = sim->HamiltonianObservable({0.4, 0.8}, {px, py}); + ObsIdType thz = sim->TensorObservable({hxy, pz}); + + CHECK(sim->Expval(thz) == Approx(-0.4).margin(1e-5)); +} + +TEST_CASE("Expval(Tensor(HermitianObs, Hamiltonian()) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 3; + std::vector Qs = sim->AllocateQubits(n); + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType her = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[1]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[2]}); + ObsIdType hxy = sim->HamiltonianObservable({0.4, 0.8}, {px, py}); + ObsIdType ten = sim->TensorObservable({her, hxy}); + + CHECK(sim->Expval(ten) == Approx(0.0).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian(NamedObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hxyz = sim->HamiltonianObservable({0.4, 0.8, 0.2}, {px, py, pz}); + + CHECK(sim->Expval(hxyz) == Approx(0.2).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian(NamedObs[])) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hxyz = sim->HamiltonianObservable({0.4, 0.8, 0.2}, {px, py, pz}); + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + CHECK(sim->Expval(hxyz) == Approx(0.2).margin(5e-2)); +} + +TEST_CASE("Expval(Hamiltonian(TensorObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpxy = sim->TensorObservable({px, py}); + ObsIdType tpxz = sim->TensorObservable({px, pz}); + ObsIdType hxyz = sim->HamiltonianObservable({0.2, 0.6}, {tpxy, tpxz}); + + CHECK(sim->Expval(hxyz) == Approx(-.6).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian(Hermitian[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hxhz = sim->HamiltonianObservable({0.2, 0.3, 0.6}, {px, h, pz}); + + CHECK(sim->Expval(hxhz) == Approx(0.5).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian({TensorProd, Hermitian}[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tp = sim->TensorObservable({px, pz}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {h, tp}); + + CHECK(sim->Expval(hhtp) == Approx(1.2).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian({Hamiltonian, Hermitian}[])) test", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hp = sim->HamiltonianObservable({0.2, 0.6}, {px, pz}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {h, hp}); + + CHECK(sim->Expval(hhtp) == Approx(1.38).margin(1e-5)); +} + +TEST_CASE("Expval(Hamiltonian({Hamiltonian(Hamiltonian), Hermitian}[])) test", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hp = sim->HamiltonianObservable({0.2, 0.6}, {px, pz}); + ObsIdType hhp = sim->HamiltonianObservable({1}, {hp}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {hhp, h}); + + CHECK(sim->Expval(hhtp) == Approx(0.7).margin(1e-5)); +} + +TEST_CASE("Var(NamedObs) test with numWires=4", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[3]}); + + CHECK(sim->Var(px) == Approx(.0).margin(1e-5)); + CHECK(sim->Var(py) == Approx(1.0).margin(1e-5)); + CHECK(sim->Var(pz) == Approx(.0).margin(1e-5)); +} + +TEST_CASE("Var(NamedObs) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 5000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[0]}); + + CHECK(sim->Var(py) == Approx(1.0).margin(5e-2)); +} + +TEST_CASE("Var(HermitianObs) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + + CHECK(sim->Var(h1) == Approx(.0).margin(1e-5)); + CHECK(sim->Var(h2) == Approx(1.0).margin(1e-5)); +} + +TEST_CASE("Var(TensorProd(NamedObs)) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpx = sim->TensorObservable({px}); + ObsIdType tpy = sim->TensorObservable({py}); + ObsIdType tpz = sim->TensorObservable({pz}); + + CHECK(sim->Var(tpx) == Approx(.0).margin(1e-5)); + CHECK(sim->Var(tpy) == Approx(1.0).margin(1e-5)); + CHECK(sim->Var(tpz) == Approx(.0).margin(1e-5)); +} + +TEST_CASE("Var(TensorProd(NamedObs)) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpx = sim->TensorObservable({px}); + ObsIdType tpz = sim->TensorObservable({pz}); + + CHECK(sim->Var(tpx) == Approx(.0).margin(5e-2)); + CHECK(sim->Var(tpz) == Approx(.0).margin(5e-2)); +} + +TEST_CASE("Var(TensorProd(NamedObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpxy = sim->TensorObservable({px, py}); + ObsIdType tpxz = sim->TensorObservable({px, pz}); + + CHECK(sim->Var(tpxy) == Approx(1.0).margin(1e-5)); + CHECK(sim->Var(tpxz) == Approx(0.0).margin(1e-5)); +} + +TEST_CASE("Var(TensorProd(HermitianObs)) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(16, {0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[0], Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tph1 = sim->TensorObservable({h1}); + ObsIdType tph2 = sim->TensorObservable({h2}); + + CHECK(sim->Var(tph1) == Approx(.0).margin(1e-5)); + CHECK(sim->Var(tph2) == Approx(1.0).margin(1e-5)); +} + +TEST_CASE("Var(TensorProd(HermitianObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 2; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat1(4, {1.0, 0}); + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType h1 = sim->Observable(ObsId::Hermitian, mat1, {Qs[1]}); + ObsIdType h2 = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tp = sim->TensorObservable({h1, h2}); + + CHECK(sim->Var(tp) == Approx(2.0).margin(1e-5)); +} + +TEST_CASE("Var(TensorProd(Obs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType tp = sim->TensorObservable({px, h, pz}); + + CHECK(sim->Var(tp) == Approx(4.0).margin(1e-5)); +} + +TEST_CASE("Var(Tensor(Hamiltonian(NamedObs[]), NamedObs)) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + ObsIdType hxy = sim->HamiltonianObservable({0.4, 0.8}, {px, py}); + ObsIdType thz = sim->TensorObservable({hxy, pz}); + + CHECK(sim->Var(thz) == Approx(0.64).margin(1e-5)); +} + +TEST_CASE("Var(Tensor(NamedObs[])) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 5000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + ObsIdType thz = sim->TensorObservable({px, py, pz}); + + CHECK(sim->Var(thz) == Approx(0.99998976).margin(5e-2)); +} + +TEST_CASE("Var(Tensor(NamedObs[])) shots test without gates " + "(influenced from a bug in Lightning)", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 3; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 5000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[0]}); + ObsIdType thz = sim->TensorObservable({px, py, pz}); + + CHECK(sim->Var(thz) == Approx(0.99966144).margin(5e-2)); +} + +TEST_CASE("Var(Tensor(HermitianObs, Hamiltonian()) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 3; + std::vector Qs = sim->AllocateQubits(n); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + std::vector> mat2{ + {1.0, 0.0}, {0.0, 0.0}, {-1.0, 0.0}, {0.0, 0.0}}; + + ObsIdType her = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[1]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[2]}); + ObsIdType hxy = sim->HamiltonianObservable({0.4, 0.8}, {px, py}); + ObsIdType ten = sim->TensorObservable({her, hxy}); + + CHECK(sim->Var(ten) == Approx(0.8).margin(1e-5)); +} + +TEST_CASE("Var(Tensor(HermitianObs, Hamiltonian()) shots test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 3; + std::vector Qs = sim->AllocateQubits(n); + + constexpr std::size_t num_shots = 5000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[1]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[2]}); + ObsIdType hxy = sim->HamiltonianObservable({0.4, 0.8}, {px, py}); + + CHECK(sim->Var(hxy) == Approx(0.8).margin(5e-2)); +} + +TEST_CASE("Var(Hamiltonian(NamedObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hxyz = sim->HamiltonianObservable({0.4, 0.8, 0.2}, {px, py, pz}); + + CHECK(sim->Var(hxyz) == Approx(0.64).margin(1e-5)); +} + +TEST_CASE("Var(Hamiltonian(TensorObs[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim->Observable(ObsId::PauliY, {}, {Qs[1]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tpxy = sim->TensorObservable({px, py}); + ObsIdType tpxz = sim->TensorObservable({px, pz}); + ObsIdType hxyz = sim->HamiltonianObservable({0.2, 0.6}, {tpxy, tpxz}); + + CHECK(sim->Var(hxyz) == Approx(0.04).margin(1e-5)); +} + +TEST_CASE("Var(Hamiltonian(Hermitian[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hxhz = sim->HamiltonianObservable({0.2, 0.3, 0.6}, {px, h, pz}); + + CHECK(sim->Var(hxhz) == Approx(0.36).margin(1e-5)); +} + +TEST_CASE("Var(Hamiltonian({TensorProd, Hermitian}[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType tp = sim->TensorObservable({px, pz}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {h, tp}); + + CHECK(sim->Var(hhtp) == Approx(1.0).margin(1e-5)); +} + +TEST_CASE("Var(Hamiltonian({Hamiltonian, Hermitian}[])) test", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hp = sim->HamiltonianObservable({0.2, 0.6}, {px, pz}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {h, hp}); + + CHECK(sim->Var(hhtp) == Approx(1.0).margin(1e-5)); +} + +TEST_CASE("Var(Hamiltonian({Hamiltonian(Hamiltonian), Hermitian}[])) test", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("PauliX", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim->Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType pz = sim->Observable(ObsId::PauliZ, {}, {Qs[1]}); + ObsIdType hp = sim->HamiltonianObservable({0.2, 0.6}, {px, pz}); + ObsIdType hhp = sim->HamiltonianObservable({1}, {hp}); + + std::vector> mat2{ + {1.0, 0.0}, {2.0, 0.0}, {-1.0, 0.0}, {3.0, 0.0}}; + ObsIdType h = sim->Observable(ObsId::Hermitian, mat2, {Qs[0]}); + ObsIdType hhtp = sim->HamiltonianObservable({0.5, 0.3}, {hhp, h}); + + CHECK(sim->Var(hhtp) == Approx(0.36).margin(1e-5)); +} + +TEST_CASE("State test with incorrect size", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs = sim->AllocateQubits(n); + + std::vector> state(1U << (n - 1)); + DataView, 1> view(state); + REQUIRE_THROWS_WITH( + sim->State(view), + Catch::Contains("Invalid size for the pre-allocated state vector")); +} + +TEST_CASE("State test with numWires=4", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs = sim->AllocateQubits(n); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + std::vector> state(1U << sim->GetNumQubits()); + DataView, 1> view(state); + sim->State(view); + + for (std::size_t i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 12 || i == 14) { + CHECK(std::real(state[i]) == Approx(0.).margin(1e-5)); + CHECK(std::imag(state[i]) == Approx(0.5).margin(1e-5)); + } else { + CHECK(std::real(state[i]) == Approx(0.).margin(1e-5)); + CHECK(std::imag(state[i]) == Approx(0.).margin(1e-5)); + } + } +} + +TEST_CASE("PartialProbs test with incorrect numWires and numAlloc", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + std::vector probs_vec(1); + DataView probs_view(probs_vec); + + REQUIRE_THROWS_WITH( + sim->PartialProbs(probs_view, {Qs[0], Qs[1], Qs[2], Qs[3], Qs[0]}), + Catch::Contains("Invalid number of wires")); + + REQUIRE_THROWS_WITH( + sim->PartialProbs(probs_view, {Qs[0]}), + Catch::Contains( + "Invalid size for the pre-allocated partial-probabilities")); + + REQUIRE_THROWS_WITH( + sim->Probs(probs_view), + Catch::Contains("Invalid size for the pre-allocated probabilities")); + + sim->ReleaseQubit(Qs[0]); + + REQUIRE_THROWS_WITH(sim->PartialProbs(probs_view, {Qs[0]}), + Catch::Contains("Invalid given wires to measure")); +} + +TEST_CASE("Probs and PartialProbs tests with numWires=0-4", "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + std::vector probs0(1); + DataView view0(probs0); + sim->PartialProbs(view0, std::vector{}); + + std::vector probs1(2); + DataView view1(probs1); + sim->PartialProbs(view1, std::vector{Qs[2]}); + + std::vector probs2(4); + DataView view2(probs2); + sim->PartialProbs(view2, std::vector{Qs[0], Qs[3]}); + + std::vector probs3(16); + DataView view3(probs3); + sim->PartialProbs(view3, Qs); + + std::vector probs4(16); + DataView view4(probs4); + sim->Probs(view4); + + CHECK(probs0.size() == 1); + CHECK(probs0[0] == Approx(1.0)); + CHECK(probs1[0] == Approx(0.5).margin(1e-5)); + CHECK(probs1[1] == Approx(0.5).margin(1e-5)); + for (std::size_t i = 0; i < 4; i++) { + if (i == 0 || i == 2) { + CHECK(probs2[i] == Approx(0.5).margin(1e-5)); + } else { + CHECK(probs2[i] == Approx(0.).margin(1e-5)); + } + } + for (std::size_t i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 12 || i == 14) { + CHECK(probs3[i] == Approx(0.25).margin(1e-5)); + CHECK(probs4[i] == Approx(0.25).margin(1e-5)); + } else { + CHECK(probs3[i] == Approx(0.).margin(1e-5)); + CHECK(probs4[i] == Approx(0.).margin(1e-5)); + } + } +} + +TEST_CASE("Probs and PartialProbs shots tests with numWires=0-4", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + constexpr std::size_t num_shots = 10000; + sim->SetDeviceShots(num_shots); + + sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim->NamedOperation("PauliY", {}, {Qs[1]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim->NamedOperation("PauliZ", {}, {Qs[3]}, false); + + std::vector probs0(1); + DataView view0(probs0); + sim->PartialProbs(view0, std::vector{}); + + std::vector probs1(2); + DataView view1(probs1); + sim->PartialProbs(view1, std::vector{Qs[2]}); + + std::vector probs2(4); + DataView view2(probs2); + sim->PartialProbs(view2, std::vector{Qs[0], Qs[3]}); + + std::vector probs3(16); + DataView view3(probs3); + sim->PartialProbs(view3, Qs); + + std::vector probs4(16); + DataView view4(probs4); + sim->Probs(view4); + + CHECK(probs0.size() == 1); + CHECK(probs0[0] == Approx(1.0).margin(5e-2)); + CHECK(probs1[0] == Approx(0.5).margin(5e-2)); + CHECK(probs1[1] == Approx(0.5).margin(5e-2)); + for (std::size_t i = 0; i < 4; i++) { + if (i == 0 || i == 2) { + CHECK(probs2[i] == Approx(0.5).margin(5e-2)); + } else { + CHECK(probs2[i] == Approx(0.).margin(5e-2)); + } + } + for (std::size_t i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 12 || i == 14) { + CHECK(probs3[i] == Approx(0.25).margin(5e-2)); + CHECK(probs4[i] == Approx(0.25).margin(5e-2)); + } else { + CHECK(probs3[i] == Approx(0.).margin(5e-2)); + CHECK(probs4[i] == Approx(0.).margin(5e-2)); + } + } +} + +TEST_CASE("PartialSample test with incorrect numWires and numAlloc", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + std::vector samples_vec(1); + MemRefT samples{samples_vec.data(), + samples_vec.data(), + 0, + {samples_vec.size(), 1}, + {1, 1}}; + DataView view(samples.data_aligned, samples.offset, + samples.sizes, samples.strides); + + REQUIRE_THROWS_WITH( + sim->PartialSample(view, {Qs[0], Qs[1], Qs[2], Qs[3], Qs[0]}, 4), + Catch::Contains("Invalid number of wires")); + + REQUIRE_THROWS_WITH( + sim->PartialSample(view, {Qs[0], Qs[1]}, 2), + Catch::Contains("Invalid size for the pre-allocated partial-samples")); + + REQUIRE_THROWS_WITH( + sim->Sample(view, 2), + Catch::Contains("Invalid size for the pre-allocated samples")); + + sim->ReleaseQubit(Qs[0]); + + REQUIRE_THROWS_WITH(sim->PartialSample(view, {Qs[0]}, 4), + Catch::Contains("Invalid given wires to measure")); +} + +TEST_CASE("PartialCounts test with incorrect numWires and numAlloc", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + std::vector eigvals_vec(1); + DataView eigvals_view(eigvals_vec); + + std::vector counts_vec(1); + DataView counts_view(counts_vec); + + REQUIRE_THROWS_WITH(sim->PartialCounts(eigvals_view, counts_view, + {Qs[0], Qs[1], Qs[2], Qs[3], Qs[0]}, + 4), + Catch::Contains("Invalid number of wires")); + + REQUIRE_THROWS_WITH( + sim->PartialCounts(eigvals_view, counts_view, {Qs[0]}, 1), + Catch::Contains("Invalid size for the pre-allocated partial-counts")); + + REQUIRE_THROWS_WITH( + sim->Counts(eigvals_view, counts_view, 1), + Catch::Contains("Invalid size for the pre-allocated counts")); + + sim->ReleaseQubit(Qs[0]); + + REQUIRE_THROWS_WITH( + sim->PartialCounts(eigvals_view, counts_view, {Qs[0]}, 4), + Catch::Contains("Invalid given wires to measure")); +} + +TEST_CASE("Sample and PartialSample tests with numWires=0-4 shots=100", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("RX", {0.5}, {Qs[0]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + sim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + + std::size_t shots = 100; + + std::vector samples1(shots * 1); + MemRefT buffer1{ + samples1.data(), samples1.data(), 0, {shots, 1}, {1, 1}}; + DataView view1(buffer1.data_aligned, buffer1.offset, + buffer1.sizes, buffer1.strides); + sim->PartialSample(view1, std::vector{Qs[2]}, shots); + + std::vector samples2(shots * 2); + MemRefT buffer2{ + samples2.data(), samples2.data(), 0, {shots, 2}, {1, 1}}; + DataView view2(buffer2.data_aligned, buffer2.offset, + buffer2.sizes, buffer2.strides); + sim->PartialSample(view2, std::vector{Qs[0], Qs[3]}, shots); + + std::vector samples3(shots * 4); + MemRefT buffer3{ + samples3.data(), samples3.data(), 0, {shots, 4}, {1, 1}}; + DataView view3(buffer3.data_aligned, buffer3.offset, + buffer3.sizes, buffer3.strides); + sim->PartialSample(view3, Qs, shots); + + std::vector samples4(shots * 4); + MemRefT buffer4{ + samples4.data(), samples4.data(), 0, {shots, 4}, {1, 1}}; + DataView view4(buffer4.data_aligned, buffer4.offset, + buffer4.sizes, buffer4.strides); + sim->Sample(view4, shots); + + for (std::size_t i = 0; i < shots * 1; i++) + CHECK((samples1[i] == 0. || samples1[i] == 1.)); + for (std::size_t i = 0; i < shots * 2; i++) + CHECK((samples2[i] == 0. || samples2[i] == 1.)); + for (std::size_t i = 0; i < shots * 4; i++) + CHECK((samples3[i] == 0. || samples3[i] == 1.)); + for (std::size_t i = 0; i < shots * 4; i++) + CHECK((samples4[i] == 0. || samples4[i] == 1.)); +} + +TEST_CASE("Sample and PartialSample tests with numWires=0-4 " + "shots=1000 mcmc=True num_burnin=200", + "[Measures]") { + std::unique_ptr sim = + std::make_unique("{mcmc : True, num_burnin : 200}"); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim->AllocateQubit()); + } + + sim->NamedOperation("RX", {0.5}, {Qs[0]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + sim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + + std::size_t shots = 100; + + std::vector samples1(shots * 1); + MemRefT buffer1{ + samples1.data(), samples1.data(), 0, {shots, 1}, {1, 1}}; + DataView view1(buffer1.data_aligned, buffer1.offset, + buffer1.sizes, buffer1.strides); + sim->PartialSample(view1, std::vector{Qs[2]}, shots); + + std::vector samples2(shots * 2); + MemRefT buffer2{ + samples2.data(), samples2.data(), 0, {shots, 2}, {1, 1}}; + DataView view2(buffer2.data_aligned, buffer2.offset, + buffer2.sizes, buffer2.strides); + sim->PartialSample(view2, std::vector{Qs[0], Qs[3]}, shots); + + std::vector samples3(shots * 4); + MemRefT buffer3{ + samples3.data(), samples3.data(), 0, {shots, 4}, {1, 1}}; + DataView view3(buffer3.data_aligned, buffer3.offset, + buffer3.sizes, buffer3.strides); + sim->PartialSample(view3, Qs, shots); + + std::vector samples4(shots * 4); + MemRefT buffer4{ + samples4.data(), samples4.data(), 0, {shots, 4}, {1, 1}}; + DataView view4(buffer4.data_aligned, buffer4.offset, + buffer4.sizes, buffer4.strides); + sim->Sample(view4, shots); + + for (std::size_t i = 0; i < shots * 1; i++) + CHECK((samples1[i] == 0. || samples1[i] == 1.)); + for (std::size_t i = 0; i < shots * 2; i++) + CHECK((samples2[i] == 0. || samples2[i] == 1.)); + for (std::size_t i = 0; i < shots * 4; i++) + CHECK((samples3[i] == 0. || samples3[i] == 1.)); + for (std::size_t i = 0; i < shots * 4; i++) + CHECK((samples4[i] == 0. || samples4[i] == 1.)); +} + +TEST_CASE("Counts and PartialCounts tests with numWires=0-4 shots=100", + "[Measures]") { + std::unique_ptr sim = std::make_unique(); + + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs = sim->AllocateQubits(n); + + sim->NamedOperation("RX", {0.5}, {Qs[0]}, false); + sim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + sim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + + std::size_t shots = 100; + + std::vector eigvals0(1); + std::vector counts0(1); + DataView eview0(eigvals0); + DataView cview0(counts0); + sim->PartialCounts(eview0, cview0, std::vector{}, shots); + + std::vector eigvals1(2); + std::vector counts1(2); + DataView eview1(eigvals1); + DataView cview1(counts1); + sim->PartialCounts(eview1, cview1, std::vector{Qs[2]}, shots); + + std::vector eigvals2(4); + std::vector counts2(4); + DataView eview2(eigvals2); + DataView cview2(counts2); + sim->PartialCounts(eview2, cview2, std::vector{Qs[0], Qs[3]}, + shots); + + std::vector eigvals3(16); + std::vector counts3(16); + DataView eview3(eigvals3); + DataView cview3(counts3); + sim->PartialCounts(eview3, cview3, Qs, shots); + + std::vector eigvals4(16); + std::vector counts4(16); + DataView eview4(eigvals4); + DataView cview4(counts4); + sim->Counts(eview4, cview4, shots); + + CHECK(eigvals0.size() == 1); + CHECK(eigvals0[0] == 0.0); + CHECK(counts0.size() == 1); + CHECK(counts0[0] == static_cast(shots)); + CHECK((eigvals1[0] == 0. && eigvals1[1] == 1.)); + CHECK((eigvals2[0] == 0. && eigvals2[1] == 1. && eigvals2[2] == 2. && + eigvals2[3] == 3.)); + for (std::size_t i = 0; i < 16; i++) { + CHECK(eigvals3[i] == static_cast(i)); + CHECK(eigvals4[i] == static_cast(i)); + } + + CHECK(counts1[0] + counts1[1] == static_cast(shots)); + CHECK(counts2[0] + counts2[1] + counts2[2] + counts2[3] == + static_cast(shots)); + std::size_t sum3 = 0, sum4 = 0; + for (std::size_t i = 0; i < 16; i++) { + sum3 += counts3[i]; + sum4 += counts4[i]; + } + CHECK(sum3 == shots); + CHECK(sum4 == shots); +} diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosSimulator.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosSimulator.cpp new file mode 100644 index 0000000000..4df813266d --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosSimulator.cpp @@ -0,0 +1,662 @@ +// Copyright 2018-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 + +#include + +#include "LightningKokkosSimulator.hpp" +#include "QuantumDevice.hpp" +#include "TestHelpers.hpp" + +/// @cond DEV +namespace { +using namespace Catalyst::Runtime::Simulator; +using namespace Pennylane::Util; +using LKSimulator = LightningKokkosSimulator; +using QDevice = Catalyst::Runtime::QuantumDevice; + +GENERATE_DEVICE_FACTORY(LightningKokkosSimulator, + Catalyst::Runtime::Simulator::LightningKokkosSimulator); +} // namespace +/// @endcond + +/** + * @brief Tests the LightningKokkosSimulator class. + * + */ +TEST_CASE("LightningKokkosSimulator::constructor", "[constructibility]") { + SECTION("LightningKokkosSimulator") { + REQUIRE(std::is_constructible::value); + } + SECTION("LightningKokkosSimulator(string))") { + REQUIRE(std::is_constructible::value); + } +} + +TEST_CASE("Test the device factory method", "[constructibility]") { + std::unique_ptr LKsim(LightningKokkosSimulatorFactory("")); + REQUIRE(LKsim->GetNumQubits() == 0); +} + +TEST_CASE("LightningKokkosSimulator::unit_tests", "[unit tests]") { + SECTION("Managing Qubits") { + std::unique_ptr LKsim = std::make_unique(); + std::vector Qs = LKsim->AllocateQubits(0); + REQUIRE(LKsim->GetNumQubits() == 0); + LKsim->AllocateQubits(2); + REQUIRE(LKsim->GetNumQubits() == 2); + LKsim->AllocateQubits(2); + REQUIRE(LKsim->GetNumQubits() == 4); + LKsim->ReleaseQubit(0); + REQUIRE( + LKsim->GetNumQubits() == + 4); // releasing only one qubit does not change the total number. + LKsim->ReleaseAllQubits(); + REQUIRE(LKsim->GetNumQubits() == + 0); // releasing all qubits resets the simulator. + } + SECTION("Tape recording") { + std::unique_ptr LKsim = std::make_unique(); + std::vector Qs = LKsim->AllocateQubits(1); + REQUIRE_NOTHROW(LKsim->StartTapeRecording()); + REQUIRE_THROWS_WITH( + LKsim->StartTapeRecording(), + Catch::Matchers::Contains("Cannot re-activate the cache manager")); + REQUIRE_NOTHROW(LKsim->StopTapeRecording()); + REQUIRE_THROWS_WITH( + LKsim->StopTapeRecording(), + Catch::Matchers::Contains( + "Cannot stop an already stopped cache manager")); + } +} + +TEST_CASE("LightningKokkosSimulator::GateSet", "[GateSet]") { + SECTION("Identity gate") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 10; + std::vector Qs; + Qs.reserve(n_qubits); + for (std::size_t ind = 0; ind < n_qubits; ind++) { + Qs[ind] = LKsim->AllocateQubit(); + } + + for (std::size_t ind = 0; ind < n_qubits; ind += 2) { + LKsim->NamedOperation("Identity", {}, {Qs[ind]}, false); + } + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{1, 0}); + + std::complex sum{0, 0}; + for (std::size_t ind = 1; ind < state.size(); ind++) { + sum += state[ind]; + } + + CHECK(sum == std::complex{0, 0}); + } + + SECTION("PauliX gate") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 3; + std::vector Qs; + Qs.reserve(n_qubits); + for (std::size_t ind = 0; ind < n_qubits; ind++) { + Qs[ind] = LKsim->AllocateQubit(); + } + + for (std::size_t ind = 0; ind < n_qubits; ind++) { + LKsim->NamedOperation("PauliX", {}, {Qs[ind]}, false); + } + for (std::size_t ind = n_qubits; ind > 0; ind--) { + LKsim->NamedOperation("PauliX", {}, {Qs[ind - 1]}, false); + } + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{1, 0}); + + std::complex sum{0, 0}; + for (std::size_t ind = 1; ind < state.size(); ind++) { + sum += state[ind]; + } + + CHECK(sum == std::complex{0, 0}); + } + + SECTION("PauliY gate") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs; + Qs.reserve(n_qubits); + for (std::size_t ind = 0; ind < n_qubits; ind++) { + Qs[ind] = LKsim->AllocateQubit(); + } + + for (std::size_t ind = 0; ind < n_qubits; ind++) { + LKsim->NamedOperation("PauliY", {}, {Qs[ind]}, false); + } + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{0, 0}); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK(state.at(2) == std::complex{0, 0}); + CHECK(state.at(3) == std::complex{-1, 0}); + } + + SECTION("PauliY and PauliZ gates") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs; + Qs.reserve(n_qubits); + for (std::size_t ind = 0; ind < n_qubits; ind++) { + Qs[ind] = LKsim->AllocateQubit(); + } + + LKsim->NamedOperation("PauliY", {}, {Qs[0]}, false); + LKsim->NamedOperation("PauliZ", {}, {Qs[1]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{0, 0}); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK(state.at(2) == std::complex{0, 1}); + CHECK(state.at(3) == std::complex{0, 0}); + } + + SECTION("Hadamard gate") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs; + Qs.reserve(n_qubits); + for (std::size_t ind = 0; ind < n_qubits; ind++) { + Qs[ind] = LKsim->AllocateQubit(); + } + + for (std::size_t ind = 0; ind < n_qubits; ind++) { + LKsim->NamedOperation("Hadamard", {}, {Qs[ind]}, false); + } + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state[0] == + PLApproxComplex(std::complex{0.5, 0}).epsilon(1e-5)); + CHECK(state.at(1) == state.at(0)); + CHECK(state.at(2) == state.at(0)); + CHECK(state.at(3) == state.at(0)); + } + + SECTION("R(X, Y, Z) and PauliX gates") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 4; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("PauliX", {}, {Qs[0]}, false); + + LKsim->NamedOperation("RX", {0.123}, {Qs[1]}, false); + LKsim->NamedOperation("RY", {0.456}, {Qs[2]}, false); + LKsim->NamedOperation("RZ", {0.789}, {Qs[3]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + // calculated by pennylane. + CHECK(state.at(0) == std::complex{0, 0}); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK(state.at(2) == std::complex{0, 0}); + CHECK(state.at(3) == std::complex{0, 0}); + CHECK(state.at(4) == std::complex{0, 0}); + CHECK(state.at(5) == std::complex{0, 0}); + CHECK(state.at(6) == std::complex{0, 0}); + CHECK(state.at(7) == std::complex{0, 0}); + CHECK(state[8] == + PLApproxComplex( + std::complex{0.8975969498074641, -0.3736920921192206}) + .epsilon(1e-5)); + CHECK(state.at(9) == std::complex{0, 0}); + CHECK(state[10] == + PLApproxComplex(std::complex{0.20827363966052723, + -0.08670953277495183}) + .epsilon(1e-5)); + CHECK(state.at(11) == std::complex{0, 0}); + CHECK(state[12] == + PLApproxComplex(std::complex{-0.023011082205037697, + -0.055271914055973925}) + .epsilon(1e-5)); + CHECK(state.at(13) == std::complex{0, 0}); + CHECK(state[14] == + PLApproxComplex(std::complex{-0.005339369573836912, + -0.012825002038956146}) + .epsilon(1e-5)); + CHECK(state.at(15) == std::complex{0, 0}); + } + + SECTION("Hadamard, RX, PhaseShift with cache manager") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs; + Qs.reserve(n_qubits); + + Qs[0] = LKsim->AllocateQubit(); + Qs[1] = LKsim->AllocateQubit(); + + LKsim->StartTapeRecording(); + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("RX", {0.123}, {Qs[1]}, false); + LKsim->NamedOperation("PhaseShift", {0.456}, {Qs[0]}, false); + LKsim->StopTapeRecording(); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + // calculated by pennylane. + CHECK(state[0] == PLApproxComplex(std::complex{0.7057699753, 0}) + .epsilon(1e-5)); + CHECK(state[1] == PLApproxComplex(std::complex{0, -0.04345966}) + .epsilon(1e-5)); + CHECK(state[2] == + PLApproxComplex(std::complex{0.63365519, 0.31079312}) + .epsilon(1e-5)); + CHECK(state[3] == + PLApproxComplex(std::complex{0.01913791, -0.039019}) + .epsilon(1e-5)); + + std::tuple, std::vector> + expected{3, 0, 2, {"Hadamard", "RX", "PhaseShift"}, {}}; + REQUIRE(LKsim->CacheManagerInfo() == expected); + } + + // ============= 2-qubit operations ============= + + SECTION("PauliX and CNOT") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs; + Qs.reserve(n_qubits); + + for (std::size_t i = 0; i < n_qubits; i++) { + Qs[i] = LKsim->AllocateQubit(); + } + + LKsim->NamedOperation("PauliX", {}, {Qs[0]}, false); + LKsim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{0, 0}); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK(state.at(2) == std::complex{0, 0}); + CHECK(state.at(3) == std::complex{1, 0}); + } + + SECTION("Hadamard and CR(X, Y, Z)") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 4; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("CRX", {0.123}, {Qs[0], Qs[1]}, false); + LKsim->NamedOperation("CRY", {0.456}, {Qs[0], Qs[2]}, false); + LKsim->NamedOperation("CRZ", {0.789}, {Qs[0], Qs[3]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + // calculated by pennylane. + CHECK( + state[0] == + PLApproxComplex(std::complex{M_SQRT1_2, 0}).epsilon(1e-5)); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK(state.at(2) == std::complex{0, 0}); + CHECK(state.at(3) == std::complex{0, 0}); + CHECK(state.at(4) == std::complex{0, 0}); + CHECK(state.at(5) == std::complex{0, 0}); + CHECK(state.at(6) == std::complex{0, 0}); + CHECK(state.at(7) == std::complex{0, 0}); + CHECK(state[8] == + PLApproxComplex( + std::complex{0.6346968899812189, -0.2642402124132889}) + .epsilon(1e-5)); + CHECK(state.at(9) == std::complex{0, 0}); + CHECK(state[10] == + PLApproxComplex(std::complex{0.14727170294636227, + -0.061312898618685635}) + .epsilon(1e-5)); + CHECK(state.at(11) == std::complex{0, 0}); + CHECK(state[12] == + PLApproxComplex(std::complex{-0.016271292269623247, + -0.03908314523813921}) + .epsilon(1e-5)); + CHECK(state.at(13) == std::complex{0, 0}); + CHECK(state[14] == + PLApproxComplex(std::complex{-0.0037755044329212074, + -0.009068645910477189}) + .epsilon(1e-5)); + CHECK(state.at(15) == std::complex{0, 0}); + } + + SECTION("Hadamard and CRot") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("CRot", {M_PI, M_PI_2, 0.5}, {Qs[0], Qs[1]}, + false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK( + state[0] == + PLApproxComplex(std::complex{M_SQRT1_2, 0}).epsilon(1e-5)); + + CHECK(state[1] == + PLApproxComplex(std::complex{0, 0}).epsilon(1e-5)); + + CHECK(state[2] == PLApproxComplex(std::complex{-0.1237019796, + -0.4844562109}) + .epsilon(1e-5)); + CHECK(state[3] == + PLApproxComplex(std::complex{0.1237019796, -0.4844562109}) + .epsilon(1e-5)); + } + + SECTION("Hadamard, PauliZ, IsingXY, SWAP") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("PauliZ", {}, {Qs[0]}, false); + LKsim->NamedOperation("IsingXY", {0.2}, {Qs[1], Qs[0]}, false); + LKsim->NamedOperation("SWAP", {}, {Qs[0], Qs[1]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK( + state[0] == + PLApproxComplex(std::complex{M_SQRT1_2, 0}).epsilon(1e-5)); + CHECK(state[1] == PLApproxComplex(std::complex{-0.70357419, 0}) + .epsilon(1e-5)); + CHECK(state[2] == PLApproxComplex(std::complex{0, -0.07059289}) + .epsilon(1e-5)); + CHECK(state[3] == + PLApproxComplex(std::complex{0, 0}).epsilon(1e-5)); + } + + SECTION("Hadamard, PauliX and Toffoli") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 3; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("PauliX", {}, {Qs[1]}, false); + LKsim->NamedOperation("Toffoli", {}, {Qs[0], Qs[1], Qs[2]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state.at(0) == std::complex{0, 0}); + CHECK(state.at(1) == std::complex{0, 0}); + CHECK( + state[2] == + PLApproxComplex(std::complex{M_SQRT1_2, 0}).epsilon(1e-5)); + CHECK(state.at(3) == std::complex{0, 0}); + CHECK(state.at(4) == std::complex{0, 0}); + CHECK(state.at(5) == std::complex{0, 0}); + CHECK(state.at(6) == std::complex{0, 0}); + CHECK( + state[7] == + PLApproxComplex(std::complex{M_SQRT1_2, 0}).epsilon(1e-5)); + } + + SECTION("RX, Hadamard and MultiRZ") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("RX", {M_PI}, {Qs[1]}, false); + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + LKsim->NamedOperation("MultiRZ", {M_PI}, {Qs[0], Qs[1]}, false); + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state[2] == + PLApproxComplex(std::complex{-1, 0}).epsilon(1e-5)); + } + + SECTION("Hadamard, CNOT and Matrix") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("CNOT", {}, {Qs[0], Qs[1]}, false); + + const std::vector wires = {Qs[0]}; + std::vector> matrix{ + {-0.6709485262524046, -0.6304426335363695}, + {-0.14885403153998722, 0.3608498832392019}, + {-0.2376311670004963, 0.3096798175687841}, + {-0.8818365947322423, -0.26456390390903695}, + }; + LKsim->MatrixOperation(matrix, wires, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state[0] == + PLApproxComplex(std::complex{-0.474432, -0.44579}) + .epsilon(1e-5)); + CHECK(state[1] == + PLApproxComplex(std::complex{-0.105256, 0.255159}) + .epsilon(1e-5)); + CHECK(state[2] == + PLApproxComplex(std::complex{-0.168031, 0.218977}) + .epsilon(1e-5)); + CHECK(state[3] == + PLApproxComplex(std::complex{-0.623553, -0.187075}) + .epsilon(1e-5)); + } + + SECTION("Hadamard, CR(X, Y, Z) and Matrix") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 4; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("CRX", {0.123}, {Qs[0], Qs[1]}, false); + LKsim->NamedOperation("CRY", {0.456}, {Qs[0], Qs[2]}, false); + LKsim->NamedOperation("CRZ", {0.789}, {Qs[0], Qs[3]}, false); + + const std::vector wires = {Qs[0], Qs[1], Qs[2]}; + std::vector> matrix{ + {-0.14601911598243822, -0.18655250647340088}, + {-0.03917826201290317, -0.031161687050443518}, + {0.11497626236175404, 0.38310733543366354}, + {-0.0929691815340695, 0.1219804125497268}, + {0.07306514883467692, 0.017445444816725875}, + {-0.27330866098918355, -0.6007032759764033}, + {0.4530754397715841, -0.08267189625512258}, + {0.32125201986075, -0.036845158875036116}, + {0.032317572838307884, 0.02292755555300329}, + {-0.18775945295623664, -0.060215004737844156}, + {-0.3093351335745536, -0.2061961962889725}, + {0.4216087567144761, 0.010534488410902099}, + {0.2769943541718527, -0.26016137877135465}, + {0.18727884147867532, 0.02830415812286322}, + {0.3367562196770689, -0.5250999173939218}, + {0.05770014289220745, 0.26595514845958573}, + {0.37885720163317027, 0.3110931426403546}, + {0.13436510737129648, -0.4083415934958021}, + {-0.5443665467635203, 0.2458343977310266}, + {-0.050346912365833024, 0.08709833123617361}, + {0.11505259829552131, 0.010155858056939438}, + {-0.2930849061531229, 0.019339259194141145}, + {0.011825409829453282, 0.011597907736881019}, + {-0.10565527258356637, -0.3113689446440079}, + {0.0273191284561944, -0.2479498526173881}, + {-0.5528072425836249, -0.06114469689935285}, + {-0.20560364740746587, -0.3800208994544297}, + {-0.008236143958221483, 0.3017421511504845}, + {0.04817188123334976, 0.08550951191632741}, + {-0.24081054643565586, -0.3412671345149831}, + {-0.38913538197001885, 0.09288402897806938}, + {-0.07937578245883717, 0.013979426755633685}, + {0.22246583652015395, -0.18276674810033927}, + {0.22376666162382491, 0.2995723155125488}, + {-0.1727191441070097, -0.03880522034607489}, + {0.075780203819001, 0.2818783673816625}, + {-0.6161322400651016, 0.26067347179217193}, + {-0.021161519614267765, -0.08430919051054794}, + {0.1676500381348944, -0.30645601624407504}, + {-0.28858251997285883, 0.018089595494883842}, + {-0.19590767481842053, -0.12844366632033652}, + {0.18707834504831794, -0.1363932722670649}, + {-0.07224221779769334, -0.11267803536286894}, + {-0.23897684826459387, -0.39609971967853685}, + {-0.0032110880452929555, -0.29294331305690136}, + {-0.3188741682462722, -0.17338979346647143}, + {0.08194395032821632, -0.002944814673179825}, + {-0.5695791830944521, 0.33299548924055095}, + {-0.4983660307441444, -0.4222358493977972}, + {0.05533914327048402, -0.42575842134560576}, + {-0.2187623521182678, -0.03087596187054778}, + {0.11278255885846857, 0.07075886163492914}, + {-0.3054684775292515, -0.1739796870866232}, + {0.14151567663565712, 0.20399935744127418}, + {0.06720165377364941, 0.07543463072363207}, + {0.08019665306716581, -0.3473013434358584}, + {-0.2600167605995786, -0.08795704036197827}, + {0.125680477777759, 0.266342700305046}, + {-0.1586772594600269, 0.187360909108502}, + {-0.4653314704208982, 0.4048609954619629}, + {0.39992560380733094, -0.10029244177901954}, + {0.2533527906886461, 0.05222114898540775}, + {-0.15840033949128557, -0.2727320427534386}, + {-0.21590866323269536, -0.1191163626522938}, + }; + LKsim->MatrixOperation(matrix, wires, false); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + CHECK(state[0] == + PLApproxComplex(std::complex{-0.141499, -0.230993}) + .epsilon(1e-5)); + CHECK(state[2] == + PLApproxComplex(std::complex{0.135423, -0.235563}) + .epsilon(1e-5)); + CHECK(state[4] == + PLApproxComplex(std::complex{0.299458, 0.218321}) + .epsilon(1e-5)); + CHECK(state[6] == + PLApproxComplex(std::complex{0.0264869, -0.154913}) + .epsilon(1e-5)); + CHECK(state[8] == + PLApproxComplex(std::complex{-0.186607, 0.188884}) + .epsilon(1e-5)); + CHECK(state[10] == + PLApproxComplex(std::complex{-0.271843, -0.281136}) + .epsilon(1e-5)); + CHECK(state[12] == + PLApproxComplex(std::complex{-0.560499, -0.310176}) + .epsilon(1e-5)); + CHECK(state[14] == + PLApproxComplex(std::complex{0.0756372, -0.226334}) + .epsilon(1e-5)); + } + + SECTION("Hadamard and IsingZZ and cache manager") { + std::unique_ptr LKsim = std::make_unique(); + + constexpr std::size_t n_qubits = 2; + std::vector Qs = LKsim->AllocateQubits(n_qubits); + + LKsim->StartTapeRecording(); + LKsim->NamedOperation("Hadamard", {}, {Qs[0]}, false); + LKsim->NamedOperation("Hadamard", {}, {Qs[1]}, false); + LKsim->NamedOperation("IsingZZ", {M_PI_4}, {Qs[0], Qs[1]}, false); + LKsim->StopTapeRecording(); + + std::vector> state(1U << LKsim->GetNumQubits()); + DataView, 1> view(state); + LKsim->State(view); + + std::complex c1{0.4619397663, -0.1913417162}; + std::complex c2{0.4619397663, 0.1913417162}; + + CHECK(state[0] == PLApproxComplex(c1).epsilon(1e-5)); + CHECK(state[1] == PLApproxComplex(c2).epsilon(1e-5)); + CHECK(state[2] == PLApproxComplex(c2).epsilon(1e-5)); + CHECK(state[3] == PLApproxComplex(c1).epsilon(1e-5)); + + std::tuple, std::vector> + expected{3, 0, 1, {"Hadamard", "Hadamard", "IsingZZ"}, {}}; + REQUIRE(LKsim->CacheManagerInfo() == expected); + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/runner_lightning_kokkos_catalyst.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/runner_lightning_kokkos_catalyst.cpp new file mode 100644 index 0000000000..4ed06df1f7 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/runner_lightning_kokkos_catalyst.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include