diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index d607381fde..02116881ad 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -9,6 +9,9 @@ * Introduces requirements-dev.txt and improves dockerfile. [(#330)](https://github.com/PennyLaneAI/pennylane-lightning/pull/330) +* Support `expval` for a Hamiltonian. +[(#333)](https://github.com/PennyLaneAI/pennylane-lightning/pull/333) + ### Documentation ### Bug fixes diff --git a/pennylane_lightning/_serialize.py b/pennylane_lightning/_serialize.py index 309484ad3d..e0e546192a 100644 --- a/pennylane_lightning/_serialize.py +++ b/pennylane_lightning/_serialize.py @@ -27,6 +27,7 @@ from pennylane.grouping import is_pauli_word from pennylane.operation import Observable, Tensor from pennylane.tape import QuantumTape +from pennylane.math import unwrap # Remove after the next release of PL # Add from pennylane import matrix @@ -111,7 +112,7 @@ def _serialize_hamiltonian(ob, wires_map: dict, use_csingle: bool): rtype = np.float64 hamiltonian_obs = HamiltonianC128 - coeffs = np.array(ob.coeffs).astype(rtype) + coeffs = np.array(unwrap(ob.coeffs)).astype(rtype) terms = [_serialize_ob(t, wires_map, use_csingle) for t in ob.ops] return hamiltonian_obs(coeffs, terms) diff --git a/pennylane_lightning/_version.py b/pennylane_lightning/_version.py index 35bb0bee32..41ac2b39c3 100644 --- a/pennylane_lightning/_version.py +++ b/pennylane_lightning/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.26.0-dev3" +__version__ = "0.26.0-dev4" diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index c865756569..ce6159a279 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -57,7 +57,7 @@ best_alignment, ) - from ._serialize import _serialize_observables, _serialize_ops + from ._serialize import _serialize_ob, _serialize_observables, _serialize_ops CPP_BINARY_AVAILABLE = True except ModuleNotFoundError: @@ -621,8 +621,6 @@ def expval(self, observable, shot_range=None, bin_size=None): if isinstance(observable.name, List) or observable.name in [ "Identity", "Projector", - "Hermitian", - "Hamiltonian", ]: return super().expval(observable, shot_range=shot_range, bin_size=bin_size) @@ -648,6 +646,10 @@ def expval(self, observable, shot_range=None, bin_size=None): ) return super().expval(observable, shot_range=shot_range, bin_size=bin_size) + if observable.name in ["Hamiltonian", "Hermitian"]: + ob_serialized = _serialize_ob(observable, self.wire_map, use_csingle=self.use_csingle) + return M.expval(ob_serialized) + # translate to wire labels used by device observable_wires = self.map_wires(observable.wires) diff --git a/pennylane_lightning/src/algorithms/AlgUtil.hpp b/pennylane_lightning/src/algorithms/AlgUtil.hpp index 8e1f50a064..4ea92712e6 100644 --- a/pennylane_lightning/src/algorithms/AlgUtil.hpp +++ b/pennylane_lightning/src/algorithms/AlgUtil.hpp @@ -66,7 +66,7 @@ inline void applyOperationAdj(StateVectorManagedCPU<T> &state, */ template <typename T> inline void applyObservable(StateVectorManagedCPU<T> &state, - Observable<T> &observable) { + Simulators::Observable<T> &observable) { observable.applyInPlace(state); } @@ -79,10 +79,11 @@ inline void applyObservable(StateVectorManagedCPU<T> &state, * @param observables Vector of observables to apply to each statevector. */ template <typename T> -inline void applyObservables( - std::vector<StateVectorManagedCPU<T>> &states, - const StateVectorManagedCPU<T> &reference_state, - const std::vector<std::shared_ptr<Observable<T>>> &observables) { +inline void +applyObservables(std::vector<StateVectorManagedCPU<T>> &states, + const StateVectorManagedCPU<T> &reference_state, + const std::vector<std::shared_ptr<Simulators::Observable<T>>> + &observables) { std::exception_ptr ex = nullptr; size_t num_observables = observables.size(); diff --git a/pennylane_lightning/src/algorithms/CMakeLists.txt b/pennylane_lightning/src/algorithms/CMakeLists.txt index 7b7bfca8d1..50e315a4fe 100644 --- a/pennylane_lightning/src/algorithms/CMakeLists.txt +++ b/pennylane_lightning/src/algorithms/CMakeLists.txt @@ -1,6 +1,6 @@ project(lightning_algorithms LANGUAGES CXX) -set(ALGORITHM_FILES AdjointDiff.cpp Observables.cpp JacobianTape.cpp StateVecAdjDiff.cpp CACHE INTERNAL "" FORCE) +set(ALGORITHM_FILES AdjointDiff.cpp JacobianTape.cpp StateVecAdjDiff.cpp CACHE INTERNAL "" FORCE) add_library(lightning_algorithms STATIC ${ALGORITHM_FILES}) target_link_libraries(lightning_algorithms PRIVATE lightning_compile_options diff --git a/pennylane_lightning/src/algorithms/JacobianTape.hpp b/pennylane_lightning/src/algorithms/JacobianTape.hpp index 1c94696a1b..848db63950 100644 --- a/pennylane_lightning/src/algorithms/JacobianTape.hpp +++ b/pennylane_lightning/src/algorithms/JacobianTape.hpp @@ -206,7 +206,7 @@ template <class T> class JacobianData { * @var observables * Observables for which to calculate Jacobian. */ - const std::vector<std::shared_ptr<Observable<T>>> observables; + const std::vector<std::shared_ptr<Simulators::Observable<T>>> observables; /** * @var operations @@ -238,7 +238,7 @@ template <class T> class JacobianData { * @endrst */ JacobianData(size_t num_params, size_t num_elem, std::complex<T> *ps, - std::vector<std::shared_ptr<Observable<T>>> obs, + std::vector<std::shared_ptr<Simulators::Observable<T>>> obs, OpsData<T> ops, std::vector<size_t> trainP) : num_parameters(num_params), num_elements(num_elem), psi(ps), observables(std::move(obs)), operations(std::move(ops)), @@ -278,7 +278,7 @@ template <class T> class JacobianData { * @return List of observables */ [[nodiscard]] auto getObservables() const - -> const std::vector<std::shared_ptr<Observable<T>>> & { + -> const std::vector<std::shared_ptr<Simulators::Observable<T>>> & { return observables; } diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index 9ec619c7b4..92caecefe9 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -18,6 +18,7 @@ #include "Bindings.hpp" #include "GateUtil.hpp" +#include "Measures.hpp" #include "StateVecAdjDiff.hpp" #include "StateVectorManagedCPU.hpp" @@ -27,6 +28,7 @@ namespace { using namespace Pennylane; using namespace Pennylane::Util; +using namespace Pennylane::Simulators; using namespace Pennylane::Algorithms; using namespace Pennylane::Gates; @@ -95,6 +97,13 @@ void lightning_class_bindings(py::module_ &m) { const std::string &, const std::vector<size_t> &)>( &Measures<PrecisionT>::expval), "Expected value of an operation by name.") + .def( + "expval", + [](Measures<PrecisionT> &M, + const std::shared_ptr<Observable<PrecisionT>> &ob) { + return M.expval(*ob); + }, + "Expected value of an operation object.") .def( "expval", [](Measures<PrecisionT> &M, const np_arr_sparse_ind row_map, @@ -413,7 +422,7 @@ PYBIND11_MODULE(lightning_qubit_ops, // NOLINT: No control over Pybind internals /* Add compile info */ m.def("compile_info", &getCompileInfo, "Compiled binary information."); - /* Add compile info */ + /* Add runtime info */ m.def("runtime_info", &getRuntimeInfo, "Runtime information."); /* Add Kokkos and Kokkos Kernels info */ diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index 50da06f71c..e1ea946e93 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -1,23 +1,13 @@ project(lightning_simulator) -set(SIMULATOR_FILES StateVectorRawCPU.cpp StateVectorManagedCPU.cpp Measures.cpp CACHE INTERNAL "" FORCE) +set(SIMULATOR_FILES StateVectorRawCPU.cpp Observables.cpp StateVectorManagedCPU.cpp Measures.cpp CACHE INTERNAL "" FORCE) add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) if (UNIX AND (${CMAKE_SYSTEM_PROCESSOR} MATCHES "(AMD64)|(X64)|(x64)|(x86_64)")) - add_library(lightning_simulator_assign_kernels_x64 STATIC KernelMap_X64.cpp AssignKernelMap_AVX2.cpp AssignKernelMap_AVX512.cpp AssignKernelMap_Default.cpp) - target_link_libraries(lightning_simulator_assign_kernels_x64 PRIVATE lightning_compile_options - lightning_external_libs - lightning_gates - lightning_utils) - target_link_libraries(lightning_simulator PRIVATE lightning_simulator_assign_kernels_x64) + target_sources(lightning_simulator PRIVATE KernelMap_X64.cpp AssignKernelMap_AVX2.cpp AssignKernelMap_AVX512.cpp AssignKernelMap_Default.cpp) else() - add_library(lightning_simulator_assign_kernels_default STATIC KernelMap_Default.cpp AssignKernelMap_Default.cpp) - target_link_libraries(lightning_simulator_assign_kernels_default PRIVATE lightning_compile_options - lightning_external_libs - lightning_gates - lightning_utils) - target_link_libraries(lightning_simulator PRIVATE lightning_simulator_assign_kernels_default) + target_sources(lightning_simulator PRIVATE KernelMap_Default.cpp AssignKernelMap_Default.cpp) endif() target_include_directories(lightning_simulator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/pennylane_lightning/src/simulator/Measures.cpp b/pennylane_lightning/src/simulator/Measures.cpp index c817639950..513b898f51 100644 --- a/pennylane_lightning/src/simulator/Measures.cpp +++ b/pennylane_lightning/src/simulator/Measures.cpp @@ -15,6 +15,7 @@ #include "Measures.hpp" // explicit instantiation -template class Pennylane::Measures<float, Pennylane::StateVectorRawCPU<float>>; -template class Pennylane::Measures<double, - Pennylane::StateVectorRawCPU<double>>; +template class Pennylane::Simulators::Measures< + float, Pennylane::StateVectorRawCPU<float>>; +template class Pennylane::Simulators::Measures< + double, Pennylane::StateVectorRawCPU<double>>; diff --git a/pennylane_lightning/src/simulator/Measures.hpp b/pennylane_lightning/src/simulator/Measures.hpp index e0d2d53f0c..d053e4359a 100644 --- a/pennylane_lightning/src/simulator/Measures.hpp +++ b/pennylane_lightning/src/simulator/Measures.hpp @@ -30,10 +30,11 @@ #include "Kokkos_Sparse.hpp" #include "LinearAlgebra.hpp" +#include "Observables.hpp" #include "StateVectorManagedCPU.hpp" #include "StateVectorRawCPU.hpp" -namespace Pennylane { +namespace Pennylane::Simulators { /** * @brief Observable's Measurement Class. @@ -217,6 +218,20 @@ class Measures { return expected_value_list; } + /** + * @brief Expectation value for a general Observable + * + * @param ob Observable + */ + auto expval(const Observable<fp_t> &ob) -> fp_t { + StateVectorManagedCPU<fp_t> op_sv(original_statevector); + ob.applyInPlace(op_sv); + const auto inner_prod = + Util::innerProdC(original_statevector.getData(), op_sv.getData(), + original_statevector.getLength()); + return std::real(inner_prod); + } + /** * @brief Variance of an observable. * @@ -380,4 +395,4 @@ class Measures { return samples; } }; // class Measures -} // namespace Pennylane +} // namespace Pennylane::Simulators diff --git a/pennylane_lightning/src/algorithms/Observables.cpp b/pennylane_lightning/src/simulator/Observables.cpp similarity index 57% rename from pennylane_lightning/src/algorithms/Observables.cpp rename to pennylane_lightning/src/simulator/Observables.cpp index d784910db0..8b200a2651 100644 --- a/pennylane_lightning/src/algorithms/Observables.cpp +++ b/pennylane_lightning/src/simulator/Observables.cpp @@ -14,14 +14,14 @@ #include "Observables.hpp" -template class Pennylane::Algorithms::NamedObs<float>; -template class Pennylane::Algorithms::NamedObs<double>; +template class Pennylane::Simulators::NamedObs<float>; +template class Pennylane::Simulators::NamedObs<double>; -template class Pennylane::Algorithms::HermitianObs<float>; -template class Pennylane::Algorithms::HermitianObs<double>; +template class Pennylane::Simulators::HermitianObs<float>; +template class Pennylane::Simulators::HermitianObs<double>; -template class Pennylane::Algorithms::TensorProdObs<float>; -template class Pennylane::Algorithms::TensorProdObs<double>; +template class Pennylane::Simulators::TensorProdObs<float>; +template class Pennylane::Simulators::TensorProdObs<double>; -template class Pennylane::Algorithms::Hamiltonian<float>; -template class Pennylane::Algorithms::Hamiltonian<double>; +template class Pennylane::Simulators::Hamiltonian<float>; +template class Pennylane::Simulators::Hamiltonian<double>; diff --git a/pennylane_lightning/src/algorithms/Observables.hpp b/pennylane_lightning/src/simulator/Observables.hpp similarity index 99% rename from pennylane_lightning/src/algorithms/Observables.hpp rename to pennylane_lightning/src/simulator/Observables.hpp index bf2e765272..f803276e96 100644 --- a/pennylane_lightning/src/algorithms/Observables.hpp +++ b/pennylane_lightning/src/simulator/Observables.hpp @@ -21,7 +21,7 @@ #include <memory> #include <unordered_set> -namespace Pennylane::Algorithms { +namespace Pennylane::Simulators { /** * @brief A base class for all observable classes. @@ -442,4 +442,4 @@ template <typename T> class Hamiltonian final : public Observable<T> { } }; -} // namespace Pennylane::Algorithms +} // namespace Pennylane::Simulators diff --git a/pennylane_lightning/src/tests/Test_AdjDiff.cpp b/pennylane_lightning/src/tests/Test_AdjDiff.cpp index a131433ab0..78ffdc5045 100644 --- a/pennylane_lightning/src/tests/Test_AdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_AdjDiff.cpp @@ -23,6 +23,7 @@ using namespace Pennylane; using namespace Pennylane::Algorithms; +using namespace Pennylane::Simulators; TEST_CASE("Algorithms::adjointJacobian Op=RX, Obs=Z", "[Algorithms]") { const std::vector<double> param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; @@ -540,7 +541,7 @@ TEST_CASE( "Algorithms::adjointJacobian with exceedingly complicated Hamiltonian", "[Algorithms]") { using namespace std::literals; - using Pennylane::Algorithms::detail::HamiltonianApplyInPlace; + using Pennylane::Simulators::detail::HamiltonianApplyInPlace; std::vector<double> param{-M_PI / 7, M_PI / 5, 2 * M_PI / 3}; std::vector<size_t> t_params{0, 2}; diff --git a/pennylane_lightning/src/tests/Test_AlgUtil.cpp b/pennylane_lightning/src/tests/Test_AlgUtil.cpp index 3698ada7ec..51a1853e6b 100644 --- a/pennylane_lightning/src/tests/Test_AlgUtil.cpp +++ b/pennylane_lightning/src/tests/Test_AlgUtil.cpp @@ -5,6 +5,7 @@ using namespace Pennylane; using namespace Pennylane::Algorithms; +using namespace Pennylane::Simulators; class TestException : public std::exception {}; diff --git a/pennylane_lightning/src/tests/Test_Measures.cpp b/pennylane_lightning/src/tests/Test_Measures.cpp index 311cc714c4..ff6760f7bf 100644 --- a/pennylane_lightning/src/tests/Test_Measures.cpp +++ b/pennylane_lightning/src/tests/Test_Measures.cpp @@ -16,6 +16,7 @@ using namespace Pennylane; using namespace Pennylane::Util; +using namespace Pennylane::Simulators; using std::complex; using std::size_t; diff --git a/pennylane_lightning/src/tests/Test_Measures_Sparse.cpp b/pennylane_lightning/src/tests/Test_Measures_Sparse.cpp index 32a2aca8b9..0e361ffde3 100644 --- a/pennylane_lightning/src/tests/Test_Measures_Sparse.cpp +++ b/pennylane_lightning/src/tests/Test_Measures_Sparse.cpp @@ -17,6 +17,7 @@ using namespace Pennylane; using namespace Pennylane::Util; +using namespace Pennylane::Simulators; namespace { using std::complex; diff --git a/pennylane_lightning/src/tests/Test_Observables.cpp b/pennylane_lightning/src/tests/Test_Observables.cpp index fb37dba954..c1b62275a9 100644 --- a/pennylane_lightning/src/tests/Test_Observables.cpp +++ b/pennylane_lightning/src/tests/Test_Observables.cpp @@ -4,7 +4,7 @@ #include <catch2/catch.hpp> using namespace Pennylane; -using namespace Pennylane::Algorithms; +using namespace Pennylane::Simulators; using Pennylane::Util::LightningException; // NOLINTNEXTLINE(readability-function-cognitive-complexity) diff --git a/pennylane_lightning/src/tests/Test_StateVecAdjDiff.cpp b/pennylane_lightning/src/tests/Test_StateVecAdjDiff.cpp index 092e0e380b..8b26641cda 100644 --- a/pennylane_lightning/src/tests/Test_StateVecAdjDiff.cpp +++ b/pennylane_lightning/src/tests/Test_StateVecAdjDiff.cpp @@ -15,6 +15,7 @@ using namespace Pennylane; using namespace Pennylane::Util; using namespace Pennylane::Algorithms; +using namespace Pennylane::Simulators; /** * @brief diff --git a/tests/test_measures.py b/tests/test_measures.py index 8df8864b40..4c83c24b4c 100644 --- a/tests/test_measures.py +++ b/tests/test_measures.py @@ -227,6 +227,41 @@ def circuit(): assert np.allclose(circuit(), cases[1], atol=tol, rtol=0) + @pytest.mark.parametrize( + "obs, coeffs, res", + [ + ([qml.PauliX(0) @ qml.PauliZ(1)], [1.0], 0.0), + ([qml.PauliZ(0) @ qml.PauliZ(1)], [1.0], math.cos(0.4) * math.cos(-0.2)), + ( + [ + qml.PauliX(0) @ qml.PauliZ(1), + qml.Hermitian( + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 3.0, 0.0, 0.0], + [0.0, 0.0, -1.0, 1.0], + [0.0, 0.0, 1.0, -2.0], + ], + wires=[0, 1], + ), + ], + [0.3, 1.0], + 0.9319728930156066, + ), + ], + ) + def test_expval_hamiltonian(self, obs, coeffs, res, tol, dev): + """Test expval with Hamiltonian""" + ham = qml.Hamiltonian(coeffs, obs) + + @qml.qnode(dev) + def circuit(): + qml.RX(0.4, wires=[0]) + qml.RY(-0.2, wires=[1]) + return qml.expval(ham) + + assert np.allclose(circuit(), res, atol=tol, rtol=0) + def test_value(self, dev, tol): """Test that the expval interface works""" @@ -428,6 +463,55 @@ def circuit2(): assert np.allclose(circuit1(), circuit2(), atol=tol) + @pytest.mark.parametrize( + "wires1, wires2", + [ + ([2, 3, 0], [2, 3, 0]), + ([0, 1], [0, 1]), + ([0, 2, 3], [2, 0, 3]), + (["a", "c", "d"], [2, 3, 0]), + ([-1, -2, -3], ["q1", "ancilla", 2]), + (["a", "c"], [3, 0]), + ([-1, -2], ["ancilla", 2]), + ], + ) + @pytest.mark.parametrize("C", [np.complex64, np.complex128]) + def test_wires_expval_hermitian(self, wires1, wires2, C, tol): + """Test that the expectation of a circuit is independent from the wire labels used.""" + dev1 = qml.device("lightning.qubit", wires=wires1, c_dtype=C) + dev1._state = dev1._asarray(dev1._state, C) + + dev2 = qml.device("lightning.qubit", wires=wires2) + dev2._state = dev2._asarray(dev2._state, C) + ob_mat = [ + [1.0, 2.0, 0.0, 1.0], + [2.0, -1.0, 0.0, 0.0], + [0.0, 0.0, 2.0, 0.0], + [1.0, 0.0, 0.0, -1.0], + ] + + n_wires = len(wires1) + ob1 = qml.Hermitian(ob_mat, wires=[wires1[0 % n_wires], wires1[1 % n_wires]]) + ob2 = qml.Hermitian(ob_mat, wires=[wires2[0 % n_wires], wires2[1 % n_wires]]) + + @qml.qnode(dev1) + def circuit1(): + qml.RX(0.5, wires=wires1[0 % n_wires]) + qml.RY(2.0, wires=wires1[1 % n_wires]) + if n_wires > 1: + qml.CNOT(wires=[wires1[0], wires1[1]]) + return [qml.expval(ob1)] + + @qml.qnode(dev2) + def circuit2(): + qml.RX(0.5, wires=wires2[0 % n_wires]) + qml.RY(2.0, wires=wires2[1 % n_wires]) + if n_wires > 1: + qml.CNOT(wires=[wires2[0], wires2[1]]) + return [qml.expval(ob2)] + + assert np.allclose(circuit1(), circuit2(), atol=tol) + class TestSample: """Tests that samples are properly calculated."""