From fbdeeddf4b5e2d2d5f64950438cd8411630e2847 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Thu, 19 Aug 2021 17:56:22 +0100 Subject: [PATCH 01/72] Add inner product routine with STL and BLAS options --- pennylane_lightning/src/Util.hpp | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pennylane_lightning/src/Util.hpp b/pennylane_lightning/src/Util.hpp index b430b03c2f..8d7fec9aee 100644 --- a/pennylane_lightning/src/Util.hpp +++ b/pennylane_lightning/src/Util.hpp @@ -26,6 +26,13 @@ #include #include +#if __has_include() && defined _ENABLE_BLAS +#include +#define USE_CBLAS 1 +#else +#define USE_CBLAS 0 +#endif + namespace Pennylane { namespace Util { @@ -50,6 +57,12 @@ inline static constexpr std::complex ConstMult(std::complex a, a.real() * b.imag() + a.imag() * b.real()}; } +template +inline static constexpr std::complex ConstSum(std::complex a, + std::complex b) { + return a + b; +} + /** * @brief Return complex value 1+0i in the given precision. * @@ -155,6 +168,38 @@ template inline size_t dimSize(const std::vector &data) { return log2(sqrt(data.size())); } +/** + * @brief Calculates the inner-product using the best available method. + * + * @tparam T Floating point precision type. + * @param data_1 Complex data array 1. + * @param data_2 Complex data array 2. + * @return std::complex Result of inner product operation. + */ +template +std::complex innerProd(const std::complex *data_1, + const std::complex *data_2, + const size_t data_size) { + std::complex result(0, 0); + + if constexpr (USE_CBLAS) { + if constexpr (std::is_same_v) + cblas_cdotc_sub(data_size, data_1, 1, data_2, 1, &result); + else if constexpr (std::is_same_v) + cblas_zdotc_sub(data_size, data_1, 1, data_2, 1, &result); + } else { + std::inner_product(data_1, data_1 + data_size, data_2, + std::complex(0, 0), ConstSum, ConstMult); + } + return result; +} + +template +inline std::complex innerProd(const std::vector> &data_1, + const std::vector> &data_2) { + return innerProd(data_1.data(), data_2.data(), data_1.size()); +} + } // namespace Util } // namespace Pennylane From a26cb1e23026d9f11932e7f62c6d591966b5424c Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Thu, 19 Aug 2021 18:00:23 +0100 Subject: [PATCH 02/72] Add prelim adj diff implementation --- .../src/algorithms/AdjointDiff.hpp | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 pennylane_lightning/src/algorithms/AdjointDiff.hpp diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp new file mode 100644 index 0000000000..3e0ff65a77 --- /dev/null +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "../StateVector.hpp" +#include "../Util.hpp" + +// Generators not needed outside this translation unit +namespace { + +using namespace Pennylane::Util; + +template static constexpr std::vector> getP00() { + return {ONE(), ZERO(), ZERO(), ZERO()}; +} + +template static constexpr std::vector> getP11() { + return {ZERO(), ZERO(), ZERO(), ONE()}; +} + +template +void applyGeneratorRX(Pennylane::StateVector &sv, + const std::vector &wires) { + sv.applyOperation("PauliX", wires, false); +} + +template +void applyGeneratorRY(Pennylane::StateVector &sv, + const std::vector &wires) { + sv.applyOperation("PauliY", wires, false); +} + +template +void applyGeneratorRZ(Pennylane::StateVector &sv, + const std::vector &wires) { + sv.applyOperation("PauliY", wires, false); +} + +template +void applyGeneratorPhaseShift(Pennylane::StateVector &sv, + const std::vector &wires) { + sv.applyOperation(getP11(), wires, false); +} + +template +void applyGeneratorCRX(Pennylane::StateVector &sv, + const std::vector &wires) { + const vector internalIndices = sv.generateBitPatterns(wires); + const vector externalWires = sv.getIndicesAfterExclusion(wires); + const vector externalIndices = + sv.generateBitPatterns(externalWires); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = sv.getData() + externalIndex; + shiftedState[internalIndices[0]] = shiftedState[internalIndices[1]] = 0; + std::swap(shiftedState[internalIndices[2]], + shiftedState[internalIndices[3]]); + } +} + +template +void applyGeneratorCRY(Pennylane::StateVector &sv, + const std::vector &wires) { + const vector internalIndices = sv.generateBitPatterns(wires); + const vector externalWires = sv.getIndicesAfterExclusion(wires); + const vector externalIndices = + sv.generateBitPatterns(externalWires); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = sv.getData() + externalIndex; + std::complex v0 = shiftedState[internalIndices[0]]; + shiftedState[internalIndices[0]] = shiftedState[internalIndices[1]] = 0; + shiftedState[internalIndices[2]] = + -IMAG() * shiftedState[internalIndices[3]]; + shiftedState[internalIndices[3]] = IMAG() * v0; + } +} + +template +void applyGeneratorCRZ(Pennylane::StateVector &sv, + const std::vector &wires) { + const vector internalIndices = sv.generateBitPatterns(wires); + const vector externalWires = sv.getIndicesAfterExclusion(wires); + const vector externalIndices = + sv.generateBitPatterns(externalWires); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = sv.getData() + externalIndex; + shiftedState[internalIndices[0]] = shiftedState[internalIndices[1]] = 0; + shiftedState[internalIndices[3]] *= -1; + } +} + +template +void applyGeneratorControlledPhaseShift(Pennylane::StateVector &sv, + const std::vector &wires) { + const vector internalIndices = sv.generateBitPatterns(wires); + const vector externalWires = sv.getIndicesAfterExclusion(wires); + const vector externalIndices = + sv.generateBitPatterns(externalWires); + for (const size_t &externalIndex : externalIndices) { + std::complex *shiftedState = sv.getData() + externalIndex; + shiftedState[internalIndices[0]] = 0; + shiftedState[internalIndices[1]] = 0; + shiftedState[internalIndices[2]] = 0; + } +} + +} // namespace + +namespace Pennylane { +namespace Algorithms { + +template class AdjointJacobian { + private: + typedef void (*GeneratorFunc)( + Pennylane::StateVector &sv, + const std::vector &wires); // function pointer type + + inline const std::unordered_map generator_map{ + {"RX", &::applyGeneratorRX}, + {"RY", &::applyGeneratorRY}, + {"RZ", &::applyGeneratorRZ}, + {"PhaseShift", &::applyGeneratorPhaseShift}, + {"CRX", &::applyGeneratorCRX}, + {"CRY", &::applyGeneratorCRY}, + {"CRZ", &::applyGeneratorCRZ}, + {"ControlledPhaseShift", &::applyGeneratorControlledPhaseShift}}; + + inline const std::unordered_map scaling_factors{ + {"RX", -0.5}, {"RY", -0.5}, + {"RZ", -0.5}, {"PhaseShift", 1}, + {"CRX", -0.5}, {"CRY", -0.5}, + {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; + + public: + AdjointJacobian() {} + + template + void adjointJacobian(StateVector &phi, T *jac, + const vector &observables, + const vector> &obsParams, + const vector> &obsWires, + const vector &operations, + const vector> &opParams, + const vector> &opWires, + const vector &trainableParams, + size_t paramNumber) { + + size_t numObservables = observables.size(); + int trainableParamNumber = trainableParams.size() - 1; + int current_param_idx = paramNumber - 1; + + const size_t num_elements = phi.getLength(); + + // 1. Copy the input state, create lambda + std::unique_ptr[]> SV_lambda_data( + new std::complex[num_elements]); + std::copy(phi.getData(), phi.getData() + num_elements, + SV_lambda_data.get()); + StateVector SV_lambda(SV_lambda_data.data(), num_elements); + + // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda + std::vector inverses(operations.size(), false); + SV_lambda.applyOperations(operations, opWires, inverses, opParams); + + // 3-4. Copy lambda and apply the observables + // SV_lambda becomes |phi> + std::vector> lambdas(numObservables); + std::vector[]>> lambdas_data; + +#pragma omp parallel for + for (size_t i = 0; i < numObservables; i++) { + // copy |phi> and apply observables one at a time + lambdas_data.emplace_back(new std::complex[num_elements])); + std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, + lambdas_data.back().get()); + + StateVector phiCopy(lambdas_data.back().get(), num_elements); + + phiCopy.applyOperation(observables[i], obsWires[i], false, + obsParams[i]); + + lambdas[i] = std::move(phiCopy); + } + + // replace with reverse iterator over values? + for (int i = operations.size() - 1; i >= 0; i--) { + if (opParams[i].size() > 1) { + throw std::invalid_argument( + "The operation is not supported using " + "the adjoint differentiation method"); + } else if ((operations[i] != "QubitStateVector") && + (operations[i] != "BasisState")) { + // copy |phi> to |mu> before applying Uj* + + std::unique_ptr[]> phiCopyArr( + new std::complex[num_elements]); + std::copy(SV_lambda.getData(), + SV_lambda.getData() + num_elements, phiCopyArr.get()); + + StateVector mu(phiCopyArr.get(), num_elements); + + // create |phi'> = Uj*|phi> + SV_lambda.applyOperation(operations[i], opWires[i], true, + opParams[i]); + + // We have a parametrized gate + if (!opParams[i].empty()) { + if (std::find(trainableParams.begin(), + trainableParams.end(), + current_param_idx) != trainableParams.end()) { + + // create iH|phi> = d/d dUj/dtheta Uj* |phi> = + // dUj/dtheta|phi'> + const T scalingFactor = + scaling_factors.at(operations[i]); + + generator_map.at(operations[i])(mu, opWires[i]); + + for (size_t j = 0; j < lambdas.size(); j++) { + std::complex sum = + innerProd(lambdas[j].getData(), mu.getData(), + num_elements); + + // calculate 2 * shift * Real(i * sum) = -2 * shift + // * Imag(sum) + jac[j * trainableParams.size() + + trainableParamNumber] = + -2 * scalingFactor * std::imag(sum); + } + trainableParamNumber--; + } + current_param_idx--; + } + + for (unsigned int j = 0; j < lambdas.size(); j++) { + Pennylane::constructAndApplyOperation( + lambdas[j], operations[i], opWires[i], opParams[i], + true, phi.getNumQubits()); + } + } + /// missing else? + } + // delete copied state arrays + for (int i; i < lambdas.size(); i++) { + delete[] lambdas[i].arr; + } + } +}; + +} // namespace Algorithms +} // namespace Pennylane \ No newline at end of file From b0d49a2f3bf840ab26831c4bf829698d26c0f3ab Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 11:23:25 +0100 Subject: [PATCH 03/72] Refactor build-system structure to enable better extensibility --- CMakeLists.txt | 17 ++++++++-------- pennylane_lightning/src/Bindings.cpp | 3 ++- pennylane_lightning/src/CMakeLists.txt | 16 +++++++++++++++ .../src/algorithms/AdjointDiff.hpp | 20 +++++++++---------- .../src/algorithms/CMakeLists.txt | 5 +++++ .../src/simulator/CMakeLists.txt | 10 ++++++++++ .../src/{ => simulator}/Gates.hpp | 0 .../src/{ => simulator}/StateVector.cpp | 0 .../src/{ => simulator}/StateVector.hpp | 0 pennylane_lightning/src/tests/CMakeLists.txt | 2 +- pennylane_lightning/src/util/CMakeLists.txt | 6 ++++++ pennylane_lightning/src/util/Dispatcher.hpp | 0 pennylane_lightning/src/util/Generators.hpp | 17 ++++++++++++++++ pennylane_lightning/src/{ => util}/Util.hpp | 1 + setup.py | 10 +++++++--- 15 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 pennylane_lightning/src/CMakeLists.txt create mode 100644 pennylane_lightning/src/algorithms/CMakeLists.txt create mode 100644 pennylane_lightning/src/simulator/CMakeLists.txt rename pennylane_lightning/src/{ => simulator}/Gates.hpp (100%) rename pennylane_lightning/src/{ => simulator}/StateVector.cpp (100%) rename pennylane_lightning/src/{ => simulator}/StateVector.hpp (100%) create mode 100644 pennylane_lightning/src/util/CMakeLists.txt create mode 100644 pennylane_lightning/src/util/Dispatcher.hpp create mode 100644 pennylane_lightning/src/util/Generators.hpp rename pennylane_lightning/src/{ => util}/Util.hpp (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ed0b5dc0d..f5f0fd8194 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,6 @@ message(STATUS "pennylane_lightning version ${VERSION_STRING}") set(PROJECT_VERSION ${VERSION_STRING}) set(CMAKE_CXX_STANDARD 17) # At least C++17 is required -find_package(OpenMP REQUIRED) # find OpenMP if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) @@ -39,7 +38,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(pybind11) -add_library(pennylane_lightning SHARED "pennylane_lightning/src/StateVector.cpp") +add_subdirectory(pennylane_lightning/src) + +add_library(pennylane_lightning INTERFACE) +target_link_libraries(pennylane_lightning INTERFACE lightning_utils + lightning_simulator + lightning_algorithms) target_include_directories(pennylane_lightning INTERFACE "pennylane_lightning/src") add_library(external_dependency INTERFACE) @@ -50,12 +54,10 @@ if ("$ENV{USE_OPENBLAS}" OR "${USE_OPENBLAS}") target_compile_options(external_dependency INTERFACE "-DOPENBLAS=1") endif() -pybind11_add_module(lightning_qubit_ops "pennylane_lightning/src/StateVector.cpp" - "pennylane_lightning/src/Bindings.cpp") -target_link_libraries(lightning_qubit_ops PRIVATE external_dependency) +pybind11_add_module(lightning_qubit_ops "pennylane_lightning/src/Bindings.cpp") +target_link_libraries(lightning_qubit_ops PRIVATE pennylane_lightning external_dependency) set_target_properties(lightning_qubit_ops PROPERTIES CXX_VISIBILITY_PRESET hidden) -target_link_libraries(lightning_qubit_ops PRIVATE OpenMP::OpenMP_CXX) target_compile_options(lightning_qubit_ops PRIVATE "$<$:-W>") target_compile_options(lightning_qubit_ops PRIVATE "$<$:-Wall>") target_compile_definitions(lightning_qubit_ops PRIVATE VERSION_INFO=${VERSION_STRING}) @@ -66,8 +68,7 @@ if(ENABLE_NATIVE) target_compile_options(lightning_qubit_ops PRIVATE -march=native) endif() - if (BUILD_TESTS) enable_testing() - add_subdirectory("pennylane_lightning/src/tests" "tests") + #add_subdirectory("pennylane_lightning/src/tests" "tests") endif() diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/Bindings.cpp index 9ca0918e75..efb5739c73 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/Bindings.cpp @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "StateVector.hpp" #include "pybind11/complex.h" #include "pybind11/numpy.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" +#include "StateVector.hpp" + using Pennylane::StateVector; using std::complex; using std::string; diff --git a/pennylane_lightning/src/CMakeLists.txt b/pennylane_lightning/src/CMakeLists.txt new file mode 100644 index 0000000000..9afbc32786 --- /dev/null +++ b/pennylane_lightning/src/CMakeLists.txt @@ -0,0 +1,16 @@ +project(lightning_components LANGUAGES CXX) + +############################################################################### +# Include all nested sources directories +############################################################################### +set(COMPONENT_SUBDIRS simulator; + algorithms; + util; +) +foreach(COMP ${COMPONENT_SUBDIRS}) + add_subdirectory(${COMP}) +endforeach() + +if (BUILD_TESTS) + add_subdirectory("tests" "tests") +endif() \ No newline at end of file diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 3e0ff65a77..42bc0a6261 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -171,19 +171,22 @@ template class AdjointJacobian { // SV_lambda becomes |phi> std::vector> lambdas(numObservables); std::vector[]>> lambdas_data; + lambdas_data.reserve(10); + for (int i = 0; i < 10; ++i) + lambdas_data.emplace_back(new std::complex[num_elements])); #pragma omp parallel for for (size_t i = 0; i < numObservables; i++) { // copy |phi> and apply observables one at a time - lambdas_data.emplace_back(new std::complex[num_elements])); std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, - lambdas_data.back().get()); + lambdas_data[i].get()); - StateVector phiCopy(lambdas_data.back().get(), num_elements); + StateVector phiCopy(lambdas_data[i].get(), num_elements); phiCopy.applyOperation(observables[i], obsWires[i], false, obsParams[i]); + // Potentially to be replaced with an emplace operation lambdas[i] = std::move(phiCopy); } @@ -237,18 +240,13 @@ template class AdjointJacobian { current_param_idx--; } - for (unsigned int j = 0; j < lambdas.size(); j++) { - Pennylane::constructAndApplyOperation( - lambdas[j], operations[i], opWires[i], opParams[i], - true, phi.getNumQubits()); + for (size_t j = 0; j < lambdas.size(); j++) { + lambdas[j].applyOperation(operations[i], opWires[i], true, + opParams[i]); } } /// missing else? } - // delete copied state arrays - for (int i; i < lambdas.size(); i++) { - delete[] lambdas[i].arr; - } } }; diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt new file mode 100644 index 0000000000..2e27f8a83d --- /dev/null +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -0,0 +1,5 @@ +project(lightning_algorithms LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) + +add_library(lightning_algorithms INTERFACE) +target_include_directories(lightning_algorithms INTERFACE $) \ No newline at end of file diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt new file mode 100644 index 0000000000..65ed76502f --- /dev/null +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -0,0 +1,10 @@ +project(lightning_simulator) +set(CMAKE_CXX_STANDARD 17) + +set(SIMULATOR_FILES StateVector.cpp StateVector.hpp Gates.hpp CACHE INTERNAL "" FORCE) +add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) + +find_package(OpenMP REQUIRED) + +target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +target_link_libraries(lightning_simulator PRIVATE lightning_utils OpenMP::OpenMP_CXX) \ No newline at end of file diff --git a/pennylane_lightning/src/Gates.hpp b/pennylane_lightning/src/simulator/Gates.hpp similarity index 100% rename from pennylane_lightning/src/Gates.hpp rename to pennylane_lightning/src/simulator/Gates.hpp diff --git a/pennylane_lightning/src/StateVector.cpp b/pennylane_lightning/src/simulator/StateVector.cpp similarity index 100% rename from pennylane_lightning/src/StateVector.cpp rename to pennylane_lightning/src/simulator/StateVector.cpp diff --git a/pennylane_lightning/src/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp similarity index 100% rename from pennylane_lightning/src/StateVector.hpp rename to pennylane_lightning/src/simulator/StateVector.hpp diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index bc5ad4c284..59a18e604c 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -30,7 +30,7 @@ include(CTest) include(Catch) add_executable(runner runner_main.cpp) -target_link_libraries(runner pennylane_lightning Catch2::Catch2) +target_link_libraries(runner lightning_simulator lightning_utils lightning_algorithms Catch2::Catch2) target_sources(runner PRIVATE Test_Bindings.cpp Test_StateVector_Nonparam.cpp diff --git a/pennylane_lightning/src/util/CMakeLists.txt b/pennylane_lightning/src/util/CMakeLists.txt new file mode 100644 index 0000000000..faaa6037de --- /dev/null +++ b/pennylane_lightning/src/util/CMakeLists.txt @@ -0,0 +1,6 @@ +project(lightning_utils LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) + +#set(UTIL_FILES Util.hpp CACHE INTERNAL "" FORCE) +add_library(lightning_utils INTERFACE) +target_include_directories(lightning_utils INTERFACE $) \ No newline at end of file diff --git a/pennylane_lightning/src/util/Dispatcher.hpp b/pennylane_lightning/src/util/Dispatcher.hpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pennylane_lightning/src/util/Generators.hpp b/pennylane_lightning/src/util/Generators.hpp new file mode 100644 index 0000000000..41c213dbd5 --- /dev/null +++ b/pennylane_lightning/src/util/Generators.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +#include "Util.hpp" + +namespace { +using namespace Pennylane::Util; +} + +namespace Pennylane { +namespace Gates { +namespace Generators {} +} // namespace Gates +}; // namespace Pennylane \ No newline at end of file diff --git a/pennylane_lightning/src/Util.hpp b/pennylane_lightning/src/util/Util.hpp similarity index 99% rename from pennylane_lightning/src/Util.hpp rename to pennylane_lightning/src/util/Util.hpp index 8d7fec9aee..2b9fecee94 100644 --- a/pennylane_lightning/src/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include diff --git a/setup.py b/setup.py index 26cec8d540..9c7b8d0807 100644 --- a/setup.py +++ b/setup.py @@ -121,6 +121,9 @@ def build_extensions(self): include_dirs = [ get_pybind_include(), "./include", + "pennylane_lightning/src/algorithms", + "pennylane_lightning/src/simulator", + "pennylane_lightning/src/util" ] library_dirs = [i for i in os.environ.get("LD_LIBRARY_PATH", "").split(":") if i] @@ -147,12 +150,13 @@ def build_extensions(self): Extension( "lightning_qubit_ops", sources=[ - "pennylane_lightning/src/StateVector.cpp", + "pennylane_lightning/src/simulator/StateVector.cpp", "pennylane_lightning/src/Bindings.cpp", ], depends=[ - "pennylane_lightning/src/StateVector.hpp", - "pennylane_lightning/src/Util.hpp", + "pennylane_lightning/src/algorithms/AdjointDiff.hpp", + "pennylane_lightning/src/simulator/StateVector.hpp", + "pennylane_lightning/src/util/Util.hpp", ], include_dirs=include_dirs, language="c++", From adbc267633e22289f6b7f077ed3c588fa0e1b448 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 11:57:46 +0100 Subject: [PATCH 04/72] Add explicit build support for algorithms --- .../src/algorithms/AdjointDiff.cpp | 19 +++++++++++++++++++ .../src/algorithms/CMakeLists.txt | 7 +++++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 pennylane_lightning/src/algorithms/AdjointDiff.cpp diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.cpp b/pennylane_lightning/src/algorithms/AdjointDiff.cpp new file mode 100644 index 0000000000..ae23f00eac --- /dev/null +++ b/pennylane_lightning/src/algorithms/AdjointDiff.cpp @@ -0,0 +1,19 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "AdjointDiff.hpp" + +// explicit instantiation +template class Pennylane::Algorithms::AdjointJacobian; +template class Pennylane::Algorithms::AdjointJacobian; \ No newline at end of file diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 2e27f8a83d..301be8ac27 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -1,5 +1,8 @@ project(lightning_algorithms LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) -add_library(lightning_algorithms INTERFACE) -target_include_directories(lightning_algorithms INTERFACE $) \ No newline at end of file +set(ALGORITHM_FILES AdjointDiff.hpp AdjointDiff.cpp CACHE INTERNAL "" FORCE) +add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) + +target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) \ No newline at end of file From 88a15953b1b6a3fc78fc31973155b2c6f2f29bb3 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 11:58:07 +0100 Subject: [PATCH 05/72] Clean Util cmake --- pennylane_lightning/src/util/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/src/util/CMakeLists.txt b/pennylane_lightning/src/util/CMakeLists.txt index faaa6037de..ffffaa50d3 100644 --- a/pennylane_lightning/src/util/CMakeLists.txt +++ b/pennylane_lightning/src/util/CMakeLists.txt @@ -1,6 +1,5 @@ project(lightning_utils LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) -#set(UTIL_FILES Util.hpp CACHE INTERNAL "" FORCE) add_library(lightning_utils INTERFACE) target_include_directories(lightning_utils INTERFACE $) \ No newline at end of file From ec9db6845ea39a0d444918b79893d325200492dd Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 11:58:43 +0100 Subject: [PATCH 06/72] Add explicit overload resolution to innerProd --- pennylane_lightning/src/util/Util.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index 2b9fecee94..e6cff142e4 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -190,7 +190,7 @@ std::complex innerProd(const std::complex *data_1, cblas_zdotc_sub(data_size, data_1, 1, data_2, 1, &result); } else { std::inner_product(data_1, data_1 + data_size, data_2, - std::complex(0, 0), ConstSum, ConstMult); + std::complex(0, 0), ConstSum, static_cast(*)(std::complex,std::complex)>(&ConstMult)); } return result; } From d47eb2c639c555f213e8320075698dfc302379a6 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 11:59:13 +0100 Subject: [PATCH 07/72] Add build support for AdjointDiff --- .../src/algorithms/AdjointDiff.hpp | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 42bc0a6261..ff4fbcc2c2 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -8,8 +8,8 @@ #include #include -#include "../StateVector.hpp" -#include "../Util.hpp" +#include "StateVector.hpp" +#include "Util.hpp" // Generators not needed outside this translation unit namespace { @@ -120,7 +120,7 @@ template class AdjointJacobian { Pennylane::StateVector &sv, const std::vector &wires); // function pointer type - inline const std::unordered_map generator_map{ + const std::unordered_map generator_map{ {"RX", &::applyGeneratorRX}, {"RY", &::applyGeneratorRY}, {"RZ", &::applyGeneratorRZ}, @@ -130,7 +130,7 @@ template class AdjointJacobian { {"CRZ", &::applyGeneratorCRZ}, {"ControlledPhaseShift", &::applyGeneratorControlledPhaseShift}}; - inline const std::unordered_map scaling_factors{ + const std::unordered_map scaling_factors{ {"RX", -0.5}, {"RY", -0.5}, {"RZ", -0.5}, {"PhaseShift", 1}, {"CRX", -0.5}, {"CRY", -0.5}, @@ -139,7 +139,6 @@ template class AdjointJacobian { public: AdjointJacobian() {} - template void adjointJacobian(StateVector &phi, T *jac, const vector &observables, const vector> &obsParams, @@ -161,7 +160,7 @@ template class AdjointJacobian { new std::complex[num_elements]); std::copy(phi.getData(), phi.getData() + num_elements, SV_lambda_data.get()); - StateVector SV_lambda(SV_lambda_data.data(), num_elements); + StateVector SV_lambda(SV_lambda_data.get(), num_elements); // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda std::vector inverses(operations.size(), false); @@ -169,11 +168,14 @@ template class AdjointJacobian { // 3-4. Copy lambda and apply the observables // SV_lambda becomes |phi> - std::vector> lambdas(numObservables); + std::vector> lambdas; + lambdas.reserve(numObservables); std::vector[]>> lambdas_data; - lambdas_data.reserve(10); - for (int i = 0; i < 10; ++i) - lambdas_data.emplace_back(new std::complex[num_elements])); + lambdas_data.reserve(numObservables); + for (int i = 0; i < numObservables; ++i){ + lambdas_data.emplace_back(new std::complex[num_elements]); + lambdas.emplace_back(StateVector(lambdas_data[i].get(), num_elements)); + } #pragma omp parallel for for (size_t i = 0; i < numObservables; i++) { @@ -181,13 +183,8 @@ template class AdjointJacobian { std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, lambdas_data[i].get()); - StateVector phiCopy(lambdas_data[i].get(), num_elements); - - phiCopy.applyOperation(observables[i], obsWires[i], false, + lambdas[i].applyOperation(observables[i], obsWires[i], false, obsParams[i]); - - // Potentially to be replaced with an emplace operation - lambdas[i] = std::move(phiCopy); } // replace with reverse iterator over values? From ca950b3eb73d9d499cf2979f65808ed64cb09692 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 12:14:28 +0100 Subject: [PATCH 08/72] Update testing for new utils --- .../src/algorithms/AdjointDiff.hpp | 7 ++++--- pennylane_lightning/src/tests/Test_Util.cpp | 11 +++++++++++ pennylane_lightning/src/util/Util.hpp | 15 +++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index ff4fbcc2c2..47ef4a592a 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -172,9 +172,10 @@ template class AdjointJacobian { lambdas.reserve(numObservables); std::vector[]>> lambdas_data; lambdas_data.reserve(numObservables); - for (int i = 0; i < numObservables; ++i){ + for (int i = 0; i < numObservables; ++i) { lambdas_data.emplace_back(new std::complex[num_elements]); - lambdas.emplace_back(StateVector(lambdas_data[i].get(), num_elements)); + lambdas.emplace_back( + StateVector(lambdas_data[i].get(), num_elements)); } #pragma omp parallel for @@ -184,7 +185,7 @@ template class AdjointJacobian { lambdas_data[i].get()); lambdas[i].applyOperation(observables[i], obsWires[i], false, - obsParams[i]); + obsParams[i]); } // replace with reverse iterator over values? diff --git a/pennylane_lightning/src/tests/Test_Util.cpp b/pennylane_lightning/src/tests/Test_Util.cpp index 97fff14c8e..74cceaf7fb 100644 --- a/pennylane_lightning/src/tests/Test_Util.cpp +++ b/pennylane_lightning/src/tests/Test_Util.cpp @@ -94,4 +94,15 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util]", float, double) { } } } + SECTION("innerProd") { + for (size_t i = 0; i < 12; i++) { + std::vector> data1(1UL << i, {1, 1}); + std::vector> data2(1UL << i, {1, 1}); + std::complex expected_result(1UL << (i + 1), 0); + std::complex result = Util::innerProd(data1, data2); + CAPTURE(result); + CAPTURE(expected_result); + CHECK(isApproxEqual(result, expected_result)); + } + } } diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index e6cff142e4..c7615d8dca 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -57,6 +57,12 @@ inline static constexpr std::complex ConstMult(std::complex a, return {a.real() * b.real() - a.imag() * b.imag(), a.real() * b.imag() + a.imag() * b.real()}; } +template +inline static constexpr std::complex ConstMultConj(std::complex a, + std::complex b) { + return {a.real() * b.real() + a.imag() * b.imag(), + a.real() * b.imag() - a.imag() * b.real()}; +} template inline static constexpr std::complex ConstSum(std::complex a, @@ -185,12 +191,13 @@ std::complex innerProd(const std::complex *data_1, if constexpr (USE_CBLAS) { if constexpr (std::is_same_v) - cblas_cdotc_sub(data_size, data_1, 1, data_2, 1, &result); + result = cblas_cdotc_sub(data_size, data_1, 1, data_2, 1, &result); else if constexpr (std::is_same_v) - cblas_zdotc_sub(data_size, data_1, 1, data_2, 1, &result); + result = cblas_zdotc_sub(data_size, data_1, 1, data_2, 1, &result); } else { - std::inner_product(data_1, data_1 + data_size, data_2, - std::complex(0, 0), ConstSum, static_cast(*)(std::complex,std::complex)>(&ConstMult)); + result = std::inner_product(data_1, data_1 + data_size, data_2, + std::complex(0, 0), ConstSum, + ConstMultConj); } return result; } From 409759ff19ed188ccbc5179e1b5633b1ebd26409 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 13:42:44 +0100 Subject: [PATCH 09/72] Ensure formatting is run in nested dirs --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fb9f4ff26f..33e3336654 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ test-cpp: .PHONY: format format: ifdef check - ./bin/format --check pennylane_lightning/src tests + ./bin/format --check pennylane_lightning/src/* tests else - ./bin/format pennylane_lightning/src tests + ./bin/format pennylane_lightning/src/* tests endif From b955bee8fc5c2751d57159326b2a9e4c13017350 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 16:22:54 +0100 Subject: [PATCH 10/72] Add cout functionality for StateVector --- .../src/simulator/StateVector.hpp | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index eb7bad55df..7a7ff293d9 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -112,9 +112,9 @@ template class StateVector { {"CRot", bind(&StateVector::applyCRot_, this, _1, _2, _3, _4)}} {}; - CFP_t *getData() { return arr_; } - std::size_t getLength() { return length_; } - std::size_t getNumQubits() { return num_qubits_; } + CFP_t *getData() const { return arr_; } + std::size_t getLength() const { return length_; } + std::size_t getNumQubits() const { return num_qubits_; } /** * @brief Apply a single gate to the state-vector. @@ -750,4 +750,20 @@ template class StateVector { } }; +template +inline std::ostream &operator<<(std::ostream &out, const StateVector &sv) { + const auto num_qubits = sv.getNumQubits(); + const auto length = sv.getLength(); + const auto data_ptr = sv.getData(); + out << "num_qubits=" << num_qubits << std::endl; + out << "data=["; + out << data_ptr[0]; + for (size_t i = 1; i < length - 1; i++) { + out << "," << data_ptr[i]; + } + out << "," << data_ptr[length - 1] << "]"; + + return out; +} + } // namespace Pennylane From 140f950376bbc2be4ccc8c35ac7dfb54786fd41e Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 16:23:59 +0100 Subject: [PATCH 11/72] Add dot for conjugated and non conjugated data --- pennylane_lightning/src/util/Util.hpp | 52 +++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index c7615d8dca..eb6ef4117d 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -61,7 +61,7 @@ template inline static constexpr std::complex ConstMultConj(std::complex a, std::complex b) { return {a.real() * b.real() + a.imag() * b.imag(), - a.real() * b.imag() - a.imag() * b.real()}; + -a.imag() * b.real() + a.real() * b.imag()}; } template @@ -189,6 +189,35 @@ std::complex innerProd(const std::complex *data_1, const size_t data_size) { std::complex result(0, 0); + if constexpr (USE_CBLAS) { + if constexpr (std::is_same_v) + result = cblas_cdotu_sub(data_size, data_1, 1, data_2, 1, &result); + else if constexpr (std::is_same_v) + result = cblas_zdotu_sub(data_size, data_1, 1, data_2, 1, &result); + } else { + result = std::inner_product( + data_1, data_1 + data_size, data_2, std::complex(), ConstSum, + static_cast (*)(std::complex, std::complex)>( + &ConstMult)); + } + return result; +} + +/** + * @brief Calculates the inner-product using the best available method with the + * first dataset conjugated. + * + * @tparam T Floating point precision type. + * @param data_1 Complex data array 1; conjugated before application. + * @param data_2 Complex data array 2. + * @return std::complex Result of inner product operation. + */ +template +std::complex innerProdC(const std::complex *data_1, + const std::complex *data_2, + const size_t data_size) { + std::complex result(0, 0); + if constexpr (USE_CBLAS) { if constexpr (std::is_same_v) result = cblas_cdotc_sub(data_size, data_1, 1, data_2, 1, &result); @@ -196,7 +225,7 @@ std::complex innerProd(const std::complex *data_1, result = cblas_zdotc_sub(data_size, data_1, 1, data_2, 1, &result); } else { result = std::inner_product(data_1, data_1 + data_size, data_2, - std::complex(0, 0), ConstSum, + std::complex(), ConstSum, ConstMultConj); } return result; @@ -208,6 +237,25 @@ inline std::complex innerProd(const std::vector> &data_1, return innerProd(data_1.data(), data_2.data(), data_1.size()); } +template +inline std::complex innerProdC(const std::vector> &data_1, + const std::vector> &data_2) { + return innerProdC(data_1.data(), data_2.data(), data_1.size()); +} + +template +inline std::ostream &operator<<(std::ostream &os, const std::vector &vec) { + os << '['; + for (size_t i = 0; i < vec.size(); i++) { + if (i > 0 && i < vec.size() - 1) { + os << ","; + } + os << vec[i]; + } + os << ']'; + return os; +} + } // namespace Util } // namespace Pennylane From cf07cc95d5f007158e66ca512751476ece1879f5 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 16:24:26 +0100 Subject: [PATCH 12/72] Begin dev of adjoint diff tests --- pennylane_lightning/src/tests/CMakeLists.txt | 3 +- .../src/tests/Test_AdjDiff.cpp | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 pennylane_lightning/src/tests/Test_AdjDiff.cpp diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index 59a18e604c..141c75563a 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -32,7 +32,8 @@ include(Catch) add_executable(runner runner_main.cpp) target_link_libraries(runner lightning_simulator lightning_utils lightning_algorithms Catch2::Catch2) -target_sources(runner PRIVATE Test_Bindings.cpp +target_sources(runner PRIVATE Test_AdjDiff.cpp + Test_Bindings.cpp Test_StateVector_Nonparam.cpp Test_StateVector_Param.cpp Test_Util.cpp diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp new file mode 100644 index 0000000000..243b5220e9 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "AdjointDiff.hpp" +#include "StateVector.hpp" +#include "Util.hpp" + +#include "TestHelpers.hpp" + +using namespace Pennylane; +using namespace Pennylane::Algorithms; + +/** + * @brief Tests the constructability of the StateVector class. + * + */ +TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", + float, double) { + SECTION("AdjointJacobian") { + REQUIRE(std::is_constructible>::value); + } + SECTION("AdjointJacobian {}") { + REQUIRE(std::is_constructible>::value); + } +} + +TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { + + AdjointJacobian adj; + std::vector param{1, -2, 1.623, -0.051, 0}; + + SECTION("RX gradient") { + const size_t num_qubits = 1; + const size_t num_params = 1; + const size_t num_obs = 1; + for (const auto &p : param) { + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + adj.adjointJacobian(psi, jacobian.data(), {"PauliZ"}, {{}}, {{0}}, + {"RX"}, {{p}}, {{0}}, {0}, 1); + CHECK(-sin(p) == Approx(jacobian.front())); + } + } + + SECTION("RY gradient") { + const size_t num_qubits = 1; + const size_t num_params = 1; + const size_t num_obs = 1; + for (const auto &p : param) { + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + adj.adjointJacobian(psi, jacobian.data(), {"PauliX"}, {{}}, {{0}}, + {"RY"}, {{p}}, {{0}}, {0}, 1); + CHECK(cos(p) == Approx(jacobian.front())); + } + } + SECTION("Single RX gradient, multiple wires") { + const size_t num_qubits = 3; + const size_t num_params = 1; + const size_t num_obs = 1; + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + adj.adjointJacobian( + psi, jacobian.data(), {"PauliZ", "PauliZ", "PauliZ"}, {{}}, + {{0}, {1}, {2}}, {"RX"}, {{param[0]}}, {{0}}, {0}, num_params); + CAPTURE(jacobian); + for (size_t i = 0; i < num_params; i++) { + CHECK(-sin(param[i]) == Approx(jacobian[i])); + } + } + SECTION("Multiple RX gradient") { + const size_t num_qubits = 3; + const size_t num_params = 3; + const size_t num_obs = 3; + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + adj.adjointJacobian(psi, jacobian.data(), + {"PauliZ", "PauliZ", "PauliZ"}, {{}}, + {{0}, {1}, {2}}, {"RX", "RX", "RX"}, + {{param[0]}, {param[1]}, {param[2]}}, + {{0}, {1}, {2}}, {0, 1, 2}, num_params); + CAPTURE(jacobian); + for (size_t i = 0; i < num_params; i++) { + CHECK(-sin(param[i]) == Approx(jacobian[i])); + } + } +} From 9e8ae62c45952b6df7fd386fadf19ee5160ce2d7 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 16:27:04 +0100 Subject: [PATCH 13/72] Add additional tests to util suite --- pennylane_lightning/src/tests/Test_Util.cpp | 47 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/src/tests/Test_Util.cpp b/pennylane_lightning/src/tests/Test_Util.cpp index 74cceaf7fb..ece243a89e 100644 --- a/pennylane_lightning/src/tests/Test_Util.cpp +++ b/pennylane_lightning/src/tests/Test_Util.cpp @@ -95,14 +95,49 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util]", float, double) { } } SECTION("innerProd") { - for (size_t i = 0; i < 12; i++) { - std::vector> data1(1UL << i, {1, 1}); - std::vector> data2(1UL << i, {1, 1}); - std::complex expected_result(1UL << (i + 1), 0); + SECTION("Iterative increment") { + for (size_t i = 0; i < 12; i++) { + std::vector> data1(1UL << i, {1, 1}); + std::vector> data2(1UL << i, {1, 1}); + std::complex expected_result(0, 1UL << (i + 1)); + std::complex result = Util::innerProd(data1, data2); + CHECK(isApproxEqual(result, expected_result)); + } + } + SECTION("Random complex") { + std::vector> data1{ + {0.326417, 0}, {-0, 0.343918}, {0, 0.508364}, {-0.53562, -0}, + {0, -0.178322}, {0.187883, -0}, {0.277721, 0}, {-0, 0.292611}}; + std::vector> data2{ + {0, -0.479426}, {0, 0}, {2.77556e-17, 0}, {0, 0}, + {0.877583, 0}, {0, 0}, {0, 0}, {0, 0}}; + std::complex expected_result(0, -0.312985152368); std::complex result = Util::innerProd(data1, data2); - CAPTURE(result); - CAPTURE(expected_result); CHECK(isApproxEqual(result, expected_result)); } } + SECTION("innerProdC") { + SECTION("Iterative increment") { + for (size_t i = 0; i < 12; i++) { + std::vector> data1(1UL << i, {1, 1}); + std::vector> data2(1UL << i, {1, 1}); + std::complex expected_result(1UL << (i + 1), 0); + std::complex result = Util::innerProdC(data1, data2); + + CHECK(isApproxEqual(result, expected_result)); + } + } + SECTION("Random complex") { + std::vector> data1{ + {0, -0.479426}, {0, 0}, {2.77556e-17, 0}, {0, 0}, + {0.877583, 0}, {0, 0}, {0, 0}, {0, 0}}; + std::vector> data2{ + {0.326417, 0}, {-0, 0.343918}, {0, 0.508364}, {-0.53562, -0}, + {0, -0.178322}, {0.187883, -0}, {0.277721, 0}, {-0, 0.292611}}; + std::complex expected_result(0, 4.40916e-7); + std::complex result = Util::innerProdC(data1, data2); + CAPTURE(result); + CHECK(isApproxEqual(result, expected_result, 1e-5)); + } + } } From aa19e98a4e16580131f561987eae9a3f697a9878 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 20 Aug 2021 16:27:26 +0100 Subject: [PATCH 14/72] Attempt debugging of multi-obs issues --- .../src/algorithms/AdjointDiff.hpp | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 47ef4a592a..f94ed1ce69 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -11,6 +11,8 @@ #include "StateVector.hpp" #include "Util.hpp" +#include + // Generators not needed outside this translation unit namespace { @@ -147,11 +149,11 @@ template class AdjointJacobian { const vector> &opParams, const vector> &opWires, const vector &trainableParams, - size_t paramNumber) { + size_t num_params) { size_t numObservables = observables.size(); int trainableParamNumber = trainableParams.size() - 1; - int current_param_idx = paramNumber - 1; + int current_param_idx = num_params - 1; const size_t num_elements = phi.getLength(); @@ -178,7 +180,7 @@ template class AdjointJacobian { StateVector(lambdas_data[i].get(), num_elements)); } -#pragma omp parallel for + //#pragma omp parallel for for (size_t i = 0; i < numObservables; i++) { // copy |phi> and apply observables one at a time std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, @@ -190,6 +192,7 @@ template class AdjointJacobian { // replace with reverse iterator over values? for (int i = operations.size() - 1; i >= 0; i--) { + if (opParams[i].size() > 1) { throw std::invalid_argument( "The operation is not supported using " @@ -211,6 +214,7 @@ template class AdjointJacobian { // We have a parametrized gate if (!opParams[i].empty()) { + if (std::find(trainableParams.begin(), trainableParams.end(), current_param_idx) != trainableParams.end()) { @@ -221,14 +225,24 @@ template class AdjointJacobian { scaling_factors.at(operations[i]); generator_map.at(operations[i])(mu, opWires[i]); + std::cout << "mu::{\n\t" << mu << "\n}" << std::endl; for (size_t j = 0; j < lambdas.size(); j++) { + std::cout << "lambdas[" << j << "]::{\n\t" + << lambdas[j] << "\n}" << std::endl; + std::complex sum = - innerProd(lambdas[j].getData(), mu.getData(), - num_elements); + innerProdC(lambdas[j].getData(), mu.getData(), + num_elements); // calculate 2 * shift * Real(i * sum) = -2 * shift // * Imag(sum) + std::cout << "sum[" << current_param_idx + << "]=" << sum << ", " << num_elements + << ", " + << j * trainableParams.size() + + trainableParamNumber + << std::endl; jac[j * trainableParams.size() + trainableParamNumber] = -2 * scalingFactor * std::imag(sum); From edf6b1bf4f298931d897bffbc6a14bb2fb2d39be Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 23 Aug 2021 10:06:25 +0100 Subject: [PATCH 15/72] Ensure FPIC on for generated .a libs --- pennylane_lightning/src/algorithms/CMakeLists.txt | 4 +++- pennylane_lightning/src/simulator/CMakeLists.txt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 301be8ac27..33851da081 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -5,4 +5,6 @@ set(ALGORITHM_FILES AdjointDiff.hpp AdjointDiff.cpp CACHE INTERNAL "" FORCE) add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) \ No newline at end of file +target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) + +set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) \ No newline at end of file diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index 65ed76502f..e0dd2d05c2 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -7,4 +7,6 @@ add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) find_package(OpenMP REQUIRED) target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(lightning_simulator PRIVATE lightning_utils OpenMP::OpenMP_CXX) \ No newline at end of file +target_link_libraries(lightning_simulator PRIVATE lightning_utils OpenMP::OpenMP_CXX) + +set_property(TARGET lightning_simulator PROPERTY POSITION_INDEPENDENT_CODE ON) \ No newline at end of file From 4ea6c3d5d100a93e211371116c28201090ede198 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 23 Aug 2021 10:55:00 +0100 Subject: [PATCH 16/72] Fix native flag in CMake --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5f0fd8194..c2cb69a2bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,11 +64,10 @@ target_compile_definitions(lightning_qubit_ops PRIVATE VERSION_INFO=${VERSION_ST if(ENABLE_NATIVE) message(STATUS "ENABLE_NATIVE is ON. Use -march=native for lightning_qubit_ops.") - target_compile_options(pennylane_lightning PRIVATE -march=native) + target_compile_options(pennylane_lightning INTERFACE -march=native) target_compile_options(lightning_qubit_ops PRIVATE -march=native) endif() if (BUILD_TESTS) enable_testing() - #add_subdirectory("pennylane_lightning/src/tests" "tests") endif() From 5859083c0d371e1b997456dcc68b9dec7bb8d505 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 23 Aug 2021 11:00:46 +0100 Subject: [PATCH 17/72] Ensure propagation of flags to lower dirs --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c2cb69a2bc..7843d31000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,8 @@ target_compile_options(lightning_qubit_ops PRIVATE "$<$:-Wall>") target_compile_definitions(lightning_qubit_ops PRIVATE VERSION_INFO=${VERSION_STRING}) if(ENABLE_NATIVE) - message(STATUS "ENABLE_NATIVE is ON. Use -march=native for lightning_qubit_ops.") + message(STATUS "ENABLE_NATIVE is ON. Using -march=native") + add_compile_options(-march=native) target_compile_options(pennylane_lightning INTERFACE -march=native) target_compile_options(lightning_qubit_ops PRIVATE -march=native) endif() From 1d456fa399d795633d976557793cbdf0e4606d64 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 23 Aug 2021 11:04:32 +0100 Subject: [PATCH 18/72] Update build flags --- pennylane_lightning/src/algorithms/CMakeLists.txt | 6 +++++- pennylane_lightning/src/simulator/CMakeLists.txt | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 33851da081..8cea03eb35 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -7,4 +7,8 @@ add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) -set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) \ No newline at end of file +set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) + +if(ENABLE_NATIVE) + target_compile_options(lightning_algorithms PRIVATE -march=native) +endif() \ No newline at end of file diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index e0dd2d05c2..0f95c8b947 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -9,4 +9,8 @@ find_package(OpenMP REQUIRED) target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(lightning_simulator PRIVATE lightning_utils OpenMP::OpenMP_CXX) -set_property(TARGET lightning_simulator PROPERTY POSITION_INDEPENDENT_CODE ON) \ No newline at end of file +set_property(TARGET lightning_simulator PROPERTY POSITION_INDEPENDENT_CODE ON) + +if(ENABLE_NATIVE) + target_compile_options(lightning_simulator PRIVATE -march=native) +endif() \ No newline at end of file From 3e91d07755a0a9a6f53ac95ee6f5aa6ad16a3028 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Tue, 24 Aug 2021 14:19:12 +0100 Subject: [PATCH 19/72] Add additional testing --- .../src/algorithms/AdjointDiff.hpp | 123 ++++++++++++++++-- .../src/tests/Test_AdjDiff.cpp | 30 ++--- 2 files changed, 124 insertions(+), 29 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index f94ed1ce69..e7088fcb1e 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -141,7 +142,7 @@ template class AdjointJacobian { public: AdjointJacobian() {} - void adjointJacobian(StateVector &phi, T *jac, + void adjointJacobian(StateVector &psi, std::vector &jac, const vector &observables, const vector> &obsParams, const vector> &obsWires, @@ -155,12 +156,12 @@ template class AdjointJacobian { int trainableParamNumber = trainableParams.size() - 1; int current_param_idx = num_params - 1; - const size_t num_elements = phi.getLength(); + const size_t num_elements = psi.getLength(); // 1. Copy the input state, create lambda std::unique_ptr[]> SV_lambda_data( new std::complex[num_elements]); - std::copy(phi.getData(), phi.getData() + num_elements, + std::copy(psi.getData(), psi.getData() + num_elements, SV_lambda_data.get()); StateVector SV_lambda(SV_lambda_data.get(), num_elements); @@ -174,7 +175,7 @@ template class AdjointJacobian { lambdas.reserve(numObservables); std::vector[]>> lambdas_data; lambdas_data.reserve(numObservables); - for (int i = 0; i < numObservables; ++i) { + for (size_t i = 0; i < numObservables; i++) { lambdas_data.emplace_back(new std::complex[num_elements]); lambdas.emplace_back( StateVector(lambdas_data[i].get(), num_elements)); @@ -199,7 +200,6 @@ template class AdjointJacobian { "the adjoint differentiation method"); } else if ((operations[i] != "QubitStateVector") && (operations[i] != "BasisState")) { - // copy |phi> to |mu> before applying Uj* std::unique_ptr[]> phiCopyArr( new std::complex[num_elements]); @@ -225,11 +225,8 @@ template class AdjointJacobian { scaling_factors.at(operations[i]); generator_map.at(operations[i])(mu, opWires[i]); - std::cout << "mu::{\n\t" << mu << "\n}" << std::endl; for (size_t j = 0; j < lambdas.size(); j++) { - std::cout << "lambdas[" << j << "]::{\n\t" - << lambdas[j] << "\n}" << std::endl; std::complex sum = innerProdC(lambdas[j].getData(), mu.getData(), @@ -237,12 +234,7 @@ template class AdjointJacobian { // calculate 2 * shift * Real(i * sum) = -2 * shift // * Imag(sum) - std::cout << "sum[" << current_param_idx - << "]=" << sum << ", " << num_elements - << ", " - << j * trainableParams.size() + - trainableParamNumber - << std::endl; + jac[j * trainableParams.size() + trainableParamNumber] = -2 * scalingFactor * std::imag(sum); @@ -260,6 +252,109 @@ template class AdjointJacobian { /// missing else? } } + + /* + void adjointJacobian( + StateVector& phi, + double* jac, + const vector& observables, + const vector >& obsParams, + const vector >& obsWires, + const vector& operations, + const vector >& opParams, + const vector >& opWires, + const vector& trainableParams, + int paramNumber + ) { + using CplxType = std::complex; + std::vector> lambdas; + size_t numObservables = observables.size(); + size_t trainableParamNumber = trainableParams.size() - 1; + + CplxType* lambdaStateArr = new std::complex[phi.getLength()]; + std::memcpy(lambdaStateArr, phi.getData(), + sizeof(CplxType)*phi.getLength()); StateVector + lambdaState(lambdaStateArr, phi.getLength()); + // forward pass on lambda + + for (unsigned int i = 0; i < numObservables; i++) { + // copy |phi> and apply observables one at a time + CplxType* phiCopyArr = new CplxType[lambdaState.getLength()]; + std::memcpy(phiCopyArr, lambdaState.getData(), + sizeof(CplxType)*lambdaState.getLength()); StateVector + phiCopy(phiCopyArr, lambdaState.getLength()); + phiCopy.applyOperation(observables[i], obsWires[i], false, + obsParams[i]); + + lambdas.push_back(phiCopy); + } + + for (int i = operations.size() - 1; i >= 0; i--) { + if (opParams[i].size() > 1) { + throw std::invalid_argument("The operation is not supported + using the adjoint differentiation method"); } else if ((operations[i] != + "QubitStateVector") && (operations[i] != "BasisState")) { + // copy |phi> to |mu> before applying Uj* + CplxType* phiCopyArr = new CplxType[phi.getLength()]; + std::memcpy(phiCopyArr, phi.getData(), + sizeof(CplxType)*phi.getLength()); StateVector mu(phiCopyArr, + phi.getLength()); + // create |phi'> = Uj*|phi> + phi.applyOperation(operations[i], opWires[i], true, + opParams[i]); + + + if (std::find(trainableParams.begin(), trainableParams.end(), + paramNumber) != trainableParams.end()) { + // create iH|phi> = d/d dUj/dtheta Uj* |phi> = + dUj/dtheta|phi'> + //std::unique_ptr gate = + constructGate(operations[i], opParams[i]); + // double scalingFactor = gate->generatorScalingFactor; + //double scalingFactor = + Pennylane::RotationYGate::generatorScalingFactor; const T scalingFactor = + scaling_factors.at(operations[i]); + + generator_map.at(operations[i])(mu, opWires[i]); + + for (unsigned int j = 0; j < lambdas.size(); j++) { + int lambdaStateSize = lambdas[j].getLength(); + + CplxType sum = 0; + for (int k = 0; k < lambdaStateSize; k++) { + sum += (std::conj(lambdas[j].getData()[k]) * + mu.getData()[k]); + } + // calculate 2 * shift * Real(i * sum) = -2 * shift * + Imag(sum) jac[j * trainableParams.size() + trainableParamNumber] = -2 * + scalingFactor * std::imag(sum); + } + delete[] phiCopyArr; + trainableParamNumber--; + } + paramNumber--; + + // if i > 0: (?) + for (unsigned int j = 0; j < lambdas.size(); j++) { + lambdas[i].applyOperation(operations[i], opWires[i], true, + opParams[i]); + /*Pennylane::constructAndApplyOperation( + lambdas[j], + operations[i], + opWires[i], + opParams[i], + true, + opWires[i].size() + ); + } + } + } + // delete copied state arrays + for (int i; i < lambdas.size(); i++) { + delete[] lambdas[i].getData(); + } + } + */ }; } // namespace Algorithms diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 243b5220e9..834ab5ce30 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -48,8 +49,9 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian.data(), {"PauliZ"}, {{}}, {{0}}, - {"RX"}, {{p}}, {{0}}, {0}, 1); + adj.adjointJacobian(psi, jacobian, {"PauliZ"}, {{}}, {{0}}, {"RX"}, + {{p}}, {{0}}, {0}, 1); + CAPTURE(jacobian); CHECK(-sin(p) == Approx(jacobian.front())); } } @@ -65,28 +67,27 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian.data(), {"PauliX"}, {{}}, {{0}}, - {"RY"}, {{p}}, {{0}}, {0}, 1); - CHECK(cos(p) == Approx(jacobian.front())); + adj.adjointJacobian(psi, jacobian, {"PauliX"}, {{}}, {{0}}, {"RY"}, + {{p}}, {{0}}, {0}, 1); + CAPTURE(jacobian); + CHECK(cos(p) == Approx(jacobian.front()).epsilon(0.01)); } } SECTION("Single RX gradient, multiple wires") { const size_t num_qubits = 3; const size_t num_params = 1; - const size_t num_obs = 1; + const size_t num_obs = 3; std::vector jacobian(num_obs * num_params, 0.0); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian( - psi, jacobian.data(), {"PauliZ", "PauliZ", "PauliZ"}, {{}}, - {{0}, {1}, {2}}, {"RX"}, {{param[0]}}, {{0}}, {0}, num_params); + adj.adjointJacobian(psi, jacobian, {"PauliZ", "PauliZ", "PauliZ"}, {{}}, + {{0}, {1}, {2}}, {"RX"}, {{param[0]}}, {{0}}, {0}, + num_params); CAPTURE(jacobian); - for (size_t i = 0; i < num_params; i++) { - CHECK(-sin(param[i]) == Approx(jacobian[i])); - } + CHECK(-sin(param[0]) == Approx(jacobian[0])); } SECTION("Multiple RX gradient") { const size_t num_qubits = 3; @@ -98,14 +99,13 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian.data(), - {"PauliZ", "PauliZ", "PauliZ"}, {{}}, + adj.adjointJacobian(psi, jacobian, {"PauliZ", "PauliZ", "PauliZ"}, {{}}, {{0}, {1}, {2}}, {"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, {{0}, {1}, {2}}, {0, 1, 2}, num_params); CAPTURE(jacobian); for (size_t i = 0; i < num_params; i++) { - CHECK(-sin(param[i]) == Approx(jacobian[i])); + CHECK(-cos(param[i]) == Approx(jacobian[i * num_params + i])); } } } From 1a12eb9eb29a38303d3a899a60d033603b81d030 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 12:20:44 +0100 Subject: [PATCH 20/72] Update tests --- .../src/algorithms/AdjointDiff.hpp | 386 +++++++++++++----- .../src/tests/Test_AdjDiff.cpp | 74 +++- 2 files changed, 342 insertions(+), 118 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index e7088fcb1e..fc6e778499 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -19,6 +19,50 @@ namespace { using namespace Pennylane::Util; +template +class SVUnique : public Pennylane::StateVector { + private: + std::unique_ptr> arr_; + size_t length_; + size_t num_qubits_; + + public: + SVUnique(size_t data_size) + : arr_{new std::complex[data_size]}, length_{data_size}, + num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), + data_size} {} + + SVUnique(const Pennylane::StateVector &sv) + : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + length_ = sv.getLength(); + num_qubits_ = sv.getNumQubits(); + }; + + SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + }; + + std::complex *getData() { return arr_.get(); } + std::complex *getData() const { return arr_.get(); } +}; + +template +inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { + const size_t num_qubits = sv.getNumQubits(); + const size_t length = sv.getLength(); + const auto data_ptr = sv.getData(); + out << "num_qubits=" << num_qubits << std::endl; + out << "data=["; + out << data_ptr[0]; + for (size_t i = 1; i < length - 1; i++) { + out << "," << data_ptr[i]; + } + out << "," << data_ptr[length - 1] << "]"; + + return out; +} + template static constexpr std::vector> getP00() { return {ONE(), ZERO(), ZERO(), ZERO()}; } @@ -42,7 +86,7 @@ void applyGeneratorRY(Pennylane::StateVector &sv, template void applyGeneratorRZ(Pennylane::StateVector &sv, const std::vector &wires) { - sv.applyOperation("PauliY", wires, false); + sv.applyOperation("PauliZ", wires, false); } template @@ -139,13 +183,95 @@ template class AdjointJacobian { {"CRX", -0.5}, {"CRY", -0.5}, {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; + /** + * @brief Utility struct for a single local or tensor observable + * + */ + struct ObsData { + const std::vector obs_name_; + const std::vector> obs_params_; + const std::vector> obs_wires_; + ObsData(const std::vector &obs_name, + const std::vector> &obs_params, + const std::vector> &obs_wires) + : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ + obs_wires} {}; + size_t getSize() const { return obs_name_.size(); } + const std::vector &getObsName() const { return obs_name_; } + const std::vector> &getObsParams() const { + return obs_params_; + } + const std::vector> &getObsWires() const { + return obs_wires_; + } + }; + public: AdjointJacobian() {} + const ObsData + createObsDS(const std::vector &obs_name, + const std::vector> &obs_params, + const std::vector> &obs_wires) { + return ObsData(obs_name, obs_params, obs_wires); + } + + std::vector adj_jac(const StateVector &psi, + const vector &observables, + const vector> &obsWires, + const vector &operations, + const vector> &opWires, + const vector &opInverse, + const vector> &opParams) { + + const size_t num_observables = observables.size(); + const size_t num_params = opParams.size(); + const size_t num_elements = psi.getLength(); + + std::vector jacobian(observables.size()); + SVUnique lambda(psi); + // std::cout << lambda << std::endl; + + for (size_t op_idx = 0; op_idx < operations.size(); op_idx++) { + lambda.applyOperation(operations[op_idx], opWires[op_idx], + opInverse[op_idx], opParams[op_idx]); + } + std::vector> H_lambda; + + for (size_t h_i = 0; h_i < num_observables; h_i++) { + H_lambda.push_back(lambda); + H_lambda[h_i].applyOperation(observables[h_i], obsWires[h_i], false, + {}); + // std::cout << H_lambda[h_i] << std::endl; + } + + SVUnique phi(lambda); + for (int op_idx = operations.size() - 1; op_idx >= 0; op_idx--) { + phi.applyOperation(operations[op_idx], opWires[op_idx], + !opInverse[op_idx], opParams[op_idx]); + SVUnique mu(phi); + generator_map.at(operations[op_idx])(mu, opWires[op_idx]); + const T scalingFactor = scaling_factors.at(operations[op_idx]); + for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + jacobian[obs_idx * operations.size() + obs_idx] = + -2 * scalingFactor * + std::imag(innerProdC(H_lambda[obs_idx].getData(), + mu.getData(), num_elements)); + if (op_idx > 0) { + H_lambda[obs_idx].applyOperation( + operations[op_idx], opWires[op_idx], !opInverse[op_idx], + opParams[op_idx]); + } + } + } + return jacobian; + } + void adjointJacobian(StateVector &psi, std::vector &jac, - const vector &observables, - const vector> &obsParams, - const vector> &obsWires, + const std::vector observables, + // const vector> &observables, + // const vector> &obsParams, + // const vector> &obsWires, const vector &operations, const vector> &opParams, const vector> &opWires, @@ -171,6 +297,13 @@ template class AdjointJacobian { // 3-4. Copy lambda and apply the observables // SV_lambda becomes |phi> + + std::unique_ptr[]> phi_data( + new std::complex[num_elements]); + std::copy(SV_lambda.getData(), SV_lambda.getData() + num_elements, + phi_data.get()); + StateVector phi_1(phi_data.get(), num_elements); + std::vector> lambdas; lambdas.reserve(numObservables); std::vector[]>> lambdas_data; @@ -187,8 +320,20 @@ template class AdjointJacobian { std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, lambdas_data[i].get()); - lambdas[i].applyOperation(observables[i], obsWires[i], false, - obsParams[i]); + for (size_t j = 0; j < observables[i].getSize(); j++) { + // std::cout << "i=" << i << ", j=" << j << std::endl; + // std::cout << "observables[i].getObsName()[j]=" << + // observables[i].getObsName()[j] << std::endl; std::cout << + // "observables[i].getObsWires()[j]=" << + // observables[i].getObsWires()[j][0] << std::endl; std::cout << + // "observables[i].getObsParams()[j]=" << + // observables[i].getObsParams()[j][0] << std::endl; + + lambdas[i].applyOperation(observables[i].getObsName()[j], + observables[i].getObsWires()[j], + false, + observables[i].getObsParams()[j]); + } } // replace with reverse iterator over values? @@ -201,16 +346,16 @@ template class AdjointJacobian { } else if ((operations[i] != "QubitStateVector") && (operations[i] != "BasisState")) { - std::unique_ptr[]> phiCopyArr( + std::unique_ptr[]> mu_data( new std::complex[num_elements]); - std::copy(SV_lambda.getData(), - SV_lambda.getData() + num_elements, phiCopyArr.get()); + std::copy(phi_1.getData(), phi_1.getData() + num_elements, + mu_data.get()); - StateVector mu(phiCopyArr.get(), num_elements); + StateVector mu(mu_data.get(), num_elements); // create |phi'> = Uj*|phi> - SV_lambda.applyOperation(operations[i], opWires[i], true, - opParams[i]); + phi_1.applyOperation(operations[i], opWires[i], true, + opParams[i]); // We have a parametrized gate if (!opParams[i].empty()) { @@ -226,6 +371,13 @@ template class AdjointJacobian { generator_map.at(operations[i])(mu, opWires[i]); + /*std::transform( + mu_data.get(), mu_data.get() + num_elements, + mu_data.get(), + [](std::complex c) -> std::complex { + return {-c.imag(), c.real()}; + });*/ + for (size_t j = 0; j < lambdas.size(); j++) { std::complex sum = @@ -234,9 +386,15 @@ template class AdjointJacobian { // calculate 2 * shift * Real(i * sum) = -2 * shift // * Imag(sum) - + // std::cout << "L[" << i << ", " << j + // << "]=" << lambdas[j] << std::endl; + // std::cout << "mu[" << i << ", " << j << "]=" << + // mu + // << std::endl + // << std::endl; jac[j * trainableParams.size() + trainableParamNumber] = + // 2 * scalingFactor * std::real(sum); -2 * scalingFactor * std::imag(sum); } trainableParamNumber--; @@ -252,110 +410,132 @@ template class AdjointJacobian { /// missing else? } } +}; + +} // namespace Algorithms +} // namespace Pennylane + +/* + void adjointJacobian(StateVector &psi, std::vector &jac, + const vector> &observables, + const vector> &obsParams, + const vector> &obsWires, + const vector &operations, + const vector> &opParams, + const vector> &opWires, + const vector &trainableParams, + size_t num_params) { - /* - void adjointJacobian( - StateVector& phi, - double* jac, - const vector& observables, - const vector >& obsParams, - const vector >& obsWires, - const vector& operations, - const vector >& opParams, - const vector >& opWires, - const vector& trainableParams, - int paramNumber - ) { - using CplxType = std::complex; - std::vector> lambdas; size_t numObservables = observables.size(); - size_t trainableParamNumber = trainableParams.size() - 1; + int trainableParamNumber = trainableParams.size() - 1; + int current_param_idx = num_params - 1; - CplxType* lambdaStateArr = new std::complex[phi.getLength()]; - std::memcpy(lambdaStateArr, phi.getData(), - sizeof(CplxType)*phi.getLength()); StateVector - lambdaState(lambdaStateArr, phi.getLength()); - // forward pass on lambda + const size_t num_elements = psi.getLength(); + + // 1. Copy the input state, create lambda + std::unique_ptr[]> SV_lambda_data( + new std::complex[num_elements]); + std::copy(psi.getData(), psi.getData() + num_elements, + SV_lambda_data.get()); + StateVector SV_lambda(SV_lambda_data.get(), num_elements); + + // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda + std::vector inverses(operations.size(), false); + SV_lambda.applyOperations(operations, opWires, inverses, opParams); + + // 3-4. Copy lambda and apply the observables + // SV_lambda becomes |phi> - for (unsigned int i = 0; i < numObservables; i++) { + std::unique_ptr[]> phi_data( + new std::complex[num_elements]); + std::copy(SV_lambda.getData(), SV_lambda.getData() + num_elements, + phi_data.get()); + StateVector phi_1(phi_data.get(), num_elements); + + std::vector> lambdas; + lambdas.reserve(numObservables); + std::vector[]>> lambdas_data; + lambdas_data.reserve(numObservables); + for (size_t i = 0; i < numObservables; i++) { + lambdas_data.emplace_back(new std::complex[num_elements]); + lambdas.emplace_back( + StateVector(lambdas_data[i].get(), num_elements)); + } + + //#pragma omp parallel for + for (size_t i = 0; i < numObservables; i++) { // copy |phi> and apply observables one at a time - CplxType* phiCopyArr = new CplxType[lambdaState.getLength()]; - std::memcpy(phiCopyArr, lambdaState.getData(), - sizeof(CplxType)*lambdaState.getLength()); StateVector - phiCopy(phiCopyArr, lambdaState.getLength()); - phiCopy.applyOperation(observables[i], obsWires[i], false, - obsParams[i]); - - lambdas.push_back(phiCopy); + std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, + lambdas_data[i].get()); + + lambdas[i].applyOperation(observables[i], obsWires[i], false, + obsParams[i]); } + // replace with reverse iterator over values? for (int i = operations.size() - 1; i >= 0; i--) { + if (opParams[i].size() > 1) { - throw std::invalid_argument("The operation is not supported - using the adjoint differentiation method"); } else if ((operations[i] != - "QubitStateVector") && (operations[i] != "BasisState")) { - // copy |phi> to |mu> before applying Uj* - CplxType* phiCopyArr = new CplxType[phi.getLength()]; - std::memcpy(phiCopyArr, phi.getData(), - sizeof(CplxType)*phi.getLength()); StateVector mu(phiCopyArr, - phi.getLength()); + throw std::invalid_argument( + "The operation is not supported using " + "the adjoint differentiation method"); + } else if ((operations[i] != "QubitStateVector") && + (operations[i] != "BasisState")) { + + std::unique_ptr[]> mu_data( + new std::complex[num_elements]); + std::copy(phi_1.getData(), phi_1.getData() + num_elements, + mu_data.get()); + + StateVector mu(mu_data.get(), num_elements); + // create |phi'> = Uj*|phi> - phi.applyOperation(operations[i], opWires[i], true, - opParams[i]); - - - if (std::find(trainableParams.begin(), trainableParams.end(), - paramNumber) != trainableParams.end()) { - // create iH|phi> = d/d dUj/dtheta Uj* |phi> = - dUj/dtheta|phi'> - //std::unique_ptr gate = - constructGate(operations[i], opParams[i]); - // double scalingFactor = gate->generatorScalingFactor; - //double scalingFactor = - Pennylane::RotationYGate::generatorScalingFactor; const T scalingFactor = - scaling_factors.at(operations[i]); - - generator_map.at(operations[i])(mu, opWires[i]); - - for (unsigned int j = 0; j < lambdas.size(); j++) { - int lambdaStateSize = lambdas[j].getLength(); - - CplxType sum = 0; - for (int k = 0; k < lambdaStateSize; k++) { - sum += (std::conj(lambdas[j].getData()[k]) * - mu.getData()[k]); + phi_1.applyOperation(operations[i], opWires[i], true, + opParams[i]); + + // We have a parametrized gate + if (!opParams[i].empty()) { + + if (std::find(trainableParams.begin(), + trainableParams.end(), + current_param_idx) != trainableParams.end()) { + + // create iH|phi> = d/d dUj/dtheta Uj* |phi> = + // dUj/dtheta|phi'> + const T scalingFactor = + scaling_factors.at(operations[i]); + + generator_map.at(operations[i])(mu, opWires[i]); + + for (size_t j = 0; j < lambdas.size(); j++) { + + std::complex sum = + innerProdC(lambdas[j].getData(), mu.getData(), + num_elements); + + // calculate 2 * shift * Real(i * sum) = -2 * shift + // * Imag(sum) + std::cout << "L[" << i << ", " << j + << "]=" << lambdas[j] << std::endl; + std::cout << "mu[" << i << ", " << j << "]=" << mu + << std::endl + << std::endl; + jac[j * trainableParams.size() + + trainableParamNumber] = + // 2 * scalingFactor * std::real(sum); + -2 * scalingFactor * std::imag(sum); } - // calculate 2 * shift * Real(i * sum) = -2 * shift * - Imag(sum) jac[j * trainableParams.size() + trainableParamNumber] = -2 * - scalingFactor * std::imag(sum); + trainableParamNumber--; } - delete[] phiCopyArr; - trainableParamNumber--; + current_param_idx--; } - paramNumber--; - - // if i > 0: (?) - for (unsigned int j = 0; j < lambdas.size(); j++) { - lambdas[i].applyOperation(operations[i], opWires[i], true, - opParams[i]); - /*Pennylane::constructAndApplyOperation( - lambdas[j], - operations[i], - opWires[i], - opParams[i], - true, - opWires[i].size() - ); + + for (size_t j = 0; j < lambdas.size(); j++) { + lambdas[j].applyOperation(operations[i], opWires[i], true, + opParams[i]); } } - } - // delete copied state arrays - for (int i; i < lambdas.size(); i++) { - delete[] lambdas[i].getData(); + /// missing else? } } - */ -}; - -} // namespace Algorithms -} // namespace Pennylane \ No newline at end of file + */ \ No newline at end of file diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 834ab5ce30..71daa78583 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -36,12 +36,16 @@ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { AdjointJacobian adj; - std::vector param{1, -2, 1.623, -0.051, 0}; + // std::vector param{1, -2, 1.623, -0.051, 0}; + std::vector param{M_PI, M_PI_2, M_PI / 3}; SECTION("RX gradient") { const size_t num_qubits = 1; const size_t num_params = 1; const size_t num_obs = 1; + + auto obs = adj.createObsDS({{"PauliZ"}}, {{}}, {{0}}); + for (const auto &p : param) { std::vector jacobian(num_obs * num_params, 0.0); @@ -49,8 +53,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {"PauliZ"}, {{}}, {{0}}, {"RX"}, - {{p}}, {{0}}, {0}, 1); + adj.adjointJacobian(psi, jacobian, {obs}, {"RX"}, {{p}}, {{0}}, {0}, + 1); CAPTURE(jacobian); CHECK(-sin(p) == Approx(jacobian.front())); } @@ -60,6 +64,9 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_qubits = 1; const size_t num_params = 1; const size_t num_obs = 1; + + auto obs = adj.createObsDS({"PauliX"}, {{}}, {{0}}); + for (const auto &p : param) { std::vector jacobian(num_obs * num_params, 0.0); @@ -67,13 +74,13 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {"PauliX"}, {{}}, {{0}}, {"RY"}, - {{p}}, {{0}}, {0}, 1); + adj.adjointJacobian(psi, jacobian, {obs}, {"RY"}, {{p}}, {{0}}, {0}, + 1); CAPTURE(jacobian); - CHECK(cos(p) == Approx(jacobian.front()).epsilon(0.01)); + CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); } } - SECTION("Single RX gradient, multiple wires") { + SECTION("Single RX gradient, single expval per wire") { const size_t num_qubits = 3; const size_t num_params = 1; const size_t num_obs = 3; @@ -83,13 +90,19 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {"PauliZ", "PauliZ", "PauliZ"}, {{}}, - {{0}, {1}, {2}}, {"RX"}, {{param[0]}}, {{0}}, {0}, - num_params); + auto obs1 = adj.createObsDS({"PauliZ"}, {{}}, {{0}}); + auto obs2 = adj.createObsDS({"PauliZ"}, {{}}, {{1}}); + auto obs3 = adj.createObsDS({"PauliZ"}, {{}}, {{2}}); + + adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, {"RX"}, + {{param[0]}}, {{0}}, {0}, num_params); + + // jacobian = adj.adj_jac(psi, {"PauliZ", "PauliZ", "PauliZ"}, {{0}, + // {1}, {2}}, {"RX"}, {{0}}, {false}, {{0.234}} ); CAPTURE(jacobian); - CHECK(-sin(param[0]) == Approx(jacobian[0])); + CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); } - SECTION("Multiple RX gradient") { + SECTION("Multiple RX gradient, single expval per wire") { const size_t num_qubits = 3; const size_t num_params = 3; const size_t num_obs = 3; @@ -99,13 +112,44 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {"PauliZ", "PauliZ", "PauliZ"}, {{}}, - {{0}, {1}, {2}}, {"RX", "RX", "RX"}, + auto obs1 = adj.createObsDS({"PauliZ"}, {{}}, {{0}}); + auto obs2 = adj.createObsDS({"PauliZ"}, {{}}, {{1}}); + auto obs3 = adj.createObsDS({"PauliZ"}, {{}}, {{2}}); + + adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, + {"RX", "RX", "RX"}, + {{param[0]}, {param[1]}, {param[2]}}, + {{0}, {1}, {2}}, {0, 1, 2}, num_params); + + // adj.adjointJacobian(psi, jacobian, obs, {"RX", "RX", "RX"}, + // {{param[0]}, {param[1]}, {param[2]}}, + // {{0}, {1}, {2}}, {0, 1, 2}, num_params); + + // jacobian = adj.adj_jac(psi, {"PauliZ", "PauliZ", "PauliZ"}, {{0}, + // {1}, {2}}, {"RX"}, {{0}}, {false}, {{0.234}} ); + CAPTURE(jacobian); + CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); + } + SECTION("Multiple RX gradient, tensor expval") { + const size_t num_qubits = 3; + const size_t num_params = 3; + const size_t num_obs = 1; + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + auto obs = adj.createObsDS({"PauliZ", "PauliZ", "PauliZ"}, {{}}, + {{0}, {1}, {2}}); + + adj.adjointJacobian(psi, jacobian, {obs}, {"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, {{0}, {1}, {2}}, {0, 1, 2}, num_params); CAPTURE(jacobian); for (size_t i = 0; i < num_params; i++) { - CHECK(-cos(param[i]) == Approx(jacobian[i * num_params + i])); + CHECK(-sin(param[i]) == + Approx(jacobian[i * num_params + i]).margin(1e-7)); } } } From 2317a94306c87cb9fe37d8bc9bd8d244a082152d Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 14:33:48 +0100 Subject: [PATCH 21/72] Encapsulate ops and obs data for Python serialization --- .../src/algorithms/AdjointDiff.hpp | 233 ++++++++++-------- .../src/tests/Test_AdjDiff.cpp | 60 ++--- 2 files changed, 156 insertions(+), 137 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index fc6e778499..bed6fe4648 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -184,16 +184,16 @@ template class AdjointJacobian { {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; /** - * @brief Utility struct for a single local or tensor observable + * @brief Utility struct for a observable operations * */ - struct ObsData { + struct ObsDatum { const std::vector obs_name_; const std::vector> obs_params_; const std::vector> obs_wires_; - ObsData(const std::vector &obs_name, - const std::vector> &obs_params, - const std::vector> &obs_wires) + ObsDatum(const std::vector &obs_name, + const std::vector> &obs_params, + const std::vector> &obs_wires) : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ obs_wires} {}; size_t getSize() const { return obs_name_.size(); } @@ -205,76 +205,63 @@ template class AdjointJacobian { return obs_wires_; } }; + struct OpsData { + const std::vector ops_name_; + const std::vector> ops_params_; + const std::vector> ops_wires_; + const std::vector ops_inverses_; + const std::vector>> ops_matrices_; + + OpsData(const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector>> &ops_matrices = + {{}}) + : ops_name_{ops_name}, ops_params_{ops_params}, + ops_wires_{ops_wires}, ops_inverses_{ops_inverses}, + ops_matrices_{ops_matrices} {}; + + size_t getSize() const { return ops_name_.size(); } + const std::vector &getOpsName() const { return ops_name_; } + const std::vector> &getOpsParams() const { + return ops_params_; + } + const std::vector> &getOpsWires() const { + return ops_wires_; + } + const std::vector &getOpsInverses() const { + return ops_inverses_; + } + const std::vector>> & + getOpsMatrices() const { + return ops_matrices_; + } + }; public: AdjointJacobian() {} - const ObsData - createObsDS(const std::vector &obs_name, - const std::vector> &obs_params, - const std::vector> &obs_wires) { - return ObsData(obs_name, obs_params, obs_wires); + const ObsDatum + createObs(const std::vector &obs_name, + const std::vector> &obs_params, + const std::vector> &obs_wires) { + return ObsDatum(obs_name, obs_params, obs_wires); } - std::vector adj_jac(const StateVector &psi, - const vector &observables, - const vector> &obsWires, - const vector &operations, - const vector> &opWires, - const vector &opInverse, - const vector> &opParams) { - - const size_t num_observables = observables.size(); - const size_t num_params = opParams.size(); - const size_t num_elements = psi.getLength(); - - std::vector jacobian(observables.size()); - SVUnique lambda(psi); - // std::cout << lambda << std::endl; - - for (size_t op_idx = 0; op_idx < operations.size(); op_idx++) { - lambda.applyOperation(operations[op_idx], opWires[op_idx], - opInverse[op_idx], opParams[op_idx]); - } - std::vector> H_lambda; - - for (size_t h_i = 0; h_i < num_observables; h_i++) { - H_lambda.push_back(lambda); - H_lambda[h_i].applyOperation(observables[h_i], obsWires[h_i], false, - {}); - // std::cout << H_lambda[h_i] << std::endl; - } - - SVUnique phi(lambda); - for (int op_idx = operations.size() - 1; op_idx >= 0; op_idx--) { - phi.applyOperation(operations[op_idx], opWires[op_idx], - !opInverse[op_idx], opParams[op_idx]); - SVUnique mu(phi); - generator_map.at(operations[op_idx])(mu, opWires[op_idx]); - const T scalingFactor = scaling_factors.at(operations[op_idx]); - for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - jacobian[obs_idx * operations.size() + obs_idx] = - -2 * scalingFactor * - std::imag(innerProdC(H_lambda[obs_idx].getData(), - mu.getData(), num_elements)); - if (op_idx > 0) { - H_lambda[obs_idx].applyOperation( - operations[op_idx], opWires[op_idx], !opInverse[op_idx], - opParams[op_idx]); - } - } - } - return jacobian; + const OpsData createOpsData( + const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector>> &ops_matrices = {{}}) { + return OpsData(ops_name, ops_params, ops_wires, ops_inverses, + ops_matrices); } void adjointJacobian(StateVector &psi, std::vector &jac, - const std::vector observables, - // const vector> &observables, - // const vector> &obsParams, - // const vector> &obsWires, - const vector &operations, - const vector> &opParams, - const vector> &opWires, + const std::vector &observables, + const OpsData &operations, const vector &trainableParams, size_t num_params) { @@ -292,8 +279,9 @@ template class AdjointJacobian { StateVector SV_lambda(SV_lambda_data.get(), num_elements); // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda - std::vector inverses(operations.size(), false); - SV_lambda.applyOperations(operations, opWires, inverses, opParams); + SV_lambda.applyOperations( + operations.getOpsName(), operations.getOpsWires(), + operations.getOpsInverses(), operations.getOpsParams()); // 3-4. Copy lambda and apply the observables // SV_lambda becomes |phi> @@ -321,14 +309,6 @@ template class AdjointJacobian { lambdas_data[i].get()); for (size_t j = 0; j < observables[i].getSize(); j++) { - // std::cout << "i=" << i << ", j=" << j << std::endl; - // std::cout << "observables[i].getObsName()[j]=" << - // observables[i].getObsName()[j] << std::endl; std::cout << - // "observables[i].getObsWires()[j]=" << - // observables[i].getObsWires()[j][0] << std::endl; std::cout << - // "observables[i].getObsParams()[j]=" << - // observables[i].getObsParams()[j][0] << std::endl; - lambdas[i].applyOperation(observables[i].getObsName()[j], observables[i].getObsWires()[j], false, @@ -337,14 +317,14 @@ template class AdjointJacobian { } // replace with reverse iterator over values? - for (int i = operations.size() - 1; i >= 0; i--) { + for (int i = operations.getOpsName().size() - 1; i >= 0; i--) { - if (opParams[i].size() > 1) { + if (operations.getOpsParams()[i].size() > 1) { throw std::invalid_argument( "The operation is not supported using " "the adjoint differentiation method"); - } else if ((operations[i] != "QubitStateVector") && - (operations[i] != "BasisState")) { + } else if ((operations.getOpsName()[i] != "QubitStateVector") && + (operations.getOpsName()[i] != "BasisState")) { std::unique_ptr[]> mu_data( new std::complex[num_elements]); @@ -354,11 +334,13 @@ template class AdjointJacobian { StateVector mu(mu_data.get(), num_elements); // create |phi'> = Uj*|phi> - phi_1.applyOperation(operations[i], opWires[i], true, - opParams[i]); + phi_1.applyOperation(operations.getOpsName()[i], + operations.getOpsWires()[i], + !operations.getOpsInverses()[i], + operations.getOpsParams()[i]); // We have a parametrized gate - if (!opParams[i].empty()) { + if (!operations.getOpsParams()[i].empty()) { if (std::find(trainableParams.begin(), trainableParams.end(), @@ -367,34 +349,17 @@ template class AdjointJacobian { // create iH|phi> = d/d dUj/dtheta Uj* |phi> = // dUj/dtheta|phi'> const T scalingFactor = - scaling_factors.at(operations[i]); - - generator_map.at(operations[i])(mu, opWires[i]); - - /*std::transform( - mu_data.get(), mu_data.get() + num_elements, - mu_data.get(), - [](std::complex c) -> std::complex { - return {-c.imag(), c.real()}; - });*/ + scaling_factors.at(operations.getOpsName()[i]); + generator_map.at(operations.getOpsName()[i])( + mu, operations.getOpsWires()[i]); for (size_t j = 0; j < lambdas.size(); j++) { std::complex sum = innerProdC(lambdas[j].getData(), mu.getData(), num_elements); - - // calculate 2 * shift * Real(i * sum) = -2 * shift - // * Imag(sum) - // std::cout << "L[" << i << ", " << j - // << "]=" << lambdas[j] << std::endl; - // std::cout << "mu[" << i << ", " << j << "]=" << - // mu - // << std::endl - // << std::endl; jac[j * trainableParams.size() + trainableParamNumber] = - // 2 * scalingFactor * std::real(sum); -2 * scalingFactor * std::imag(sum); } trainableParamNumber--; @@ -403,11 +368,12 @@ template class AdjointJacobian { } for (size_t j = 0; j < lambdas.size(); j++) { - lambdas[j].applyOperation(operations[i], opWires[i], true, - opParams[i]); + lambdas[j].applyOperation(operations.getOpsName()[i], + operations.getOpsWires()[i], + !operations.getOpsInverses()[i], + operations.getOpsParams()[i]); } } - /// missing else? } } }; @@ -538,4 +504,57 @@ template class AdjointJacobian { /// missing else? } } - */ \ No newline at end of file + */ + +/* + std::vector adj_jac(const StateVector &psi, + const vector &observables, + const vector> &obsWires, + const vector &operations, + const vector> &opWires, + const vector &opInverse, + const vector> &opParams) { + + const size_t num_observables = observables.size(); + const size_t num_params = opParams.size(); + const size_t num_elements = psi.getLength(); + + std::vector jacobian(observables.size()); + SVUnique lambda(psi); + // std::cout << lambda << std::endl; + + for (size_t op_idx = 0; op_idx < operations.size(); op_idx++) { + lambda.applyOperation(operations[op_idx], opWires[op_idx], + opInverse[op_idx], opParams[op_idx]); + } + std::vector> H_lambda; + + for (size_t h_i = 0; h_i < num_observables; h_i++) { + H_lambda.push_back(lambda); + H_lambda[h_i].applyOperation(observables[h_i], obsWires[h_i], +false, + {}); + // std::cout << H_lambda[h_i] << std::endl; + } + + SVUnique phi(lambda); + for (int op_idx = operations.size() - 1; op_idx >= 0; op_idx--) { + phi.applyOperation(operations[op_idx], opWires[op_idx], + !opInverse[op_idx], opParams[op_idx]); + SVUnique mu(phi); + generator_map.at(operations[op_idx])(mu, opWires[op_idx]); + const T scalingFactor = scaling_factors.at(operations[op_idx]); + for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + jacobian[obs_idx * operations.size() + obs_idx] = + -2 * scalingFactor * + std::imag(innerProdC(H_lambda[obs_idx].getData(), + mu.getData(), num_elements)); + if (op_idx > 0) { + H_lambda[obs_idx].applyOperation( + operations[op_idx], opWires[op_idx], +!opInverse[op_idx], opParams[op_idx]); + } + } + } + return jacobian; + }*/ diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 71daa78583..3bb301e7e7 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -44,17 +44,18 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_params = 1; const size_t num_obs = 1; - auto obs = adj.createObsDS({{"PauliZ"}}, {{}}, {{0}}); + auto obs = adj.createObs({"PauliZ"}, {{}}, {{0}}); for (const auto &p : param) { + auto ops = adj.createOpsData({"RX"}, {{p}}, {{0}}, {false}); + std::vector jacobian(num_obs * num_params, 0.0); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {obs}, {"RX"}, {{p}}, {{0}}, {0}, - 1); + adj.adjointJacobian(psi, jacobian, {obs}, ops, {0}, 1); CAPTURE(jacobian); CHECK(-sin(p) == Approx(jacobian.front())); } @@ -65,17 +66,19 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_params = 1; const size_t num_obs = 1; - auto obs = adj.createObsDS({"PauliX"}, {{}}, {{0}}); + auto obs = adj.createObs({"PauliX"}, {{}}, {{0}}); for (const auto &p : param) { + auto ops = adj.createOpsData({"RY"}, {{p}}, {{0}}, {false}); + std::vector jacobian(num_obs * num_params, 0.0); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {obs}, {"RY"}, {{p}}, {{0}}, {0}, - 1); + adj.adjointJacobian(psi, jacobian, {obs}, ops, {0}, 1); + CAPTURE(jacobian); CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); } @@ -90,15 +93,15 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs1 = adj.createObsDS({"PauliZ"}, {{}}, {{0}}); - auto obs2 = adj.createObsDS({"PauliZ"}, {{}}, {{1}}); - auto obs3 = adj.createObsDS({"PauliZ"}, {{}}, {{2}}); + auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); + auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); + + auto ops = adj.createOpsData({"RX"}, {{param[0]}}, {{0}}, {false}); - adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, {"RX"}, - {{param[0]}}, {{0}}, {0}, num_params); + adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, ops, {0}, + num_params); - // jacobian = adj.adj_jac(psi, {"PauliZ", "PauliZ", "PauliZ"}, {{0}, - // {1}, {2}}, {"RX"}, {{0}}, {false}, {{0.234}} ); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); } @@ -112,21 +115,17 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs1 = adj.createObsDS({"PauliZ"}, {{}}, {{0}}); - auto obs2 = adj.createObsDS({"PauliZ"}, {{}}, {{1}}); - auto obs3 = adj.createObsDS({"PauliZ"}, {{}}, {{2}}); + auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); + auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); - adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, - {"RX", "RX", "RX"}, - {{param[0]}, {param[1]}, {param[2]}}, - {{0}, {1}, {2}}, {0, 1, 2}, num_params); + auto ops = adj.createOpsData({"RX", "RX", "RX"}, + {{param[0]}, {param[1]}, {param[2]}}, + {{0}, {1}, {2}}, {false, false, false}); - // adj.adjointJacobian(psi, jacobian, obs, {"RX", "RX", "RX"}, - // {{param[0]}, {param[1]}, {param[2]}}, - // {{0}, {1}, {2}}, {0, 1, 2}, num_params); + adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, ops, {0, 1, 2}, + num_params); - // jacobian = adj.adj_jac(psi, {"PauliZ", "PauliZ", "PauliZ"}, {{0}, - // {1}, {2}}, {"RX"}, {{0}}, {false}, {{0.234}} ); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); } @@ -140,12 +139,13 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs = adj.createObsDS({"PauliZ", "PauliZ", "PauliZ"}, {{}}, - {{0}, {1}, {2}}); + auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}}, + {{0}, {1}, {2}}); + auto ops = adj.createOpsData({"RX", "RX", "RX"}, + {{param[0]}, {param[1]}, {param[2]}}, + {{0}, {1}, {2}}, {false, false, false}); - adj.adjointJacobian(psi, jacobian, {obs}, {"RX", "RX", "RX"}, - {{param[0]}, {param[1]}, {param[2]}}, - {{0}, {1}, {2}}, {0, 1, 2}, num_params); + adj.adjointJacobian(psi, jacobian, {obs}, ops, {0, 1, 2}, num_params); CAPTURE(jacobian); for (size_t i = 0; i < num_params; i++) { CHECK(-sin(param[i]) == From 2216b5103cdc256a8aa44ddbaf5b826aea4b9dd3 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 16:09:37 +0100 Subject: [PATCH 22/72] Restructure the obs and ops data for adjoint calcs --- pennylane_lightning/src/Bindings.cpp | 84 ++++++++++- .../src/algorithms/AdjointDiff.hpp | 137 +++++++++--------- .../src/tests/Test_AdjDiff.cpp | 17 ++- 3 files changed, 162 insertions(+), 76 deletions(-) diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/Bindings.cpp index 332b8ed67f..a581d5300e 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/Bindings.cpp @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +#include "AdjointDiff.hpp" #include "StateVector.hpp" #include "pybind11/complex.h" #include "pybind11/numpy.h" @@ -20,6 +23,7 @@ /// @cond DEV namespace { +using namespace Pennylane::Algorithms; using Pennylane::StateVector; using std::complex; using std::string; @@ -459,6 +463,48 @@ template class StateVecBinder : public StateVector { } }; + +/** + * @brief Binding class for exposing C++ AdjointJaconian class to Python. + * + * @tparam fp_t Floating point precision type. + */ +template class AdjJacBinder : public AdjointJacobian { + private: + using ObsTuple = std::tuple< + std::vector>, + std::vector>, + std::vector> + >; + using OpsTuple = std::tuple< + std::vector, + std::vector>, + std::vector>, + std::vector, + std::vector>> + >; + +//names, params, wires, inverses, mats + public: + explicit AdjJacBinder() + : AdjointJacobian() {} + + void adjoint_jacobian_py(const ObsTuple& obs, const OpsTuple& ops, const std::vector& trainableParamIndices, size_t num_params){ + //std::get<0>(obs) + + } +}; + +/**** + * + * jac = adj.adjoint_jacobian( + *obs_serialized, *ops_serialized, tape.trainable_params, tape.num_params + ) + * + * + / + + /** * @brief Templated class to build all required precisions for Python module. * @@ -471,7 +517,8 @@ void lightning_class_bindings(py::module &m) { // Enable module name to be based on size of complex datatype const std::string bitsize = std::to_string(sizeof(std::complex) * 8); - const std::string class_name = "StateVectorC" + bitsize; + std::string class_name = "StateVectorC" + bitsize; + py::class_>(m, class_name.c_str()) .def(py::init< py::array_t, @@ -624,6 +671,40 @@ void lightning_class_bindings(py::module &m) { const std::vector &>( &StateVecBinder::template applyCRot), "Apply the CRot gate."); + + class_name = "ObsStructC" + bitsize; + py::class_>(m, class_name.c_str()) + .def(py::init< + const std::vector&, + const std::vector>&, + const std::vector>&>()); + + class_name = "OpsStructC" + bitsize; + py::class_>(m, class_name.c_str()) + .def(py::init< + const std::vector&, + const std::vector>&, + const std::vector>&, + const std::vector&, + const std::vector>>& >()); + + class_name = "AdjointJacobianC" + bitsize; + py::class_>(m, class_name.c_str()) + .def(py::init<>()) + .def("create_obs", &AdjointJacobian::createObs) + .def("create_ops_list", &AdjointJacobian::createOpsData) + .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) + .def("adjoint_jacobian", + []( AdjointJacobian& adj, + const StateVecBinder &sv, + const std::vector> &observables, + const OpsData &operations, + const vector &trainableParams, + size_t num_params) { + std::vector jac(num_params*observables.size()); + adj.adjointJacobian(sv.getData(), sv.getLength(), jac, observables, operations, trainableParams, num_params); + return jac; + }); } /** @@ -631,6 +712,7 @@ void lightning_class_bindings(py::module &m) { */ PYBIND11_MODULE(lightning_qubit_ops, m) { // Suppress doxygen autogenerated signatures + py::options options; options.disable_function_signatures(); diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index bed6fe4648..dd3329d73a 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -161,6 +161,65 @@ void applyGeneratorControlledPhaseShift(Pennylane::StateVector &sv, namespace Pennylane { namespace Algorithms { +/** + * @brief Utility struct for a observable operations used by AdjointJacobian + * class. + * + */ +template struct ObsDatum { + const std::vector obs_name_; + const std::vector> obs_params_; + const std::vector> obs_wires_; + ObsDatum(const std::vector &obs_name, + const std::vector> &obs_params, + const std::vector> &obs_wires) + : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ + obs_wires} {}; + size_t getSize() const { return obs_name_.size(); } + const std::vector &getObsName() const { return obs_name_; } + const std::vector> &getObsParams() const { + return obs_params_; + } + const std::vector> &getObsWires() const { + return obs_wires_; + } +}; + +/** + * @brief Utility class for encapsulating operations used by AdjointJacobian + * class. + * + */ +template struct OpsData { + const std::vector ops_name_; + const std::vector> ops_params_; + const std::vector> ops_wires_; + const std::vector ops_inverses_; + const std::vector>> ops_matrices_; + + OpsData( + const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector>> &ops_matrices = {{}}) + : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, + ops_inverses_{ops_inverses}, ops_matrices_{ops_matrices} {}; + + size_t getSize() const { return ops_name_.size(); } + const std::vector &getOpsName() const { return ops_name_; } + const std::vector> &getOpsParams() const { + return ops_params_; + } + const std::vector> &getOpsWires() const { + return ops_wires_; + } + const std::vector &getOpsInverses() const { return ops_inverses_; } + const std::vector>> &getOpsMatrices() const { + return ops_matrices_; + } +}; + template class AdjointJacobian { private: typedef void (*GeneratorFunc)( @@ -183,85 +242,30 @@ template class AdjointJacobian { {"CRX", -0.5}, {"CRY", -0.5}, {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; - /** - * @brief Utility struct for a observable operations - * - */ - struct ObsDatum { - const std::vector obs_name_; - const std::vector> obs_params_; - const std::vector> obs_wires_; - ObsDatum(const std::vector &obs_name, - const std::vector> &obs_params, - const std::vector> &obs_wires) - : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ - obs_wires} {}; - size_t getSize() const { return obs_name_.size(); } - const std::vector &getObsName() const { return obs_name_; } - const std::vector> &getObsParams() const { - return obs_params_; - } - const std::vector> &getObsWires() const { - return obs_wires_; - } - }; - struct OpsData { - const std::vector ops_name_; - const std::vector> ops_params_; - const std::vector> ops_wires_; - const std::vector ops_inverses_; - const std::vector>> ops_matrices_; - - OpsData(const std::vector &ops_name, - const std::vector> &ops_params, - const std::vector> &ops_wires, - const std::vector &ops_inverses, - const std::vector>> &ops_matrices = - {{}}) - : ops_name_{ops_name}, ops_params_{ops_params}, - ops_wires_{ops_wires}, ops_inverses_{ops_inverses}, - ops_matrices_{ops_matrices} {}; - - size_t getSize() const { return ops_name_.size(); } - const std::vector &getOpsName() const { return ops_name_; } - const std::vector> &getOpsParams() const { - return ops_params_; - } - const std::vector> &getOpsWires() const { - return ops_wires_; - } - const std::vector &getOpsInverses() const { - return ops_inverses_; - } - const std::vector>> & - getOpsMatrices() const { - return ops_matrices_; - } - }; - public: AdjointJacobian() {} - const ObsDatum + const ObsDatum createObs(const std::vector &obs_name, const std::vector> &obs_params, const std::vector> &obs_wires) { - return ObsDatum(obs_name, obs_params, obs_wires); + return ObsDatum(obs_name, obs_params, obs_wires); } - const OpsData createOpsData( + const OpsData createOpsData( const std::vector &ops_name, const std::vector> &ops_params, const std::vector> &ops_wires, const std::vector &ops_inverses, const std::vector>> &ops_matrices = {{}}) { - return OpsData(ops_name, ops_params, ops_wires, ops_inverses, - ops_matrices); + return OpsData(ops_name, ops_params, ops_wires, ops_inverses, + ops_matrices); } - void adjointJacobian(StateVector &psi, std::vector &jac, - const std::vector &observables, - const OpsData &operations, + void adjointJacobian(const std::complex *psi, size_t num_elements, + std::vector &jac, + const std::vector> &observables, + const OpsData &operations, const vector &trainableParams, size_t num_params) { @@ -269,13 +273,10 @@ template class AdjointJacobian { int trainableParamNumber = trainableParams.size() - 1; int current_param_idx = num_params - 1; - const size_t num_elements = psi.getLength(); - // 1. Copy the input state, create lambda std::unique_ptr[]> SV_lambda_data( new std::complex[num_elements]); - std::copy(psi.getData(), psi.getData() + num_elements, - SV_lambda_data.get()); + std::copy(psi, psi + num_elements, SV_lambda_data.get()); StateVector SV_lambda(SV_lambda_data.get(), num_elements); // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 3bb301e7e7..73d1ee70f1 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -55,7 +55,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {obs}, ops, {0}, 1); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, {0}, 1); CAPTURE(jacobian); CHECK(-sin(p) == Approx(jacobian.front())); } @@ -77,7 +78,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - adj.adjointJacobian(psi, jacobian, {obs}, ops, {0}, 1); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, {0}, 1); CAPTURE(jacobian); CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); @@ -99,8 +101,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { auto ops = adj.createOpsData({"RX"}, {{param[0]}}, {{0}}, {false}); - adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, ops, {0}, - num_params); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, + {obs1, obs2, obs3}, ops, {0}, num_params); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); @@ -123,8 +125,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {{param[0]}, {param[1]}, {param[2]}}, {{0}, {1}, {2}}, {false, false, false}); - adj.adjointJacobian(psi, jacobian, {obs1, obs2, obs3}, ops, {0, 1, 2}, - num_params); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, + {obs1, obs2, obs3}, ops, {0, 1, 2}, num_params); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); @@ -145,7 +147,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {{param[0]}, {param[1]}, {param[2]}}, {{0}, {1}, {2}}, {false, false, false}); - adj.adjointJacobian(psi, jacobian, {obs}, ops, {0, 1, 2}, num_params); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, {0, 1, 2}, num_params); CAPTURE(jacobian); for (size_t i = 0; i < num_params; i++) { CHECK(-sin(param[i]) == From f5e029a1cc0e0a2916a75feb4fd065966a14d4b3 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 16:16:16 +0100 Subject: [PATCH 23/72] Lint and add defines --- pennylane_lightning/src/Bindings.cpp | 94 +++++++++---------- .../src/tests/Test_AdjDiff.cpp | 2 + setup.py | 1 + 3 files changed, 48 insertions(+), 49 deletions(-) diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/Bindings.cpp index a581d5300e..a465198cca 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/Bindings.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include "AdjointDiff.hpp" #include "StateVector.hpp" @@ -463,45 +463,41 @@ template class StateVecBinder : public StateVector { } }; - /** * @brief Binding class for exposing C++ AdjointJaconian class to Python. * * @tparam fp_t Floating point precision type. */ -template class AdjJacBinder : public AdjointJacobian { - private: - using ObsTuple = std::tuple< - std::vector>, - std::vector>, - std::vector> - >; - using OpsTuple = std::tuple< - std::vector, - std::vector>, - std::vector>, - std::vector, - std::vector>> - >; - -//names, params, wires, inverses, mats - public: - explicit AdjJacBinder() - : AdjointJacobian() {} - - void adjoint_jacobian_py(const ObsTuple& obs, const OpsTuple& ops, const std::vector& trainableParamIndices, size_t num_params){ - //std::get<0>(obs) +template +class AdjJacBinder : public AdjointJacobian { + private: + using ObsTuple = std::tuple>, + std::vector>, + std::vector>>; + using OpsTuple = + std::tuple, std::vector>, + std::vector>, std::vector, + std::vector>>>; + + // names, params, wires, inverses, mats + public: + explicit AdjJacBinder() : AdjointJacobian() {} + void adjoint_jacobian_py(const ObsTuple &obs, const OpsTuple &ops, + const std::vector &trainableParamIndices, + size_t num_params) { + // std::get<0>(obs) } }; /**** - * + * * jac = adj.adjoint_jacobian( - *obs_serialized, *ops_serialized, tape.trainable_params, tape.num_params - ) - * - * + *obs_serialized, *ops_serialized, tape.trainable_params, +tape.num_params + ) + * + * / @@ -671,22 +667,21 @@ void lightning_class_bindings(py::module &m) { const std::vector &>( &StateVecBinder::template applyCRot), "Apply the CRot gate."); - + class_name = "ObsStructC" + bitsize; py::class_>(m, class_name.c_str()) - .def(py::init< - const std::vector&, - const std::vector>&, - const std::vector>&>()); + .def(py::init &, + const std::vector> &, + const std::vector> &>()); class_name = "OpsStructC" + bitsize; py::class_>(m, class_name.c_str()) .def(py::init< - const std::vector&, - const std::vector>&, - const std::vector>&, - const std::vector&, - const std::vector>>& >()); + const std::vector &, + const std::vector> &, + const std::vector> &, + const std::vector &, + const std::vector>> &>()); class_name = "AdjointJacobianC" + bitsize; py::class_>(m, class_name.c_str()) @@ -694,17 +689,18 @@ void lightning_class_bindings(py::module &m) { .def("create_obs", &AdjointJacobian::createObs) .def("create_ops_list", &AdjointJacobian::createOpsData) .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) - .def("adjoint_jacobian", - []( AdjointJacobian& adj, + .def("adjoint_jacobian", + [](AdjointJacobian &adj, const StateVecBinder &sv, - const std::vector> &observables, - const OpsData &operations, - const vector &trainableParams, - size_t num_params) { - std::vector jac(num_params*observables.size()); - adj.adjointJacobian(sv.getData(), sv.getLength(), jac, observables, operations, trainableParams, num_params); - return jac; - }); + const std::vector> &observables, + const OpsData &operations, + const vector &trainableParams, size_t num_params) { + std::vector jac(num_params * observables.size()); + adj.adjointJacobian(sv.getData(), sv.getLength(), jac, + observables, operations, trainableParams, + num_params); + return jac; + }); } /** diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 73d1ee70f1..900d35d623 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -1,3 +1,5 @@ +#define _USE_MATH_DEFINES + #include #include #include diff --git a/setup.py b/setup.py index 9c7b8d0807..b9f296f08e 100644 --- a/setup.py +++ b/setup.py @@ -151,6 +151,7 @@ def build_extensions(self): "lightning_qubit_ops", sources=[ "pennylane_lightning/src/simulator/StateVector.cpp", + "pennylane_lightning/src/algorithms/AdjointDiff.cpp", "pennylane_lightning/src/Bindings.cpp", ], depends=[ From ca706c28a92d773b1a8c802d1d70302e3a3cf037 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 16:23:37 +0100 Subject: [PATCH 24/72] Fix testing binary location --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5d21d0859..d4524771cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: cmake . -BBuild -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=1 cmake --build ./Build mkdir -p ./Build/tests/results - ./Build/tests/runner --order lex --reporter junit --out ./Build/tests/results/report.xml + ./Build/pennylane_lightning/src/tests/runner --order lex --reporter junit --out ./Build/tests/results/report.xml - name: Upload test results uses: actions/upload-artifact@v2 From 5df74a075d64ee9e22689845b403c7007191bdca Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 25 Aug 2021 16:50:58 +0100 Subject: [PATCH 25/72] Attempt to appease codefactor --- .../src/algorithms/AdjointDiff.hpp | 89 ++++++++++--------- .../src/simulator/StateVector.hpp | 2 +- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index dd3329d73a..9a7dbd34ce 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -19,50 +19,6 @@ namespace { using namespace Pennylane::Util; -template -class SVUnique : public Pennylane::StateVector { - private: - std::unique_ptr> arr_; - size_t length_; - size_t num_qubits_; - - public: - SVUnique(size_t data_size) - : arr_{new std::complex[data_size]}, length_{data_size}, - num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), - data_size} {} - - SVUnique(const Pennylane::StateVector &sv) - : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - length_ = sv.getLength(); - num_qubits_ = sv.getNumQubits(); - }; - - SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - }; - - std::complex *getData() { return arr_.get(); } - std::complex *getData() const { return arr_.get(); } -}; - -template -inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { - const size_t num_qubits = sv.getNumQubits(); - const size_t length = sv.getLength(); - const auto data_ptr = sv.getData(); - out << "num_qubits=" << num_qubits << std::endl; - out << "data=["; - out << data_ptr[0]; - for (size_t i = 1; i < length - 1; i++) { - out << "," << data_ptr[i]; - } - out << "," << data_ptr[length - 1] << "]"; - - return out; -} - template static constexpr std::vector> getP00() { return {ONE(), ZERO(), ZERO(), ZERO()}; } @@ -508,6 +464,51 @@ template class AdjointJacobian { */ /* + +template +class SVUnique : public Pennylane::StateVector { + private: + std::unique_ptr> arr_; + size_t length_; + size_t num_qubits_; + + public: + SVUnique(size_t data_size) + : arr_{new std::complex[data_size]}, length_{data_size}, + num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), + data_size} {} + + SVUnique(const Pennylane::StateVector &sv) + : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + length_ = sv.getLength(); + num_qubits_ = sv.getNumQubits(); + }; + + SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + }; + + std::complex *getData() { return arr_.get(); } + std::complex *getData() const { return arr_.get(); } +}; + +template +inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { + const size_t num_qubits = sv.getNumQubits(); + const size_t length = sv.getLength(); + const auto data_ptr = sv.getData(); + out << "num_qubits=" << num_qubits << std::endl; + out << "data=["; + out << data_ptr[0]; + for (size_t i = 1; i < length - 1; i++) { + out << "," << data_ptr[i]; + } + out << "," << data_ptr[length - 1] << "]"; + + return out; +} + std::vector adj_jac(const StateVector &psi, const vector &observables, const vector> &obsWires, diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 7d3bbc585d..74fb02f904 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -19,7 +19,7 @@ #pragma once // Required for compilation with MSVC -#define _USE_MATH_DEFINES +#define _USE_MATH_DEFINES #include #include From f154807392112b143644316aabdfd502dab3ba88 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Thu, 26 Aug 2021 16:28:30 +0100 Subject: [PATCH 26/72] Fix memory alignment errors on output for adj --- .../src/algorithms/AdjointDiff.hpp | 269 ++++++------------ .../src/simulator/StateVector.hpp | 27 +- .../src/tests/Test_AdjDiff.cpp | 58 +++- .../src/tests/Test_StateVector_Nonparam.cpp | 71 +++-- .../src/tests/Test_StateVector_Param.cpp | 6 +- pennylane_lightning/src/tests/Test_Util.cpp | 10 +- 6 files changed, 205 insertions(+), 236 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 9a7dbd34ce..f33b707d33 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -112,6 +112,34 @@ void applyGeneratorControlledPhaseShift(Pennylane::StateVector &sv, } } +template +class SVUnique : public Pennylane::StateVector { + private: + std::unique_ptr> arr_; + size_t length_; + size_t num_qubits_; + + public: + SVUnique(size_t data_size) + : arr_{new std::complex[data_size]}, length_{data_size}, + num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), + data_size} {} + + SVUnique(const Pennylane::StateVector &sv) + : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + length_ = sv.getLength(); + num_qubits_ = sv.getNumQubits(); + }; + + SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { + std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); + }; + + std::complex *getData() { return arr_.get(); } + std::complex *getData() const { return arr_.get(); } +}; + } // namespace namespace Pennylane { @@ -218,6 +246,11 @@ template class AdjointJacobian { ops_matrices); } + std::vector> + copyStateData(const std::complex *input_state, size_t state_length) { + return {input_state, input_state + state_length}; + } + void adjointJacobian(const std::complex *psi, size_t num_elements, std::vector &jac, const std::vector> &observables, @@ -225,113 +258,74 @@ template class AdjointJacobian { const vector &trainableParams, size_t num_params) { - size_t numObservables = observables.size(); - int trainableParamNumber = trainableParams.size() - 1; - int current_param_idx = num_params - 1; + const size_t num_observables = observables.size(); - // 1. Copy the input state, create lambda - std::unique_ptr[]> SV_lambda_data( - new std::complex[num_elements]); - std::copy(psi, psi + num_elements, SV_lambda_data.get()); - StateVector SV_lambda(SV_lambda_data.get(), num_elements); - - // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda - SV_lambda.applyOperations( - operations.getOpsName(), operations.getOpsWires(), - operations.getOpsInverses(), operations.getOpsParams()); - - // 3-4. Copy lambda and apply the observables - // SV_lambda becomes |phi> - - std::unique_ptr[]> phi_data( - new std::complex[num_elements]); - std::copy(SV_lambda.getData(), SV_lambda.getData() + num_elements, - phi_data.get()); - StateVector phi_1(phi_data.get(), num_elements); + auto lambda_data = copyStateData(psi, num_elements); + StateVector lambda(lambda_data.data(), num_elements); - std::vector> lambdas; - lambdas.reserve(numObservables); - std::vector[]>> lambdas_data; - lambdas_data.reserve(numObservables); - for (size_t i = 0; i < numObservables; i++) { - lambdas_data.emplace_back(new std::complex[num_elements]); - lambdas.emplace_back( - StateVector(lambdas_data[i].get(), num_elements)); + for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); + op_idx++) { + lambda.applyOperation(operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); } - - //#pragma omp parallel for - for (size_t i = 0; i < numObservables; i++) { - // copy |phi> and apply observables one at a time - std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, - lambdas_data[i].get()); - - for (size_t j = 0; j < observables[i].getSize(); j++) { - lambdas[i].applyOperation(observables[i].getObsName()[j], - observables[i].getObsWires()[j], - false, - observables[i].getObsParams()[j]); + std::vector>> H_lambda_data; + std::vector> H_lambda; + + for (size_t h_i = 0; h_i < num_observables; h_i++) { + H_lambda_data.push_back(lambda_data); + H_lambda.push_back( + StateVector(H_lambda_data[h_i].data(), num_elements)); + + for (size_t j = 0; j < observables[h_i].getSize(); j++) { + H_lambda[h_i].applyOperation( + observables[h_i].getObsName()[j], + observables[h_i].getObsWires()[j], false, + observables[h_i].getObsParams()[j]); } } - // replace with reverse iterator over values? - for (int i = operations.getOpsName().size() - 1; i >= 0; i--) { - - if (operations.getOpsParams()[i].size() > 1) { - throw std::invalid_argument( - "The operation is not supported using " - "the adjoint differentiation method"); - } else if ((operations.getOpsName()[i] != "QubitStateVector") && - (operations.getOpsName()[i] != "BasisState")) { - - std::unique_ptr[]> mu_data( - new std::complex[num_elements]); - std::copy(phi_1.getData(), phi_1.getData() + num_elements, - mu_data.get()); - - StateVector mu(mu_data.get(), num_elements); - - // create |phi'> = Uj*|phi> - phi_1.applyOperation(operations.getOpsName()[i], - operations.getOpsWires()[i], - !operations.getOpsInverses()[i], - operations.getOpsParams()[i]); - - // We have a parametrized gate - if (!operations.getOpsParams()[i].empty()) { - - if (std::find(trainableParams.begin(), - trainableParams.end(), - current_param_idx) != trainableParams.end()) { - - // create iH|phi> = d/d dUj/dtheta Uj* |phi> = - // dUj/dtheta|phi'> - const T scalingFactor = - scaling_factors.at(operations.getOpsName()[i]); - - generator_map.at(operations.getOpsName()[i])( - mu, operations.getOpsWires()[i]); - for (size_t j = 0; j < lambdas.size(); j++) { - - std::complex sum = - innerProdC(lambdas[j].getData(), mu.getData(), - num_elements); - jac[j * trainableParams.size() + - trainableParamNumber] = - -2 * scalingFactor * std::imag(sum); - } - trainableParamNumber--; - } - current_param_idx--; - } - - for (size_t j = 0; j < lambdas.size(); j++) { - lambdas[j].applyOperation(operations.getOpsName()[i], - operations.getOpsWires()[i], - !operations.getOpsInverses()[i], - operations.getOpsParams()[i]); + std::vector> phi_data(lambda_data); + StateVector phi(phi_data.data(), num_elements); + + for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; + op_idx--) { + std::vector> mu_data(lambda_data); + StateVector mu(mu_data.data(), num_elements); + + phi.applyOperation(operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); + + generator_map.at(operations.getOpsName()[op_idx])( + mu, operations.getOpsWires()[op_idx]); + const T scalingFactor = + scaling_factors.at(operations.getOpsName()[op_idx]); + + for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + std::cout << "IDX=" << op_idx * num_observables + obs_idx + << " Exp=" + << -2 * scalingFactor * + std::imag( + innerProdC(H_lambda[obs_idx].getData(), + mu.getData(), num_elements)) + << std::endl; + jac[op_idx * num_observables + obs_idx] = + -2 * scalingFactor * + std::imag(innerProdC(H_lambda[obs_idx].getData(), + mu.getData(), num_elements)); + if (op_idx > 0) { + H_lambda[obs_idx].applyOperation( + operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); } } } + // return jacobian; } }; @@ -465,33 +459,6 @@ template class AdjointJacobian { /* -template -class SVUnique : public Pennylane::StateVector { - private: - std::unique_ptr> arr_; - size_t length_; - size_t num_qubits_; - - public: - SVUnique(size_t data_size) - : arr_{new std::complex[data_size]}, length_{data_size}, - num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), - data_size} {} - - SVUnique(const Pennylane::StateVector &sv) - : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - length_ = sv.getLength(); - num_qubits_ = sv.getNumQubits(); - }; - - SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - }; - - std::complex *getData() { return arr_.get(); } - std::complex *getData() const { return arr_.get(); } -}; template inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { @@ -509,54 +476,4 @@ inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { return out; } - std::vector adj_jac(const StateVector &psi, - const vector &observables, - const vector> &obsWires, - const vector &operations, - const vector> &opWires, - const vector &opInverse, - const vector> &opParams) { - - const size_t num_observables = observables.size(); - const size_t num_params = opParams.size(); - const size_t num_elements = psi.getLength(); - - std::vector jacobian(observables.size()); - SVUnique lambda(psi); - // std::cout << lambda << std::endl; - - for (size_t op_idx = 0; op_idx < operations.size(); op_idx++) { - lambda.applyOperation(operations[op_idx], opWires[op_idx], - opInverse[op_idx], opParams[op_idx]); - } - std::vector> H_lambda; - - for (size_t h_i = 0; h_i < num_observables; h_i++) { - H_lambda.push_back(lambda); - H_lambda[h_i].applyOperation(observables[h_i], obsWires[h_i], -false, - {}); - // std::cout << H_lambda[h_i] << std::endl; - } - - SVUnique phi(lambda); - for (int op_idx = operations.size() - 1; op_idx >= 0; op_idx--) { - phi.applyOperation(operations[op_idx], opWires[op_idx], - !opInverse[op_idx], opParams[op_idx]); - SVUnique mu(phi); - generator_map.at(operations[op_idx])(mu, opWires[op_idx]); - const T scalingFactor = scaling_factors.at(operations[op_idx]); - for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - jacobian[obs_idx * operations.size() + obs_idx] = - -2 * scalingFactor * - std::imag(innerProdC(H_lambda[obs_idx].getData(), - mu.getData(), num_elements)); - if (op_idx > 0) { - H_lambda[obs_idx].applyOperation( - operations[op_idx], opWires[op_idx], -!opInverse[op_idx], opParams[op_idx]); - } - } - } - return jacobian; - }*/ +*/ diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 74fb02f904..9fae8dac72 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -19,7 +19,7 @@ #pragma once // Required for compilation with MSVC -#define _USE_MATH_DEFINES +#define _USE_MATH_DEFINES // for C++ #include #include @@ -30,6 +30,8 @@ #include #include +#include + #include "Gates.hpp" #include "Util.hpp" @@ -196,6 +198,21 @@ template class StateVector { std::to_string(gate_wires_.at(opName)) + " wires, but " + std::to_string(wires.size()) + " were supplied"); + std::cout << "[SVADDR::" << arr_ << "]"; + std::cout << "[GATE_CALL::" << opName << "][WIRES::"; + for (auto &w : wires) { + std::cout << w << ","; + } + std::cout << "][INVERT::" << inverse << "][PARAMS::"; + if (!params.empty()) { + for (auto &p : params) { + std::cout << p << ","; + } + } else { + std::cout << "EMPTY"; + } + std::cout << "]" << std::endl; + const vector internalIndices = generateBitPatterns(wires); const vector externalWires = getIndicesAfterExclusion(wires); const vector externalIndices = @@ -215,7 +232,6 @@ template class StateVector { void applyOperation(const std::vector &matrix, const vector &wires, bool inverse = false, [[maybe_unused]] const vector ¶ms = {}) { - auto dim = Util::dimSize(matrix); if (dim != wires.size()) @@ -572,10 +588,11 @@ template class StateVector { void applyRX(const vector &indices, const vector &externalIndices, bool inverse, Param_t angle) { - const CFP_t c(std::cos(angle / 2), 0); - const CFP_t js = (inverse == true) ? CFP_t(0, -std::sin(-angle / 2)) - : CFP_t(0, std::sin(-angle / 2)); + const Param_t angle_ = (inverse == true) ? -angle : angle; + const CFP_t c(std::cos(angle_ / 2), 0); + + const CFP_t js(0, -std::sin(angle_ / 2)); for (const size_t &externalIndex : externalIndices) { CFP_t *shiftedState = arr_ + externalIndex; diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 900d35d623..8a1699ce47 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -39,7 +39,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { AdjointJacobian adj; // std::vector param{1, -2, 1.623, -0.051, 0}; - std::vector param{M_PI, M_PI_2, M_PI / 3}; + std::vector param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; SECTION("RX gradient") { const size_t num_qubits = 1; @@ -87,10 +87,10 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); } } - SECTION("Single RX gradient, single expval per wire") { - const size_t num_qubits = 3; + SECTION("Single RX gradient, 2 expval") { + const size_t num_qubits = 2; const size_t num_params = 1; - const size_t num_obs = 3; + const size_t num_obs = 2; std::vector jacobian(num_obs * num_params, 0.0); std::vector> cdata(0b1 << num_qubits); @@ -99,12 +99,11 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); - auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); auto ops = adj.createOpsData({"RX"}, {{param[0]}}, {{0}}, {false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, - {obs1, obs2, obs3}, ops, {0}, num_params); + {obs1, obs2}, ops, {0}, num_params); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); @@ -143,7 +142,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}}, + auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}, {}, {}}, {{0}, {1}, {2}}); auto ops = adj.createOpsData({"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, @@ -152,9 +151,46 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, ops, {0, 1, 2}, num_params); CAPTURE(jacobian); - for (size_t i = 0; i < num_params; i++) { - CHECK(-sin(param[i]) == - Approx(jacobian[i * num_params + i]).margin(1e-7)); - } + + // Computed with parameter shift + CHECK(-0.1755096592645253 == Approx(jacobian[0]).margin(1e-7)); + CHECK(0.26478810666384334 == Approx(jacobian[1]).margin(1e-7)); + CHECK(-0.6312451595102775 == Approx(jacobian[2]).margin(1e-7)); + } + + SECTION("Mixed gradient, tensor expval") { + const size_t num_qubits = 3; + const size_t num_params = 6; + const size_t num_obs = 1; + std::vector jacobian(num_obs * num_params, 0.0); + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}, {}, {}}, + {{0}, {1}, {2}}); + auto ops = + adj.createOpsData({"RX", "RX", "RX", "RY", "RY", "RY"}, + {{param[0]}, + {param[1]}, + {param[2]}, + {param[0]}, + {param[1]}, + {param[2]}}, + {{0}, {1}, {2}, {0}, {1}, {2}}, + {false, false, false, false, false, false}); + + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, {0, 1, 2, 3, 4, 5}, num_params); + CAPTURE(jacobian); + + // Computed with parameter shift + CHECK(0.06396442 == Approx(jacobian[0]).margin(1e-7)); + CHECK(-0.09650191 == Approx(jacobian[1]).margin(1e-7)); + CHECK(0.23005702 == Approx(jacobian[2]).margin(1e-7)); + CHECK(0.06396442 == Approx(jacobian[3]).margin(1e-7)); + CHECK(-0.09650191 == Approx(jacobian[4]).margin(1e-7)); + CHECK(0.23005702 == Approx(jacobian[5]).margin(1e-7)); } } diff --git a/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp b/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp index cfe8cbccae..c472e3deb0 100644 --- a/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp +++ b/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp @@ -183,7 +183,7 @@ TEMPLATE_TEST_CASE("StateVector::applyHadamard", "[StateVector_Nonparam]", CHECK(svdat.cdata[0] == cp_t{1, 0}); svdat.sv.applyHadamard(int_idx, ext_idx, false); - cp_t expected = {1 / std::sqrt(2), 0}; + cp_t expected = (1 / std::sqrt(2), 0); CHECK(expected.real() == Approx(svdat.cdata[0].real())); CHECK(expected.imag() == Approx(svdat.cdata[0].imag())); @@ -203,7 +203,7 @@ TEMPLATE_TEST_CASE("StateVector::applyHadamard", "[StateVector_Nonparam]", CHECK(svdat.cdata[0] == cp_t{1, 0}); svdat.sv.applyOperation("Hadamard", {index}, false); - cp_t expected = {1 / std::sqrt(2), 0}; + cp_t expected = (1 / std::sqrt(2), 0); CHECK(expected.real() == Approx(svdat.cdata[0].real())); CHECK(expected.imag() == Approx(svdat.cdata[0].imag())); @@ -379,8 +379,8 @@ TEMPLATE_TEST_CASE("StateVector::applyT", "[StateVector_Nonparam]", float, svdat.sv.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - cp_t r = {1 / (2 * std::sqrt(2)), 0}; - cp_t i = {1.0 / 4, 1.0 / 4}; + cp_t r = (1 / (2 * std::sqrt(2)), 0); + cp_t i = (1.0 / 4, 1.0 / 4); const std::vector> expected_results = { {r, r, r, r, i, i, i, i}, @@ -467,8 +467,8 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, std::vector expected{ Util::ZERO(), Util::ZERO(), Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO()}; + (1 / sqrt(2), 0), Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -485,7 +485,7 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, SECTION("SWAP0,2 |+10> -> |01+>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, {1 / sqrt(2), 0}, + (1 / sqrt(2), 0), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), Util::ZERO()}; @@ -501,9 +501,9 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, } SECTION("SWAP1,2 |+10> -> |+01>") { std::vector expected{ - Util::ZERO(), {1 / sqrt(2), 0}, + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}, + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO()}; SVData svdat12{num_qubits, init_state}; @@ -522,8 +522,8 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, std::vector expected{ Util::ZERO(), Util::ZERO(), Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO()}; + (1 / sqrt(2), 0), Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -538,7 +538,7 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, SECTION("SWAP0,2 |+10> -> |01+>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, {1 / sqrt(2), 0}, + (1 / sqrt(2), 0), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), Util::ZERO()}; @@ -553,9 +553,9 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, } SECTION("SWAP1,2 |+10> -> |+01>") { std::vector expected{ - Util::ZERO(), {1 / sqrt(2), 0}, + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}, + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO()}; SVData svdat12{num_qubits, init_state}; @@ -582,21 +582,18 @@ TEMPLATE_TEST_CASE("StateVector::applyCZ", "[StateVector_Nonparam]", float, const auto init_state = svdat.cdata; SECTION("Apply directly") { - CHECK(svdat.cdata == std::vector{Util::ZERO(), - Util::ZERO(), - {1 / sqrt(2), 0}, - Util::ZERO(), - Util::ZERO(), - Util::ZERO(), - {1 / sqrt(2), 0}, - Util::ZERO()}); + CHECK(svdat.cdata == + std::vector{Util::ZERO(), Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), + Util::ZERO(), Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO()}); SECTION("CZ0,1 |+10> -> |-10>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), - {-1 / sqrt(2), 0}, Util::ZERO()}; + (-1 / sqrt(2), 0), Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -642,9 +639,9 @@ TEMPLATE_TEST_CASE("StateVector::applyCZ", "[StateVector_Nonparam]", float, SECTION("CZ0,1 |+10> -> |1+0>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), - {-1 / sqrt(2), 0}, Util::ZERO()}; + (-1 / sqrt(2), 0), Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -673,9 +670,9 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Toffoli 0,1,2 |+10> -> |010> + |111>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}}; + Util::ZERO(), (1 / sqrt(2), 0)}; SVData svdat012{num_qubits, init_state}; @@ -689,9 +686,9 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Toffoli 1,0,2 |+10> -> |010> + |111>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}}; + Util::ZERO(), (1 / sqrt(2), 0)}; SVData svdat102{num_qubits, init_state}; @@ -728,9 +725,9 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Toffoli [0,1,2], [1,0,2] |+10> -> |+1+>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), + (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}}; + Util::ZERO(), (1 / sqrt(2), 0)}; SVData svdat012{num_qubits, init_state}; SVData svdat102{num_qubits, init_state}; @@ -759,8 +756,8 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}, + (1 / sqrt(2), 0), Util::ZERO(), + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO()}; SVData svdat012{num_qubits, init_state}; @@ -773,7 +770,7 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, SECTION("CSWAP 1,0,2 |+10> -> |01+>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, {1 / sqrt(2), 0}, + (1 / sqrt(2), 0), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), Util::ZERO(), Util::ZERO()}; @@ -799,8 +796,8 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { std::vector expected{ Util::ZERO(), Util::ZERO(), - {1 / sqrt(2), 0}, Util::ZERO(), - Util::ZERO(), {1 / sqrt(2), 0}, + (1 / sqrt(2), 0), Util::ZERO(), + Util::ZERO(), (1 / sqrt(2), 0), Util::ZERO(), Util::ZERO()}; SVData svdat012{num_qubits, init_state}; diff --git a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp index 5fea884326..3e12f9b78c 100644 --- a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp +++ b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp @@ -138,7 +138,7 @@ TEMPLATE_TEST_CASE("StateVector::applyRZ", "[StateVector_Param]", float, {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.2, 0.7, 2.9}; - const cp_t coef = {1 / (2 * std::sqrt(2)), 0}; + const cp_t coef = (1 / (2 * std::sqrt(2)), 0); std::vector> rz_data; for (auto &a : angles) { @@ -198,7 +198,7 @@ TEMPLATE_TEST_CASE("StateVector::applyPhaseShift", "[StateVector_Param]", float, {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 0.8, 2.4}; - const cp_t coef = {1 / (2 * std::sqrt(2)), 0}; + const cp_t coef = (1 / (2 * std::sqrt(2)), 0); std::vector> ps_data; for (auto &a : angles) { @@ -259,7 +259,7 @@ TEMPLATE_TEST_CASE("StateVector::applyControlledPhaseShift", {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 2.4}; - const cp_t coef = {1 / (2 * std::sqrt(2)), 0}; + const cp_t coef = (1 / (2 * std::sqrt(2)), 0); std::vector> ps_data; for (auto &a : angles) { diff --git a/pennylane_lightning/src/tests/Test_Util.cpp b/pennylane_lightning/src/tests/Test_Util.cpp index ece243a89e..e7376cf42f 100644 --- a/pennylane_lightning/src/tests/Test_Util.cpp +++ b/pennylane_lightning/src/tests/Test_Util.cpp @@ -123,20 +123,22 @@ TEMPLATE_TEST_CASE("Utility math functions", "[Util]", float, double) { std::vector> data2(1UL << i, {1, 1}); std::complex expected_result(1UL << (i + 1), 0); std::complex result = Util::innerProdC(data1, data2); - + CAPTURE(result); + CAPTURE(expected_result); CHECK(isApproxEqual(result, expected_result)); } } SECTION("Random complex") { - std::vector> data1{ + std::vector> data2{ {0, -0.479426}, {0, 0}, {2.77556e-17, 0}, {0, 0}, {0.877583, 0}, {0, 0}, {0, 0}, {0, 0}}; - std::vector> data2{ + std::vector> data1{ {0.326417, 0}, {-0, 0.343918}, {0, 0.508364}, {-0.53562, -0}, {0, -0.178322}, {0.187883, -0}, {0.277721, 0}, {-0, 0.292611}}; - std::complex expected_result(0, 4.40916e-7); + std::complex expected_result(0, -4.40916e-7); std::complex result = Util::innerProdC(data1, data2); CAPTURE(result); + CAPTURE(expected_result); CHECK(isApproxEqual(result, expected_result, 1e-5)); } } From 09fb87dd8366203fb10a88d629d7dbc9bf1135e5 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Thu, 26 Aug 2021 17:42:47 +0100 Subject: [PATCH 27/72] Fix broken tests due to MVP --- .../src/tests/Test_StateVector_Nonparam.cpp | 201 +++++++++++------- .../src/tests/Test_StateVector_Param.cpp | 6 +- 2 files changed, 127 insertions(+), 80 deletions(-) diff --git a/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp b/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp index c472e3deb0..18ed35b7e6 100644 --- a/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp +++ b/pennylane_lightning/src/tests/Test_StateVector_Nonparam.cpp @@ -183,7 +183,7 @@ TEMPLATE_TEST_CASE("StateVector::applyHadamard", "[StateVector_Nonparam]", CHECK(svdat.cdata[0] == cp_t{1, 0}); svdat.sv.applyHadamard(int_idx, ext_idx, false); - cp_t expected = (1 / std::sqrt(2), 0); + cp_t expected(1 / std::sqrt(2), 0); CHECK(expected.real() == Approx(svdat.cdata[0].real())); CHECK(expected.imag() == Approx(svdat.cdata[0].imag())); @@ -203,7 +203,7 @@ TEMPLATE_TEST_CASE("StateVector::applyHadamard", "[StateVector_Nonparam]", CHECK(svdat.cdata[0] == cp_t{1, 0}); svdat.sv.applyOperation("Hadamard", {index}, false); - cp_t expected = (1 / std::sqrt(2), 0); + cp_t expected(1.0 / std::sqrt(2), 0); CHECK(expected.real() == Approx(svdat.cdata[0].real())); CHECK(expected.imag() == Approx(svdat.cdata[0].imag())); @@ -299,8 +299,8 @@ TEMPLATE_TEST_CASE("StateVector::applyPauliZ", "[StateVector_Nonparam]", float, svdat.sv.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - constexpr cp_t p = static_cast(0.5) * Util::INVSQRT2(); - constexpr cp_t m = Util::ConstMult(-1, p); + constexpr cp_t p(static_cast(0.5) * Util::INVSQRT2()); + constexpr cp_t m(Util::ConstMult(-1, p)); const std::vector> expected_results = { {p, p, p, p, m, m, m, m}, @@ -339,8 +339,8 @@ TEMPLATE_TEST_CASE("StateVector::applyS", "[StateVector_Nonparam]", float, svdat.sv.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - constexpr cp_t r = static_cast(0.5) * Util::INVSQRT2(); - constexpr cp_t i = Util::ConstMult(r, Util::IMAG()); + constexpr cp_t r(static_cast(0.5) * Util::INVSQRT2()); + constexpr cp_t i(Util::ConstMult(r, Util::IMAG())); const std::vector> expected_results = { {r, r, r, r, i, i, i, i}, @@ -379,8 +379,8 @@ TEMPLATE_TEST_CASE("StateVector::applyT", "[StateVector_Nonparam]", float, svdat.sv.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, {{0}, {1}, {2}}, {{false}, {false}, {false}}); - cp_t r = (1 / (2 * std::sqrt(2)), 0); - cp_t i = (1.0 / 4, 1.0 / 4); + cp_t r(1.0 / (2.0 * std::sqrt(2)), 0); + cp_t i(1.0 / 4, 1.0 / 4); const std::vector> expected_results = { {r, r, r, r, i, i, i, i}, @@ -464,11 +464,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, Util::INVSQRT2(), Util::ZERO()}); SECTION("SWAP0,1 |+10> -> |1+0>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -483,11 +486,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, } SECTION("SWAP0,2 |+10> -> |01+>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; SVData svdat02{num_qubits, init_state}; SVData svdat20{num_qubits, init_state}; @@ -500,11 +506,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, CHECK(svdat20.cdata == expected); } SECTION("SWAP1,2 |+10> -> |+01>") { - std::vector expected{ - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; SVData svdat12{num_qubits, init_state}; SVData svdat21{num_qubits, init_state}; @@ -519,11 +528,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, } SECTION("Apply using dispatcher") { SECTION("SWAP0,1 |+10> -> |1+0>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -536,11 +548,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, } SECTION("SWAP0,2 |+10> -> |01+>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; SVData svdat02{num_qubits, init_state}; SVData svdat20{num_qubits, init_state}; @@ -552,11 +567,14 @@ TEMPLATE_TEST_CASE("StateVector::applySWAP", "[StateVector_Nonparam]", float, CHECK(svdat20.cdata == expected); } SECTION("SWAP1,2 |+10> -> |+01>") { - std::vector expected{ - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; SVData svdat12{num_qubits, init_state}; SVData svdat21{num_qubits, init_state}; @@ -584,16 +602,21 @@ TEMPLATE_TEST_CASE("StateVector::applyCZ", "[StateVector_Nonparam]", float, SECTION("Apply directly") { CHECK(svdat.cdata == std::vector{Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO()}); + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}); SECTION("CZ0,1 |+10> -> |-10>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - (-1 / sqrt(2), 0), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(-1 / sqrt(2), 0), + Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -637,11 +660,14 @@ TEMPLATE_TEST_CASE("StateVector::applyCZ", "[StateVector_Nonparam]", float, } SECTION("Apply using dispatcher") { SECTION("CZ0,1 |+10> -> |1+0>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - (-1 / sqrt(2), 0), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(-1 / sqrt(2), 0), + Util::ZERO()}; SVData svdat01{num_qubits, init_state}; SVData svdat10{num_qubits, init_state}; @@ -669,10 +695,14 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Apply directly") { SECTION("Toffoli 0,1,2 |+10> -> |010> + |111>") { std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0)}; + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; SVData svdat012{num_qubits, init_state}; @@ -685,10 +715,14 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Toffoli 1,0,2 |+10> -> |010> + |111>") { std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0)}; + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; SVData svdat102{num_qubits, init_state}; @@ -724,10 +758,14 @@ TEMPLATE_TEST_CASE("StateVector::applyToffoli", "[StateVector_Nonparam]", float, SECTION("Apply using dispatcher") { SECTION("Toffoli [0,1,2], [1,0,2] |+10> -> |+1+>") { std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0)}; + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; SVData svdat012{num_qubits, init_state}; SVData svdat102{num_qubits, init_state}; @@ -754,11 +792,14 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, SECTION("Apply directly") { SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; SVData svdat012{num_qubits, init_state}; svdat012.sv.applyCSWAP(svdat.getInternalIndices({0, 1, 2}), @@ -768,11 +809,14 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, } SECTION("CSWAP 1,0,2 |+10> -> |01+>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO(), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; SVData svdat102{num_qubits, init_state}; @@ -794,11 +838,14 @@ TEMPLATE_TEST_CASE("StateVector::applyCSWAP", "[StateVector_Nonparam]", float, } SECTION("Apply using dispatcher") { SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { - std::vector expected{ - Util::ZERO(), Util::ZERO(), - (1 / sqrt(2), 0), Util::ZERO(), - Util::ZERO(), (1 / sqrt(2), 0), - Util::ZERO(), Util::ZERO()}; + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; SVData svdat012{num_qubits, init_state}; svdat012.sv.applyOperation("CSWAP", {0, 1, 2}); diff --git a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp index 3e12f9b78c..0bd52a577b 100644 --- a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp +++ b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp @@ -138,7 +138,7 @@ TEMPLATE_TEST_CASE("StateVector::applyRZ", "[StateVector_Param]", float, {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.2, 0.7, 2.9}; - const cp_t coef = (1 / (2 * std::sqrt(2)), 0); + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); std::vector> rz_data; for (auto &a : angles) { @@ -198,7 +198,7 @@ TEMPLATE_TEST_CASE("StateVector::applyPhaseShift", "[StateVector_Param]", float, {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 0.8, 2.4}; - const cp_t coef = (1 / (2 * std::sqrt(2)), 0); + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); std::vector> ps_data; for (auto &a : angles) { @@ -259,7 +259,7 @@ TEMPLATE_TEST_CASE("StateVector::applyControlledPhaseShift", {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 2.4}; - const cp_t coef = (1 / (2 * std::sqrt(2)), 0); + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); std::vector> ps_data; for (auto &a : angles) { From 75530bdd3797b3fb69edafb6b556e763648adf72 Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Fri, 27 Aug 2021 04:00:05 -0400 Subject: [PATCH 28/72] Add adjoint method to Python frontend (#137) * Add observable serialization * Add to lightning * Update * Add operation serialization * add framework * Apply black * Add tests for obs_has_kernel * Add tests for _serialize_obs * Add wires * Add * Rename to _obs_has_kernel_ * Add tests * Adapt tests to lightning * Update obs * Update serialization * Potential facepalm moment * Revert "Potential facepalm moment" This reverts commit 4230615187a3a73481f40d35236652e65d9469b2. * Revert "Update serialization" This reverts commit 38352c2bf36d3370fced4393bf3fdaf49465f42c. * Revert "Update obs" This reverts commit ee7a377cecb30c88f5c3a701ef967a9741365a35. * Update * Update --- pennylane_lightning/_serialize.py | 131 +++++++ pennylane_lightning/lightning_qubit.py | 53 ++- tests/test_adjoint_jacobian.py | 504 +++++++++++++++++++++++++ tests/test_serialize.py | 308 +++++++++++++++ 4 files changed, 991 insertions(+), 5 deletions(-) create mode 100644 pennylane_lightning/_serialize.py create mode 100644 tests/test_adjoint_jacobian.py create mode 100644 tests/test_serialize.py diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py new file mode 100644 index 0000000000..8d5fd7f254 --- /dev/null +++ b/pennylane_lightning/_serialize.py @@ -0,0 +1,131 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +r""" +Helper functions for serializing quantum tapes. +""" +from typing import List, Tuple + +import numpy as np +from pennylane import BasisState, Hadamard, Projector, QubitStateVector +from pennylane.grouping import is_pauli_word +from pennylane.operation import Observable, Tensor +from pennylane.tape import QuantumTape + +try: + from .lightning_qubit_ops import StateVectorC128, ObsStructC128 +except ImportError: + pass + + +def _obs_has_kernel(obs: Observable) -> bool: + """Returns True if the input observable has a supported kernel in the C++ backend. + + Args: + obs (Observable): the input observable + + Returns: + bool: indicating whether ``obs`` has a dedicated kernel in the backend + """ + if is_pauli_word(obs): + return True + if isinstance(obs, (Hadamard, Projector)): + return True + if isinstance(obs, Tensor): + return all(_obs_has_kernel(o) for o in obs.obs) + return False + + +def _serialize_obs( + tape: QuantumTape, wires_map: dict +) -> List[ObsStructC128]: + """Serializes the observables of an input tape. + + Args: + tape (QuantumTape): the input quantum tape + wires_map (dict): a dictionary mapping input wires to the device's backend wires + + Returns: + list(ObsStructC128): A list of observable objects compatible with the C++ backend + """ + obs = [] + + for o in tape.observables: + is_tensor = isinstance(o, Tensor) + + wires_list = o.wires.tolist() + wires = [wires_map[w] for w in wires_list] + name = o.name if is_tensor else [o.name] + + params = [] + + if not _obs_has_kernel(o): + if is_tensor: + for o_ in o.obs: + if not _obs_has_kernel(o_): + params.append(o_.matrix) + else: + params.append(o.matrix) + + ob = ObsStructC128(name, params, [wires]) + obs.append(ob) + + return obs + + +def _serialize_ops( + tape: QuantumTape, wires_map: dict +) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]: + """Serializes the operations of an input tape. + + The state preparation operations are not included. + + Args: + tape (QuantumTape): the input quantum tape + wires_map (dict): a dictionary mapping input wires to the device's backend wires + + Returns: + Tuple[list, list, list, list, list]: A serialization of the operations, containing a list + of operation names, a list of operation parameters, a list of observable wires, a list of + inverses, and a list of matrices for the operations that do not have a dedicated kernel. + """ + names = [] + params = [] + wires = [] + inverses = [] + mats = [] + + for o in tape.operations: + if isinstance(o, (BasisState, QubitStateVector)): + continue + + is_inverse = o.inverse + + name = o.name if not is_inverse else o.name[:-4] + names.append(name) + + if getattr(StateVectorC128, name, None) is None: + params.append([]) + mats.append(o.matrix) + + if is_inverse: + is_inverse = False + else: + params.append(o.parameters) + mats.append([]) + + wires_list = o.wires.tolist() + wires.append([wires_map[w] for w in wires_list]) + inverses.append(is_inverse) + + return names, params, wires, inverses, mats diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index c8c64fa95f..941e12520d 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -17,19 +17,22 @@ """ from warnings import warn -from pennylane.devices import DefaultQubit import numpy as np -from pennylane import QubitStateVector, BasisState, DeviceError, QubitUnitary +from pennylane import (BasisState, DeviceError, QuantumFunctionError, + QubitStateVector, QubitUnitary) +from pennylane.devices import DefaultQubit +from pennylane.operation import Expectation + +from ._serialize import _serialize_obs, _serialize_ops +from ._version import __version__ try: - from .lightning_qubit_ops import apply, StateVectorC64, StateVectorC128 + from .lightning_qubit_ops import apply, StateVectorC64, StateVectorC128, AdjointJacobianC128 CPP_BINARY_AVAILABLE = True except ModuleNotFoundError: CPP_BINARY_AVAILABLE = False -from ._version import __version__ - class LightningQubit(DefaultQubit): """PennyLane Lightning device. @@ -131,6 +134,46 @@ def apply_lightning(self, state, operations): return np.reshape(state_vector, state.shape) + def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): + + if self.shots is not None: + warn( + "Requested adjoint differentiation to be computed with finite shots." + " The derivative is always exact when using the adjoint differentiation method.", + UserWarning, + ) + + for m in tape.measurements: + if m.return_type is not Expectation: + raise QuantumFunctionError( + "Adjoint differentiation method does not support" + f" measurement {m.return_type.value}" + ) + + # Initialization of state + if starting_state is not None: + ket = np.ravel(starting_state) + else: + if not use_device_state: + self.reset() + self.execute(tape) + ket = self._pre_rotated_state + + # TODO: How to accommodate for tensor product observables? + adj = AdjointJacobianC128() + jac = np.zeros((len(tape.observables), len(tape.trainable_params))) + + obs_serialized = _serialize_obs(tape, self.wire_map) + ops_serialized = _serialize_ops(tape, self.wire_map) + + ops_serialized = adj.create_ops_list(*ops_serialized) + + adj.adjoint_jacobian( + jac, ket, obs_serialized, ops_serialized, tape.trainable_params, tape.num_params + ) + + return super().adjoint_jacobian(tape, starting_state, use_device_state) + if not CPP_BINARY_AVAILABLE: diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py new file mode 100644 index 0000000000..a1c5d8dd32 --- /dev/null +++ b/tests/test_adjoint_jacobian.py @@ -0,0 +1,504 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tests for the ``adjoint_jacobian`` method of LightningQubit. +""" +import math +import pytest + +import pennylane as qml +from pennylane import numpy as np +from pennylane import QNode, qnode + + +I, X, Y, Z = np.eye(2), qml.PauliX.matrix, qml.PauliY.matrix, qml.PauliZ.matrix + + +def Rx(theta): + r"""One-qubit rotation about the x axis. + + Args: + theta (float): rotation angle + Returns: + array: unitary 2x2 rotation matrix :math:`e^{-i \sigma_x \theta/2}` + """ + return math.cos(theta / 2) * I + 1j * math.sin(-theta / 2) * X + + +def Ry(theta): + r"""One-qubit rotation about the y axis. + + Args: + theta (float): rotation angle + Returns: + array: unitary 2x2 rotation matrix :math:`e^{-i \sigma_y \theta/2}` + """ + return math.cos(theta / 2) * I + 1j * math.sin(-theta / 2) * Y + + +def Rz(theta): + r"""One-qubit rotation about the z axis. + + Args: + theta (float): rotation angle + Returns: + array: unitary 2x2 rotation matrix :math:`e^{-i \sigma_z \theta/2}` + """ + return math.cos(theta / 2) * I + 1j * math.sin(-theta / 2) * Z + + +class TestAdjointJacobian: + """Tests for the adjoint_jacobian method""" + + @pytest.fixture + def dev(self): + return qml.device("lightning.qubit", wires=2) + + def test_not_expval(self, dev): + """Test if a QuantumFunctionError is raised for a tape with measurements that are not + expectation values""" + + with qml.tape.JacobianTape() as tape: + qml.RX(0.1, wires=0) + qml.var(qml.PauliZ(0)) + + with pytest.raises(qml.QuantumFunctionError, match="Adjoint differentiation method does"): + dev.adjoint_jacobian(tape) + + def test_finite_shots_warns(self): + """Tests warning raised when finite shots specified""" + + dev = qml.device("lightning.qubit", wires=1, shots=1) + + with qml.tape.JacobianTape() as tape: + qml.expval(qml.PauliZ(0)) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + dev.adjoint_jacobian(tape) + + def test_unsupported_op(self, dev): + """Test if a QuantumFunctionError is raised for an unsupported operation, i.e., + multi-parameter operations that are not qml.Rot""" + + with qml.tape.JacobianTape() as tape: + qml.CRot(0.1, 0.2, 0.3, wires=[0, 1]) + qml.expval(qml.PauliZ(0)) + + with pytest.raises(qml.QuantumFunctionError, match="The CRot operation is not"): + dev.adjoint_jacobian(tape) + + @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) + @pytest.mark.parametrize("G", [qml.RX, qml.RY, qml.RZ]) + def test_pauli_rotation_gradient(self, G, theta, tol, dev): + """Tests that the automatic gradients of Pauli rotations are correct.""" + + with qml.tape.JacobianTape() as tape: + qml.QubitStateVector(np.array([1.0, -1.0]) / np.sqrt(2), wires=0) + G(theta, wires=[0]) + qml.expval(qml.PauliZ(0)) + + tape.trainable_params = {1} + + calculated_val = dev.adjoint_jacobian(tape) + + # compare to finite differences + numeric_val = tape.jacobian(dev, method="numeric") + assert np.allclose(calculated_val, numeric_val, atol=tol, rtol=0) + + @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) + def test_Rot_gradient(self, theta, tol, dev): + """Tests that the device gradient of an arbitrary Euler-angle-parameterized gate is + correct.""" + params = np.array([theta, theta ** 3, np.sqrt(2) * theta]) + + with qml.tape.JacobianTape() as tape: + qml.QubitStateVector(np.array([1.0, -1.0]) / np.sqrt(2), wires=0) + qml.Rot(*params, wires=[0]) + qml.expval(qml.PauliZ(0)) + + tape.trainable_params = {1, 2, 3} + + calculated_val = dev.adjoint_jacobian(tape) + + # compare to finite differences + numeric_val = tape.jacobian(dev, method="numeric") + assert np.allclose(calculated_val, numeric_val, atol=tol, rtol=0) + + @pytest.mark.parametrize("par", [1, -2, 1.623, -0.051, 0]) # integers, floats, zero + def test_ry_gradient(self, par, tol, dev): + """Test that the gradient of the RY gate matches the exact analytic formula.""" + + with qml.tape.JacobianTape() as tape: + qml.RY(par, wires=[0]) + qml.expval(qml.PauliX(0)) + + tape.trainable_params = {0} + + # gradients + exact = np.cos(par) + grad_F = tape.jacobian(dev, method="numeric") + grad_A = dev.adjoint_jacobian(tape) + + # different methods must agree + assert np.allclose(grad_F, exact, atol=tol, rtol=0) + assert np.allclose(grad_A, exact, atol=tol, rtol=0) + + def test_rx_gradient(self, tol, dev): + """Test that the gradient of the RX gate matches the known formula.""" + a = 0.7418 + + with qml.tape.JacobianTape() as tape: + qml.RX(a, wires=0) + qml.expval(qml.PauliZ(0)) + + # circuit jacobians + dev_jacobian = dev.adjoint_jacobian(tape) + expected_jacobian = -np.sin(a) + assert np.allclose(dev_jacobian, expected_jacobian, atol=tol, rtol=0) + + def test_multiple_rx_gradient(self, tol): + """Tests that the gradient of multiple RX gates in a circuit yields the correct result.""" + dev = qml.device("lightning.qubit", wires=3) + params = np.array([np.pi, np.pi / 2, np.pi / 3]) + + with qml.tape.JacobianTape() as tape: + qml.RX(params[0], wires=0) + qml.RX(params[1], wires=1) + qml.RX(params[2], wires=2) + + for idx in range(3): + qml.expval(qml.PauliZ(idx)) + + # circuit jacobians + dev_jacobian = dev.adjoint_jacobian(tape) + expected_jacobian = -np.diag(np.sin(params)) + assert np.allclose(dev_jacobian, expected_jacobian, atol=tol, rtol=0) + + qubit_ops = [getattr(qml, name) for name in qml.ops._qubit__ops__] + ops = {qml.RX, qml.RY, qml.RZ, qml.PhaseShift, qml.CRX, qml.CRY, qml.CRZ, qml.Rot} + + @pytest.mark.parametrize("obs", [qml.PauliX, qml.PauliY]) + @pytest.mark.parametrize("op", ops) + def test_gradients(self, op, obs, tol, dev): + """Tests that the gradients of circuits match between the finite difference and device + methods.""" + args = np.linspace(0.2, 0.5, op.num_params) + + with qml.tape.JacobianTape() as tape: + qml.Hadamard(wires=0) + qml.RX(0.543, wires=0) + qml.CNOT(wires=[0, 1]) + + op(*args, wires=range(op.num_wires)) + + qml.Rot(1.3, -2.3, 0.5, wires=[0]) + qml.RZ(-0.5, wires=0) + qml.RY(0.5, wires=1).inv() + qml.CNOT(wires=[0, 1]) + + qml.expval(obs(wires=0)) + qml.expval(qml.PauliZ(wires=1)) + + tape.execute(dev) + + tape.trainable_params = set(range(1, 1 + op.num_params)) + + grad_F = tape.jacobian(dev, method="numeric") + grad_D = dev.adjoint_jacobian(tape) + + assert np.allclose(grad_D, grad_F, atol=tol, rtol=0) + + def test_gradient_gate_with_multiple_parameters(self, tol, dev): + """Tests that gates with multiple free parameters yield correct gradients.""" + x, y, z = [0.5, 0.3, -0.7] + + with qml.tape.JacobianTape() as tape: + qml.RX(0.4, wires=[0]) + qml.Rot(x, y, z, wires=[0]) + qml.RY(-0.2, wires=[0]) + qml.expval(qml.PauliZ(0)) + + tape.trainable_params = {1, 2, 3} + + grad_D = dev.adjoint_jacobian(tape) + grad_F = tape.jacobian(dev, method="numeric") + + # gradient has the correct shape and every element is nonzero + assert grad_D.shape == (1, 3) + assert np.count_nonzero(grad_D) == 3 + # the different methods agree + assert np.allclose(grad_D, grad_F, atol=tol, rtol=0) + + def test_use_device_state(self, tol, dev): + """Tests that when using the device state, the correct answer is still returned.""" + + x, y, z = [0.5, 0.3, -0.7] + + with qml.tape.JacobianTape() as tape: + qml.RX(0.4, wires=[0]) + qml.Rot(x, y, z, wires=[0]) + qml.RY(-0.2, wires=[0]) + qml.expval(qml.PauliZ(0)) + + tape.trainable_params = {1, 2, 3} + + dM1 = dev.adjoint_jacobian(tape) + + tape.execute(dev) + dM2 = dev.adjoint_jacobian(tape, use_device_state=True) + + assert np.allclose(dM1, dM2, atol=tol, rtol=0) + + def test_provide_starting_state(self, tol, dev): + """Tests provides correct answer when provided starting state.""" + x, y, z = [0.5, 0.3, -0.7] + + with qml.tape.JacobianTape() as tape: + qml.RX(0.4, wires=[0]) + qml.Rot(x, y, z, wires=[0]) + qml.RY(-0.2, wires=[0]) + qml.expval(qml.PauliZ(0)) + + tape.trainable_params = {1, 2, 3} + + dM1 = dev.adjoint_jacobian(tape) + + tape.execute(dev) + dM2 = dev.adjoint_jacobian(tape, starting_state=dev._pre_rotated_state) + + assert np.allclose(dM1, dM2, atol=tol, rtol=0) + + +class TestAdjointJacobianQNode: + """Test QNode integration with the adjoint_jacobian method""" + + @pytest.fixture + def dev(self): + return qml.device("lightning.qubit", wires=2) + + def test_finite_shots_warning(self): + """Tests that a warning is raised when computing the adjoint diff on a device with finite shots""" + + dev = qml.device("lightning.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + + @qml.qnode(dev, diff_method="adjoint") + def circ(x): + qml.RX(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + qml.grad(circ)(0.1) + + def test_qnode(self, mocker, tol, dev): + """Test that specifying diff_method allows the adjoint method to be selected""" + args = np.array([0.54, 0.1, 0.5], requires_grad=True) + + def circuit(x, y, z): + qml.Hadamard(wires=0) + qml.RX(0.543, wires=0) + qml.CNOT(wires=[0, 1]) + + qml.Rot(x, y, z, wires=0) + + qml.Rot(1.3, -2.3, 0.5, wires=[0]) + qml.RZ(-0.5, wires=0) + qml.RY(0.5, wires=1) + qml.CNOT(wires=[0, 1]) + + return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) + + qnode1 = QNode(circuit, dev, diff_method="adjoint") + spy = mocker.spy(dev, "adjoint_jacobian") + + grad_fn = qml.grad(qnode1) + grad_A = grad_fn(*args) + + spy.assert_called() + + qnode2 = QNode(circuit, dev, diff_method="finite-diff") + grad_fn = qml.grad(qnode2) + grad_F = grad_fn(*args) + + assert np.allclose(grad_A, grad_F, atol=tol, rtol=0) + + thetas = np.linspace(-2 * np.pi, 2 * np.pi, 8) + + @pytest.mark.parametrize("reused_p", thetas ** 3 / 19) + @pytest.mark.parametrize("other_p", thetas ** 2 / 1) + def test_fanout_multiple_params(self, reused_p, other_p, tol, mocker, dev): + """Tests that the correct gradient is computed for qnodes which + use the same parameter in multiple gates.""" + + def expZ(state): + return np.abs(state[0]) ** 2 - np.abs(state[1]) ** 2 + + extra_param = np.array(0.31, requires_grad=False) + + @qnode(dev, diff_method="adjoint") + def cost(p1, p2): + qml.RX(extra_param, wires=[0]) + qml.RY(p1, wires=[0]) + qml.RZ(p2, wires=[0]) + qml.RX(p1, wires=[0]) + return qml.expval(qml.PauliZ(0)) + + zero_state = np.array([1.0, 0.0]) + cost(reused_p, other_p) + + spy = mocker.spy(dev, "adjoint_jacobian") + + # analytic gradient + grad_fn = qml.grad(cost) + grad_D = grad_fn(reused_p, other_p) + + spy.assert_called_once() + + # manual gradient + grad_true0 = ( + expZ( + Rx(reused_p) @ Rz(other_p) @ Ry(reused_p + np.pi / 2) @ Rx(extra_param) @ zero_state + ) + - expZ( + Rx(reused_p) @ Rz(other_p) @ Ry(reused_p - np.pi / 2) @ Rx(extra_param) @ zero_state + ) + ) / 2 + grad_true1 = ( + expZ( + Rx(reused_p + np.pi / 2) @ Rz(other_p) @ Ry(reused_p) @ Rx(extra_param) @ zero_state + ) + - expZ( + Rx(reused_p - np.pi / 2) @ Rz(other_p) @ Ry(reused_p) @ Rx(extra_param) @ zero_state + ) + ) / 2 + expected = grad_true0 + grad_true1 # product rule + + assert np.allclose(grad_D[0], expected, atol=tol, rtol=0) + + def test_gradient_repeated_gate_parameters(self, mocker, tol, dev): + """Tests that repeated use of a free parameter in a multi-parameter gate yields correct + gradients.""" + params = np.array([0.8, 1.3], requires_grad=True) + + def circuit(params): + qml.RX(np.array(np.pi / 4, requires_grad=False), wires=[0]) + qml.Rot(params[1], params[0], 2 * params[0], wires=[0]) + return qml.expval(qml.PauliX(0)) + + spy_numeric = mocker.spy(qml.tape.JacobianTape, "numeric_pd") + spy_analytic = mocker.spy(dev, "adjoint_jacobian") + + cost = QNode(circuit, dev, diff_method="finite-diff") + + grad_fn = qml.grad(cost) + grad_F = grad_fn(params) + + spy_numeric.assert_called() + spy_analytic.assert_not_called() + + cost = QNode(circuit, dev, diff_method="adjoint") + grad_fn = qml.grad(cost) + grad_D = grad_fn(params) + + spy_analytic.assert_called_once() + + # the different methods agree + assert np.allclose(grad_D, grad_F, atol=tol, rtol=0) + + def test_interface_tf(self, dev): + """Test if gradients agree between the adjoint and finite-diff methods when using the + TensorFlow interface""" + tf = pytest.importorskip("tensorflow") + + def f(params1, params2): + qml.RX(0.4, wires=[0]) + qml.RZ(params1 * tf.sqrt(params2), wires=[0]) + qml.RY(tf.cos(params2), wires=[0]) + return qml.expval(qml.PauliZ(0)) + + params1 = tf.Variable(0.3, dtype=tf.float64) + params2 = tf.Variable(0.4, dtype=tf.float64) + + qnode1 = QNode(f, dev, interface="tf", diff_method="adjoint") + qnode2 = QNode(f, dev, interface="tf", diff_method="finite-diff") + + with tf.GradientTape() as tape: + res1 = qnode1(params1, params2) + + g1 = tape.gradient(res1, [params1, params2]) + + with tf.GradientTape() as tape: + res2 = qnode2(params1, params2) + + g2 = tape.gradient(res2, [params1, params2]) + + assert np.allclose(g1, g2) + + def test_interface_torch(self, dev): + """Test if gradients agree between the adjoint and finite-diff methods when using the + Torch interface""" + torch = pytest.importorskip("torch") + + def f(params1, params2): + qml.RX(0.4, wires=[0]) + qml.RZ(params1 * torch.sqrt(params2), wires=[0]) + qml.RY(torch.cos(params2), wires=[0]) + return qml.expval(qml.PauliZ(0)) + + params1 = torch.tensor(0.3, requires_grad=True) + params2 = torch.tensor(0.4, requires_grad=True) + + qnode1 = QNode(f, dev, interface="torch", diff_method="adjoint") + qnode2 = QNode(f, dev, interface="torch", diff_method="finite-diff") + + res1 = qnode1(params1, params2) + res1.backward() + + grad_adjoint = params1.grad, params2.grad + + res2 = qnode2(params1, params2) + res2.backward() + + grad_fd = params1.grad, params2.grad + + assert np.allclose(grad_adjoint, grad_fd) + + def test_interface_jax(self, dev): + """Test if the gradients agree between adjoint and finite-difference methods in the + jax interface""" + jax = pytest.importorskip("jax") + + def f(params1, params2): + qml.RX(0.4, wires=[0]) + qml.RZ(params1 * jax.numpy.sqrt(params2), wires=[0]) + qml.RY(jax.numpy.cos(params2), wires=[0]) + return qml.expval(qml.PauliZ(0)) + + params1 = jax.numpy.array(0.3) + params2 = jax.numpy.array(0.4) + + qnode_adjoint = QNode(f, dev, interface="jax", diff_method="adjoint") + qnode_fd = QNode(f, dev, interface="jax", diff_method="finite-diff") + + grad_adjoint = jax.grad(qnode_adjoint)(params1, params2) + grad_fd = jax.grad(qnode_fd)(params1, params2) + + assert np.allclose(grad_adjoint, grad_fd) diff --git a/tests/test_serialize.py b/tests/test_serialize.py new file mode 100644 index 0000000000..ff50668137 --- /dev/null +++ b/tests/test_serialize.py @@ -0,0 +1,308 @@ +# Copyright 2020 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. +""" +Unit tests for the serialization helper functions +""" +import pennylane as qml +from pennylane import numpy as np + +from pennylane_lightning._serialize import (_serialize_obs, _serialize_ops, + _obs_has_kernel) + + +class TestOpsHasKernel: + """Tests for the _obs_has_kernel function""" + + def test_pauli_z(self): + """Tests if return is true for a PauliZ observable""" + o = qml.PauliZ(0) + assert _obs_has_kernel(o) + + def test_tensor_pauli(self): + """Tests if return is true for a tensor product of Pauli terms""" + o = qml.PauliZ(0) @ qml.PauliZ(1) + assert _obs_has_kernel(o) + + def test_hadamard(self): + """Tests if return is true for a Hadamard observable""" + o = qml.Hadamard(0) + assert _obs_has_kernel(o) + + def test_projector(self): + """Tests if return is true for a Projector observable""" + o = qml.Projector([0], wires=0) + assert _obs_has_kernel(o) + + def test_hermitian(self): + """Tests if return is false for a Hermitian observable""" + o = qml.Hermitian(np.eye(2), wires=0) + assert not _obs_has_kernel(o) + + def test_tensor_product_of_valid_terms(self): + """Tests if return is true for a tensor product of Pauli, Hadamard, and Projector terms""" + o = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.Projector([0], wires=2) + assert _obs_has_kernel(o) + + def test_tensor_product_of_invalid_terms(self): + """Tests if return is false for a tensor product of Hermitian terms""" + o = qml.Hermitian(np.eye(2), wires=0) @ qml.Hermitian(np.eye(2), wires=1) + assert not _obs_has_kernel(o) + + def test_tensor_product_of_mixed_terms(self): + """Tests if return is false for a tensor product of valid and invalid terms""" + o = qml.PauliZ(0) @ qml.Hermitian(np.eye(2), wires=1) + assert not _obs_has_kernel(o) + + +class TestSerializeObs: + """Tests for the _serialize_obs function""" + + wires_dict = {i: i for i in range(10)} + + def test_basic_return(self): + """Test expected serialization for a simple return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.PauliZ(0)) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["PauliZ"]], [], [[0]]) + assert s == s_expected + + def test_tensor_return(self): + """Test expected serialization for a tensor product return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["PauliZ", "PauliZ"]], [], [[0, 1]]) + assert s == s_expected + + def test_tensor_non_tensor_return(self): + """Test expected serialization for a mixture of tensor product and non-tensor product + return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) + qml.expval(qml.Hadamard(1)) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["PauliZ", "PauliX"], ["Hadamard"]], [], [[0, 1], [1]]) + assert s == s_expected + + def test_hermitian_return(self): + """Test expected serialization for a Hermitian return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["Hermitian"]], [np.eye(4)], [[0, 1]]) + + assert s[0] == s_expected[0] + assert np.allclose(s[1], s_expected[1]) + assert s[2] == s_expected[2] + + def test_hermitian_tensor_return(self): + """Test expected serialization for a Hermitian return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.Hermitian(np.eye(2), wires=[2])) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["Hermitian", "Hermitian"]], [np.eye(4), np.eye(2)], [[0, 1, 2]]) + + assert s[0] == s_expected[0] + assert np.allclose(s[1][0], s_expected[1][0]) + assert np.allclose(s[1][1], s_expected[1][1]) + assert s[2] == s_expected[2] + + def test_mixed_tensor_return(self): + """Test expected serialization for a mixture of Hermitian and Pauli return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2)) + + s = _serialize_obs(tape, self.wires_dict) + s_expected = ([["Hermitian", "PauliY"]], [np.eye(4)], [[0, 1, 2]]) + + assert s[0] == s_expected[0] + assert np.allclose(s[1][0], s_expected[1][0]) + assert s[2] == s_expected[2] + + def test_integration(self): + """Test for a comprehensive range of returns""" + wires_dict = {"a": 0, 1: 1, "b": 2, -1: 3, 3.141: 4, "five": 5, 6: 6, 77: 7, 9: 8} + I = np.eye(2) + X = qml.PauliX.matrix + Y = qml.PauliY.matrix + Z = qml.PauliZ.matrix + + with qml.tape.QuantumTape() as tape: + qml.expval(qml.PauliZ("a") @ qml.PauliX("b")) + qml.expval(qml.Hermitian(I, wires=1)) + qml.expval(qml.PauliZ(-1) @ qml.Hermitian(X, wires=3.141) @ qml.Hadamard("five")) + qml.expval(qml.Projector([1, 1], wires=[6, 77]) @ qml.Hermitian(Y, wires=9)) + qml.expval(qml.Hermitian(Z, wires="a") @ qml.Identity(1)) + + s = _serialize_obs(tape, wires_dict) + s_expected = ( + [ + ["PauliZ", "PauliX"], + ["Hermitian"], + ["PauliZ", "Hermitian", "Hadamard"], + ["Projector", "Hermitian"], + ["Hermitian", "Identity"], + ], + [I, X, Y, Z], + [[0, 2], [1], [3, 4, 5], [6, 7, 8], [0, 1]], + ) + + assert s[0] == s_expected[0] + assert all(np.allclose(s1, s2) for s1, s2 in zip(s[1], s_expected[1])) + assert s[2] == s_expected[2] + + +class TestSerializeOps: + """Tests for the _serialize_ops function""" + + wires_dict = {i: i for i in range(10)} + + def test_basic_circuit(self): + """Test expected serialization for a simple circuit""" + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1) + qml.CNOT(wires=[0, 1]) + + s = _serialize_ops(tape, self.wires_dict) + s_expected = ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ) + assert s == s_expected + + def test_skips_prep_circuit(self): + """Test expected serialization for a simple circuit with state preparation, such that + the state preparation is skipped""" + with qml.tape.QuantumTape() as tape: + qml.QubitStateVector([1, 0], wires=0) + qml.BasisState([1], wires=1) + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1) + qml.CNOT(wires=[0, 1]) + + s = _serialize_ops(tape, self.wires_dict) + s_expected = ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ) + assert s == s_expected + + def test_inverse_circuit(self): + """Test expected serialization for a simple circuit that includes an inverse gate""" + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1).inv() + qml.CNOT(wires=[0, 1]) + + s = _serialize_ops(tape, self.wires_dict) + s_expected = ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, True, False], + [[], [], []], + ) + assert s == s_expected + + def test_unsupported_kernel_circuit(self): + """Test expected serialization for a circuit including gates that do not have a dedicated + kernel""" + with qml.tape.QuantumTape() as tape: + qml.SingleExcitationPlus(0.4, wires=[0, 1]) + qml.SingleExcitationMinus(0.5, wires=[1, 2]).inv() + qml.CNOT(wires=[0, 1]) + qml.RZ(0.2, wires=2) + + s = _serialize_ops(tape, self.wires_dict) + s_expected = ( + ["SingleExcitationPlus", "SingleExcitationMinus", "CNOT", "RZ"], + [[], [], [], [0.2]], + [[0, 1], [1, 2], [0, 1], [2]], + [False, False, False, False], + [ + qml.SingleExcitationPlus(0.4, wires=[0, 1]).matrix, + qml.SingleExcitationMinus(0.5, wires=[1, 2]).inv().matrix, + [], + [], + ], + ) + assert s[0] == s_expected[0] + assert s[1] == s_expected[1] + assert s[2] == s_expected[2] + assert s[3] == s_expected[3] + + assert all(np.allclose(s1, s2) for s1, s2 in zip(s[4], s_expected[4])) + + def test_custom_wires_circuit(self): + """Test expected serialization for a simple circuit with custom wire labels""" + wires_dict = {"a": 0, 3.2: 1} + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires="a") + qml.RY(0.6, wires=3.2) + qml.CNOT(wires=["a", 3.2]) + + s = _serialize_ops(tape, wires_dict) + s_expected = ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ) + assert s == s_expected + + def test_integration(self): + """Test expected serialization for a random circuit""" + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1).inv().inv() + qml.CNOT(wires=[0, 1]) + qml.QubitUnitary(np.eye(4), wires=[0, 1]) + qml.QFT(wires=[0, 1, 2]).inv() + qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) + + s = _serialize_ops(tape, self.wires_dict) + s_expected = ( + ["RX", "RY", "CNOT", "QubitUnitary", "QFT", "DoubleExcitation"], + [[0.4], [0.6], [], [], [], []], + [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0]], + [False, False, False, False, False, False], + [ + [], + [], + [], + qml.QubitUnitary(np.eye(4), wires=[0, 1]).matrix, + qml.QFT(wires=[0, 1, 2]).inv().matrix, + qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]).matrix, + ], + ) + assert s[0] == s_expected[0] + assert s[1] == s_expected[1] + assert s[2] == s_expected[2] + assert s[3] == s_expected[3] + + assert all(np.allclose(s1, s2) for s1, s2 in zip(s[4], s_expected[4])) From 1743fc4a7f4302281c2ab9a084995585722513e5 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 14:52:14 +0100 Subject: [PATCH 29/72] Add support for statevector with own managed memory --- .../src/simulator/StateVector.hpp | 93 ++- pennylane_lightning/src/tests/CMakeLists.txt | 1 + .../Test_StateVectorManaged_Nonparam.cpp | 753 ++++++++++++++++++ pennylane_lightning/src/util/Error.hpp | 100 +++ 4 files changed, 926 insertions(+), 21 deletions(-) create mode 100644 pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp create mode 100644 pennylane_lightning/src/util/Error.hpp diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 9fae8dac72..1d1de9a5ac 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -30,11 +30,11 @@ #include #include -#include - #include "Gates.hpp" #include "Util.hpp" +#include + /// @cond DEV namespace { using namespace std::placeholders; @@ -83,7 +83,7 @@ template class StateVector { //***********************************************************************// - CFP_t *const arr_; + CFP_t *arr_; const size_t length_; const size_t num_qubits_; @@ -167,6 +167,8 @@ template class StateVector { */ CFP_t *getData() { return arr_; } + void setData(CFP_t *data_ptr) { arr_ = data_ptr; } + /** * @brief Get the number of data elements in the statevector array. * @@ -198,21 +200,6 @@ template class StateVector { std::to_string(gate_wires_.at(opName)) + " wires, but " + std::to_string(wires.size()) + " were supplied"); - std::cout << "[SVADDR::" << arr_ << "]"; - std::cout << "[GATE_CALL::" << opName << "][WIRES::"; - for (auto &w : wires) { - std::cout << w << ","; - } - std::cout << "][INVERT::" << inverse << "][PARAMS::"; - if (!params.empty()) { - for (auto &p : params) { - std::cout << p << ","; - } - } else { - std::cout << "EMPTY"; - } - std::cout << "]" << std::endl; - const vector internalIndices = generateBitPatterns(wires); const vector externalWires = getIndicesAfterExclusion(wires); const vector externalIndices = @@ -618,9 +605,11 @@ template class StateVector { void applyRY(const vector &indices, const vector &externalIndices, bool inverse, Param_t angle) { - const CFP_t c(std::cos(angle / 2), 0); - const CFP_t s = (inverse == true) ? CFP_t(-std::sin(angle / 2), 0) - : CFP_t(std::sin(angle / 2), 0); + + const Param_t angle_ = (inverse == true) ? -angle : angle; + + const CFP_t c(std::cos(angle_ / 2), 0); + const CFP_t s(std::sin(angle_ / 2), 0); for (const size_t &externalIndex : externalIndices) { CFP_t *shiftedState = arr_ + externalIndex; @@ -1072,6 +1061,68 @@ template class StateVector { } }; +/** + * @brief Managed memory version of StateVector class. Memory ownership resides + * within class. + * + * @tparam fp_t + */ +template +class StateVectorManaged : public StateVector { + private: + using CFP_t = std::complex; + + std::vector data_; + + public: + StateVectorManaged() : StateVector() {} + StateVectorManaged(size_t num_qubits) + : data_(static_cast(Util::exp2(num_qubits)), CFP_t{0, 0}), + StateVector(nullptr, + static_cast(Util::exp2(num_qubits))) { + StateVector::setData(data_.data()); + data_[0] = {1, 0}; + } + StateVectorManaged(const StateVector other) + : data_{other.getData(), other.getData() + other.getLength()}, + StateVector(nullptr, data_.size()) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const std::vector &other_data) + : data_{other_data}, StateVector(nullptr, other_data.size()) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const CFP_t *other_data, size_t other_size) + : data_{other_data, other_data + other_size}, StateVector( + nullptr, + data_.size()) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const StateVectorManaged &other) + : data_{other.data_}, StateVector(nullptr, data_.size()) { + StateVector::setData(data_.data()); + } + + std::vector &getDataVector() { return data_; } + std::vector &getDataVector() const { return data_; } + + std::vector + getInternalIndices(const std::vector &qubit_indices) { + return StateVector::generateBitPatterns(qubit_indices, + Util::log2(data_.size())); + } + std::vector + getExternalIndices(const std::vector &qubit_indices) { + std::vector externalWires = + StateVector::getIndicesAfterExclusion( + qubit_indices, Util::log2(data_.size())); + std::vector externalIndices = + StateVector::generateBitPatterns(externalWires, + Util::log2(data_.size())); + return externalIndices; + } +}; + template inline std::ostream &operator<<(std::ostream &out, const StateVector &sv) { const size_t num_qubits = sv.getNumQubits(); diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index 141c75563a..215d573a7a 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -36,6 +36,7 @@ target_sources(runner PRIVATE Test_AdjDiff.cpp Test_Bindings.cpp Test_StateVector_Nonparam.cpp Test_StateVector_Param.cpp + Test_StateVectorManaged_Nonparam.cpp Test_Util.cpp ) diff --git a/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp b/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp new file mode 100644 index 0000000000..aa18a0088b --- /dev/null +++ b/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp @@ -0,0 +1,753 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Gates.hpp" +#include "StateVector.hpp" +#include "Util.hpp" + +#include "TestHelpers.hpp" + +using namespace Pennylane; + +/** + * @brief Tests the constructability of the StateVector class. + * + */ +TEMPLATE_TEST_CASE("StateVectorManaged::StateVectorManaged", + "[StateVectorManaged_Nonparam]", float, double) { + SECTION("StateVectorManaged") { + REQUIRE(std::is_constructible>::value); + } + SECTION("StateVectorManaged {}") { + REQUIRE(std::is_constructible>::value); + } + SECTION("StateVectorManaged {const " + "std::vector>&}") { + REQUIRE(std::is_constructible< + StateVectorManaged, + const std::vector> &>::value); + } + SECTION("StateVectorManaged {std::complex*, size_t}") { + REQUIRE(std::is_constructible, + std::complex *, size_t>::value); + } + SECTION( + "StateVectorManaged {const StateVectorManaged&}") { + REQUIRE( + std::is_constructible, + const StateVectorManaged &>::value); + } + + SECTION("StateVectorManaged {const StateVector&}") { + REQUIRE(std::is_constructible, + const StateVector &>::value); + } +} + +namespace {} // namespace + +TEMPLATE_TEST_CASE("StateVectorManaged::applyHadamard", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat(num_qubits); + INFO("GOT HERE 1"); + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + INFO("GOT HERE 2"); + + CHECK(svdat.getDataVector()[0] == cp_t{1, 0}); + svdat.applyHadamard(int_idx, ext_idx, false); + + cp_t expected(1 / std::sqrt(2), 0); + CHECK(expected.real() == Approx(svdat.getDataVector()[0].real())); + CHECK(expected.imag() == Approx(svdat.getDataVector()[0].imag())); + + CHECK(expected.real() == + Approx(svdat + .getDataVector()[0b1 << (svdat.getNumQubits() - + index - 1)] + .real())); + CHECK(expected.imag() == + Approx(svdat + .getDataVector()[0b1 << (svdat.getNumQubits() - + index - 1)] + .imag())); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat{num_qubits}; + CHECK(svdat.getDataVector()[0] == cp_t{1, 0}); + svdat.applyOperation("Hadamard", {index}, false); + + cp_t expected(1.0 / std::sqrt(2), 0); + + CHECK(expected.real() == Approx(svdat.getDataVector()[0].real())); + CHECK(expected.imag() == Approx(svdat.getDataVector()[0].imag())); + + CHECK(expected.real() == + Approx(svdat + .getDataVector()[0b1 << (svdat.getNumQubits() - + index - 1)] + .real())); + CHECK(expected.imag() == + Approx(svdat + .getDataVector()[0b1 << (svdat.getNumQubits() - + index - 1)] + .imag())); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyPauliX", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat{num_qubits}; + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + CHECK(svdat.getDataVector()[0] == Util::ONE()); + svdat.applyPauliX(int_idx, ext_idx, false); + CHECK(svdat.getDataVector()[0] == Util::ZERO()); + CHECK(svdat.getDataVector()[0b1 << (svdat.getNumQubits() - index - + 1)] == Util::ONE()); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat{num_qubits}; + CHECK(svdat.getDataVector()[0] == Util::ONE()); + svdat.applyOperation("PauliX", {index}, false); + CHECK(svdat.getDataVector()[0] == Util::ZERO()); + CHECK(svdat.getDataVector()[0b1 << (svdat.getNumQubits() - index - + 1)] == Util::ONE()); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyPauliY", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + constexpr cp_t p = Util::ConstMult( + static_cast(0.5), + Util::ConstMult(Util::INVSQRT2(), Util::IMAG())); + constexpr cp_t m = Util::ConstMult(-1, p); + + const std::vector> expected_results = { + {m, m, m, m, p, p, p, p}, + {m, m, p, p, m, m, p, p}, + {m, p, m, p, m, p, m, p}}; + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct(init_state); + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + CHECK(svdat_direct.getDataVector() == init_state); + svdat_direct.applyPauliY(int_idx, ext_idx, false); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + CHECK(svdat_dispatch.getDataVector() == init_state); + svdat_dispatch.applyOperation("PauliY", {index}, false); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyPauliZ", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + constexpr cp_t p(static_cast(0.5) * Util::INVSQRT2()); + constexpr cp_t m(Util::ConstMult(-1, p)); + + const std::vector> expected_results = { + {p, p, p, p, m, m, m, m}, + {p, p, m, m, p, p, m, m}, + {p, m, p, m, p, m, p, m}}; + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + CHECK(svdat_direct.getDataVector() == init_state); + svdat_direct.applyPauliZ(int_idx, ext_idx, false); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + CHECK(svdat_dispatch.getDataVector() == init_state); + svdat_dispatch.applyOperation("PauliZ", {index}, false); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyS", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + constexpr cp_t r(static_cast(0.5) * Util::INVSQRT2()); + constexpr cp_t i(Util::ConstMult(r, Util::IMAG())); + + const std::vector> expected_results = { + {r, r, r, r, i, i, i, i}, + {r, r, i, i, r, r, i, i}, + {r, i, r, i, r, i, r, i}}; + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + CHECK(svdat_direct.getDataVector() == init_state); + svdat_direct.applyS(int_idx, ext_idx, false); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + CHECK(svdat_dispatch.getDataVector() == init_state); + svdat_dispatch.applyOperation("S", {index}, false); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyT", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + cp_t r(1.0 / (2.0 * std::sqrt(2)), 0); + cp_t i(1.0 / 4, 1.0 / 4); + + const std::vector> expected_results = { + {r, r, r, r, i, i, i, i}, + {r, r, i, i, r, r, i, i}, + {r, i, r, i, r, i, r, i}}; + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + CHECK(svdat_direct.getDataVector() == init_state); + svdat_direct.applyT(int_idx, ext_idx, false); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + CHECK(svdat_dispatch.getDataVector() == init_state); + svdat_dispatch.applyOperation("T", {index}, false); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyCNOT", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+00> state to generate 3-qubit GHZ state + svdat.applyOperation("Hadamard", {0}); + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + StateVectorManaged svdat_direct{init_state}; + + for (size_t index = 1; index < num_qubits; index++) { + auto int_idx = svdat_direct.getInternalIndices({index - 1, index}); + auto ext_idx = svdat_direct.getExternalIndices({index - 1, index}); + + svdat_direct.applyCNOT(int_idx, ext_idx, false); + } + CHECK(svdat_direct.getDataVector().front() == + Util::INVSQRT2()); + CHECK(svdat_direct.getDataVector().back() == + Util::INVSQRT2()); + } + + SECTION("Apply using dispatcher") { + StateVectorManaged svdat_dispatch{init_state}; + + for (size_t index = 1; index < num_qubits; index++) { + svdat_dispatch.applyOperation("CNOT", {index - 1, index}, false); + } + CHECK(svdat_dispatch.getDataVector().front() == + Util::INVSQRT2()); + CHECK(svdat_dispatch.getDataVector().back() == + Util::INVSQRT2()); + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applySWAP", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+10> state + svdat.applyOperations({{"Hadamard"}, {"PauliX"}}, {{0}, {1}}, + {false, false}); + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + CHECK(svdat.getDataVector() == + std::vector{ + Util::ZERO(), Util::ZERO(), + Util::INVSQRT2(), Util::ZERO(), + Util::ZERO(), Util::ZERO(), + Util::INVSQRT2(), Util::ZERO()}); + + SECTION("SWAP0,1 |+10> -> |1+0>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}; + + StateVectorManaged svdat01{init_state}; + StateVectorManaged svdat10{init_state}; + + svdat01.applySWAP(svdat.getInternalIndices({0, 1}), + svdat.getExternalIndices({0, 1}), false); + svdat10.applySWAP(svdat.getInternalIndices({1, 0}), + svdat.getExternalIndices({1, 0}), false); + + CHECK(svdat01.getDataVector() == expected); + CHECK(svdat10.getDataVector() == expected); + } + + SECTION("SWAP0,2 |+10> -> |01+>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + StateVectorManaged svdat02{init_state}; + StateVectorManaged svdat20{init_state}; + + svdat02.applySWAP(svdat.getInternalIndices({0, 2}), + svdat.getExternalIndices({0, 2}), false); + svdat20.applySWAP(svdat.getInternalIndices({2, 0}), + svdat.getExternalIndices({2, 0}), false); + CHECK(svdat02.getDataVector() == expected); + CHECK(svdat20.getDataVector() == expected); + } + SECTION("SWAP1,2 |+10> -> |+01>") { + std::vector expected{Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + + StateVectorManaged svdat12{init_state}; + StateVectorManaged svdat21{init_state}; + + svdat12.applySWAP(svdat.getInternalIndices({1, 2}), + svdat.getExternalIndices({1, 2}), false); + svdat21.applySWAP(svdat.getInternalIndices({2, 1}), + svdat.getExternalIndices({2, 1}), false); + CHECK(svdat12.getDataVector() == expected); + CHECK(svdat21.getDataVector() == expected); + } + } + SECTION("Apply using dispatcher") { + SECTION("SWAP0,1 |+10> -> |1+0>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}; + + StateVectorManaged svdat01{init_state}; + StateVectorManaged svdat10{init_state}; + + svdat01.applyOperation("SWAP", {0, 1}); + svdat10.applyOperation("SWAP", {1, 0}); + + CHECK(svdat01.getDataVector() == expected); + CHECK(svdat10.getDataVector() == expected); + } + + SECTION("SWAP0,2 |+10> -> |01+>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + StateVectorManaged svdat02{init_state}; + StateVectorManaged svdat20{init_state}; + + svdat02.applyOperation("SWAP", {0, 2}); + svdat20.applyOperation("SWAP", {2, 0}); + + CHECK(svdat02.getDataVector() == expected); + CHECK(svdat20.getDataVector() == expected); + } + SECTION("SWAP1,2 |+10> -> |+01>") { + std::vector expected{Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + + StateVectorManaged svdat12{init_state}; + StateVectorManaged svdat21{init_state}; + + svdat12.applyOperation("SWAP", {1, 2}); + svdat21.applyOperation("SWAP", {2, 1}); + + CHECK(svdat12.getDataVector() == expected); + CHECK(svdat21.getDataVector() == expected); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyCZ", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+10> state + svdat.applyOperations({{"Hadamard"}, {"PauliX"}}, {{0}, {1}}, + {false, false}); + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + CHECK(svdat.getDataVector() == + std::vector{Util::ZERO(), Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO()}); + + SECTION("CZ0,1 |+10> -> |-10>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(-1 / sqrt(2), 0), + Util::ZERO()}; + + StateVectorManaged svdat01{init_state}; + StateVectorManaged svdat10{init_state}; + + svdat01.applyCZ(svdat.getInternalIndices({0, 1}), + svdat.getExternalIndices({0, 1}), false); + svdat10.applyCZ(svdat.getInternalIndices({1, 0}), + svdat.getExternalIndices({1, 0}), false); + + CHECK(svdat01.getDataVector() == expected); + CHECK(svdat10.getDataVector() == expected); + } + + SECTION("CZ0,2 |+10> -> |+10>") { + std::vector expected{init_state}; + + StateVectorManaged svdat02{init_state}; + StateVectorManaged svdat20{init_state}; + + svdat02.applyCZ(svdat.getInternalIndices({0, 2}), + svdat.getExternalIndices({0, 2}), false); + svdat20.applyCZ(svdat.getInternalIndices({2, 0}), + svdat.getExternalIndices({2, 0}), false); + CHECK(svdat02.getDataVector() == expected); + CHECK(svdat20.getDataVector() == expected); + } + SECTION("CZ1,2 |+10> -> |+10>") { + std::vector expected{init_state}; + + StateVectorManaged svdat12{init_state}; + StateVectorManaged svdat21{init_state}; + + svdat12.applyCZ(svdat.getInternalIndices({1, 2}), + svdat.getExternalIndices({1, 2}), false); + svdat21.applyCZ(svdat.getInternalIndices({2, 1}), + svdat.getExternalIndices({2, 1}), false); + + CHECK(svdat12.getDataVector() == expected); + CHECK(svdat21.getDataVector() == expected); + } + } + SECTION("Apply using dispatcher") { + SECTION("CZ0,1 |+10> -> |1+0>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(-1 / sqrt(2), 0), + Util::ZERO()}; + + StateVectorManaged svdat01{init_state}; + StateVectorManaged svdat10{init_state}; + + svdat01.applyOperation("CZ", {0, 1}); + svdat10.applyOperation("CZ", {1, 0}); + + CHECK(svdat01.getDataVector() == expected); + CHECK(svdat10.getDataVector() == expected); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyToffoli", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+10> state + svdat.applyOperations({{"Hadamard"}, {"PauliX"}}, {{0}, {1}}, + {false, false}); + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + SECTION("Toffoli 0,1,2 |+10> -> |010> + |111>") { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; + + StateVectorManaged svdat012{init_state}; + + svdat012.applyToffoli(svdat.getInternalIndices({0, 1, 2}), + svdat.getExternalIndices({0, 1, 2}), false); + + CHECK(svdat012.getDataVector() == expected); + } + + SECTION("Toffoli 1,0,2 |+10> -> |010> + |111>") { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; + + StateVectorManaged svdat102{init_state}; + + svdat102.applyToffoli(svdat.getInternalIndices({1, 0, 2}), + svdat.getExternalIndices({1, 0, 2}), false); + + CHECK(svdat102.getDataVector() == expected); + } + SECTION("Toffoli 0,2,1 |+10> -> |+10>") { + std::vector expected{init_state}; + + StateVectorManaged svdat021{init_state}; + + svdat021.applyToffoli(svdat.getInternalIndices({0, 2, 1}), + svdat.getExternalIndices({0, 2, 1}), false); + + CHECK(svdat021.getDataVector() == expected); + } + SECTION("Toffoli 1,2,0 |+10> -> |+10>") { + std::vector expected{init_state}; + + StateVectorManaged svdat120{init_state}; + + svdat120.applyToffoli(svdat.getInternalIndices({1, 2, 0}), + svdat.getExternalIndices({1, 2, 0}), false); + + CHECK(svdat120.getDataVector() == expected); + } + } + SECTION("Apply using dispatcher") { + SECTION("Toffoli [0,1,2], [1,0,2] |+10> -> |+1+>") { + std::vector expected{ + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0)}; + + StateVectorManaged svdat012{init_state}; + StateVectorManaged svdat102{init_state}; + + svdat012.applyOperation("Toffoli", {0, 1, 2}); + svdat102.applyOperation("Toffoli", {1, 0, 2}); + + CHECK(svdat012.getDataVector() == expected); + CHECK(svdat102.getDataVector() == expected); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyCSWAP", + "[StateVectorManaged_Nonparam]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+10> state + svdat.applyOperations({{"Hadamard"}, {"PauliX"}}, {{0}, {1}}, + {false, false}); + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + StateVectorManaged svdat012{init_state}; + + svdat012.applyCSWAP(svdat.getInternalIndices({0, 1, 2}), + svdat.getExternalIndices({0, 1, 2}), false); + + CHECK(svdat012.getDataVector() == expected); + } + + SECTION("CSWAP 1,0,2 |+10> -> |01+>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + Util::ZERO(), + Util::ZERO()}; + + StateVectorManaged svdat102{init_state}; + + svdat102.applyCSWAP(svdat.getInternalIndices({1, 0, 2}), + svdat.getExternalIndices({1, 0, 2}), false); + + CHECK(svdat102.getDataVector() == expected); + } + SECTION("CSWAP 2,1,0 |+10> -> |+10>") { + std::vector expected{init_state}; + + StateVectorManaged svdat021{init_state}; + + svdat021.applyCSWAP(svdat.getInternalIndices({2, 1, 0}), + svdat.getExternalIndices({2, 1, 0}), false); + + CHECK(svdat021.getDataVector() == expected); + } + } + SECTION("Apply using dispatcher") { + SECTION("CSWAP 0,1,2 |+10> -> |010> + |101>") { + std::vector expected{Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO(), + std::complex(1.0 / sqrt(2), 0), + Util::ZERO(), + Util::ZERO()}; + StateVectorManaged svdat012{init_state}; + + svdat012.applyOperation("CSWAP", {0, 1, 2}); + + CHECK(svdat012.getDataVector() == expected); + } + } +} \ No newline at end of file diff --git a/pennylane_lightning/src/util/Error.hpp b/pennylane_lightning/src/util/Error.hpp new file mode 100644 index 0000000000..419f2eede9 --- /dev/null +++ b/pennylane_lightning/src/util/Error.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +/** + * @brief Macro that throws `%LightningException` with given message. + * + * @param message string literal describing error + */ +#define PL_ABORT(message) \ + Pennylane::Util::Abort(message, __FILE__, __LINE__, __func__) +/** + * @brief Macro that throws `%LightningException` if expression evaluates to + * true. + * + * @param expression an expression + * @param message string literal describing error + */ +#define PL_ABORT_IF(expression, message) \ + if ((expression)) { \ + PL_ABORT(message); \ + } +/** + * @brief Macro that throws `%LightningException` with error message if + * expression evaluates to false. + * + * @param expression an expression + * @param message string literal describing error + */ +#define PL_ABORT_IF_NOT(expression, message) \ + if (!(expression)) { \ + PL_ABORT(message); \ + } + +/** + * @brief Macro that throws `%LightningException` with the given expression and + * source location if expression evaluates to false. + * + * @param expression an expression + */ +#define PL_ASSERT(expression) \ + PL_ABORT_IF_NOT(expression, "Assertion failed: " #expression) + +namespace Pennylane::Util { + +/** + * @brief `%LightningException` is the general exception thrown by PennyLane for + * runtime errors. + * + */ +class LightningException : public std::exception { + public: + /** + * @brief Constructs a new `%LightningException` exception. + * + * @param err_msg Error message explaining the exception condition. + */ + explicit LightningException(const std::string &err_msg) noexcept + : err_msg(err_msg) {} + + /** + * @brief Destroys the `%LightningException` object. + */ + virtual ~LightningException() = default; + + /** + * @brief Returns a string containing the exception message. Overrides + * the `std::exception` method. + * + * @return Exception message. + */ + const char *what() const noexcept { return err_msg.c_str(); } + + private: + std::string err_msg; +}; + +/** + * @brief Throws a `%LightningException` with the given error message. + * + * This function should not be called directly - use one of the `PL_ASSERT()` + * or `PL_ABORT()` macros, which provide the source location at compile time. + * + * @param message string literal describing the error + * @param file_name source file where error occured + * @param line line of source file + * @param function_name function in which error occured + */ +inline void Abort(const char *message, const char *file_name, int line, + const char *function_name) { + std::stringstream err_msg; + err_msg << "[" << file_name << "][Line:" << line + << "][Method:" << function_name + << "]: Error in PennyLane Lightning: " << message; + throw LightningException(err_msg.str()); +} + +} // namespace Pennylane::Util \ No newline at end of file From 8f1a585fe65279cf966cfdb2deebd008f731c1f9 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 15:12:10 +0100 Subject: [PATCH 30/72] Add param gateset tests for SVM --- pennylane_lightning/src/tests/CMakeLists.txt | 1 + .../tests/Test_StateVectorManaged_Param.cpp | 580 ++++++++++++++++++ 2 files changed, 581 insertions(+) create mode 100644 pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index 215d573a7a..e7f2c665b6 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -37,6 +37,7 @@ target_sources(runner PRIVATE Test_AdjDiff.cpp Test_StateVector_Nonparam.cpp Test_StateVector_Param.cpp Test_StateVectorManaged_Nonparam.cpp + Test_StateVectorManaged_Param.cpp Test_Util.cpp ) diff --git a/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp new file mode 100644 index 0000000000..0a119d2d01 --- /dev/null +++ b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp @@ -0,0 +1,580 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Gates.hpp" +#include "StateVector.hpp" +#include "Util.hpp" + +#include "TestHelpers.hpp" + +using namespace Pennylane; + +TEMPLATE_TEST_CASE("StateVectorManaged::applyRX", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + const std::vector angles{0.1, 0.6, 2.1}; + std::vector> expected_results{ + std::vector(8), std::vector(8), std::vector(8)}; + + for (size_t i = 0; i < angles.size(); i++) { + const auto rx_mat = Gates::getRX(angles[i]); + expected_results[i][0] = rx_mat[0]; + expected_results[i][0b1 << (num_qubits - i - 1)] = rx_mat[1]; + } + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{num_qubits}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + svdat_direct.applyRX(int_idx, ext_idx, false, {angles[index]}); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{num_qubits}; + svdat_dispatch.applyOperation("RX", {index}, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyRY", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + const std::vector angles{0.2, 0.7, 2.9}; + std::vector> expected_results{ + std::vector(8), std::vector(8), std::vector(8)}; + + for (size_t i = 0; i < angles.size(); i++) { + const auto ry_mat = Gates::getRY(angles[i]); + expected_results[i][0] = ry_mat[0]; + expected_results[i][0b1 << (num_qubits - i - 1)] = ry_mat[2]; + } + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{num_qubits}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + svdat_direct.applyRY(int_idx, ext_idx, false, {angles[index]}); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{num_qubits}; + svdat_dispatch.applyOperation("RY", {index}, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyRZ", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + const std::vector angles{0.2, 0.7, 2.9}; + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> rz_data; + for (auto &a : angles) { + rz_data.push_back(Gates::getRZ(a)); + } + + std::vector> expected_results = { + {rz_data[0][0], rz_data[0][0], rz_data[0][0], rz_data[0][0], + rz_data[0][3], rz_data[0][3], rz_data[0][3], rz_data[0][3]}, + { + rz_data[1][0], + rz_data[1][0], + rz_data[1][3], + rz_data[1][3], + rz_data[1][0], + rz_data[1][0], + rz_data[1][3], + rz_data[1][3], + }, + {rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3], + rz_data[2][0], rz_data[2][3], rz_data[2][0], rz_data[2][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + svdat_direct.applyRZ(int_idx, ext_idx, false, {angles[index]}); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + svdat_dispatch.applyOperation("RZ", {index}, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyPhaseShift", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + const std::vector angles{0.3, 0.8, 2.4}; + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> ps_data; + for (auto &a : angles) { + ps_data.push_back(Gates::getPhaseShift(a)); + } + + std::vector> expected_results = { + {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], + ps_data[0][3], ps_data[0][3], ps_data[0][3], ps_data[0][3]}, + { + ps_data[1][0], + ps_data[1][0], + ps_data[1][3], + ps_data[1][3], + ps_data[1][0], + ps_data[1][0], + ps_data[1][3], + ps_data[1][3], + }, + {ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3], + ps_data[2][0], ps_data[2][3], ps_data[2][0], ps_data[2][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + + svdat_direct.applyPhaseShift(int_idx, ext_idx, false, + {angles[index]}); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{init_state}; + svdat_dispatch.applyOperation("PhaseShift", {index}, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyControlledPhaseShift", + "[StateVectorManaged_Param]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + // Test using |+++> state + svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {{false}, {false}, {false}}); + + const std::vector angles{0.3, 2.4}; + const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); + + std::vector> ps_data; + for (auto &a : angles) { + ps_data.push_back(Gates::getPhaseShift(a)); + } + + std::vector> expected_results = { + {ps_data[0][0], ps_data[0][0], ps_data[0][0], ps_data[0][0], + ps_data[0][0], ps_data[0][0], ps_data[0][3], ps_data[0][3]}, + {ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3], + ps_data[1][0], ps_data[1][0], ps_data[1][0], ps_data[1][3]}}; + + for (auto &vec : expected_results) { + scaleVector(vec, coef); + } + + const auto init_state = svdat.getDataVector(); + SECTION("Apply directly") { + StateVectorManaged svdat_direct{init_state}; + auto int_idx = svdat_direct.getInternalIndices({0, 1}); + auto ext_idx = svdat_direct.getExternalIndices({0, 1}); + + svdat_direct.applyControlledPhaseShift(int_idx, ext_idx, false, + {angles[0]}); + CAPTURE(svdat_direct.getDataVector()); + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[0])); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat_dispatch{init_state}; + svdat_dispatch.applyOperation("ControlledPhaseShift", {1, 2}, false, + {angles[1]}); + CAPTURE(svdat_dispatch.getDataVector()); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[1])); + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyRot", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + const std::vector> angles{ + std::vector{0.3, 0.8, 2.4}, + std::vector{0.5, 1.1, 3.0}, + std::vector{2.3, 0.1, 0.4}}; + + std::vector> expected_results{ + std::vector(0b1 << num_qubits), + std::vector(0b1 << num_qubits), + std::vector(0b1 << num_qubits)}; + + for (size_t i = 0; i < angles.size(); i++) { + const auto rot_mat = + Gates::getRot(angles[i][0], angles[i][1], angles[i][2]); + expected_results[i][0] = rot_mat[0]; + expected_results[i][0b1 << (num_qubits - i - 1)] = rot_mat[2]; + } + + SECTION("Apply directly") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_direct{num_qubits}; + auto int_idx = svdat_direct.getInternalIndices({index}); + auto ext_idx = svdat_direct.getExternalIndices({index}); + svdat_direct.applyRot(int_idx, ext_idx, false, angles[index][0], + angles[index][1], angles[index][2]); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < num_qubits; index++) { + StateVectorManaged svdat_dispatch{num_qubits}; + svdat_dispatch.applyOperation("Rot", {index}, false, + angles[index]); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", "[StateVectorManaged_Param]", float, + double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + StateVectorManaged svdat{num_qubits}; + + const std::vector angles{0.3, 0.8, 2.4}; + + std::vector expected_results(8); + const auto rot_mat = + Gates::getRot(angles[0], angles[1], angles[2]); + expected_results[0b1 << (num_qubits - 1)] = rot_mat[0]; + expected_results[(0b1 << num_qubits) - 2] = rot_mat[2]; + + const auto init_state = svdat.getDataVector(); + + SECTION("Apply directly") { + SECTION("CRot0,1 |000> -> |000>") { + StateVectorManaged svdat_direct{num_qubits}; + auto int_idx = svdat_direct.getInternalIndices({0, 1}); + auto ext_idx = svdat_direct.getExternalIndices({0, 1}); + svdat_direct.applyCRot(int_idx, ext_idx, false, angles[0], + angles[1], angles[2]); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), init_state)); + } + SECTION("CRot0,1 |100> -> |1>(a|0>+b|1>)|0>") { + StateVectorManaged svdat_direct{num_qubits}; + svdat_direct.applyOperation("PauliX", {0}); + + auto int_idx = svdat_direct.getInternalIndices({0, 1}); + auto ext_idx = svdat_direct.getExternalIndices({0, 1}); + + svdat_direct.applyCRot(int_idx, ext_idx, false, angles[0], + angles[1], angles[2]); + + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results)); + } + } + SECTION("Apply using dispatcher") { + SECTION("CRot0,1 |100> -> |1>(a|0>+b|1>)|0>") { + StateVectorManaged svdat_direct{num_qubits}; + svdat_direct.applyOperation("PauliX", {0}); + + svdat_direct.applyOperation("CRot", {0, 1}, false, angles); + CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results)); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManaged_Param]", + float, double) { + using cp_t = std::complex; + const size_t num_qubits = 5; + + // Note: gates are defined as right-to-left order + + SECTION("Apply XZ gate") { + const std::vector xz_gate{ + Util::ZERO(), Util::ONE(), + -Util::ONE(), Util::ZERO()}; + + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(xz_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliX(int_idx, ext_idx, false); + svdat_expected.applyPauliZ(int_idx, ext_idx, false); + } + + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliX"}, {"PauliZ"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(xz_gate, {index}, false); + } + + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } + SECTION("Apply ZX gate") { + const std::vector zx_gate{ + Util::ZERO(), -Util::ONE(), + Util::ONE(), Util::ZERO()}; + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(zx_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliZ(int_idx, ext_idx, false); + svdat_expected.applyPauliX(int_idx, ext_idx, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliZ"}, {"PauliX"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(zx_gate, {index}, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } + SECTION("Apply XY gate") { + const std::vector xy_gate{ + -Util::IMAG(), Util::ZERO(), + Util::ZERO(), Util::IMAG()}; + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(xy_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliX(int_idx, ext_idx, false); + svdat_expected.applyPauliY(int_idx, ext_idx, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliX"}, {"PauliY"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(xy_gate, {index}, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } + SECTION("Apply YX gate") { + const std::vector yx_gate{ + Util::IMAG(), Util::ZERO(), + Util::ZERO(), -Util::IMAG()}; + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(yx_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliY(int_idx, ext_idx, false); + svdat_expected.applyPauliX(int_idx, ext_idx, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliY"}, {"PauliX"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(yx_gate, {index}, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } + SECTION("Apply YZ gate") { + const std::vector yz_gate{ + Util::ZERO(), -Util::IMAG(), + -Util::IMAG(), Util::ZERO()}; + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(yz_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliY(int_idx, ext_idx, false); + svdat_expected.applyPauliZ(int_idx, ext_idx, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliY"}, {"PauliZ"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(yz_gate, {index}, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } + SECTION("Apply ZY gate") { + const std::vector zy_gate{ + Util::ZERO(), Util::IMAG(), + Util::IMAG(), Util::ZERO()}; + SECTION("Apply directly") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + auto int_idx = svdat.getInternalIndices({index}); + auto ext_idx = svdat.getExternalIndices({index}); + svdat.applyMatrix(zy_gate, int_idx, ext_idx, false); + + svdat_expected.applyPauliZ(int_idx, ext_idx, false); + svdat_expected.applyPauliY(int_idx, ext_idx, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + SECTION("Apply using dispatcher") { + StateVectorManaged svdat{num_qubits}; + StateVectorManaged svdat_expected{num_qubits}; + + for (size_t index = 0; index < num_qubits; index++) { + svdat_expected.applyOperations({{"PauliZ"}, {"PauliY"}}, + {{index}, {index}}, + {false, false}); + svdat.applyOperation(zy_gate, {index}, false); + } + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } + } +} + +TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix multiple wires", + "[StateVectorManaged_Param]", float, double) { + using cp_t = std::complex; + const size_t num_qubits = 3; + + StateVectorManaged svdat_init{num_qubits}; + svdat_init.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, + {{0}, {1}, {2}}, {false, false, false}); + + const auto cz_gate = Gates::getCZ(); + const auto tof_gate = Gates::getToffoli(); + const auto arb_gate = Gates::getToffoli(); + + SECTION("Apply CZ gate") { + StateVectorManaged svdat{svdat_init.getDataVector()}; + StateVectorManaged svdat_expected{svdat_init.getDataVector()}; + + svdat_expected.applyOperations( + {{"Hadamard"}, {"CNOT"}, {"Hadamard"}}, {{1}, {0, 1}, {1}}, + {false, false, false}); + svdat.applyOperation(cz_gate, {0, 1}, false); + + CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + } +} From ebcf2169c88f095841c81d842e9c1efe233a3296 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 16:08:21 +0100 Subject: [PATCH 31/72] Fixed memory access issues with memory managed statevector --- .../src/algorithms/AdjointDiff.hpp | 381 ++++++------------ .../src/simulator/StateVector.hpp | 16 +- .../src/tests/Test_AdjDiff.cpp | 62 +-- .../tests/Test_StateVectorManaged_Param.cpp | 167 ++++---- 4 files changed, 263 insertions(+), 363 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index f33b707d33..c591017250 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -9,6 +9,7 @@ #include #include +#include "Error.hpp" #include "StateVector.hpp" #include "Util.hpp" @@ -17,6 +18,7 @@ // Generators not needed outside this translation unit namespace { +using namespace Pennylane; using namespace Pennylane::Util; template static constexpr std::vector> getP00() { @@ -27,33 +29,28 @@ template static constexpr std::vector> getP11() { return {ZERO(), ZERO(), ZERO(), ONE()}; } -template -void applyGeneratorRX(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorRX(SVType &sv, const std::vector &wires) { sv.applyOperation("PauliX", wires, false); } -template -void applyGeneratorRY(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorRY(SVType &sv, const std::vector &wires) { sv.applyOperation("PauliY", wires, false); } -template -void applyGeneratorRZ(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorRZ(SVType &sv, const std::vector &wires) { sv.applyOperation("PauliZ", wires, false); } -template -void applyGeneratorPhaseShift(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorPhaseShift(SVType &sv, const std::vector &wires) { sv.applyOperation(getP11(), wires, false); } -template -void applyGeneratorCRX(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorCRX(SVType &sv, const std::vector &wires) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -66,9 +63,8 @@ void applyGeneratorCRX(Pennylane::StateVector &sv, } } -template -void applyGeneratorCRY(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorCRY(SVType &sv, const std::vector &wires) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -83,9 +79,8 @@ void applyGeneratorCRY(Pennylane::StateVector &sv, } } -template -void applyGeneratorCRZ(Pennylane::StateVector &sv, - const std::vector &wires) { +template > +void applyGeneratorCRZ(SVType &sv, const std::vector &wires) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -97,8 +92,8 @@ void applyGeneratorCRZ(Pennylane::StateVector &sv, } } -template -void applyGeneratorControlledPhaseShift(Pennylane::StateVector &sv, +template > +void applyGeneratorControlledPhaseShift(SVType &sv, const std::vector &wires) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); @@ -112,34 +107,6 @@ void applyGeneratorControlledPhaseShift(Pennylane::StateVector &sv, } } -template -class SVUnique : public Pennylane::StateVector { - private: - std::unique_ptr> arr_; - size_t length_; - size_t num_qubits_; - - public: - SVUnique(size_t data_size) - : arr_{new std::complex[data_size]}, length_{data_size}, - num_qubits_{log2(length_)}, Pennylane::StateVector{arr_.get(), - data_size} {} - - SVUnique(const Pennylane::StateVector &sv) - : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - length_ = sv.getLength(); - num_qubits_ = sv.getNumQubits(); - }; - - SVUnique(const SVUnique &sv) : SVUnique(sv.getLength()) { - std::copy(sv.getData(), sv.getData() + sv.getLength(), arr_.get()); - }; - - std::complex *getData() { return arr_.get(); } - std::complex *getData() const { return arr_.get(); } -}; - } // namespace namespace Pennylane { @@ -181,15 +148,21 @@ template struct OpsData { const std::vector ops_inverses_; const std::vector>> ops_matrices_; - OpsData( - const std::vector &ops_name, - const std::vector> &ops_params, - const std::vector> &ops_wires, - const std::vector &ops_inverses, - const std::vector>> &ops_matrices = {{}}) + OpsData(const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector>> &ops_matrices) : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, ops_inverses_{ops_inverses}, ops_matrices_{ops_matrices} {}; + OpsData(const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses) + : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, + ops_inverses_{ops_inverses}, ops_matrices_(ops_name.size()){}; + size_t getSize() const { return ops_name_.size(); } const std::vector &getOpsName() const { return ops_name_; } const std::vector> &getOpsParams() const { @@ -207,18 +180,19 @@ template struct OpsData { template class AdjointJacobian { private: typedef void (*GeneratorFunc)( - Pennylane::StateVector &sv, + StateVectorManaged &sv, const std::vector &wires); // function pointer type const std::unordered_map generator_map{ - {"RX", &::applyGeneratorRX}, - {"RY", &::applyGeneratorRY}, - {"RZ", &::applyGeneratorRZ}, - {"PhaseShift", &::applyGeneratorPhaseShift}, - {"CRX", &::applyGeneratorCRX}, - {"CRY", &::applyGeneratorCRY}, - {"CRZ", &::applyGeneratorCRZ}, - {"ControlledPhaseShift", &::applyGeneratorControlledPhaseShift}}; + {"RX", &::applyGeneratorRX>}, + {"RY", &::applyGeneratorRY>}, + {"RZ", &::applyGeneratorRZ>}, + {"PhaseShift", &::applyGeneratorPhaseShift>}, + {"CRX", &::applyGeneratorCRX>}, + {"CRY", &::applyGeneratorCRY>}, + {"CRZ", &::applyGeneratorCRZ>}, + {"ControlledPhaseShift", + &::applyGeneratorControlledPhaseShift>}}; const std::unordered_map scaling_factors{ {"RX", -0.5}, {"RY", -0.5}, @@ -226,6 +200,39 @@ template class AdjointJacobian { {"CRX", -0.5}, {"CRY", -0.5}, {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; + inline void updateJacobian(const std::complex *sv1, + const std::complex *sv2, std::vector &jac, + size_t num_elements, T scaling_coeff, + size_t index) { + jac[index] = + -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2, num_elements)); + } + inline void updateJacobian(const std::vector> &sv1, + const std::vector> &sv2, + std::vector &jac, size_t num_elements, + T scaling_coeff, size_t index) { + PL_ASSERT(index < jac.size()); + jac[index] = -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); + } + inline void applyOperations(StateVectorManaged &state, + const OpsData &operations) { + for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); + op_idx++) { + state.applyOperation(operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); + } + } + inline void applyObservable(StateVectorManaged &state, + const ObsDatum &observable) { + for (size_t j = 0; j < observable.getSize(); j++) { + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], false, + observable.getObsParams()[j]); + } + } + public: AdjointJacobian() {} @@ -233,7 +240,7 @@ template class AdjointJacobian { createObs(const std::vector &obs_name, const std::vector> &obs_params, const std::vector> &obs_wires) { - return ObsDatum(obs_name, obs_params, obs_wires); + return {obs_name, obs_params, obs_wires}; } const OpsData createOpsData( @@ -242,8 +249,7 @@ template class AdjointJacobian { const std::vector> &ops_wires, const std::vector &ops_inverses, const std::vector>> &ops_matrices = {{}}) { - return OpsData(ops_name, ops_params, ops_wires, ops_inverses, - ops_matrices); + return {ops_name, ops_params, ops_wires, ops_inverses, ops_matrices}; } std::vector> @@ -259,221 +265,78 @@ template class AdjointJacobian { size_t num_params) { const size_t num_observables = observables.size(); + unsigned int trainableParamNumber = trainableParams.size() - 1; + int current_param_idx = num_params - 1; - auto lambda_data = copyStateData(psi, num_elements); - StateVector lambda(lambda_data.data(), num_elements); + // 1. Create $U_{1:p}\vert \lambda \rangle$ + StateVectorManaged lambda(psi, num_elements); - for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); - op_idx++) { - lambda.applyOperation(operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); - } - std::vector>> H_lambda_data; - std::vector> H_lambda; + applyOperations(lambda, operations); + + // 2. Create observable-applied state-vectors + std::vector> H_lambda; + //#pragma omp parallel for + //{ for (size_t h_i = 0; h_i < num_observables; h_i++) { - H_lambda_data.push_back(lambda_data); - H_lambda.push_back( - StateVector(H_lambda_data[h_i].data(), num_elements)); - - for (size_t j = 0; j < observables[h_i].getSize(); j++) { - H_lambda[h_i].applyOperation( - observables[h_i].getObsName()[j], - observables[h_i].getObsWires()[j], false, - observables[h_i].getObsParams()[j]); - } + H_lambda.push_back(lambda); + applyObservable(H_lambda[h_i], observables[h_i]); } - std::vector> phi_data(lambda_data); - StateVector phi(phi_data.data(), num_elements); + //} + StateVectorManaged mu(lambda.getNumQubits()); for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; op_idx--) { - std::vector> mu_data(lambda_data); - StateVector mu(mu_data.data(), num_elements); - - phi.applyOperation(operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); - - generator_map.at(operations.getOpsName()[op_idx])( - mu, operations.getOpsWires()[op_idx]); - const T scalingFactor = - scaling_factors.at(operations.getOpsName()[op_idx]); - - for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - std::cout << "IDX=" << op_idx * num_observables + obs_idx - << " Exp=" - << -2 * scalingFactor * - std::imag( - innerProdC(H_lambda[obs_idx].getData(), - mu.getData(), num_elements)) - << std::endl; - jac[op_idx * num_observables + obs_idx] = - -2 * scalingFactor * - std::imag(innerProdC(H_lambda[obs_idx].getData(), - mu.getData(), num_elements)); - if (op_idx > 0) { - H_lambda[obs_idx].applyOperation( - operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); - } - } - } - // return jacobian; - } -}; - -} // namespace Algorithms -} // namespace Pennylane - -/* - void adjointJacobian(StateVector &psi, std::vector &jac, - const vector> &observables, - const vector> &obsParams, - const vector> &obsWires, - const vector &operations, - const vector> &opParams, - const vector> &opWires, - const vector &trainableParams, - size_t num_params) { - - size_t numObservables = observables.size(); - int trainableParamNumber = trainableParams.size() - 1; - int current_param_idx = num_params - 1; - - const size_t num_elements = psi.getLength(); - - // 1. Copy the input state, create lambda - std::unique_ptr[]> SV_lambda_data( - new std::complex[num_elements]); - std::copy(psi.getData(), psi.getData() + num_elements, - SV_lambda_data.get()); - StateVector SV_lambda(SV_lambda_data.get(), num_elements); - - // 2. Apply the unitaries (\hat{U}_{1:P}) to lambda - std::vector inverses(operations.size(), false); - SV_lambda.applyOperations(operations, opWires, inverses, opParams); - - // 3-4. Copy lambda and apply the observables - // SV_lambda becomes |phi> - - std::unique_ptr[]> phi_data( - new std::complex[num_elements]); - std::copy(SV_lambda.getData(), SV_lambda.getData() + num_elements, - phi_data.get()); - StateVector phi_1(phi_data.get(), num_elements); - - std::vector> lambdas; - lambdas.reserve(numObservables); - std::vector[]>> lambdas_data; - lambdas_data.reserve(numObservables); - for (size_t i = 0; i < numObservables; i++) { - lambdas_data.emplace_back(new std::complex[num_elements]); - lambdas.emplace_back( - StateVector(lambdas_data[i].get(), num_elements)); - } - - //#pragma omp parallel for - for (size_t i = 0; i < numObservables; i++) { - // copy |phi> and apply observables one at a time - std::copy(SV_lambda_data.get(), SV_lambda_data.get() + num_elements, - lambdas_data[i].get()); - - lambdas[i].applyOperation(observables[i], obsWires[i], false, - obsParams[i]); - } - - // replace with reverse iterator over values? - for (int i = operations.size() - 1; i >= 0; i--) { + PL_ABORT_IF(operations.getOpsParams()[op_idx].size() > 1, + "The operation is not supported using the adjoint " + "differentiation method"); - if (opParams[i].size() > 1) { - throw std::invalid_argument( - "The operation is not supported using " - "the adjoint differentiation method"); - } else if ((operations[i] != "QubitStateVector") && - (operations[i] != "BasisState")) { + if ((operations.getOpsName()[op_idx] != "QubitStateVector") && + (operations.getOpsName()[op_idx] != "BasisState")) { - std::unique_ptr[]> mu_data( - new std::complex[num_elements]); - std::copy(phi_1.getData(), phi_1.getData() + num_elements, - mu_data.get()); + mu.updateData(lambda.getDataVector()); - StateVector mu(mu_data.get(), num_elements); - - // create |phi'> = Uj*|phi> - phi_1.applyOperation(operations[i], opWires[i], true, - opParams[i]); - - // We have a parametrized gate - if (!opParams[i].empty()) { + lambda.applyOperation(operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); + if (!operations.getOpsParams()[op_idx].empty()) { if (std::find(trainableParams.begin(), trainableParams.end(), current_param_idx) != trainableParams.end()) { - // create iH|phi> = d/d dUj/dtheta Uj* |phi> = - // dUj/dtheta|phi'> + generator_map.at(operations.getOpsName()[op_idx])( + mu, operations.getOpsWires()[op_idx]); const T scalingFactor = - scaling_factors.at(operations[i]); - - generator_map.at(operations[i])(mu, opWires[i]); - - for (size_t j = 0; j < lambdas.size(); j++) { - - std::complex sum = - innerProdC(lambdas[j].getData(), mu.getData(), - num_elements); - - // calculate 2 * shift * Real(i * sum) = -2 * shift - // * Imag(sum) - std::cout << "L[" << i << ", " << j - << "]=" << lambdas[j] << std::endl; - std::cout << "mu[" << i << ", " << j << "]=" << mu - << std::endl - << std::endl; - jac[j * trainableParams.size() + - trainableParamNumber] = - // 2 * scalingFactor * std::real(sum); - -2 * scalingFactor * std::imag(sum); + scaling_factors.at(operations.getOpsName()[op_idx]); + + size_t index; + for (size_t obs_idx = 0; obs_idx < num_observables; + obs_idx++) { + index = obs_idx * trainableParams.size() + + trainableParamNumber; + updateJacobian(H_lambda[obs_idx].getData(), + mu.getData(), jac, num_elements, + scalingFactor, index); } trainableParamNumber--; } current_param_idx--; } - - for (size_t j = 0; j < lambdas.size(); j++) { - lambdas[j].applyOperation(operations[i], opWires[i], true, - opParams[i]); + for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + H_lambda[obs_idx].applyOperation( + operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); } } - /// missing else? } } - */ - -/* - - -template -inline std::ostream &operator<<(std::ostream &out, const SVUnique &sv) { - const size_t num_qubits = sv.getNumQubits(); - const size_t length = sv.getLength(); - const auto data_ptr = sv.getData(); - out << "num_qubits=" << num_qubits << std::endl; - out << "data=["; - out << data_ptr[0]; - for (size_t i = 1; i < length - 1; i++) { - out << "," << data_ptr[i]; - } - out << "," << data_ptr[length - 1] << "]"; - - return out; -} + // return jacobian; +}; -*/ +} // namespace Algorithms +} // namespace Pennylane \ No newline at end of file diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 1d1de9a5ac..4e365041c5 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -30,6 +30,7 @@ #include #include +#include "Error.hpp" #include "Gates.hpp" #include "Util.hpp" @@ -1083,7 +1084,7 @@ class StateVectorManaged : public StateVector { StateVector::setData(data_.data()); data_[0] = {1, 0}; } - StateVectorManaged(const StateVector other) + StateVectorManaged(const StateVector &other) : data_{other.getData(), other.getData() + other.getLength()}, StateVector(nullptr, data_.size()) { StateVector::setData(data_.data()); @@ -1094,17 +1095,17 @@ class StateVectorManaged : public StateVector { } StateVectorManaged(const CFP_t *other_data, size_t other_size) : data_{other_data, other_data + other_size}, StateVector( - nullptr, - data_.size()) { + nullptr, other_size) { StateVector::setData(data_.data()); } StateVectorManaged(const StateVectorManaged &other) - : data_{other.data_}, StateVector(nullptr, data_.size()) { + : data_{other.data_}, StateVector(nullptr, + other.getDataVector().size()) { StateVector::setData(data_.data()); } std::vector &getDataVector() { return data_; } - std::vector &getDataVector() const { return data_; } + const std::vector &getDataVector() const { return data_; } std::vector getInternalIndices(const std::vector &qubit_indices) { @@ -1121,6 +1122,11 @@ class StateVectorManaged : public StateVector { Util::log2(data_.size())); return externalIndices; } + void updateData(const std::vector &new_data) { + PL_ABORT_IF_NOT(data_.size() == new_data.size(), + "New data must be the same size as old data.") + std::copy(new_data.begin(), new_data.end(), data_.begin()); + } }; template diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 8a1699ce47..d4bb7b11b1 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -43,20 +43,18 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { SECTION("RX gradient") { const size_t num_qubits = 1; - const size_t num_params = 1; + const size_t num_params = 3; const size_t num_obs = 1; - auto obs = adj.createObs({"PauliZ"}, {{}}, {{0}}); + std::vector jacobian(num_obs * num_params, 0.0); for (const auto &p : param) { auto ops = adj.createOpsData({"RX"}, {{p}}, {{0}}, {false}); - std::vector jacobian(num_obs * num_params, 0.0); - std::vector> cdata(0b1 << num_qubits); - StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; + StateVector psi(cdata.data(), cdata.size()); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, ops, {0}, 1); CAPTURE(jacobian); @@ -66,20 +64,20 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { SECTION("RY gradient") { const size_t num_qubits = 1; - const size_t num_params = 1; + const size_t num_params = 3; const size_t num_obs = 1; auto obs = adj.createObs({"PauliX"}, {{}}, {{0}}); + std::vector jacobian(num_obs * num_params, 0.0); for (const auto &p : param) { auto ops = adj.createOpsData({"RY"}, {{p}}, {{0}}, {false}); - std::vector jacobian(num_obs * num_params, 0.0); - std::vector> cdata(0b1 << num_qubits); - StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; + StateVector psi(cdata.data(), cdata.size()); + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, ops, {0}, 1); @@ -107,6 +105,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); + CHECK(0.0 == Approx(jacobian[1]).margin(1e-7)); } SECTION("Multiple RX gradient, single expval per wire") { const size_t num_qubits = 3; @@ -130,7 +129,12 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {obs1, obs2, obs3}, ops, {0, 1, 2}, num_params); CAPTURE(jacobian); - CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); + CHECK(-sin(param[0]) == + Approx(jacobian[0 * num_params + 0]).margin(1e-7)); + CHECK(-sin(param[1]) == + Approx(jacobian[1 * num_params + 1]).margin(1e-7)); + CHECK(-sin(param[2]) == + Approx(jacobian[2 * num_params + 2]).margin(1e-7)); } SECTION("Multiple RX gradient, tensor expval") { const size_t num_qubits = 3; @@ -168,29 +172,31 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}, {}, {}}, + auto obs = adj.createObs({"PauliX", "PauliX", "PauliX"}, {{}, {}, {}}, {{0}, {1}, {2}}); - auto ops = - adj.createOpsData({"RX", "RX", "RX", "RY", "RY", "RY"}, - {{param[0]}, - {param[1]}, - {param[2]}, - {param[0]}, - {param[1]}, - {param[2]}}, - {{0}, {1}, {2}, {0}, {1}, {2}}, - {false, false, false, false, false, false}); + auto ops = adj.createOpsData( + {"RZ", "RY", "RZ", "CNOT", "CNOT", "RZ", "RY", "RZ"}, + {{param[0]}, + {param[1]}, + {param[2]}, + {}, + {}, + {param[0]}, + {param[1]}, + {param[2]}}, + {{0}, {0}, {0}, {0, 1}, {1, 2}, {1}, {1}, {1}}, + {false, false, false, false, false, false, false, false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, ops, {0, 1, 2, 3, 4, 5}, num_params); CAPTURE(jacobian); - // Computed with parameter shift - CHECK(0.06396442 == Approx(jacobian[0]).margin(1e-7)); - CHECK(-0.09650191 == Approx(jacobian[1]).margin(1e-7)); - CHECK(0.23005702 == Approx(jacobian[2]).margin(1e-7)); - CHECK(0.06396442 == Approx(jacobian[3]).margin(1e-7)); - CHECK(-0.09650191 == Approx(jacobian[4]).margin(1e-7)); - CHECK(0.23005702 == Approx(jacobian[5]).margin(1e-7)); + // Computed with PennyLane using default.qubit.adjoint_jacobian + CHECK(0.0 == Approx(jacobian[0]).margin(1e-7)); + CHECK(-0.674214427 == Approx(jacobian[1]).margin(1e-7)); + CHECK(0.275139672 == Approx(jacobian[2]).margin(1e-7)); + CHECK(0.275139672 == Approx(jacobian[3]).margin(1e-7)); + CHECK(-0.0129093062 == Approx(jacobian[4]).margin(1e-7)); + CHECK(0.323846156 == Approx(jacobian[5]).margin(1e-7)); } } diff --git a/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp index 0a119d2d01..882148a2c1 100644 --- a/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp +++ b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp @@ -16,8 +16,8 @@ using namespace Pennylane; -TEMPLATE_TEST_CASE("StateVectorManaged::applyRX", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyRX", "[StateVectorManaged_Param]", + float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; @@ -41,21 +41,23 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyRX", "[StateVectorManaged_Param]", svdat_direct.applyRX(int_idx, ext_idx, false, {angles[index]}); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); } } SECTION("Apply using dispatcher") { for (size_t index = 0; index < num_qubits; index++) { StateVectorManaged svdat_dispatch{num_qubits}; svdat_dispatch.applyOperation("RX", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); } } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyRY", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyRY", "[StateVectorManaged_Param]", + float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; @@ -79,28 +81,30 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyRY", "[StateVectorManaged_Param]", svdat_direct.applyRY(int_idx, ext_idx, false, {angles[index]}); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); } } SECTION("Apply using dispatcher") { for (size_t index = 0; index < num_qubits; index++) { StateVectorManaged svdat_dispatch{num_qubits}; svdat_dispatch.applyOperation("RY", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); } } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyRZ", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyRZ", "[StateVectorManaged_Param]", + float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; // Test using |+++> state svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, - {{0}, {1}, {2}}, {{false}, {false}, {false}}); + {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.2, 0.7, 2.9}; const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); @@ -139,28 +143,30 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyRZ", "[StateVectorManaged_Param]", svdat_direct.applyRZ(int_idx, ext_idx, false, {angles[index]}); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); } } SECTION("Apply using dispatcher") { for (size_t index = 0; index < num_qubits; index++) { StateVectorManaged svdat_dispatch{init_state}; svdat_dispatch.applyOperation("RZ", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); } } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyPhaseShift", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyPhaseShift", + "[StateVectorManaged_Param]", float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; // Test using |+++> state svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, - {{0}, {1}, {2}}, {{false}, {false}, {false}}); + {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 0.8, 2.4}; const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); @@ -198,17 +204,19 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyPhaseShift", "[StateVectorManaged_P auto ext_idx = svdat_direct.getExternalIndices({index}); svdat_direct.applyPhaseShift(int_idx, ext_idx, false, - {angles[index]}); + {angles[index]}); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); } } SECTION("Apply using dispatcher") { for (size_t index = 0; index < num_qubits; index++) { StateVectorManaged svdat_dispatch{init_state}; svdat_dispatch.applyOperation("PhaseShift", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); } } } @@ -221,7 +229,7 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyControlledPhaseShift", // Test using |+++> state svdat.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, - {{0}, {1}, {2}}, {{false}, {false}, {false}}); + {{0}, {1}, {2}}, {{false}, {false}, {false}}); const std::vector angles{0.3, 2.4}; const cp_t coef(1.0 / (2 * std::sqrt(2)), 0); @@ -248,21 +256,22 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyControlledPhaseShift", auto ext_idx = svdat_direct.getExternalIndices({0, 1}); svdat_direct.applyControlledPhaseShift(int_idx, ext_idx, false, - {angles[0]}); + {angles[0]}); CAPTURE(svdat_direct.getDataVector()); CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[0])); } SECTION("Apply using dispatcher") { StateVectorManaged svdat_dispatch{init_state}; svdat_dispatch.applyOperation("ControlledPhaseShift", {1, 2}, false, - {angles[1]}); + {angles[1]}); CAPTURE(svdat_dispatch.getDataVector()); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[1])); + CHECK( + isApproxEqual(svdat_dispatch.getDataVector(), expected_results[1])); } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyRot", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyRot", "[StateVectorManaged_Param]", + float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; @@ -290,23 +299,24 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyRot", "[StateVectorManaged_Param]", auto int_idx = svdat_direct.getInternalIndices({index}); auto ext_idx = svdat_direct.getExternalIndices({index}); svdat_direct.applyRot(int_idx, ext_idx, false, angles[index][0], - angles[index][1], angles[index][2]); + angles[index][1], angles[index][2]); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results[index])); + CHECK(isApproxEqual(svdat_direct.getDataVector(), + expected_results[index])); } } SECTION("Apply using dispatcher") { for (size_t index = 0; index < num_qubits; index++) { StateVectorManaged svdat_dispatch{num_qubits}; - svdat_dispatch.applyOperation("Rot", {index}, false, - angles[index]); - CHECK(isApproxEqual(svdat_dispatch.getDataVector(), expected_results[index])); + svdat_dispatch.applyOperation("Rot", {index}, false, angles[index]); + CHECK(isApproxEqual(svdat_dispatch.getDataVector(), + expected_results[index])); } } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", "[StateVectorManaged_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", + "[StateVectorManaged_Param]", float, double) { using cp_t = std::complex; const size_t num_qubits = 3; StateVectorManaged svdat{num_qubits}; @@ -327,7 +337,7 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", "[StateVectorManaged_Param]" auto int_idx = svdat_direct.getInternalIndices({0, 1}); auto ext_idx = svdat_direct.getExternalIndices({0, 1}); svdat_direct.applyCRot(int_idx, ext_idx, false, angles[0], - angles[1], angles[2]); + angles[1], angles[2]); CHECK(isApproxEqual(svdat_direct.getDataVector(), init_state)); } @@ -339,9 +349,10 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", "[StateVectorManaged_Param]" auto ext_idx = svdat_direct.getExternalIndices({0, 1}); svdat_direct.applyCRot(int_idx, ext_idx, false, angles[0], - angles[1], angles[2]); + angles[1], angles[2]); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results)); + CHECK( + isApproxEqual(svdat_direct.getDataVector(), expected_results)); } } SECTION("Apply using dispatcher") { @@ -350,13 +361,14 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyCRot", "[StateVectorManaged_Param]" svdat_direct.applyOperation("PauliX", {0}); svdat_direct.applyOperation("CRot", {0, 1}, false, angles); - CHECK(isApproxEqual(svdat_direct.getDataVector(), expected_results)); + CHECK( + isApproxEqual(svdat_direct.getDataVector(), expected_results)); } } } -TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManaged_Param]", - float, double) { +TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", + "[StateVectorManaged_Param]", float, double) { using cp_t = std::complex; const size_t num_qubits = 5; @@ -380,7 +392,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliZ(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -388,12 +401,13 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliX"}, {"PauliZ"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(xz_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } SECTION("Apply ZX gate") { @@ -412,7 +426,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliZ(int_idx, ext_idx, false); svdat_expected.applyPauliX(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -420,11 +435,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliZ"}, {"PauliX"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(zx_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } SECTION("Apply XY gate") { @@ -443,7 +459,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliX(int_idx, ext_idx, false); svdat_expected.applyPauliY(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -451,11 +468,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliX"}, {"PauliY"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(xy_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } SECTION("Apply YX gate") { @@ -474,7 +492,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliY(int_idx, ext_idx, false); svdat_expected.applyPauliX(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -482,11 +501,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliY"}, {"PauliX"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(yx_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } SECTION("Apply YZ gate") { @@ -505,7 +525,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliY(int_idx, ext_idx, false); svdat_expected.applyPauliZ(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -513,11 +534,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliY"}, {"PauliZ"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(yz_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } SECTION("Apply ZY gate") { @@ -536,7 +558,8 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage svdat_expected.applyPauliZ(int_idx, ext_idx, false); svdat_expected.applyPauliY(int_idx, ext_idx, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } SECTION("Apply using dispatcher") { StateVectorManaged svdat{num_qubits}; @@ -544,11 +567,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix 1 wire", "[StateVectorManage for (size_t index = 0; index < num_qubits; index++) { svdat_expected.applyOperations({{"PauliZ"}, {"PauliY"}}, - {{index}, {index}}, - {false, false}); + {{index}, {index}}, + {false, false}); svdat.applyOperation(zy_gate, {index}, false); } - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } } @@ -560,7 +584,7 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix multiple wires", StateVectorManaged svdat_init{num_qubits}; svdat_init.applyOperations({{"Hadamard"}, {"Hadamard"}, {"Hadamard"}}, - {{0}, {1}, {2}}, {false, false, false}); + {{0}, {1}, {2}}, {false, false, false}); const auto cz_gate = Gates::getCZ(); const auto tof_gate = Gates::getToffoli(); @@ -570,11 +594,12 @@ TEMPLATE_TEST_CASE("StateVectorManaged::applyMatrix multiple wires", StateVectorManaged svdat{svdat_init.getDataVector()}; StateVectorManaged svdat_expected{svdat_init.getDataVector()}; - svdat_expected.applyOperations( - {{"Hadamard"}, {"CNOT"}, {"Hadamard"}}, {{1}, {0, 1}, {1}}, - {false, false, false}); + svdat_expected.applyOperations({{"Hadamard"}, {"CNOT"}, {"Hadamard"}}, + {{1}, {0, 1}, {1}}, + {false, false, false}); svdat.applyOperation(cz_gate, {0, 1}, false); - CHECK(isApproxEqual(svdat.getDataVector(), svdat_expected.getDataVector())); + CHECK(isApproxEqual(svdat.getDataVector(), + svdat_expected.getDataVector())); } } From c1a7c27e9c9bb0320b7bc80dfd290350702687fd Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 17:09:15 +0100 Subject: [PATCH 32/72] Fix C++ adjoint calls --- pennylane_lightning/_serialize.py | 8 ++--- pennylane_lightning/lightning_qubit.py | 43 +++++++++++++++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index 8d5fd7f254..2bb1cdb9df 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -46,9 +46,7 @@ def _obs_has_kernel(obs: Observable) -> bool: return False -def _serialize_obs( - tape: QuantumTape, wires_map: dict -) -> List[ObsStructC128]: +def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: """Serializes the observables of an input tape. Args: @@ -85,7 +83,9 @@ def _serialize_obs( def _serialize_ops( tape: QuantumTape, wires_map: dict -) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]: +) -> Tuple[ + List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray] +]: """Serializes the operations of an input tape. The state preparation operations are not included. diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 941e12520d..235f38023e 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -18,8 +18,13 @@ from warnings import warn import numpy as np -from pennylane import (BasisState, DeviceError, QuantumFunctionError, - QubitStateVector, QubitUnitary) +from pennylane import ( + BasisState, + DeviceError, + QuantumFunctionError, + QubitStateVector, + QubitUnitary, +) from pennylane.devices import DefaultQubit from pennylane.operation import Expectation @@ -27,7 +32,12 @@ from ._version import __version__ try: - from .lightning_qubit_ops import apply, StateVectorC64, StateVectorC128, AdjointJacobianC128 + from .lightning_qubit_ops import ( + apply, + StateVectorC64, + StateVectorC128, + AdjointJacobianC128, + ) CPP_BINARY_AVAILABLE = True except ModuleNotFoundError: @@ -78,10 +88,14 @@ def apply(self, operations, rotations=None, **kwargs): # State preparation is currently done in Python if operations: # make sure operations[0] exists if isinstance(operations[0], QubitStateVector): - self._apply_state_vector(operations[0].parameters[0].copy(), operations[0].wires) + self._apply_state_vector( + operations[0].parameters[0].copy(), operations[0].wires + ) del operations[0] elif isinstance(operations[0], BasisState): - self._apply_basis_state(operations[0].parameters[0], operations[0].wires) + self._apply_basis_state( + operations[0].parameters[0], operations[0].wires + ) del operations[0] for operation in operations: @@ -100,7 +114,9 @@ def apply(self, operations, rotations=None, **kwargs): if any(isinstance(r, QubitUnitary) for r in rotations): super().apply(operations=[], rotations=rotations) else: - self._state = self.apply_lightning(np.copy(self._pre_rotated_state), rotations) + self._state = self.apply_lightning( + np.copy(self._pre_rotated_state), rotations + ) else: self._state = self._pre_rotated_state @@ -119,7 +135,9 @@ def apply_lightning(self, state, operations): sim = StateVectorC128(state_vector) for o in operations: - name = o.name.split(".")[0] # The split is because inverse gates have .inv appended + name = o.name.split(".")[ + 0 + ] # The split is because inverse gates have .inv appended method = getattr(sim, name, None) wires = self.wires.indices(o.wires) @@ -161,18 +179,21 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): # TODO: How to accommodate for tensor product observables? adj = AdjointJacobianC128() - jac = np.zeros((len(tape.observables), len(tape.trainable_params))) obs_serialized = _serialize_obs(tape, self.wire_map) ops_serialized = _serialize_ops(tape, self.wire_map) ops_serialized = adj.create_ops_list(*ops_serialized) - adj.adjoint_jacobian( - jac, ket, obs_serialized, ops_serialized, tape.trainable_params, tape.num_params + jac = adj.adjoint_jacobian( + StateVectorC128(ket), + obs_serialized, + ops_serialized, + list(tape.trainable_params), + tape.num_params, ) - return super().adjoint_jacobian(tape, starting_state, use_device_state) + return jac # super().adjoint_jacobian(tape, starting_state, use_device_state) if not CPP_BINARY_AVAILABLE: From bf181e1d4c0765081d6a4c12b9357523ae6de239 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 19:47:05 +0100 Subject: [PATCH 33/72] Update parallelisation using OpenMP --- .../src/algorithms/AdjointDiff.hpp | 35 ++++++++++-------- .../src/simulator/StateVector.hpp | 37 ++++++++++++++----- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index c591017250..a6b0cbd171 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -274,16 +274,16 @@ template class AdjointJacobian { applyOperations(lambda, operations); // 2. Create observable-applied state-vectors - std::vector> H_lambda; - - //#pragma omp parallel for - //{ - for (size_t h_i = 0; h_i < num_observables; h_i++) { - H_lambda.push_back(lambda); - applyObservable(H_lambda[h_i], observables[h_i]); + std::vector> H_lambda(num_observables, + {lambda.getNumQubits()}); + +#pragma omp parallel for + { + for (size_t h_i = 0; h_i < num_observables; h_i++) { + H_lambda[h_i].updateData(lambda.getDataVector()); + applyObservable(H_lambda[h_i], observables[h_i]); + } } - - //} StateVectorManaged mu(lambda.getNumQubits()); for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; @@ -325,12 +325,17 @@ template class AdjointJacobian { } current_param_idx--; } - for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - H_lambda[obs_idx].applyOperation( - operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); + +#pragma omp parallel for + { + for (size_t obs_idx = 0; obs_idx < num_observables; + obs_idx++) { + H_lambda[obs_idx].applyOperation( + operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); + } } } } diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 4e365041c5..fec15bbfeb 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -85,8 +85,8 @@ template class StateVector { //***********************************************************************// CFP_t *arr_; - const size_t length_; - const size_t num_qubits_; + size_t length_; + size_t num_qubits_; public: StateVector() @@ -169,6 +169,14 @@ template class StateVector { CFP_t *getData() { return arr_; } void setData(CFP_t *data_ptr) { arr_ = data_ptr; } + void setLength(size_t length) { + length_ = length; + num_qubits_ = Util::log2(length_); + } + void setNumQubits(size_t qubits) { + num_qubits_ = qubits; + Util::exp2(num_qubits_); + } /** * @brief Get the number of data elements in the statevector array. @@ -577,10 +585,10 @@ template class StateVector { const vector &externalIndices, bool inverse, Param_t angle) { - const Param_t angle_ = (inverse == true) ? -angle : angle; - const CFP_t c(std::cos(angle_ / 2), 0); + const CFP_t c(std::cos(angle / 2), 0); - const CFP_t js(0, -std::sin(angle_ / 2)); + const CFP_t js = (inverse == true) ? CFP_t(0, -std::sin(-angle / 2)) + : CFP_t(0, std::sin(-angle / 2)); for (const size_t &externalIndex : externalIndices) { CFP_t *shiftedState = arr_ + externalIndex; @@ -607,10 +615,9 @@ template class StateVector { const vector &externalIndices, bool inverse, Param_t angle) { - const Param_t angle_ = (inverse == true) ? -angle : angle; - - const CFP_t c(std::cos(angle_ / 2), 0); - const CFP_t s(std::sin(angle_ / 2), 0); + const CFP_t c(std::cos(angle / 2), 0); + const CFP_t s = (inverse == true) ? CFP_t(-std::sin(angle / 2), 0) + : CFP_t(std::sin(angle / 2), 0); for (const size_t &externalIndex : externalIndices) { CFP_t *shiftedState = arr_ + externalIndex; @@ -1104,6 +1111,18 @@ class StateVectorManaged : public StateVector { StateVector::setData(data_.data()); } + StateVectorManaged &operator=(const StateVectorManaged &other) { + if (this != &other) { + if (data_.size() != other.getLength()) { + data_.resize(other.getLength()); + StateVector::setData(data_.data()); + StateVector::setLength(other.getLength()); + } + std::copy(other.data_.data(), + other.data_.data() + other.getLength(), data_.data()); + } + return *this; + } std::vector &getDataVector() { return data_; } const std::vector &getDataVector() const { return data_; } From 09f9e4b0a6b98a5881ee47fc6c8e444a6a895cf6 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 27 Aug 2021 19:49:52 +0100 Subject: [PATCH 34/72] Update Python and run black --- pennylane_lightning/_serialize.py | 40 ++++++++++++++------------ pennylane_lightning/lightning_qubit.py | 18 ++++-------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index 2bb1cdb9df..d1509268c8 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -17,7 +17,7 @@ from typing import List, Tuple import numpy as np -from pennylane import BasisState, Hadamard, Projector, QubitStateVector +from pennylane import BasisState, Hadamard, Projector, QubitStateVector, Rot from pennylane.grouping import is_pauli_word from pennylane.operation import Observable, Tensor from pennylane.tape import QuantumTape @@ -83,9 +83,7 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: def _serialize_ops( tape: QuantumTape, wires_map: dict -) -> Tuple[ - List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray] -]: +) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]: """Serializes the operations of an input tape. The state preparation operations are not included. @@ -108,24 +106,28 @@ def _serialize_ops( for o in tape.operations: if isinstance(o, (BasisState, QubitStateVector)): continue + elif isinstance(o, Rot): + op_list = o.expand().operations + else: + op_list = [o] - is_inverse = o.inverse - - name = o.name if not is_inverse else o.name[:-4] - names.append(name) + for single_op in op_list: + is_inverse = single_op.inverse - if getattr(StateVectorC128, name, None) is None: - params.append([]) - mats.append(o.matrix) + name = single_op.name if not is_inverse else single_op.name[:-4] + names.append(name) - if is_inverse: - is_inverse = False - else: - params.append(o.parameters) - mats.append([]) + if getattr(StateVectorC128, name, None) is None: + params.append([]) + mats.append(single_op.matrix) - wires_list = o.wires.tolist() - wires.append([wires_map[w] for w in wires_list]) - inverses.append(is_inverse) + if is_inverse: + is_inverse = False + else: + params.append(single_op.parameters) + mats.append([]) + wires_list = single_op.wires.tolist() + wires.append([wires_map[w] for w in wires_list]) + inverses.append(is_inverse) return names, params, wires, inverses, mats diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 235f38023e..ad3f4626a9 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -88,14 +88,10 @@ def apply(self, operations, rotations=None, **kwargs): # State preparation is currently done in Python if operations: # make sure operations[0] exists if isinstance(operations[0], QubitStateVector): - self._apply_state_vector( - operations[0].parameters[0].copy(), operations[0].wires - ) + self._apply_state_vector(operations[0].parameters[0].copy(), operations[0].wires) del operations[0] elif isinstance(operations[0], BasisState): - self._apply_basis_state( - operations[0].parameters[0], operations[0].wires - ) + self._apply_basis_state(operations[0].parameters[0], operations[0].wires) del operations[0] for operation in operations: @@ -114,9 +110,7 @@ def apply(self, operations, rotations=None, **kwargs): if any(isinstance(r, QubitUnitary) for r in rotations): super().apply(operations=[], rotations=rotations) else: - self._state = self.apply_lightning( - np.copy(self._pre_rotated_state), rotations - ) + self._state = self.apply_lightning(np.copy(self._pre_rotated_state), rotations) else: self._state = self._pre_rotated_state @@ -135,9 +129,7 @@ def apply_lightning(self, state, operations): sim = StateVectorC128(state_vector) for o in operations: - name = o.name.split(".")[ - 0 - ] # The split is because inverse gates have .inv appended + name = o.name.split(".")[0] # The split is because inverse gates have .inv appended method = getattr(sim, name, None) wires = self.wires.indices(o.wires) @@ -193,7 +185,7 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): tape.num_params, ) - return jac # super().adjoint_jacobian(tape, starting_state, use_device_state) + return jac # super().adjoint_jacobian(tape, starting_state, use_device_state) if not CPP_BINARY_AVAILABLE: From 31864cc7681f9b4cf99f13f7adc17d8ee9bdbaa4 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:52:21 +0100 Subject: [PATCH 35/72] Fix serialisation for tensor expvals --- pennylane_lightning/_serialize.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index d1509268c8..12cdc18a75 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -75,7 +75,11 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: else: params.append(o.matrix) - ob = ObsStructC128(name, params, [wires]) + ob = ( + ObsStructC128(name, params, [[w] for w in wires]) + if is_tensor + else ObsStructC128(name, params, [wires]) + ) obs.append(ob) return obs From 224bc9a3af9afae717cc7012424695ba7ae71e1a Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:52:49 +0100 Subject: [PATCH 36/72] Ensure statevector is linear 1D array before offloading to C++ --- pennylane_lightning/lightning_qubit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index ad3f4626a9..defbe193a0 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -167,9 +167,8 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): if not use_device_state: self.reset() self.execute(tape) - ket = self._pre_rotated_state + ket = np.ravel(self._pre_rotated_state) - # TODO: How to accommodate for tensor product observables? adj = AdjointJacobianC128() obs_serialized = _serialize_obs(tape, self.wire_map) From 1e85c63566661c88ac2c0bb028d8393175cfe8dd Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:53:23 +0100 Subject: [PATCH 37/72] Add repr bindings for improved debugging --- pennylane_lightning/src/Bindings.cpp | 38 ++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/Bindings.cpp index a465198cca..13d2195cae 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/Bindings.cpp @@ -490,17 +490,6 @@ class AdjJacBinder : public AdjointJacobian { } }; -/**** - * - * jac = adj.adjoint_jacobian( - *obs_serialized, *ops_serialized, tape.trainable_params, -tape.num_params - ) - * - * - / - - /** * @brief Templated class to build all required precisions for Python module. * @@ -672,7 +661,19 @@ void lightning_class_bindings(py::module &m) { py::class_>(m, class_name.c_str()) .def(py::init &, const std::vector> &, - const std::vector> &>()); + const std::vector> &>()) + .def("__repr__", [](const ObsDatum& obs){ + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for(size_t o = 1; o < obs.getObsName().size(); o++){ + if(o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + obs_stream.str() + " }"; + }); class_name = "OpsStructC" + bitsize; py::class_>(m, class_name.c_str()) @@ -681,7 +682,18 @@ void lightning_class_bindings(py::module &m) { const std::vector> &, const std::vector> &, const std::vector &, - const std::vector>> &>()); + const std::vector>> &>()) + .def("__repr__", [](const OpsData& ops){ + using namespace Pennylane::Util; + std::ostringstream ops_stream; + for(size_t op = 0; op < ops.getSize(); op++){ + ops_stream << "{'name': " << ops.getOpsName()[op]; + ops_stream << ", 'params': " << ops.getOpsParams()[op] << "}"; + if(op < ops.getSize()-1) + ops_stream << ","; + } + return "Operations: [" + ops_stream.str() + "]"; + }); class_name = "AdjointJacobianC" + bitsize; py::class_>(m, class_name.c_str()) From ff4cee3c46e397b63dcbf13c2692ceda95c06212 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:54:10 +0100 Subject: [PATCH 38/72] Enable OpenMP for observables --- .../src/algorithms/AdjointDiff.hpp | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index a6b0cbd171..e010b65e34 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -270,7 +270,6 @@ template class AdjointJacobian { // 1. Create $U_{1:p}\vert \lambda \rangle$ StateVectorManaged lambda(psi, num_elements); - applyOperations(lambda, operations); // 2. Create observable-applied state-vectors @@ -278,12 +277,11 @@ template class AdjointJacobian { {lambda.getNumQubits()}); #pragma omp parallel for - { - for (size_t h_i = 0; h_i < num_observables; h_i++) { - H_lambda[h_i].updateData(lambda.getDataVector()); - applyObservable(H_lambda[h_i], observables[h_i]); - } + for (size_t h_i = 0; h_i < num_observables; h_i++) { + H_lambda[h_i].updateData(lambda.getDataVector()); + applyObservable(H_lambda[h_i], observables[h_i]); } + StateVectorManaged mu(lambda.getNumQubits()); for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; @@ -306,13 +304,14 @@ template class AdjointJacobian { if (std::find(trainableParams.begin(), trainableParams.end(), current_param_idx) != trainableParams.end()) { - + // Apply generator function generator_map.at(operations.getOpsName()[op_idx])( mu, operations.getOpsWires()[op_idx]); const T scalingFactor = scaling_factors.at(operations.getOpsName()[op_idx]); size_t index; +#pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { index = obs_idx * trainableParams.size() + @@ -321,21 +320,19 @@ template class AdjointJacobian { mu.getData(), jac, num_elements, scalingFactor, index); } + trainableParamNumber--; } current_param_idx--; } #pragma omp parallel for - { - for (size_t obs_idx = 0; obs_idx < num_observables; - obs_idx++) { - H_lambda[obs_idx].applyOperation( - operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); - } + for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { + H_lambda[obs_idx].applyOperation( + operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx], + operations.getOpsParams()[op_idx]); } } } From bf2de2abfd108de9fd8e2a3bcbb2fa58e6ef885c Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:54:43 +0100 Subject: [PATCH 39/72] Ensure OpenMP is optional requirement --- pennylane_lightning/src/algorithms/CMakeLists.txt | 8 +++++++- pennylane_lightning/src/simulator/CMakeLists.txt | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 8cea03eb35..e7b803dc12 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -5,7 +5,13 @@ set(ALGORITHM_FILES AdjointDiff.hpp AdjointDiff.cpp CACHE INTERNAL "" FORCE) add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) target_include_directories(lightning_algorithms PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils) +target_link_libraries(lightning_algorithms PRIVATE lightning_simulator lightning_utils OpenMP::OpenMP_CXX) + +find_package(OpenMP) +if(OpenMP_FOUND) + target_link_libraries(lightning_algorithms PRIVATE OpenMP::OpenMP_CXX) +endif() + set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index 0f95c8b947..5b7216ca3b 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -4,10 +4,14 @@ set(CMAKE_CXX_STANDARD 17) set(SIMULATOR_FILES StateVector.cpp StateVector.hpp Gates.hpp CACHE INTERNAL "" FORCE) add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) -find_package(OpenMP REQUIRED) +find_package(OpenMP) target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -target_link_libraries(lightning_simulator PRIVATE lightning_utils OpenMP::OpenMP_CXX) +target_link_libraries(lightning_simulator PRIVATE lightning_utils) + +if(OpenMP_FOUND) + target_link_libraries(lightning_simulator PRIVATE OpenMP::OpenMP_CXX) +endif() set_property(TARGET lightning_simulator PROPERTY POSITION_INDEPENDENT_CODE ON) From 3b10acae64114a9f156046d74912798f3afbc4fe Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 14:56:08 +0100 Subject: [PATCH 40/72] Fix vector stream formatting --- pennylane_lightning/src/util/Util.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index d0901b5597..21cbfd7758 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -257,10 +257,7 @@ template inline std::ostream &operator<<(std::ostream &os, const std::vector &vec) { os << '['; for (size_t i = 0; i < vec.size(); i++) { - if (i > 0 && i < vec.size() - 1) { - os << ","; - } - os << vec[i]; + os << vec[i] << ","; } os << ']'; return os; From ac017813147ec065793903a45d2e6147445d2944 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 15:10:35 +0100 Subject: [PATCH 41/72] Move and format bindings --- .../src/{ => bindings}/Bindings.cpp | 17 +++++++++-------- setup.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) rename pennylane_lightning/src/{ => bindings}/Bindings.cpp (98%) diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp similarity index 98% rename from pennylane_lightning/src/Bindings.cpp rename to pennylane_lightning/src/bindings/Bindings.cpp index 13d2195cae..c8512ba60a 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -662,18 +662,19 @@ void lightning_class_bindings(py::module &m) { .def(py::init &, const std::vector> &, const std::vector> &>()) - .def("__repr__", [](const ObsDatum& obs){ + .def("__repr__", [](const ObsDatum &obs) { using namespace Pennylane::Util; std::ostringstream obs_stream; std::string obs_name = obs.getObsName()[0]; - for(size_t o = 1; o < obs.getObsName().size(); o++){ - if(o < obs.getObsName().size()) + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) obs_name += " @ "; obs_name += obs.getObsName()[o]; } obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + obs_stream.str() + " }"; - }); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; + }); class_name = "OpsStructC" + bitsize; py::class_>(m, class_name.c_str()) @@ -683,13 +684,13 @@ void lightning_class_bindings(py::module &m) { const std::vector> &, const std::vector &, const std::vector>> &>()) - .def("__repr__", [](const OpsData& ops){ + .def("__repr__", [](const OpsData &ops) { using namespace Pennylane::Util; std::ostringstream ops_stream; - for(size_t op = 0; op < ops.getSize(); op++){ + for (size_t op = 0; op < ops.getSize(); op++) { ops_stream << "{'name': " << ops.getOpsName()[op]; ops_stream << ", 'params': " << ops.getOpsParams()[op] << "}"; - if(op < ops.getSize()-1) + if (op < ops.getSize() - 1) ops_stream << ","; } return "Operations: [" + ops_stream.str() + "]"; diff --git a/setup.py b/setup.py index b9f296f08e..de1f7fdd7f 100644 --- a/setup.py +++ b/setup.py @@ -152,7 +152,7 @@ def build_extensions(self): sources=[ "pennylane_lightning/src/simulator/StateVector.cpp", "pennylane_lightning/src/algorithms/AdjointDiff.cpp", - "pennylane_lightning/src/Bindings.cpp", + "pennylane_lightning/src/bindings/Bindings.cpp", ], depends=[ "pennylane_lightning/src/algorithms/AdjointDiff.hpp", From 2756cdff00fba3b5564641e51e656104697697d3 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 15:16:58 +0100 Subject: [PATCH 42/72] Aim to appease codefactor --- CMakeLists.txt | 2 +- pennylane_lightning/src/algorithms/AdjointDiff.hpp | 3 --- pennylane_lightning/src/simulator/StateVector.hpp | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7843d31000..e41b7064a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ if ("$ENV{USE_OPENBLAS}" OR "${USE_OPENBLAS}") target_compile_options(external_dependency INTERFACE "-DOPENBLAS=1") endif() -pybind11_add_module(lightning_qubit_ops "pennylane_lightning/src/Bindings.cpp") +pybind11_add_module(lightning_qubit_ops "pennylane_lightning/src/bindings/Bindings.cpp") target_link_libraries(lightning_qubit_ops PRIVATE pennylane_lightning external_dependency) set_target_properties(lightning_qubit_ops PROPERTIES CXX_VISIBILITY_PRESET hidden) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index e010b65e34..12ca768109 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -15,7 +15,6 @@ #include -// Generators not needed outside this translation unit namespace { using namespace Pennylane; @@ -147,7 +146,6 @@ template struct OpsData { const std::vector> ops_wires_; const std::vector ops_inverses_; const std::vector>> ops_matrices_; - OpsData(const std::vector &ops_name, const std::vector> &ops_params, const std::vector> &ops_wires, @@ -263,7 +261,6 @@ template class AdjointJacobian { const OpsData &operations, const vector &trainableParams, size_t num_params) { - const size_t num_observables = observables.size(); unsigned int trainableParamNumber = trainableParams.size() - 1; int current_param_idx = num_params - 1; diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index fec15bbfeb..7ec40b2fef 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -152,7 +152,7 @@ template class StateVector { {"CRZ", bind(&StateVector::applyCRZ_, this, _1, _2, _3, _4)}, {"CRot", - bind(&StateVector::applyCRot_, this, _1, _2, _3, _4)}} {}; + bind(&StateVector::applyCRot_, this, _1, _2, _3, _4)}}{}; /** * @brief Get the underlying data pointer. @@ -614,7 +614,6 @@ template class StateVector { void applyRY(const vector &indices, const vector &externalIndices, bool inverse, Param_t angle) { - const CFP_t c(std::cos(angle / 2), 0); const CFP_t s = (inverse == true) ? CFP_t(-std::sin(angle / 2), 0) : CFP_t(std::sin(angle / 2), 0); From c705157f30be3ecaa4e78d84da1415001ecb7814 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 15:24:51 +0100 Subject: [PATCH 43/72] Offload StateVectorManaged to separate file --- .../src/algorithms/AdjointDiff.hpp | 2 +- .../src/simulator/CMakeLists.txt | 2 +- .../src/simulator/StateVector.hpp | 81 +------------------ .../src/tests/Test_AdjDiff.cpp | 1 - .../Test_StateVectorManaged_Nonparam.cpp | 2 +- .../tests/Test_StateVectorManaged_Param.cpp | 2 +- 6 files changed, 5 insertions(+), 85 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 12ca768109..65fe08ef6c 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -11,6 +11,7 @@ #include "Error.hpp" #include "StateVector.hpp" +#include "StateVectorManaged.hpp" #include "Util.hpp" #include @@ -286,7 +287,6 @@ template class AdjointJacobian { PL_ABORT_IF(operations.getOpsParams()[op_idx].size() > 1, "The operation is not supported using the adjoint " "differentiation method"); - if ((operations.getOpsName()[op_idx] != "QubitStateVector") && (operations.getOpsName()[op_idx] != "BasisState")) { diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index 5b7216ca3b..b190613401 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -1,7 +1,7 @@ project(lightning_simulator) set(CMAKE_CXX_STANDARD 17) -set(SIMULATOR_FILES StateVector.cpp StateVector.hpp Gates.hpp CACHE INTERNAL "" FORCE) +set(SIMULATOR_FILES StateVector.cpp StateVector.hpp StateVectorManaged.hpp Gates.hpp CACHE INTERNAL "" FORCE) add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) find_package(OpenMP) diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 7ec40b2fef..a6a8c2d3a1 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -152,7 +152,7 @@ template class StateVector { {"CRZ", bind(&StateVector::applyCRZ_, this, _1, _2, _3, _4)}, {"CRot", - bind(&StateVector::applyCRot_, this, _1, _2, _3, _4)}}{}; + bind(&StateVector::applyCRot_, this, _1, _2, _3, _4)}} {}; /** * @brief Get the underlying data pointer. @@ -1068,85 +1068,6 @@ template class StateVector { } }; -/** - * @brief Managed memory version of StateVector class. Memory ownership resides - * within class. - * - * @tparam fp_t - */ -template -class StateVectorManaged : public StateVector { - private: - using CFP_t = std::complex; - - std::vector data_; - - public: - StateVectorManaged() : StateVector() {} - StateVectorManaged(size_t num_qubits) - : data_(static_cast(Util::exp2(num_qubits)), CFP_t{0, 0}), - StateVector(nullptr, - static_cast(Util::exp2(num_qubits))) { - StateVector::setData(data_.data()); - data_[0] = {1, 0}; - } - StateVectorManaged(const StateVector &other) - : data_{other.getData(), other.getData() + other.getLength()}, - StateVector(nullptr, data_.size()) { - StateVector::setData(data_.data()); - } - StateVectorManaged(const std::vector &other_data) - : data_{other_data}, StateVector(nullptr, other_data.size()) { - StateVector::setData(data_.data()); - } - StateVectorManaged(const CFP_t *other_data, size_t other_size) - : data_{other_data, other_data + other_size}, StateVector( - nullptr, other_size) { - StateVector::setData(data_.data()); - } - StateVectorManaged(const StateVectorManaged &other) - : data_{other.data_}, StateVector(nullptr, - other.getDataVector().size()) { - StateVector::setData(data_.data()); - } - - StateVectorManaged &operator=(const StateVectorManaged &other) { - if (this != &other) { - if (data_.size() != other.getLength()) { - data_.resize(other.getLength()); - StateVector::setData(data_.data()); - StateVector::setLength(other.getLength()); - } - std::copy(other.data_.data(), - other.data_.data() + other.getLength(), data_.data()); - } - return *this; - } - std::vector &getDataVector() { return data_; } - const std::vector &getDataVector() const { return data_; } - - std::vector - getInternalIndices(const std::vector &qubit_indices) { - return StateVector::generateBitPatterns(qubit_indices, - Util::log2(data_.size())); - } - std::vector - getExternalIndices(const std::vector &qubit_indices) { - std::vector externalWires = - StateVector::getIndicesAfterExclusion( - qubit_indices, Util::log2(data_.size())); - std::vector externalIndices = - StateVector::generateBitPatterns(externalWires, - Util::log2(data_.size())); - return externalIndices; - } - void updateData(const std::vector &new_data) { - PL_ABORT_IF_NOT(data_.size() == new_data.size(), - "New data must be the same size as old data.") - std::copy(new_data.begin(), new_data.end(), data_.begin()); - } -}; - template inline std::ostream &operator<<(std::ostream &out, const StateVector &sv) { const size_t num_qubits = sv.getNumQubits(); diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index d4bb7b11b1..d8ace7868e 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -36,7 +36,6 @@ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", } TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { - AdjointJacobian adj; // std::vector param{1, -2, 1.623, -0.051, 0}; std::vector param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; diff --git a/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp b/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp index aa18a0088b..a0d020d26d 100644 --- a/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp +++ b/pennylane_lightning/src/tests/Test_StateVectorManaged_Nonparam.cpp @@ -9,7 +9,7 @@ #include #include "Gates.hpp" -#include "StateVector.hpp" +#include "StateVectorManaged.hpp" #include "Util.hpp" #include "TestHelpers.hpp" diff --git a/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp index 882148a2c1..2c34314cd6 100644 --- a/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp +++ b/pennylane_lightning/src/tests/Test_StateVectorManaged_Param.cpp @@ -9,7 +9,7 @@ #include #include "Gates.hpp" -#include "StateVector.hpp" +#include "StateVectorManaged.hpp" #include "Util.hpp" #include "TestHelpers.hpp" From 0cc4db3e550f3621d89cd8e406432861f4945356 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 15:25:17 +0100 Subject: [PATCH 44/72] Readd missing file --- .../src/simulator/StateVectorManaged.hpp | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 pennylane_lightning/src/simulator/StateVectorManaged.hpp diff --git a/pennylane_lightning/src/simulator/StateVectorManaged.hpp b/pennylane_lightning/src/simulator/StateVectorManaged.hpp new file mode 100644 index 0000000000..29eccf5e9f --- /dev/null +++ b/pennylane_lightning/src/simulator/StateVectorManaged.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "StateVector.hpp" + +namespace Pennylane { + +/** + * @brief Managed memory version of StateVector class. Memory ownership resides + * within class. + * + * @tparam fp_t + */ +template +class StateVectorManaged : public StateVector { + private: + using CFP_t = std::complex; + + std::vector data_; + + public: + StateVectorManaged() : StateVector() {} + StateVectorManaged(size_t num_qubits) + : data_(static_cast(Util::exp2(num_qubits)), CFP_t{0, 0}), + StateVector(nullptr, + static_cast(Util::exp2(num_qubits))) { + StateVector::setData(data_.data()); + data_[0] = {1, 0}; + } + StateVectorManaged(const StateVector &other) + : data_{other.getData(), other.getData() + other.getLength()}, + StateVector(nullptr, data_.size()) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const std::vector &other_data) + : data_{other_data}, StateVector(nullptr, other_data.size()) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const CFP_t *other_data, size_t other_size) + : data_{other_data, other_data + other_size}, StateVector( + nullptr, other_size) { + StateVector::setData(data_.data()); + } + StateVectorManaged(const StateVectorManaged &other) + : data_{other.data_}, StateVector(nullptr, + other.getDataVector().size()) { + StateVector::setData(data_.data()); + } + + StateVectorManaged &operator=(const StateVectorManaged &other) { + if (this != &other) { + if (data_.size() != other.getLength()) { + data_.resize(other.getLength()); + StateVector::setData(data_.data()); + StateVector::setLength(other.getLength()); + } + std::copy(other.data_.data(), + other.data_.data() + other.getLength(), data_.data()); + } + return *this; + } + std::vector &getDataVector() { return data_; } + const std::vector &getDataVector() const { return data_; } + + std::vector + getInternalIndices(const std::vector &qubit_indices) { + return StateVector::generateBitPatterns(qubit_indices, + Util::log2(data_.size())); + } + std::vector + getExternalIndices(const std::vector &qubit_indices) { + std::vector externalWires = + StateVector::getIndicesAfterExclusion( + qubit_indices, Util::log2(data_.size())); + std::vector externalIndices = + StateVector::generateBitPatterns(externalWires, + Util::log2(data_.size())); + return externalIndices; + } + void updateData(const std::vector &new_data) { + PL_ABORT_IF_NOT(data_.size() == new_data.size(), + "New data must be the same size as old data.") + std::copy(new_data.begin(), new_data.end(), data_.begin()); + } +}; + +} // namespace Pennylane \ No newline at end of file From 26a960799a5b65c2762e5e6e99693beb4e095cab Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 16:32:48 +0100 Subject: [PATCH 45/72] Avoid loading cmath before math defines for Windows --- pennylane_lightning/src/algorithms/AdjointDiff.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 65fe08ef6c..7a0bbe6a72 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -1,9 +1,7 @@ #pragma once -#include #include #include -#include #include #include #include From c02d21cb630c54ae40437d48c3dd5000d5949d40 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 16:40:57 +0100 Subject: [PATCH 46/72] Add defines to compiler rather than source --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index de1f7fdd7f..e08dc2b171 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ class BuildExt(build_ext): """A custom build extension for adding compiler-specific options.""" c_opts = { - "msvc": ["-EHsc", "-O2", "-W1", "-std:c++17"], + "msvc": ["-EHsc", "-O2", "-W1", "-std:c++17", "-D_USE_MATH_DEFINES"], "unix": ["-O3", "-W", "-fPIC", "-shared", "-fopenmp"], } From 9cea641420d887c4ae08c86823f5444c3f19aeeb Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 16:42:48 +0100 Subject: [PATCH 47/72] Add guards for C++ binding utility modules import --- pennylane_lightning/lightning_qubit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index defbe193a0..2de496d689 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -28,7 +28,6 @@ from pennylane.devices import DefaultQubit from pennylane.operation import Expectation -from ._serialize import _serialize_obs, _serialize_ops from ._version import __version__ try: @@ -38,6 +37,7 @@ StateVectorC128, AdjointJacobianC128, ) + from ._serialize import _serialize_obs, _serialize_ops CPP_BINARY_AVAILABLE = True except ModuleNotFoundError: @@ -145,6 +145,8 @@ def apply_lightning(self, state, operations): return np.reshape(state_vector, state.shape) def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): + if not CPP_BINARY_AVAILABLE: + return super().adjoint_jacobian(tape, starting_state, use_device_state) if self.shots is not None: warn( From 707c0e8614d15f1eb1a67ebcd81639cf78a6e5a4 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 30 Aug 2021 17:46:49 +0100 Subject: [PATCH 48/72] Add comments to AdjointDiff to silence codefactor --- .../src/algorithms/AdjointDiff.hpp | 182 +++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 7a0bbe6a72..793d93e757 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -115,20 +115,53 @@ namespace Algorithms { * class. * */ -template struct ObsDatum { +template class ObsDatum { + private: const std::vector obs_name_; const std::vector> obs_params_; const std::vector> obs_wires_; + + public: + /** + * @brief Construct an ObsDatum object, representing a given observable. + * + * @param obs_name Name of each operation of the observable. Tensor product + * observables have more than one operation. + * @param obs_params Parameters for a given obserable opeartion ({} if + * optional). + * @param ops_wires Wires upon which to apply operation. Each observable + * operation will eb a separate nested list. + */ ObsDatum(const std::vector &obs_name, const std::vector> &obs_params, const std::vector> &obs_wires) : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ obs_wires} {}; + /** + * @brief Get the number of operations in observable. + * + * @return size_t + */ size_t getSize() const { return obs_name_.size(); } + /** + * @brief Get the name of the observable operations. + * + * @return const std::vector& + */ const std::vector &getObsName() const { return obs_name_; } + /** + * @brief Get the parameters for the observable operations. + * + * @return const std::vector>& + */ const std::vector> &getObsParams() const { return obs_params_; } + /** + * @brief Get the wires for each observable operation. + * + * @return const std::vector>& + */ const std::vector> &getObsWires() const { return obs_wires_; } @@ -145,6 +178,19 @@ template struct OpsData { const std::vector> ops_wires_; const std::vector ops_inverses_; const std::vector>> ops_matrices_; + + /** + * @brief Construct an OpsData object, representing the serialized + * operations to apply upon the `%StateVector`. + * + * @param ops_name Name of each operation to apply. + * @param ops_params Parameters for a given operation ({} if optional). + * @param ops_wires Wires upon which to apply operation + * @param ops_inverses Value to represent whether given operation is + * adjoint. + * @param ops_matrices Numerical representation of given matrix if not + * supported. + */ OpsData(const std::vector &ops_name, const std::vector> &ops_params, const std::vector> &ops_wires, @@ -153,6 +199,16 @@ template struct OpsData { : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, ops_inverses_{ops_inverses}, ops_matrices_{ops_matrices} {}; + /** + * @brief Construct an OpsData object, representing the serialized + operations to apply upon the `%StateVector`. + * + * @see OpsData(const std::vector &ops_name, + const std::vector> &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector>> &ops_matrices) + */ OpsData(const std::vector &ops_name, const std::vector> &ops_params, const std::vector> &ops_wires, @@ -160,26 +216,66 @@ template struct OpsData { : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, ops_inverses_{ops_inverses}, ops_matrices_(ops_name.size()){}; + /** + * @brief Get the number of operations to be applied. + * + * @return size_t Number of operations. + */ size_t getSize() const { return ops_name_.size(); } + + /** + * @brief Get the names of the operations to be applied. + * + * @return const std::vector& + */ const std::vector &getOpsName() const { return ops_name_; } + /** + * @brief Get the (optional) parameters for each operation. Given entries + * are empty ({}) if not required. + * + * @return const std::vector>& + */ const std::vector> &getOpsParams() const { return ops_params_; } + /** + * @brief Get the wires for each operation. + * + * @return const std::vector>& + */ const std::vector> &getOpsWires() const { return ops_wires_; } + /** + * @brief Get the adjoint flag for each operation. + * + * @return const std::vector& + */ const std::vector &getOpsInverses() const { return ops_inverses_; } + /** + * @brief Get the numerical matrix for a given unsupported operation. Given + * entries are empty ({}) if not required. + * + * @return const std::vector>>& + */ const std::vector>> &getOpsMatrices() const { return ops_matrices_; } }; +/** + * @brief Represent the logic for the adjoint Jacobian method of + * arXiV:2009.02823 + * + * @tparam T Floating-point precision. + */ template class AdjointJacobian { private: typedef void (*GeneratorFunc)( StateVectorManaged &sv, const std::vector &wires); // function pointer type + // Holds the mapping from gate labels to associated generator functions. const std::unordered_map generator_map{ {"RX", &::applyGeneratorRX>}, {"RY", &::applyGeneratorRY>}, @@ -191,12 +287,24 @@ template class AdjointJacobian { {"ControlledPhaseShift", &::applyGeneratorControlledPhaseShift>}}; + // Holds the mappings from gate labels to associated generator coefficients. const std::unordered_map scaling_factors{ {"RX", -0.5}, {"RY", -0.5}, {"RZ", -0.5}, {"PhaseShift", 1}, {"CRX", -0.5}, {"CRY", -0.5}, {"CRZ", -0.5}, {"ControlledPhaseShift", 1}}; + /** + * @brief Utility method to update the Jacobian at a given index by + * calculating the overlap between two given states. + * + * @param sv1 Statevector + * @param jac Jacobian receiving the values. + * @param num_elements Length of statevectors + * @param scaling_coeff Generator coefficient for given gate derivative. + * @param index Position of Jacobian to update. + */ inline void updateJacobian(const std::complex *sv1, const std::complex *sv2, std::vector &jac, size_t num_elements, T scaling_coeff, @@ -204,6 +312,15 @@ template class AdjointJacobian { jac[index] = -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2, num_elements)); } + /** + * @brief Utility method to update the Jacobian at a given index by + calculating the overlap between two given states. + * + * @see updateJacobian(const std::complex *sv1, + const std::complex *sv2, std::vector &jac, + size_t num_elements, T scaling_coeff, + size_t index) + */ inline void updateJacobian(const std::vector> &sv1, const std::vector> &sv2, std::vector &jac, size_t num_elements, @@ -211,6 +328,14 @@ template class AdjointJacobian { PL_ASSERT(index < jac.size()); jac[index] = -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); } + + /** + * @brief Utility method to apply all operations from given `%OpsData` + * object to `%StateVectorManaged` + * + * @param state Statevector to be updated. + * @param operations Operations to apply. + */ inline void applyOperations(StateVectorManaged &state, const OpsData &operations) { for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); @@ -221,6 +346,13 @@ template class AdjointJacobian { operations.getOpsParams()[op_idx]); } } + /** + * @brief Utility method to apply a given operations from given + * `%ObsDatum` object to `%StateVectorManaged` + * + * @param state Statevector to be updated. + * @param observable Observable to apply. + */ inline void applyObservable(StateVectorManaged &state, const ObsDatum &observable) { for (size_t j = 0; j < observable.getSize(); j++) { @@ -233,6 +365,14 @@ template class AdjointJacobian { public: AdjointJacobian() {} + /** + * @brief Utility to create a given observable object. + * + * @param obs_name + * @param obs_params + * @param obs_wires + * @return const ObsDatum + */ const ObsDatum createObs(const std::vector &obs_name, const std::vector> &obs_params, @@ -240,6 +380,16 @@ template class AdjointJacobian { return {obs_name, obs_params, obs_wires}; } + /** + * @brief Utility to create a given operations object. + * + * @param ops_name + * @param ops_params + * @param ops_wires + * @param ops_inverses + * @param ops_matrices + * @return const OpsData + */ const OpsData createOpsData( const std::vector &ops_name, const std::vector> &ops_params, @@ -249,11 +399,40 @@ template class AdjointJacobian { return {ops_name, ops_params, ops_wires, ops_inverses, ops_matrices}; } + /** + * @brief Copies complex data array into a `%vector` of the same dimension. + * + * @param input_state + * @param state_length + * @return std::vector> + */ std::vector> copyStateData(const std::complex *input_state, size_t state_length) { return {input_state, input_state + state_length}; } + /** + * @brief Calculates the Jacobian for the statevector for the selected set + * of parametric gates. + * + * For the statevector data associated with `psi` of length `num_elements`, + * we make internal copies to a `%StateVectorManaged` object, with one + * per required observable. The `operations` will be applied to the internal + * statevector copies, with the operation indices participating in the + * gradient calculations given in `trainableParams`, and the overall number + * of parameters for the gradient calculation provided within `num_params`. + * The resulting row-major ordered `jac` matrix representation will be of + * size `trainableParams.size() * observables.size()`. OpenMP is used to + * enable independent operations to be offloaded to threads. + * + * @param psi + * @param num_elements + * @param jac + * @param observables + * @param operations + * @param trainableParams + * @param num_params + */ void adjointJacobian(const std::complex *psi, size_t num_elements, std::vector &jac, const std::vector> &observables, @@ -332,7 +511,6 @@ template class AdjointJacobian { } } } - // return jacobian; }; } // namespace Algorithms From 30bd6b468f313cbf7989c3bccd08b6c5bdc41db6 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Tue, 31 Aug 2021 17:21:55 +0100 Subject: [PATCH 49/72] Add ostream for statevectors --- .../src/simulator/StateVector.hpp | 17 +++++++++-------- .../src/simulator/StateVectorManaged.hpp | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index a6a8c2d3a1..c226540a64 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -89,6 +89,8 @@ template class StateVector { size_t num_qubits_; public: + using scalar_type_t = fp_t; + StateVector() : arr_{nullptr}, length_{0}, num_qubits_{0}, gate_wires_{}, gates_{} {}; @@ -1067,19 +1069,18 @@ template class StateVector { applyCSWAP(indices, externalIndices, inverse); } }; - template inline std::ostream &operator<<(std::ostream &out, const StateVector &sv) { - const size_t num_qubits = sv.getNumQubits(); - const size_t length = sv.getLength(); - const auto data_ptr = sv.getData(); - out << "num_qubits=" << num_qubits << std::endl; + const auto length = sv.getLength(); + const auto qubits = sv.getNumQubits(); + const auto data = sv.getData(); + out << "num_qubits=" << qubits << std::endl; out << "data=["; - out << data_ptr[0]; + out << data[0]; for (size_t i = 1; i < length - 1; i++) { - out << "," << data_ptr[i]; + out << "," << data[i]; } - out << "," << data_ptr[length - 1] << "]"; + out << "," << data[length - 1] << "]"; return out; } diff --git a/pennylane_lightning/src/simulator/StateVectorManaged.hpp b/pennylane_lightning/src/simulator/StateVectorManaged.hpp index 29eccf5e9f..0112c4ef6b 100644 --- a/pennylane_lightning/src/simulator/StateVectorManaged.hpp +++ b/pennylane_lightning/src/simulator/StateVectorManaged.hpp @@ -83,4 +83,20 @@ class StateVectorManaged : public StateVector { } }; +/* +template +inline std::ostream& operator<<(std::ostream& out, const StateVectorManaged& +sv){ const auto length = sv.getLength(); const auto qubits = sv.getNumQubits(); + const auto data = sv.getData(); + out << "num_qubits=" << qubits << std::endl; + out << "data=["; + out << data[0]; + for (size_t i = 1; i < length - 1; i++) { + out << "," << data[i]; + } + out << "," << data[length - 1] << "]"; + + return out; +}*/ + } // namespace Pennylane \ No newline at end of file From fa81fc52acd8e92dbaee59cc6528facb6167c77f Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Tue, 31 Aug 2021 17:22:21 +0100 Subject: [PATCH 50/72] Add ostream for vector and sets --- pennylane_lightning/src/util/Util.hpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pennylane_lightning/src/util/Util.hpp b/pennylane_lightning/src/util/Util.hpp index 21cbfd7758..36cc724992 100644 --- a/pennylane_lightning/src/util/Util.hpp +++ b/pennylane_lightning/src/util/Util.hpp @@ -24,9 +24,12 @@ #include #include #include +#include #include #include +#include + #if __has_include() && defined _ENABLE_BLAS #include #define USE_CBLAS 1 @@ -263,6 +266,25 @@ inline std::ostream &operator<<(std::ostream &os, const std::vector &vec) { return os; } +template +inline std::ostream &operator<<(std::ostream &os, const std::set &s) { + os << '{'; + for (const auto &e : s) { + os << e << ","; + } + os << '}'; + return os; +} + +template std::vector linspace(T start, T end, size_t num_points) { + std::vector data(num_points); + T step = (end - start) / (num_points - 1); + for (size_t i = 0; i < num_points; i++) { + data[i] = start + (step * i); + } + return data; +} + /** * @brief Exception for functions that are not yet implemented. * From ac8b85755b1c99f84e33e95cafff203408547a9c Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Tue, 31 Aug 2021 17:23:04 +0100 Subject: [PATCH 51/72] Improve performance of Adj method --- .../src/algorithms/AdjointDiff.hpp | 34 +++++--- pennylane_lightning/src/bindings/Bindings.cpp | 5 +- .../src/tests/Test_AdjDiff.cpp | 85 +++++++++++++++++-- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 793d93e757..9227f04a12 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -437,17 +438,23 @@ template class AdjointJacobian { std::vector &jac, const std::vector> &observables, const OpsData &operations, - const vector &trainableParams, - size_t num_params) { - const size_t num_observables = observables.size(); - unsigned int trainableParamNumber = trainableParams.size() - 1; + const std::set &trainableParams, + size_t num_params, bool apply_operations = false) { + PL_ABORT_IF(trainableParams.empty(), + "No trainable parameters provided."); + + size_t num_observables = observables.size(); + size_t trainableParamNumber = trainableParams.size() - 1; int current_param_idx = num_params - 1; - // 1. Create $U_{1:p}\vert \lambda \rangle$ + // Create $U_{1:p}\vert \lambda \rangle$ StateVectorManaged lambda(psi, num_elements); - applyOperations(lambda, operations); - // 2. Create observable-applied state-vectors + // Apply given operations to statevector if requested + if (apply_operations) + applyOperations(lambda, operations); + + // Create observable-applied state-vectors std::vector> H_lambda(num_observables, {lambda.getNumQubits()}); @@ -456,11 +463,13 @@ template class AdjointJacobian { H_lambda[h_i].updateData(lambda.getDataVector()); applyObservable(H_lambda[h_i], observables[h_i]); } - StateVectorManaged mu(lambda.getNumQubits()); + auto it = trainableParams.end(); + for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; op_idx--) { + PL_ABORT_IF(operations.getOpsParams()[op_idx].size() > 1, "The operation is not supported using the adjoint " "differentiation method"); @@ -475,9 +484,10 @@ template class AdjointJacobian { operations.getOpsParams()[op_idx]); if (!operations.getOpsParams()[op_idx].empty()) { - if (std::find(trainableParams.begin(), - trainableParams.end(), - current_param_idx) != trainableParams.end()) { + + if (std::find(trainableParams.begin(), it, + current_param_idx) != it) { + // Apply generator function generator_map.at(operations.getOpsName()[op_idx])( mu, operations.getOpsWires()[op_idx]); @@ -494,8 +504,8 @@ template class AdjointJacobian { mu.getData(), jac, num_elements, scalingFactor, index); } - trainableParamNumber--; + std::advance(it, -1); } current_param_idx--; } diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index c8512ba60a..5a56694795 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #include "AdjointDiff.hpp" #include "StateVector.hpp" @@ -26,6 +28,7 @@ namespace { using namespace Pennylane::Algorithms; using Pennylane::StateVector; using std::complex; +using std::set; using std::string; using std::vector; @@ -707,7 +710,7 @@ void lightning_class_bindings(py::module &m) { const StateVecBinder &sv, const std::vector> &observables, const OpsData &operations, - const vector &trainableParams, size_t num_params) { + const set &trainableParams, size_t num_params) { std::vector jac(num_params * observables.size()); adj.adjointJacobian(sv.getData(), sv.getLength(), jac, observables, operations, trainableParams, diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index d8ace7868e..fbb4df9113 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -55,7 +55,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, - ops, {0}, 1); + ops, {0}, 1, true); CAPTURE(jacobian); CHECK(-sin(p) == Approx(jacobian.front())); } @@ -78,7 +78,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, - ops, {0}, 1); + ops, {0}, 1, true); CAPTURE(jacobian); CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); @@ -100,7 +100,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { auto ops = adj.createOpsData({"RX"}, {{param[0]}}, {{0}}, {false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, - {obs1, obs2}, ops, {0}, num_params); + {obs1, obs2}, ops, {0}, num_params, true); CAPTURE(jacobian); CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); @@ -125,7 +125,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {{0}, {1}, {2}}, {false, false, false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, - {obs1, obs2, obs3}, ops, {0, 1, 2}, num_params); + {obs1, obs2, obs3}, ops, {0, 1, 2}, num_params, + true); CAPTURE(jacobian); CHECK(-sin(param[0]) == @@ -135,6 +136,36 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CHECK(-sin(param[2]) == Approx(jacobian[2 * num_params + 2]).margin(1e-7)); } + SECTION("Multiple RX gradient, single expval per wire, subset of params") { + const size_t num_qubits = 3; + const size_t num_params = 3; + const size_t num_obs = 3; + std::vector jacobian(num_params * num_obs, 0.0); + std::set t_params{0, 2}; + + std::vector> cdata(0b1 << num_qubits); + StateVector psi(cdata.data(), cdata.size()); + cdata[0] = std::complex{1, 0}; + + auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); + auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); + + auto ops = adj.createOpsData({"RX", "RX", "RX"}, + {{param[0]}, {param[1]}, {param[2]}}, + {{0}, {1}, {2}}, {false, false, false}); + + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, + {obs1, obs2, obs3}, ops, t_params, num_params, + true); + + CAPTURE(jacobian); + CHECK(-sin(param[0]) == + Approx(jacobian[0 * num_params + 0]).margin(1e-7)); + CHECK(0 == Approx(jacobian[1 * num_params + 1]).margin(1e-7)); + CHECK(-sin(param[2]) == + Approx(jacobian[1 * num_params + 2]).margin(1e-7)); + } SECTION("Multiple RX gradient, tensor expval") { const size_t num_qubits = 3; const size_t num_params = 3; @@ -152,7 +183,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {{0}, {1}, {2}}, {false, false, false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, - ops, {0, 1, 2}, num_params); + ops, {0, 1, 2}, num_params, true); CAPTURE(jacobian); // Computed with parameter shift @@ -187,7 +218,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {false, false, false, false, false, false, false, false}); adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, - ops, {0, 1, 2, 3, 4, 5}, num_params); + ops, {0, 1, 2, 3, 4, 5}, num_params, true); CAPTURE(jacobian); // Computed with PennyLane using default.qubit.adjoint_jacobian @@ -198,4 +229,46 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CHECK(-0.0129093062 == Approx(jacobian[4]).margin(1e-7)); CHECK(0.323846156 == Approx(jacobian[5]).margin(1e-7)); } + + SECTION("Decomposed Rot gate, non computational basis state") { + const size_t num_qubits = 1; + const size_t num_params = 3; + const size_t num_obs = 1; + + const auto thetas = Util::linspace(-2 * M_PI, 2 * M_PI, 7); + std::unordered_map> expec_results{ + {thetas[0], {0, -9.90819496e-01, 0}}, + {thetas[1], {-8.18996553e-01, 1.62526544e-01, 0}}, + {thetas[2], {-0.203949, 0.48593716, 0}}, + {thetas[3], {0, 1, 0}}, + {thetas[4], {-2.03948985e-01, 4.85937177e-01, 0}}, + {thetas[5], {-8.18996598e-01, 1.62526487e-01, 0}}, + {thetas[6], {0, -9.90819511e-01, 0}}}; + + for (const auto &theta : thetas) { + std::vector local_params{theta, std::pow(theta, 3), + SQRT2() * theta}; + std::vector jacobian(num_obs * num_params, 0); + + std::vector> cdata{INVSQRT2(), + -INVSQRT2()}; + StateVector psi(cdata.data(), cdata.size()); + + auto obs = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto ops = adj.createOpsData( + {"RZ", "RY", "RZ"}, + {{local_params[0]}, {local_params[1]}, {local_params[2]}}, + {{0}, {0}, {0}}, {false, false, false}); + + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, {0, 1, 2}, num_params, true); + CAPTURE(theta); + CAPTURE(jacobian); + + // Computed with PennyLane using default.qubit + CHECK(expec_results[theta][0] == Approx(jacobian[0]).margin(1e-7)); + CHECK(expec_results[theta][1] == Approx(jacobian[1]).margin(1e-7)); + CHECK(expec_results[theta][2] == Approx(jacobian[2]).margin(1e-7)); + } + } } From 101b31ad68eb8a9889707973e7e62a4e53b27571 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Tue, 31 Aug 2021 17:23:22 +0100 Subject: [PATCH 52/72] Ensure set passed to C++ layer --- pennylane_lightning/lightning_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 2de496d689..255362752a 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -182,7 +182,7 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): StateVectorC128(ket), obs_serialized, ops_serialized, - list(tape.trainable_params), + tape.trainable_params, tape.num_params, ) From f1b38c627cc65cd11766d00efabed2ec6af85f98 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 09:32:30 +0100 Subject: [PATCH 53/72] Simplify call structure --- .../src/algorithms/AdjointDiff.hpp | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 9227f04a12..ffd11ca53d 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -173,13 +173,15 @@ template class ObsDatum { * class. * */ -template struct OpsData { +template class OpsData { + private: const std::vector ops_name_; const std::vector> ops_params_; const std::vector> ops_wires_; const std::vector ops_inverses_; const std::vector>> ops_matrices_; + public: /** * @brief Construct an OpsData object, representing the serialized * operations to apply upon the `%StateVector`. @@ -262,6 +264,10 @@ template struct OpsData { const std::vector>> &getOpsMatrices() const { return ops_matrices_; } + + inline bool hasParams(size_t index) const { + return !ops_params_[index].empty(); + } }; /** @@ -299,7 +305,7 @@ template class AdjointJacobian { * @brief Utility method to update the Jacobian at a given index by * calculating the overlap between two given states. * - * @param sv1 Statevector * @param jac Jacobian receiving the values. * @param num_elements Length of statevectors @@ -329,6 +335,24 @@ template class AdjointJacobian { PL_ASSERT(index < jac.size()); jac[index] = -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); } + /** + * @brief Utility method to update the Jacobian at a given index by + calculating the overlap between two given states. + * + * @see updateJacobian(const std::complex *sv1, + const std::complex *sv2, std::vector &jac, + size_t num_elements, T scaling_coeff, + size_t index) + */ + inline void updateJacobian(const StateVectorManaged &sv1, + const StateVectorManaged &sv2, + std::vector &jac, size_t num_elements, + T scaling_coeff, size_t index) { + PL_ASSERT(index < jac.size()); + jac[index] = + -2 * scaling_coeff * + std::imag(innerProdC(sv1.getDataVector(), sv2.getDataVector())); + } /** * @brief Utility method to apply all operations from given `%OpsData` @@ -412,6 +436,22 @@ template class AdjointJacobian { return {input_state, input_state + state_length}; } + /** + * @brief Applies the gate generator for a given parameteric gate. Returns + * the associated scaling coefficient. + * + * @param sv Statevector data to operate upon. + * @param op_name Name of parametric gate. + * @param wires Wires to operate upon. + * @return T Generator scaling coefficient. + */ + inline T applyGenerator(StateVectorManaged &sv, + const std::string &op_name, + const std::vector &wires) { + generator_map.at(op_name)(sv, wires); + return scaling_factors.at(op_name); + } + /** * @brief Calculates the Jacobian for the statevector for the selected set * of parametric gates. @@ -465,7 +505,7 @@ template class AdjointJacobian { } StateVectorManaged mu(lambda.getNumQubits()); - auto it = trainableParams.end(); + auto tp_it = trainableParams.end(); for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; op_idx--) { @@ -483,16 +523,13 @@ template class AdjointJacobian { !operations.getOpsInverses()[op_idx], operations.getOpsParams()[op_idx]); - if (!operations.getOpsParams()[op_idx].empty()) { - - if (std::find(trainableParams.begin(), it, - current_param_idx) != it) { + if (operations.hasParams(op_idx)) { + if (std::find(trainableParams.begin(), tp_it, + current_param_idx) != tp_it) { - // Apply generator function - generator_map.at(operations.getOpsName()[op_idx])( - mu, operations.getOpsWires()[op_idx]); const T scalingFactor = - scaling_factors.at(operations.getOpsName()[op_idx]); + applyGenerator(mu, operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx]); size_t index; #pragma omp parallel for @@ -500,12 +537,11 @@ template class AdjointJacobian { obs_idx++) { index = obs_idx * trainableParams.size() + trainableParamNumber; - updateJacobian(H_lambda[obs_idx].getData(), - mu.getData(), jac, num_elements, - scalingFactor, index); + updateJacobian(H_lambda[obs_idx], mu, jac, + num_elements, scalingFactor, index); } trainableParamNumber--; - std::advance(it, -1); + std::advance(tp_it, -1); } current_param_idx--; } From 340982844857a1c8c744298b9a64c8beac6a5ec5 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 10:01:09 +0100 Subject: [PATCH 54/72] Tidy loop structure --- .../src/algorithms/AdjointDiff.hpp | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index ffd11ca53d..ae8b2ffe8e 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -265,6 +265,13 @@ template class OpsData { return ops_matrices_; } + /** + * @brief Notify if the operation at a given index is parametric. + * + * @param index Operation index. + * @return true Gate is parametric (has parameters). + * @return false Gate in non-parametric. + */ inline bool hasParams(size_t index) const { return !ops_params_[index].empty(); } @@ -360,17 +367,37 @@ template class AdjointJacobian { * * @param state Statevector to be updated. * @param operations Operations to apply. + * @param adj Take the adjoint of the given operations. */ inline void applyOperations(StateVectorManaged &state, - const OpsData &operations) { + const OpsData &operations, + bool adj = false) { + for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); op_idx++) { state.applyOperation(operations.getOpsName()[op_idx], operations.getOpsWires()[op_idx], - operations.getOpsInverses()[op_idx], + operations.getOpsInverses()[op_idx] ^ adj, operations.getOpsParams()[op_idx]); } } + /** + * @brief Utility method to apply all operations from given `%OpsData` + * object to `%StateVectorManaged` + * + * @param state Statevector to be updated. + * @param operations Operations to apply. + * @param adj Take the adjoint of the given operations. + */ + inline void applyOperation(StateVectorManaged &state, + const OpsData &operations, size_t op_idx, + bool adj = false) { + state.applyOperation(operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + operations.getOpsInverses()[op_idx] ^ adj, + operations.getOpsParams()[op_idx]); + } + /** * @brief Utility method to apply a given operations from given * `%ObsDatum` object to `%StateVectorManaged` @@ -452,6 +479,11 @@ template class AdjointJacobian { return scaling_factors.at(op_name); } + inline size_t getJacIndex(size_t obs_index, size_t tp_index, + size_t tp_size) { + return obs_index * tp_size + tp_index; + } + /** * @brief Calculates the Jacobian for the statevector for the selected set * of parametric gates. @@ -483,9 +515,11 @@ template class AdjointJacobian { PL_ABORT_IF(trainableParams.empty(), "No trainable parameters provided."); + // Track positions within par and non-par operations size_t num_observables = observables.size(); size_t trainableParamNumber = trainableParams.size() - 1; int current_param_idx = num_params - 1; + auto tp_it = trainableParams.end(); // Create $U_{1:p}\vert \lambda \rangle$ StateVectorManaged lambda(psi, num_elements); @@ -503,13 +537,11 @@ template class AdjointJacobian { H_lambda[h_i].updateData(lambda.getDataVector()); applyObservable(H_lambda[h_i], observables[h_i]); } - StateVectorManaged mu(lambda.getNumQubits()); - auto tp_it = trainableParams.end(); + StateVectorManaged mu(lambda.getNumQubits()); for (int op_idx = operations.getOpsName().size() - 1; op_idx >= 0; op_idx--) { - PL_ABORT_IF(operations.getOpsParams()[op_idx].size() > 1, "The operation is not supported using the adjoint " "differentiation method"); @@ -518,10 +550,7 @@ template class AdjointJacobian { mu.updateData(lambda.getDataVector()); - lambda.applyOperation(operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); + applyOperation(lambda, operations, op_idx, true); if (operations.hasParams(op_idx)) { if (std::find(trainableParams.begin(), tp_it, @@ -535,8 +564,8 @@ template class AdjointJacobian { #pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - index = obs_idx * trainableParams.size() + - trainableParamNumber; + index = getJacIndex(obs_idx, trainableParamNumber, + trainableParams.size()); updateJacobian(H_lambda[obs_idx], mu, jac, num_elements, scalingFactor, index); } @@ -548,11 +577,7 @@ template class AdjointJacobian { #pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - H_lambda[obs_idx].applyOperation( - operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx], - !operations.getOpsInverses()[op_idx], - operations.getOpsParams()[op_idx]); + applyOperation(H_lambda[obs_idx], operations, op_idx, true); } } } From 11d62d4e6d6bd8c48606b76b2fa67657d9a90adb Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 10:56:27 +0100 Subject: [PATCH 55/72] Update Obs class for float and complex data --- .../src/algorithms/AdjointDiff.hpp | 2 +- .../src/tests/Test_AdjDiff.cpp | 32 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index ae8b2ffe8e..97fd6af62d 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -128,7 +128,7 @@ template class ObsDatum { * * @param obs_name Name of each operation of the observable. Tensor product * observables have more than one operation. - * @param obs_params Parameters for a given obserable opeartion ({} if + * @param obs_params Parameters for a given obserable operation ({} if * optional). * @param ops_wires Wires upon which to apply operation. Each observable * operation will eb a separate nested list. diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index fbb4df9113..5aafa3c0ed 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -22,7 +22,7 @@ using namespace Pennylane; using namespace Pennylane::Algorithms; /** - * @brief Tests the constructability of the StateVector class. + * @brief Tests the constructability of the AdjointDiff.hpp classes. * */ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", @@ -35,6 +35,36 @@ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", } } +TEMPLATE_TEST_CASE("ObsDatum::ObsDatum", "[AdjointJacobian]", float, double) { + SECTION("ObsDatum") { + REQUIRE_FALSE(std::is_constructible>::value); + } + SECTION("ObsDatum {}") { + REQUIRE_FALSE(std::is_constructible>::value); + } + SECTION("ObsDatum> {}") { + REQUIRE_FALSE( + std::is_constructible>>::value); + } + SECTION("ObsDatum {const std::vector &, const " + "std::vector> &, const " + "std::vector> &}") { + REQUIRE(std::is_constructible< + ObsDatum, const std::vector &, + const std::vector> &, + const std::vector> &>::value); + } + SECTION("ObsDatum> {const std::vector " + "&, const std::vector>> &, " + "const std::vector> &}") { + REQUIRE(std::is_constructible< + ObsDatum>, + const std::vector &, + const std::vector>> &, + const std::vector> &>::value); + } +} + TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { AdjointJacobian adj; // std::vector param{1, -2, 1.623, -0.051, 0}; From ae5d41ff97a8c2b303c3e8fab3617fb4db7ee62d Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 11:09:45 +0100 Subject: [PATCH 56/72] Track updated Obs structure --- pennylane_lightning/_serialize.py | 10 ++-- .../src/algorithms/AdjointDiff.cpp | 7 ++- .../src/algorithms/CMakeLists.txt | 1 - pennylane_lightning/src/bindings/Bindings.cpp | 60 +++++++++++++++---- tests/test_adjoint_jacobian.py | 4 +- tests/test_serialize.py | 16 +++-- 6 files changed, 70 insertions(+), 28 deletions(-) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index 12cdc18a75..ace3b8af70 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -23,7 +23,7 @@ from pennylane.tape import QuantumTape try: - from .lightning_qubit_ops import StateVectorC128, ObsStructC128 + from .lightning_qubit_ops import StateVectorC128, ObsC128 except ImportError: pass @@ -46,7 +46,7 @@ def _obs_has_kernel(obs: Observable) -> bool: return False -def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: +def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsC128]: """Serializes the observables of an input tape. Args: @@ -54,7 +54,7 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: wires_map (dict): a dictionary mapping input wires to the device's backend wires Returns: - list(ObsStructC128): A list of observable objects compatible with the C++ backend + list(ObsC128): A list of observable objects compatible with the C++ backend """ obs = [] @@ -76,9 +76,9 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: params.append(o.matrix) ob = ( - ObsStructC128(name, params, [[w] for w in wires]) + ObsC128(name, params, [[w] for w in wires]) if is_tensor - else ObsStructC128(name, params, [wires]) + else ObsC128(name, params, [wires]) ) obs.append(ob) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.cpp b/pennylane_lightning/src/algorithms/AdjointDiff.cpp index ae23f00eac..4b2b1ec8ad 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.cpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.cpp @@ -16,4 +16,9 @@ // explicit instantiation template class Pennylane::Algorithms::AdjointJacobian; -template class Pennylane::Algorithms::AdjointJacobian; \ No newline at end of file +template class Pennylane::Algorithms::AdjointJacobian; + +template class Pennylane::Algorithms::ObsDatum; +template class Pennylane::Algorithms::ObsDatum; +template class Pennylane::Algorithms::ObsDatum>; +template class Pennylane::Algorithms::ObsDatum>; \ No newline at end of file diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index e7b803dc12..319aaf214f 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -12,7 +12,6 @@ if(OpenMP_FOUND) target_link_libraries(lightning_algorithms PRIVATE OpenMP::OpenMP_CXX) endif() - set_property(TARGET lightning_algorithms PROPERTY POSITION_INDEPENDENT_CODE ON) if(ENABLE_NATIVE) diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 5a56694795..1bf20628db 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -660,23 +660,57 @@ void lightning_class_bindings(py::module &m) { &StateVecBinder::template applyCRot), "Apply the CRot gate."); - class_name = "ObsStructC" + bitsize; + class_name = "ObsC" + bitsize; + py::class_>>(m, class_name.c_str()) + .def(py::init &, + const std::vector>>&, + const std::vector> &>()) + .def("__repr__", + [](const ObsDatum> &obs) { + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; + }) + .def("as_tuple", [](const ObsDatum> &obs) { + return std::tuple, + std::vector>>, + std::vector>>{ + obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; + }); + + + class_name = "Obs" + bitsize; py::class_>(m, class_name.c_str()) .def(py::init &, const std::vector> &, const std::vector> &>()) - .def("__repr__", [](const ObsDatum &obs) { - using namespace Pennylane::Util; - std::ostringstream obs_stream; - std::string obs_name = obs.getObsName()[0]; - for (size_t o = 1; o < obs.getObsName().size(); o++) { - if (o < obs.getObsName().size()) - obs_name += " @ "; - obs_name += obs.getObsName()[o]; - } - obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + - obs_stream.str() + " }"; + .def("__repr__", + [](const ObsDatum &obs) { + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; + }) + .def("as_tuple", [](const ObsDatum &obs) { + return std::tuple, + std::vector>, + std::vector>>{ + obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; }); class_name = "OpsStructC" + bitsize; diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index a1c5d8dd32..67bf052d63 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -97,7 +97,7 @@ def test_unsupported_op(self, dev): qml.CRot(0.1, 0.2, 0.3, wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with pytest.raises(qml.QuantumFunctionError, match="The CRot operation is not"): + with pytest.raises(qml.QuantumFunctionError, match=".*Error in PennyLane Lightning: The operation is not.*"): dev.adjoint_jacobian(tape) @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) @@ -113,7 +113,7 @@ def test_pauli_rotation_gradient(self, G, theta, tol, dev): tape.trainable_params = {1} calculated_val = dev.adjoint_jacobian(tape) - + # compare to finite differences numeric_val = tape.jacobian(dev, method="numeric") assert np.allclose(calculated_val, numeric_val, atol=tol, rtol=0) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index ff50668137..6cd7074bd2 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -76,8 +76,8 @@ def test_basic_return(self): qml.expval(qml.PauliZ(0)) s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["PauliZ"]], [], [[0]]) - assert s == s_expected + s_expected = (["PauliZ"], [], [[0]],) + assert s[0].as_tuple() == s_expected def test_tensor_return(self): """Test expected serialization for a tensor product return""" @@ -85,8 +85,8 @@ def test_tensor_return(self): qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["PauliZ", "PauliZ"]], [], [[0, 1]]) - assert s == s_expected + s_expected = (["PauliZ", "PauliZ"], [], [[0], [1]],) + assert s[0].as_tuple() == s_expected def test_tensor_non_tensor_return(self): """Test expected serialization for a mixture of tensor product and non-tensor product @@ -96,8 +96,12 @@ def test_tensor_non_tensor_return(self): qml.expval(qml.Hadamard(1)) s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["PauliZ", "PauliX"], ["Hadamard"]], [], [[0, 1], [1]]) - assert s == s_expected + s_expected = [ + (["PauliZ", "PauliX"], [], [[0], [1]]), + (["Hadamard"], [], [[1]]), + ] + + assert [ob.as_tuple() for ob in s] == s_expected def test_hermitian_return(self): """Test expected serialization for a Hermitian return""" From 61ccffc72d07e3bca78c9c63ab756c0136b7675f Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 15:47:31 +0100 Subject: [PATCH 57/72] Add variant support for observables from Python --- .../src/algorithms/AdjointDiff.hpp | 68 ++++++--- pennylane_lightning/src/bindings/Bindings.cpp | 129 +++++++++++------- .../src/tests/Test_AdjDiff.cpp | 60 ++------ 3 files changed, 144 insertions(+), 113 deletions(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 97fd6af62d..6ad7a5d3db 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include "Error.hpp" @@ -117,12 +119,11 @@ namespace Algorithms { * */ template class ObsDatum { - private: - const std::vector obs_name_; - const std::vector> obs_params_; - const std::vector> obs_wires_; - public: + using param_var_t = + std::variant>, + std::vector>; + /** * @brief Construct an ObsDatum object, representing a given observable. * @@ -134,10 +135,17 @@ template class ObsDatum { * operation will eb a separate nested list. */ ObsDatum(const std::vector &obs_name, - const std::vector> &obs_params, + const std::vector &obs_params, const std::vector> &obs_wires) - : obs_name_{obs_name}, obs_params_{obs_params}, obs_wires_{ - obs_wires} {}; + : obs_name_{obs_name}, + obs_params_(obs_params), obs_wires_{obs_wires} {}; + + ObsDatum(std::vector &&obs_name, + std::vector &&obs_params, + std::vector> &&obs_wires) + : obs_name_{std::move(obs_name)}, obs_params_{std::move(obs_params)}, + obs_wires_{std::move(obs_wires)} {}; + /** * @brief Get the number of operations in observable. * @@ -155,9 +163,7 @@ template class ObsDatum { * * @return const std::vector>& */ - const std::vector> &getObsParams() const { - return obs_params_; - } + const std::vector &getObsParams() const { return obs_params_; } /** * @brief Get the wires for each observable operation. * @@ -166,6 +172,11 @@ template class ObsDatum { const std::vector> &getObsWires() const { return obs_wires_; } + + private: + const std::vector obs_name_; + const std::vector obs_params_; + const std::vector> obs_wires_; }; /** @@ -408,9 +419,32 @@ template class AdjointJacobian { inline void applyObservable(StateVectorManaged &state, const ObsDatum &observable) { for (size_t j = 0; j < observable.getSize(); j++) { - state.applyOperation(observable.getObsName()[j], - observable.getObsWires()[j], false, - observable.getObsParams()[j]); + std::visit( + [&](const auto ¶m) { + using p_t = std::decay_t; + + // Apply supported gate with given params + if constexpr (std::is_same_v>) { + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], false, + param); + } + // Apply provided matrix + else if constexpr (std::is_same_v< + p_t, std::vector>>) { + state.applyOperation(param, observable.getObsWires()[j], + false); + } + // No params provided, offload to SV dispatcher + else if constexpr (std::is_same_v) { + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], + false); + } else { + PL_ABORT("Parameter type not supported"); + } + }, + observable.getObsParams()[j]); } } @@ -425,12 +459,12 @@ template class AdjointJacobian { * @param obs_wires * @return const ObsDatum */ - const ObsDatum + /*const ObsDatum createObs(const std::vector &obs_name, const std::vector> &obs_params, const std::vector> &obs_wires) { - return {obs_name, obs_params, obs_wires}; - } + return ObsDatum{obs_name, obs_params, obs_wires}; + }*/ /** * @brief Utility to create a given operations object. diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 1bf20628db..5035cf357b 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -660,59 +660,86 @@ void lightning_class_bindings(py::module &m) { &StateVecBinder::template applyCRot), "Apply the CRot gate."); - class_name = "ObsC" + bitsize; - py::class_>>(m, class_name.c_str()) - .def(py::init &, - const std::vector>>&, - const std::vector> &>()) - .def("__repr__", - [](const ObsDatum> &obs) { - using namespace Pennylane::Util; - std::ostringstream obs_stream; - std::string obs_name = obs.getObsName()[0]; - for (size_t o = 1; o < obs.getObsName().size(); o++) { - if (o < obs.getObsName().size()) - obs_name += " @ "; - obs_name += obs.getObsName()[o]; - } - obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + - obs_stream.str() + " }"; - }) - .def("as_tuple", [](const ObsDatum> &obs) { - return std::tuple, - std::vector>>, - std::vector>>{ - obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; - }); - + //***********************************************************************// + // Observable::complex params + //***********************************************************************// + /* + class_name = "ObsC" + bitsize; + py::class_>>(m, class_name.c_str()) + .def(py::init &, + // const + std::vector>>&, const + std::vector>> &, const + std::vector> &>()) .def(py::init( + [](const std::vector &opnames, + const std::vector< + py::array_t< + std::complex, py::array::c_style | + py::array::forcecast + > + > &opmatrix, + const std::vector> &opwires) { + + return ObsDatum>{{opnames}, + {opmatrix.data(), opmatrix.}, {opwires} + }; + })) + .def("__repr__", + [](const ObsDatum> &obs) { + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; + }) + .def("as_tuple", [](const ObsDatum> &obs) { + return std::tuple, + // + std::vector>>, + std::vector>>, + std::vector>>{ + obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; + });*/ + + //***********************************************************************// + // Observable::real params + //***********************************************************************// class_name = "Obs" + bitsize; py::class_>(m, class_name.c_str()) - .def(py::init &, - const std::vector> &, - const std::vector> &>()) - .def("__repr__", - [](const ObsDatum &obs) { - using namespace Pennylane::Util; - std::ostringstream obs_stream; - std::string obs_name = obs.getObsName()[0]; - for (size_t o = 1; o < obs.getObsName().size(); o++) { - if (o < obs.getObsName().size()) - obs_name += " @ "; - obs_name += obs.getObsName()[o]; - } - obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + - obs_stream.str() + " }"; - }) - .def("as_tuple", [](const ObsDatum &obs) { - return std::tuple, - std::vector>, - std::vector>>{ - obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; + .def(py::init< + const std::vector &, + const std::vector::param_var_t> &, + const std::vector> &>()) + .def("__repr__", [](const ObsDatum &obs) { + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; }); - + //.def("as_tuple", [](const ObsDatum &obs) { + // return std::tuple, + // std::vector>, + // std::vector>>{ + // obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; + //}); + + //***********************************************************************// + // Operations + //***********************************************************************// class_name = "OpsStructC" + bitsize; py::class_>(m, class_name.c_str()) .def(py::init< @@ -736,8 +763,8 @@ void lightning_class_bindings(py::module &m) { class_name = "AdjointJacobianC" + bitsize; py::class_>(m, class_name.c_str()) .def(py::init<>()) - .def("create_obs", &AdjointJacobian::createObs) - .def("create_ops_list", &AdjointJacobian::createOpsData) + //.def("create_obs", &AdjointJacobian::createObs) + //.def("create_ops_list", &AdjointJacobian::createOpsData) .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) .def("adjoint_jacobian", [](AdjointJacobian &adj, diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 5aafa3c0ed..8677f2e5b5 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -35,36 +35,6 @@ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", } } -TEMPLATE_TEST_CASE("ObsDatum::ObsDatum", "[AdjointJacobian]", float, double) { - SECTION("ObsDatum") { - REQUIRE_FALSE(std::is_constructible>::value); - } - SECTION("ObsDatum {}") { - REQUIRE_FALSE(std::is_constructible>::value); - } - SECTION("ObsDatum> {}") { - REQUIRE_FALSE( - std::is_constructible>>::value); - } - SECTION("ObsDatum {const std::vector &, const " - "std::vector> &, const " - "std::vector> &}") { - REQUIRE(std::is_constructible< - ObsDatum, const std::vector &, - const std::vector> &, - const std::vector> &>::value); - } - SECTION("ObsDatum> {const std::vector " - "&, const std::vector>> &, " - "const std::vector> &}") { - REQUIRE(std::is_constructible< - ObsDatum>, - const std::vector &, - const std::vector>> &, - const std::vector> &>::value); - } -} - TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { AdjointJacobian adj; // std::vector param{1, -2, 1.623, -0.051, 0}; @@ -74,7 +44,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_qubits = 1; const size_t num_params = 3; const size_t num_obs = 1; - auto obs = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto obs = ObsDatum({"PauliZ"}, {{}}, {{0}}); std::vector jacobian(num_obs * num_params, 0.0); for (const auto &p : param) { @@ -96,7 +66,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_params = 3; const size_t num_obs = 1; - auto obs = adj.createObs({"PauliX"}, {{}}, {{0}}); + auto obs = ObsDatum({"PauliX"}, {{}}, {{0}}); std::vector jacobian(num_obs * num_params, 0.0); for (const auto &p : param) { @@ -124,8 +94,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); - auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); + auto obs1 = ObsDatum({"PauliZ"}, {{}}, {{0}}); + auto obs2 = ObsDatum({"PauliZ"}, {{}}, {{1}}); auto ops = adj.createOpsData({"RX"}, {{param[0]}}, {{0}}, {false}); @@ -146,9 +116,9 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); - auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); - auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); + auto obs1 = ObsDatum({"PauliZ"}, {{}}, {{0}}); + auto obs2 = ObsDatum({"PauliZ"}, {{}}, {{1}}); + auto obs3 = ObsDatum({"PauliZ"}, {{}}, {{2}}); auto ops = adj.createOpsData({"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, @@ -177,9 +147,9 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs1 = adj.createObs({"PauliZ"}, {{}}, {{0}}); - auto obs2 = adj.createObs({"PauliZ"}, {{}}, {{1}}); - auto obs3 = adj.createObs({"PauliZ"}, {{}}, {{2}}); + auto obs1 = ObsDatum({"PauliZ"}, {{}}, {{0}}); + auto obs2 = ObsDatum({"PauliZ"}, {{}}, {{1}}); + auto obs3 = ObsDatum({"PauliZ"}, {{}}, {{2}}); auto ops = adj.createOpsData({"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, @@ -206,8 +176,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs = adj.createObs({"PauliZ", "PauliZ", "PauliZ"}, {{}, {}, {}}, - {{0}, {1}, {2}}); + auto obs = ObsDatum({"PauliZ", "PauliZ", "PauliZ"}, + {{}, {}, {}}, {{0}, {1}, {2}}); auto ops = adj.createOpsData({"RX", "RX", "RX"}, {{param[0]}, {param[1]}, {param[2]}}, {{0}, {1}, {2}}, {false, false, false}); @@ -232,8 +202,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { StateVector psi(cdata.data(), cdata.size()); cdata[0] = std::complex{1, 0}; - auto obs = adj.createObs({"PauliX", "PauliX", "PauliX"}, {{}, {}, {}}, - {{0}, {1}, {2}}); + auto obs = ObsDatum({"PauliX", "PauliX", "PauliX"}, + {{}, {}, {}}, {{0}, {1}, {2}}); auto ops = adj.createOpsData( {"RZ", "RY", "RZ", "CNOT", "CNOT", "RZ", "RY", "RZ"}, {{param[0]}, @@ -284,7 +254,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { -INVSQRT2()}; StateVector psi(cdata.data(), cdata.size()); - auto obs = adj.createObs({"PauliZ"}, {{}}, {{0}}); + auto obs = ObsDatum({"PauliZ"}, {{}}, {{0}}); auto ops = adj.createOpsData( {"RZ", "RY", "RZ"}, {{local_params[0]}, {local_params[1]}, {local_params[2]}}, From 3626f2fdaebad93641607ea5338aba5145d2ade1 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Wed, 1 Sep 2021 17:30:20 +0100 Subject: [PATCH 58/72] Allow support for "Hermitian" observables --- pennylane_lightning/src/bindings/Bindings.cpp | 126 ++++++++---------- 1 file changed, 57 insertions(+), 69 deletions(-) diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 5035cf357b..03674daa7f 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -661,81 +661,69 @@ void lightning_class_bindings(py::module &m) { "Apply the CRot gate."); //***********************************************************************// - // Observable::complex params + // Observable //***********************************************************************// - /* - class_name = "ObsC" + bitsize; - py::class_>>(m, class_name.c_str()) - .def(py::init &, - // const - std::vector>>&, const - std::vector>> &, const - std::vector> &>()) .def(py::init( - [](const std::vector &opnames, - const std::vector< - py::array_t< - std::complex, py::array::c_style | - py::array::forcecast - > - > &opmatrix, - const std::vector> &opwires) { - - return ObsDatum>{{opnames}, - {opmatrix.data(), opmatrix.}, {opwires} - }; - })) - .def("__repr__", - [](const ObsDatum> &obs) { - using namespace Pennylane::Util; - std::ostringstream obs_stream; - std::string obs_name = obs.getObsName()[0]; - for (size_t o = 1; o < obs.getObsName().size(); o++) { - if (o < obs.getObsName().size()) - obs_name += " @ "; - obs_name += obs.getObsName()[o]; - } - obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + - obs_stream.str() + " }"; - }) - .def("as_tuple", [](const ObsDatum> &obs) { - return std::tuple, - // - std::vector>>, - std::vector>>, - std::vector>>{ - obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; - });*/ - //***********************************************************************// - // Observable::real params - //***********************************************************************// + class_name = "ObsC" + bitsize; + using np_arr_c = py::array_t, + py::array::c_style | py::array::forcecast>; + using np_arr_r = + py::array_t; - class_name = "Obs" + bitsize; + using obs_data_var = std::variant; py::class_>(m, class_name.c_str()) - .def(py::init< - const std::vector &, - const std::vector::param_var_t> &, - const std::vector> &>()) - .def("__repr__", [](const ObsDatum &obs) { - using namespace Pennylane::Util; - std::ostringstream obs_stream; - std::string obs_name = obs.getObsName()[0]; - for (size_t o = 1; o < obs.getObsName().size(); o++) { - if (o < obs.getObsName().size()) - obs_name += " @ "; - obs_name += obs.getObsName()[o]; + .def(py::init([](const std::vector &names, + const std::vector ¶ms, + const std::vector> &wires) { + std::vector::param_var_t> conv_params( + params.size()); + for (size_t p_idx = 0; p_idx < params.size(); p_idx++) { + std::visit( + [&](const auto ¶m) { + using p_t = std::decay_t; + auto buffer = param.request(); + + if constexpr (std::is_same_v) { + auto ptr = static_cast *>( + buffer.ptr); + conv_params[p_idx] = + std::vector>{ + ptr, ptr + buffer.size}; + } else if constexpr (std::is_same_v) { + auto ptr = static_cast(buffer.ptr); + conv_params[p_idx] = + std::vector{ptr, ptr + buffer.size}; + } else { + PL_ABORT( + "Parameter datatype not current supported"); + } + }, + params[p_idx]); } - obs_stream << "'wires' : " << obs.getObsWires(); - return "Observable: { 'name' : " + obs_name + ", " + - obs_stream.str() + " }"; + return ObsDatum(names, conv_params, wires); + })) + .def("__repr__", + [](const ObsDatum &obs) { + using namespace Pennylane::Util; + std::ostringstream obs_stream; + std::string obs_name = obs.getObsName()[0]; + for (size_t o = 1; o < obs.getObsName().size(); o++) { + if (o < obs.getObsName().size()) + obs_name += " @ "; + obs_name += obs.getObsName()[o]; + } + obs_stream << "'wires' : " << obs.getObsWires(); + return "Observable: { 'name' : " + obs_name + ", " + + obs_stream.str() + " }"; + }) + .def("as_tuple", [](const ObsDatum &obs) { + using tup_t = std::tuple< + const std::vector, + std::vector::param_var_t>, + std::vector>>; + return tup_t{obs.getObsName(), obs.getObsParams(), + obs.getObsWires()}; }); - //.def("as_tuple", [](const ObsDatum &obs) { - // return std::tuple, - // std::vector>, - // std::vector>>{ - // obs.getObsName(), obs.getObsParams(), obs.getObsWires()}; - //}); //***********************************************************************// // Operations From 05bdacb94cf566a5d8ab07fba848c3c5e7d407cb Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Thu, 2 Sep 2021 13:32:24 +0100 Subject: [PATCH 59/72] Add support for mixing of observable types --- pennylane_lightning/_serialize.py | 14 ++--- .../src/algorithms/AdjointDiff.hpp | 59 ++++++++++--------- pennylane_lightning/src/bindings/Bindings.cpp | 27 +++++---- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index ace3b8af70..dfeb474fde 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -14,7 +14,7 @@ r""" Helper functions for serializing quantum tapes. """ -from typing import List, Tuple +from typing import List, Tuple, Union import numpy as np from pennylane import BasisState, Hadamard, Projector, QubitStateVector, Rot @@ -23,7 +23,7 @@ from pennylane.tape import QuantumTape try: - from .lightning_qubit_ops import StateVectorC128, ObsC128 + from .lightning_qubit_ops import StateVectorC128, ObsStructC128 except ImportError: pass @@ -46,7 +46,7 @@ def _obs_has_kernel(obs: Observable) -> bool: return False -def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsC128]: +def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: """Serializes the observables of an input tape. Args: @@ -54,13 +54,13 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsC128]: wires_map (dict): a dictionary mapping input wires to the device's backend wires Returns: - list(ObsC128): A list of observable objects compatible with the C++ backend + list(ObsStructC128): A list of observable objects compatible with the C++ backend """ obs = [] for o in tape.observables: is_tensor = isinstance(o, Tensor) - + wires_list = o.wires.tolist() wires = [wires_map[w] for w in wires_list] name = o.name if is_tensor else [o.name] @@ -76,9 +76,9 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsC128]: params.append(o.matrix) ob = ( - ObsC128(name, params, [[w] for w in wires]) + ObsStructC128(name, params, [[w] for w in wires]) if is_tensor - else ObsC128(name, params, [wires]) + else ObsStructC128(name, params, [wires]) ) obs.append(ob) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 6ad7a5d3db..e9ed3c672e 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -120,9 +120,8 @@ namespace Algorithms { */ template class ObsDatum { public: - using param_var_t = - std::variant>, - std::vector>; + using param_var_t = std::variant, + std::vector>>; /** * @brief Construct an ObsDatum object, representing a given observable. @@ -419,32 +418,34 @@ template class AdjointJacobian { inline void applyObservable(StateVectorManaged &state, const ObsDatum &observable) { for (size_t j = 0; j < observable.getSize(); j++) { - std::visit( - [&](const auto ¶m) { - using p_t = std::decay_t; - - // Apply supported gate with given params - if constexpr (std::is_same_v>) { - state.applyOperation(observable.getObsName()[j], - observable.getObsWires()[j], false, - param); - } - // Apply provided matrix - else if constexpr (std::is_same_v< - p_t, std::vector>>) { - state.applyOperation(param, observable.getObsWires()[j], - false); - } - // No params provided, offload to SV dispatcher - else if constexpr (std::is_same_v) { - state.applyOperation(observable.getObsName()[j], - observable.getObsWires()[j], - false); - } else { - PL_ABORT("Parameter type not supported"); - } - }, - observable.getObsParams()[j]); + if (observable.getObsParams().size() > 0) { + std::visit( + [&](const auto param) { + using p_t = std::decay_t; + // Apply supported gate with given params + if constexpr (std::is_same_v>) { + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], + false, param); + } + // Apply provided matrix + else if constexpr (std::is_same_v< + p_t, + std::vector>>) { + state.applyOperation( + param, observable.getObsWires()[j], false); + } else { // Monostate: vector entry with an empty + // parameter list + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], + false); + } + }, + observable.getObsParams()[j]); + } else { // Offloat to SV dispatcher if no parameters provided + state.applyOperation(observable.getObsName()[j], + observable.getObsWires()[j], false); + } } } diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 03674daa7f..9bb94b3279 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -482,15 +482,12 @@ class AdjJacBinder : public AdjointJacobian { std::vector>, std::vector, std::vector>>>; - // names, params, wires, inverses, mats public: explicit AdjJacBinder() : AdjointJacobian() {} void adjoint_jacobian_py(const ObsTuple &obs, const OpsTuple &ops, const std::vector &trainableParamIndices, - size_t num_params) { - // std::get<0>(obs) - } + size_t num_params) {} }; /** @@ -664,7 +661,7 @@ void lightning_class_bindings(py::module &m) { // Observable //***********************************************************************// - class_name = "ObsC" + bitsize; + class_name = "ObsStructC" + bitsize; using np_arr_c = py::array_t, py::array::c_style | py::array::forcecast>; using np_arr_r = @@ -678,21 +675,25 @@ void lightning_class_bindings(py::module &m) { std::vector::param_var_t> conv_params( params.size()); for (size_t p_idx = 0; p_idx < params.size(); p_idx++) { + std::visit( [&](const auto ¶m) { using p_t = std::decay_t; - auto buffer = param.request(); - if constexpr (std::is_same_v) { + auto buffer = param.request(); auto ptr = static_cast *>( buffer.ptr); - conv_params[p_idx] = - std::vector>{ - ptr, ptr + buffer.size}; + if (buffer.size > 0) + conv_params[p_idx] = + std::vector>{ + ptr, ptr + buffer.size}; } else if constexpr (std::is_same_v) { + auto buffer = param.request(); + auto ptr = static_cast(buffer.ptr); - conv_params[p_idx] = - std::vector{ptr, ptr + buffer.size}; + if (buffer.size > 0) + conv_params[p_idx] = std::vector{ + ptr, ptr + buffer.size}; } else { PL_ABORT( "Parameter datatype not current supported"); @@ -752,7 +753,7 @@ void lightning_class_bindings(py::module &m) { py::class_>(m, class_name.c_str()) .def(py::init<>()) //.def("create_obs", &AdjointJacobian::createObs) - //.def("create_ops_list", &AdjointJacobian::createOpsData) + .def("create_ops_list", &AdjointJacobian::createOpsData) .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) .def("adjoint_jacobian", [](AdjointJacobian &adj, From f95aa0287b5e736b569bb68b2e8975d0642204a9 Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Fri, 3 Sep 2021 12:27:32 -0400 Subject: [PATCH 60/72] Fix serialization tests for adjoint method (#141) * Update tests * Add problematic statements * Fix tests * Fix type hinting * Make function as expected * Add quick return for no jacobian * Move position * Add raise for non supported ops * Ensure jacobian shape formatted as expected * Return jacobian directly as numpy array * Ensure correct return type of jacobian * Ensure param number tracking is correct for adjjac * Fix CRY gate index * All tests passing * Calculate parametric ops when creating opsdata object * Apply formatting * Ensure MacOS wheel builder uses a more modern compiler toolchain * Fix mac wheel builder Co-authored-by: Lee J. O'Riordan --- .github/workflows/wheel_macos_x86_64.yml | 4 +- CMakeLists.txt | 2 +- pennylane_lightning/_serialize.py | 35 ++-- pennylane_lightning/lightning_qubit.py | 19 +- .../src/algorithms/AdjointDiff.hpp | 84 +++++--- pennylane_lightning/src/bindings/Bindings.cpp | 8 +- .../src/simulator/StateVector.hpp | 3 +- pennylane_lightning/src/tests/CMakeLists.txt | 6 +- .../src/tests/Test_AdjDiff.cpp | 121 ++++++++---- pennylane_lightning/src/util/CMakeLists.txt | 4 +- setup.py | 4 +- tests/test_adjoint_jacobian.py | 3 +- tests/test_serialize.py | 182 ++++++++++++------ 13 files changed, 324 insertions(+), 151 deletions(-) diff --git a/.github/workflows/wheel_macos_x86_64.yml b/.github/workflows/wheel_macos_x86_64.yml index 09c67f2309..00b565d3e0 100644 --- a/.github/workflows/wheel_macos_x86_64.yml +++ b/.github/workflows/wheel_macos_x86_64.yml @@ -11,7 +11,7 @@ env: # MacOS specific build settings CIBW_BEFORE_ALL_MACOS: | brew uninstall --force oclint - brew install gcc libomp + brew install llvm libomp # Python build settings CIBW_BEFORE_BUILD: | @@ -53,6 +53,8 @@ jobs: run: python -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS_MACOS: ${{matrix.arch}} + CC: /usr/local/opt/llvm/bin/clang + CXX: /usr/local/opt/llvm/bin/clang++ - uses: actions/upload-artifact@v2 if: github.ref == 'refs/heads/master' diff --git a/CMakeLists.txt b/CMakeLists.txt index e41b7064a5..744ab8b153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ set(PROJECT_VERSION ${VERSION_STRING}) set(CMAKE_CXX_STANDARD 17) # At least C++17 is required if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) + set(CMAKE_BUILD_TYPE Debug) endif() option(ENABLE_NATIVE "Enable native CPU build tuning" OFF) diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index dfeb474fde..463f93ea82 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -14,7 +14,7 @@ r""" Helper functions for serializing quantum tapes. """ -from typing import List, Tuple, Union +from typing import List, Tuple import numpy as np from pennylane import BasisState, Hadamard, Projector, QubitStateVector, Rot @@ -46,7 +46,7 @@ def _obs_has_kernel(obs: Observable) -> bool: return False -def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: +def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List: """Serializes the observables of an input tape. Args: @@ -60,9 +60,19 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: for o in tape.observables: is_tensor = isinstance(o, Tensor) - - wires_list = o.wires.tolist() - wires = [wires_map[w] for w in wires_list] + + wires = [] + + if is_tensor: + for o_ in o.obs: + wires_list = o_.wires.tolist() + w = [wires_map[w] for w in wires_list] + wires.append(w) + else: + wires_list = o.wires.tolist() + w = [wires_map[w] for w in wires_list] + wires.append(w) + name = o.name if is_tensor else [o.name] params = [] @@ -71,15 +81,11 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List[ObsStructC128]: if is_tensor: for o_ in o.obs: if not _obs_has_kernel(o_): - params.append(o_.matrix) + params.append(o_.matrix.ravel()) else: - params.append(o.matrix) + params.append(o.matrix.ravel()) - ob = ( - ObsStructC128(name, params, [[w] for w in wires]) - if is_tensor - else ObsStructC128(name, params, [wires]) - ) + ob = ObsStructC128(name, params, wires) obs.append(ob) return obs @@ -107,8 +113,11 @@ def _serialize_ops( inverses = [] mats = [] + uses_stateprep = False + for o in tape.operations: if isinstance(o, (BasisState, QubitStateVector)): + uses_stateprep = True continue elif isinstance(o, Rot): op_list = o.expand().operations @@ -134,4 +143,4 @@ def _serialize_ops( wires_list = single_op.wires.tolist() wires.append([wires_map[w] for w in wires_list]) inverses.append(is_inverse) - return names, params, wires, inverses, mats + return (names, params, wires, inverses, mats), uses_stateprep diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 255362752a..16331b0674 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -24,6 +24,7 @@ QuantumFunctionError, QubitStateVector, QubitUnitary, + Rot, ) from pennylane.devices import DefaultQubit from pennylane.operation import Expectation @@ -155,6 +156,9 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): UserWarning, ) + if len(tape.trainable_params) == 0: + return np.array(0) + for m in tape.measurements: if m.return_type is not Expectation: raise QuantumFunctionError( @@ -162,6 +166,13 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): f" measurement {m.return_type.value}" ) + for op in tape.operations: + if op.num_params > 1 and not isinstance(op, Rot): + raise QuantumFunctionError( + f"The {op.name} operation is not supported using " + 'the "adjoint" differentiation method' + ) + # Initialization of state if starting_state is not None: ket = np.ravel(starting_state) @@ -174,19 +185,21 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): adj = AdjointJacobianC128() obs_serialized = _serialize_obs(tape, self.wire_map) - ops_serialized = _serialize_ops(tape, self.wire_map) + ops_serialized, use_sp = _serialize_ops(tape, self.wire_map) ops_serialized = adj.create_ops_list(*ops_serialized) + tp_shift = tape.trainable_params if not use_sp else {i - 1 for i in tape.trainable_params} + jac = adj.adjoint_jacobian( StateVectorC128(ket), obs_serialized, ops_serialized, - tape.trainable_params, + tp_shift, tape.num_params, ) - return jac # super().adjoint_jacobian(tape, starting_state, use_device_state) + return jac # .reshape((1,jac.size)) # super().adjoint_jacobian(tape, starting_state, use_device_state) if not CPP_BINARY_AVAILABLE: diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index e9ed3c672e..09d8d14620 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -72,7 +72,7 @@ void applyGeneratorCRY(SVType &sv, const std::vector &wires) { sv.generateBitPatterns(externalWires); for (const size_t &externalIndex : externalIndices) { std::complex *shiftedState = sv.getData() + externalIndex; - std::complex v0 = shiftedState[internalIndices[0]]; + std::complex v0 = shiftedState[internalIndices[2]]; shiftedState[internalIndices[0]] = shiftedState[internalIndices[1]] = 0; shiftedState[internalIndices[2]] = -IMAG() * shiftedState[internalIndices[3]]; @@ -185,6 +185,8 @@ template class ObsDatum { */ template class OpsData { private: + size_t num_par_ops_; + size_t num_nonpar_ops_; const std::vector ops_name_; const std::vector> ops_params_; const std::vector> ops_wires_; @@ -210,7 +212,15 @@ template class OpsData { const std::vector &ops_inverses, const std::vector>> &ops_matrices) : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, - ops_inverses_{ops_inverses}, ops_matrices_{ops_matrices} {}; + ops_inverses_{ops_inverses}, ops_matrices_{ops_matrices} { + num_par_ops_ = 0; + for (const auto &p : ops_params) { + if (p.size() > 0) { + num_par_ops_++; + } + } + num_nonpar_ops_ = ops_params.size() - num_par_ops_; + }; /** * @brief Construct an OpsData object, representing the serialized @@ -227,7 +237,15 @@ template class OpsData { const std::vector> &ops_wires, const std::vector &ops_inverses) : ops_name_{ops_name}, ops_params_{ops_params}, ops_wires_{ops_wires}, - ops_inverses_{ops_inverses}, ops_matrices_(ops_name.size()){}; + ops_inverses_{ops_inverses}, ops_matrices_(ops_name.size()) { + num_par_ops_ = 0; + for (const auto &p : ops_params) { + if (p.size() > 0) { + num_par_ops_++; + } + } + num_nonpar_ops_ = ops_params.size() - num_par_ops_; + }; /** * @brief Get the number of operations to be applied. @@ -285,6 +303,20 @@ template class OpsData { inline bool hasParams(size_t index) const { return !ops_params_[index].empty(); } + + /** + * @brief Get the number of parametric operations. + * + * @return size_t + */ + size_t getNumParOps() const { return num_par_ops_; } + + /** + * @brief Get the number of non-parametric ops. + * + * @return size_t + */ + size_t getNumNonParOps() const { return num_nonpar_ops_; } }; /** @@ -330,11 +362,12 @@ template class AdjointJacobian { * @param index Position of Jacobian to update. */ inline void updateJacobian(const std::complex *sv1, - const std::complex *sv2, std::vector &jac, + const std::complex *sv2, + std::vector> &jac, size_t num_elements, T scaling_coeff, - size_t index) { - jac[index] = - -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2, num_elements)); + size_t obs_index, size_t param_index) { + jac[obs_index][param_index] = + -2 * scaling_coeff * std::real(innerProdC(sv1, sv2, num_elements)); } /** * @brief Utility method to update the Jacobian at a given index by @@ -347,10 +380,11 @@ template class AdjointJacobian { */ inline void updateJacobian(const std::vector> &sv1, const std::vector> &sv2, - std::vector &jac, size_t num_elements, - T scaling_coeff, size_t index) { - PL_ASSERT(index < jac.size()); - jac[index] = -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); + std::vector> &jac, + size_t num_elements, T scaling_coeff, + size_t obs_index, size_t param_index) { + jac[obs_index][param_index] = + -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); } /** * @brief Utility method to update the Jacobian at a given index by @@ -363,10 +397,10 @@ template class AdjointJacobian { */ inline void updateJacobian(const StateVectorManaged &sv1, const StateVectorManaged &sv2, - std::vector &jac, size_t num_elements, - T scaling_coeff, size_t index) { - PL_ASSERT(index < jac.size()); - jac[index] = + std::vector> &jac, + size_t num_elements, T scaling_coeff, + size_t obs_index, size_t param_index) { + jac[obs_index][param_index] = -2 * scaling_coeff * std::imag(innerProdC(sv1.getDataVector(), sv2.getDataVector())); } @@ -417,10 +451,11 @@ template class AdjointJacobian { */ inline void applyObservable(StateVectorManaged &state, const ObsDatum &observable) { + using namespace Pennylane::Util; for (size_t j = 0; j < observable.getSize(); j++) { if (observable.getObsParams().size() > 0) { std::visit( - [&](const auto param) { + [&](const auto ¶m) { using p_t = std::decay_t; // Apply supported gate with given params if constexpr (std::is_same_v>) { @@ -434,8 +469,7 @@ template class AdjointJacobian { std::vector>>) { state.applyOperation( param, observable.getObsWires()[j], false); - } else { // Monostate: vector entry with an empty - // parameter list + } else { state.applyOperation(observable.getObsName()[j], observable.getObsWires()[j], false); @@ -542,7 +576,7 @@ template class AdjointJacobian { * @param num_params */ void adjointJacobian(const std::complex *psi, size_t num_elements, - std::vector &jac, + std::vector> &jac, const std::vector> &observables, const OpsData &operations, const std::set &trainableParams, @@ -553,7 +587,9 @@ template class AdjointJacobian { // Track positions within par and non-par operations size_t num_observables = observables.size(); size_t trainableParamNumber = trainableParams.size() - 1; - int current_param_idx = num_params - 1; + size_t current_param_idx = + operations.getNumParOps() - 1; // total number of parametric ops + auto tp_it = trainableParams.end(); // Create $U_{1:p}\vert \lambda \rangle$ @@ -584,25 +620,21 @@ template class AdjointJacobian { (operations.getOpsName()[op_idx] != "BasisState")) { mu.updateData(lambda.getDataVector()); - applyOperation(lambda, operations, op_idx, true); if (operations.hasParams(op_idx)) { if (std::find(trainableParams.begin(), tp_it, current_param_idx) != tp_it) { - const T scalingFactor = applyGenerator(mu, operations.getOpsName()[op_idx], operations.getOpsWires()[op_idx]); - size_t index; #pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - index = getJacIndex(obs_idx, trainableParamNumber, - trainableParams.size()); updateJacobian(H_lambda[obs_idx], mu, jac, - num_elements, scalingFactor, index); + num_elements, scalingFactor, obs_idx, + trainableParamNumber); } trainableParamNumber--; std::advance(tp_it, -1); diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 9bb94b3279..dab54a3612 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -675,7 +675,6 @@ void lightning_class_bindings(py::module &m) { std::vector::param_var_t> conv_params( params.size()); for (size_t p_idx = 0; p_idx < params.size(); p_idx++) { - std::visit( [&](const auto ¶m) { using p_t = std::decay_t; @@ -752,7 +751,6 @@ void lightning_class_bindings(py::module &m) { class_name = "AdjointJacobianC" + bitsize; py::class_>(m, class_name.c_str()) .def(py::init<>()) - //.def("create_obs", &AdjointJacobian::createObs) .def("create_ops_list", &AdjointJacobian::createOpsData) .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) .def("adjoint_jacobian", @@ -761,11 +759,13 @@ void lightning_class_bindings(py::module &m) { const std::vector> &observables, const OpsData &operations, const set &trainableParams, size_t num_params) { - std::vector jac(num_params * observables.size()); + std::vector> jac( + observables.size(), + std::vector(num_params, 0)); adj.adjointJacobian(sv.getData(), sv.getLength(), jac, observables, operations, trainableParams, num_params); - return jac; + return py::array_t(py::cast(jac)); }); } diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index c226540a64..2b34c980dd 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -204,6 +204,7 @@ template class StateVector { */ void applyOperation(const string &opName, const vector &wires, bool inverse = false, const vector ¶ms = {}) { + using namespace Pennylane::Util; const auto gate = gates_.at(opName); if (gate_wires_.at(opName) != wires.size()) throw std::invalid_argument( @@ -215,7 +216,6 @@ template class StateVector { const vector externalWires = getIndicesAfterExclusion(wires); const vector externalIndices = generateBitPatterns(externalWires); - gate(internalIndices, externalIndices, inverse, params); } @@ -465,6 +465,7 @@ template class StateVector { void applyPauliX(const vector &indices, const vector &externalIndices, bool inverse) { for (const size_t &externalIndex : externalIndices) { + CFP_t *shiftedState = arr_ + externalIndex; std::swap(shiftedState[indices[0]], shiftedState[indices[1]]); } diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index e7f2c665b6..883b27ea2f 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -6,9 +6,9 @@ set(CMAKE_CXX_STANDARD 17) find_package(OpenMP) # Default build type for test code is Debug -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) -endif() +#if(NOT CMAKE_BUILD_TYPE) +set(CMAKE_BUILD_TYPE Debug) +#endif() option(ENABLE_NATIVE "Enable native CPU build tuning" OFF) diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 8677f2e5b5..8e26593185 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -45,7 +45,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_params = 3; const size_t num_obs = 1; auto obs = ObsDatum({"PauliZ"}, {{}}, {{0}}); - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); for (const auto &p : param) { auto ops = adj.createOpsData({"RX"}, {{p}}, {{0}}, {false}); @@ -57,7 +58,7 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, ops, {0}, 1, true); CAPTURE(jacobian); - CHECK(-sin(p) == Approx(jacobian.front())); + CHECK(-sin(p) == Approx(jacobian[0].front())); } } @@ -67,7 +68,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { const size_t num_obs = 1; auto obs = ObsDatum({"PauliX"}, {{}}, {{0}}); - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); for (const auto &p : param) { auto ops = adj.createOpsData({"RY"}, {{p}}, {{0}}, {false}); @@ -81,14 +83,15 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { ops, {0}, 1, true); CAPTURE(jacobian); - CHECK(cos(p) == Approx(jacobian.front()).margin(1e-7)); + CHECK(cos(p) == Approx(jacobian[0].front()).margin(1e-7)); } } SECTION("Single RX gradient, 2 expval") { const size_t num_qubits = 2; const size_t num_params = 1; const size_t num_obs = 2; - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); @@ -103,14 +106,15 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { {obs1, obs2}, ops, {0}, num_params, true); CAPTURE(jacobian); - CHECK(-sin(param[0]) == Approx(jacobian[0]).margin(1e-7)); - CHECK(0.0 == Approx(jacobian[1]).margin(1e-7)); + CHECK(-sin(param[0]) == Approx(jacobian[0][0]).margin(1e-7)); + CHECK(0.0 == Approx(jacobian[1][1]).margin(1e-7)); } SECTION("Multiple RX gradient, single expval per wire") { const size_t num_qubits = 3; const size_t num_params = 3; const size_t num_obs = 3; - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); @@ -129,18 +133,16 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { true); CAPTURE(jacobian); - CHECK(-sin(param[0]) == - Approx(jacobian[0 * num_params + 0]).margin(1e-7)); - CHECK(-sin(param[1]) == - Approx(jacobian[1 * num_params + 1]).margin(1e-7)); - CHECK(-sin(param[2]) == - Approx(jacobian[2 * num_params + 2]).margin(1e-7)); + CHECK(-sin(param[0]) == Approx(jacobian[0][0]).margin(1e-7)); + CHECK(-sin(param[1]) == Approx(jacobian[1][1]).margin(1e-7)); + CHECK(-sin(param[2]) == Approx(jacobian[2][2]).margin(1e-7)); } SECTION("Multiple RX gradient, single expval per wire, subset of params") { const size_t num_qubits = 3; const size_t num_params = 3; const size_t num_obs = 3; - std::vector jacobian(num_params * num_obs, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::set t_params{0, 2}; std::vector> cdata(0b1 << num_qubits); @@ -160,17 +162,16 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { true); CAPTURE(jacobian); - CHECK(-sin(param[0]) == - Approx(jacobian[0 * num_params + 0]).margin(1e-7)); - CHECK(0 == Approx(jacobian[1 * num_params + 1]).margin(1e-7)); - CHECK(-sin(param[2]) == - Approx(jacobian[1 * num_params + 2]).margin(1e-7)); + CHECK(-sin(param[0]) == Approx(jacobian[0][0]).margin(1e-7)); + CHECK(0 == Approx(jacobian[1][1]).margin(1e-7)); + CHECK(-sin(param[2]) == Approx(jacobian[2][1]).margin(1e-7)); } SECTION("Multiple RX gradient, tensor expval") { const size_t num_qubits = 3; const size_t num_params = 3; const size_t num_obs = 1; - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); @@ -187,16 +188,17 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CAPTURE(jacobian); // Computed with parameter shift - CHECK(-0.1755096592645253 == Approx(jacobian[0]).margin(1e-7)); - CHECK(0.26478810666384334 == Approx(jacobian[1]).margin(1e-7)); - CHECK(-0.6312451595102775 == Approx(jacobian[2]).margin(1e-7)); + CHECK(-0.1755096592645253 == Approx(jacobian[0][0]).margin(1e-7)); + CHECK(0.26478810666384334 == Approx(jacobian[0][1]).margin(1e-7)); + CHECK(-0.6312451595102775 == Approx(jacobian[0][2]).margin(1e-7)); } SECTION("Mixed gradient, tensor expval") { const size_t num_qubits = 3; const size_t num_params = 6; const size_t num_obs = 1; - std::vector jacobian(num_obs * num_params, 0.0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::vector> cdata(0b1 << num_qubits); StateVector psi(cdata.data(), cdata.size()); @@ -222,12 +224,12 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CAPTURE(jacobian); // Computed with PennyLane using default.qubit.adjoint_jacobian - CHECK(0.0 == Approx(jacobian[0]).margin(1e-7)); - CHECK(-0.674214427 == Approx(jacobian[1]).margin(1e-7)); - CHECK(0.275139672 == Approx(jacobian[2]).margin(1e-7)); - CHECK(0.275139672 == Approx(jacobian[3]).margin(1e-7)); - CHECK(-0.0129093062 == Approx(jacobian[4]).margin(1e-7)); - CHECK(0.323846156 == Approx(jacobian[5]).margin(1e-7)); + CHECK(0.0 == Approx(jacobian[0][0]).margin(1e-7)); + CHECK(-0.674214427 == Approx(jacobian[0][1]).margin(1e-7)); + CHECK(0.275139672 == Approx(jacobian[0][2]).margin(1e-7)); + CHECK(0.275139672 == Approx(jacobian[0][3]).margin(1e-7)); + CHECK(-0.0129093062 == Approx(jacobian[0][4]).margin(1e-7)); + CHECK(0.323846156 == Approx(jacobian[0][5]).margin(1e-7)); } SECTION("Decomposed Rot gate, non computational basis state") { @@ -248,7 +250,8 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { for (const auto &theta : thetas) { std::vector local_params{theta, std::pow(theta, 3), SQRT2() * theta}; - std::vector jacobian(num_obs * num_params, 0); + std::vector> jacobian( + num_obs, std::vector(num_params, 0)); std::vector> cdata{INVSQRT2(), -INVSQRT2()}; @@ -266,9 +269,57 @@ TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { CAPTURE(jacobian); // Computed with PennyLane using default.qubit - CHECK(expec_results[theta][0] == Approx(jacobian[0]).margin(1e-7)); - CHECK(expec_results[theta][1] == Approx(jacobian[1]).margin(1e-7)); - CHECK(expec_results[theta][2] == Approx(jacobian[2]).margin(1e-7)); + CHECK(expec_results[theta][0] == + Approx(jacobian[0][0]).margin(1e-7)); + CHECK(expec_results[theta][1] == + Approx(jacobian[0][1]).margin(1e-7)); + CHECK(expec_results[theta][2] == + Approx(jacobian[0][2]).margin(1e-7)); } } + SECTION("Trainable params subset") { + const size_t num_qubits = 2; + const std::set t_params{1, 2, 3}; + const size_t num_obs = 1; + + const auto thetas = Util::linspace(-2 * M_PI, 2 * M_PI, 8); + + std::vector local_params{0.543, 0.54, 0.1, 0.5, 1.3, + -2.3, 0.5, -0.5, 0.5}; + std::vector> jacobian( + num_obs, std::vector(t_params.size(), 0)); + + std::vector> cdata{ONE(), ZERO(), + ZERO(), ZERO()}; + StateVector psi(cdata.data(), cdata.size()); + + auto obs = ObsDatum({"PauliX", "PauliZ"}, {{}, {}}, {{0}, {1}}); + auto ops = adj.createOpsData( + {"Hadamard", "RX", "CNOT", "RZ", "RY", "RZ", "RZ", "RY", "RZ", "RZ", + "RY", "CNOT"}, + {{}, + {local_params[0]}, + {}, + {local_params[1]}, + {local_params[2]}, + {local_params[3]}, + {local_params[4]}, + {local_params[5]}, + {local_params[6]}, + {local_params[7]}, + {local_params[8]}, + {}}, + {{0}, {0}, {0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {1}, {0, 1}}, + {false, false, false, false, false, false, false, false, false, + false, false, false}); + + adj.adjointJacobian(psi.getData(), psi.getLength(), jacobian, {obs}, + ops, t_params, t_params.size(), true); + CAPTURE(jacobian); + + // Computed with PennyLane using default.qubit + CHECK(-0.71429188 == Approx(jacobian[0][0])); + CHECK(0.04998561 == Approx(jacobian[0][1])); + CHECK(-0.71904837 == Approx(jacobian[0][2])); + } } diff --git a/pennylane_lightning/src/util/CMakeLists.txt b/pennylane_lightning/src/util/CMakeLists.txt index ffffaa50d3..20e75282f5 100644 --- a/pennylane_lightning/src/util/CMakeLists.txt +++ b/pennylane_lightning/src/util/CMakeLists.txt @@ -2,4 +2,6 @@ project(lightning_utils LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) add_library(lightning_utils INTERFACE) -target_include_directories(lightning_utils INTERFACE $) \ No newline at end of file +target_include_directories(lightning_utils INTERFACE $ + $ +) \ No newline at end of file diff --git a/setup.py b/setup.py index e08dc2b171..da9f9d18c1 100644 --- a/setup.py +++ b/setup.py @@ -75,12 +75,12 @@ class BuildExt(build_ext): c_opts = { "msvc": ["-EHsc", "-O2", "-W1", "-std:c++17", "-D_USE_MATH_DEFINES"], - "unix": ["-O3", "-W", "-fPIC", "-shared", "-fopenmp"], + "unix": ["-O0", "-g", "-W", "-fPIC", "-shared", "-fopenmp"], } l_opts = { "msvc": [], - "unix": ["-O3", "-W", "-fPIC", "-shared", "-fopenmp"], + "unix": ["-O0", "-g", "-W", "-fPIC", "-shared", "-fopenmp"], } if platform.system() == "Darwin": diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 67bf052d63..773f78ddb7 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -97,7 +97,7 @@ def test_unsupported_op(self, dev): qml.CRot(0.1, 0.2, 0.3, wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with pytest.raises(qml.QuantumFunctionError, match=".*Error in PennyLane Lightning: The operation is not.*"): + with pytest.raises(qml.QuantumFunctionError, match="The CRot operation is not supported using the"): dev.adjoint_jacobian(tape) @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) @@ -218,6 +218,7 @@ def test_gradients(self, op, obs, tol, dev): grad_F = tape.jacobian(dev, method="numeric") grad_D = dev.adjoint_jacobian(tape) + print(grad_D, grad_F) assert np.allclose(grad_D, grad_F, atol=tol, rtol=0) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 6cd7074bd2..931430ff94 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -20,6 +20,17 @@ from pennylane_lightning._serialize import (_serialize_obs, _serialize_ops, _obs_has_kernel) +import pytest + +try: + from pennylane_lightning.lightning_qubit_ops import ObsStructC128 +except ImportError: + pytestmark = pytest.mark.skip + +from unittest import mock + +import pennylane_lightning + class TestOpsHasKernel: """Tests for the _obs_has_kernel function""" @@ -70,77 +81,123 @@ class TestSerializeObs: wires_dict = {i: i for i in range(10)} - def test_basic_return(self): + def test_basic_return(self, monkeypatch): """Test expected serialization for a simple return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ(0)) - s = _serialize_obs(tape, self.wires_dict) - s_expected = (["PauliZ"], [], [[0]],) - assert s[0].as_tuple() == s_expected + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) - def test_tensor_return(self): + s = mock_obs.call_args[0] + s_expected = (["PauliZ"], [], [[0]]) + ObsStructC128(*s_expected) + + assert s == s_expected + + def test_tensor_return(self, monkeypatch): """Test expected serialization for a tensor product return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - s = _serialize_obs(tape, self.wires_dict) - s_expected = (["PauliZ", "PauliZ"], [], [[0], [1]],) - assert s[0].as_tuple() == s_expected + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) - def test_tensor_non_tensor_return(self): + s = mock_obs.call_args[0] + s_expected = (["PauliZ", "PauliZ"], [], [[0], [1]]) + ObsStructC128(*s_expected) + + assert s == s_expected + + def test_tensor_non_tensor_return(self, monkeypatch): """Test expected serialization for a mixture of tensor product and non-tensor product return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) qml.expval(qml.Hadamard(1)) - s = _serialize_obs(tape, self.wires_dict) + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) + + s = mock_obs.call_args_list + s_expected = [ - (["PauliZ", "PauliX"], [], [[0], [1]]), - (["Hadamard"], [], [[1]]), + (['PauliZ', 'PauliX'], [], [[0], [1]]), + (['Hadamard'], [], [[1]]), ] + [ObsStructC128(*s_expected) for s_expected in s_expected] - assert [ob.as_tuple() for ob in s] == s_expected + assert s[0][0] == s_expected[0] + assert s[1][0] == s_expected[1] - def test_hermitian_return(self): + def test_hermitian_return(self, monkeypatch): """Test expected serialization for a Hermitian return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) - s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["Hermitian"]], [np.eye(4)], [[0, 1]]) + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) + + s = mock_obs.call_args[0] + s_expected = (["Hermitian"], [np.eye(4).ravel()], [[0, 1]]) + ObsStructC128(*s_expected) assert s[0] == s_expected[0] assert np.allclose(s[1], s_expected[1]) assert s[2] == s_expected[2] - def test_hermitian_tensor_return(self): + def test_hermitian_tensor_return(self, monkeypatch): """Test expected serialization for a Hermitian return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.Hermitian(np.eye(2), wires=[2])) - s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["Hermitian", "Hermitian"]], [np.eye(4), np.eye(2)], [[0, 1, 2]]) + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) + + s = mock_obs.call_args[0] + s_expected = (["Hermitian", "Hermitian"], [np.eye(4).ravel(), np.eye(2).ravel()], [[0, 1], [2]]) + ObsStructC128(*s_expected) assert s[0] == s_expected[0] assert np.allclose(s[1][0], s_expected[1][0]) assert np.allclose(s[1][1], s_expected[1][1]) assert s[2] == s_expected[2] - def test_mixed_tensor_return(self): + def test_mixed_tensor_return(self, monkeypatch): """Test expected serialization for a mixture of Hermitian and Pauli return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2)) - s = _serialize_obs(tape, self.wires_dict) - s_expected = ([["Hermitian", "PauliY"]], [np.eye(4)], [[0, 1, 2]]) + mock_obs = mock.MagicMock() + + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, self.wires_dict) + + s = mock_obs.call_args[0] + s_expected = (["Hermitian", "PauliY"], [np.eye(4).ravel()], [[0, 1], [2]]) + ObsStructC128(*s_expected) assert s[0] == s_expected[0] assert np.allclose(s[1][0], s_expected[1][0]) assert s[2] == s_expected[2] - def test_integration(self): + def test_integration(self, monkeypatch): """Test for a comprehensive range of returns""" wires_dict = {"a": 0, 1: 1, "b": 2, -1: 3, 3.141: 4, "five": 5, 6: 6, 77: 7, 9: 8} I = np.eye(2) @@ -148,6 +205,8 @@ def test_integration(self): Y = qml.PauliY.matrix Z = qml.PauliZ.matrix + mock_obs = mock.MagicMock() + with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ("a") @ qml.PauliX("b")) qml.expval(qml.Hermitian(I, wires=1)) @@ -155,22 +214,24 @@ def test_integration(self): qml.expval(qml.Projector([1, 1], wires=[6, 77]) @ qml.Hermitian(Y, wires=9)) qml.expval(qml.Hermitian(Z, wires="a") @ qml.Identity(1)) - s = _serialize_obs(tape, wires_dict) - s_expected = ( - [ - ["PauliZ", "PauliX"], - ["Hermitian"], - ["PauliZ", "Hermitian", "Hadamard"], - ["Projector", "Hermitian"], - ["Hermitian", "Identity"], - ], - [I, X, Y, Z], - [[0, 2], [1], [3, 4, 5], [6, 7, 8], [0, 1]], - ) + with monkeypatch.context() as m: + m.setattr(pennylane_lightning._serialize, "ObsStructC128", mock_obs) + _serialize_obs(tape, wires_dict) - assert s[0] == s_expected[0] - assert all(np.allclose(s1, s2) for s1, s2 in zip(s[1], s_expected[1])) - assert s[2] == s_expected[2] + s = mock_obs.call_args_list + + s_expected = [ + (["PauliZ", "PauliX"], [], [[0], [2]]), + (['Hermitian'], [I.ravel()], [[1]]), + (["PauliZ", "Hermitian", "Hadamard"], [X.ravel()], [[3], [4], [5]]), + (["Projector", "Hermitian"], [Y.ravel()], [[6, 7], [8]]), + (["Hermitian", "Identity"], [Z.ravel()], [[0], [1]]), + ] + [ObsStructC128(*s_expected) for s_expected in s_expected] + + assert all(s1[0][0] == s2[0] for s1, s2 in zip(s, s_expected)) + assert all(np.allclose(s1[0][1], s2[1]) for s1, s2 in zip(s, s_expected)) + assert all(s1[0][2] == s2[2] for s1, s2 in zip(s, s_expected)) class TestSerializeOps: @@ -186,13 +247,13 @@ def test_basic_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = ( + s_expected = (( ["RX", "RY", "CNOT"], [[0.4], [0.6], []], [[0], [1], [0, 1]], [False, False, False], [[], [], []], - ) + ), False) assert s == s_expected def test_skips_prep_circuit(self): @@ -206,13 +267,13 @@ def test_skips_prep_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = ( + s_expected = (( ["RX", "RY", "CNOT"], [[0.4], [0.6], []], [[0], [1], [0, 1]], [False, False, False], [[], [], []], - ) + ), True) assert s == s_expected def test_inverse_circuit(self): @@ -223,13 +284,13 @@ def test_inverse_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = ( + s_expected = (( ["RX", "RY", "CNOT"], [[0.4], [0.6], []], [[0], [1], [0, 1]], [False, True, False], [[], [], []], - ) + ), False) assert s == s_expected def test_unsupported_kernel_circuit(self): @@ -242,7 +303,7 @@ def test_unsupported_kernel_circuit(self): qml.RZ(0.2, wires=2) s = _serialize_ops(tape, self.wires_dict) - s_expected = ( + s_expected = (( ["SingleExcitationPlus", "SingleExcitationMinus", "CNOT", "RZ"], [[], [], [], [0.2]], [[0, 1], [1, 2], [0, 1], [2]], @@ -253,13 +314,13 @@ def test_unsupported_kernel_circuit(self): [], [], ], - ) - assert s[0] == s_expected[0] - assert s[1] == s_expected[1] - assert s[2] == s_expected[2] - assert s[3] == s_expected[3] + ), False) + assert s[0][0] == s_expected[0][0] + assert s[0][1] == s_expected[0][1] + assert s[0][2] == s_expected[0][2] + assert s[0][3] == s_expected[0][3] - assert all(np.allclose(s1, s2) for s1, s2 in zip(s[4], s_expected[4])) + assert all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) def test_custom_wires_circuit(self): """Test expected serialization for a simple circuit with custom wire labels""" @@ -270,13 +331,13 @@ def test_custom_wires_circuit(self): qml.CNOT(wires=["a", 3.2]) s = _serialize_ops(tape, wires_dict) - s_expected = ( + s_expected = (( ["RX", "RY", "CNOT"], [[0.4], [0.6], []], [[0], [1], [0, 1]], [False, False, False], [[], [], []], - ) + ), False) assert s == s_expected def test_integration(self): @@ -290,7 +351,7 @@ def test_integration(self): qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) s = _serialize_ops(tape, self.wires_dict) - s_expected = ( + s_expected = (( ["RX", "RY", "CNOT", "QubitUnitary", "QFT", "DoubleExcitation"], [[0.4], [0.6], [], [], [], []], [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0]], @@ -299,14 +360,15 @@ def test_integration(self): [], [], [], - qml.QubitUnitary(np.eye(4), wires=[0, 1]).matrix, + qml.QubitUnitary(np.eye(4, dtype=np.complex128), wires=[0, 1]).matrix, qml.QFT(wires=[0, 1, 2]).inv().matrix, qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]).matrix, ], - ) - assert s[0] == s_expected[0] + ), False) + assert s[0][0] == s_expected[0][0] + assert s[0][1] == s_expected[0][1] + assert s[0][2] == s_expected[0][2] + assert s[0][3] == s_expected[0][3] assert s[1] == s_expected[1] - assert s[2] == s_expected[2] - assert s[3] == s_expected[3] - assert all(np.allclose(s1, s2) for s1, s2 in zip(s[4], s_expected[4])) + assert all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) From 380135acbbe64cc2b010fdd8a829a742d9b0df2f Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Fri, 3 Sep 2021 17:36:08 +0100 Subject: [PATCH 61/72] Fix compilation --- CMakeLists.txt | 2 +- pennylane_lightning/src/tests/CMakeLists.txt | 6 +++--- setup.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 744ab8b153..e41b7064a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ set(PROJECT_VERSION ${VERSION_STRING}) set(CMAKE_CXX_STANDARD 17) # At least C++17 is required if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) + set(CMAKE_BUILD_TYPE Release) endif() option(ENABLE_NATIVE "Enable native CPU build tuning" OFF) diff --git a/pennylane_lightning/src/tests/CMakeLists.txt b/pennylane_lightning/src/tests/CMakeLists.txt index 883b27ea2f..b343c589ec 100644 --- a/pennylane_lightning/src/tests/CMakeLists.txt +++ b/pennylane_lightning/src/tests/CMakeLists.txt @@ -6,9 +6,9 @@ set(CMAKE_CXX_STANDARD 17) find_package(OpenMP) # Default build type for test code is Debug -#if(NOT CMAKE_BUILD_TYPE) -set(CMAKE_BUILD_TYPE Debug) -#endif() +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() option(ENABLE_NATIVE "Enable native CPU build tuning" OFF) diff --git a/setup.py b/setup.py index da9f9d18c1..e08dc2b171 100644 --- a/setup.py +++ b/setup.py @@ -75,12 +75,12 @@ class BuildExt(build_ext): c_opts = { "msvc": ["-EHsc", "-O2", "-W1", "-std:c++17", "-D_USE_MATH_DEFINES"], - "unix": ["-O0", "-g", "-W", "-fPIC", "-shared", "-fopenmp"], + "unix": ["-O3", "-W", "-fPIC", "-shared", "-fopenmp"], } l_opts = { "msvc": [], - "unix": ["-O0", "-g", "-W", "-fPIC", "-shared", "-fopenmp"], + "unix": ["-O3", "-W", "-fPIC", "-shared", "-fopenmp"], } if platform.system() == "Darwin": From bdab8488007d964fc85253b348c1a548abda0271 Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Fri, 10 Sep 2021 13:35:23 -0400 Subject: [PATCH 62/72] Add new integration test for adjoint jacobian (#142) * Add test for dev pl * Add test * Move to original tests * Add additional ops creation method * Fix QFT errors * Reformat * Revert "Fix QFT errors" This reverts commit f62a76374b5dc89bba93a0251da0652b25749fb6. * Preclude certain gates * Update test * Add double comments * Update test * Add tests for standard and custom wire labels * Remove print * Extend tests for RX,RY * Prevent trainable params from going negative * Ensure correct coef applied when using generator * Update Ops repr * Reenable tests * Update CI for non compiled builds * Add exception for Projector observable * Add Hermitian in tensor product * Ensure data is passed as complex128 to cpp * Run black * Ensure serialisation of numpy data is cast correctly * Remove redundant methods * Add support to get obs data * Fix serialize test array structures * Add hermitian checks * Ensure testing complete for current requirements * Reformat bindings * Skip test for Hermitian on default * Ensure bin available passed as class param * Skip for non binary tests Co-authored-by: Lee J. O'Riordan --- .github/workflows/tests.yml | 2 + .github/workflows/tests_without_binary.yml | 2 + pennylane_lightning/_serialize.py | 6 +- pennylane_lightning/lightning_qubit.py | 50 ++++- .../src/algorithms/AdjointDiff.hpp | 124 +++++------- pennylane_lightning/src/bindings/Bindings.cpp | 71 ++++++- .../src/simulator/StateVector.hpp | 1 - .../src/tests/Test_StateVector_Param.cpp | 176 ++++++++++++------ tests/test_adjoint_jacobian.py | 161 +++++++++++++++- tests/test_gates.py | 15 +- tests/test_serialize.py | 163 +++++++++------- 11 files changed, 546 insertions(+), 225 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4524771cb..23f6d87ee5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -74,6 +74,8 @@ jobs: cd main python -m pip install --upgrade pip pip install -r requirements.txt + pip uninstall pennylane -y + pip install git+https://github.com/PennyLaneAI/pennylane.git - name: Install lightning.qubit device run: | diff --git a/.github/workflows/tests_without_binary.yml b/.github/workflows/tests_without_binary.yml index 331d186d36..9a770dd968 100644 --- a/.github/workflows/tests_without_binary.yml +++ b/.github/workflows/tests_without_binary.yml @@ -37,6 +37,8 @@ jobs: cd main python -m pip install --upgrade pip pip install -r requirements.txt + pip uninstall pennylane -y + pip install git+https://github.com/PennyLaneAI/pennylane.git - name: Install lightning.qubit device run: | diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index 463f93ea82..d9e6a96790 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -81,9 +81,11 @@ def _serialize_obs(tape: QuantumTape, wires_map: dict) -> List: if is_tensor: for o_ in o.obs: if not _obs_has_kernel(o_): - params.append(o_.matrix.ravel()) + params.append(o_.matrix.ravel().astype(np.complex128)) + else: + params.append([]) else: - params.append(o.matrix.ravel()) + params.append(o.matrix.ravel().astype(np.complex128)) ob = ObsStructC128(name, params, wires) obs.append(ob) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 16331b0674..f399f56d56 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -24,8 +24,8 @@ QuantumFunctionError, QubitStateVector, QubitUnitary, - Rot, ) +import pennylane as qml from pennylane.devices import DefaultQubit from pennylane.operation import Expectation @@ -44,6 +44,19 @@ except ModuleNotFoundError: CPP_BINARY_AVAILABLE = False +UNSUPPORTED_PARAM_GATES_ADJOINT = ( + "MultiRZ", + "IsingXX", + "IsingYY", + "IsingZZ", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + "DoubleExcitation", + "DoubleExcitationPlus", + "DoubleExcitationMinus", +) + class LightningQubit(DefaultQubit): """PennyLane Lightning device. @@ -67,6 +80,7 @@ class LightningQubit(DefaultQubit): pennylane_requires = ">=0.15" version = __version__ author = "Xanadu Inc." + _CPP_BINARY_AVAILABLE = True def __init__(self, wires, *, shots=None): super().__init__(wires, shots=shots) @@ -146,7 +160,7 @@ def apply_lightning(self, state, operations): return np.reshape(state_vector, state.shape) def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): - if not CPP_BINARY_AVAILABLE: + if not self._CPP_BINARY_AVAILABLE: return super().adjoint_jacobian(tape, starting_state, use_device_state) if self.shots is not None: @@ -165,9 +179,29 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): "Adjoint differentiation method does not support" f" measurement {m.return_type.value}" ) + if not isinstance(m.obs, qml.operation.Tensor): + if isinstance(m.obs, qml.Projector): + raise QuantumFunctionError( + "Adjoint differentiation method does not support the Projector observable" + ) + if isinstance(m.obs, qml.Hermitian): + raise QuantumFunctionError( + "Lightning adjoint differentiation method does not currently support the Hermitian observable" + ) + else: + if any([isinstance(o, qml.Projector) for o in m.obs.non_identity_obs]): + raise QuantumFunctionError( + "Adjoint differentiation method does not support the Projector observable" + ) + if any([isinstance(o, qml.Hermitian) for o in m.obs.non_identity_obs]): + raise QuantumFunctionError( + "Lightning adjoint differentiation method does not currently support the Hermitian observable" + ) for op in tape.operations: - if op.num_params > 1 and not isinstance(op, Rot): + if ( + op.num_params > 1 and not isinstance(op, qml.Rot) + ) or op.name in UNSUPPORTED_PARAM_GATES_ADJOINT: raise QuantumFunctionError( f"The {op.name} operation is not supported using " 'the "adjoint" differentiation method' @@ -189,7 +223,11 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): ops_serialized = adj.create_ops_list(*ops_serialized) - tp_shift = tape.trainable_params if not use_sp else {i - 1 for i in tape.trainable_params} + tp_shift = ( + tape.trainable_params + if not use_sp + else {i - 1 for i in tape.trainable_params.difference({0})} + ) # exclude first index if explicitly setting sv jac = adj.adjoint_jacobian( StateVectorC128(ket), @@ -198,8 +236,7 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): tp_shift, tape.num_params, ) - - return jac # .reshape((1,jac.size)) # super().adjoint_jacobian(tape, starting_state, use_device_state) + return jac if not CPP_BINARY_AVAILABLE: @@ -211,6 +248,7 @@ class LightningQubit(DefaultQubit): pennylane_requires = ">=0.15" version = __version__ author = "Xanadu Inc." + _CPP_BINARY_AVAILABLE = False def __init__(self, *args, **kwargs): warn( diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 09d8d14620..718399a02d 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -31,27 +31,32 @@ template static constexpr std::vector> getP11() { } template > -void applyGeneratorRX(SVType &sv, const std::vector &wires) { - sv.applyOperation("PauliX", wires, false); +void applyGeneratorRX(SVType &sv, const std::vector &wires, + const bool adj = false) { + sv.applyOperation("PauliX", wires, adj); } template > -void applyGeneratorRY(SVType &sv, const std::vector &wires) { - sv.applyOperation("PauliY", wires, false); +void applyGeneratorRY(SVType &sv, const std::vector &wires, + const bool adj = false) { + sv.applyOperation("PauliY", wires, adj); } template > -void applyGeneratorRZ(SVType &sv, const std::vector &wires) { - sv.applyOperation("PauliZ", wires, false); +void applyGeneratorRZ(SVType &sv, const std::vector &wires, + const bool adj = false) { + sv.applyOperation("PauliZ", wires, adj); } template > -void applyGeneratorPhaseShift(SVType &sv, const std::vector &wires) { - sv.applyOperation(getP11(), wires, false); +void applyGeneratorPhaseShift(SVType &sv, const std::vector &wires, + const bool adj = false) { + sv.applyOperation(getP11(), wires, adj); } template > -void applyGeneratorCRX(SVType &sv, const std::vector &wires) { +void applyGeneratorCRX(SVType &sv, const std::vector &wires, + const bool adj = false) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -65,7 +70,8 @@ void applyGeneratorCRX(SVType &sv, const std::vector &wires) { } template > -void applyGeneratorCRY(SVType &sv, const std::vector &wires) { +void applyGeneratorCRY(SVType &sv, const std::vector &wires, + const bool adj = false) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -73,7 +79,8 @@ void applyGeneratorCRY(SVType &sv, const std::vector &wires) { for (const size_t &externalIndex : externalIndices) { std::complex *shiftedState = sv.getData() + externalIndex; std::complex v0 = shiftedState[internalIndices[2]]; - shiftedState[internalIndices[0]] = shiftedState[internalIndices[1]] = 0; + shiftedState[internalIndices[0]] = ZERO(); + shiftedState[internalIndices[1]] = ZERO(); shiftedState[internalIndices[2]] = -IMAG() * shiftedState[internalIndices[3]]; shiftedState[internalIndices[3]] = IMAG() * v0; @@ -81,7 +88,8 @@ void applyGeneratorCRY(SVType &sv, const std::vector &wires) { } template > -void applyGeneratorCRZ(SVType &sv, const std::vector &wires) { +void applyGeneratorCRZ(SVType &sv, const std::vector &wires, + const bool adj = false) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -95,7 +103,8 @@ void applyGeneratorCRZ(SVType &sv, const std::vector &wires) { template > void applyGeneratorControlledPhaseShift(SVType &sv, - const std::vector &wires) { + const std::vector &wires, + const bool adj = false) { const vector internalIndices = sv.generateBitPatterns(wires); const vector externalWires = sv.getIndicesAfterExclusion(wires); const vector externalIndices = @@ -327,9 +336,9 @@ template class OpsData { */ template class AdjointJacobian { private: - typedef void (*GeneratorFunc)( - StateVectorManaged &sv, - const std::vector &wires); // function pointer type + typedef void (*GeneratorFunc)(StateVectorManaged &sv, + const std::vector &wires, + const bool adj); // function pointer type // Holds the mapping from gate labels to associated generator functions. const std::unordered_map generator_map{ @@ -357,49 +366,14 @@ template class AdjointJacobian { * @param sv1 Statevector * @param jac Jacobian receiving the values. - * @param num_elements Length of statevectors * @param scaling_coeff Generator coefficient for given gate derivative. * @param index Position of Jacobian to update. */ - inline void updateJacobian(const std::complex *sv1, - const std::complex *sv2, - std::vector> &jac, - size_t num_elements, T scaling_coeff, - size_t obs_index, size_t param_index) { - jac[obs_index][param_index] = - -2 * scaling_coeff * std::real(innerProdC(sv1, sv2, num_elements)); - } - /** - * @brief Utility method to update the Jacobian at a given index by - calculating the overlap between two given states. - * - * @see updateJacobian(const std::complex *sv1, - const std::complex *sv2, std::vector &jac, - size_t num_elements, T scaling_coeff, - size_t index) - */ - inline void updateJacobian(const std::vector> &sv1, - const std::vector> &sv2, - std::vector> &jac, - size_t num_elements, T scaling_coeff, - size_t obs_index, size_t param_index) { - jac[obs_index][param_index] = - -2 * scaling_coeff * std::imag(innerProdC(sv1, sv2)); - } - /** - * @brief Utility method to update the Jacobian at a given index by - calculating the overlap between two given states. - * - * @see updateJacobian(const std::complex *sv1, - const std::complex *sv2, std::vector &jac, - size_t num_elements, T scaling_coeff, - size_t index) - */ inline void updateJacobian(const StateVectorManaged &sv1, const StateVectorManaged &sv2, std::vector> &jac, - size_t num_elements, T scaling_coeff, - size_t obs_index, size_t param_index) { + T scaling_coeff, size_t obs_index, + size_t param_index) { jac[obs_index][param_index] = -2 * scaling_coeff * std::imag(innerProdC(sv1.getDataVector(), sv2.getDataVector())); @@ -416,7 +390,6 @@ template class AdjointJacobian { inline void applyOperations(StateVectorManaged &state, const OpsData &operations, bool adj = false) { - for (size_t op_idx = 0; op_idx < operations.getOpsName().size(); op_idx++) { state.applyOperation(operations.getOpsName()[op_idx], @@ -433,12 +406,11 @@ template class AdjointJacobian { * @param operations Operations to apply. * @param adj Take the adjoint of the given operations. */ - inline void applyOperation(StateVectorManaged &state, - const OpsData &operations, size_t op_idx, - bool adj = false) { + inline void applyOperationAdj(StateVectorManaged &state, + const OpsData &operations, size_t op_idx) { state.applyOperation(operations.getOpsName()[op_idx], operations.getOpsWires()[op_idx], - operations.getOpsInverses()[op_idx] ^ adj, + !operations.getOpsInverses()[op_idx], operations.getOpsParams()[op_idx]); } @@ -453,7 +425,7 @@ template class AdjointJacobian { const ObsDatum &observable) { using namespace Pennylane::Util; for (size_t j = 0; j < observable.getSize(); j++) { - if (observable.getObsParams().size() > 0) { + if (!observable.getObsParams().empty()) { std::visit( [&](const auto ¶m) { using p_t = std::decay_t; @@ -486,21 +458,6 @@ template class AdjointJacobian { public: AdjointJacobian() {} - /** - * @brief Utility to create a given observable object. - * - * @param obs_name - * @param obs_params - * @param obs_wires - * @return const ObsDatum - */ - /*const ObsDatum - createObs(const std::vector &obs_name, - const std::vector> &obs_params, - const std::vector> &obs_wires) { - return ObsDatum{obs_name, obs_params, obs_wires}; - }*/ - /** * @brief Utility to create a given operations object. * @@ -543,8 +500,8 @@ template class AdjointJacobian { */ inline T applyGenerator(StateVectorManaged &sv, const std::string &op_name, - const std::vector &wires) { - generator_map.at(op_name)(sv, wires); + const std::vector &wires, const bool adj) { + generator_map.at(op_name)(sv, wires, adj); return scaling_factors.at(op_name); } @@ -620,20 +577,25 @@ template class AdjointJacobian { (operations.getOpsName()[op_idx] != "BasisState")) { mu.updateData(lambda.getDataVector()); - applyOperation(lambda, operations, op_idx, true); + applyOperationAdj(lambda, operations, op_idx); if (operations.hasParams(op_idx)) { if (std::find(trainableParams.begin(), tp_it, current_param_idx) != tp_it) { + const T scalingFactor = - applyGenerator(mu, operations.getOpsName()[op_idx], - operations.getOpsWires()[op_idx]); + applyGenerator( + mu, operations.getOpsName()[op_idx], + operations.getOpsWires()[op_idx], + !operations.getOpsInverses()[op_idx]) * + (2 * (0b1 ^ operations.getOpsInverses()[op_idx]) - + 1); size_t index; #pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { updateJacobian(H_lambda[obs_idx], mu, jac, - num_elements, scalingFactor, obs_idx, + scalingFactor, obs_idx, trainableParamNumber); } trainableParamNumber--; @@ -644,7 +606,7 @@ template class AdjointJacobian { #pragma omp parallel for for (size_t obs_idx = 0; obs_idx < num_observables; obs_idx++) { - applyOperation(H_lambda[obs_idx], operations, op_idx, true); + applyOperationAdj(H_lambda[obs_idx], operations, op_idx); } } } diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index dab54a3612..5659d87dfc 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -716,13 +716,35 @@ void lightning_class_bindings(py::module &m) { return "Observable: { 'name' : " + obs_name + ", " + obs_stream.str() + " }"; }) - .def("as_tuple", [](const ObsDatum &obs) { - using tup_t = std::tuple< - const std::vector, - std::vector::param_var_t>, - std::vector>>; - return tup_t{obs.getObsName(), obs.getObsParams(), - obs.getObsWires()}; + .def("get_name", + [](const ObsDatum &obs) { return obs.getObsName(); }) + .def("get_wires", + [](const ObsDatum &obs) { return obs.getObsWires(); }) + .def("get_params", [](const ObsDatum &obs) { + py::list params; + for (size_t i = 0; i < obs.getObsParams().size(); i++) { + std::visit( + [&](const auto ¶m) { + using p_t = std::decay_t; + if constexpr (std::is_same_v< + p_t, + std::vector>>) { + params.append(py::array_t>( + py::cast(param))); + } else if constexpr (std::is_same_v< + p_t, std::vector>) { + params.append( + py::array_t(py::cast(param))); + } else if constexpr (std::is_same_v) { + params.append(py::list{}); + } else { + throw("Unsupported data type"); + } + }, + obs.getObsParams()[i]); + } + return params; }); //***********************************************************************// @@ -741,7 +763,9 @@ void lightning_class_bindings(py::module &m) { std::ostringstream ops_stream; for (size_t op = 0; op < ops.getSize(); op++) { ops_stream << "{'name': " << ops.getOpsName()[op]; - ops_stream << ", 'params': " << ops.getOpsParams()[op] << "}"; + ops_stream << ", 'params': " << ops.getOpsParams()[op]; + ops_stream << ", 'inv': " << ops.getOpsInverses()[op]; + ops_stream << "}"; if (op < ops.getSize() - 1) ops_stream << ","; } @@ -752,6 +776,37 @@ void lightning_class_bindings(py::module &m) { py::class_>(m, class_name.c_str()) .def(py::init<>()) .def("create_ops_list", &AdjointJacobian::createOpsData) + .def("create_ops_list", + [](AdjointJacobian &adj, + const std::vector &ops_name, + const std::vector &ops_params, + const std::vector> &ops_wires, + const std::vector &ops_inverses, + const std::vector &ops_matrices) { + std::vector> conv_params( + ops_params.size()); + std::vector>> + conv_matrices(ops_matrices.size()); + for (size_t op = 0; op < ops_name.size(); op++) { + const auto p_buffer = ops_params[op].request(); + const auto m_buffer = ops_matrices[op].request(); + if (p_buffer.size > 0) { + const auto p_ptr = + static_cast(p_buffer.ptr); + conv_params[op] = + std::vector{p_ptr, p_ptr + p_buffer.size}; + } + if (m_buffer.size > 0) { + const auto m_ptr = + static_cast *>( + m_buffer.ptr); + conv_matrices[op] = std::vector>{ + m_ptr, m_ptr + m_buffer.size}; + } + } + return OpsData{ops_name, conv_params, ops_wires, + ops_inverses, conv_matrices}; + }) .def("adjoint_jacobian", &AdjointJacobian::adjointJacobian) .def("adjoint_jacobian", [](AdjointJacobian &adj, diff --git a/pennylane_lightning/src/simulator/StateVector.hpp b/pennylane_lightning/src/simulator/StateVector.hpp index 2b34c980dd..213b347bcd 100644 --- a/pennylane_lightning/src/simulator/StateVector.hpp +++ b/pennylane_lightning/src/simulator/StateVector.hpp @@ -204,7 +204,6 @@ template class StateVector { */ void applyOperation(const string &opName, const vector &wires, bool inverse = false, const vector ¶ms = {}) { - using namespace Pennylane::Util; const auto gate = gates_.at(opName); if (gate_wires_.at(opName) != wires.size()) throw std::invalid_argument( diff --git a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp index 0bd52a577b..683128a360 100644 --- a/pennylane_lightning/src/tests/Test_StateVector_Param.cpp +++ b/pennylane_lightning/src/tests/Test_StateVector_Param.cpp @@ -29,14 +29,20 @@ template struct SVData { StateVector sv; explicit SVData(size_t num_qubits) - : num_qubits{num_qubits}, // qubit_indices{num_qubits}, - cdata(0b1 << num_qubits), sv{cdata.data(), cdata.size()} { + : num_qubits{num_qubits}, + cdata(0b1 << num_qubits, std::complex{0, 0}), + sv{StateVector(nullptr, + static_cast(Util::exp2(num_qubits)))} { cdata[0] = std::complex{1, 0}; + sv.setData(cdata.data()); } explicit SVData(size_t num_qubits, const std::vector> &cdata_input) - : num_qubits{num_qubits}, // qubit_indices{num_qubits}, - cdata(cdata_input), sv{cdata.data(), cdata.size()} {} + : num_qubits{num_qubits}, cdata{cdata_input.begin(), cdata_input.end()}, + sv{StateVector(nullptr, + static_cast(Util::exp2(num_qubits)))} { + sv.setData(cdata.data()); + } vector getInternalIndices(const std::vector &qubit_indices) { return sv.generateBitPatterns(qubit_indices); @@ -48,43 +54,75 @@ template struct SVData { vector externalIndices = sv.generateBitPatterns(externalWires); return externalIndices; } + void setCData() {} }; } // namespace -TEMPLATE_TEST_CASE("StateVector::applyRX", "[StateVector_Param]", float, - double) { +TEMPLATE_TEST_CASE("StateVector::applyRX", "[StateVector_Param]", double) { using cp_t = std::complex; - const size_t num_qubits = 3; + const size_t num_qubits = 1; SVData svdat{num_qubits}; - const std::vector angles{0.1, 0.6, 2.1}; + const std::vector angles{{0.1}, {0.6}}; std::vector> expected_results{ - std::vector(8), std::vector(8), std::vector(8)}; + std::vector{{0.9987502603949663, 0.0}, + {0.0, -0.04997916927067834}}, + std::vector{{0.9553364891256061, 0.0}, {0, -0.2955202066613395}}, + std::vector{{0.49757104789172696, 0.0}, {0, -0.867423225594017}}}; - for (size_t i = 0; i < angles.size(); i++) { - const auto rx_mat = Gates::getRX(angles[i]); - expected_results[i][0] = rx_mat[0]; - expected_results[i][0b1 << (num_qubits - i - 1)] = rx_mat[1]; - } + std::vector> expected_results_adj{ + std::vector{{0.9987502603949663, 0.0}, + {0.0, 0.04997916927067834}}, + std::vector{{0.9553364891256061, 0.0}, {0, 0.2955202066613395}}, + std::vector{{0.49757104789172696, 0.0}, {0, 0.867423225594017}}}; const auto init_state = svdat.cdata; - SECTION("Apply directly") { - for (size_t index = 0; index < num_qubits; index++) { - SVData svdat_direct{num_qubits}; - auto int_idx = svdat_direct.getInternalIndices({index}); - auto ext_idx = svdat_direct.getExternalIndices({index}); + SECTION("adj = false") { + SECTION("Apply directly") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_direct{num_qubits, init_state}; - svdat_direct.sv.applyRX(int_idx, ext_idx, false, {angles[index]}); + auto int_idx = svdat_direct.getInternalIndices({0}); + auto ext_idx = svdat_direct.getExternalIndices({0}); - CHECK(isApproxEqual(svdat_direct.cdata, expected_results[index])); + svdat_direct.sv.applyRX(int_idx, ext_idx, false, angles[index]); + + CHECK(isApproxEqual(svdat_direct.cdata, expected_results[index], + 1e-7)); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_dispatch{num_qubits, init_state}; + svdat_dispatch.sv.applyOperation("RX", {0}, false, + {angles[index]}); + + CHECK(isApproxEqual(svdat_dispatch.cdata, + expected_results[index], 1e-7)); + } } } - SECTION("Apply using dispatcher") { - for (size_t index = 0; index < num_qubits; index++) { - SVData svdat_dispatch{num_qubits}; - svdat_dispatch.sv.applyOperation("RX", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.cdata, expected_results[index])); + SECTION("adj = true") { + SECTION("Apply directly") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_direct{num_qubits, init_state}; + auto int_idx = svdat_direct.getInternalIndices({0}); + auto ext_idx = svdat_direct.getExternalIndices({0}); + + svdat_direct.sv.applyRX(int_idx, ext_idx, true, + {angles[index]}); + CHECK(isApproxEqual(svdat_direct.cdata, + expected_results_adj[index], 1e-7)); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_dispatch{num_qubits, init_state}; + svdat_dispatch.sv.applyOperation("RX", {0}, true, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.cdata, + expected_results_adj[index], 1e-7)); + } } } } @@ -92,37 +130,71 @@ TEMPLATE_TEST_CASE("StateVector::applyRX", "[StateVector_Param]", float, TEMPLATE_TEST_CASE("StateVector::applyRY", "[StateVector_Param]", float, double) { using cp_t = std::complex; - const size_t num_qubits = 3; + const size_t num_qubits = 1; SVData svdat{num_qubits}; const std::vector angles{0.2, 0.7, 2.9}; std::vector> expected_results{ - std::vector(8), std::vector(8), std::vector(8)}; - - for (size_t i = 0; i < angles.size(); i++) { - const auto ry_mat = Gates::getRY(angles[i]); - expected_results[i][0] = ry_mat[0]; - expected_results[i][0b1 << (num_qubits - i - 1)] = ry_mat[2]; - } - - const auto init_state = svdat.cdata; - SECTION("Apply directly") { - for (size_t index = 0; index < num_qubits; index++) { - SVData svdat_direct{num_qubits}; - auto int_idx = svdat_direct.getInternalIndices({index}); - auto ext_idx = svdat_direct.getExternalIndices({index}); - - svdat_direct.sv.applyRY(int_idx, ext_idx, false, {angles[index]}); - - CHECK(isApproxEqual(svdat_direct.cdata, expected_results[index])); + std::vector{{0.8731983044562817, 0.04786268954660339}, + {0.0876120655431924, -0.47703040785184303}}, + std::vector{{0.8243771119105122, 0.16439396602553008}, + {0.3009211363333468, -0.45035926880694604}}, + std::vector{{0.10575112905629831, 0.47593196040758534}, + {0.8711876098966215, -0.0577721051072477}}}; + std::vector> expected_results_adj{ + std::vector{{0.8731983044562817, -0.04786268954660339}, + {-0.0876120655431924, -0.47703040785184303}}, + std::vector{{0.8243771119105122, -0.16439396602553008}, + {-0.3009211363333468, -0.45035926880694604}}, + std::vector{{0.10575112905629831, -0.47593196040758534}, + {-0.8711876098966215, -0.0577721051072477}}}; + + const std::vector init_state{{0.8775825618903728, 0.0}, + {0.0, -0.47942553860420306}}; + SECTION("adj = false") { + SECTION("Apply directly") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_direct{num_qubits, init_state}; + auto int_idx = svdat_direct.getInternalIndices({0}); + auto ext_idx = svdat_direct.getExternalIndices({0}); + + svdat_direct.sv.applyRY(int_idx, ext_idx, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_direct.cdata, expected_results[index], + 1e-5)); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_dispatch{num_qubits, init_state}; + svdat_dispatch.sv.applyOperation("RY", {0}, false, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.cdata, + expected_results[index], 1e-5)); + } } } - SECTION("Apply using dispatcher") { - for (size_t index = 0; index < num_qubits; index++) { - SVData svdat_dispatch{num_qubits}; - svdat_dispatch.sv.applyOperation("RY", {index}, false, - {angles[index]}); - CHECK(isApproxEqual(svdat_dispatch.cdata, expected_results[index])); + SECTION("adj = true") { + SECTION("Apply directly") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_direct{num_qubits, init_state}; + auto int_idx = svdat_direct.getInternalIndices({0}); + auto ext_idx = svdat_direct.getExternalIndices({0}); + + svdat_direct.sv.applyRY(int_idx, ext_idx, true, + {angles[index]}); + CHECK(isApproxEqual(svdat_direct.cdata, + expected_results_adj[index], 1e-5)); + } + } + SECTION("Apply using dispatcher") { + for (size_t index = 0; index < angles.size(); index++) { + SVData svdat_dispatch{num_qubits, init_state}; + svdat_dispatch.sv.applyOperation("RY", {0}, true, + {angles[index]}); + CHECK(isApproxEqual(svdat_dispatch.cdata, + expected_results_adj[index], 1e-5)); + } } } } diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 773f78ddb7..85dc6970eb 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -20,6 +20,7 @@ import pennylane as qml from pennylane import numpy as np from pennylane import QNode, qnode +from scipy.stats import unitary_group I, X, Y, Z = np.eye(2), qml.PauliX.matrix, qml.PauliY.matrix, qml.PauliZ.matrix @@ -61,6 +62,8 @@ def Rz(theta): class TestAdjointJacobian: """Tests for the adjoint_jacobian method""" + from pennylane_lightning import LightningQubit as lq + @pytest.fixture def dev(self): return qml.device("lightning.qubit", wires=2) @@ -89,6 +92,7 @@ def test_finite_shots_warns(self): ): dev.adjoint_jacobian(tape) + @pytest.mark.skipif(not lq._CPP_BINARY_AVAILABLE, reason="Lightning binary required") def test_unsupported_op(self, dev): """Test if a QuantumFunctionError is raised for an unsupported operation, i.e., multi-parameter operations that are not qml.Rot""" @@ -97,7 +101,44 @@ def test_unsupported_op(self, dev): qml.CRot(0.1, 0.2, 0.3, wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with pytest.raises(qml.QuantumFunctionError, match="The CRot operation is not supported using the"): + with pytest.raises( + qml.QuantumFunctionError, match="The CRot operation is not supported using the" + ): + dev.adjoint_jacobian(tape) + + with qml.tape.JacobianTape() as tape: + qml.SingleExcitation(0.1, wires=[0, 1]) + qml.expval(qml.PauliZ(0)) + + with pytest.raises( + qml.QuantumFunctionError, + match="The SingleExcitation operation is not supported using the", + ): + dev.adjoint_jacobian(tape) + + @pytest.mark.skipif(not lq._CPP_BINARY_AVAILABLE, reason="Lightning binary required") + def test_proj_unsupported(self, dev): + """Test if a QuantumFunctionError is raised for a Projector observable""" + with qml.tape.JacobianTape() as tape: + qml.CRX(0.1, wires=[0, 1]) + qml.expval(qml.Projector([0, 1], wires=[0, 1])) + + with pytest.raises( + qml.QuantumFunctionError, match="differentiation method does not support the Projector" + ): + dev.adjoint_jacobian(tape) + + @pytest.mark.skipif(not lq._CPP_BINARY_AVAILABLE, reason="Lightning binary required") + def test_unsupported_hermitian_expectation(self, dev): + obs = np.array([[1, 0], [0, -1]], dtype=np.complex128, requires_grad=False) + + with qml.tape.JacobianTape() as tape: + qml.RY(0.1, wires=(0,)) + qml.expval(qml.Hermitian(obs, wires=(0,))) + + with pytest.raises( + qml.QuantumFunctionError, match="Lightning adjoint differentiation method does not" + ): dev.adjoint_jacobian(tape) @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) @@ -113,7 +154,7 @@ def test_pauli_rotation_gradient(self, G, theta, tol, dev): tape.trainable_params = {1} calculated_val = dev.adjoint_jacobian(tape) - + # compare to finite differences numeric_val = tape.jacobian(dev, method="numeric") assert np.allclose(calculated_val, numeric_val, atol=tol, rtol=0) @@ -218,7 +259,6 @@ def test_gradients(self, op, obs, tol, dev): grad_F = tape.jacobian(dev, method="numeric") grad_D = dev.adjoint_jacobian(tape) - print(grad_D, grad_F) assert np.allclose(grad_D, grad_F, atol=tol, rtol=0) @@ -503,3 +543,118 @@ def f(params1, params2): grad_fd = jax.grad(qnode_fd)(params1, params2) assert np.allclose(grad_adjoint, grad_fd) + + +def circuit_ansatz(params, wires): + """Circuit ansatz containing all the parametrized gates""" + qml.QubitStateVector(unitary_group.rvs(2 ** 4, random_state=0)[0], wires=wires) + qml.RX(params[0], wires=wires[0]) + qml.RY(params[1], wires=wires[1]) + qml.RX(params[2], wires=wires[2]).inv() + qml.RZ(params[0], wires=wires[3]) + qml.CRX(params[3], wires=[wires[3], wires[0]]) + qml.PhaseShift(params[4], wires=wires[2]) + qml.CRY(params[5], wires=[wires[2], wires[1]]) + qml.CRZ(params[5], wires=[wires[0], wires[3]]).inv() + qml.PhaseShift(params[6], wires=wires[0]).inv() + qml.Rot(params[6], params[7], params[8], wires=wires[0]) + # # qml.Rot(params[8], params[8], params[9], wires=wires[1]).inv() + # # qml.MultiRZ(params[11], wires=[wires[0], wires[1]]) + # # qml.PauliRot(params[12], "XXYZ", wires=[wires[0], wires[1], wires[2], wires[3]]) + qml.CPhase(params[12], wires=[wires[3], wires[2]]) + # # qml.IsingXX(params[13], wires=[wires[1], wires[0]]) + # # qml.IsingYY(params[14], wires=[wires[3], wires[2]]) + # # qml.IsingZZ(params[14], wires=[wires[2], wires[1]]) + qml.U1(params[15], wires=wires[0]) + qml.U2(params[16], params[17], wires=wires[0]) + qml.U3(params[18], params[19], params[20], wires=wires[1]) + # # qml.CRot(params[21], params[22], params[23], wires=[wires[1], wires[2]]).inv() # expected tofail + # # qml.SingleExcitation(params[24], wires=[wires[2], wires[0]]) + # # qml.DoubleExcitation(params[25], wires=[wires[2], wires[0], wires[1], wires[3]]) + # # qml.SingleExcitationPlus(params[26], wires=[wires[0], wires[2]]) + # # qml.SingleExcitationMinus(params[27], wires=[wires[0], wires[2]]) + # # qml.DoubleExcitationPlus(params[27], wires=[wires[2], wires[0], wires[1], wires[3]]) + # # qml.DoubleExcitationMinus(params[27], wires=[wires[2], wires[0], wires[1], wires[3]]) + qml.RX(params[28], wires=wires[0]) + qml.RX(params[29], wires=wires[1]) + + +@pytest.mark.parametrize( + "returns", + [ + qml.PauliZ(0), + qml.PauliX(2), + qml.PauliZ(0) @ qml.PauliY(3), + qml.Hadamard(2), + qml.Hadamard(3) @ qml.PauliZ(2), + # qml.Projector([0, 1], wires=[0, 2]) @ qml.Hadamard(3) + # qml.Projector([0, 0], wires=[2, 0]) + qml.PauliX(0) @ qml.PauliY(3), + qml.PauliY(0) @ qml.PauliY(2) @ qml.PauliY(3), + # qml.Hermitian(np.kron(qml.PauliY.matrix, qml.PauliZ.matrix), wires=[3, 2]), + # qml.Hermitian(np.array([[0,1],[1,0]], requires_grad=False), wires=0), + # qml.Hermitian(np.array([[0,1],[1,0]], requires_grad=False), wires=0) @ qml.PauliZ(2), + ], +) +def test_integration(returns): + """Integration tests that compare to default.qubit for a large circuit containing parametrized + operations""" + dev_def = qml.device("default.qubit", wires=range(4)) + dev_lightning = qml.device("lightning.qubit", wires=range(4)) + + def circuit(params): + circuit_ansatz(params, wires=range(4)) + return qml.expval(returns), qml.expval(qml.PauliY(1)) + + n_params = 30 + params = np.linspace(0, 10, n_params) + + qnode_def = qml.QNode(circuit, dev_def) + qnode_lightning = qml.QNode(circuit, dev_lightning, diff_method="adjoint") + + j_def = qml.jacobian(qnode_def)(params) + j_lightning = qml.jacobian(qnode_lightning)(params) + + assert np.allclose(j_def, j_lightning) + + +custom_wires = ["alice", 3.14, -1, 0] + + +@pytest.mark.parametrize( + "returns", + [ + qml.PauliZ(custom_wires[0]), + qml.PauliX(custom_wires[2]), + qml.PauliZ(custom_wires[0]) @ qml.PauliY(custom_wires[3]), + qml.Hadamard(custom_wires[2]), + qml.Hadamard(custom_wires[3]) @ qml.PauliZ(custom_wires[2]), + # qml.Projector([0, 1], wires=[custom_wires[0], custom_wires[2]]) @ qml.Hadamard(custom_wires[3]) + # qml.Projector([0, 0], wires=[custom_wires[2], custom_wires[0]]) + qml.PauliX(custom_wires[0]) @ qml.PauliY(custom_wires[3]), + qml.PauliY(custom_wires[0]) @ qml.PauliY(custom_wires[2]) @ qml.PauliY(custom_wires[3]), + # qml.Hermitian(np.array([[0,1],[1,0]], requires_grad=False), wires=custom_wires[0]), + # qml.Hermitian(np.kron(qml.PauliY.matrix, qml.PauliZ.matrix), wires=[custom_wires[3], custom_wires[2]]), + # qml.Hermitian(np.array([[0,1],[1,0]], requires_grad=False), wires=custom_wires[0]) @ qml.PauliZ(custom_wires[2]), + ], +) +def test_integration_custom_wires(returns): + """Integration tests that compare to default.qubit for a large circuit containing parametrized + operations and when using custom wire labels""" + dev_def = qml.device("default.qubit", wires=custom_wires) + dev_lightning = qml.device("lightning.qubit", wires=custom_wires) + + def circuit(params): + circuit_ansatz(params, wires=custom_wires) + return qml.expval(returns), qml.expval(qml.PauliY(custom_wires[1])) + + n_params = 30 + params = np.linspace(0, 10, n_params) + + qnode_def = qml.QNode(circuit, dev_def) + qnode_lightning = qml.QNode(circuit, dev_lightning, diff_method="adjoint") + + j_def = qml.jacobian(qnode_def)(params) + j_lightning = qml.jacobian(qnode_lightning)(params) + + assert np.allclose(j_def, j_lightning) diff --git a/tests/test_gates.py b/tests/test_gates.py index 0227085a92..2d4f080b97 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -35,7 +35,12 @@ def test_gate_unitary_correct(op, op_name): if op_name in ("BasisState", "QubitStateVector"): pytest.skip("Skipping operation because it is a state preparation") - if op_name in ("ControlledQubitUnitary", "QubitUnitary", "MultiControlledX", "DiagonalQubitUnitary"): + if op_name in ( + "ControlledQubitUnitary", + "QubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + ): pytest.skip("Skipping operation.") # These are tested in the device test-suite wires = op.num_wires @@ -71,7 +76,12 @@ def test_inverse_unitary_correct(op, op_name): if op_name in ("BasisState", "QubitStateVector"): pytest.skip("Skipping operation because it is a state preparation") - if op_name in ("ControlledQubitUnitary", "QubitUnitary", "MultiControlledX", "DiagonalQubitUnitary"): + if op_name in ( + "ControlledQubitUnitary", + "QubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + ): pytest.skip("Skipping operation.") # These are tested in the device test-suite wires = op.num_wires @@ -129,6 +139,7 @@ def output(input): ] ) + def test_arbitrary_unitary_correct(): """Test if lightning.qubit correctly applies an arbitrary unitary by reconstructing its matrix""" diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 931430ff94..aa245ff614 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -17,8 +17,7 @@ import pennylane as qml from pennylane import numpy as np -from pennylane_lightning._serialize import (_serialize_obs, _serialize_ops, - _obs_has_kernel) +from pennylane_lightning._serialize import _serialize_obs, _serialize_ops, _obs_has_kernel import pytest @@ -131,8 +130,8 @@ def test_tensor_non_tensor_return(self, monkeypatch): s = mock_obs.call_args_list s_expected = [ - (['PauliZ', 'PauliX'], [], [[0], [1]]), - (['Hadamard'], [], [[1]]), + (["PauliZ", "PauliX"], [], [[0], [1]]), + (["Hadamard"], [], [[1]]), ] [ObsStructC128(*s_expected) for s_expected in s_expected] @@ -170,7 +169,11 @@ def test_hermitian_tensor_return(self, monkeypatch): _serialize_obs(tape, self.wires_dict) s = mock_obs.call_args[0] - s_expected = (["Hermitian", "Hermitian"], [np.eye(4).ravel(), np.eye(2).ravel()], [[0, 1], [2]]) + s_expected = ( + ["Hermitian", "Hermitian"], + [np.eye(4).ravel(), np.eye(2).ravel()], + [[0, 1], [2]], + ) ObsStructC128(*s_expected) assert s[0] == s_expected[0] @@ -200,10 +203,10 @@ def test_mixed_tensor_return(self, monkeypatch): def test_integration(self, monkeypatch): """Test for a comprehensive range of returns""" wires_dict = {"a": 0, 1: 1, "b": 2, -1: 3, 3.141: 4, "five": 5, 6: 6, 77: 7, 9: 8} - I = np.eye(2) - X = qml.PauliX.matrix - Y = qml.PauliY.matrix - Z = qml.PauliZ.matrix + I = np.eye(2).astype(np.complex128) + X = qml.PauliX.matrix.astype(np.complex128) + Y = qml.PauliY.matrix.astype(np.complex128) + Z = qml.PauliZ.matrix.astype(np.complex128) mock_obs = mock.MagicMock() @@ -211,7 +214,7 @@ def test_integration(self, monkeypatch): qml.expval(qml.PauliZ("a") @ qml.PauliX("b")) qml.expval(qml.Hermitian(I, wires=1)) qml.expval(qml.PauliZ(-1) @ qml.Hermitian(X, wires=3.141) @ qml.Hadamard("five")) - qml.expval(qml.Projector([1, 1], wires=[6, 77]) @ qml.Hermitian(Y, wires=9)) + # qml.expval(qml.Projector([1, 1], wires=[6, 77]) @ qml.Hermitian(Y, wires=9)) qml.expval(qml.Hermitian(Z, wires="a") @ qml.Identity(1)) with monkeypatch.context() as m: @@ -222,15 +225,17 @@ def test_integration(self, monkeypatch): s_expected = [ (["PauliZ", "PauliX"], [], [[0], [2]]), - (['Hermitian'], [I.ravel()], [[1]]), - (["PauliZ", "Hermitian", "Hadamard"], [X.ravel()], [[3], [4], [5]]), - (["Projector", "Hermitian"], [Y.ravel()], [[6, 7], [8]]), - (["Hermitian", "Identity"], [Z.ravel()], [[0], [1]]), + (["Hermitian"], [I.ravel()], [[1]]), + (["PauliZ", "Hermitian", "Hadamard"], [[], X.ravel(), []], [[3], [4], [5]]), + # (["Projector", "Hermitian"], [[],Y.ravel().astype(np.complex128)], [[6, 7], [8]]), + (["Hermitian", "Identity"], [Z.ravel(), []], [[0], [1]]), ] [ObsStructC128(*s_expected) for s_expected in s_expected] assert all(s1[0][0] == s2[0] for s1, s2 in zip(s, s_expected)) - assert all(np.allclose(s1[0][1], s2[1]) for s1, s2 in zip(s, s_expected)) + for s1, s2 in zip(s, s_expected): + for v1, v2 in zip(s1[0][1], s2[1]): + assert np.allclose(v1, v2) assert all(s1[0][2] == s2[2] for s1, s2 in zip(s, s_expected)) @@ -247,13 +252,16 @@ def test_basic_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = (( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - ), False) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ), + False, + ) assert s == s_expected def test_skips_prep_circuit(self): @@ -267,13 +275,16 @@ def test_skips_prep_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = (( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - ), True) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ), + True, + ) assert s == s_expected def test_inverse_circuit(self): @@ -284,13 +295,16 @@ def test_inverse_circuit(self): qml.CNOT(wires=[0, 1]) s = _serialize_ops(tape, self.wires_dict) - s_expected = (( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, True, False], - [[], [], []], - ), False) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, True, False], + [[], [], []], + ), + False, + ) assert s == s_expected def test_unsupported_kernel_circuit(self): @@ -303,18 +317,21 @@ def test_unsupported_kernel_circuit(self): qml.RZ(0.2, wires=2) s = _serialize_ops(tape, self.wires_dict) - s_expected = (( - ["SingleExcitationPlus", "SingleExcitationMinus", "CNOT", "RZ"], - [[], [], [], [0.2]], - [[0, 1], [1, 2], [0, 1], [2]], - [False, False, False, False], - [ - qml.SingleExcitationPlus(0.4, wires=[0, 1]).matrix, - qml.SingleExcitationMinus(0.5, wires=[1, 2]).inv().matrix, - [], - [], - ], - ), False) + s_expected = ( + ( + ["SingleExcitationPlus", "SingleExcitationMinus", "CNOT", "RZ"], + [[], [], [], [0.2]], + [[0, 1], [1, 2], [0, 1], [2]], + [False, False, False, False], + [ + qml.SingleExcitationPlus(0.4, wires=[0, 1]).matrix, + qml.SingleExcitationMinus(0.5, wires=[1, 2]).inv().matrix, + [], + [], + ], + ), + False, + ) assert s[0][0] == s_expected[0][0] assert s[0][1] == s_expected[0][1] assert s[0][2] == s_expected[0][2] @@ -331,13 +348,16 @@ def test_custom_wires_circuit(self): qml.CNOT(wires=["a", 3.2]) s = _serialize_ops(tape, wires_dict) - s_expected = (( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - ), False) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + ), + False, + ) assert s == s_expected def test_integration(self): @@ -347,24 +367,27 @@ def test_integration(self): qml.RY(0.6, wires=1).inv().inv() qml.CNOT(wires=[0, 1]) qml.QubitUnitary(np.eye(4), wires=[0, 1]) - qml.QFT(wires=[0, 1, 2]).inv() + qml.templates.QFT(wires=[0, 1, 2]).inv() qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) s = _serialize_ops(tape, self.wires_dict) - s_expected = (( - ["RX", "RY", "CNOT", "QubitUnitary", "QFT", "DoubleExcitation"], - [[0.4], [0.6], [], [], [], []], - [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0]], - [False, False, False, False, False, False], - [ - [], - [], - [], - qml.QubitUnitary(np.eye(4, dtype=np.complex128), wires=[0, 1]).matrix, - qml.QFT(wires=[0, 1, 2]).inv().matrix, - qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]).matrix, - ], - ), False) + s_expected = ( + ( + ["RX", "RY", "CNOT", "QubitUnitary", "QFT", "DoubleExcitation"], + [[0.4], [0.6], [], [], [], []], + [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0]], + [False, False, False, False, False, False], + [ + [], + [], + [], + qml.QubitUnitary(np.eye(4, dtype=np.complex128), wires=[0, 1]).matrix, + qml.templates.QFT(wires=[0, 1, 2]).inv().matrix, + qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]).matrix, + ], + ), + False, + ) assert s[0][0] == s_expected[0][0] assert s[0][1] == s_expected[0][1] assert s[0][2] == s_expected[0][2] From e0c4006083f66ee3cd9843511ff983e9870fb1ea Mon Sep 17 00:00:00 2001 From: Lee James O'Riordan Date: Mon, 13 Sep 2021 08:50:20 +0100 Subject: [PATCH 63/72] Update pennylane_lightning/src/algorithms/AdjointDiff.hpp Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane_lightning/src/algorithms/AdjointDiff.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 718399a02d..6fe03a7724 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -1,3 +1,16 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #pragma once #include From 2ae4d7e086a31eab78488e926b7a4f6d080e8d71 Mon Sep 17 00:00:00 2001 From: Lee James O'Riordan Date: Mon, 13 Sep 2021 08:50:29 +0100 Subject: [PATCH 64/72] Update pennylane_lightning/src/simulator/StateVectorManaged.hpp Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- .../src/simulator/StateVectorManaged.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pennylane_lightning/src/simulator/StateVectorManaged.hpp b/pennylane_lightning/src/simulator/StateVectorManaged.hpp index 0112c4ef6b..784d4a5b1b 100644 --- a/pennylane_lightning/src/simulator/StateVectorManaged.hpp +++ b/pennylane_lightning/src/simulator/StateVectorManaged.hpp @@ -1,3 +1,13 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #pragma once #include "StateVector.hpp" From cf5ac07f3b8a5d8a370cefb53a8b0a1123a0765b Mon Sep 17 00:00:00 2001 From: Lee James O'Riordan Date: Mon, 13 Sep 2021 08:50:36 +0100 Subject: [PATCH 65/72] Update pennylane_lightning/src/util/Error.hpp Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane_lightning/src/util/Error.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pennylane_lightning/src/util/Error.hpp b/pennylane_lightning/src/util/Error.hpp index 419f2eede9..b763f05ce4 100644 --- a/pennylane_lightning/src/util/Error.hpp +++ b/pennylane_lightning/src/util/Error.hpp @@ -1,3 +1,13 @@ +// Copyright 2021 Xanadu Quantum Technologies Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #pragma once #include From 9003139d13d43f013cc719f1a7f15116a4a01543 Mon Sep 17 00:00:00 2001 From: Lee James O'Riordan Date: Mon, 13 Sep 2021 08:50:57 +0100 Subject: [PATCH 66/72] Update pennylane_lightning/src/algorithms/AdjointDiff.hpp Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane_lightning/src/algorithms/AdjointDiff.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 6fe03a7724..314ff0b804 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -136,7 +136,7 @@ namespace Pennylane { namespace Algorithms { /** - * @brief Utility struct for a observable operations used by AdjointJacobian + * @brief Utility struct for observable operations used by AdjointJacobian * class. * */ From 7c51f2c8f738b067b7e29cdc3df132d7dc64f90c Mon Sep 17 00:00:00 2001 From: Lee James O'Riordan Date: Mon, 13 Sep 2021 08:53:01 +0100 Subject: [PATCH 67/72] Update pennylane_lightning/src/algorithms/AdjointDiff.hpp Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane_lightning/src/algorithms/AdjointDiff.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/src/algorithms/AdjointDiff.hpp b/pennylane_lightning/src/algorithms/AdjointDiff.hpp index 314ff0b804..14907a6e4b 100644 --- a/pennylane_lightning/src/algorithms/AdjointDiff.hpp +++ b/pennylane_lightning/src/algorithms/AdjointDiff.hpp @@ -153,7 +153,7 @@ template class ObsDatum { * @param obs_params Parameters for a given obserable operation ({} if * optional). * @param ops_wires Wires upon which to apply operation. Each observable - * operation will eb a separate nested list. + * operation will be a separate nested list. */ ObsDatum(const std::vector &obs_name, const std::vector &obs_params, From e6f99d9dafaac709bc7c708a7ab4dfc3ba7049f2 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 13 Sep 2021 08:57:27 +0100 Subject: [PATCH 68/72] Fix typos and remove comments --- pennylane_lightning/lightning_qubit.py | 3 --- .../src/simulator/StateVectorManaged.hpp | 16 ---------------- pennylane_lightning/src/tests/Test_AdjDiff.cpp | 1 - 3 files changed, 20 deletions(-) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index f399f56d56..19735a2429 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -160,9 +160,6 @@ def apply_lightning(self, state, operations): return np.reshape(state_vector, state.shape) def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): - if not self._CPP_BINARY_AVAILABLE: - return super().adjoint_jacobian(tape, starting_state, use_device_state) - if self.shots is not None: warn( "Requested adjoint differentiation to be computed with finite shots." diff --git a/pennylane_lightning/src/simulator/StateVectorManaged.hpp b/pennylane_lightning/src/simulator/StateVectorManaged.hpp index 784d4a5b1b..fec8b806c7 100644 --- a/pennylane_lightning/src/simulator/StateVectorManaged.hpp +++ b/pennylane_lightning/src/simulator/StateVectorManaged.hpp @@ -93,20 +93,4 @@ class StateVectorManaged : public StateVector { } }; -/* -template -inline std::ostream& operator<<(std::ostream& out, const StateVectorManaged& -sv){ const auto length = sv.getLength(); const auto qubits = sv.getNumQubits(); - const auto data = sv.getData(); - out << "num_qubits=" << qubits << std::endl; - out << "data=["; - out << data[0]; - for (size_t i = 1; i < length - 1; i++) { - out << "," << data[i]; - } - out << "," << data[length - 1] << "]"; - - return out; -}*/ - } // namespace Pennylane \ No newline at end of file diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index 8e26593185..5aae94e15a 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -37,7 +37,6 @@ TEMPLATE_TEST_CASE("AdjointJacobian::AdjointJacobian", "[AdjointJacobian]", TEST_CASE("AdjointJacobian::adjointJacobian", "[AdjointJacobian]") { AdjointJacobian adj; - // std::vector param{1, -2, 1.623, -0.051, 0}; std::vector param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; SECTION("RX gradient") { From 189a837b4698ab271ac46cb24668c6c43edebb33 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 13 Sep 2021 09:03:00 +0100 Subject: [PATCH 69/72] Update changelog --- .github/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index cf987432d5..aa1d6c4bf1 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -9,6 +9,17 @@ ### Improvements +* A new state-vector class `StateVectorManaged` was added, enabling memory use to be bound to + statevector lifetime. + [(136)](https://github.com/PennyLaneAI/pennylane-lightning/pull/136) + +* The repository now has a well-defined component hierarchy, allowing each indepedent unit to be + compiled and linked separately. + [(136)](https://github.com/PennyLaneAI/pennylane-lightning/pull/136) + +* PennyLane-Lightning now has support for the adjoint Jacobian method of http://arxiv.org/abs/2009.02823. + [(136)](https://github.com/PennyLaneAI/pennylane-lightning/pull/136) + * PennyLane-Lightning can now be installed without compiling its C++ binaries and will fall back to using the `default.qubit` implementation. Skipping compilation is achieved by setting the `SKIP_COMPILATION` environment variable, e.g., Linux/MacOS: `export SKIP_COMPILATION=True`, @@ -32,6 +43,8 @@ ### Bug fixes +* An indexing error in the CRY gate is fixed. [(#136)](https://github.com/PennyLaneAI/pennylane-lightning/pull/136) + * Column-major data in numpy is now correctly converted to row-major upon pass to the C++ layer. [(#126)](https://github.com/PennyLaneAI/pennylane-lightning/pull/126) From 0441afd8c5b8df53b5e00b8a9e87efe007649992 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 13 Sep 2021 09:16:46 +0100 Subject: [PATCH 70/72] Fix codecov reduction --- tests/test_adjoint_jacobian.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 85dc6970eb..a2cba33b1e 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -128,6 +128,15 @@ def test_proj_unsupported(self, dev): ): dev.adjoint_jacobian(tape) + with qml.tape.JacobianTape() as tape: + qml.CRX(0.1, wires=[0, 1]) + qml.expval(qml.Projector([0], wires=[0]) @ qml.PauliZ(0)) + + with pytest.raises( + qml.QuantumFunctionError, match="differentiation method does not support the Projector" + ): + dev.adjoint_jacobian(tape) + @pytest.mark.skipif(not lq._CPP_BINARY_AVAILABLE, reason="Lightning binary required") def test_unsupported_hermitian_expectation(self, dev): obs = np.array([[1, 0], [0, -1]], dtype=np.complex128, requires_grad=False) @@ -141,6 +150,15 @@ def test_unsupported_hermitian_expectation(self, dev): ): dev.adjoint_jacobian(tape) + with qml.tape.JacobianTape() as tape: + qml.RY(0.1, wires=(0,)) + qml.expval(qml.Hermitian(obs, wires=(0,)) @ qml.PauliZ(wires=1)) + + with pytest.raises( + qml.QuantumFunctionError, match="Lightning adjoint differentiation method does not" + ): + dev.adjoint_jacobian(tape) + @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) @pytest.mark.parametrize("G", [qml.RX, qml.RY, qml.RZ]) def test_pauli_rotation_gradient(self, G, theta, tol, dev): From 04ee64690429c72452f712c48fcb47cecfcadf04 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 13 Sep 2021 10:05:13 +0100 Subject: [PATCH 71/72] Trigger build From e1d569f2fcf4ab9cbdacd28ebbd62a632767c804 Mon Sep 17 00:00:00 2001 From: "Lee J. O'Riordan" Date: Mon, 13 Sep 2021 10:11:13 +0100 Subject: [PATCH 72/72] CodeCov fix