diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index d76910abdf..8b233348a7 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -52,6 +52,12 @@ ### Improvements +* Refactor `cuda_utils` to remove its dependency on `custatevec.h`. + [(#681)](https://github.com/PennyLaneAI/pennylane-lightning/pull/681) + +* Add `test_templates.py` module where Grover and QSVT are tested. + [(#684)](https://github.com/PennyLaneAI/pennylane-lightning/pull/684) + * Create `cuda_utils` for common usage of CUDA related backends. [(#676)](https://github.com/PennyLaneAI/pennylane-lightning/pull/676) @@ -74,6 +80,12 @@ ### Bug fixes +* `LightningQubit` correctly decomposes state prep operations when used in the middle of a circuit. + [(#687)](https://github.com/PennyLaneAI/pennylane/pull/687) + +* `LightningQubit` correctly decomposes `qml.QFT` and `qml.GroverOperator` if `len(wires)` is greater than 9 and 12 respectively. + [(#687)](https://github.com/PennyLaneAI/pennylane/pull/687) + * Specify `isort` `--py` (Python version) and `-l` (max line length) to stabilize `isort` across Python versions and environments. [(#647)](https://github.com/PennyLaneAI/pennylane-lightning/pull/647) @@ -83,6 +95,15 @@ * `lightning.qubit` correctly decomposed state preparation operations with adjoint differentiation. [(#661)](https://github.com/PennyLaneAI/pennylane-lightning/pull/661) +* Fix the failed observable serialization unit tests. + [(#683)](https://github.com/PennyLaneAI/pennylane-lightning/pull/683) + +* Update the `LightningQubit` new device API to work with Catalyst. + [(#665)](https://github.com/PennyLaneAI/pennylane-lightning/pull/665) + +* Update the version of `codecov-action` to v4 and fix the CodeCov issue with the PL-Lightning check-compatibility actions. + [(#682)](https://github.com/PennyLaneAI/pennylane-lightning/pull/682) + ### Contributors This release contains contributions from (in alphabetical order): diff --git a/.github/workflows/compat-check-latest-latest.yml b/.github/workflows/compat-check-latest-latest.yml index 63533e089d..fadf592dee 100644 --- a/.github/workflows/compat-check-latest-latest.yml +++ b/.github/workflows/compat-check-latest-latest.yml @@ -12,27 +12,32 @@ jobs: with: lightning-version: latest pennylane-version: latest + secrets: inherit # pass all secrets tests_lkokkos_gpu: name: Lightning Compatibility test (tests_lkokkos_gpu) - latest/latest uses: ./.github/workflows/tests_gpu_kokkos.yml with: lightning-version: latest pennylane-version: latest + secrets: inherit # pass all secrets tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - latest/latest uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: latest pennylane-version: latest + secrets: inherit # pass all secrets tests_lgpu_gpu_mpi: name: Lightning Compatibility test (tests_lgpu_gpu_mpi) - latest/latest uses: ./.github/workflows/tests_linux_x86_mpi_gpu.yml with: lightning-version: latest pennylane-version: latest + secrets: inherit # pass all secrets tests_without_binary: name: Lightning Compatibility test (tests_without_binary) - latest/latest uses: ./.github/workflows/tests_without_binary.yml with: lightning-version: latest pennylane-version: latest + secrets: inherit # pass all secrets diff --git a/.github/workflows/compat-check-latest-stable.yml b/.github/workflows/compat-check-latest-stable.yml index 3e589283eb..d395d09530 100644 --- a/.github/workflows/compat-check-latest-stable.yml +++ b/.github/workflows/compat-check-latest-stable.yml @@ -12,27 +12,32 @@ jobs: with: lightning-version: latest pennylane-version: stable + secrets: inherit # pass all secrets tests_lkokkos_gpu: name: Lightning Compatibility test (tests_lkokkos_gpu) - latest/stable uses: ./.github/workflows/tests_gpu_kokkos.yml with: lightning-version: latest pennylane-version: stable + secrets: inherit # pass all secrets tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - latest/stable uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: latest pennylane-version: stable + secrets: inherit # pass all secrets tests_lgpu_gpu_mpi: name: Lightning Compatibility test (tests_lgpu_gpu_mpi) - latest/stable uses: ./.github/workflows/tests_linux_x86_mpi_gpu.yml with: lightning-version: latest pennylane-version: stable + secrets: inherit # pass all secrets tests_without_binary: name: Lightning Compatibility test (tests_without_binary) - latest/stable uses: ./.github/workflows/tests_without_binary.yml with: lightning-version: latest pennylane-version: stable + secrets: inherit # pass all secrets diff --git a/.github/workflows/compat-check-release-release.yml b/.github/workflows/compat-check-release-release.yml index 52c6bea41c..2c3f9becfb 100644 --- a/.github/workflows/compat-check-release-release.yml +++ b/.github/workflows/compat-check-release-release.yml @@ -12,27 +12,32 @@ jobs: with: lightning-version: release pennylane-version: release + secrets: inherit # pass all secrets tests_lkokkos_gpu: name: Lightning Compatibility test (tests_lkokkos_gpu) - release/release uses: ./.github/workflows/tests_gpu_kokkos.yml with: lightning-version: release pennylane-version: release + secrets: inherit # pass all secrets tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - release/release uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: release pennylane-version: release + secrets: inherit # pass all secrets tests_lgpu_gpu_mpi: name: Lightning Compatibility test (tests_lgpu_gpu_mpi) - release/release uses: ./.github/workflows/tests_linux_x86_mpi_gpu.yml with: lightning-version: release pennylane-version: release + secrets: inherit # pass all secrets tests_without_binary: name: Lightning Compatibility test (tests_without_binary) - release/release uses: ./.github/workflows/tests_without_binary.yml with: lightning-version: release pennylane-version: release + secrets: inherit # pass all secrets diff --git a/.github/workflows/compat-check-stable-latest.yml b/.github/workflows/compat-check-stable-latest.yml index f7e125ca69..a06e4b9a99 100644 --- a/.github/workflows/compat-check-stable-latest.yml +++ b/.github/workflows/compat-check-stable-latest.yml @@ -12,27 +12,32 @@ jobs: with: lightning-version: stable pennylane-version: latest + secrets: inherit # pass all secrets tests_lkokkos_gpu: name: Lightning Compatibility test (tests_lkokkos_gpu) - stable/latest uses: ./.github/workflows/tests_gpu_kokkos.yml with: lightning-version: stable pennylane-version: latest + secrets: inherit # pass all secrets tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - stable/latest uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: stable pennylane-version: latest + secrets: inherit # pass all secrets tests_lgpu_gpu_mpi: name: Lightning Compatibility test (tests_lgpu_gpu_mpi) - stable/latest uses: ./.github/workflows/tests_linux_x86_mpi_gpu.yml with: lightning-version: stable pennylane-version: latest + secrets: inherit # pass all secrets tests_without_binary: name: Lightning Compatibility test (tests_without_binary) - stable/latest uses: ./.github/workflows/tests_without_binary.yml with: lightning-version: stable pennylane-version: latest + secrets: inherit # pass all secrets diff --git a/.github/workflows/compat-check-stable-stable.yml b/.github/workflows/compat-check-stable-stable.yml index 8f33068c39..ddf6e07b19 100644 --- a/.github/workflows/compat-check-stable-stable.yml +++ b/.github/workflows/compat-check-stable-stable.yml @@ -12,27 +12,32 @@ jobs: with: lightning-version: stable pennylane-version: stable + secrets: inherit # pass all secrets tests_lkokkos_gpu: name: Lightning Compatibility test (tests_lkokkos_gpu) - stable/stable uses: ./.github/workflows/tests_gpu_kokkos.yml with: lightning-version: stable pennylane-version: stable + secrets: inherit # pass all secrets tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - stable/stable uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: stable pennylane-version: stable + secrets: inherit # pass all secrets tests_lgpu_gpu_mpi: name: Lightning Compatibility test (tests_lgpu_gpu_mpi) - stable/stable uses: ./.github/workflows/tests_linux_x86_mpi_gpu.yml with: lightning-version: stable pennylane-version: stable + secrets: inherit # pass all secrets tests_without_binary: name: Lightning Compatibility test (tests_without_binary) - stable/stable uses: ./.github/workflows/tests_without_binary.yml with: lightning-version: stable pennylane-version: stable + secrets: inherit # pass all secrets diff --git a/.github/workflows/tests_gpu_cuda.yml b/.github/workflows/tests_gpu_cuda.yml index 9d276b1704..c15f4fa1ba 100644 --- a/.github/workflows/tests_gpu_cuda.yml +++ b/.github/workflows/tests_gpu_cuda.yml @@ -340,9 +340,10 @@ jobs: name: ubuntu-codecov-results-python - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true + verbose: true token: ${{ secrets.CODECOV_TOKEN }} upload-to-codecov-linux-cpp: @@ -359,9 +360,10 @@ jobs: name: ubuntu-codecov-results-cpp - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true + verbose: true token: ${{ secrets.CODECOV_TOKEN }} - name: Cleanup diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index 6f88852119..840575d24a 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -560,10 +560,13 @@ jobs: cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS - OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} + # TODO: Remove this if-cond with release v0.36.0 + if [ -f tests/test_native_mcm.py ]; then + OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "test_native_mcm" $COVERAGE_FLAGS --cov-append + fi - name: Install all backend devices if: ${{ matrix.pl_backend == 'all' }} @@ -622,9 +625,10 @@ jobs: python -m coverage xml -i -o coverage-${{ github.job }}.xml - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true + verbose: true token: ${{ secrets.CODECOV_TOKEN }} upload-to-codecov-linux-cpp: @@ -641,9 +645,10 @@ jobs: name: ubuntu-codecov-results-cpp - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true + verbose: true token: ${{ secrets.CODECOV_TOKEN }} cpptestsWithMultipleBackends: diff --git a/.github/workflows/tests_linux_x86_mpi_gpu.yml b/.github/workflows/tests_linux_x86_mpi_gpu.yml index 1e324524b0..4d78d4cfff 100644 --- a/.github/workflows/tests_linux_x86_mpi_gpu.yml +++ b/.github/workflows/tests_linux_x86_mpi_gpu.yml @@ -297,7 +297,7 @@ jobs: name: ubuntu-codecov-results-cpp - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true verbose: true @@ -324,7 +324,7 @@ jobs: name: ubuntu-codecov-results-python - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true verbose: true diff --git a/.github/workflows/tests_windows.yml b/.github/workflows/tests_windows.yml index 19add8c6c2..b31ff91c9a 100644 --- a/.github/workflows/tests_windows.yml +++ b/.github/workflows/tests_windows.yml @@ -262,7 +262,8 @@ jobs: name: windows-coverage-report - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tests_without_binary.yml b/.github/workflows/tests_without_binary.yml index 9a0b682850..c061e64d7d 100644 --- a/.github/workflows/tests_without_binary.yml +++ b/.github/workflows/tests_without_binary.yml @@ -109,8 +109,9 @@ jobs: PL_DEVICE=${DEVICENAME} python -m pytest tests/ $COVERAGE_FLAGS - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: ./main/coverage.xml fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/pennylane_lightning/core/_serialize.py b/pennylane_lightning/core/_serialize.py index efb5ce9a18..79ee59af86 100644 --- a/pennylane_lightning/core/_serialize.py +++ b/pennylane_lightning/core/_serialize.py @@ -255,6 +255,9 @@ def map_wire(wire: int): def _pauli_sentence(self, observable, wires_map: dict = None): """Serialize a :class:`pennylane.pauli.PauliSentence` into a Hamiltonian.""" + # Trivial Pauli sentences' items is empty, cannot unpack + if not observable: + return self.hamiltonian_obs(np.array([0.0]).astype(self.rtype), [self._ob(Identity(0))]) pwords, coeffs = zip(*observable.items()) terms = [self._pauli_word(pw, wires_map) for pw in pwords] coeffs = np.array(coeffs).astype(self.rtype) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 3da22800fb..9c6e90e856 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev28" +__version__ = "0.36.0-dev33" diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/MPIWorker.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/MPIWorker.hpp index f4c509f76d..eae1aa2413 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/MPIWorker.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/MPIWorker.hpp @@ -21,6 +21,7 @@ #include "MPIManager.hpp" #include "MPI_helpers.hpp" #include "cuError.hpp" +#include "cuStateVecError.hpp" #include #include #include diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaBase.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaBase.hpp index 3e40420bd5..fba307ebed 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaBase.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaBase.hpp @@ -27,6 +27,7 @@ #include "DevTag.hpp" #include "Error.hpp" #include "StateVectorBase.hpp" +#include "cuStateVecError.hpp" #include "cuda_helpers.hpp" /// @cond DEV diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaMPI.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaMPI.hpp index c7da5b82c3..0496a8042e 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaMPI.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaMPI.hpp @@ -36,6 +36,8 @@ #include "StateVectorCudaBase.hpp" #include "cuGateCache.hpp" #include "cuGates_host.hpp" +#include "cuStateVecError.hpp" +#include "cuStateVec_helpers.hpp" #include "cuda_helpers.hpp" #include "CPUMemoryModel.hpp" diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaManaged.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaManaged.hpp index 15fd843190..4e7c749ba6 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaManaged.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/StateVectorCudaManaged.hpp @@ -37,6 +37,8 @@ #include "CPUMemoryModel.hpp" #include "cuError.hpp" +#include "cuStateVecError.hpp" +#include "cuStateVec_helpers.hpp" #include "LinearAlg.hpp" diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp index fca0175177..dc213c126f 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp @@ -34,6 +34,8 @@ #include "Observables.hpp" #include "ObservablesGPU.hpp" #include "StateVectorCudaManaged.hpp" +#include "cuStateVecError.hpp" +#include "cuStateVec_helpers.hpp" #include "cuda_helpers.hpp" /// @cond DEV diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPUMPI.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPUMPI.hpp index d82ebfd0e4..9d0d986fa4 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPUMPI.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPUMPI.hpp @@ -39,6 +39,7 @@ #include "ObservablesGPUMPI.hpp" #include "StateVectorCudaMPI.hpp" #include "StateVectorCudaManaged.hpp" +#include "cuStateVecError.hpp" #include "cuda_helpers.hpp" /// @cond DEV diff --git a/pennylane_lightning/core/src/utils/cuda_utils/MPI_helpers.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/utils/MPI_helpers.hpp similarity index 100% rename from pennylane_lightning/core/src/utils/cuda_utils/MPI_helpers.hpp rename to pennylane_lightning/core/src/simulators/lightning_gpu/utils/MPI_helpers.hpp diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVecError.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVecError.hpp new file mode 100644 index 0000000000..691ac7a2cf --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVecError.hpp @@ -0,0 +1,96 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from JET: https://github.com/XanaduAI/jet.git +// and from Lightning: https://github.com/PennylaneAI/pennylane-lightning.git + +/** + * @file cuStateVecError.hpp + * Defines macros that throws Exception from cuStateVec failure error codes. + */ + +#pragma once +#include +#include + +#include + +#include "Error.hpp" +#include "Util.hpp" + +// LCOV_EXCL_START +/// @cond DEV +namespace { +using namespace Pennylane::Util; +} +/// @endcond + +#ifndef CUDA_UNSAFE + +/** + * @brief Macro that throws Exception from cuStateVec failure error codes. + * + * @param err cuStateVec function error-code. + */ +#define PL_CUSTATEVEC_IS_SUCCESS(err) \ + PL_ABORT_IF_NOT( \ + err == CUSTATEVEC_STATUS_SUCCESS, \ + Pennylane::LightningGPU::Util::GetCuStateVecErrorString(err).c_str()) + +#else +#define PL_CUSTATEVEC_IS_SUCCESS(err) \ + { static_cast(err); } +#endif + +namespace Pennylane::LightningGPU::Util { +static const std::string +GetCuStateVecErrorString(const custatevecStatus_t &err) { + using namespace std::string_literals; + + switch (err) { + case CUSTATEVEC_STATUS_SUCCESS: + return "No errors"s; + case CUSTATEVEC_STATUS_NOT_INITIALIZED: + return "custatevec not initialized"s; + case CUSTATEVEC_STATUS_ALLOC_FAILED: + return "custatevec memory allocation failed"s; + case CUSTATEVEC_STATUS_INVALID_VALUE: + return "custatevec invalid value"s; + case CUSTATEVEC_STATUS_ARCH_MISMATCH: + return "custatevec CUDA device architecture mismatch"s; + case CUSTATEVEC_STATUS_EXECUTION_FAILED: + return "custatevec execution failed"s; + case CUSTATEVEC_STATUS_INTERNAL_ERROR: + return "custatevec internal error"s; + case CUSTATEVEC_STATUS_NOT_SUPPORTED: + return "custatevec unsupported operation/device"s; + case CUSTATEVEC_STATUS_INSUFFICIENT_WORKSPACE: + return "custatevec insufficient memory for gate-application workspace"s; + case CUSTATEVEC_STATUS_SAMPLER_NOT_PREPROCESSED: + return "custatevec sampler not preprocessed"s; + case CUSTATEVEC_STATUS_NO_DEVICE_ALLOCATOR: + return "custatevec no device allocator"s; + case CUSTATEVEC_STATUS_DEVICE_ALLOCATOR_ERROR: + return "custatevec device allocator error"s; + case CUSTATEVEC_STATUS_COMMUNICATOR_ERROR: + return "custatevec communicator failure"s; + case CUSTATEVEC_STATUS_LOADING_LIBRARY_FAILED: + return "custatevec dynamic library load failure"s; + default: + return "custatevec status not found. Error code="s + + std::to_string(err); + } +} +} // namespace Pennylane::LightningGPU::Util +// LCOV_EXCL_STOP diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVec_helpers.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVec_helpers.hpp new file mode 100644 index 0000000000..8bd27c2dc8 --- /dev/null +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/utils/cuStateVec_helpers.hpp @@ -0,0 +1,104 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from JET: https://github.com/XanaduAI/jet.git + +/** + * @file cudaStateVec_helpers.hpp + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#include "cuStateVecError.hpp" + +namespace Pennylane::LightningGPU::Util { + +inline static auto pauliStringToEnum(const std::string &pauli_word) + -> std::vector { + // Map string rep to Pauli enums + const std::unordered_map pauli_map{ + std::pair{std::string("I"), + CUSTATEVEC_PAULI_I}, + std::pair{std::string("X"), + CUSTATEVEC_PAULI_X}, + std::pair{std::string("Y"), + CUSTATEVEC_PAULI_Y}, + std::pair{std::string("Z"), + CUSTATEVEC_PAULI_Z}}; + + constexpr std::size_t num_char = 1; + + std::vector output; + output.reserve(pauli_word.size()); + + for (const auto &ch : pauli_word) { + auto out = pauli_map.at(std::string(num_char, ch)); + output.push_back(out); + } + return output; +} + +inline static auto pauliStringToOpNames(const std::string &pauli_word) + -> std::vector { + // Map string rep to Pauli + const std::unordered_map pauli_map{ + std::pair{std::string("I"), + std::string("Identity")}, + std::pair{std::string("X"), + std::string("PauliX")}, + std::pair{std::string("Y"), + std::string("PauliY")}, + std::pair{std::string("Z"), + std::string("PauliZ")}}; + + static constexpr std::size_t num_char = 1; + + std::vector output; + output.reserve(pauli_word.size()); + + for (const auto ch : pauli_word) { + auto out = pauli_map.at(std::string(num_char, ch)); + output.push_back(out); + } + return output; +} + +/** + * Utility function object to tell std::shared_ptr how to + * release/destroy cuStateVec objects. + */ +struct handleDeleter { + void operator()(custatevecHandle_t handle) const { + PL_CUSTATEVEC_IS_SUCCESS(custatevecDestroy(handle)); + } +}; + +using SharedCusvHandle = + std::shared_ptr::type>; + +/** + * @brief Creates a SharedCusvHandle (a shared pointer to a custatevecHandle) + */ +inline SharedCusvHandle make_shared_cusv_handle() { + custatevecHandle_t h; + PL_CUSTATEVEC_IS_SUCCESS(custatevecCreate(&h)); + return {h, handleDeleter()}; +} +} // namespace Pennylane::LightningGPU::Util diff --git a/pennylane_lightning/core/src/utils/config.h b/pennylane_lightning/core/src/utils/config.h index 8f887c7d2d..d9c12fd532 100644 --- a/pennylane_lightning/core/src/utils/config.h +++ b/pennylane_lightning/core/src/utils/config.h @@ -14,10 +14,10 @@ /** * @file - * Record the path to scipy.libs at compile time. + * Config file for the path to scipy.libs at compile time. */ #ifndef CONFIG_H #define CONFIG_H #define SCIPY_LIBS_PATH "" -#endif \ No newline at end of file +#endif diff --git a/pennylane_lightning/core/src/utils/cuda_utils/LinearAlg.hpp b/pennylane_lightning/core/src/utils/cuda_utils/LinearAlg.hpp index 13a7ec9a90..eef358e9c4 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/LinearAlg.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/LinearAlg.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include "DataBuffer.hpp" #include "cuError.hpp" @@ -247,17 +246,12 @@ struct HandleDeleter { void operator()(cublasHandle_t handle) const { PL_CUBLAS_IS_SUCCESS(cublasDestroy(handle)); } - void operator()(custatevecHandle_t handle) const { - PL_CUSTATEVEC_IS_SUCCESS(custatevecDestroy(handle)); - } void operator()(cusparseHandle_t handle) const { PL_CUSPARSE_IS_SUCCESS(cusparseDestroy(handle)); } }; using SharedCublasCaller = std::shared_ptr; -using SharedCusvHandle = - std::shared_ptr::type>; using SharedCusparseHandle = std::shared_ptr::type>; @@ -268,15 +262,6 @@ inline SharedCublasCaller make_shared_cublas_caller() { return std::make_shared(); } -/** - * @brief Creates a SharedCusvHandle (a shared pointer to a custatevecHandle) - */ -inline SharedCusvHandle make_shared_cusv_handle() { - custatevecHandle_t h; - PL_CUSTATEVEC_IS_SUCCESS(custatevecCreate(&h)); - return {h, HandleDeleter()}; -} - /** * @brief Creates a SharedCusparseHandle (a shared pointer to a cusparseHandle) */ diff --git a/pennylane_lightning/core/src/utils/cuda_utils/MPILinearAlg.hpp b/pennylane_lightning/core/src/utils/cuda_utils/MPILinearAlg.hpp index 9f0f3972a6..cfe9ec7d81 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/MPILinearAlg.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/MPILinearAlg.hpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "DataBuffer.hpp" #include "cuError.hpp" diff --git a/pennylane_lightning/core/src/utils/cuda_utils/MPIManager.hpp b/pennylane_lightning/core/src/utils/cuda_utils/MPIManager.hpp index e32295c9c6..d42d39082a 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/MPIManager.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/MPIManager.hpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -30,6 +29,10 @@ #include #include +#ifdef _ENABLE_PLGPU +#include +#endif + #include "DataBuffer.hpp" #include "Error.hpp" @@ -155,7 +158,9 @@ class MPIManager final { {cppTypeToString(), MPI_C_FLOAT_COMPLEX}, {cppTypeToString(), MPI_C_DOUBLE_COMPLEX}, {cppTypeToString(), MPI_C_DOUBLE_COMPLEX}, +#ifdef _ENABLE_PLGPU {cppTypeToString(), MPI_INT64_T}, +#endif // cuda related types {cppTypeToString(), MPI_UINT8_T}, {cppTypeToString(), MPI_UINT8_T}}; diff --git a/pennylane_lightning/core/src/utils/cuda_utils/cuError.hpp b/pennylane_lightning/core/src/utils/cuda_utils/cuError.hpp index 11ff04e3b9..4824517493 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/cuError.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/cuError.hpp @@ -25,7 +25,6 @@ #include #include #include -#include #include "Error.hpp" #include "Util.hpp" @@ -50,16 +49,6 @@ using namespace Pennylane::Util; #define PL_CUSPARSE_IS_SUCCESS(err) \ PL_ABORT_IF_NOT(err == CUSPARSE_STATUS_SUCCESS, GetCuSparseErrorString(err)) -/** - * @brief Macro that throws Exception from cuQuantum failure error codes. - * - * @param err cuQuantum function error-code. - */ -#define PL_CUSTATEVEC_IS_SUCCESS(err) \ - PL_ABORT_IF_NOT( \ - err == CUSTATEVEC_STATUS_SUCCESS, \ - Pennylane::LightningGPU::Util::GetCuStateVecErrorString(err).c_str()) - #else #define PL_CUDA_IS_SUCCESS(err) \ { static_cast(err); } @@ -67,8 +56,6 @@ using namespace Pennylane::Util; { static_cast(err); } #define PL_CUSPARSE_IS_SUCCESS(err) \ { static_cast(err); } -#define PL_CUSTATEVEC_IS_SUCCESS(err) \ - { static_cast(err); } #endif namespace Pennylane::LightningGPU::Util { @@ -150,58 +137,5 @@ static const std::string GetCuSparseErrorString(const cusparseStatus_t &err) { return result; } -static const std::string -GetCuStateVecErrorString(const custatevecStatus_t &err) { - std::string result; - switch (err) { - case CUSTATEVEC_STATUS_SUCCESS: - result = "No errors"; - break; - case CUSTATEVEC_STATUS_NOT_INITIALIZED: - result = "custatevec not initialized"; - break; - case CUSTATEVEC_STATUS_ALLOC_FAILED: - result = "custatevec memory allocation failed"; - break; - case CUSTATEVEC_STATUS_INVALID_VALUE: - result = "custatevec invalid value"; - break; - case CUSTATEVEC_STATUS_ARCH_MISMATCH: - result = "custatevec CUDA device architecture mismatch"; - break; - case CUSTATEVEC_STATUS_EXECUTION_FAILED: - result = "custatevec execution failed"; - break; - case CUSTATEVEC_STATUS_INTERNAL_ERROR: - result = "custatevec internal error"; - break; - case CUSTATEVEC_STATUS_NOT_SUPPORTED: - result = "custatevec unsupported operation/device"; - break; - case CUSTATEVEC_STATUS_INSUFFICIENT_WORKSPACE: - result = - "custatevec insufficient memory for gate-application workspace"; - break; - case CUSTATEVEC_STATUS_SAMPLER_NOT_PREPROCESSED: - result = "custatevec sampler not preprocessed"; - break; - case CUSTATEVEC_STATUS_NO_DEVICE_ALLOCATOR: - result = "custatevec no device allocator"; - break; - case CUSTATEVEC_STATUS_DEVICE_ALLOCATOR_ERROR: - result = "custatevec device allocator error"; - break; - case CUSTATEVEC_STATUS_COMMUNICATOR_ERROR: - result = "custatevec communicator failure"; - break; - case CUSTATEVEC_STATUS_LOADING_LIBRARY_FAILED: - result = "custatevec dynamic library load failure"; - break; - default: - result = - "custatevec status not found. Error code=" + std::to_string(err); - } - return result; -} } // namespace Pennylane::LightningGPU::Util - // LCOV_EXCL_STOP \ No newline at end of file + // LCOV_EXCL_STOP diff --git a/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp b/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp index c7eeeb975c..1f0aa824cd 100644 --- a/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp +++ b/pennylane_lightning/core/src/utils/cuda_utils/cuda_helpers.hpp @@ -33,7 +33,6 @@ #include #include #include -#include #include "DevTag.hpp" #include "cuError.hpp" @@ -339,56 +338,6 @@ static std::pair getGPUArch(int device_number = 0) { return std::make_pair(deviceProp.major, deviceProp.minor); } -inline static auto pauliStringToEnum(const std::string &pauli_word) - -> std::vector { - // Map string rep to Pauli enums - const std::unordered_map pauli_map{ - std::pair{std::string("X"), - CUSTATEVEC_PAULI_X}, - std::pair{std::string("Y"), - CUSTATEVEC_PAULI_Y}, - std::pair{std::string("Z"), - CUSTATEVEC_PAULI_Z}, - std::pair{std::string("I"), - CUSTATEVEC_PAULI_I}}; - - static constexpr std::size_t num_char = 1; - - std::vector output; - output.reserve(pauli_word.size()); - - for (const auto ch : pauli_word) { - auto out = pauli_map.at(std::string(num_char, ch)); - output.push_back(out); - } - return output; -} - -inline static auto pauliStringToOpNames(const std::string &pauli_word) - -> std::vector { - // Map string rep to Pauli - const std::unordered_map pauli_map{ - std::pair{std::string("X"), - std::string("PauliX")}, - std::pair{std::string("Y"), - std::string("PauliY")}, - std::pair{std::string("Z"), - std::string("PauliZ")}, - std::pair{std::string("I"), - std::string("Identity")}}; - - static constexpr std::size_t num_char = 1; - - std::vector output; - output.reserve(pauli_word.size()); - - for (const auto ch : pauli_word) { - auto out = pauli_map.at(std::string(num_char, ch)); - output.push_back(out); - } - return output; -} - /** * Utility hash function for complex vectors representing matrices. */ diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.toml b/pennylane_lightning/lightning_kokkos/lightning_kokkos.toml index b8261d6808..e4cf01a526 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.toml +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.toml @@ -18,6 +18,7 @@ observables = [ "SProd", "Prod", "Exp", + "LinearCombination", ] # The union of all gate types listed in this section must match what diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 0dce9eb303..bfefacb4bd 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -34,7 +34,7 @@ validate_observables, ) from pennylane.measurements import MidMeasureMP -from pennylane.operation import Tensor +from pennylane.operation import DecompositionUndefinedError, Operator, Tensor from pennylane.ops import Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumTape from pennylane.transforms.core import TransformProgram @@ -182,9 +182,6 @@ def simulate_and_vjp( _operations = frozenset( { "Identity", - "BasisState", - "QubitStateVector", - "StatePrep", "QubitUnitary", "ControlledQubitUnitary", "MultiControlledX", @@ -264,8 +261,6 @@ def simulate_and_vjp( "QFT", "ECR", "BlockEncode", - "MidMeasureMP", - "Conditional", } ) # The set of supported operations. @@ -292,17 +287,30 @@ def simulate_and_vjp( # The set of supported observables. -def stopping_condition(op: qml.operation.Operator) -> bool: +def stopping_condition(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.qubit``.""" + # These thresholds are adapted from `lightning_base.py` + # To avoid building matrices beyond the given thresholds. + # This should reduce runtime overheads for larger systems. + if isinstance(op, qml.QFT): + return len(op.wires) < 10 + if isinstance(op, qml.GroverOperator): + return len(op.wires) < 13 return op.name in _operations -def accepted_observables(obs: qml.operation.Operator) -> bool: +def stopping_condition_shots(op: Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.qubit`` + with finite shots.""" + return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional)) + + +def accepted_observables(obs: Operator) -> bool: """A function that determines whether or not an observable is supported by ``lightning.qubit``.""" return obs.name in _observables -def adjoint_observables(obs: qml.operation.Operator) -> bool: +def adjoint_observables(obs: Operator) -> bool: """A function that determines whether or not an observable is supported by ``lightning.qubit`` when using the adjoint differentiation method.""" if isinstance(obs, qml.Projector): @@ -336,7 +344,7 @@ def _supports_adjoint(circuit): try: prog((circuit,)) - except (qml.operation.DecompositionUndefinedError, qml.DeviceError, AttributeError): + except (DecompositionUndefinedError, qml.DeviceError, AttributeError): return False return True @@ -356,7 +364,11 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: name = "adjoint + lightning.qubit" program.add_transform(no_sampling, name=name) program.add_transform( - decompose, stopping_condition=adjoint_ops, name=name, skip_initial_state_prep=False + decompose, + stopping_condition=adjoint_ops, + stopping_condition_shots=stopping_condition_shots, + name=name, + skip_initial_state_prep=False, ) program.add_transform(validate_observables, accepted_observables, name=name) program.add_transform( @@ -408,7 +420,9 @@ class LightningQubit(Device): _CPP_BINARY_AVAILABLE = LQ_CPP_BINARY_AVAILABLE _new_API = True _backend_info = backend_info if LQ_CPP_BINARY_AVAILABLE else None - _config = Path(__file__).parent / "lightning_qubit.toml" + + # This `config` is used in Catalyst-Frontend + config = Path(__file__).parent / "lightning_qubit.toml" # TODO: Move supported ops/obs to TOML file operations = _operations @@ -463,7 +477,7 @@ def __init__( # pylint: disable=too-many-arguments self._num_burnin = num_burnin else: self._kernel_name = None - self._num_burnin = None + self._num_burnin = 0 @property def name(self): @@ -515,19 +529,25 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) * Currently does not intrinsically support parameter broadcasting """ - config = self._setup_execution_config(execution_config) + exec_config = self._setup_execution_config(execution_config) program = TransformProgram() program.add_transform(validate_measurements, name=self.name) program.add_transform(validate_observables, accepted_observables, name=self.name) program.add_transform(validate_device_wires, self.wires, name=self.name) program.add_transform(mid_circuit_measurements, device=self) - program.add_transform(decompose, stopping_condition=stopping_condition, name=self.name) + program.add_transform( + decompose, + stopping_condition=stopping_condition, + stopping_condition_shots=stopping_condition_shots, + skip_initial_state_prep=True, + name=self.name, + ) program.add_transform(qml.transforms.broadcast_expand) - if config.gradient_method == "adjoint": + if exec_config.gradient_method == "adjoint": _add_adjoint_transforms(program) - return program, config + return program, exec_config # pylint: disable=unused-argument def execute( diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.toml b/pennylane_lightning/lightning_qubit/lightning_qubit.toml index e5517c6e17..3d659337d5 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.toml +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.toml @@ -19,6 +19,7 @@ observables = [ "SProd", "Prod", "Exp", + "LinearCombination", ] # The union of all gate types listed in this section must match what diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 7f9e594326..bf27cbcb47 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -31,8 +31,10 @@ adjoint_measurements, adjoint_observables, decompose, + mid_circuit_measurements, no_sampling, stopping_condition, + stopping_condition_shots, validate_adjoint_trainable_params, validate_device_wires, validate_measurements, @@ -107,6 +109,7 @@ def test_add_adjoint_transforms(self): expected_program.add_transform( decompose, stopping_condition=adjoint_ops, + stopping_condition_shots=stopping_condition_shots, name=name, skip_initial_state_prep=False, ) @@ -184,7 +187,7 @@ def process_and_execute(device, tape): "batch_obs": False, "mcmc": False, "kernel_name": None, - "num_burnin": None, + "num_burnin": 0, } @pytest.mark.parametrize( @@ -222,7 +225,7 @@ def process_and_execute(device, tape): "batch_obs": False, "mcmc": True, "kernel_name": None, - "num_burnin": None, + "num_burnin": 0, }, ), ), @@ -256,11 +259,13 @@ def test_preprocess(self, adjoint): expected_program.add_transform(validate_measurements, name=device.name) expected_program.add_transform(validate_observables, accepted_observables, name=device.name) expected_program.add_transform(validate_device_wires, device.wires, name=device.name) + expected_program.add_transform(mid_circuit_measurements, device=device) expected_program.add_transform( - qml.devices.preprocess.mid_circuit_measurements, device=device - ) - expected_program.add_transform( - decompose, stopping_condition=stopping_condition, name=device.name + decompose, + stopping_condition=stopping_condition, + stopping_condition_shots=stopping_condition_shots, + skip_initial_state_prep=True, + name=device.name, ) expected_program.add_transform(qml.transforms.broadcast_expand) @@ -270,6 +275,7 @@ def test_preprocess(self, adjoint): expected_program.add_transform( decompose, stopping_condition=adjoint_ops, + stopping_condition_shots=stopping_condition_shots, name=name, skip_initial_state_prep=False, ) @@ -287,6 +293,63 @@ def test_preprocess(self, adjoint): actual_program, _ = device.preprocess(config) assert actual_program == expected_program + @pytest.mark.parametrize( + "op, is_trainable", + [ + (qml.StatePrep([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), False), + (qml.StatePrep(qml.numpy.array([1 / np.sqrt(2), 1 / np.sqrt(2)]), wires=0), True), + (qml.StatePrep(np.array([1, 0]), wires=0), False), + (qml.BasisState([1, 1], wires=[0, 1]), False), + (qml.BasisState(qml.numpy.array([1, 1]), wires=[0, 1]), True), + ], + ) + def test_preprocess_state_prep_first_op_decomposition(self, op, is_trainable): + """Test that state prep ops in the beginning of a tape are decomposed with adjoint + but not otherwise.""" + tape = qml.tape.QuantumScript([op, qml.RX(1.23, wires=0)], [qml.expval(qml.PauliZ(0))]) + device = LightningDevice(wires=3) + + if is_trainable: + # Need to decompose twice as the state prep ops we use first decompose into a template + decomp = op.decomposition()[0].decomposition() + else: + decomp = [op] + + config = ExecutionConfig(gradient_method="best" if is_trainable else None) + program, _ = device.preprocess(config) + [new_tape], _ = program([tape]) + expected_tape = qml.tape.QuantumScript([*decomp, qml.RX(1.23, wires=0)], tape.measurements) + assert qml.equal(new_tape, expected_tape) + + @pytest.mark.parametrize( + "op, decomp_depth", + [ + (qml.StatePrep([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 1), + (qml.StatePrep(np.array([1, 0]), wires=0), 1), + (qml.BasisState([1, 1], wires=[0, 1]), 1), + (qml.BasisState(qml.numpy.array([1, 1]), wires=[0, 1]), 1), + (qml.AmplitudeEmbedding([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 2), + (qml.MottonenStatePreparation([1 / np.sqrt(2), 1 / np.sqrt(2)], wires=0), 0), + ], + ) + def test_preprocess_state_prep_middle_op_decomposition(self, op, decomp_depth): + """Test that state prep ops in the middle of a tape are always decomposed.""" + tape = qml.tape.QuantumScript( + [qml.RX(1.23, wires=0), op, qml.CNOT([0, 1])], [qml.expval(qml.PauliZ(0))] + ) + device = LightningDevice(wires=3) + + for _ in range(decomp_depth): + op = op.decomposition()[0] + decomp = op.decomposition() + + program, _ = device.preprocess() + [new_tape], _ = program([tape]) + expected_tape = qml.tape.QuantumScript( + [qml.RX(1.23, wires=0), *decomp, qml.CNOT([0, 1])], tape.measurements + ) + assert qml.equal(new_tape, expected_tape) + @pytest.mark.usefixtures("use_legacy_and_new_opmath") @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) @pytest.mark.parametrize( diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 1b9fdb2e67..092752b780 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -1481,6 +1481,13 @@ def convert_to_array_gpu(params): def convert_to_array_gpu_default(params): return np.hstack(qnode_gpu_default(params)) + i_cpu = qnode_cpu(params) + i_gpu = qnode_gpu(params) + i_gpu_default = qnode_gpu_default(params) + + assert np.allclose(i_cpu, i_gpu) + assert np.allclose(i_gpu, i_gpu_default) + j_cpu = qml.jacobian(qnode_cpu)(params) j_gpu = qml.jacobian(qnode_gpu)(params) j_gpu_default = qml.jacobian(qnode_gpu_default)(params) diff --git a/tests/test_execute.py b/tests/test_execute.py index 82f3c52a78..49f3f21fd4 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -87,40 +87,3 @@ def dev_l_execute(t): assert np.allclose(grad_dev_l, grad_qml_l, tol) assert np.allclose(grad_dev_l, grad_qml_d, tol) - - -class TestGrover: - """Test Grover's algorithm (multi-controlled gates, decomposition, etc.)""" - - @pytest.mark.parametrize("num_qubits", range(4, 8)) - def test_grover(self, num_qubits): - np.random.seed(42) - omega = np.random.rand(num_qubits) > 0.5 - dev = qml.device(device_name, wires=num_qubits) - wires = list(range(num_qubits)) - - @qml.qnode(dev, diff_method=None) - def circuit(omega): - iterations = int(np.round(np.sqrt(2**num_qubits) * np.pi / 4)) - - # Initial state preparation - for wire in wires: - qml.Hadamard(wires=wire) - - # Grover's iterator - for _ in range(iterations): - qml.FlipSign(omega, wires=wires) - qml.templates.GroverOperator(wires) - - return qml.probs(wires=wires) - - prob = circuit(omega) - index = omega.astype(int) - index = functools.reduce( - lambda sum, x: sum + (1 << x[0]) * x[1], - zip([i for i in range(len(index) - 1, -1, -1)], index), - 0, - ) - assert np.allclose(np.sum(prob), 1.0) - assert prob[index] > 0.95 - assert np.sum(prob) - prob[index] < 0.05 diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 181271f97d..b71986f0b4 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -695,23 +695,36 @@ def circuit2(): @flaky(max_runs=5) -@pytest.mark.parametrize("shots", [10000, [10000, 11111]]) +@pytest.mark.parametrize("shots", [None, 10000, [10000, 11111]]) @pytest.mark.parametrize("measure_f", [qml.counts, qml.expval, qml.probs, qml.sample, qml.var]) @pytest.mark.parametrize( - "obs", [[0], [0, 1], qml.PauliZ(0), qml.PauliY(1), qml.PauliZ(0) @ qml.PauliY(1)] + "obs", + [ + [0], + [0, 1], + qml.PauliZ(0), + qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliY(1), + qml.PauliZ(1) @ qml.PauliY(2), + ], ) @pytest.mark.parametrize("mcmc", [False, True]) @pytest.mark.parametrize("kernel_name", ["Local", "NonZeroRandom"]) def test_shots_single_measure_obs(shots, measure_f, obs, mcmc, kernel_name): """Tests that Lightning handles shots in a circuit where a single measurement of a common observable is performed at the end.""" - n_qubits = 2 + n_qubits = 3 - if device_name in ("lightning.gpu", "lightning.kokkos") and (mcmc or kernel_name != "Local"): + if (shots is None or device_name in ("lightning.gpu", "lightning.kokkos")) and ( + mcmc or kernel_name != "Local" + ): pytest.skip(f"Device {device_name} does not have an mcmc option.") if measure_f in (qml.expval, qml.var) and isinstance(obs, Sequence): pytest.skip("qml.expval, qml.var do not take wire arguments.") + if measure_f in (qml.counts, qml.sample) and shots is None: + pytest.skip("qml.counts, qml.sample do not work with shots = None.") + if device_name in ("lightning.gpu", "lightning.kokkos"): dev = qml.device(device_name, wires=n_qubits, shots=shots) else: @@ -725,6 +738,7 @@ def func(x, y): qml.RX(x, 0) qml.RX(y, 0) qml.RX(y, 1) + qml.RX(x, 2) return measure_f(wires=obs) if isinstance(obs, Sequence) else measure_f(op=obs) func1 = qml.QNode(func, dev) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 2598232b9b..5b6e6b2f4f 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -317,28 +317,13 @@ def test_hamiltonian_tensor_return(self, use_csingle, wires_map): tape, wires_map ) - # Expression (ham @ obs) is converted internally by Pennylane - # where obs is appended to each term of the ham - if qml.operation.active_new_opmath(): - first_term = tensor_prod_obs( - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - ] - ), - named_obs("PauliZ", [3]), - ] - ) - else: - first_term = tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - named_obs("PauliZ", [3]), - ] - ) + first_term = tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + named_obs("PauliZ", [3]), + ] + ) s_expected = hamiltonian_obs( np.array([0.3, 0.5, 0.4], dtype=r_dtype), @@ -498,7 +483,8 @@ def test_prod(self, use_csingle, wires_map): s_expected = tensor_obs( [ - tensor_obs([named_obs("PauliX", [1]), named_obs("PauliZ", [0])]), + named_obs("PauliZ", [0]), + named_obs("PauliX", [1]), named_obs("Hadamard", [2]), ] ) diff --git a/tests/test_templates.py b/tests/test_templates.py new file mode 100644 index 0000000000..f133547282 --- /dev/null +++ b/tests/test_templates.py @@ -0,0 +1,786 @@ +# Copyright 2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Test the correctness of templates with Lightning devices. +""" +import functools + +import pennylane as qml +import pytest +from conftest import LightningDevice, device_name +from pennylane import numpy as np + +# pylint: disable=missing-function-docstring, too-few-public-methods + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +class TestGrover: + """Test Grover's algorithm (multi-controlled gates, decomposition, etc.)""" + + @pytest.mark.parametrize("n_qubits", range(4, 8)) + def test_grover(self, n_qubits): + np.random.seed(42) + omega = np.random.rand(n_qubits) > 0.5 + dev = qml.device(device_name, wires=n_qubits) + wires = list(range(n_qubits)) + + @qml.qnode(dev, diff_method=None) + def circuit(omega): + iterations = int(np.round(np.sqrt(2**n_qubits) * np.pi / 4)) + + # Initial state preparation + for wire in wires: + qml.Hadamard(wires=wire) + + # Grover's iterator + for _ in range(iterations): + qml.FlipSign(omega, wires=wires) + qml.templates.GroverOperator(wires) + + return qml.probs(wires=wires) + + prob = circuit(omega) + index = omega.astype(int) + index = functools.reduce( + lambda sum, x: sum + (1 << x[0]) * x[1], + zip([i for i in range(len(index) - 1, -1, -1)], index), + 0, + ) + assert np.allclose(np.sum(prob), 1.0) + assert prob[index] > 0.95 + + @pytest.mark.skipif(not LightningDevice._new_API, reason="New API required.") + @pytest.mark.parametrize("wires", [5, 10, 13, 15]) + def test_preprocess_grover_operator_decomposition(self, wires): + """Test that qml.GroverOperator is not decomposed for less than 10 wires.""" + tape = qml.tape.QuantumScript( + [qml.GroverOperator(wires=list(range(wires)))], [qml.expval(qml.PauliZ(0))] + ) + dev = LightningDevice(wires=wires) + + program, _ = dev.preprocess() + [new_tape], _ = program([tape]) + + if wires >= 13: + assert all(not isinstance(op, qml.GroverOperator) for op in new_tape.operations) + else: + assert tape.operations == [qml.GroverOperator(wires=list(range(wires)))] + + +class TestAngleEmbedding: + """Test the AngleEmbedding algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_angleembedding(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(feature_vector): + qml.AngleEmbedding(features=feature_vector, wires=range(n_qubits), rotation="Z") + return qml.state() + + X = np.random.rand(n_qubits) + + res = qml.QNode(circuit, dev, diff_method=None)(X) + ref = qml.QNode(circuit, dq, diff_method=None)(X) + + assert np.allclose(res, ref) + + +class TestAmplitudeEmbedding: + """Test the AmplitudeEmbedding algorithm.""" + + @pytest.mark.parametrize("first_op", [False, True]) + @pytest.mark.parametrize("n_qubits", range(2, 10, 2)) + def test_amplitudeembedding(self, first_op, n_qubits): + if not first_op: + if device_name != "lightning.qubit": + pytest.xfail( + f"Operation StatePrep cannot be used after other Operations have already been applied on a {device_name} device." + ) + + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(f=None): + if not first_op: + qml.Hadamard(0) + qml.AmplitudeEmbedding(features=f, wires=range(n_qubits)) + return qml.state() + + X = np.random.rand(2**n_qubits) + X /= np.linalg.norm(X) + res = qml.QNode(circuit, dev, diff_method=None)(f=X) + ref = qml.QNode(circuit, dq, diff_method=None)(f=X) + + assert np.allclose(res, ref) + + +class TestBasisEmbedding: + """Test the BasisEmbedding algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_basisembedding(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(feature_vector): + qml.BasisEmbedding(features=feature_vector, wires=range(n_qubits)) + return qml.state() + + X = np.ones(n_qubits) + + res = qml.QNode(circuit, dev, diff_method=None)(X) + ref = qml.QNode(circuit, dq, diff_method=None)(X) + + assert np.allclose(res, ref) + + +class TestDisplacementSqueezingEmbedding: + """Test the DisplacementEmbedding and SqueezingEmbedding algorithms.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + @pytest.mark.parametrize("template", [qml.DisplacementEmbedding, qml.SqueezingEmbedding]) + def test_displacementembedding(self, n_qubits, template): + dev = qml.device(device_name, wires=n_qubits) + + def circuit(feature_vector): + template(features=feature_vector, wires=range(n_qubits)) + qml.QuadraticPhase(0.1, wires=1) + return qml.state() + + X = np.arange(1, n_qubits + 1) + + with pytest.raises(qml._device.DeviceError, match="not supported"): + _ = qml.QNode(circuit, dev, diff_method=None)(X) + + +class TestIQPEmbedding: + """Test the IQPEmbedding algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_iqpembedding(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(feature_vector): + qml.IQPEmbedding(feature_vector, wires=range(n_qubits)) + return [qml.expval(qml.Z(w)) for w in range(n_qubits)] + + X = np.arange(1, n_qubits + 1) + + res = qml.QNode(circuit, dev, diff_method=None)(X) + ref = qml.QNode(circuit, dq, diff_method=None)(X) + + assert np.allclose(res, ref) + + +class TestQAOAEmbedding: + """Test the QAOAEmbedding algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_qaoaembedding(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(feature_vector, weights): + qml.Hadamard(0) + qml.QAOAEmbedding(features=feature_vector, weights=weights, wires=range(n_qubits)) + return qml.state() + + X = np.random.rand(n_qubits) + weights = np.random.rand(2, 3 if n_qubits == 2 else 2 * n_qubits) + + res = qml.QNode(circuit, dev, diff_method=None)(X, weights) + ref = qml.QNode(circuit, dq, diff_method=None)(X, weights) + + assert np.allclose(res, ref) + + +class TestCVNeuralNetLayers: + """Test the CVNeuralNetLayers algorithm.""" + + def test_cvneuralnetlayers(self): + n_qubits = 2 + dev = qml.device(device_name, wires=n_qubits) + + def circuit(weights): + qml.CVNeuralNetLayers(*weights, wires=[0, 1]) + return qml.state() + + shapes = qml.CVNeuralNetLayers.shape(n_layers=2, n_wires=n_qubits) + weights = [np.random.random(shape) for shape in shapes] + + with pytest.raises(qml._device.DeviceError, match="not supported"): + _ = qml.QNode(circuit, dev, diff_method=None)(weights) + + +class TestRandomLayers: + """Test the RandomLayers algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_randomlayers(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit", wires=n_qubits) + + def circuit(weights): + qml.RandomLayers(weights=weights, wires=range(n_qubits)) + return qml.state() + + weights = np.array([[0.1, -2.1, 1.4]]) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestStronglyEntanglingLayers: + """Test the StronglyEntanglingLayers algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_stronglyentanglinglayers(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(weights): + qml.StronglyEntanglingLayers(weights=weights, wires=range(n_qubits)) + return qml.state() + + shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=n_qubits) + weights = np.random.random(size=shape) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestSimplifiedTwoDesign: + """Test the SimplifiedTwoDesign algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_simplifiedtwodesign(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(init_weights, weights): + qml.SimplifiedTwoDesign( + initial_layer_weights=init_weights, weights=weights, wires=range(n_qubits) + ) + return [qml.expval(qml.Z(i)) for i in range(n_qubits)] + + init_weights = np.random.rand(n_qubits) + weights = np.random.rand(2, n_qubits - 1, 2) + + res = qml.QNode(circuit, dev, diff_method=None)(init_weights, weights) + ref = qml.QNode(circuit, dq, diff_method=None)(init_weights, weights) + + assert np.allclose(res, ref) + + +class TestBasicEntanglerLayers: + """Test the BasicEntanglerLayers algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_basicentanglerlayers(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(weights): + qml.BasicEntanglerLayers(weights=weights, wires=range(n_qubits)) + return [qml.expval(qml.Z(i)) for i in range(n_qubits)] + + weights = np.random.rand(1, n_qubits) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestMottonenStatePreparation: + """Test the MottonenStatePreparation algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 6, 2)) + def test_mottonenstatepreparation(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(state): + qml.MottonenStatePreparation(state_vector=state, wires=range(n_qubits)) + return qml.state() + + state = np.random.rand(2**n_qubits) + 1j * np.random.rand(2**n_qubits) + state = state / np.linalg.norm(state) + + res = qml.QNode(circuit, dev, diff_method=None)(state) + ref = qml.QNode(circuit, dq, diff_method=None)(state) + + assert np.allclose(res, ref) + + +class TestArbitraryStatePreparation: + """Test the ArbitraryStatePreparation algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 6, 2)) + def test_arbitrarystatepreparation(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(weights): + qml.ArbitraryStatePreparation(weights, wires=range(n_qubits)) + return qml.state() + + weights = np.random.rand(2 ** (n_qubits + 1) - 2) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestCosineWindow: + """Test the CosineWindow algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 6, 2)) + def test_cosinewindow(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(): + qml.CosineWindow(wires=range(n_qubits)) + return qml.state() + + res = qml.QNode(circuit, dev, diff_method=None)() + ref = qml.QNode(circuit, dq, diff_method=None)() + + assert np.allclose(res, ref) + + +class TestAllSinglesDoubles: + """Test the AllSinglesDoubles algorithm.""" + + def test_allsinglesdoubles(self): + n_qubits = 4 + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + electrons = 2 + + # Define the HF state + hf_state = qml.qchem.hf_state(electrons, n_qubits) + + # Generate all single and double excitations + singles, doubles = qml.qchem.excitations(electrons, n_qubits) + + def circuit(weights, hf_state, singles, doubles): + qml.templates.AllSinglesDoubles(weights, range(n_qubits), hf_state, singles, doubles) + return qml.state() + + weights = np.random.normal(0, np.pi, len(singles) + len(doubles)) + res = qml.QNode(circuit, dev, diff_method=None)(weights, hf_state, singles, doubles) + ref = qml.QNode(circuit, dq, diff_method=None)(weights, hf_state, singles, doubles) + + assert np.allclose(res, ref) + + +class TestBasisRotation: + """Test the BasisRotation algorithm.""" + + def test_basisrotation(self): + n_qubits = 3 + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(unitary_matrix): + qml.BasisState(np.array([1, 1, 0]), wires=[0, 1, 2]) + qml.BasisRotation( + wires=range(3), + unitary_matrix=unitary_matrix, + ) + return qml.state() + + unitary_matrix = np.array( + [ + [0.51378719 + 0.0j, 0.0546265 + 0.79145487j, -0.2051466 + 0.2540723j], + [0.62651582 + 0.0j, -0.00828925 - 0.60570321j, -0.36704948 + 0.32528067j], + [-0.58608928 + 0.0j, 0.03902657 + 0.04633548j, -0.57220635 + 0.57044649j], + ] + ) + + res = qml.QNode(circuit, dev, diff_method=None)(unitary_matrix) + ref = qml.QNode(circuit, dq, diff_method=None)(unitary_matrix) + + assert np.allclose(res, ref) + + +class TestGateFabric: + """Test the GateFabric algorithm.""" + + def test_gatefabric(self): + # Build the electronic Hamiltonian + symbols = ["H", "H"] + coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) + _, n_qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates) + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + # Define the Hartree-Fock state + electrons = 2 + ref_state = qml.qchem.hf_state(electrons, n_qubits) + + def circuit(weights): + qml.GateFabric(weights, wires=[0, 1, 2, 3], init_state=ref_state, include_pi=True) + return qml.state() + + layers = 2 + shape = qml.GateFabric.shape(n_layers=layers, n_wires=n_qubits) + weights = np.random.random(size=shape) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestUCCSD: + """Test the UCCSD algorithm.""" + + def test_uccsd(self): + # Define the molecule + symbols = ["H", "H", "H"] + geometry = np.array( + [ + [0.01076341, 0.04449877, 0.0], + [0.98729513, 1.63059094, 0.0], + [1.87262415, -0.00815842, 0.0], + ], + requires_grad=False, + ) + electrons = 2 + charge = 1 + + # Build the electronic Hamiltonian + _, n_qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, charge=charge) + + # Define the HF state + hf_state = qml.qchem.hf_state(electrons, n_qubits) + + # Generate single and double excitations + singles, doubles = qml.qchem.excitations(electrons, n_qubits) + + # Map excitations to the wires the UCCSD circuit will act on + s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(weights): + qml.UCCSD(weights, range(n_qubits), s_wires, d_wires, hf_state) + return qml.state() + + weights = np.random.random(len(singles) + len(doubles)) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestkUpCCGSD: + """Test the kUpCCGSD algorithm.""" + + def test_kupccgsd(self): + # Define the molecule + symbols = ["H", "H", "H"] + geometry = np.array( + [ + [0.01076341, 0.04449877, 0.0], + [0.98729513, 1.63059094, 0.0], + [1.87262415, -0.00815842, 0.0], + ], + requires_grad=False, + ) + electrons = 2 + charge = 1 + + # Build the electronic Hamiltonian + _, n_qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, charge=charge) + + # Define the HF state + hf_state = qml.qchem.hf_state(electrons, n_qubits) + + # Map excitations to the wires the kUpCCGSD circuit will act on + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(weights): + qml.kUpCCGSD(weights, range(n_qubits), k=1, delta_sz=0, init_state=hf_state) + return qml.state() + + # Get the shape of the weights for this template + layers = 1 + shape = qml.kUpCCGSD.shape(k=layers, n_wires=n_qubits, delta_sz=0) + weights = np.random.random(size=shape) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestParticleConservingU1: + """Test the ParticleConservingU1 algorithm.""" + + def test_particleconservingu1(self): + # Build the electronic Hamiltonian + symbols, coordinates = (["H", "H"], np.array([0.0, 0.0, -0.66140414, 0.0, 0.0, 0.66140414])) + _, n_qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates) + + # Define the Hartree-Fock state + electrons = 2 + hf_state = qml.qchem.hf_state(electrons, n_qubits) + + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + # Define the ansatz + ansatz = functools.partial(qml.ParticleConservingU1, init_state=hf_state, wires=dev.wires) + + # Define the cost function + def circuit(params): + ansatz(params) + return qml.state() + + layers = 2 + shape = qml.ParticleConservingU1.shape(layers, n_qubits) + weights = np.random.random(shape) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestParticleConservingU2: + """Test the ParticleConservingU2 algorithm.""" + + def test_particleconservingu2(self): + # Build the electronic Hamiltonian + symbols, coordinates = (["H", "H"], np.array([0.0, 0.0, -0.66140414, 0.0, 0.0, 0.66140414])) + _, n_qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates) + + # Define the Hartree-Fock state + electrons = 2 + hf_state = qml.qchem.hf_state(electrons, n_qubits) + + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + # Define the ansatz + ansatz = functools.partial(qml.ParticleConservingU2, init_state=hf_state, wires=dev.wires) + + # Define the cost function + def circuit(params): + ansatz(params) + return qml.state() + + layers = 2 + shape = qml.ParticleConservingU2.shape(layers, n_qubits) + weights = np.random.random(shape) + + res = qml.QNode(circuit, dev, diff_method=None)(weights) + ref = qml.QNode(circuit, dq, diff_method=None)(weights) + + assert np.allclose(res, ref) + + +class TestApproxTimeEvolution: + """Test the ApproxTimeEvolution algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_approxtimeevolution(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + coeffs = [1] * n_qubits + obs = [qml.X(i) for i in range(n_qubits)] + hamiltonian = qml.Hamiltonian(coeffs, obs) + + def circuit(time): + qml.ApproxTimeEvolution(hamiltonian, time, 1) + return qml.state() + + res = qml.QNode(circuit, dev, diff_method=None)(1.3) + ref = qml.QNode(circuit, dq, diff_method=None)(1.3) + + assert np.allclose(res, ref) + + +class TestQDrift: + """Test the QDrift algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_qdrift(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit", wires=n_qubits) + + coeffs = [1] * n_qubits + obs = [qml.X(i) for i in range(n_qubits)] + hamiltonian = qml.Hamiltonian(coeffs, obs) + + def circuit(time): + qml.QDrift(hamiltonian, time=time, n=10, seed=10) + return qml.state() + + res = qml.QNode(circuit, dev, diff_method=None)(1.3) + ref = qml.QNode(circuit, dq, diff_method=None)(1.3) + + assert np.allclose(res, ref) + + +class TestTrotterProduct: + """Test the TrotterProduct algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_trotterproduct(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + coeffs = [1] * n_qubits + obs = [qml.X(i) for i in range(n_qubits)] + hamiltonian = qml.Hamiltonian(coeffs, obs) + + def circuit(time): + qml.TrotterProduct(hamiltonian, time=time, order=2) + return qml.state() + + res = qml.QNode(circuit, dev, diff_method=None)(1.3) + ref = qml.QNode(circuit, dq, diff_method=None)(1.3) + + assert np.allclose(res, ref) + + +class TestQuantumPhaseEstimation: + """Test the QuantumPhaseEstimation algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 12, 2)) + def test_quantumphaseestimation(self, n_qubits): + phase = 5 + target_wires = [0] + unitary = qml.RX(phase, wires=0).matrix() + n_estimation_wires = n_qubits - 1 + estimation_wires = range(1, n_estimation_wires + 1) + + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(): + # Start in the |+> eigenstate of the unitary + qml.Hadamard(wires=target_wires) + + qml.QuantumPhaseEstimation( + unitary, + target_wires=target_wires, + estimation_wires=estimation_wires, + ) + + return qml.probs(estimation_wires) + + res = qml.QNode(circuit, dev, diff_method=None)() + ref = qml.QNode(circuit, dq, diff_method=None)() + + assert np.allclose(res, ref) + + +class TestQFT: + """Test the QFT algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_qft(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(basis_state): + qml.BasisState(basis_state, wires=range(n_qubits)) + qml.QFT(wires=range(n_qubits)) + return qml.state() + + basis_state = [0] * n_qubits + basis_state[0] = 1 + res = qml.QNode(circuit, dev, diff_method=None)(basis_state) + ref = qml.QNode(circuit, dq, diff_method=None)(basis_state) + + assert np.allclose(res, ref) + + @pytest.mark.skipif(not LightningDevice._new_API, reason="New API required") + @pytest.mark.parametrize("wires", [5, 9, 10, 13]) + def test_preprocess_qft_decomposition(self, wires): + """Test that qml.QFT is not decomposed for less than 10 wires.""" + tape = qml.tape.QuantumScript( + [qml.QFT(wires=list(range(wires)))], [qml.expval(qml.PauliZ(0))] + ) + dev = LightningDevice(wires=wires) + + program, _ = dev.preprocess() + [new_tape], _ = program([tape]) + + if wires >= 10: + assert all(not isinstance(op, qml.QFT) for op in new_tape.operations) + else: + assert tape.operations == [qml.QFT(wires=list(range(wires)))] + + +class TestAQFT: + """Test the AQFT algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(4, 14, 2)) + def test_aqft(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + + def circuit(basis_state): + qml.BasisState(basis_state, wires=range(n_qubits)) + qml.AQFT(order=1, wires=range(n_qubits)) + return qml.state() + + basis_state = [0] * n_qubits + basis_state[0] = 1 + res = qml.QNode(circuit, dev, diff_method=None)(basis_state) + ref = qml.QNode(circuit, dq, diff_method=None)(basis_state) + + assert np.allclose(res, ref) + + +class TestQSVT: + """Test the QSVT algorithm.""" + + @pytest.mark.parametrize("n_qubits", range(2, 20, 2)) + def test_qsvt(self, n_qubits): + dev = qml.device(device_name, wires=n_qubits) + dq = qml.device("default.qubit") + A = np.array([[0.1]]) + block_encode = qml.BlockEncode(A, wires=[0, 1]) + shifts = [qml.PCPhase(i + 0.1, dim=1, wires=[0, 1]) for i in range(3)] + + def circuit(): + qml.QSVT(block_encode, shifts) + return qml.expval(qml.Z(0)) + + res = qml.QNode(circuit, dev, diff_method=None)() + ref = qml.QNode(circuit, dq, diff_method=None)() + + assert np.allclose(res, ref)