From 9fdd4c46384e12a8f5ef32b37b365e3a961fc56a Mon Sep 17 00:00:00 2001 From: albi3ro Date: Fri, 2 Feb 2024 18:14:44 -0500 Subject: [PATCH 001/105] lightning interface for new device api --- .../lightning_qubit/lightning_qubit2.py | 354 ++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 pennylane_lightning/lightning_qubit/lightning_qubit2.py diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py new file mode 100644 index 0000000000..30cabef7df --- /dev/null +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -0,0 +1,354 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This module contains the LightningQubit2 class that inherits from the new device interface. + +""" +from typing import Union, Sequence, Optional +from dataclasses import replace +import numpy as np + +import pennylane as qml +from pennylane.devices import Device, ExecutionConfig, DefaultExecutionConfig +from pennylane.devices.preprocess import decompose, validate_device_wires, decompose, validate_measurements, validate_observables, no_sampling +from pennylane.devices.qubit.sampling import get_num_shots_and_executions +from pennylane.tape import QuantumScript +from pennylane.transforms.core import TransformProgram +from pennylane.typing import Result, ResultBatch + + +def dummy_simulate(circuit, rng=None, c_dtype=np.complex128, batch_obs=False, mcmc=False, + kernel_name="Local", num_burnin=100): + return tuple(0.0 for _ in circuit.measurements) + +def dummy_jacobian(circuit): + return np.array(0.0) + +def dummy_simulate_and_jacobian(circuit): + return np.array(0.0), np.array(0.0) + +Result_or_ResultBatch = Union[Result, ResultBatch] +QuantumTapeBatch = Sequence[qml.tape.QuantumTape] +QuantumTape_or_Batch = Union[qml.tape.QuantumTape, QuantumTapeBatch] + + +_operations = frozenset({ + "Identity", + "BasisState", + "QubitStateVector", + "StatePrep", + "QubitUnitary", + "ControlledQubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + "PauliX", + "PauliY", + "PauliZ", + "MultiRZ", + "Hadamard", + "S", + "Adjoint(S)", + "T", + "Adjoint(T)", + "SX", + "Adjoint(SX)", + "CNOT", + "SWAP", + "ISWAP", + "PSWAP", + "Adjoint(ISWAP)", + "SISWAP", + "Adjoint(SISWAP)", + "SQISW", + "CSWAP", + "Toffoli", + "CY", + "CZ", + "PhaseShift", + "ControlledPhaseShift", + "CPhase", + "RX", + "RY", + "RZ", + "Rot", + "CRX", + "CRY", + "CRZ", + "C(PauliX)", + "C(PauliY)", + "C(PauliZ)", + "C(Hadamard)", + "C(S)", + "C(T)", + "C(PhaseShift)", + "C(RX)", + "C(RY)", + "C(RZ)", + "C(SWAP)", + "C(IsingXX)", + "C(IsingXY)", + "C(IsingYY)", + "C(IsingZZ)", + "C(SingleExcitation)", + "C(SingleExcitationMinus)", + "C(SingleExcitationPlus)", + "C(DoubleExcitation)", + "C(DoubleExcitationMinus)", + "C(DoubleExcitationPlus)", + "CRot", + "IsingXX", + "IsingYY", + "IsingZZ", + "IsingXY", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + "DoubleExcitation", + "DoubleExcitationPlus", + "DoubleExcitationMinus", + "QubitCarry", + "QubitSum", + "OrbitalRotation", + "QFT", + "ECR", + "BlockEncode", +}) + +_observables = frozenset({ + "PauliX", + "PauliY", + "PauliZ", + "Hadamard", + "Hermitian", + "Identity", + "Projector", + "SparseHamiltonian", + "Hamiltonian", + "Sum", + "SProd", + "Prod", + "Exp", +}) + +def stopping_condition(op: qml.operation.Operator) -> bool: + return op.name in _operations + +def accepted_observables(obs: qml.operation.Operator) -> bool: + return obs.name in _observables + +def accepted_measurements(m : qml.measurements.MeasurementProcess) -> bool: + return isinstance(m, (qml.measurements.ExpectationMP)) + +class LightningQubit2(Device): + """PennyLane Lightning Qubit device. + + A device that interfaces with C++ to perform fast linear algebra calculations. + + Use of this device requires pre-built binaries or compilation from source. Check out the + :doc:`/lightning_qubit/installation` guide for more details. + + Args: + wires (int): the number of wires to initialize the device with + c_dtype: Datatypes for statevector representation. Must be one of + ``np.complex64`` or ``np.complex128``. + shots (int): How many times the circuit should be evaluated (or sampled) to estimate + the expectation values. Defaults to ``None`` if not specified. Setting + to ``None`` results in computing statistics like expectation values and + variances analytically. + seed (str, int, rng) + mcmc (bool): Determine whether to use the approximate Markov Chain Monte Carlo + sampling method when generating samples. + kernel_name (str): name of transition kernel. The current version supports + two kernels: ``"Local"`` and ``"NonZeroRandom"``. + The local kernel conducts a bit-flip local transition between states. + The local kernel generates a random qubit site and then generates a random + number to determine the new bit at that qubit site. The ``"NonZeroRandom"`` kernel + randomly transits between states that have nonzero probability. + num_burnin (int): number of steps that will be dropped. Increasing this value will + result in a closer approximation but increased runtime. + batch_obs (bool): Determine whether we process observables in parallel when + computing the jacobian. This value is only relevant when the lightning + qubit is built with OpenMP. + """ + + name = 'lightning.qubit2' + + _device_options = ["rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin"] + + def __init__( # pylint: disable=too-many-arguments + self, + wires=None, + *, + c_dtype=np.complex128, + shots=None, + seed="global", + mcmc=False, + kernel_name="Local", + num_burnin=100, + batch_obs=False, + ): + super().__init__(wires=wires, shots=shots) + seed = np.random.randint(0, high=10000000) if seed == "global" else seed + self._rng = np.random.default_rng(seed) + + self._c_dtype = c_dtype + self._batch_obs = batch_obs + self._mcmc = mcmc + if self._mcmc: + if kernel_name not in [ + "Local", + "NonZeroRandom", + ]: + raise NotImplementedError( + f"The {kernel_name} is not supported and currently " + "only 'Local' and 'NonZeroRandom' kernels are supported." + ) + if num_burnin >= shots: + raise ValueError("Shots should be greater than num_burnin.") + self._kernel_name = kernel_name + self._num_burnin = num_burnin + else: + self._kernel_name = None + self._num_burnin = None + + @property + def operation(self) -> frozenset[str]: + """The names of supported operations. + """ + return _operations + + @property + def observables(self) -> frozenset[str]: + """The names of supported observables. + """ + return _observables + + + def _setup_execution_config(self, config): + updated_values = {} + if config.gradient_method == "best": + updated_values["gradient_method"] = "adjoint" + if config.use_device_gradient is None: + updated_values["use_device_gradient"] = config.gradient_method in ("best", "adjoint") + if config.grad_on_execution is None: + updated_values["grad_on_execution"] = True + + new_device_options = dict(config.device_options) + for option in self._device_options: + if option not in new_device_options: + new_device_options[option] = getattr(self, f"_{option}", None) + + return replace(config, **updated_values, device_options=new_device_options) + + + def supports_derivatives( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[qml.tape.QuantumTape] = None, + ) -> bool: + if execution_config is None and circuit is None: + return True + if execution_config.gradient_method not in {"adjoint", "best"}: + return False + if circuit is None: + return True + return all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) and not circuit.shots + + + def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): + program = TransformProgram() + program.add_transform(validate_measurements, analytic_measurements=accepted_measurements, name=self.name) + program.add_transform(no_sampling) + program.add_transform(validate_observables, accepted_observables, name=self.name) + program.add_transform(validate_device_wires, self.wires, name=self.name) + program.add_transform(qml.defer_measurements, device=self) + program.add_transform(decompose, stopping_condition=stopping_condition, name=self.name) + program.add_transform(qml.transforms.broadcast_expand) + return program, self._setup_execution_config(execution_config) + + def _execute_tracking(self, circuits): + self.tracker.update(batches=1) + self.tracker.record() + for c in circuits: + qpu_executions, shots = get_num_shots_and_executions(c) + if c.shots: + self.tracker.update( + simulations=1, + executions=qpu_executions, + shots=shots, + ) + else: + self.tracker.update( + simulations=1, + executions=qpu_executions, + ) + self.tracker.record() + + def execute(self, circuits : QuantumTape_or_Batch, execution_config: ExecutionConfig = DefaultExecutionConfig) -> Result_or_ResultBatch: + is_single_circuit = False + if isinstance(circuits, QuantumScript): + is_single_circuit = True + circuits = (circuits,) + + if self.tracker.active: + self._execute_tracking(circuits) + + results = [] + for circuit in circuits: + results.append(dummy_simulate(circuit, **execution_config.device_options)) + + return results[0] if is_single_circuit else tuple(results) + + + def compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + is_single_circuit = False + if isinstance(circuits, QuantumScript): + is_single_circuit = True + circuits = [circuits] + + if self.tracker.active: + self.tracker.update(derivative_batches=1, derivatives=len(circuits)) + self.tracker.record() + res = tuple(dummy_jacobian(circuit) for circuit in circuits) + + return res[0] if is_single_circuit else res + + + def execute_and_compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + is_single_circuit = False + if isinstance(circuits, QuantumScript): + is_single_circuit = True + circuits = [circuits] + + if self.tracker.active: + for c in circuits: + self.tracker.update(resources=c.specs["resources"]) + self.tracker.update( + execute_and_derivative_batches=1, + executions=len(circuits), + derivatives=len(circuits), + ) + self.tracker.record() + + results = tuple(dummy_simulate_and_jacobian(c) for c in circuits) + results, jacs = tuple(zip(*results)) + return (results[0], jacs[0]) if is_single_circuit else (results, jacs) \ No newline at end of file From 50c3bae4b5d1a67bb97235684e8f2bf8057b6acf Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 2 Feb 2024 23:23:35 +0000 Subject: [PATCH 002/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 2501bfbac7..87a919ae46 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.35.0-dev11" +__version__ = "0.35.0-dev12" From 6ac4b89d5cf7021b1dcf0bb759eee24d21864e39 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 08:59:23 -0500 Subject: [PATCH 003/105] Merge Master --- .github/CHANGELOG.md | 9 + .../workflows/compat-check-latest-latest.yml | 2 +- .../workflows/compat-check-latest-stable.yml | 2 +- .../compat-check-release-release.yml | 2 +- .../workflows/compat-check-stable-latest.yml | 2 +- .../workflows/compat-check-stable-stable.yml | 2 +- ...{tests_gpu_cu11.yml => tests_gpu_cuda.yml} | 17 +- .github/workflows/tests_linux_x86_mpi_gpu.yml | 37 ++-- .github/workflows/tests_windows.yml | 205 ++++++++++-------- ...4_cu11.yml => wheel_linux_x86_64_cuda.yml} | 29 ++- README.rst | 8 +- bin/auditwheel | 20 +- doc/requirements.txt | 2 +- pennylane_lightning/core/lightning_base.py | 14 +- .../lightning_gpu/lightning_gpu.py | 2 +- requirements-dev.txt | 2 +- tests/test_adjoint_jacobian.py | 1 + tests/test_decomposition.py | 6 +- 18 files changed, 211 insertions(+), 151 deletions(-) rename .github/workflows/{tests_gpu_cu11.yml => tests_gpu_cuda.yml} (96%) rename .github/workflows/{wheel_linux_x86_64_cu11.yml => wheel_linux_x86_64_cuda.yml} (76%) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index be57b94646..bc36e8a477 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -7,8 +7,14 @@ ### Breaking changes +* Migrate `lightning.gpu` to CUDA 12. + [(#606)](https://github.com/PennyLaneAI/pennylane-lightning/pull/606) + ### Improvements +* Lower the overheads of Windows CI tests. + [(#610)](https://github.com/PennyLaneAI/pennylane-lightning/pull/610) + * Decouple LightningQubit memory ownership from numpy and migrate it to Lightning-Qubit managed state-vector class. [(#601)](https://github.com/PennyLaneAI/pennylane-lightning/pull/601) @@ -40,6 +46,9 @@ ### Bug fixes +* Ensure the stopping condition decompositions are respected for larger templated QFT and Grover operators. + [(#609)](https://github.com/PennyLaneAI/pennylane-lightning/pull/609) + * Move concurrency group specifications from reusable Docker build workflow to the root workflows. [(#604)](https://github.com/PennyLaneAI/pennylane-lightning/pull/604) diff --git a/.github/workflows/compat-check-latest-latest.yml b/.github/workflows/compat-check-latest-latest.yml index 16a11078b1..63533e089d 100644 --- a/.github/workflows/compat-check-latest-latest.yml +++ b/.github/workflows/compat-check-latest-latest.yml @@ -20,7 +20,7 @@ jobs: pennylane-version: latest tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - latest/latest - uses: ./.github/workflows/tests_gpu_cu11.yml + uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: latest pennylane-version: latest diff --git a/.github/workflows/compat-check-latest-stable.yml b/.github/workflows/compat-check-latest-stable.yml index d370a1240e..3e589283eb 100644 --- a/.github/workflows/compat-check-latest-stable.yml +++ b/.github/workflows/compat-check-latest-stable.yml @@ -20,7 +20,7 @@ jobs: pennylane-version: stable tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - latest/stable - uses: ./.github/workflows/tests_gpu_cu11.yml + uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: latest pennylane-version: stable diff --git a/.github/workflows/compat-check-release-release.yml b/.github/workflows/compat-check-release-release.yml index 9c179708e9..52c6bea41c 100644 --- a/.github/workflows/compat-check-release-release.yml +++ b/.github/workflows/compat-check-release-release.yml @@ -20,7 +20,7 @@ jobs: pennylane-version: release tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - release/release - uses: ./.github/workflows/tests_gpu_cu11.yml + uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: release pennylane-version: release diff --git a/.github/workflows/compat-check-stable-latest.yml b/.github/workflows/compat-check-stable-latest.yml index 65eceb4aed..f7e125ca69 100644 --- a/.github/workflows/compat-check-stable-latest.yml +++ b/.github/workflows/compat-check-stable-latest.yml @@ -20,7 +20,7 @@ jobs: pennylane-version: latest tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - stable/latest - uses: ./.github/workflows/tests_gpu_cu11.yml + uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: stable pennylane-version: latest diff --git a/.github/workflows/compat-check-stable-stable.yml b/.github/workflows/compat-check-stable-stable.yml index 4bcba8fcbb..8f33068c39 100644 --- a/.github/workflows/compat-check-stable-stable.yml +++ b/.github/workflows/compat-check-stable-stable.yml @@ -20,7 +20,7 @@ jobs: pennylane-version: stable tests_lgpu_gpu: name: Lightning Compatibility test (tests_lgpu_gpu) - stable/stable - uses: ./.github/workflows/tests_gpu_cu11.yml + uses: ./.github/workflows/tests_gpu_cuda.yml with: lightning-version: stable pennylane-version: stable diff --git a/.github/workflows/tests_gpu_cu11.yml b/.github/workflows/tests_gpu_cuda.yml similarity index 96% rename from .github/workflows/tests_gpu_cu11.yml rename to .github/workflows/tests_gpu_cuda.yml index 12d4c26786..06741a1152 100644 --- a/.github/workflows/tests_gpu_cu11.yml +++ b/.github/workflows/tests_gpu_cuda.yml @@ -39,25 +39,27 @@ jobs: matrix: os: [ubuntu-22.04] pl_backend: ["lightning_gpu"] + cuda_version: ["12"] steps: - name: Validate GPU version and installed compiler run: | source /etc/profile.d/modules.sh module use /opt/modules - module load cuda/11.8 + module load cuda/${{ matrix.cuda_version }} echo "${PATH}" >> $GITHUB_PATH echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV nvcc --version nvidia-smi - cpptestswithLGPU_cu11: + cpptestswithLGPU: if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} needs: [builddeps] strategy: matrix: os: [ubuntu-22.04] pl_backend: ["lightning_gpu"] + cuda_version: ["12"] name: C++ tests (Lightning-GPU) runs-on: @@ -70,7 +72,7 @@ jobs: run: | source /etc/profile.d/modules.sh module use /opt/modules - module load cuda/11.8 + module load cuda/${{ matrix.cuda_version }} echo "${PATH}" >> $GITHUB_PATH echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV nvcc --version @@ -117,7 +119,7 @@ jobs: - name: Install required packages run: | - python -m pip install ninja cmake custatevec-cu11 + python -m pip install ninja cmake custatevec-cu${{ matrix.cuda_version }} sudo apt-get -y -q install liblapack-dev - name: Build and run unit tests @@ -161,6 +163,7 @@ jobs: os: [ubuntu-22.04] pl_backend: ["lightning_gpu"] default_backend: ["lightning_qubit"] + cuda_version: ["12"] name: Python tests with LGPU runs-on: @@ -173,7 +176,7 @@ jobs: run: | source /etc/profile.d/modules.sh module use /opt/modules - module load cuda/11.8 + module load cuda/${{ matrix.cuda_version }} echo "${PATH}" >> $GITHUB_PATH echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV nvcc --version @@ -238,7 +241,7 @@ jobs: run: | cd main python -m pip install -r requirements-dev.txt - python -m pip install cmake custatevec-cu11 openfermionpyscf + python -m pip install cmake custatevec-cu${{ matrix.cuda_version }} openfermionpyscf - name: Checkout PennyLane for release build if: inputs.pennylane-version == 'release' @@ -344,7 +347,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} upload-to-codecov-linux-cpp: - needs: [cpptestswithLGPU_cu11] + needs: [cpptestswithLGPU] name: Upload coverage data to codecov runs-on: ubuntu-latest steps: diff --git a/.github/workflows/tests_linux_x86_mpi_gpu.yml b/.github/workflows/tests_linux_x86_mpi_gpu.yml index 648e68bdb3..7af97b5046 100644 --- a/.github/workflows/tests_linux_x86_mpi_gpu.yml +++ b/.github/workflows/tests_linux_x86_mpi_gpu.yml @@ -39,6 +39,8 @@ jobs: max-parallel: 1 matrix: mpilib: ["mpich", "openmpi"] + cuda_version_maj: ["12"] + cuda_version_min: ["2"] timeout-minutes: 30 steps: @@ -92,28 +94,29 @@ jobs: - name: Install required packages run: | python -m pip install -r requirements-dev.txt - python -m pip install cmake custatevec-cu11 + python -m pip install cmake custatevec-cu12 sudo apt-get -y -q install liblapack-dev - - name: Validate GPU version and installed compiler + - name: Validate GPU version and installed compiler and modules run: | - source /etc/profile.d/modules.sh && module use /opt/modules && module load cuda/11.8 + source /etc/profile.d/modules.sh && module use /opt/modules && module load cuda/${{ matrix.cuda_version_maj }} which -a nvcc nvcc --version + ls -R /opt/modules - name: Validate Multi-GPU packages run: | - source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }} + source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} echo 'Checking for ${{ matrix.mpilib }}' which -a mpirun mpirun --version which -a mpicxx mpicxx --version - module unload ${{ matrix.mpilib }} + module unload ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} - name: Build and run unit tests run: | - source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }} + source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} export CUQUANTUM_SDK=$(python -c "import site; print( f'{site.getsitepackages()[0]}/cuquantum/lib')") cmake . -BBuild \ -DPL_BACKEND=lightning_gpu \ @@ -123,7 +126,7 @@ jobs: -DBUILD_TESTS=ON \ -DENABLE_LAPACK=ON \ -DCMAKE_CXX_COMPILER=mpicxx \ - -DCMAKE_CUDA_COMPILER="/usr/local/cuda/bin/nvcc" \ + -DCMAKE_CUDA_COMPILER=$(which nvcc) \ -DCMAKE_CUDA_ARCHITECTURES="86" \ -DPython_EXECUTABLE:FILE="${{ steps.python_path.outputs.python }}" \ -G Ninja @@ -131,7 +134,7 @@ jobs: cd ./Build mkdir -p ./tests/results for file in *runner ; do ./$file --order lex --reporter junit --out ./tests/results/report_$file.xml; done; - for file in *runner_mpi ; do /opt/mpi/${{ matrix.mpilib }}/bin/mpirun -np 2 ./$file --order lex --reporter junit --out ./tests/results/report_$file.xml; done; + for file in *runner_mpi ; do mpirun -np 2 ./$file --order lex --reporter junit --out ./tests/results/report_$file.xml; done; lcov --directory . -b ../pennylane_lightning/src --capture --output-file coverage.info lcov --remove coverage.info '/usr/*' --output-file coverage.info mv coverage.info coverage-${{ github.job }}-lightning_gpu_${{ matrix.mpilib }}.info @@ -171,6 +174,8 @@ jobs: max-parallel: 1 matrix: mpilib: ["mpich", "openmpi"] + cuda_version_maj: ["12"] + cuda_version_min: ["2"] timeout-minutes: 30 steps: @@ -232,9 +237,9 @@ jobs: - name: Install required packages run: | - source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }} + source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} python -m pip install -r requirements-dev.txt - python -m pip install custatevec-cu11 mpi4py openfermionpyscf + python -m pip install custatevec-cu${{ matrix.cuda_version_maj }} mpi4py openfermionpyscf SKIP_COMPILATION=True PL_BACKEND=lightning_qubit python -m pip install -e . -vv - name: Checkout PennyLane for release build @@ -256,25 +261,25 @@ jobs: env: CUQUANTUM_SDK: $(python -c "import site; print( f'{site.getsitepackages()[0]}/cuquantum/lib')") run: | - source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }} - CMAKE_ARGS="-DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DENABLE_MPI=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -DCMAKE_CUDA_ARCHITECTURES=${{ env.CI_CUDA_ARCH }} -DPython_EXECUTABLE=${{ steps.python_path.outputs.python }}" \ + source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} + CMAKE_ARGS="-DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DENABLE_MPI=ON -DCMAKE_CUDA_COMPILER=$(which nvcc) -DCMAKE_CUDA_ARCHITECTURES=${{ env.CI_CUDA_ARCH }} -DPython_EXECUTABLE=${{ steps.python_path.outputs.python }}" \ PL_BACKEND=lightning_gpu python -m pip install -e . --verbose # There are issues running py-cov with MPI. A solution is to use coverage as reported # [here](https://github.com/pytest-dev/pytest-cov/issues/237#issuecomment-544824228) - name: Run unit tests for MPI-enabled lightning.gpu device run: | - source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }} - PL_DEVICE=lightning.gpu /opt/mpi/${{ matrix.mpilib }}/bin/mpirun -np 2 \ + source /etc/profile.d/modules.sh && module use /opt/modules/ && module load ${{ matrix.mpilib }}/cuda-${{ matrix.cuda_version_maj }}.${{ matrix.cuda_version_min }} + PL_DEVICE=lightning.gpu mpirun -np 2 \ coverage run --rcfile=.coveragerc --source=pennylane_lightning -p -m mpi4py -m pytest ./mpitests --tb=native coverage combine - coverage xml -o coverage-${{ github.job }}-lightning_gpu_${{ matrix.mpilib }}-main.xml + coverage xml -o coverage-${{ github.job }}-lightning_gpu_${{ matrix.mpilib }}_cu${{ matrix.cuda_version_maj }}-main.xml - name: Upload code coverage results uses: actions/upload-artifact@v3 with: name: ubuntu-codecov-results-python - path: coverage-${{ github.job }}-lightning_gpu_${{ matrix.mpilib }}-*.xml + path: coverage-${{ github.job }}-lightning_gpu_${{ matrix.mpilib }}_cu${{ matrix.cuda_version_maj }}-*.xml if-no-files-found: error - name: Cleanup diff --git a/.github/workflows/tests_windows.yml b/.github/workflows/tests_windows.yml index 1d56af45b5..395d4a8bdd 100644 --- a/.github/workflows/tests_windows.yml +++ b/.github/workflows/tests_windows.yml @@ -20,76 +20,6 @@ concurrency: cancel-in-progress: true jobs: - cpptests: - if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} - timeout-minutes: 45 - name: C++ tests (Windows) - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest] - pl_backend: ["lightning_qubit"] - steps: - - name: Checkout PennyLane-Lightning - uses: actions/checkout@v3 - - - name: Configure MSVC for amd64 # Use cl.exe as a default compiler - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: amd64 - - - name: Install lapack with vcpkg - run: | - git clone --quiet --recursive https://github.com/microsoft/vcpkg.git - cd vcpkg - .\bootstrap-vcpkg.bat - vcpkg install lapack - - - name: Setup OpenCppCoverage and add to PATH - run: | - choco install OpenCppCoverage -y - echo "C:\Program Files\OpenCppCoverage" >> $env:GITHUB_PATH - - - name: Build and run unit tests for code coverage - run: | - cmake -BBuild ` - -DBUILD_TESTS=ON ` - -DENABLE_OPENMP=OFF ` - -DENABLE_PYTHON=OFF ` - -DENABLE_GATE_DISPATCHER=OFF ` - -DPL_BACKEND=${{ matrix.pl_backend }} ` - -DENABLE_LAPACK=ON ` - -DCMAKE_TOOLCHAIN_FILE=D:/a/pennylane-lightning/pennylane-lightning/vcpkg/scripts/buildsystems/vcpkg.cmake ` - -DENABLE_WARNINGS=OFF - cmake --build .\Build --config Debug - mkdir -p .\Build\tests\results - $test_bins = Get-ChildItem -Include *.exe -Recurse -Path ./Build/Debug - foreach ($file in $test_bins) - { - $filename = $file.ToString() -replace '.{4}$' - $filename = $filename.Substring($filename.LastIndexOf("\")+1) - $test_call = $file.ToString() + " --order lex --reporter junit --out .\Build\tests\results\report_" + $filename + ".xml" - Invoke-Expression $test_call - $cov_call = "OpenCppCoverage --sources pennylane_lightning\core\src --export_type cobertura:coverage.xml " + $file.ToString() - Invoke-Expression $cov_call - } - Move-Item -Path .\coverage.xml -Destination .\coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml - - - name: Upload test results - uses: actions/upload-artifact@v3 - if: always() - with: - name: windows-test-report-${{ github.job }}-${{ matrix.pl_backend }} - path: .\Build\tests\results\ - if-no-files-found: error - - - name: Upload coverage results - uses: actions/upload-artifact@v3 - with: - name: windows-coverage-report - path: .\coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml - if-no-files-found: error - win-set-matrix-x86: name: Set builder matrix runs-on: ubuntu-latest @@ -110,7 +40,7 @@ jobs: exec_model: ${{ steps.exec_model.outputs.exec_model }} kokkos_version: ${{ steps.kokkos_version.outputs.kokkos_version }} - build_dependencies: + build_dependencies_kokkos: needs: [win-set-matrix-x86] strategy: fail-fast: false @@ -125,7 +55,7 @@ jobs: steps: - name: Cache installation directories id: kokkos-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: D:\a\install_dir\${{ matrix.exec_model }} key: ${{ matrix.os }}-kokkos${{ matrix.kokkos_version }}-${{ matrix.exec_model }}-Debug @@ -163,9 +93,111 @@ jobs: cmake --build ./Build --config Debug --verbose cmake --install ./Build --config Debug --verbose + build_dependencies_vcpkg: + strategy: + fail-fast: false + matrix: + os: [windows-latest] + timeout-minutes: 30 + name: vcpkg dependencies + runs-on: ${{ matrix.os }} + + steps: + - name: Cache installation directories + id: vcpkg-cache + uses: actions/cache@v4 + with: + path: D:\a\install_dir\vcpkg + key: ${{ matrix.os }}-vcpkg + + - name: Clone vcpkg + if: steps.vcpkg-cache.outputs.cache-hit != 'true' + run: | + mkdir -p D:\a\install_dir + cd D:\a\install_dir\ + git clone --quiet --recursive https://github.com/microsoft/vcpkg.git + + - name: Install dependencies + if: steps.vcpkg-cache.outputs.cache-hit != 'true' + run: | + python -m pip install cmake build ninja + + - name: Build Lapack library + if: steps.vcpkg-cache.outputs.cache-hit != 'true' + run: | + cd D:\a\install_dir\vcpkg + .\bootstrap-vcpkg.bat + vcpkg install lapack + + cpptests: + needs: [build_dependencies_vcpkg] + if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} + timeout-minutes: 30 + name: C++ tests (Windows) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest] + pl_backend: ["lightning_qubit"] + steps: + - name: Checkout PennyLane-Lightning + uses: actions/checkout@v3 + + - name: Restoring cached vcpkg + id: vcpkg-cache + uses: actions/cache@v4 + with: + path: D:\a\install_dir\vcpkg + key: ${{ matrix.os }}-vcpkg + + - name: Setup OpenCppCoverage and add to PATH + run: | + choco install OpenCppCoverage -y + echo "C:\Program Files\OpenCppCoverage" >> $env:GITHUB_PATH + + - name: Build and run unit tests for code coverage + run: | + cmake -BBuild ` + -DBUILD_TESTS=ON ` + -DENABLE_OPENMP=OFF ` + -DENABLE_PYTHON=OFF ` + -DENABLE_GATE_DISPATCHER=OFF ` + -DPL_BACKEND=${{ matrix.pl_backend }} ` + -DENABLE_LAPACK=ON ` + -DCMAKE_TOOLCHAIN_FILE=D:\a\install_dir\vcpkg\scripts\buildsystems\vcpkg.cmake ` + -DENABLE_WARNINGS=OFF + cmake --build .\Build --config RelWithDebInfo + mkdir -p .\Build\tests\results + $test_bins = Get-ChildItem -Include *.exe -Recurse -Path ./Build/RelWithDebInfo + foreach ($file in $test_bins) + { + $filename = $file.ToString() -replace '.{4}$' + $filename = $filename.Substring($filename.LastIndexOf("\")+1) + $test_call = $file.ToString() + " --order lex --reporter junit --out .\Build\tests\results\report_" + $filename + ".xml" + Invoke-Expression $test_call + $cov_call = "OpenCppCoverage --sources pennylane_lightning\core\src --excluded_modules D:\a\install_dir\* --excluded_modules C:\Windows\System32\* --export_type cobertura:coverage.xml " + $file.ToString() + Invoke-Expression $cov_call + } + Move-Item -Path .\coverage.xml -Destination .\coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml + + - name: Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: windows-test-report-${{ github.job }}-${{ matrix.pl_backend }} + path: .\Build\tests\results\ + if-no-files-found: error + + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: windows-coverage-report + path: .\coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml + if-no-files-found: error + cpptestswithkokkos: if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} - needs: [build_dependencies, win-set-matrix-x86] + needs: [build_dependencies_kokkos, build_dependencies_vcpkg, win-set-matrix-x86] strategy: matrix: os: [windows-latest] @@ -180,32 +212,27 @@ jobs: steps: - name: Restoring cached Kokkos id: kokkos-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: D:\a\install_dir\${{ matrix.exec_model }} key: ${{ matrix.os }}-kokkos${{ matrix.kokkos_version }}-${{ matrix.exec_model }}-Debug + + - name: Restoring cached vcpkg + id: vcpkg-cache + uses: actions/cache@v4 + with: + path: D:\a\install_dir\vcpkg + key: ${{ matrix.os }}-vcpkg - name: Checkout PennyLane-Lightning uses: actions/checkout@v3 - - name: Copy cached libraries + - name: Copy cached Kokkos libraries if: steps.kokkos-cache.outputs.cache-hit == 'true' run: | Copy-Item -Path "D:\a\install_dir\${{ matrix.exec_model }}\" ` -Destination "D:\a\pennylane-lightning\pennylane-lightning\Kokkos" -Recurse -Force - - name: Configure MSVC for amd64 # Use cl.exe as a default compiler - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: amd64 - - - name: Install lapack with vcpkg - run: | - git clone --quiet --recursive https://github.com/microsoft/vcpkg.git - cd vcpkg - .\bootstrap-vcpkg.bat - vcpkg install lapack - - name: Enable long paths run: | powershell.exe New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force @@ -224,7 +251,7 @@ jobs: -DENABLE_PYTHON=OFF ` -DENABLE_GATE_DISPATCHER=OFF ` -DCMAKE_PREFIX_PATH=D:\a\pennylane-lightning\pennylane-lightning\Kokkos ` - -DCMAKE_TOOLCHAIN_FILE=D:/a/pennylane-lightning/pennylane-lightning/vcpkg/scripts/buildsystems/vcpkg.cmake ` + -DCMAKE_TOOLCHAIN_FILE=D:\a\install_dir\vcpkg\scripts\buildsystems\vcpkg.cmake ` -DENABLE_OPENMP=OFF ` -DPL_BACKEND=${{ matrix.pl_backend }} ` -DENABLE_LAPACK=ON ` @@ -238,7 +265,7 @@ jobs: $filename = $filename.Substring($filename.LastIndexOf("\")+1) $test_call = $file.ToString() + " --order lex --reporter junit --out .\Build\tests\results\report_" + $filename + ".xml" Invoke-Expression $test_call - $cov_call = "OpenCppCoverage --sources pennylane_lightning\core\src --export_type cobertura:coverage.xml " + $file.ToString() + $cov_call = "OpenCppCoverage --sources pennylane_lightning\core\src --excluded_modules D:\a\install_dir\* --excluded_modules C:\Windows\System32\* --export_type cobertura:coverage.xml " + $file.ToString() Invoke-Expression $cov_call } Move-Item -Path .\coverage.xml -Destination .\coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml diff --git a/.github/workflows/wheel_linux_x86_64_cu11.yml b/.github/workflows/wheel_linux_x86_64_cuda.yml similarity index 76% rename from .github/workflows/wheel_linux_x86_64_cu11.yml rename to .github/workflows/wheel_linux_x86_64_cuda.yml index 33a5d6f8b3..27a1841a4e 100644 --- a/.github/workflows/wheel_linux_x86_64_cu11.yml +++ b/.github/workflows/wheel_linux_x86_64_cuda.yml @@ -1,4 +1,4 @@ -name: Wheel::Linux::x86_64::CUDA-11 +name: Wheel::Linux::x86_64::CUDA # **What it does**: Builds python wheels for Linux (ubuntu-latest) architecture x86_64 and store it as artifacts. # Python versions: 3.9, 3.10, 3.11, 3.12. @@ -6,9 +6,7 @@ name: Wheel::Linux::x86_64::CUDA-11 # **Who does it impact**: Wheels to be uploaded to PyPI. env: - GCC_VERSION: 11 - CUDA_VERSION_MAJOR: 11 - CUDA_VERSION_MINOR: 5 + GCC_VERSION: "11" on: pull_request: @@ -19,7 +17,7 @@ on: types: [published] concurrency: - group: wheel_linux_x86_64_cu11-${{ github.ref }} + group: wheel_linux_x86_64_cu12-${{ github.ref }} cancel-in-progress: true jobs: @@ -37,15 +35,15 @@ jobs: os: [ubuntu-latest] arch: [x86_64] pl_backend: ["lightning_gpu"] + cuda_version: ["12"] cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }} container_img: ["quay.io/pypa/manylinux2014_x86_64"] timeout-minutes: 30 - name: ${{ matrix.os }}::${{ matrix.arch }} - ${{ matrix.pl_backend }} (Python ${{ fromJson('{ "cp39-*":"3.9","cp310-*":"3.10","cp311-*":"3.11","cp312-*":"3.12" }')[matrix.cibw_build] }}) + name: ${{ matrix.os }}::${{ matrix.arch }} - ${{ matrix.pl_backend }} CUDA ${{ matrix.cuda_version }} (Python ${{ fromJson('{ "cp39-*":"3.9","cp310-*":"3.10","cp311-*":"3.11","cp312-*":"3.12" }')[matrix.cibw_build] }}) runs-on: ${{ matrix.os }} container: ${{ matrix.container_img }} steps: - - name: Checkout PennyLane-Lightning uses: actions/checkout@v3 @@ -67,24 +65,25 @@ jobs: CIBW_SKIP: "*-musllinux*" - CIBW_CONFIG_SETTINGS: --global-option=build_ext --global-option=--define="CMAKE_CXX_COMPILER=$(which g++-11);CMAKE_C_COMPILER=$(which gcc-11);LIGHTNING_RELEASE_TAG=master" + CIBW_CONFIG_SETTINGS: --global-option=build_ext --global-option=--define="CMAKE_CXX_COMPILER=$(which g++);CMAKE_C_COMPILER=$(which gcc);LIGHTNING_RELEASE_TAG=master" # Python build settings CIBW_BEFORE_BUILD: | cat /etc/yum.conf | sed "s/\[main\]/\[main\]\ntimeout=5/g" > /etc/yum.conf - python -m pip install ninja cmake~=3.24.3 auditwheel custatevec-cu11 + python -m pip install ninja cmake~=3.24.3 auditwheel custatevec-cu${{ matrix.cuda_version }} yum clean all -y yum install centos-release-scl-rh -y yum install devtoolset-11-gcc-c++ -y source /opt/rh/devtoolset-11/enable -y yum-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/cuda-rhel7.repo -y - yum -y install cuda-11-5 git openssh wget + yum -y install cuda-${{ matrix.cuda_version }}-0 git openssh wget # ensure nvcc is available CIBW_ENVIRONMENT: | - PATH=$PATH:/usr/local/cuda/bin \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 \ - PL_BACKEND="${{ matrix.pl_backend }}" + PATH=/opt/rh/devtoolset-11/root/usr/bin:$PATH:/usr/local/cuda-${{ matrix.cuda_version }}/bin \ + LD_LIBRARY_PATH=/opt/rh/devtoolset-11/root/usr/lib64:/opt/rh/devtoolset-11/root/usr/lib:/opt/rh/devtoolset-11/root/usr/lib64/dyninst:/opt/rh/devtoolset-11/root/usr/lib/dyninst:$LD_LIBRARY_PATH:/usr/local/cuda-${{ matrix.cuda_version }}/lib64 \ + PL_BACKEND="${{ matrix.pl_backend }}" \ + PKG_CONFIG_PATH=/opt/rh/devtoolset-11/root/usr/lib64/pkgconfig:$PKG_CONFIG_PATH CIBW_REPAIR_WHEEL_COMMAND_LINUX: "./bin/auditwheel repair -w {dest_dir} {wheel}" @@ -108,7 +107,7 @@ jobs: - uses: actions/upload-artifact@v3 if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/master' || steps.rc_build.outputs.match != ''}} with: - name: ${{ runner.os }}-wheels-${{ matrix.pl_backend }}-${{ matrix.arch }}.zip + name: ${{ runner.os }}-wheels-${{ matrix.pl_backend }}-${{ matrix.arch }}-cu${{ matrix.cuda_version }} path: ./wheelhouse/*.whl upload-pypi: @@ -122,7 +121,7 @@ jobs: steps: - uses: actions/download-artifact@v3 with: - name: Linux-wheels-${{ matrix.pl_backend }}-${{ matrix.arch }}.zip + name: ${{ runner.os }}-wheels-${{ matrix.pl_backend }}-${{ matrix.arch }}-cu${{ matrix.cuda_version }} path: dist - name: Upload wheels to PyPI diff --git a/README.rst b/README.rst index f82b4282a6..74090c8266 100644 --- a/README.rst +++ b/README.rst @@ -121,7 +121,7 @@ On MacOS, we recommend using the latest version of ``clang++`` and ``libomp``: $ brew install llvm libomp -The Lightning-GPU backend has several dependencies (e.g. ``CUDA``, ``custatevec-cu11``, etc.), and hence we recommend referring to Lightning-GPU installation section. +The Lightning-GPU backend has several dependencies (e.g. ``CUDA``, ``custatevec-cu12``, etc.), and hence we recommend referring to Lightning-GPU installation section. Similarly, for Lightning-Kokkos it is recommended to configure and install Kokkos independently as prescribed in the Lightning-Kokkos installation section. Development installation @@ -227,7 +227,7 @@ Lightning-GPU can be installed using ``pip``: pip install pennylane-lightning[gpu] -Lightning-GPU requires the `cuQuantum SDK `_ (only the `cuStateVec `_ library is required). +Lightning-GPU requires CUDA 12 and the `cuQuantum SDK `_ (only the `cuStateVec `_ library is required). The SDK may be installed within the Python environment ``site-packages`` directory using ``pip`` or ``conda`` or the SDK library path appended to the ``LD_LIBRARY_PATH`` environment variable. Please see the `cuQuantum SDK`_ install guide for more information. @@ -247,7 +247,7 @@ Then the `cuStateVec`_ library can be installed and set a ``CUQUANTUM_SDK`` envi .. code-block:: console - python -m pip install wheel custatevec-cu11 + python -m pip install wheel custatevec-cu12 export CUQUANTUM_SDK=$(python -c "import site; print( f'{site.getsitepackages()[0]}/cuquantum/lib')") The Lightning-GPU can then be installed with ``pip``: @@ -261,7 +261,7 @@ To simplify the build, we recommend using the containerized build process descri Install Lightning-GPU with MPI ============================== -Building Lightning-GPU with MPI also requires the ``NVIDIA cuQuantum SDK`` (currently supported version: `custatevec-cu11 `_), ``mpi4py`` and ``CUDA-aware MPI`` (Message Passing Interface). +Building Lightning-GPU with MPI also requires the ``NVIDIA cuQuantum SDK`` (currently supported version: `custatevec-cu12 `_), ``mpi4py`` and ``CUDA-aware MPI`` (Message Passing Interface). ``CUDA-aware MPI`` allows data exchange between GPU memory spaces of different nodes without the need for CPU-mediated transfers. Both the ``MPICH`` and ``OpenMPI`` libraries are supported, provided they are compiled with CUDA support. The path to ``libmpi.so`` should be found in ``LD_LIBRARY_PATH``. diff --git a/bin/auditwheel b/bin/auditwheel index e7142fa4ab..84de9fa7e1 100755 --- a/bin/auditwheel +++ b/bin/auditwheel @@ -5,21 +5,35 @@ import sys from auditwheel.main import main -from auditwheel.policy import _POLICIES as POLICIES +from auditwheel.policy import WheelPolicies as WP # Do not include licensed dynamic libraries libs = [ "libcudart.so.11.0", + "libcudart.so.12.0", "libcublasLt.so.11", + "libcublasLt.so.12", "libcublas.so.11", + "libcublas.so.12", "libcusparse.so.11", + "libcusparse.so.12", "libcustatevec.so.1", ] print(f"Excluding {libs}") -for p in POLICIES: - p["lib_whitelist"].extend(libs) +for arch in [ + "manylinux2014_x86_64", + "manylinux_2_28_x86_64", + "manylinux2014_aarch64", + "manylinux_2_28_aarch64", + "manylinux2014_ppc64le", + "manylinux_2_28_ppc64le", +]: + wheel_policy = WP() + arch_pol = wheel_policy.get_policy_by_name(arch) + if arch_pol: + arch_pol["lib_whitelist"].extend(libs) if __name__ == "__main__": sys.exit(main()) diff --git a/doc/requirements.txt b/doc/requirements.txt index ff26c3ded6..4887af0718 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -11,7 +11,7 @@ sphinxcontrib-qthelp==1.0.3 sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-serializinghtml==1.1.5 pennylane-sphinx-theme -custatevec-cu11 +custatevec-cu12 wheel sphinxext-opengraph matplotlib \ No newline at end of file diff --git a/pennylane_lightning/core/lightning_base.py b/pennylane_lightning/core/lightning_base.py index 9b784866f5..e17f83fe3b 100644 --- a/pennylane_lightning/core/lightning_base.py +++ b/pennylane_lightning/core/lightning_base.py @@ -93,13 +93,13 @@ def stopping_condition(self): and observable) and returns ``True`` if supported by the device.""" def accepts_obj(obj): - if obj.name == "QFT" and len(obj.wires) < 10: - return True - if obj.name == "GroverOperator" and len(obj.wires) < 13: - return True - return (not isinstance(obj, qml.tape.QuantumTape)) and getattr( - self, "supports_operation", lambda name: False - )(obj.name) + if obj.name == "QFT": + return len(obj.wires) < 10 + if obj.name == "GroverOperator": + return len(obj.wires) < 13 + is_not_tape = not isinstance(obj, qml.tape.QuantumTape) + is_supported = getattr(self, "supports_operation", lambda name: False)(obj.name) + return is_not_tape and is_supported return qml.BooleanFn(accepts_obj) diff --git a/pennylane_lightning/lightning_gpu/lightning_gpu.py b/pennylane_lightning/lightning_gpu/lightning_gpu.py index 349e821959..fba2612207 100644 --- a/pennylane_lightning/lightning_gpu/lightning_gpu.py +++ b/pennylane_lightning/lightning_gpu/lightning_gpu.py @@ -60,7 +60,7 @@ "cuquantum" ): # pragma: no cover raise ImportError( - 'custatevec libraries not found. Please pip install appropriate custatevec in a virtual environment and then add its path to the "LD_LIBRARY_PATH" environment variable.' + "custatevec libraries not found. Please pip install the appropriate custatevec library in a virtual environment." ) if not DevPool.getTotalDevices(): # pragma: no cover raise ValueError("No supported CUDA-capable device found") diff --git a/requirements-dev.txt b/requirements-dev.txt index 10e01a32cc..94456d4e0f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,5 +12,5 @@ black==23.7.0 clang-tidy~=16.0 clang-format~=16.0 cmake -custatevec-cu11 +custatevec-cu12 pylint diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 86c0a9e246..422ad06eef 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -727,6 +727,7 @@ def test_controlled_jacobian(self, par, n_qubits, control_value, operation, tol) np.random.seed(1337) init_state = np.random.rand(2**n_qubits) + 1.0j * np.random.rand(2**n_qubits) init_state /= np.sqrt(np.dot(np.conj(init_state), init_state)) + init_state = np.array(init_state, requires_grad=False) if operation.num_wires > n_qubits: return diff --git a/tests/test_decomposition.py b/tests/test_decomposition.py index cfd7cf7fed..a80e955cfd 100644 --- a/tests/test_decomposition.py +++ b/tests/test_decomposition.py @@ -31,12 +31,14 @@ class TestDenseMatrixDecompositionThreshold: input = [ (qml.QFT, 8, True), (qml.QFT, 10, False), + (qml.QFT, 14, False), (qml.GroverOperator, 8, True), (qml.GroverOperator, 13, False), ] @pytest.mark.parametrize("op, n_wires, condition", input) def test_threshold(self, op, n_wires, condition): - wires = np.linspace(0, n_wires - 1, n_wires, dtype=int) + wires = range(n_wires) op = op(wires=wires) - assert ld.stopping_condition.__get__(op)(op) == condition + dev = ld(n_wires) + assert dev.stopping_condition(op) == condition From af41a638245df4800757005d4153d15d613b8e4d Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 08:59:51 -0500 Subject: [PATCH 004/105] update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 87a919ae46..676b0361c2 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.35.0-dev12" +__version__ = "0.35.0-dev14" \ No newline at end of file From 17a4fb035626027ad1c86bc7286a8a2a26bd88bf Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:02:18 -0500 Subject: [PATCH 005/105] update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 676b0361c2..af34c02f2a 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.35.0-dev14" \ No newline at end of file +__version__ = "0.35.0-dev14" From 7d9c0483c41c43de9dafc7fed500481df9355310 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:04:23 -0500 Subject: [PATCH 006/105] update lightning_qubit_2 --- .../lightning_qubit/lightning_qubit2.py | 299 +++++++++++------- 1 file changed, 178 insertions(+), 121 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 30cabef7df..6e2fac9ce7 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -21,135 +21,184 @@ import pennylane as qml from pennylane.devices import Device, ExecutionConfig, DefaultExecutionConfig -from pennylane.devices.preprocess import decompose, validate_device_wires, decompose, validate_measurements, validate_observables, no_sampling +from pennylane.devices.preprocess import ( + decompose, + validate_device_wires, + decompose, + validate_measurements, + validate_observables, + no_sampling, +) from pennylane.devices.qubit.sampling import get_num_shots_and_executions from pennylane.tape import QuantumScript from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch +from ._state_vector import LightningStateVector +from ._measurements import LightningMeasurements -def dummy_simulate(circuit, rng=None, c_dtype=np.complex128, batch_obs=False, mcmc=False, - kernel_name="Local", num_burnin=100): - return tuple(0.0 for _ in circuit.measurements) -def dummy_jacobian(circuit): +try: + # pylint: disable=import-error, no-name-in-module + from pennylane_lightning.lightning_qubit_ops import ( + StateVectorC64, + StateVectorC128, + ) + + LQ_CPP_BINARY_AVAILABLE = True +except ImportError: + LQ_CPP_BINARY_AVAILABLE = False + + +def simulate(circuit: QuantumScript, dtype=np.complex128, debugger=None) -> Result: + """Simulate a single quantum script. + + Args: + circuit (QuantumTape): The single circuit to simulate + dtype: Datatypes for state-vector representation. Must be one of + ``np.complex64`` or ``np.complex128``. + debugger (_Debugger): The debugger to use + + Returns: + tuple(TensorLike): The results of the simulation + + Note that this function can return measurements for non-commuting observables simultaneously. + + """ + state, is_state_batched = LightningStateVector( + num_wires=circuit.num_wires, dtype=dtype + ).get_final_state(circuit, debugger=debugger) + return LightningMeasurements(state).measure_final_state(circuit, is_state_batched) + + +def dummy_jacobian(circuit: QuantumScript): return np.array(0.0) -def dummy_simulate_and_jacobian(circuit): + +def simulate_and_jacobian(circuit: QuantumScript): return np.array(0.0), np.array(0.0) + Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[qml.tape.QuantumTape] QuantumTape_or_Batch = Union[qml.tape.QuantumTape, QuantumTapeBatch] -_operations = frozenset({ - "Identity", - "BasisState", - "QubitStateVector", - "StatePrep", - "QubitUnitary", - "ControlledQubitUnitary", - "MultiControlledX", - "DiagonalQubitUnitary", - "PauliX", - "PauliY", - "PauliZ", - "MultiRZ", - "Hadamard", - "S", - "Adjoint(S)", - "T", - "Adjoint(T)", - "SX", - "Adjoint(SX)", - "CNOT", - "SWAP", - "ISWAP", - "PSWAP", - "Adjoint(ISWAP)", - "SISWAP", - "Adjoint(SISWAP)", - "SQISW", - "CSWAP", - "Toffoli", - "CY", - "CZ", - "PhaseShift", - "ControlledPhaseShift", - "CPhase", - "RX", - "RY", - "RZ", - "Rot", - "CRX", - "CRY", - "CRZ", - "C(PauliX)", - "C(PauliY)", - "C(PauliZ)", - "C(Hadamard)", - "C(S)", - "C(T)", - "C(PhaseShift)", - "C(RX)", - "C(RY)", - "C(RZ)", - "C(SWAP)", - "C(IsingXX)", - "C(IsingXY)", - "C(IsingYY)", - "C(IsingZZ)", - "C(SingleExcitation)", - "C(SingleExcitationMinus)", - "C(SingleExcitationPlus)", - "C(DoubleExcitation)", - "C(DoubleExcitationMinus)", - "C(DoubleExcitationPlus)", - "CRot", - "IsingXX", - "IsingYY", - "IsingZZ", - "IsingXY", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - "DoubleExcitation", - "DoubleExcitationPlus", - "DoubleExcitationMinus", - "QubitCarry", - "QubitSum", - "OrbitalRotation", - "QFT", - "ECR", - "BlockEncode", -}) - -_observables = frozenset({ - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "Hermitian", - "Identity", - "Projector", - "SparseHamiltonian", - "Hamiltonian", - "Sum", - "SProd", - "Prod", - "Exp", -}) +_operations = frozenset( + { + "Identity", + "BasisState", + "QubitStateVector", + "StatePrep", + "QubitUnitary", + "ControlledQubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + "PauliX", + "PauliY", + "PauliZ", + "MultiRZ", + "Hadamard", + "S", + "Adjoint(S)", + "T", + "Adjoint(T)", + "SX", + "Adjoint(SX)", + "CNOT", + "SWAP", + "ISWAP", + "PSWAP", + "Adjoint(ISWAP)", + "SISWAP", + "Adjoint(SISWAP)", + "SQISW", + "CSWAP", + "Toffoli", + "CY", + "CZ", + "PhaseShift", + "ControlledPhaseShift", + "CPhase", + "RX", + "RY", + "RZ", + "Rot", + "CRX", + "CRY", + "CRZ", + "C(PauliX)", + "C(PauliY)", + "C(PauliZ)", + "C(Hadamard)", + "C(S)", + "C(T)", + "C(PhaseShift)", + "C(RX)", + "C(RY)", + "C(RZ)", + "C(SWAP)", + "C(IsingXX)", + "C(IsingXY)", + "C(IsingYY)", + "C(IsingZZ)", + "C(SingleExcitation)", + "C(SingleExcitationMinus)", + "C(SingleExcitationPlus)", + "C(DoubleExcitation)", + "C(DoubleExcitationMinus)", + "C(DoubleExcitationPlus)", + "CRot", + "IsingXX", + "IsingYY", + "IsingZZ", + "IsingXY", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + "DoubleExcitation", + "DoubleExcitationPlus", + "DoubleExcitationMinus", + "QubitCarry", + "QubitSum", + "OrbitalRotation", + "QFT", + "ECR", + "BlockEncode", + } +) + +_observables = frozenset( + { + "PauliX", + "PauliY", + "PauliZ", + "Hadamard", + "Hermitian", + "Identity", + "Projector", + "SparseHamiltonian", + "Hamiltonian", + "Sum", + "SProd", + "Prod", + "Exp", + } +) + def stopping_condition(op: qml.operation.Operator) -> bool: return op.name in _operations + def accepted_observables(obs: qml.operation.Operator) -> bool: return obs.name in _observables -def accepted_measurements(m : qml.measurements.MeasurementProcess) -> bool: + +def accepted_measurements(m: qml.measurements.MeasurementProcess) -> bool: return isinstance(m, (qml.measurements.ExpectationMP)) + class LightningQubit2(Device): """PennyLane Lightning Qubit device. @@ -182,13 +231,14 @@ class LightningQubit2(Device): qubit is built with OpenMP. """ - name = 'lightning.qubit2' + name = "lightning.qubit2" + _CPP_BINARY_AVAILABLE = LQ_CPP_BINARY_AVAILABLE _device_options = ["rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin"] def __init__( # pylint: disable=too-many-arguments self, - wires=None, + wires=None, # the number of wires are always needed for Lightning. Do we have a better way to get it *, c_dtype=np.complex128, shots=None, @@ -222,19 +272,21 @@ def __init__( # pylint: disable=too-many-arguments self._kernel_name = None self._num_burnin = None + @property + def c_dtype(self): + """State vector complex data type.""" + return self._c_dtype + @property def operation(self) -> frozenset[str]: - """The names of supported operations. - """ + """The names of supported operations.""" return _operations @property def observables(self) -> frozenset[str]: - """The names of supported observables. - """ + """The names of supported observables.""" return _observables - def _setup_execution_config(self, config): updated_values = {} if config.gradient_method == "best": @@ -251,7 +303,6 @@ def _setup_execution_config(self, config): return replace(config, **updated_values, device_options=new_device_options) - def supports_derivatives( self, execution_config: Optional[ExecutionConfig] = None, @@ -263,12 +314,16 @@ def supports_derivatives( return False if circuit is None: return True - return all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) and not circuit.shots - + return ( + all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) + and not circuit.shots + ) def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): program = TransformProgram() - program.add_transform(validate_measurements, analytic_measurements=accepted_measurements, name=self.name) + program.add_transform( + validate_measurements, analytic_measurements=accepted_measurements, name=self.name + ) program.add_transform(no_sampling) program.add_transform(validate_observables, accepted_observables, name=self.name) program.add_transform(validate_device_wires, self.wires, name=self.name) @@ -295,7 +350,11 @@ def _execute_tracking(self, circuits): ) self.tracker.record() - def execute(self, circuits : QuantumTape_or_Batch, execution_config: ExecutionConfig = DefaultExecutionConfig) -> Result_or_ResultBatch: + def execute( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ) -> Result_or_ResultBatch: is_single_circuit = False if isinstance(circuits, QuantumScript): is_single_circuit = True @@ -306,11 +365,10 @@ def execute(self, circuits : QuantumTape_or_Batch, execution_config: ExecutionCo results = [] for circuit in circuits: - results.append(dummy_simulate(circuit, **execution_config.device_options)) + results.append(simulate(circuit, **execution_config.device_options)) return results[0] if is_single_circuit else tuple(results) - def compute_derivatives( self, circuits: QuantumTape_or_Batch, @@ -328,7 +386,6 @@ def compute_derivatives( return res[0] if is_single_circuit else res - def execute_and_compute_derivatives( self, circuits: QuantumTape_or_Batch, @@ -349,6 +406,6 @@ def execute_and_compute_derivatives( ) self.tracker.record() - results = tuple(dummy_simulate_and_jacobian(c) for c in circuits) + results = tuple(simulate_and_jacobian(c) for c in circuits) results, jacs = tuple(zip(*results)) - return (results[0], jacs[0]) if is_single_circuit else (results, jacs) \ No newline at end of file + return (results[0], jacs[0]) if is_single_circuit else (results, jacs) From 67b11c5ec56a7eadd491bd5bde4c3177c94cedc1 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:05:47 -0500 Subject: [PATCH 007/105] add LightningQubit2 to init --- pennylane_lightning/lightning_qubit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/__init__.py b/pennylane_lightning/lightning_qubit/__init__.py index 53e50cbf00..9d8ddab62d 100644 --- a/pennylane_lightning/lightning_qubit/__init__.py +++ b/pennylane_lightning/lightning_qubit/__init__.py @@ -15,3 +15,4 @@ from pennylane_lightning.core import __version__ from .lightning_qubit import LightningQubit +from .lightning_qubit2 import LightningQubit2 From 6e82a1a17ea074c786aaa5af6c686f0bcf46afc0 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:09:15 -0500 Subject: [PATCH 008/105] add LightningStateVector class --- .../lightning_qubit/_state_vector.py | 386 ++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 pennylane_lightning/lightning_qubit/_state_vector.py diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py new file mode 100644 index 0000000000..a4e4c0ca01 --- /dev/null +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -0,0 +1,386 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Class implementation for state-vector manipulation. +""" + +try: + from pennylane_lightning.lightning_qubit_ops import ( + allocate_aligned_array, + get_alignment, + best_alignment, + StateVectorC64, + StateVectorC128, + ) +except ImportError: + pass + +import numpy as np + +import pennylane as qml +from pennylane.tape import QuantumScript +from pennylane.wires import Wires +from pennylane import ( + BasisState, + StatePrep, + DeviceError, +) + +from itertools import product + + +class LightningStateVector: + """Lightning state-vector class. + + Interfaces with C++ python binding methods for state-vector manipulation. + + Args: + num_wires(int): the number of wires to initialize the device with + dtype: Datatypes for state-vector representation. Must be one of + ``np.complex64`` or ``np.complex128``. + device_name(string): state vector device name. Options: ["lightning.qubit"] + """ + + def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit"): + self.num_wires = num_wires + self._wires = Wires(range(num_wires)) + self._dtype = dtype + self._name = device_name + self._qubit_state = self._state_dtype()(self.num_wires) + + @property + def dtype(self): + """Returns the state vector data type.""" + return self._dtype + + @property + def name(self): + """Returns the state vector device name.""" + return self._name + + @property + def wires(self): + """All wires that can be addressed on this device""" + return self._wires + + def _state_dtype(self): + """Binding to Lightning Managed state vector. + + Args: + dtype (complex): Data complex type + + Returns: the state vector class + """ + if self.dtype not in [np.complex128, np.complex64]: # pragma: no cover + raise ValueError( + f"Data type is not supported for state-vector computation: {self.dtype}" + ) + return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 + + @staticmethod + def _asarray(arr, dtype=None): + """Verify data alignment and allocate aligned memory if needed. + + Args: + arr (numpy.array): data array + dtype (dtype, optional): if provided will convert the array data type. + + Returns: + np.array: numpy array with aligned data. + """ + arr = np.asarray(arr) # arr is not copied + + if arr.dtype.kind not in ["f", "c"]: + return arr + + if not dtype: + dtype = arr.dtype + + # We allocate a new aligned memory and copy data to there if alignment or dtype + # mismatches + # Note that get_alignment does not necessarily return CPUMemoryModel(Unaligned) + # numpy allocated memory as the memory location happens to be aligned. + if int(get_alignment(arr)) < int(best_alignment()) or arr.dtype != dtype: + new_arr = allocate_aligned_array(arr.size, np.dtype(dtype), False).reshape(arr.shape) + if len(arr.shape): + new_arr[:] = arr + else: + np.copyto(new_arr, arr) + arr = new_arr + return arr + + def _create_basis_state(self, index): + """Return a computational basis state over all wires. + Args: + _qubit_state: a handle to Lightning qubit state. + index (int): integer representing the computational basis state. + """ + self._qubit_state.setBasisState(index) + + def reset_state(self): + """Reset the device's state""" + # init the state vector to |00..0> + self._qubit_state.resetStateVector() + + @property + def state(self): + """Copy the state vector data to a numpy array. + + **Example** + + >>> dev = qml.device('lightning.qubit', wires=1) + >>> dev.apply([qml.PauliX(wires=[0])]) + >>> print(dev.state) + [0.+0.j 1.+0.j] + """ + state = np.zeros(2**self.num_wires, dtype=self.dtype) + state = self._asarray(state, dtype=self.dtype) + self._qubit_state.getState(state) + return state + + @property + def state_vector(self): + """Returns a handle to the state vector.""" + return self._qubit_state + + def _preprocess_state_vector(self, state, device_wires): + """Initialize the internal state vector in a specified state. + + Args: + state (array[complex]): normalized input state of length ``2**len(wires)`` + or broadcasted state of shape ``(batch_size, 2**len(wires))`` + device_wires (Wires): wires that get initialized in the state + + Returns: + array[complex]: normalized input state of length ``2**len(wires)`` + or broadcasted state of shape ``(batch_size, 2**len(wires))`` + array[int]: indices for which the state is changed to input state vector elements + """ + + # translate to wire labels used by device + device_wires = self.map_wires(device_wires) + + # special case for integral types + if state.dtype.kind == "i": + state = qml.numpy.array(state, dtype=self.C_DTYPE) + state = self._asarray(state, dtype=self.C_DTYPE) + + if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: + return None, state + + # generate basis states on subset of qubits via the cartesian product + basis_states = np.array(list(product([0, 1], repeat=len(device_wires)))) + + # get basis states to alter on full set of qubits + unravelled_indices = np.zeros((2 ** len(device_wires), self.num_wires), dtype=int) + unravelled_indices[:, device_wires] = basis_states + + # get indices for which the state is changed to input state vector elements + ravelled_indices = np.ravel_multi_index(unravelled_indices.T, [2] * self.num_wires) + return ravelled_indices, state + + def _get_basis_state_index(self, state, wires): + """Returns the basis state index of a specified computational basis state. + + Args: + state (array[int]): computational basis state of shape ``(wires,)`` + consisting of 0s and 1s + wires (Wires): wires that the provided computational state should be initialized on + + Returns: + int: basis state index + """ + # translate to wire labels used by device + device_wires = self.map_wires(wires) + + # length of basis state parameter + n_basis_state = len(state) + + if not set(state.tolist()).issubset({0, 1}): + raise ValueError("BasisState parameter must consist of 0 or 1 integers.") + + if n_basis_state != len(device_wires): + raise ValueError("BasisState parameter and wires must be of equal length.") + + # get computational basis state number + basis_states = 2 ** (self.num_wires - 1 - np.array(device_wires)) + basis_states = qml.math.convert_like(basis_states, state) + return int(qml.math.dot(state, basis_states)) + + def _apply_state_vector(self, state, device_wires): + """Initialize the internal state vector in a specified state. + Args: + state (array[complex]): normalized input state of length ``2**len(wires)`` + or broadcasted state of shape ``(batch_size, 2**len(wires))`` + device_wires (Wires): wires that get initialized in the state + """ + + if isinstance(state, self._qubit_state.__class__): + state_data = allocate_aligned_array(state.size, np.dtype(self.dtype), True) + self._qubit_state.getState(state_data) + state = state_data + + ravelled_indices, state = self._preprocess_state_vector(state, device_wires) + + # translate to wire labels used by device + device_wires = self.map_wires(device_wires) + output_shape = [2] * self.num_wires + + if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: + # Initialize the entire device state with the input state + state = self._reshape(state, output_shape).ravel(order="C") + self._qubit_state.UpdateData(state) + return + + self._qubit_state.setStateVector(ravelled_indices, state) + + def _apply_basis_state(self, state, wires): + """Initialize the state vector in a specified computational basis state. + + Args: + state (array[int]): computational basis state of shape ``(wires,)`` + consisting of 0s and 1s. + wires (Wires): wires that the provided computational state should be + initialized on + + Note: This function does not support broadcasted inputs yet. + """ + num = self._get_basis_state_index(state, wires) + self._create_basis_state(num) + + def _apply_lightning_controlled(self, operation): + """Apply an arbitrary controlled operation to the state tensor. + + Args: + operation (~pennylane.operation.Operation): operation to apply + + Returns: + array[complex]: the output state tensor + """ + state = self.state_vector + + basename = "PauliX" if operation.name == "MultiControlledX" else operation.base.name + if basename == "Identity": + return + method = getattr(state, f"{basename}", None) + control_wires = self.wires.indices(operation.control_wires) + control_values = ( + [bool(int(i)) for i in operation.hyperparameters["control_values"]] + if operation.name == "MultiControlledX" + else operation.control_values + ) + if operation.name == "MultiControlledX": + target_wires = list(set(self.wires.indices(operation.wires)) - set(control_wires)) + else: + target_wires = self.wires.indices(operation.target_wires) + if method is not None: # apply n-controlled specialized gate + inv = False + param = operation.parameters + method(control_wires, control_values, target_wires, inv, param) + else: # apply gate as an n-controlled matrix + method = getattr(state, "applyControlledMatrix") + target_wires = self.wires.indices(operation.target_wires) + try: + method( + qml.matrix(operation.base), + control_wires, + control_values, + target_wires, + False, + ) + except AttributeError: # pragma: no cover + # To support older versions of PL + method(operation.base.matrix, control_wires, control_values, target_wires, False) + + def apply_lightning(self, operations): + """Apply a list of operations to the state tensor. + + Args: + operations (list[~pennylane.operation.Operation]): operations to apply + + Returns: + array[complex]: the output state tensor + """ + state = self.state_vector + + # Skip over identity operations instead of performing + # matrix multiplication with it. + for operation in operations: + name = operation.name + if name == "Identity": + continue + method = getattr(state, name, None) + wires = self.wires.indices(operation.wires) + + if method is not None: # apply specialized gate + inv = False + param = operation.parameters + method(wires, inv, param) + elif ( + name[0:2] == "C(" or name == "ControlledQubitUnitary" or name == "MultiControlledX" + ): # apply n-controlled gate + print("hi") + self._apply_lightning_controlled(operation) + else: # apply gate as a matrix + # Inverse can be set to False since qml.matrix(operation) is already in + # inverted form + method = getattr(state, "applyMatrix") + try: + method(qml.matrix(operation), wires, False) + except AttributeError: # pragma: no cover + # To support older versions of PL + method(operation.matrix, wires, False) + + def apply_operations(self, operations): + """Applies operations to the state vector.""" + # State preparation is currently done in Python + if operations: # make sure operations[0] exists + if isinstance(operations[0], StatePrep): + self._apply_state_vector(operations[0].parameters[0].copy(), operations[0].wires) + operations = operations[1:] + elif isinstance(operations[0], BasisState): + self._apply_basis_state(operations[0].parameters[0], operations[0].wires) + operations = operations[1:] + + for operation in operations: + if isinstance(operation, (StatePrep, BasisState)): + raise DeviceError( + f"Operation {operation.name} cannot be used after other " + f"Operations have already been applied on a {self.short_name} device." + ) + + self.apply_lightning(operations) + + def get_final_state(self, circuit: QuantumScript, debugger=None): + """ + Get the final state that results from executing the given quantum script. + + This is an internal function that will be called by the successor to ``lightning.qubit``. + + Args: + circuit (QuantumScript): The single circuit to simulate + debugger (._Debugger): The debugger to use + + Returns: + Tuple: A tuple containing the Lightning final state handler of the quantum script and + whether the state has a batch dimension. + + """ + + circuit = circuit.map_to_standard_wires() + self.apply_operations(circuit._ops) + + # No batching support yet. + + return self, False # is_state_batched From 0df46018c654e391248d497716618760813dbd33 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:10:42 -0500 Subject: [PATCH 009/105] add LightningMeasurements class --- .../lightning_qubit/_measurements.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 pennylane_lightning/lightning_qubit/_measurements.py diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py new file mode 100644 index 0000000000..a1de9000c9 --- /dev/null +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -0,0 +1,182 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Class implementation for state vector measurements. +""" + +try: + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) +except ImportError: + pass + +import numpy as np +from typing import Callable, List + +import pennylane as qml +from pennylane.measurements import StateMeasurement, MeasurementProcess, ExpectationMP +from pennylane.typing import TensorLike, Result +from pennylane.tape import QuantumScript +from pennylane.wires import Wires + +from typing import List + +from ._serialize import QuantumScriptSerializer +from ._state_vector import LightningStateVector + + +class LightningMeasurements: + """Lightning Measurements class + + Measures the state provided by the LightningStateVector class. + + Args: + qubit_state(LightningStateVector) + """ + + def __init__(self, qubit_state: LightningStateVector) -> None: + self._qubit_state = qubit_state + self.state = qubit_state.state_vector + self.dtype = qubit_state.dtype + self.measurement_lightning = self._measurement_dtype()(self.state) + + def _measurement_dtype(self): + """Binding to Lightning Managed state vector. + + Args: + dtype (complex): Data complex type + + Returns: the state vector class + """ + return MeasurementsC64 if self.dtype == np.complex64 else MeasurementsC128 + + def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> TensorLike: + """Apply a measurement to state when the measurement process has an observable with diagonalizing gates. + This method will is bypassing the measurement process to default.qubit implementation. + + Args: + measurementprocess (StateMeasurement): measurement to apply to the state + + Returns: + TensorLike: the result of the measurement + """ + self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) + + state_array = self._qubit_state.state + total_wires = int(np.log2(state_array.size)) + wires = Wires(range(total_wires)) + return measurementprocess.process_state(state_array, wires) + + def expval(self, measurementprocess: MeasurementProcess): + """Expectation value of the supplied observable contained in the MeasurementProcess. + + Args: + measurementprocess (StateMeasurement): measurement to apply to the state + + Returns: + Expectation value of the observable + """ + + if measurementprocess.obs.name == "SparseHamiltonian": + # ensuring CSR sparse representation. + CSR_SparseHamiltonian = measurementprocess.obs.sparse_matrix( + wire_order=list(range(self._qubit_state.num_wires)) + ).tocsr(copy=False) + return self.measurement_lightning.expval( + CSR_SparseHamiltonian.indptr, + CSR_SparseHamiltonian.indices, + CSR_SparseHamiltonian.data, + ) + + if ( + measurementprocess.obs.name in ["Hamiltonian", "Hermitian"] + or (measurementprocess.obs.arithmetic_depth > 0) + or isinstance(measurementprocess.obs.name, List) + ): + ob_serialized = QuantumScriptSerializer( + self._qubit_state.name, self.dtype == np.complex64 + )._ob(measurementprocess.obs) + return self.measurement_lightning.expval(ob_serialized) + + return self.measurement_lightning.expval( + measurementprocess.obs.name, measurementprocess.obs.wires + ) + + def get_measurement_function( + self, measurementprocess: MeasurementProcess + ) -> Callable[[MeasurementProcess, TensorLike], TensorLike]: + """Get the appropriate method for performing a measurement. + + Args: + measurementprocess (MeasurementProcess): measurement process to apply to the state + + Returns: + Callable: function that returns the measurement result + """ + if isinstance(measurementprocess, StateMeasurement): + if isinstance(measurementprocess, ExpectationMP): + if measurementprocess.obs.name in [ + "Identity", + "Projector", + ]: + return self.state_diagonalizing_gates + return self.expval + + if measurementprocess.obs is None or measurementprocess.obs.has_diagonalizing_gates: + return self.state_diagonalizing_gates + + raise NotImplementedError + + def measurement(self, measurementprocess: MeasurementProcess) -> TensorLike: + """Apply a measurement process to a state. + + Args: + measurementprocess (MeasurementProcess): measurement process to apply to the state + + Returns: + TensorLike: the result of the measurement + """ + return self.get_measurement_function(measurementprocess)(measurementprocess) + + def measure_final_state(self, circuit: QuantumScript, is_state_batched) -> Result: + """ + Perform the measurements required by the circuit on the provided state. + + This is an internal function that will be called by the successor to ``lightning.qubit``. + + Args: + circuit (QuantumScript): The single circuit to simulate + is_state_batched (bool): Whether the state has a batch dimension or not. + + Returns: + Tuple[TensorLike]: The measurement results + """ + if set(circuit.wires) != set(range(circuit.num_wires)): + wire_map = {w: i for i, w in enumerate(circuit.wires)} + circuit = qml.map_wires(circuit, wire_map) + + if not circuit.shots: + # analytic case + if len(circuit.measurements) == 1: + return self.measurement(circuit.measurements[0]) + + results = [] + for mp in circuit.measurements: + measure_val = self.measurement(mp) + results += [ + measure_val, + ] + return tuple(results) From 79e4d646752c11dd7d0ea330e291395c6812016f Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:11:44 -0500 Subject: [PATCH 010/105] add new QuantumScriptSerializer class --- .../lightning_qubit/_serialize.py | 390 ++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 pennylane_lightning/lightning_qubit/_serialize.py diff --git a/pennylane_lightning/lightning_qubit/_serialize.py b/pennylane_lightning/lightning_qubit/_serialize.py new file mode 100644 index 0000000000..2553126c6f --- /dev/null +++ b/pennylane_lightning/lightning_qubit/_serialize.py @@ -0,0 +1,390 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +r""" +Helper functions for serializing quantum tapes. +""" +from typing import List, Tuple +import numpy as np +from pennylane import ( + BasisState, + Hadamard, + PauliX, + PauliY, + PauliZ, + Identity, + StatePrep, + Rot, + Hamiltonian, + SparseHamiltonian, + QubitUnitary, +) +from pennylane.operation import Tensor +from pennylane.tape import QuantumTape +from pennylane.math import unwrap + +from pennylane import matrix, DeviceError + +pauli_name_map = { + "I": "Identity", + "X": "PauliX", + "Y": "PauliY", + "Z": "PauliZ", +} + + +class QuantumScriptSerializer: + """Serializer class for `pennylane.tape.QuantumScript` data. + + Args: + device_name: device shortname. + use_csingle (bool): whether to use np.complex64 instead of np.complex128 + + """ + + # pylint: disable=import-outside-toplevel, too-many-instance-attributes + def __init__( + self, device_name, use_csingle: bool = False, use_mpi: bool = False, split_obs: bool = False + ): + self.use_csingle = use_csingle + self.device_name = device_name + self.split_obs = split_obs + if device_name == "lightning.qubit" or device_name == "lightning.qubit2": + try: + import pennylane_lightning.lightning_qubit_ops as lightning_ops + except ImportError as exception: + raise ImportError( + f"Pre-compiled binaries for {device_name}" + " serialize functionality are not available." + ) from exception + elif device_name == "lightning.kokkos": + try: + import pennylane_lightning.lightning_kokkos_ops as lightning_ops + except ImportError as exception: + raise ImportError( + f"Pre-compiled binaries for {device_name}" + " serialize functionality are not available." + ) from exception + elif device_name == "lightning.gpu": + try: + import pennylane_lightning.lightning_gpu_ops as lightning_ops + except ImportError as exception: + raise ImportError( + f"Pre-compiled binaries for {device_name} are not available." + ) from exception + else: + raise DeviceError(f'The device name "{device_name}" is not a valid option.') + self.statevector_c64 = lightning_ops.StateVectorC64 + self.statevector_c128 = lightning_ops.StateVectorC128 + self.named_obs_c64 = lightning_ops.observables.NamedObsC64 + self.named_obs_c128 = lightning_ops.observables.NamedObsC128 + self.hermitian_obs_c64 = lightning_ops.observables.HermitianObsC64 + self.hermitian_obs_c128 = lightning_ops.observables.HermitianObsC128 + self.tensor_prod_obs_c64 = lightning_ops.observables.TensorProdObsC64 + self.tensor_prod_obs_c128 = lightning_ops.observables.TensorProdObsC128 + self.hamiltonian_c64 = lightning_ops.observables.HamiltonianC64 + self.hamiltonian_c128 = lightning_ops.observables.HamiltonianC128 + self.sparse_hamiltonian_c64 = lightning_ops.observables.SparseHamiltonianC64 + self.sparse_hamiltonian_c128 = lightning_ops.observables.SparseHamiltonianC128 + + self._use_mpi = use_mpi + + if self._use_mpi: + self.statevector_mpi_c64 = lightning_ops.StateVectorMPIC64 + self.statevector_mpi_c128 = lightning_ops.StateVectorMPIC128 + self.named_obs_mpi_c64 = lightning_ops.observablesMPI.NamedObsMPIC64 + self.named_obs_mpi_c128 = lightning_ops.observablesMPI.NamedObsMPIC128 + self.hermitian_obs_mpi_c64 = lightning_ops.observablesMPI.HermitianObsMPIC64 + self.hermitian_obs_mpi_c128 = lightning_ops.observablesMPI.HermitianObsMPIC128 + self.tensor_prod_obs_mpi_c64 = lightning_ops.observablesMPI.TensorProdObsMPIC64 + self.tensor_prod_obs_mpi_c128 = lightning_ops.observablesMPI.TensorProdObsMPIC128 + self.hamiltonian_mpi_c64 = lightning_ops.observablesMPI.HamiltonianMPIC64 + self.hamiltonian_mpi_c128 = lightning_ops.observablesMPI.HamiltonianMPIC128 + self.sparse_hamiltonian_mpi_c64 = lightning_ops.observablesMPI.SparseHamiltonianMPIC64 + self.sparse_hamiltonian_mpi_c128 = lightning_ops.observablesMPI.SparseHamiltonianMPIC128 + + self._mpi_manager = lightning_ops.MPIManager + + @property + def ctype(self): + """Complex type.""" + return np.complex64 if self.use_csingle else np.complex128 + + @property + def rtype(self): + """Real type.""" + return np.float32 if self.use_csingle else np.float64 + + @property + def sv_type(self): + """State vector matching ``use_csingle`` precision (and MPI if it is supported).""" + if self._use_mpi: + return self.statevector_mpi_c64 if self.use_csingle else self.statevector_mpi_c128 + return self.statevector_c64 if self.use_csingle else self.statevector_c128 + + @property + def named_obs(self): + """Named observable matching ``use_csingle`` precision.""" + if self._use_mpi: + return self.named_obs_mpi_c64 if self.use_csingle else self.named_obs_mpi_c128 + return self.named_obs_c64 if self.use_csingle else self.named_obs_c128 + + @property + def hermitian_obs(self): + """Hermitian observable matching ``use_csingle`` precision.""" + if self._use_mpi: + return self.hermitian_obs_mpi_c64 if self.use_csingle else self.hermitian_obs_mpi_c128 + return self.hermitian_obs_c64 if self.use_csingle else self.hermitian_obs_c128 + + @property + def tensor_obs(self): + """Tensor product observable matching ``use_csingle`` precision.""" + if self._use_mpi: + return ( + self.tensor_prod_obs_mpi_c64 if self.use_csingle else self.tensor_prod_obs_mpi_c128 + ) + return self.tensor_prod_obs_c64 if self.use_csingle else self.tensor_prod_obs_c128 + + @property + def hamiltonian_obs(self): + """Hamiltonian observable matching ``use_csingle`` precision.""" + if self._use_mpi: + return self.hamiltonian_mpi_c64 if self.use_csingle else self.hamiltonian_mpi_c128 + return self.hamiltonian_c64 if self.use_csingle else self.hamiltonian_c128 + + @property + def sparse_hamiltonian_obs(self): + """SparseHamiltonian observable matching ``use_csingle`` precision.""" + if self._use_mpi: + return ( + self.sparse_hamiltonian_mpi_c64 + if self.use_csingle + else self.sparse_hamiltonian_mpi_c128 + ) + return self.sparse_hamiltonian_c64 if self.use_csingle else self.sparse_hamiltonian_c128 + + def _named_obs(self, observable): + """Serializes a Named observable""" + wires = observable.wires.tolist() + if observable.name == "Identity": + wires = wires[:1] + return self.named_obs(observable.name, wires) + + def _hermitian_ob(self, observable): + """Serializes a Hermitian observable""" + assert not isinstance(observable, Tensor) + + return self.hermitian_obs( + matrix(observable).ravel().astype(self.ctype), observable.wires.tolist() + ) + + def _tensor_ob(self, observable): + """Serialize a tensor observable""" + assert isinstance(observable, Tensor) + return self.tensor_obs([self._ob(obs) for obs in observable.obs]) + + def _hamiltonian(self, observable): + coeffs = np.array(unwrap(observable.coeffs)).astype(self.rtype) + terms = [self._ob(t) for t in observable.ops] + + if self.split_obs: + return [self.hamiltonian_obs([c], [t]) for (c, t) in zip(coeffs, terms)] + + return self.hamiltonian_obs(coeffs, terms) + + def _sparse_hamiltonian(self, observable): + """Serialize an observable (Sparse Hamiltonian) + + Args: + observable (Observable): the input observable (Sparse Hamiltonian) + wire_map (dict): a dictionary mapping input wires to the device's backend wires + + Returns: + sparse_hamiltonian_obs (SparseHamiltonianC64 or SparseHamiltonianC128): A Sparse Hamiltonian observable object compatible with the C++ backend + """ + + if self._use_mpi: + Hmat = Hamiltonian([1.0], [Identity(0)]).sparse_matrix() + H_sparse = SparseHamiltonian(Hmat, wires=range(1)) + spm = H_sparse.sparse_matrix() + # Only root 0 needs the overall sparse matrix data + if self._mpi_manager().getRank() == 0: + spm = observable.sparse_matrix() + self._mpi_manager().Barrier() + else: + spm = observable.sparse_matrix() + data = np.array(spm.data).astype(self.ctype) + indices = np.array(spm.indices).astype(np.int64) + offsets = np.array(spm.indptr).astype(np.int64) + + return self.sparse_hamiltonian_obs(data, indices, offsets, observable.wires.tolist()) + + def _pauli_word(self, observable): + """Serialize a :class:`pennylane.pauli.PauliWord` into a Named or Tensor observable.""" + if len(observable) == 1: + wire, pauli = list(observable.items())[0] + return self.named_obs(pauli_name_map[pauli], [wire]) + + return self.tensor_obs( + [self.named_obs(pauli_name_map[pauli], [wire]) for wire, pauli in observable.items()] + ) + + def _pauli_sentence(self, observable): + """Serialize a :class:`pennylane.pauli.PauliSentence` into a Hamiltonian.""" + pwords, coeffs = zip(*observable.items()) + terms = [self._pauli_word(pw) for pw in pwords] + coeffs = np.array(coeffs).astype(self.rtype) + + if self.split_obs: + return [self.hamiltonian_obs([c], [t]) for (c, t) in zip(coeffs, terms)] + return self.hamiltonian_obs(coeffs, terms) + + # pylint: disable=protected-access + def _ob(self, observable): + """Serialize a :class:`pennylane.operation.Observable` into an Observable.""" + if isinstance(observable, Tensor): + return self._tensor_ob(observable) + if observable.name == "Hamiltonian": + return self._hamiltonian(observable) + if observable.name == "SparseHamiltonian": + return self._sparse_hamiltonian(observable) + if isinstance(observable, (PauliX, PauliY, PauliZ, Identity, Hadamard)): + return self._named_obs(observable) + if observable._pauli_rep is not None: + return self._pauli_sentence(observable._pauli_rep) + return self._hermitian_ob(observable) + + def serialize_observables(self, tape: QuantumTape) -> List: + """Serializes the observables of an input tape. + + Args: + tape (QuantumTape): the input quantum tape + + Returns: + list(ObsStructC128 or ObsStructC64): A list of observable objects compatible with + the C++ backend + """ + + serialized_obs = [] + offset_indices = [0] + + for observable in tape.observables: + ser_ob = self._ob(observable) + if isinstance(ser_ob, list): + serialized_obs.extend(ser_ob) + offset_indices.append(offset_indices[-1] + len(ser_ob)) + else: + serialized_obs.append(ser_ob) + offset_indices.append(offset_indices[-1] + 1) + return serialized_obs, offset_indices + + def serialize_ops( + self, tape: QuantumTape + ) -> Tuple[ + List[List[str]], + List[np.ndarray], + List[List[int]], + List[bool], + List[np.ndarray], + List[List[int]], + List[List[bool]], + ]: + """Serializes the operations of an input tape. + + The state preparation operations are not included. + + Args: + tape (QuantumTape): the input quantum tape + + 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, a list of matrices for the operations that do not have a + dedicated kernel, a list of controlled wires and a list of controlled values. + """ + names = [] + params = [] + controlled_wires = [] + controlled_values = [] + wires = [] + mats = [] + + uses_stateprep = False + + def get_wires(operation, single_op): + if operation.name[0:2] == "C(" or operation.name == "MultiControlledX": + name = "PauliX" if operation.name == "MultiControlledX" else operation.base.name + controlled_wires_list = operation.control_wires + if operation.name == "MultiControlledX": + wires_list = list(set(operation.wires) - set(controlled_wires_list)) + else: + wires_list = operation.target_wires + control_values_list = ( + [bool(int(i)) for i in operation.hyperparameters["control_values"]] + if operation.name == "MultiControlledX" + else operation.control_values + ) + if not hasattr(self.sv_type, name): + single_op = QubitUnitary(matrix(single_op.base), single_op.base.wires) + name = single_op.name + else: + name = single_op.name + wires_list = single_op.wires.tolist() + controlled_wires_list = [] + control_values_list = [] + return single_op, name, list(wires_list), controlled_wires_list, control_values_list + + for operation in tape.operations: + if isinstance(operation, (BasisState, StatePrep)): + uses_stateprep = True + continue + if isinstance(operation, Rot): + op_list = operation.expand().operations + else: + op_list = [operation] + + for single_op in op_list: + ( + single_op, + name, + wires_list, + controlled_wires_list, + controlled_values_list, + ) = get_wires(operation, single_op) + names.append(name) + # QubitUnitary is a special case, it has a parameter which is not differentiable. + # We thus pass a dummy 0.0 parameter which will not be referenced + if name == "QubitUnitary": + params.append([0.0]) + mats.append(matrix(single_op)) + elif not hasattr(self.sv_type, name): + params.append([]) + mats.append(matrix(single_op)) + else: + params.append(single_op.parameters) + mats.append([]) + + controlled_values.append(controlled_values_list) + controlled_wires.append(list(controlled_wires_list)) + wires.append(wires_list) + + inverses = [False] * len(names) + return ( + names, + params, + wires, + inverses, + mats, + controlled_wires, + controlled_values, + ), uses_stateprep From 194696af568320588ac4a3eed8debc5932a2da1c Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:14:28 -0500 Subject: [PATCH 011/105] allow lightning.qubit2 to be tested within our suite --- tests/conftest.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e10a3c203c..25b7ada6e6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,7 +87,7 @@ def n_subsystems(request): # Looking for the device for testing. default_device = "lightning.qubit" -supported_devices = {"lightning.kokkos", "lightning.qubit", "lightning.gpu"} +supported_devices = {"lightning.kokkos", "lightning.qubit", "lightning.qubit2", "lightning.gpu"} supported_devices.update({sb.replace(".", "_") for sb in supported_devices}) @@ -113,10 +113,10 @@ def get_device(): device_name = get_device() -if device_name not in qml.plugin_devices: - raise qml.DeviceError( - f"Device {device_name} does not exist. Make sure the required plugin is installed." - ) +# if device_name not in qml.plugin_devices: +# raise qml.DeviceError( +# f"Device {device_name} does not exist. Make sure the required plugin is installed." +# ) # Device specification import pennylane_lightning.lightning_qubit as lightning_ops # Any definition of lightning_ops will do @@ -131,6 +131,11 @@ def get_device(): if hasattr(pennylane_lightning, "lightning_gpu_ops"): import pennylane_lightning.lightning_gpu_ops as lightning_ops +elif device_name == "lightning.qubit2": + from pennylane_lightning.lightning_qubit import LightningQubit2 as LightningDevice + + if hasattr(pennylane_lightning, "lightning_qubit_ops"): + import pennylane_lightning.lightning_qubit_ops as lightning_ops else: from pennylane_lightning.lightning_qubit import LightningQubit as LightningDevice From ecf1bc6e59490b729b7eb997f94c0ad73b3808a2 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:15:01 -0500 Subject: [PATCH 012/105] add tests and CI workflow for lightning_qubit_2 --- .github/workflows/tests_linux.yml | 52 +- tests/lightning_qubit2/test_expval_2.py | 404 ++++++++++++++ tests/lightning_qubit2/test_serialize_2.py | 596 +++++++++++++++++++++ 3 files changed, 1050 insertions(+), 2 deletions(-) create mode 100644 tests/lightning_qubit2/test_expval_2.py create mode 100644 tests/lightning_qubit2/test_serialize_2.py diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index cf34e33a82..71aa8284b9 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -197,6 +197,54 @@ jobs: ./main/.coverage-${{ github.job }}-${{ matrix.pl_backend }} if-no-files-found: error + pythontests_LQ_2: + strategy: + matrix: + os: [ubuntu-22.04] + pl_backend: ["lightning_qubit2"] + timeout-minutes: 30 + name: Python tests + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout PennyLane-Lightning + uses: actions/checkout@v3 + with: + fetch-tags: true + path: main + + - uses: actions/setup-python@v4 + name: Install Python + with: + python-version: '3.9' + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION + + - name: Get required Python packages + run: | + cd main + python -m pip install -r requirements-dev.txt + python -m pip install openfermionpyscf + + - name: Checkout PennyLane for release build + uses: actions/checkout@v3 + with: + path: pennylane + repository: PennyLaneAI/pennylane + + - name: Install backend device + run: | + cd main + CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ + python -m pip install -e . -vv + + - name: Run PennyLane-Lightning unit tests + run: | + cd main/ + DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` + PL_DEVICE=${DEVICENAME} python -m pytest pytest tests/lightning_qubit2/ + cpptestswithOpenBLAS: if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} strategy: @@ -237,7 +285,7 @@ jobs: lcov --directory . -b ../pennylane_lightning/core/src --capture --output-file coverage.info lcov --remove coverage.info '/usr/*' --output-file coverage.info mv coverage.info coverage-${{ github.job }}-${{ matrix.pl_backend }}-Lapack.info - + - name: Upload test results uses: actions/upload-artifact@v3 if: always() @@ -642,7 +690,7 @@ jobs: - name: Combine coverage files run: | - python -m pip install coverage + python -m pip install coverage python -m coverage combine .coverage-python* python -m coverage xml -o coverage-${{ github.job }}.xml diff --git a/tests/lightning_qubit2/test_expval_2.py b/tests/lightning_qubit2/test_expval_2.py new file mode 100644 index 0000000000..cb66797b3d --- /dev/null +++ b/tests/lightning_qubit2/test_expval_2.py @@ -0,0 +1,404 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tests for process and execute (expval calculation). +""" +import pytest + +import numpy as np +import pennylane as qml +from pennylane_lightning.lightning_qubit import LightningQubit2 as LightningQubit +from pennylane.devices import DefaultQubit + +from conftest import LightningDevice # tested device + +if not LightningQubit._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + +if LightningDevice != LightningQubit: + pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) + +THETA = np.linspace(0.11, 1, 3) +PHI = np.linspace(0.32, 1, 3) +VARPHI = np.linspace(0.02, 1, 3) + + +@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) +class TestExpval: + """Test expectation value calculations""" + + @pytest.fixture(params=[np.complex64, np.complex128]) + def dev(self, request): + return LightningQubit(c_dtype=request.param) + + @staticmethod + def calculate_reference(tape): + dev = DefaultQubit(max_workers=1) + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + @staticmethod + def process_and_execute(dev, tape): + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + def test_Identity(self, theta, phi, dev, tol): + """Tests applying identities.""" + + with qml.tape.QuantumTape() as tape: + qml.Identity(wires=[0]) + qml.Identity(wires=[0, 1]) + qml.Identity(wires=[1, 2]) + qml.RX(theta, wires=[0]) + qml.RX(phi, wires=[1]) + qml.expval(qml.PauliX(0)) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_identity_expectation(self, theta, phi, dev, tol): + """Tests identity.""" + + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.Identity(wires=[0])), qml.expval(qml.Identity(wires=[1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_multi_wire_identity_expectation(self, theta, phi, dev, tol): + """Tests multi-wire identity.""" + + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.Identity(wires=[0, 1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_PauliZ_expectation(self, theta, phi, dev, tol): + """Tests PauliZ.""" + + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.PauliZ(wires=[0])), qml.expval(qml.PauliZ(wires=[1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_PauliX_expectation(self, theta, phi, dev, tol): + """Tests PauliX.""" + + tape = qml.tape.QuantumScript( + [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.PauliX(wires=[0])), qml.expval(qml.PauliX(wires=[1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_PauliY_expectation(self, theta, phi, dev, tol): + """Tests PauliY.""" + + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.PauliY(wires=[0])), qml.expval(qml.PauliY(wires=[1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_hadamard_expectation(self, theta, phi, dev, tol): + """Tests Hadamard.""" + + tape = qml.tape.QuantumScript( + [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.Hadamard(wires=[0])), qml.expval(qml.Hadamard(wires=[1]))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_hermitian_expectation(self, theta, phi, dev, tol): + """Tests an Hermitian operator.""" + + with qml.tape.QuantumTape() as tape: + qml.RX(theta, wires=0) + qml.RX(phi, wires=1) + qml.RX(theta + phi, wires=2) + + for idx in range(3): + qml.expval(qml.Hermitian([[1, 0], [0, -1]], wires=[idx])) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_hamiltonian_expectation(self, theta, phi, dev, tol): + """Tests a Hamiltonian.""" + + ham = qml.Hamiltonian( + [1.0, 0.3, 0.3, 0.4], + [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliZ(0), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliY(1), + ], + ) + + with qml.tape.QuantumTape() as tape: + qml.RX(theta, wires=0) + qml.RX(phi, wires=1) + qml.RX(theta + phi, wires=2) + + qml.expval(ham) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_sparse_hamiltonian_expectation(self, theta, phi, dev, tol): + """Tests a Hamiltonian.""" + + ham = qml.SparseHamiltonian( + qml.Hamiltonian( + [1.0, 0.3, 0.3, 0.4], + [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliZ(0), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliY(1), + ], + ).sparse_matrix(), + wires=[0, 1], + ) + + with qml.tape.QuantumTape() as tape: + qml.RX(theta, wires=0) + qml.RX(phi, wires=1) + + qml.expval(ham) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + +@pytest.mark.parametrize("phi", PHI) +class TestOperatorArithmetic: + """Test integration with SProd, Prod, and Sum.""" + + @pytest.fixture(params=[np.complex64, np.complex128]) + def dev(self, request): + return LightningQubit(c_dtype=request.param) + + @staticmethod + def calculate_reference(tape): + dev = DefaultQubit(max_workers=1) + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + @staticmethod + def process_and_execute(dev, tape): + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + def test_s_prod(self, phi, dev, tol): + """Tests the `SProd` class.""" + + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=[0])], + [qml.expval(qml.s_prod(0.5, qml.PauliZ(0)))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_prod(self, phi, dev, tol): + """Tests the `Prod` class.""" + + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=[0]), qml.Hadamard(wires=[1]), qml.PauliZ(wires=[1])], + [qml.expval(qml.prod(qml.PauliZ(0), qml.PauliX(1)))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_sum(self, phi, dev, tol): + """Tests the `Sum` class.""" + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], + [qml.expval(qml.sum(qml.PauliZ(0), qml.PauliX(1)))], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_integration(self, phi, dev, tol): + """Test a Combination of `Sum`, `SProd`, and `Prod`.""" + + obs = qml.sum(qml.s_prod(2.3, qml.PauliZ(0)), -0.5 * qml.prod(qml.PauliY(0), qml.PauliZ(1))) + + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], + [qml.expval(obs)], + ) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + +@pytest.mark.parametrize("theta, phi, varphi", list(zip(THETA, PHI, VARPHI))) +class TestTensorExpval: + """Test tensor expectation values""" + + @pytest.fixture(params=[np.complex64, np.complex128]) + def dev(self, request): + return LightningQubit(c_dtype=request.param) + + @staticmethod + def calculate_reference(tape): + dev = DefaultQubit(max_workers=1) + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + @staticmethod + def process_and_execute(dev, tape): + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + def test_PauliX_PauliY(self, theta, phi, varphi, dev, tol): + """Tests a tensor product involving PauliX and PauliY.""" + + with qml.tape.QuantumTape() as tape: + qml.RX(theta, wires=[0]) + qml.RX(phi, wires=[1]) + qml.RX(varphi, wires=[2]) + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + qml.expval(qml.PauliX(0) @ qml.PauliY(2)) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_PauliZ_identity(self, theta, phi, varphi, dev, tol): + """Tests a tensor product involving PauliZ and Identity.""" + + with qml.tape.QuantumTape() as tape: + qml.Identity(wires=[0]) + qml.RX(theta, wires=[0]) + qml.RX(phi, wires=[1]) + qml.RX(varphi, wires=[2]) + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + qml.expval(qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2)) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) + + def test_PauliZ_hadamard_PauliY(self, theta, phi, varphi, dev, tol): + """Tests a tensor product involving PauliY, PauliZ and Hadamard.""" + + with qml.tape.QuantumTape() as tape: + qml.RX(theta, wires=[0]) + qml.RX(phi, wires=[1]) + qml.RX(varphi, wires=[2]) + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + qml.expval(qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)) + + calculated_val = self.process_and_execute(dev, tape) + reference_val = self.calculate_reference(tape) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + + assert np.allclose(calculated_val, reference_val, tol) diff --git a/tests/lightning_qubit2/test_serialize_2.py b/tests/lightning_qubit2/test_serialize_2.py new file mode 100644 index 0000000000..b957ac8153 --- /dev/null +++ b/tests/lightning_qubit2/test_serialize_2.py @@ -0,0 +1,596 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the serialization helper functions. +""" +import pytest +from conftest import device_name, LightningDevice + +import numpy as np +import pennylane as qml +from pennylane_lightning.lightning_qubit._serialize import QuantumScriptSerializer + +# from pennylane_lightning.lightning_qubit._serialize_old import QuantumScriptSerializer + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + +if device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) +elif device_name == "lightning.gpu": + from pennylane_lightning.lightning_gpu_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) +else: + from pennylane_lightning.lightning_qubit_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) + + +def test_wrong_device_name(): + """Test the device name is not a valid option""" + + with pytest.raises(qml.DeviceError, match="The device name"): + QuantumScriptSerializer("thunder.qubit") + + +@pytest.mark.parametrize( + "obs,obs_type", + [ + (qml.PauliZ(0), NamedObsC128), + (qml.PauliZ(0) @ qml.PauliZ(1), TensorProdObsC128), + (qml.Hadamard(0), NamedObsC128), + (qml.Hermitian(np.eye(2), wires=0), HermitianObsC128), + ( + qml.PauliZ(0) @ qml.Hadamard(1) @ (0.1 * (qml.PauliZ(2) + qml.PauliX(3))), + HamiltonianC128, + ), + ( + ( + qml.Hermitian(np.eye(2), wires=0) + @ qml.Hermitian(np.eye(2), wires=1) + @ qml.Projector([0], wires=2) + ), + TensorProdObsC128, + ), + ( + qml.PauliZ(0) @ qml.Hermitian(np.eye(2), wires=1) @ qml.Projector([0], wires=2), + TensorProdObsC128, + ), + (qml.Projector([0], wires=0), HermitianObsC128), + (qml.Hamiltonian([1], [qml.PauliZ(0)]), HamiltonianC128), + (qml.sum(qml.Hadamard(0), qml.PauliX(1)), HermitianObsC128), + ( + qml.SparseHamiltonian(qml.Hamiltonian([1], [qml.PauliZ(0)]).sparse_matrix(), wires=[0]), + SparseHamiltonianC128, + ), + ], +) +def test_obs_returns_expected_type(obs, obs_type): + """Tests that observables get serialized to the expected type.""" + assert isinstance(QuantumScriptSerializer(device_name)._ob(obs), obs_type) + + +class TestSerializeObs: + """Tests for the serialize_observables function""" + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_tensor_non_tensor_return(self, use_csingle): + """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)) + + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + s_expected = [ + tensor_prod_obs([named_obs("PauliZ", [0]), named_obs("PauliX", [1])]), + named_obs("Hadamard", [1]), + ] + + assert s == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_hermitian_return(self, use_csingle): + """Test expected serialization for a Hermitian return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) + + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + s_expected = hermitian_obs( + np.array( + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + dtype=c_dtype, + ), + [0, 1], + ) + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_hermitian_tensor_return(self, use_csingle): + """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])) + + c_dtype = np.complex64 if use_csingle else np.complex128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + s_expected = tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [2]), + ] + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_mixed_tensor_return(self, use_csingle): + """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)) + + c_dtype = np.complex64 if use_csingle else np.complex128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + s_expected = tensor_prod_obs( + [hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), named_obs("PauliY", [2])] + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_hamiltonian_return(self, use_csingle): + """Test expected serialization for a Hamiltonian return""" + + ham = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + + with qml.tape.QuantumTape() as tape: + qml.expval(ham) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + s_expected = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + ] + ), + tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ], + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_hamiltonian_tensor_return(self, use_csingle): + """Test expected serialization for a Hamiltonian return""" + + with qml.tape.QuantumTape() as tape: + ham = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + qml.expval(ham @ qml.PauliZ(3)) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + # Expression (ham @ obs) is converted internally by Pennylane + # where obs is appended to each term of the ham + s_expected = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + named_obs("PauliZ", [3]), + ] + ), + tensor_prod_obs( + [named_obs("PauliX", [0]), named_obs("PauliY", [2]), named_obs("PauliZ", [3])] + ), + tensor_prod_obs( + [hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), named_obs("PauliZ", [3])] + ), + ], + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_hamiltonian_mix_return(self, use_csingle): + """Test expected serialization for a Hamiltonian return""" + + ham1 = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + ham2 = qml.Hamiltonian( + [0.7, 0.3], + [qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]), qml.PauliY(0) @ qml.PauliX(2)], + ) + + with qml.tape.QuantumTape() as tape: + qml.expval(ham1) + qml.expval(ham2) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + + s_expected1 = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + ] + ), + tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ], + ) + s_expected2 = hamiltonian_obs( + np.array([0.7, 0.3], dtype=r_dtype), + [ + tensor_prod_obs( + [ + named_obs("PauliX", [0]), + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]), + ] + ), + tensor_prod_obs([named_obs("PauliY", [0]), named_obs("PauliX", [2])]), + ], + ) + + assert s[0] == s_expected1 + assert s[1] == s_expected2 + + @pytest.mark.parametrize( + "obs,coeffs,terms", + [ + (qml.prod(qml.PauliZ(0), qml.PauliX(1)), [1], [[("PauliX", 1), ("PauliZ", 0)]]), + (qml.s_prod(0.1, qml.PauliX(0)), [0.1], ("PauliX", 0)), + ( + qml.sum( + 0.5 * qml.prod(qml.PauliX(0), qml.PauliZ(1)), + 0.1 * qml.prod(qml.PauliZ(0), qml.PauliY(1)), + ), + [0.5, 0.1], + [[("PauliZ", 1), ("PauliX", 0)], [("PauliY", 1), ("PauliZ", 0)]], + ), + ], + ) + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms): + """Tests that an arithmetic obs with a PauliRep serializes as a Hamiltonian.""" + tape = qml.tape.QuantumTape(measurements=[qml.expval(obs)]) + res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + assert len(res) == 1 + assert isinstance(res[0], HamiltonianC64 if use_csingle else HamiltonianC128) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + tensor_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + rtype = np.float32 if use_csingle else np.float64 + term_shape = np.array(terms).shape + + if len(term_shape) == 1: # just a single pauli op + expected_terms = [named_obs(terms[0], [terms[1]])] + elif len(term_shape) == 3: # list of tensor products + expected_terms = [ + tensor_obs([named_obs(pauli, [wire]) for pauli, wire in term]) for term in terms + ] + + coeffs = np.array(coeffs).astype(rtype) + assert res[0] == hamiltonian_obs(coeffs, expected_terms) + + @pytest.mark.parametrize("use_csingle", [True, False]) + def test_multi_wire_identity(self, use_csingle): + """Tests that multi-wire Identity does not fail serialization.""" + tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Identity(wires=[1, 2]))]) + res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) + assert len(res) == 1 + + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + assert res[0] == named_obs("Identity", [1]) + + +class TestSerializeOps: + """Tests for the _ops function""" + + 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 = QuantumScriptSerializer(device_name).serialize_ops(tape) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [np.array([0.4]), np.array([0.6]), []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + [[], [], []], + [[], [], []], + ), + False, + ) + assert s == s_expected + + def test_basic_circuit_not_implemented_ctrl_ops(self): + """Test expected serialization for a simple circuit""" + ops = qml.OrbitalRotation(0.1234, wires=range(4)) + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1) + qml.ctrl(ops, [4, 5]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape) + s_expected = ( + ( + ["RX", "RY", "QubitUnitary"], + [np.array([0.4]), np.array([0.6]), [0.0]], + [[0], [1], list(ops.wires)], + [False, False, False], + [[], [], [qml.matrix(ops)]], + [[], [], [4, 5]], + ), + 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(s0, s1) for s0, s1 in zip(s[0][4], s_expected[0][4])) + assert s[0][5] == s_expected[0][5] + assert s[1] == s_expected[1] + + def test_multicontrolledx(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.ctrl(qml.PauliX(wires=0), [1, 2, 3], control_values=[True, False, False]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape) + s_expected = ( + ( + ["RX", "RY", "PauliX"], + [np.array([0.4]), np.array([0.6]), []], + [[0], [1], [0]], + [False, False, False], + [[], [], []], + [[], [], [1, 2, 3]], + [[], [], [True, False, False]], + ), + False, + ) + assert s == s_expected + + @pytest.mark.parametrize("stateprep", [qml.QubitStateVector, qml.StatePrep]) + def test_skips_prep_circuit(self, stateprep): + """Test expected serialization for a simple circuit with state preparation, such that + the state preparation is skipped""" + with qml.tape.QuantumTape() as tape: + stateprep([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 = QuantumScriptSerializer(device_name).serialize_ops(tape) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + [[], [], []], + [[], [], []], + ), + True, + ) + 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.CNOT(wires=[0, 1]) + qml.RZ(0.2, wires=2) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape) + s_expected = ( + ( + ["CNOT", "RZ"], + [[], [0.2]], + [[0, 1], [2]], + [False, False], + ), + False, + ) + assert s[0][0] == s_expected[0][0] + assert s[0][1] == s_expected[0][1] + + # Are we still supporting custom wires??? + # def test_custom_wires_circuit(self): + # """Test expected serialization for a simple circuit with custom wire labels""" + # 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]) + # qml.SingleExcitation(0.5, wires=["a", 3.2]) + # qml.SingleExcitationPlus(0.4, wires=["a", 3.2]) + # qml.adjoint(qml.SingleExcitationMinus(0.5, wires=["a", 3.2]), lazy=False) + + # s = QuantumScriptSerializer(device_name).serialize_ops(tape) + # s_expected = ( + # ( + # [ + # "RX", + # "RY", + # "CNOT", + # "SingleExcitation", + # "SingleExcitationPlus", + # "SingleExcitationMinus", + # ], + # [[0.4], [0.6], [], [0.5], [0.4], [-0.5]], + # [[0], [1], [0, 1], [0, 1], [0, 1], [0, 1]], + # [False, False, False, False, False, False], + # [[], [], [], [], [], []], + # [[], [], [], [], [], []], + # [[], [], [], [], [], []], + # ), + # False, + # ) + # assert s == s_expected + + @pytest.mark.parametrize("C", [True, False]) + def test_integration(self, C): + """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) + qml.CNOT(wires=[0, 1]) + qml.QubitUnitary(np.eye(4), wires=[0, 1]) + qml.templates.QFT(wires=[0, 1, 2]) + qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) + qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) + qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape) + + dtype = np.complex64 if C else np.complex128 + s_expected = ( + ( + [ + "RX", + "RY", + "CNOT", + "QubitUnitary", + "QFT", + "DoubleExcitation", + "DoubleExcitationMinus", + "DoubleExcitationPlus", + ], + [[0.4], [0.6], [], [0.0], [], [0.555], [0.555], [0.555]], + [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]], + [False, False, False, False, False, False, False, False], + [ + [], + [], + [], + qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])), + qml.matrix(qml.templates.QFT(wires=[0, 1, 2])), + [], + [], + [], + ], + ), + 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 all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) From 0460ae277362ad78f61cf9739e17eaa2e95e3917 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:44:57 -0500 Subject: [PATCH 013/105] update CI --- .github/workflows/tests_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index 71aa8284b9..28b4d616ca 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -243,7 +243,7 @@ jobs: run: | cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - PL_DEVICE=${DEVICENAME} python -m pytest pytest tests/lightning_qubit2/ + PL_DEVICE=${DEVICENAME} python -m pytest tests/lightning_qubit2/ cpptestswithOpenBLAS: if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} From 1358706222c2cb060a59c03e003944f67a8a6346 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 09:47:17 -0500 Subject: [PATCH 014/105] update CI --- .github/workflows/tests_linux.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index 28b4d616ca..1e521ec5bb 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -236,8 +236,7 @@ jobs: - name: Install backend device run: | cd main - CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ - python -m pip install -e . -vv + PL_BACKEND=lightning.qubit pip install -e . -vv - name: Run PennyLane-Lightning unit tests run: | From 8c414db87711f6e512477baf9af3d15d0b209edf Mon Sep 17 00:00:00 2001 From: albi3ro Date: Tue, 13 Feb 2024 10:51:34 -0500 Subject: [PATCH 015/105] add wire mapping, black --- .../lightning_qubit/lightning_qubit2.py | 258 ++++++++++-------- 1 file changed, 143 insertions(+), 115 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 30cabef7df..e52190ffa0 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -21,135 +21,160 @@ import pennylane as qml from pennylane.devices import Device, ExecutionConfig, DefaultExecutionConfig -from pennylane.devices.preprocess import decompose, validate_device_wires, decompose, validate_measurements, validate_observables, no_sampling +from pennylane.devices.preprocess import ( + decompose, + validate_device_wires, + decompose, + validate_measurements, + validate_observables, + no_sampling, +) from pennylane.devices.qubit.sampling import get_num_shots_and_executions from pennylane.tape import QuantumScript from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -def dummy_simulate(circuit, rng=None, c_dtype=np.complex128, batch_obs=False, mcmc=False, - kernel_name="Local", num_burnin=100): +def dummy_simulate( + circuit, + rng=None, + c_dtype=np.complex128, + batch_obs=False, + mcmc=False, + kernel_name="Local", + num_burnin=100, +): return tuple(0.0 for _ in circuit.measurements) + def dummy_jacobian(circuit): return np.array(0.0) + def dummy_simulate_and_jacobian(circuit): return np.array(0.0), np.array(0.0) + Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[qml.tape.QuantumTape] QuantumTape_or_Batch = Union[qml.tape.QuantumTape, QuantumTapeBatch] -_operations = frozenset({ - "Identity", - "BasisState", - "QubitStateVector", - "StatePrep", - "QubitUnitary", - "ControlledQubitUnitary", - "MultiControlledX", - "DiagonalQubitUnitary", - "PauliX", - "PauliY", - "PauliZ", - "MultiRZ", - "Hadamard", - "S", - "Adjoint(S)", - "T", - "Adjoint(T)", - "SX", - "Adjoint(SX)", - "CNOT", - "SWAP", - "ISWAP", - "PSWAP", - "Adjoint(ISWAP)", - "SISWAP", - "Adjoint(SISWAP)", - "SQISW", - "CSWAP", - "Toffoli", - "CY", - "CZ", - "PhaseShift", - "ControlledPhaseShift", - "CPhase", - "RX", - "RY", - "RZ", - "Rot", - "CRX", - "CRY", - "CRZ", - "C(PauliX)", - "C(PauliY)", - "C(PauliZ)", - "C(Hadamard)", - "C(S)", - "C(T)", - "C(PhaseShift)", - "C(RX)", - "C(RY)", - "C(RZ)", - "C(SWAP)", - "C(IsingXX)", - "C(IsingXY)", - "C(IsingYY)", - "C(IsingZZ)", - "C(SingleExcitation)", - "C(SingleExcitationMinus)", - "C(SingleExcitationPlus)", - "C(DoubleExcitation)", - "C(DoubleExcitationMinus)", - "C(DoubleExcitationPlus)", - "CRot", - "IsingXX", - "IsingYY", - "IsingZZ", - "IsingXY", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - "DoubleExcitation", - "DoubleExcitationPlus", - "DoubleExcitationMinus", - "QubitCarry", - "QubitSum", - "OrbitalRotation", - "QFT", - "ECR", - "BlockEncode", -}) - -_observables = frozenset({ - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "Hermitian", - "Identity", - "Projector", - "SparseHamiltonian", - "Hamiltonian", - "Sum", - "SProd", - "Prod", - "Exp", -}) +_operations = frozenset( + { + "Identity", + "BasisState", + "QubitStateVector", + "StatePrep", + "QubitUnitary", + "ControlledQubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + "PauliX", + "PauliY", + "PauliZ", + "MultiRZ", + "Hadamard", + "S", + "Adjoint(S)", + "T", + "Adjoint(T)", + "SX", + "Adjoint(SX)", + "CNOT", + "SWAP", + "ISWAP", + "PSWAP", + "Adjoint(ISWAP)", + "SISWAP", + "Adjoint(SISWAP)", + "SQISW", + "CSWAP", + "Toffoli", + "CY", + "CZ", + "PhaseShift", + "ControlledPhaseShift", + "CPhase", + "RX", + "RY", + "RZ", + "Rot", + "CRX", + "CRY", + "CRZ", + "C(PauliX)", + "C(PauliY)", + "C(PauliZ)", + "C(Hadamard)", + "C(S)", + "C(T)", + "C(PhaseShift)", + "C(RX)", + "C(RY)", + "C(RZ)", + "C(SWAP)", + "C(IsingXX)", + "C(IsingXY)", + "C(IsingYY)", + "C(IsingZZ)", + "C(SingleExcitation)", + "C(SingleExcitationMinus)", + "C(SingleExcitationPlus)", + "C(DoubleExcitation)", + "C(DoubleExcitationMinus)", + "C(DoubleExcitationPlus)", + "CRot", + "IsingXX", + "IsingYY", + "IsingZZ", + "IsingXY", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + "DoubleExcitation", + "DoubleExcitationPlus", + "DoubleExcitationMinus", + "QubitCarry", + "QubitSum", + "OrbitalRotation", + "QFT", + "ECR", + "BlockEncode", + } +) + +_observables = frozenset( + { + "PauliX", + "PauliY", + "PauliZ", + "Hadamard", + "Hermitian", + "Identity", + "Projector", + "SparseHamiltonian", + "Hamiltonian", + "Sum", + "SProd", + "Prod", + "Exp", + } +) + def stopping_condition(op: qml.operation.Operator) -> bool: return op.name in _operations + def accepted_observables(obs: qml.operation.Operator) -> bool: return obs.name in _observables -def accepted_measurements(m : qml.measurements.MeasurementProcess) -> bool: + +def accepted_measurements(m: qml.measurements.MeasurementProcess) -> bool: return isinstance(m, (qml.measurements.ExpectationMP)) + class LightningQubit2(Device): """PennyLane Lightning Qubit device. @@ -182,7 +207,7 @@ class LightningQubit2(Device): qubit is built with OpenMP. """ - name = 'lightning.qubit2' + name = "lightning.qubit2" _device_options = ["rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin"] @@ -224,17 +249,14 @@ def __init__( # pylint: disable=too-many-arguments @property def operation(self) -> frozenset[str]: - """The names of supported operations. - """ + """The names of supported operations.""" return _operations @property def observables(self) -> frozenset[str]: - """The names of supported observables. - """ + """The names of supported observables.""" return _observables - def _setup_execution_config(self, config): updated_values = {} if config.gradient_method == "best": @@ -251,7 +273,6 @@ def _setup_execution_config(self, config): return replace(config, **updated_values, device_options=new_device_options) - def supports_derivatives( self, execution_config: Optional[ExecutionConfig] = None, @@ -263,12 +284,16 @@ def supports_derivatives( return False if circuit is None: return True - return all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) and not circuit.shots - + return ( + all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) + and not circuit.shots + ) def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): program = TransformProgram() - program.add_transform(validate_measurements, analytic_measurements=accepted_measurements, name=self.name) + program.add_transform( + validate_measurements, analytic_measurements=accepted_measurements, name=self.name + ) program.add_transform(no_sampling) program.add_transform(validate_observables, accepted_observables, name=self.name) program.add_transform(validate_device_wires, self.wires, name=self.name) @@ -295,7 +320,11 @@ def _execute_tracking(self, circuits): ) self.tracker.record() - def execute(self, circuits : QuantumTape_or_Batch, execution_config: ExecutionConfig = DefaultExecutionConfig) -> Result_or_ResultBatch: + def execute( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ) -> Result_or_ResultBatch: is_single_circuit = False if isinstance(circuits, QuantumScript): is_single_circuit = True @@ -306,11 +335,11 @@ def execute(self, circuits : QuantumTape_or_Batch, execution_config: ExecutionCo results = [] for circuit in circuits: + circuit = circuit.map_to_standard_wires() results.append(dummy_simulate(circuit, **execution_config.device_options)) return results[0] if is_single_circuit else tuple(results) - def compute_derivatives( self, circuits: QuantumTape_or_Batch, @@ -328,7 +357,6 @@ def compute_derivatives( return res[0] if is_single_circuit else res - def execute_and_compute_derivatives( self, circuits: QuantumTape_or_Batch, @@ -351,4 +379,4 @@ def execute_and_compute_derivatives( results = tuple(dummy_simulate_and_jacobian(c) for c in circuits) results, jacs = tuple(zip(*results)) - return (results[0], jacs[0]) if is_single_circuit else (results, jacs) \ No newline at end of file + return (results[0], jacs[0]) if is_single_circuit else (results, jacs) From 695f4b6b42709724964557af3f6e8409c8393541 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 11:23:28 -0500 Subject: [PATCH 016/105] add tests for custom wires --- .../lightning_qubit/lightning_qubit2.py | 2 +- tests/lightning_qubit2/test_expval_2.py | 12 +++++-- tests/lightning_qubit2/test_serialize_2.py | 33 ------------------- 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index a751311066..a4a093862a 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -238,7 +238,7 @@ class LightningQubit2(Device): def __init__( # pylint: disable=too-many-arguments self, - wires=None, # the number of wires are always needed for Lightning. Do we have a better way to get it + wires=None, *, c_dtype=np.complex128, shots=None, diff --git a/tests/lightning_qubit2/test_expval_2.py b/tests/lightning_qubit2/test_expval_2.py index cb66797b3d..f7d44fbc3f 100644 --- a/tests/lightning_qubit2/test_expval_2.py +++ b/tests/lightning_qubit2/test_expval_2.py @@ -105,11 +105,19 @@ def test_multi_wire_identity_expectation(self, theta, phi, dev, tol): assert np.allclose(calculated_val, reference_val, tol) - def test_PauliZ_expectation(self, theta, phi, dev, tol): + @pytest.mark.parametrize( + "wires", + [ + ([0, 1]), + (["a", 1]), + (["b", "a"]), + ], + ) + def test_PauliZ_expectation(self, theta, phi, dev, tol, wires): """Tests PauliZ.""" tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.RX(theta, wires=wires[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], [qml.expval(qml.PauliZ(wires=[0])), qml.expval(qml.PauliZ(wires=[1]))], ) diff --git a/tests/lightning_qubit2/test_serialize_2.py b/tests/lightning_qubit2/test_serialize_2.py index b957ac8153..7f14422c38 100644 --- a/tests/lightning_qubit2/test_serialize_2.py +++ b/tests/lightning_qubit2/test_serialize_2.py @@ -510,39 +510,6 @@ def test_unsupported_kernel_circuit(self): assert s[0][0] == s_expected[0][0] assert s[0][1] == s_expected[0][1] - # Are we still supporting custom wires??? - # def test_custom_wires_circuit(self): - # """Test expected serialization for a simple circuit with custom wire labels""" - # 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]) - # qml.SingleExcitation(0.5, wires=["a", 3.2]) - # qml.SingleExcitationPlus(0.4, wires=["a", 3.2]) - # qml.adjoint(qml.SingleExcitationMinus(0.5, wires=["a", 3.2]), lazy=False) - - # s = QuantumScriptSerializer(device_name).serialize_ops(tape) - # s_expected = ( - # ( - # [ - # "RX", - # "RY", - # "CNOT", - # "SingleExcitation", - # "SingleExcitationPlus", - # "SingleExcitationMinus", - # ], - # [[0.4], [0.6], [], [0.5], [0.4], [-0.5]], - # [[0], [1], [0, 1], [0, 1], [0, 1], [0, 1]], - # [False, False, False, False, False, False], - # [[], [], [], [], [], []], - # [[], [], [], [], [], []], - # [[], [], [], [], [], []], - # ), - # False, - # ) - # assert s == s_expected - @pytest.mark.parametrize("C", [True, False]) def test_integration(self, C): """Test expected serialization for a random circuit""" From 91777b30946f95d9620e71addeffc7d7c93e0f47 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 11:26:22 -0500 Subject: [PATCH 017/105] add tests for custom wires --- tests/lightning_qubit2/test_expval_2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit2/test_expval_2.py b/tests/lightning_qubit2/test_expval_2.py index f7d44fbc3f..78553c3e44 100644 --- a/tests/lightning_qubit2/test_expval_2.py +++ b/tests/lightning_qubit2/test_expval_2.py @@ -117,8 +117,8 @@ def test_PauliZ_expectation(self, theta, phi, dev, tol, wires): """Tests PauliZ.""" tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=wires[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.PauliZ(wires=[0])), qml.expval(qml.PauliZ(wires=[1]))], + [qml.RX(theta, wires=wires[0]), qml.RX(phi, wires=wires[1]), qml.CNOT(wires=wires)], + [qml.expval(qml.PauliZ(wires=wires[0])), qml.expval(qml.PauliZ(wires=wires[1]))], ) calculated_val = self.process_and_execute(dev, tape) From 6d98910cd9d069354b99352b8c6abff3c057f417 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 13:35:36 -0500 Subject: [PATCH 018/105] add review suggestions --- .../lightning_qubit/_measurements.py | 14 +------ .../lightning_qubit/_state_vector.py | 41 ++++--------------- .../lightning_qubit/lightning_qubit2.py | 9 ++-- 3 files changed, 15 insertions(+), 49 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index a1de9000c9..ffc2a8912e 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -151,7 +151,7 @@ def measurement(self, measurementprocess: MeasurementProcess) -> TensorLike: """ return self.get_measurement_function(measurementprocess)(measurementprocess) - def measure_final_state(self, circuit: QuantumScript, is_state_batched) -> Result: + def measure_final_state(self, circuit: QuantumScript) -> Result: """ Perform the measurements required by the circuit on the provided state. @@ -159,24 +159,14 @@ def measure_final_state(self, circuit: QuantumScript, is_state_batched) -> Resul Args: circuit (QuantumScript): The single circuit to simulate - is_state_batched (bool): Whether the state has a batch dimension or not. Returns: Tuple[TensorLike]: The measurement results """ - if set(circuit.wires) != set(range(circuit.num_wires)): - wire_map = {w: i for i, w in enumerate(circuit.wires)} - circuit = qml.map_wires(circuit, wire_map) if not circuit.shots: # analytic case if len(circuit.measurements) == 1: return self.measurement(circuit.measurements[0]) - results = [] - for mp in circuit.measurements: - measure_val = self.measurement(mp) - results += [ - measure_val, - ] - return tuple(results) + return tuple(self.measurement(mp) for mp in circuit.measurements) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index a4e4c0ca01..66c1d410b6 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -234,7 +234,6 @@ def _apply_state_vector(self, state, device_wires): ravelled_indices, state = self._preprocess_state_vector(state, device_wires) # translate to wire labels used by device - device_wires = self.map_wires(device_wires) output_shape = [2] * self.num_wires if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: @@ -270,20 +269,13 @@ def _apply_lightning_controlled(self, operation): """ state = self.state_vector - basename = "PauliX" if operation.name == "MultiControlledX" else operation.base.name + basename = operation.base.name if basename == "Identity": return method = getattr(state, f"{basename}", None) - control_wires = self.wires.indices(operation.control_wires) - control_values = ( - [bool(int(i)) for i in operation.hyperparameters["control_values"]] - if operation.name == "MultiControlledX" - else operation.control_values - ) - if operation.name == "MultiControlledX": - target_wires = list(set(self.wires.indices(operation.wires)) - set(control_wires)) - else: - target_wires = self.wires.indices(operation.target_wires) + control_wires = list(operation.control_wires) + control_values = operation.control_values + target_wires = list(operation.target_wires) if method is not None: # apply n-controlled specialized gate inv = False param = operation.parameters @@ -321,16 +313,13 @@ def apply_lightning(self, operations): if name == "Identity": continue method = getattr(state, name, None) - wires = self.wires.indices(operation.wires) + wires = list(operation.wires) if method is not None: # apply specialized gate inv = False param = operation.parameters method(wires, inv, param) - elif ( - name[0:2] == "C(" or name == "ControlledQubitUnitary" or name == "MultiControlledX" - ): # apply n-controlled gate - print("hi") + elif isinstance(operation, qml.ops.Controlled): # apply n-controlled gate self._apply_lightning_controlled(operation) else: # apply gate as a matrix # Inverse can be set to False since qml.matrix(operation) is already in @@ -353,16 +342,9 @@ def apply_operations(self, operations): self._apply_basis_state(operations[0].parameters[0], operations[0].wires) operations = operations[1:] - for operation in operations: - if isinstance(operation, (StatePrep, BasisState)): - raise DeviceError( - f"Operation {operation.name} cannot be used after other " - f"Operations have already been applied on a {self.short_name} device." - ) - self.apply_lightning(operations) - def get_final_state(self, circuit: QuantumScript, debugger=None): + def get_final_state(self, circuit: QuantumScript): """ Get the final state that results from executing the given quantum script. @@ -370,17 +352,12 @@ def get_final_state(self, circuit: QuantumScript, debugger=None): Args: circuit (QuantumScript): The single circuit to simulate - debugger (._Debugger): The debugger to use Returns: Tuple: A tuple containing the Lightning final state handler of the quantum script and whether the state has a batch dimension. """ + self.apply_operations(circuit.operations) - circuit = circuit.map_to_standard_wires() - self.apply_operations(circuit._ops) - - # No batching support yet. - - return self, False # is_state_batched + return self diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index a4a093862a..077b4875e2 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -50,14 +50,13 @@ LQ_CPP_BINARY_AVAILABLE = False -def simulate(circuit: QuantumScript, dtype=np.complex128, debugger=None) -> Result: +def simulate(circuit: QuantumScript, dtype=np.complex128) -> Result: """Simulate a single quantum script. Args: circuit (QuantumTape): The single circuit to simulate dtype: Datatypes for state-vector representation. Must be one of ``np.complex64`` or ``np.complex128``. - debugger (_Debugger): The debugger to use Returns: tuple(TensorLike): The results of the simulation @@ -65,10 +64,10 @@ def simulate(circuit: QuantumScript, dtype=np.complex128, debugger=None) -> Resu Note that this function can return measurements for non-commuting observables simultaneously. """ - state, is_state_batched = LightningStateVector( + state = LightningStateVector( num_wires=circuit.num_wires, dtype=dtype - ).get_final_state(circuit, debugger=debugger) - return LightningMeasurements(state).measure_final_state(circuit, is_state_batched) + ).get_final_state(circuit) + return LightningMeasurements(state).measure_final_state(circuit) def dummy_jacobian(circuit: QuantumScript): From a4147e9aa5d3fe4542f4d43669e65225f3551ac5 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 13 Feb 2024 13:36:23 -0500 Subject: [PATCH 019/105] format --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 077b4875e2..de79f1d143 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -64,9 +64,7 @@ def simulate(circuit: QuantumScript, dtype=np.complex128) -> Result: Note that this function can return measurements for non-commuting observables simultaneously. """ - state = LightningStateVector( - num_wires=circuit.num_wires, dtype=dtype - ).get_final_state(circuit) + state = LightningStateVector(num_wires=circuit.num_wires, dtype=dtype).get_final_state(circuit) return LightningMeasurements(state).measure_final_state(circuit) From d784b5f2d9146bc18dce87d4d471cc03c3712682 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Fri, 16 Feb 2024 14:01:53 -0500 Subject: [PATCH 020/105] remove python class to reverse order of PRs --- .../lightning_qubit/lightning_qubit2.py | 349 +-------------- tests/lightning_qubit2/test_expval_2.py | 412 ------------------ 2 files changed, 3 insertions(+), 758 deletions(-) delete mode 100644 tests/lightning_qubit2/test_expval_2.py diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index de79f1d143..e169b5d077 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -20,19 +20,9 @@ import numpy as np import pennylane as qml -from pennylane.devices import Device, ExecutionConfig, DefaultExecutionConfig -from pennylane.devices.preprocess import ( - decompose, - validate_device_wires, - decompose, - validate_measurements, - validate_observables, - no_sampling, -) -from pennylane.devices.qubit.sampling import get_num_shots_and_executions + + from pennylane.tape import QuantumScript -from pennylane.transforms.core import TransformProgram -from pennylane.typing import Result, ResultBatch from ._state_vector import LightningStateVector from ._measurements import LightningMeasurements @@ -73,337 +63,4 @@ def dummy_jacobian(circuit: QuantumScript): def simulate_and_jacobian(circuit: QuantumScript): - return np.array(0.0), np.array(0.0) - - -Result_or_ResultBatch = Union[Result, ResultBatch] -QuantumTapeBatch = Sequence[qml.tape.QuantumTape] -QuantumTape_or_Batch = Union[qml.tape.QuantumTape, QuantumTapeBatch] - - -_operations = frozenset( - { - "Identity", - "BasisState", - "QubitStateVector", - "StatePrep", - "QubitUnitary", - "ControlledQubitUnitary", - "MultiControlledX", - "DiagonalQubitUnitary", - "PauliX", - "PauliY", - "PauliZ", - "MultiRZ", - "Hadamard", - "S", - "Adjoint(S)", - "T", - "Adjoint(T)", - "SX", - "Adjoint(SX)", - "CNOT", - "SWAP", - "ISWAP", - "PSWAP", - "Adjoint(ISWAP)", - "SISWAP", - "Adjoint(SISWAP)", - "SQISW", - "CSWAP", - "Toffoli", - "CY", - "CZ", - "PhaseShift", - "ControlledPhaseShift", - "CPhase", - "RX", - "RY", - "RZ", - "Rot", - "CRX", - "CRY", - "CRZ", - "C(PauliX)", - "C(PauliY)", - "C(PauliZ)", - "C(Hadamard)", - "C(S)", - "C(T)", - "C(PhaseShift)", - "C(RX)", - "C(RY)", - "C(RZ)", - "C(SWAP)", - "C(IsingXX)", - "C(IsingXY)", - "C(IsingYY)", - "C(IsingZZ)", - "C(SingleExcitation)", - "C(SingleExcitationMinus)", - "C(SingleExcitationPlus)", - "C(DoubleExcitation)", - "C(DoubleExcitationMinus)", - "C(DoubleExcitationPlus)", - "CRot", - "IsingXX", - "IsingYY", - "IsingZZ", - "IsingXY", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - "DoubleExcitation", - "DoubleExcitationPlus", - "DoubleExcitationMinus", - "QubitCarry", - "QubitSum", - "OrbitalRotation", - "QFT", - "ECR", - "BlockEncode", - } -) - -_observables = frozenset( - { - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "Hermitian", - "Identity", - "Projector", - "SparseHamiltonian", - "Hamiltonian", - "Sum", - "SProd", - "Prod", - "Exp", - } -) - - -def stopping_condition(op: qml.operation.Operator) -> bool: - return op.name in _operations - - -def accepted_observables(obs: qml.operation.Operator) -> bool: - return obs.name in _observables - - -def accepted_measurements(m: qml.measurements.MeasurementProcess) -> bool: - return isinstance(m, (qml.measurements.ExpectationMP)) - - -class LightningQubit2(Device): - """PennyLane Lightning Qubit device. - - A device that interfaces with C++ to perform fast linear algebra calculations. - - Use of this device requires pre-built binaries or compilation from source. Check out the - :doc:`/lightning_qubit/installation` guide for more details. - - Args: - wires (int): the number of wires to initialize the device with - c_dtype: Datatypes for statevector representation. Must be one of - ``np.complex64`` or ``np.complex128``. - shots (int): How many times the circuit should be evaluated (or sampled) to estimate - the expectation values. Defaults to ``None`` if not specified. Setting - to ``None`` results in computing statistics like expectation values and - variances analytically. - seed (str, int, rng) - mcmc (bool): Determine whether to use the approximate Markov Chain Monte Carlo - sampling method when generating samples. - kernel_name (str): name of transition kernel. The current version supports - two kernels: ``"Local"`` and ``"NonZeroRandom"``. - The local kernel conducts a bit-flip local transition between states. - The local kernel generates a random qubit site and then generates a random - number to determine the new bit at that qubit site. The ``"NonZeroRandom"`` kernel - randomly transits between states that have nonzero probability. - num_burnin (int): number of steps that will be dropped. Increasing this value will - result in a closer approximation but increased runtime. - batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - qubit is built with OpenMP. - """ - - name = "lightning.qubit2" - _CPP_BINARY_AVAILABLE = LQ_CPP_BINARY_AVAILABLE - - _device_options = ["rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin"] - - def __init__( # pylint: disable=too-many-arguments - self, - wires=None, - *, - c_dtype=np.complex128, - shots=None, - seed="global", - mcmc=False, - kernel_name="Local", - num_burnin=100, - batch_obs=False, - ): - super().__init__(wires=wires, shots=shots) - seed = np.random.randint(0, high=10000000) if seed == "global" else seed - self._rng = np.random.default_rng(seed) - - self._c_dtype = c_dtype - self._batch_obs = batch_obs - self._mcmc = mcmc - if self._mcmc: - if kernel_name not in [ - "Local", - "NonZeroRandom", - ]: - raise NotImplementedError( - f"The {kernel_name} is not supported and currently " - "only 'Local' and 'NonZeroRandom' kernels are supported." - ) - if num_burnin >= shots: - raise ValueError("Shots should be greater than num_burnin.") - self._kernel_name = kernel_name - self._num_burnin = num_burnin - else: - self._kernel_name = None - self._num_burnin = None - - @property - def c_dtype(self): - """State vector complex data type.""" - return self._c_dtype - - @property - def operation(self) -> frozenset[str]: - """The names of supported operations.""" - return _operations - - @property - def observables(self) -> frozenset[str]: - """The names of supported observables.""" - return _observables - - def _setup_execution_config(self, config): - updated_values = {} - if config.gradient_method == "best": - updated_values["gradient_method"] = "adjoint" - if config.use_device_gradient is None: - updated_values["use_device_gradient"] = config.gradient_method in ("best", "adjoint") - if config.grad_on_execution is None: - updated_values["grad_on_execution"] = True - - new_device_options = dict(config.device_options) - for option in self._device_options: - if option not in new_device_options: - new_device_options[option] = getattr(self, f"_{option}", None) - - return replace(config, **updated_values, device_options=new_device_options) - - def supports_derivatives( - self, - execution_config: Optional[ExecutionConfig] = None, - circuit: Optional[qml.tape.QuantumTape] = None, - ) -> bool: - if execution_config is None and circuit is None: - return True - if execution_config.gradient_method not in {"adjoint", "best"}: - return False - if circuit is None: - return True - return ( - all(isinstance(m, qml.measurements.ExpectationMP) for m in circuit.measurements) - and not circuit.shots - ) - - def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): - program = TransformProgram() - program.add_transform( - validate_measurements, analytic_measurements=accepted_measurements, name=self.name - ) - program.add_transform(no_sampling) - program.add_transform(validate_observables, accepted_observables, name=self.name) - program.add_transform(validate_device_wires, self.wires, name=self.name) - program.add_transform(qml.defer_measurements, device=self) - program.add_transform(decompose, stopping_condition=stopping_condition, name=self.name) - program.add_transform(qml.transforms.broadcast_expand) - return program, self._setup_execution_config(execution_config) - - def _execute_tracking(self, circuits): - self.tracker.update(batches=1) - self.tracker.record() - for c in circuits: - qpu_executions, shots = get_num_shots_and_executions(c) - if c.shots: - self.tracker.update( - simulations=1, - executions=qpu_executions, - shots=shots, - ) - else: - self.tracker.update( - simulations=1, - executions=qpu_executions, - ) - self.tracker.record() - - def execute( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ) -> Result_or_ResultBatch: - is_single_circuit = False - if isinstance(circuits, QuantumScript): - is_single_circuit = True - circuits = (circuits,) - - if self.tracker.active: - self._execute_tracking(circuits) - - results = [] - for circuit in circuits: - circuit = circuit.map_to_standard_wires() - results.append(simulate(circuit, **execution_config.device_options)) - - return results[0] if is_single_circuit else tuple(results) - - def compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - is_single_circuit = False - if isinstance(circuits, QuantumScript): - is_single_circuit = True - circuits = [circuits] - - if self.tracker.active: - self.tracker.update(derivative_batches=1, derivatives=len(circuits)) - self.tracker.record() - res = tuple(dummy_jacobian(circuit) for circuit in circuits) - - return res[0] if is_single_circuit else res - - def execute_and_compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - is_single_circuit = False - if isinstance(circuits, QuantumScript): - is_single_circuit = True - circuits = [circuits] - - if self.tracker.active: - for c in circuits: - self.tracker.update(resources=c.specs["resources"]) - self.tracker.update( - execute_and_derivative_batches=1, - executions=len(circuits), - derivatives=len(circuits), - ) - self.tracker.record() - - results = tuple(simulate_and_jacobian(c) for c in circuits) - results, jacs = tuple(zip(*results)) - return (results[0], jacs[0]) if is_single_circuit else (results, jacs) + return np.array(0.0), np.array(0.0) \ No newline at end of file diff --git a/tests/lightning_qubit2/test_expval_2.py b/tests/lightning_qubit2/test_expval_2.py deleted file mode 100644 index 78553c3e44..0000000000 --- a/tests/lightning_qubit2/test_expval_2.py +++ /dev/null @@ -1,412 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for process and execute (expval calculation). -""" -import pytest - -import numpy as np -import pennylane as qml -from pennylane_lightning.lightning_qubit import LightningQubit2 as LightningQubit -from pennylane.devices import DefaultQubit - -from conftest import LightningDevice # tested device - -if not LightningQubit._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - -if LightningDevice != LightningQubit: - pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) - -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) -VARPHI = np.linspace(0.02, 1, 3) - - -@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) -class TestExpval: - """Test expectation value calculations""" - - @pytest.fixture(params=[np.complex64, np.complex128]) - def dev(self, request): - return LightningQubit(c_dtype=request.param) - - @staticmethod - def calculate_reference(tape): - dev = DefaultQubit(max_workers=1) - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - @staticmethod - def process_and_execute(dev, tape): - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - def test_Identity(self, theta, phi, dev, tol): - """Tests applying identities.""" - - with qml.tape.QuantumTape() as tape: - qml.Identity(wires=[0]) - qml.Identity(wires=[0, 1]) - qml.Identity(wires=[1, 2]) - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.expval(qml.PauliX(0)) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_identity_expectation(self, theta, phi, dev, tol): - """Tests identity.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.Identity(wires=[0])), qml.expval(qml.Identity(wires=[1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_multi_wire_identity_expectation(self, theta, phi, dev, tol): - """Tests multi-wire identity.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.Identity(wires=[0, 1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - @pytest.mark.parametrize( - "wires", - [ - ([0, 1]), - (["a", 1]), - (["b", "a"]), - ], - ) - def test_PauliZ_expectation(self, theta, phi, dev, tol, wires): - """Tests PauliZ.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=wires[0]), qml.RX(phi, wires=wires[1]), qml.CNOT(wires=wires)], - [qml.expval(qml.PauliZ(wires=wires[0])), qml.expval(qml.PauliZ(wires=wires[1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliX_expectation(self, theta, phi, dev, tol): - """Tests PauliX.""" - - tape = qml.tape.QuantumScript( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.PauliX(wires=[0])), qml.expval(qml.PauliX(wires=[1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliY_expectation(self, theta, phi, dev, tol): - """Tests PauliY.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.PauliY(wires=[0])), qml.expval(qml.PauliY(wires=[1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_hadamard_expectation(self, theta, phi, dev, tol): - """Tests Hadamard.""" - - tape = qml.tape.QuantumScript( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.Hadamard(wires=[0])), qml.expval(qml.Hadamard(wires=[1]))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_hermitian_expectation(self, theta, phi, dev, tol): - """Tests an Hermitian operator.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - for idx in range(3): - qml.expval(qml.Hermitian([[1, 0], [0, -1]], wires=[idx])) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_hamiltonian_expectation(self, theta, phi, dev, tol): - """Tests a Hamiltonian.""" - - ham = qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ) - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - qml.expval(ham) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_sparse_hamiltonian_expectation(self, theta, phi, dev, tol): - """Tests a Hamiltonian.""" - - ham = qml.SparseHamiltonian( - qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ).sparse_matrix(), - wires=[0, 1], - ) - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - - qml.expval(ham) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -@pytest.mark.parametrize("phi", PHI) -class TestOperatorArithmetic: - """Test integration with SProd, Prod, and Sum.""" - - @pytest.fixture(params=[np.complex64, np.complex128]) - def dev(self, request): - return LightningQubit(c_dtype=request.param) - - @staticmethod - def calculate_reference(tape): - dev = DefaultQubit(max_workers=1) - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - @staticmethod - def process_and_execute(dev, tape): - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - def test_s_prod(self, phi, dev, tol): - """Tests the `SProd` class.""" - - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0])], - [qml.expval(qml.s_prod(0.5, qml.PauliZ(0)))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_prod(self, phi, dev, tol): - """Tests the `Prod` class.""" - - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0]), qml.Hadamard(wires=[1]), qml.PauliZ(wires=[1])], - [qml.expval(qml.prod(qml.PauliZ(0), qml.PauliX(1)))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_sum(self, phi, dev, tol): - """Tests the `Sum` class.""" - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], - [qml.expval(qml.sum(qml.PauliZ(0), qml.PauliX(1)))], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_integration(self, phi, dev, tol): - """Test a Combination of `Sum`, `SProd`, and `Prod`.""" - - obs = qml.sum(qml.s_prod(2.3, qml.PauliZ(0)), -0.5 * qml.prod(qml.PauliY(0), qml.PauliZ(1))) - - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], - [qml.expval(obs)], - ) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -@pytest.mark.parametrize("theta, phi, varphi", list(zip(THETA, PHI, VARPHI))) -class TestTensorExpval: - """Test tensor expectation values""" - - @pytest.fixture(params=[np.complex64, np.complex128]) - def dev(self, request): - return LightningQubit(c_dtype=request.param) - - @staticmethod - def calculate_reference(tape): - dev = DefaultQubit(max_workers=1) - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - @staticmethod - def process_and_execute(dev, tape): - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - def test_PauliX_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliX and PauliY.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliX(0) @ qml.PauliY(2)) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_identity(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliZ and Identity.""" - - with qml.tape.QuantumTape() as tape: - qml.Identity(wires=[0]) - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2)) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_hadamard_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliY, PauliZ and Hadamard.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)) - - calculated_val = self.process_and_execute(dev, tape) - reference_val = self.calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) From 87779ea85337cd108f5a6e82d1127d635f9e0d02 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 16 Feb 2024 15:17:03 -0500 Subject: [PATCH 021/105] update simulate to get a LightningStateVector --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index e169b5d077..0b2e555121 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -40,7 +40,7 @@ LQ_CPP_BINARY_AVAILABLE = False -def simulate(circuit: QuantumScript, dtype=np.complex128) -> Result: +def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.complex128) -> Result: """Simulate a single quantum script. Args: @@ -54,8 +54,8 @@ def simulate(circuit: QuantumScript, dtype=np.complex128) -> Result: Note that this function can return measurements for non-commuting observables simultaneously. """ - state = LightningStateVector(num_wires=circuit.num_wires, dtype=dtype).get_final_state(circuit) - return LightningMeasurements(state).measure_final_state(circuit) + final_state = state.get_final_state(circuit) + return LightningMeasurements(final_state).measure_final_state(circuit) def dummy_jacobian(circuit: QuantumScript): From 9bbff300e8561de68b25d0f96ca4702aee6f6042 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 16 Feb 2024 15:23:38 -0500 Subject: [PATCH 022/105] add reset state --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 0b2e555121..27a2bd7b74 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -54,6 +54,7 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.compl Note that this function can return measurements for non-commuting observables simultaneously. """ + state = state.reset_state() final_state = state.get_final_state(circuit) return LightningMeasurements(final_state).measure_final_state(circuit) @@ -63,4 +64,4 @@ def dummy_jacobian(circuit: QuantumScript): def simulate_and_jacobian(circuit: QuantumScript): - return np.array(0.0), np.array(0.0) \ No newline at end of file + return np.array(0.0), np.array(0.0) From b4ed92b718df52db00f5f38cc6c5cb10b243876e Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 16 Feb 2024 15:26:05 -0500 Subject: [PATCH 023/105] update simulate --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 27a2bd7b74..63d7bedc9f 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -54,8 +54,8 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.compl Note that this function can return measurements for non-commuting observables simultaneously. """ - state = state.reset_state() - final_state = state.get_final_state(circuit) + final_state = state.reset_state() + final_state = final_state.get_final_state(circuit) return LightningMeasurements(final_state).measure_final_state(circuit) From 16cd1f82034ce5b507502c107b931ce80b0fa68b Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 16 Feb 2024 15:27:31 -0500 Subject: [PATCH 024/105] update docs --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 63d7bedc9f..9f46a69de3 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -45,6 +45,7 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.compl Args: circuit (QuantumTape): The single circuit to simulate + state (LightningStateVector): handle to Lightning state vector dtype: Datatypes for state-vector representation. Must be one of ``np.complex64`` or ``np.complex128``. From cd2828a6a562ebea308f9036d60d307d797e9c79 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 16 Feb 2024 15:31:26 -0500 Subject: [PATCH 025/105] add Result import --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 9f46a69de3..08a3a28251 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -23,6 +23,7 @@ from pennylane.tape import QuantumScript +from pennylane.typing import Result from ._state_vector import LightningStateVector from ._measurements import LightningMeasurements From a4350b59fc8fe04a111791efd8933df29492a64e Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:06:12 -0500 Subject: [PATCH 026/105] Update pennylane_lightning/lightning_qubit/_measurements.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_measurements.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index ffc2a8912e..466493ac8e 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -164,9 +164,10 @@ def measure_final_state(self, circuit: QuantumScript) -> Result: Tuple[TensorLike]: The measurement results """ - if not circuit.shots: - # analytic case - if len(circuit.measurements) == 1: - return self.measurement(circuit.measurements[0]) + if circuit.shots: + raise NotImplementedError + # analytic case + if len(circuit.measurements) == 1: + return self.measurement(circuit.measurements[0]) - return tuple(self.measurement(mp) for mp in circuit.measurements) + return tuple(self.measurement(mp) for mp in circuit.measurements) From 80de99572788d5ef4367de08d1a41959ca7120f5 Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:06:29 -0500 Subject: [PATCH 027/105] Update pennylane_lightning/lightning_qubit/_measurements.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_measurements.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 466493ac8e..3b0fe873f2 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -26,7 +26,6 @@ import numpy as np from typing import Callable, List -import pennylane as qml from pennylane.measurements import StateMeasurement, MeasurementProcess, ExpectationMP from pennylane.typing import TensorLike, Result from pennylane.tape import QuantumScript From dc3149f864b0074e4b16627fbbeae306a86654ef Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 11:23:05 -0500 Subject: [PATCH 028/105] fix reset state --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 08a3a28251..6b32d644c2 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -56,8 +56,8 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.compl Note that this function can return measurements for non-commuting observables simultaneously. """ - final_state = state.reset_state() - final_state = final_state.get_final_state(circuit) + state.reset_state() + final_state = state.get_final_state(circuit) return LightningMeasurements(final_state).measure_final_state(circuit) From 378e3763fa4704cd1e5fb6bd4afc45e626a9fdcf Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:57:43 -0500 Subject: [PATCH 029/105] Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_state_vector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 66c1d410b6..cc0e4a0d2d 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -169,7 +169,6 @@ def _preprocess_state_vector(self, state, device_wires): """ # translate to wire labels used by device - device_wires = self.map_wires(device_wires) # special case for integral types if state.dtype.kind == "i": From 7a5c01020fada181fceae3e9fefa1a48fe7c89c7 Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:58:02 -0500 Subject: [PATCH 030/105] Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_state_vector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index cc0e4a0d2d..8aa22c77e1 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -201,7 +201,6 @@ def _get_basis_state_index(self, state, wires): int: basis state index """ # translate to wire labels used by device - device_wires = self.map_wires(wires) # length of basis state parameter n_basis_state = len(state) From b23eabf80fe95040bd78cb7a9537068f6020bff8 Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:59:18 -0500 Subject: [PATCH 031/105] Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_state_vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 8aa22c77e1..8feeae0727 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -172,8 +172,8 @@ def _preprocess_state_vector(self, state, device_wires): # special case for integral types if state.dtype.kind == "i": - state = qml.numpy.array(state, dtype=self.C_DTYPE) - state = self._asarray(state, dtype=self.C_DTYPE) + state = qml.numpy.array(state, dtype=self.dtype) + state = self._asarray(state, dtype=self.dtype) if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: return None, state From 112d2ffc5762cd771d7af9b227875f406918f9f4 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:11:10 -0500 Subject: [PATCH 032/105] remove LightningQubit2 references --- pennylane_lightning/lightning_qubit/__init__.py | 1 - tests/conftest.py | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/__init__.py b/pennylane_lightning/lightning_qubit/__init__.py index 9d8ddab62d..53e50cbf00 100644 --- a/pennylane_lightning/lightning_qubit/__init__.py +++ b/pennylane_lightning/lightning_qubit/__init__.py @@ -15,4 +15,3 @@ from pennylane_lightning.core import __version__ from .lightning_qubit import LightningQubit -from .lightning_qubit2 import LightningQubit2 diff --git a/tests/conftest.py b/tests/conftest.py index 25b7ada6e6..d04117520c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,7 +87,7 @@ def n_subsystems(request): # Looking for the device for testing. default_device = "lightning.qubit" -supported_devices = {"lightning.kokkos", "lightning.qubit", "lightning.qubit2", "lightning.gpu"} +supported_devices = {"lightning.kokkos", "lightning.qubit", "lightning.gpu"} supported_devices.update({sb.replace(".", "_") for sb in supported_devices}) @@ -131,11 +131,6 @@ def get_device(): if hasattr(pennylane_lightning, "lightning_gpu_ops"): import pennylane_lightning.lightning_gpu_ops as lightning_ops -elif device_name == "lightning.qubit2": - from pennylane_lightning.lightning_qubit import LightningQubit2 as LightningDevice - - if hasattr(pennylane_lightning, "lightning_qubit_ops"): - import pennylane_lightning.lightning_qubit_ops as lightning_ops else: from pennylane_lightning.lightning_qubit import LightningQubit as LightningDevice From 1a8fe19731dbdbbd5b4edf8c3581edad18796dcb Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:15:11 -0500 Subject: [PATCH 033/105] remove unnecessary modules --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 6b32d644c2..fb28c8cfbd 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -15,13 +15,8 @@ This module contains the LightningQubit2 class that inherits from the new device interface. """ -from typing import Union, Sequence, Optional -from dataclasses import replace import numpy as np -import pennylane as qml - - from pennylane.tape import QuantumScript from pennylane.typing import Result From 9ded7989af699c6f9a4bb2a046252d850ec61175 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:15:57 -0500 Subject: [PATCH 034/105] merging Serializer classes --- pennylane_lightning/core/_serialize.py | 48 ++- .../lightning_qubit/_serialize.py | 390 ------------------ 2 files changed, 27 insertions(+), 411 deletions(-) delete mode 100644 pennylane_lightning/lightning_qubit/_serialize.py diff --git a/pennylane_lightning/core/_serialize.py b/pennylane_lightning/core/_serialize.py index 5e4a37213c..50810608c7 100644 --- a/pennylane_lightning/core/_serialize.py +++ b/pennylane_lightning/core/_serialize.py @@ -171,26 +171,26 @@ def sparse_hamiltonian_obs(self): ) return self.sparse_hamiltonian_c64 if self.use_csingle else self.sparse_hamiltonian_c128 - def _named_obs(self, observable, wires_map: dict): + def _named_obs(self, observable, wires_map: dict = None): """Serializes a Named observable""" - wires = [wires_map[w] for w in observable.wires] + wires = [wires_map[w] for w in observable.wires] if wires_map else observable.wires.tolist() if observable.name == "Identity": wires = wires[:1] return self.named_obs(observable.name, wires) - def _hermitian_ob(self, observable, wires_map: dict): + def _hermitian_ob(self, observable, wires_map: dict = None): """Serializes a Hermitian observable""" assert not isinstance(observable, Tensor) - wires = [wires_map[w] for w in observable.wires] + wires = [wires_map[w] for w in observable.wires] if wires_map else observable.wires.tolist() return self.hermitian_obs(matrix(observable).ravel().astype(self.ctype), wires) - def _tensor_ob(self, observable, wires_map: dict): + def _tensor_ob(self, observable, wires_map: dict = None): """Serialize a tensor observable""" assert isinstance(observable, Tensor) return self.tensor_obs([self._ob(obs, wires_map) for obs in observable.obs]) - def _hamiltonian(self, observable, wires_map: dict): + def _hamiltonian(self, observable, wires_map: dict = None): coeffs = np.array(unwrap(observable.coeffs)).astype(self.rtype) terms = [self._ob(t, wires_map) for t in observable.ops] @@ -199,7 +199,7 @@ def _hamiltonian(self, observable, wires_map: dict): return self.hamiltonian_obs(coeffs, terms) - def _sparse_hamiltonian(self, observable, wires_map: dict): + def _sparse_hamiltonian(self, observable, wires_map: dict = None): """Serialize an observable (Sparse Hamiltonian) Args: @@ -214,7 +214,7 @@ def _sparse_hamiltonian(self, observable, wires_map: dict): Hmat = Hamiltonian([1.0], [Identity(0)]).sparse_matrix() H_sparse = SparseHamiltonian(Hmat, wires=range(1)) spm = H_sparse.sparse_matrix() - # Only root 0 needs the overall sparsematrix data + # Only root 0 needs the overall sparse matrix data if self._mpi_manager().getRank() == 0: spm = observable.sparse_matrix() self._mpi_manager().Barrier() @@ -224,26 +224,28 @@ def _sparse_hamiltonian(self, observable, wires_map: dict): indices = np.array(spm.indices).astype(np.int64) offsets = np.array(spm.indptr).astype(np.int64) - wires = [] - wires_list = observable.wires.tolist() - wires.extend([wires_map[w] for w in wires_list]) + wires = [wires_map[w] for w in observable.wires] if wires_map else observable.wires.tolist() return self.sparse_hamiltonian_obs(data, indices, offsets, wires) - def _pauli_word(self, observable, wires_map: dict): + def _pauli_word(self, observable, wires_map: dict = None): """Serialize a :class:`pennylane.pauli.PauliWord` into a Named or Tensor observable.""" + + def map_wire(wire: int): + return wires_map[wire] if wires_map else wire + if len(observable) == 1: wire, pauli = list(observable.items())[0] - return self.named_obs(pauli_name_map[pauli], [wires_map[wire]]) + return self.named_obs(pauli_name_map[pauli], [map_wire(wire)]) return self.tensor_obs( [ - self.named_obs(pauli_name_map[pauli], [wires_map[wire]]) + self.named_obs(pauli_name_map[pauli], [map_wire(wire)]) for wire, pauli in observable.items() ] ) - def _pauli_sentence(self, observable, wires_map: dict): + def _pauli_sentence(self, observable, wires_map: dict = None): """Serialize a :class:`pennylane.pauli.PauliSentence` into a Hamiltonian.""" pwords, coeffs = zip(*observable.items()) terms = [self._pauli_word(pw, wires_map) for pw in pwords] @@ -254,7 +256,7 @@ def _pauli_sentence(self, observable, wires_map: dict): return self.hamiltonian_obs(coeffs, terms) # pylint: disable=protected-access - def _ob(self, observable, wires_map): + def _ob(self, observable, wires_map: dict = None): """Serialize a :class:`pennylane.operation.Observable` into an Observable.""" if isinstance(observable, Tensor): return self._tensor_ob(observable, wires_map) @@ -268,7 +270,7 @@ def _ob(self, observable, wires_map): return self._pauli_sentence(observable._pauli_rep, wires_map) return self._hermitian_ob(observable, wires_map) - def serialize_observables(self, tape: QuantumTape, wires_map: dict) -> List: + def serialize_observables(self, tape: QuantumTape, wires_map: dict = None) -> List: """Serializes the observables of an input tape. Args: @@ -294,7 +296,7 @@ def serialize_observables(self, tape: QuantumTape, wires_map: dict) -> List: return serialized_obs, offset_indices def serialize_ops( - self, tape: QuantumTape, wires_map: dict + self, tape: QuantumTape, wires_map: dict = None ) -> Tuple[ List[List[str]], List[np.ndarray], @@ -348,7 +350,7 @@ def get_wires(operation, single_op): wires_list = single_op.wires.tolist() controlled_wires_list = [] control_values_list = [] - return single_op, name, wires_list, controlled_wires_list, control_values_list + return single_op, name, list(wires_list), controlled_wires_list, control_values_list for operation in tape.operations: if isinstance(operation, (BasisState, StatePrep)): @@ -381,8 +383,12 @@ def get_wires(operation, single_op): mats.append([]) controlled_values.append(controlled_values_list) - controlled_wires.append([wires_map[w] for w in controlled_wires_list]) - wires.append([wires_map[w] for w in wires_list]) + controlled_wires.append( + [wires_map[w] for w in controlled_wires_list] + if wires_map + else list(controlled_wires_list) + ) + wires.append([wires_map[w] for w in wires_list] if wires_map else wires_list) inverses = [False] * len(names) return ( diff --git a/pennylane_lightning/lightning_qubit/_serialize.py b/pennylane_lightning/lightning_qubit/_serialize.py deleted file mode 100644 index 2553126c6f..0000000000 --- a/pennylane_lightning/lightning_qubit/_serialize.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -r""" -Helper functions for serializing quantum tapes. -""" -from typing import List, Tuple -import numpy as np -from pennylane import ( - BasisState, - Hadamard, - PauliX, - PauliY, - PauliZ, - Identity, - StatePrep, - Rot, - Hamiltonian, - SparseHamiltonian, - QubitUnitary, -) -from pennylane.operation import Tensor -from pennylane.tape import QuantumTape -from pennylane.math import unwrap - -from pennylane import matrix, DeviceError - -pauli_name_map = { - "I": "Identity", - "X": "PauliX", - "Y": "PauliY", - "Z": "PauliZ", -} - - -class QuantumScriptSerializer: - """Serializer class for `pennylane.tape.QuantumScript` data. - - Args: - device_name: device shortname. - use_csingle (bool): whether to use np.complex64 instead of np.complex128 - - """ - - # pylint: disable=import-outside-toplevel, too-many-instance-attributes - def __init__( - self, device_name, use_csingle: bool = False, use_mpi: bool = False, split_obs: bool = False - ): - self.use_csingle = use_csingle - self.device_name = device_name - self.split_obs = split_obs - if device_name == "lightning.qubit" or device_name == "lightning.qubit2": - try: - import pennylane_lightning.lightning_qubit_ops as lightning_ops - except ImportError as exception: - raise ImportError( - f"Pre-compiled binaries for {device_name}" - " serialize functionality are not available." - ) from exception - elif device_name == "lightning.kokkos": - try: - import pennylane_lightning.lightning_kokkos_ops as lightning_ops - except ImportError as exception: - raise ImportError( - f"Pre-compiled binaries for {device_name}" - " serialize functionality are not available." - ) from exception - elif device_name == "lightning.gpu": - try: - import pennylane_lightning.lightning_gpu_ops as lightning_ops - except ImportError as exception: - raise ImportError( - f"Pre-compiled binaries for {device_name} are not available." - ) from exception - else: - raise DeviceError(f'The device name "{device_name}" is not a valid option.') - self.statevector_c64 = lightning_ops.StateVectorC64 - self.statevector_c128 = lightning_ops.StateVectorC128 - self.named_obs_c64 = lightning_ops.observables.NamedObsC64 - self.named_obs_c128 = lightning_ops.observables.NamedObsC128 - self.hermitian_obs_c64 = lightning_ops.observables.HermitianObsC64 - self.hermitian_obs_c128 = lightning_ops.observables.HermitianObsC128 - self.tensor_prod_obs_c64 = lightning_ops.observables.TensorProdObsC64 - self.tensor_prod_obs_c128 = lightning_ops.observables.TensorProdObsC128 - self.hamiltonian_c64 = lightning_ops.observables.HamiltonianC64 - self.hamiltonian_c128 = lightning_ops.observables.HamiltonianC128 - self.sparse_hamiltonian_c64 = lightning_ops.observables.SparseHamiltonianC64 - self.sparse_hamiltonian_c128 = lightning_ops.observables.SparseHamiltonianC128 - - self._use_mpi = use_mpi - - if self._use_mpi: - self.statevector_mpi_c64 = lightning_ops.StateVectorMPIC64 - self.statevector_mpi_c128 = lightning_ops.StateVectorMPIC128 - self.named_obs_mpi_c64 = lightning_ops.observablesMPI.NamedObsMPIC64 - self.named_obs_mpi_c128 = lightning_ops.observablesMPI.NamedObsMPIC128 - self.hermitian_obs_mpi_c64 = lightning_ops.observablesMPI.HermitianObsMPIC64 - self.hermitian_obs_mpi_c128 = lightning_ops.observablesMPI.HermitianObsMPIC128 - self.tensor_prod_obs_mpi_c64 = lightning_ops.observablesMPI.TensorProdObsMPIC64 - self.tensor_prod_obs_mpi_c128 = lightning_ops.observablesMPI.TensorProdObsMPIC128 - self.hamiltonian_mpi_c64 = lightning_ops.observablesMPI.HamiltonianMPIC64 - self.hamiltonian_mpi_c128 = lightning_ops.observablesMPI.HamiltonianMPIC128 - self.sparse_hamiltonian_mpi_c64 = lightning_ops.observablesMPI.SparseHamiltonianMPIC64 - self.sparse_hamiltonian_mpi_c128 = lightning_ops.observablesMPI.SparseHamiltonianMPIC128 - - self._mpi_manager = lightning_ops.MPIManager - - @property - def ctype(self): - """Complex type.""" - return np.complex64 if self.use_csingle else np.complex128 - - @property - def rtype(self): - """Real type.""" - return np.float32 if self.use_csingle else np.float64 - - @property - def sv_type(self): - """State vector matching ``use_csingle`` precision (and MPI if it is supported).""" - if self._use_mpi: - return self.statevector_mpi_c64 if self.use_csingle else self.statevector_mpi_c128 - return self.statevector_c64 if self.use_csingle else self.statevector_c128 - - @property - def named_obs(self): - """Named observable matching ``use_csingle`` precision.""" - if self._use_mpi: - return self.named_obs_mpi_c64 if self.use_csingle else self.named_obs_mpi_c128 - return self.named_obs_c64 if self.use_csingle else self.named_obs_c128 - - @property - def hermitian_obs(self): - """Hermitian observable matching ``use_csingle`` precision.""" - if self._use_mpi: - return self.hermitian_obs_mpi_c64 if self.use_csingle else self.hermitian_obs_mpi_c128 - return self.hermitian_obs_c64 if self.use_csingle else self.hermitian_obs_c128 - - @property - def tensor_obs(self): - """Tensor product observable matching ``use_csingle`` precision.""" - if self._use_mpi: - return ( - self.tensor_prod_obs_mpi_c64 if self.use_csingle else self.tensor_prod_obs_mpi_c128 - ) - return self.tensor_prod_obs_c64 if self.use_csingle else self.tensor_prod_obs_c128 - - @property - def hamiltonian_obs(self): - """Hamiltonian observable matching ``use_csingle`` precision.""" - if self._use_mpi: - return self.hamiltonian_mpi_c64 if self.use_csingle else self.hamiltonian_mpi_c128 - return self.hamiltonian_c64 if self.use_csingle else self.hamiltonian_c128 - - @property - def sparse_hamiltonian_obs(self): - """SparseHamiltonian observable matching ``use_csingle`` precision.""" - if self._use_mpi: - return ( - self.sparse_hamiltonian_mpi_c64 - if self.use_csingle - else self.sparse_hamiltonian_mpi_c128 - ) - return self.sparse_hamiltonian_c64 if self.use_csingle else self.sparse_hamiltonian_c128 - - def _named_obs(self, observable): - """Serializes a Named observable""" - wires = observable.wires.tolist() - if observable.name == "Identity": - wires = wires[:1] - return self.named_obs(observable.name, wires) - - def _hermitian_ob(self, observable): - """Serializes a Hermitian observable""" - assert not isinstance(observable, Tensor) - - return self.hermitian_obs( - matrix(observable).ravel().astype(self.ctype), observable.wires.tolist() - ) - - def _tensor_ob(self, observable): - """Serialize a tensor observable""" - assert isinstance(observable, Tensor) - return self.tensor_obs([self._ob(obs) for obs in observable.obs]) - - def _hamiltonian(self, observable): - coeffs = np.array(unwrap(observable.coeffs)).astype(self.rtype) - terms = [self._ob(t) for t in observable.ops] - - if self.split_obs: - return [self.hamiltonian_obs([c], [t]) for (c, t) in zip(coeffs, terms)] - - return self.hamiltonian_obs(coeffs, terms) - - def _sparse_hamiltonian(self, observable): - """Serialize an observable (Sparse Hamiltonian) - - Args: - observable (Observable): the input observable (Sparse Hamiltonian) - wire_map (dict): a dictionary mapping input wires to the device's backend wires - - Returns: - sparse_hamiltonian_obs (SparseHamiltonianC64 or SparseHamiltonianC128): A Sparse Hamiltonian observable object compatible with the C++ backend - """ - - if self._use_mpi: - Hmat = Hamiltonian([1.0], [Identity(0)]).sparse_matrix() - H_sparse = SparseHamiltonian(Hmat, wires=range(1)) - spm = H_sparse.sparse_matrix() - # Only root 0 needs the overall sparse matrix data - if self._mpi_manager().getRank() == 0: - spm = observable.sparse_matrix() - self._mpi_manager().Barrier() - else: - spm = observable.sparse_matrix() - data = np.array(spm.data).astype(self.ctype) - indices = np.array(spm.indices).astype(np.int64) - offsets = np.array(spm.indptr).astype(np.int64) - - return self.sparse_hamiltonian_obs(data, indices, offsets, observable.wires.tolist()) - - def _pauli_word(self, observable): - """Serialize a :class:`pennylane.pauli.PauliWord` into a Named or Tensor observable.""" - if len(observable) == 1: - wire, pauli = list(observable.items())[0] - return self.named_obs(pauli_name_map[pauli], [wire]) - - return self.tensor_obs( - [self.named_obs(pauli_name_map[pauli], [wire]) for wire, pauli in observable.items()] - ) - - def _pauli_sentence(self, observable): - """Serialize a :class:`pennylane.pauli.PauliSentence` into a Hamiltonian.""" - pwords, coeffs = zip(*observable.items()) - terms = [self._pauli_word(pw) for pw in pwords] - coeffs = np.array(coeffs).astype(self.rtype) - - if self.split_obs: - return [self.hamiltonian_obs([c], [t]) for (c, t) in zip(coeffs, terms)] - return self.hamiltonian_obs(coeffs, terms) - - # pylint: disable=protected-access - def _ob(self, observable): - """Serialize a :class:`pennylane.operation.Observable` into an Observable.""" - if isinstance(observable, Tensor): - return self._tensor_ob(observable) - if observable.name == "Hamiltonian": - return self._hamiltonian(observable) - if observable.name == "SparseHamiltonian": - return self._sparse_hamiltonian(observable) - if isinstance(observable, (PauliX, PauliY, PauliZ, Identity, Hadamard)): - return self._named_obs(observable) - if observable._pauli_rep is not None: - return self._pauli_sentence(observable._pauli_rep) - return self._hermitian_ob(observable) - - def serialize_observables(self, tape: QuantumTape) -> List: - """Serializes the observables of an input tape. - - Args: - tape (QuantumTape): the input quantum tape - - Returns: - list(ObsStructC128 or ObsStructC64): A list of observable objects compatible with - the C++ backend - """ - - serialized_obs = [] - offset_indices = [0] - - for observable in tape.observables: - ser_ob = self._ob(observable) - if isinstance(ser_ob, list): - serialized_obs.extend(ser_ob) - offset_indices.append(offset_indices[-1] + len(ser_ob)) - else: - serialized_obs.append(ser_ob) - offset_indices.append(offset_indices[-1] + 1) - return serialized_obs, offset_indices - - def serialize_ops( - self, tape: QuantumTape - ) -> Tuple[ - List[List[str]], - List[np.ndarray], - List[List[int]], - List[bool], - List[np.ndarray], - List[List[int]], - List[List[bool]], - ]: - """Serializes the operations of an input tape. - - The state preparation operations are not included. - - Args: - tape (QuantumTape): the input quantum tape - - 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, a list of matrices for the operations that do not have a - dedicated kernel, a list of controlled wires and a list of controlled values. - """ - names = [] - params = [] - controlled_wires = [] - controlled_values = [] - wires = [] - mats = [] - - uses_stateprep = False - - def get_wires(operation, single_op): - if operation.name[0:2] == "C(" or operation.name == "MultiControlledX": - name = "PauliX" if operation.name == "MultiControlledX" else operation.base.name - controlled_wires_list = operation.control_wires - if operation.name == "MultiControlledX": - wires_list = list(set(operation.wires) - set(controlled_wires_list)) - else: - wires_list = operation.target_wires - control_values_list = ( - [bool(int(i)) for i in operation.hyperparameters["control_values"]] - if operation.name == "MultiControlledX" - else operation.control_values - ) - if not hasattr(self.sv_type, name): - single_op = QubitUnitary(matrix(single_op.base), single_op.base.wires) - name = single_op.name - else: - name = single_op.name - wires_list = single_op.wires.tolist() - controlled_wires_list = [] - control_values_list = [] - return single_op, name, list(wires_list), controlled_wires_list, control_values_list - - for operation in tape.operations: - if isinstance(operation, (BasisState, StatePrep)): - uses_stateprep = True - continue - if isinstance(operation, Rot): - op_list = operation.expand().operations - else: - op_list = [operation] - - for single_op in op_list: - ( - single_op, - name, - wires_list, - controlled_wires_list, - controlled_values_list, - ) = get_wires(operation, single_op) - names.append(name) - # QubitUnitary is a special case, it has a parameter which is not differentiable. - # We thus pass a dummy 0.0 parameter which will not be referenced - if name == "QubitUnitary": - params.append([0.0]) - mats.append(matrix(single_op)) - elif not hasattr(self.sv_type, name): - params.append([]) - mats.append(matrix(single_op)) - else: - params.append(single_op.parameters) - mats.append([]) - - controlled_values.append(controlled_values_list) - controlled_wires.append(list(controlled_wires_list)) - wires.append(wires_list) - - inverses = [False] * len(names) - return ( - names, - params, - wires, - inverses, - mats, - controlled_wires, - controlled_values, - ), uses_stateprep From 2e43fd663f444b7046be4eeaef7d4fdb05fa9f2b Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:16:20 -0500 Subject: [PATCH 035/105] update serialize tests --- tests/test_serialize.py | 83 ++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index d4bba3cce1..01ba41dbbd 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -105,10 +105,11 @@ def test_wrong_device_name(): ], ) def test_obs_returns_expected_type(obs, obs_type): - """Tests that observables get serialized to the expected type.""" + """Tests that observables get serialized to the expected type, with and without wires map""" assert isinstance( QuantumScriptSerializer(device_name)._ob(obs, dict(enumerate(obs.wires))), obs_type ) + assert isinstance(QuantumScriptSerializer(device_name)._ob(obs), obs_type) class TestSerializeObs: @@ -117,7 +118,8 @@ class TestSerializeObs: wires_dict = {i: i for i in range(10)} @pytest.mark.parametrize("use_csingle", [True, False]) - def test_tensor_non_tensor_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_tensor_non_tensor_return(self, use_csingle, wires_map): """Test expected serialization for a mixture of tensor product and non-tensor product return""" with qml.tape.QuantumTape() as tape: @@ -127,19 +129,19 @@ def test_tensor_non_tensor_return(self, use_csingle): tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 named_obs = NamedObsC64 if use_csingle else NamedObsC128 - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict - ) - s_expected = [ tensor_prod_obs([named_obs("PauliZ", [0]), named_obs("PauliX", [1])]), named_obs("Hadamard", [1]), ] + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) assert s == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hermitian_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hermitian_return(self, use_csingle, wires_map): """Test expected serialization for a Hermitian return""" with qml.tape.QuantumTape() as tape: qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) @@ -148,7 +150,7 @@ def test_hermitian_return(self, use_csingle): c_dtype = np.complex64 if use_csingle else np.complex128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) s_expected = hermitian_obs( np.array( @@ -160,7 +162,8 @@ def test_hermitian_return(self, use_csingle): assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hermitian_tensor_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hermitian_tensor_return(self, use_csingle, wires_map): """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])) @@ -169,7 +172,7 @@ def test_hermitian_tensor_return(self, use_csingle): tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) s_expected = tensor_prod_obs( @@ -182,7 +185,8 @@ def test_hermitian_tensor_return(self, use_csingle): assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_mixed_tensor_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_mixed_tensor_return(self, use_csingle, wires_map): """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)) @@ -193,7 +197,7 @@ def test_mixed_tensor_return(self, use_csingle): named_obs = NamedObsC64 if use_csingle else NamedObsC128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) s_expected = tensor_prod_obs( @@ -203,7 +207,8 @@ def test_mixed_tensor_return(self, use_csingle): assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_return(self, use_csingle, wires_map): """Test expected serialization for a Hamiltonian return""" ham = qml.Hamiltonian( @@ -226,7 +231,7 @@ def test_hamiltonian_return(self, use_csingle): c_dtype = np.complex64 if use_csingle else np.complex128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) s_expected = hamiltonian_obs( @@ -246,7 +251,8 @@ def test_hamiltonian_return(self, use_csingle): assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_tensor_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_tensor_return(self, use_csingle, wires_map): """Test expected serialization for a Hamiltonian return""" with qml.tape.QuantumTape() as tape: @@ -268,7 +274,7 @@ def test_hamiltonian_tensor_return(self, use_csingle): c_dtype = np.complex64 if use_csingle else np.complex128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) # Expression (ham @ obs) is converted internally by Pennylane @@ -295,7 +301,8 @@ def test_hamiltonian_tensor_return(self, use_csingle): assert s[0] == s_expected @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_mix_return(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_mix_return(self, use_csingle, wires_map): """Test expected serialization for a Hamiltonian return""" ham1 = qml.Hamiltonian( @@ -323,7 +330,7 @@ def test_hamiltonian_mix_return(self, use_csingle): c_dtype = np.complex64 if use_csingle else np.complex128 s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) s_expected1 = hamiltonian_obs( @@ -371,11 +378,12 @@ def test_hamiltonian_mix_return(self, use_csingle): ], ) @pytest.mark.parametrize("use_csingle", [True, False]) - def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms, wires_map): """Tests that an arithmetic obs with a PauliRep serializes as a Hamiltonian.""" tape = qml.tape.QuantumTape(measurements=[qml.expval(obs)]) res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) assert len(res) == 1 assert isinstance(res[0], HamiltonianC64 if use_csingle else HamiltonianC128) @@ -397,11 +405,12 @@ def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms): assert res[0] == hamiltonian_obs(coeffs, expected_terms) @pytest.mark.parametrize("use_csingle", [True, False]) - def test_multi_wire_identity(self, use_csingle): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_multi_wire_identity(self, use_csingle, wires_map): """Tests that multi-wire Identity does not fail serialization.""" tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Identity(wires=[1, 2]))]) res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, self.wires_dict + tape, wires_map ) assert len(res) == 1 @@ -414,14 +423,15 @@ class TestSerializeOps: wires_dict = {i: i for i in range(10)} - def test_basic_circuit(self): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_basic_circuit(self, wires_map): """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 = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) s_expected = ( ( ["RX", "RY", "CNOT"], @@ -436,7 +446,8 @@ def test_basic_circuit(self): ) assert s == s_expected - def test_basic_circuit_not_implemented_ctrl_ops(self): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_basic_circuit_not_implemented_ctrl_ops(self, wires_map): """Test expected serialization for a simple circuit""" ops = qml.OrbitalRotation(0.1234, wires=range(4)) with qml.tape.QuantumTape() as tape: @@ -444,7 +455,7 @@ def test_basic_circuit_not_implemented_ctrl_ops(self): qml.RY(0.6, wires=1) qml.ctrl(ops, [4, 5]) - s = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) s_expected = ( ( ["RX", "RY", "QubitUnitary"], @@ -464,14 +475,15 @@ def test_basic_circuit_not_implemented_ctrl_ops(self): assert s[0][5] == s_expected[0][5] assert s[1] == s_expected[1] - def test_multicontrolledx(self): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_multicontrolledx(self, wires_map): """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.ctrl(qml.PauliX(wires=0), [1, 2, 3], control_values=[True, False, False]) - s = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) s_expected = ( ( ["RX", "RY", "PauliX"], @@ -486,8 +498,9 @@ def test_multicontrolledx(self): ) assert s == s_expected + @pytest.mark.parametrize("wires_map", [wires_dict, None]) @pytest.mark.parametrize("stateprep", [qml.QubitStateVector, qml.StatePrep]) - def test_skips_prep_circuit(self, stateprep): + def test_skips_prep_circuit(self, stateprep, wires_map): """Test expected serialization for a simple circuit with state preparation, such that the state preparation is skipped""" with qml.tape.QuantumTape() as tape: @@ -497,7 +510,7 @@ def test_skips_prep_circuit(self, stateprep): qml.RY(0.6, wires=1) qml.CNOT(wires=[0, 1]) - s = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) s_expected = ( ( ["RX", "RY", "CNOT"], @@ -512,14 +525,15 @@ def test_skips_prep_circuit(self, stateprep): ) assert s == s_expected - def test_unsupported_kernel_circuit(self): + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_unsupported_kernel_circuit(self, wires_map): """Test expected serialization for a circuit including gates that do not have a dedicated kernel""" with qml.tape.QuantumTape() as tape: qml.CNOT(wires=[0, 1]) qml.RZ(0.2, wires=2) - s = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) s_expected = ( ( ["CNOT", "RZ"], @@ -565,8 +579,9 @@ def test_custom_wires_circuit(self): ) assert s == s_expected + @pytest.mark.parametrize("wires_map", [wires_dict, None]) @pytest.mark.parametrize("C", [True, False]) - def test_integration(self, C): + def test_integration(self, C, wires_map): """Test expected serialization for a random circuit""" with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=0) @@ -578,7 +593,7 @@ def test_integration(self, C): qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) - s = QuantumScriptSerializer(device_name).serialize_ops(tape, self.wires_dict) + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) dtype = np.complex64 if C else np.complex128 s_expected = ( From f80c363db6068e2e9f984fa8858a4537d6f18f75 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:16:47 -0500 Subject: [PATCH 036/105] update measurements with new serialize class --- pennylane_lightning/lightning_qubit/_measurements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 3b0fe873f2..2dddd2e5b0 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -33,6 +33,7 @@ from typing import List +from pennylane_lightning.core._serialize import QuantumScriptSerializer from ._serialize import QuantumScriptSerializer from ._state_vector import LightningStateVector From 4f4b29e63cb8ccccc9feda8eee1647967996b4c3 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 21 Feb 2024 15:22:13 -0500 Subject: [PATCH 037/105] remove outdated test --- tests/lightning_qubit2/test_serialize_2.py | 563 --------------------- 1 file changed, 563 deletions(-) delete mode 100644 tests/lightning_qubit2/test_serialize_2.py diff --git a/tests/lightning_qubit2/test_serialize_2.py b/tests/lightning_qubit2/test_serialize_2.py deleted file mode 100644 index 7f14422c38..0000000000 --- a/tests/lightning_qubit2/test_serialize_2.py +++ /dev/null @@ -1,563 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the serialization helper functions. -""" -import pytest -from conftest import device_name, LightningDevice - -import numpy as np -import pennylane as qml -from pennylane_lightning.lightning_qubit._serialize import QuantumScriptSerializer - -# from pennylane_lightning.lightning_qubit._serialize_old import QuantumScriptSerializer - -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) -elif device_name == "lightning.gpu": - from pennylane_lightning.lightning_gpu_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) -else: - from pennylane_lightning.lightning_qubit_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) - - -def test_wrong_device_name(): - """Test the device name is not a valid option""" - - with pytest.raises(qml.DeviceError, match="The device name"): - QuantumScriptSerializer("thunder.qubit") - - -@pytest.mark.parametrize( - "obs,obs_type", - [ - (qml.PauliZ(0), NamedObsC128), - (qml.PauliZ(0) @ qml.PauliZ(1), TensorProdObsC128), - (qml.Hadamard(0), NamedObsC128), - (qml.Hermitian(np.eye(2), wires=0), HermitianObsC128), - ( - qml.PauliZ(0) @ qml.Hadamard(1) @ (0.1 * (qml.PauliZ(2) + qml.PauliX(3))), - HamiltonianC128, - ), - ( - ( - qml.Hermitian(np.eye(2), wires=0) - @ qml.Hermitian(np.eye(2), wires=1) - @ qml.Projector([0], wires=2) - ), - TensorProdObsC128, - ), - ( - qml.PauliZ(0) @ qml.Hermitian(np.eye(2), wires=1) @ qml.Projector([0], wires=2), - TensorProdObsC128, - ), - (qml.Projector([0], wires=0), HermitianObsC128), - (qml.Hamiltonian([1], [qml.PauliZ(0)]), HamiltonianC128), - (qml.sum(qml.Hadamard(0), qml.PauliX(1)), HermitianObsC128), - ( - qml.SparseHamiltonian(qml.Hamiltonian([1], [qml.PauliZ(0)]).sparse_matrix(), wires=[0]), - SparseHamiltonianC128, - ), - ], -) -def test_obs_returns_expected_type(obs, obs_type): - """Tests that observables get serialized to the expected type.""" - assert isinstance(QuantumScriptSerializer(device_name)._ob(obs), obs_type) - - -class TestSerializeObs: - """Tests for the serialize_observables function""" - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_tensor_non_tensor_return(self, use_csingle): - """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)) - - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - s_expected = [ - tensor_prod_obs([named_obs("PauliZ", [0]), named_obs("PauliX", [1])]), - named_obs("Hadamard", [1]), - ] - - assert s == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hermitian_return(self, use_csingle): - """Test expected serialization for a Hermitian return""" - with qml.tape.QuantumTape() as tape: - qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) - - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - s_expected = hermitian_obs( - np.array( - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - dtype=c_dtype, - ), - [0, 1], - ) - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hermitian_tensor_return(self, use_csingle): - """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])) - - c_dtype = np.complex64 if use_csingle else np.complex128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - s_expected = tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [2]), - ] - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_mixed_tensor_return(self, use_csingle): - """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)) - - c_dtype = np.complex64 if use_csingle else np.complex128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - s_expected = tensor_prod_obs( - [hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), named_obs("PauliY", [2])] - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_return(self, use_csingle): - """Test expected serialization for a Hamiltonian return""" - - ham = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - - with qml.tape.QuantumTape() as tape: - qml.expval(ham) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - s_expected = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - ] - ), - tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), - ], - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_tensor_return(self, use_csingle): - """Test expected serialization for a Hamiltonian return""" - - with qml.tape.QuantumTape() as tape: - ham = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - qml.expval(ham @ qml.PauliZ(3)) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - # Expression (ham @ obs) is converted internally by Pennylane - # where obs is appended to each term of the ham - s_expected = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - named_obs("PauliZ", [3]), - ] - ), - tensor_prod_obs( - [named_obs("PauliX", [0]), named_obs("PauliY", [2]), named_obs("PauliZ", [3])] - ), - tensor_prod_obs( - [hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), named_obs("PauliZ", [3])] - ), - ], - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_hamiltonian_mix_return(self, use_csingle): - """Test expected serialization for a Hamiltonian return""" - - ham1 = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - ham2 = qml.Hamiltonian( - [0.7, 0.3], - [qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]), qml.PauliY(0) @ qml.PauliX(2)], - ) - - with qml.tape.QuantumTape() as tape: - qml.expval(ham1) - qml.expval(ham2) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - - s_expected1 = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - ] - ), - tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), - ], - ) - s_expected2 = hamiltonian_obs( - np.array([0.7, 0.3], dtype=r_dtype), - [ - tensor_prod_obs( - [ - named_obs("PauliX", [0]), - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]), - ] - ), - tensor_prod_obs([named_obs("PauliY", [0]), named_obs("PauliX", [2])]), - ], - ) - - assert s[0] == s_expected1 - assert s[1] == s_expected2 - - @pytest.mark.parametrize( - "obs,coeffs,terms", - [ - (qml.prod(qml.PauliZ(0), qml.PauliX(1)), [1], [[("PauliX", 1), ("PauliZ", 0)]]), - (qml.s_prod(0.1, qml.PauliX(0)), [0.1], ("PauliX", 0)), - ( - qml.sum( - 0.5 * qml.prod(qml.PauliX(0), qml.PauliZ(1)), - 0.1 * qml.prod(qml.PauliZ(0), qml.PauliY(1)), - ), - [0.5, 0.1], - [[("PauliZ", 1), ("PauliX", 0)], [("PauliY", 1), ("PauliZ", 0)]], - ), - ], - ) - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms): - """Tests that an arithmetic obs with a PauliRep serializes as a Hamiltonian.""" - tape = qml.tape.QuantumTape(measurements=[qml.expval(obs)]) - res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - assert len(res) == 1 - assert isinstance(res[0], HamiltonianC64 if use_csingle else HamiltonianC128) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - tensor_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - rtype = np.float32 if use_csingle else np.float64 - term_shape = np.array(terms).shape - - if len(term_shape) == 1: # just a single pauli op - expected_terms = [named_obs(terms[0], [terms[1]])] - elif len(term_shape) == 3: # list of tensor products - expected_terms = [ - tensor_obs([named_obs(pauli, [wire]) for pauli, wire in term]) for term in terms - ] - - coeffs = np.array(coeffs).astype(rtype) - assert res[0] == hamiltonian_obs(coeffs, expected_terms) - - @pytest.mark.parametrize("use_csingle", [True, False]) - def test_multi_wire_identity(self, use_csingle): - """Tests that multi-wire Identity does not fail serialization.""" - tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Identity(wires=[1, 2]))]) - res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables(tape) - assert len(res) == 1 - - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - assert res[0] == named_obs("Identity", [1]) - - -class TestSerializeOps: - """Tests for the _ops function""" - - 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 = QuantumScriptSerializer(device_name).serialize_ops(tape) - s_expected = ( - ( - ["RX", "RY", "CNOT"], - [np.array([0.4]), np.array([0.6]), []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - [[], [], []], - [[], [], []], - ), - False, - ) - assert s == s_expected - - def test_basic_circuit_not_implemented_ctrl_ops(self): - """Test expected serialization for a simple circuit""" - ops = qml.OrbitalRotation(0.1234, wires=range(4)) - with qml.tape.QuantumTape() as tape: - qml.RX(0.4, wires=0) - qml.RY(0.6, wires=1) - qml.ctrl(ops, [4, 5]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape) - s_expected = ( - ( - ["RX", "RY", "QubitUnitary"], - [np.array([0.4]), np.array([0.6]), [0.0]], - [[0], [1], list(ops.wires)], - [False, False, False], - [[], [], [qml.matrix(ops)]], - [[], [], [4, 5]], - ), - 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(s0, s1) for s0, s1 in zip(s[0][4], s_expected[0][4])) - assert s[0][5] == s_expected[0][5] - assert s[1] == s_expected[1] - - def test_multicontrolledx(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.ctrl(qml.PauliX(wires=0), [1, 2, 3], control_values=[True, False, False]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape) - s_expected = ( - ( - ["RX", "RY", "PauliX"], - [np.array([0.4]), np.array([0.6]), []], - [[0], [1], [0]], - [False, False, False], - [[], [], []], - [[], [], [1, 2, 3]], - [[], [], [True, False, False]], - ), - False, - ) - assert s == s_expected - - @pytest.mark.parametrize("stateprep", [qml.QubitStateVector, qml.StatePrep]) - def test_skips_prep_circuit(self, stateprep): - """Test expected serialization for a simple circuit with state preparation, such that - the state preparation is skipped""" - with qml.tape.QuantumTape() as tape: - stateprep([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 = QuantumScriptSerializer(device_name).serialize_ops(tape) - s_expected = ( - ( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - [[], [], []], - [[], [], []], - ), - True, - ) - 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.CNOT(wires=[0, 1]) - qml.RZ(0.2, wires=2) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape) - s_expected = ( - ( - ["CNOT", "RZ"], - [[], [0.2]], - [[0, 1], [2]], - [False, False], - ), - False, - ) - assert s[0][0] == s_expected[0][0] - assert s[0][1] == s_expected[0][1] - - @pytest.mark.parametrize("C", [True, False]) - def test_integration(self, C): - """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) - qml.CNOT(wires=[0, 1]) - qml.QubitUnitary(np.eye(4), wires=[0, 1]) - qml.templates.QFT(wires=[0, 1, 2]) - qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) - qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) - qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape) - - dtype = np.complex64 if C else np.complex128 - s_expected = ( - ( - [ - "RX", - "RY", - "CNOT", - "QubitUnitary", - "QFT", - "DoubleExcitation", - "DoubleExcitationMinus", - "DoubleExcitationPlus", - ], - [[0.4], [0.6], [], [0.0], [], [0.555], [0.555], [0.555]], - [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]], - [False, False, False, False, False, False, False, False], - [ - [], - [], - [], - qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])), - qml.matrix(qml.templates.QFT(wires=[0, 1, 2])), - [], - [], - [], - ], - ), - 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 all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) From 1f78bef726ad227a039493a316991e6af27c9da8 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 07:53:37 -0500 Subject: [PATCH 038/105] remove obsolete tests --- .github/workflows/tests_linux.yml | 47 ------------------------------- tests/conftest.py | 8 +++--- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index 1e521ec5bb..91bd44bb85 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -197,53 +197,6 @@ jobs: ./main/.coverage-${{ github.job }}-${{ matrix.pl_backend }} if-no-files-found: error - pythontests_LQ_2: - strategy: - matrix: - os: [ubuntu-22.04] - pl_backend: ["lightning_qubit2"] - timeout-minutes: 30 - name: Python tests - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout PennyLane-Lightning - uses: actions/checkout@v3 - with: - fetch-tags: true - path: main - - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: '3.9' - - - name: Install dependencies - run: sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION - - - name: Get required Python packages - run: | - cd main - python -m pip install -r requirements-dev.txt - python -m pip install openfermionpyscf - - - name: Checkout PennyLane for release build - uses: actions/checkout@v3 - with: - path: pennylane - repository: PennyLaneAI/pennylane - - - name: Install backend device - run: | - cd main - PL_BACKEND=lightning.qubit pip install -e . -vv - - - name: Run PennyLane-Lightning unit tests - run: | - cd main/ - DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - PL_DEVICE=${DEVICENAME} python -m pytest tests/lightning_qubit2/ - cpptestswithOpenBLAS: if: ${{ !contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} strategy: diff --git a/tests/conftest.py b/tests/conftest.py index d04117520c..e10a3c203c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -113,10 +113,10 @@ def get_device(): device_name = get_device() -# if device_name not in qml.plugin_devices: -# raise qml.DeviceError( -# f"Device {device_name} does not exist. Make sure the required plugin is installed." -# ) +if device_name not in qml.plugin_devices: + raise qml.DeviceError( + f"Device {device_name} does not exist. Make sure the required plugin is installed." + ) # Device specification import pennylane_lightning.lightning_qubit as lightning_ops # Any definition of lightning_ops will do From b7ccb84a634c260d8dad0a71b779097de6761cf2 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:04:18 -0500 Subject: [PATCH 039/105] remove unused dtype input from simulate --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index fb28c8cfbd..c5bf43531d 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -36,14 +36,12 @@ LQ_CPP_BINARY_AVAILABLE = False -def simulate(circuit: QuantumScript, state: LightningStateVector, dtype=np.complex128) -> Result: +def simulate(circuit: QuantumScript, state: LightningStateVector) -> Result: """Simulate a single quantum script. Args: circuit (QuantumTape): The single circuit to simulate state (LightningStateVector): handle to Lightning state vector - dtype: Datatypes for state-vector representation. Must be one of - ``np.complex64`` or ``np.complex128``. Returns: tuple(TensorLike): The results of the simulation From 58032ef23fc2d008fd3040f4d6b4890e9ec1f1d7 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:14:11 -0500 Subject: [PATCH 040/105] update measurements --- pennylane_lightning/lightning_qubit/_measurements.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 2dddd2e5b0..3feba583ef 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -23,18 +23,15 @@ except ImportError: pass -import numpy as np from typing import Callable, List +import numpy as np from pennylane.measurements import StateMeasurement, MeasurementProcess, ExpectationMP from pennylane.typing import TensorLike, Result from pennylane.tape import QuantumScript from pennylane.wires import Wires -from typing import List - from pennylane_lightning.core._serialize import QuantumScriptSerializer -from ._serialize import QuantumScriptSerializer from ._state_vector import LightningStateVector @@ -80,6 +77,7 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten wires = Wires(range(total_wires)) return measurementprocess.process_state(state_array, wires) + # pylint: disable=protected-access def expval(self, measurementprocess: MeasurementProcess): """Expectation value of the supplied observable contained in the MeasurementProcess. From a3b696e5c09b9782c891b1134ad055070294e1db Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:14:27 -0500 Subject: [PATCH 041/105] update state_vector --- pennylane_lightning/lightning_qubit/_state_vector.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 8feeae0727..b116c5fa93 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -26,6 +26,7 @@ except ImportError: pass +from itertools import product import numpy as np import pennylane as qml @@ -34,12 +35,8 @@ from pennylane import ( BasisState, StatePrep, - DeviceError, ) -from itertools import product - - class LightningStateVector: """Lightning state-vector class. @@ -208,11 +205,11 @@ def _get_basis_state_index(self, state, wires): if not set(state.tolist()).issubset({0, 1}): raise ValueError("BasisState parameter must consist of 0 or 1 integers.") - if n_basis_state != len(device_wires): + if n_basis_state != len(wires): raise ValueError("BasisState parameter and wires must be of equal length.") # get computational basis state number - basis_states = 2 ** (self.num_wires - 1 - np.array(device_wires)) + basis_states = 2 ** (self.num_wires - 1 - np.array(wires)) basis_states = qml.math.convert_like(basis_states, state) return int(qml.math.dot(state, basis_states)) From cd7c67cbe0c8b165ede5f31cc2731c97c1cb734b Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:14:47 -0500 Subject: [PATCH 042/105] update lightning_qubit2 --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index c5bf43531d..b35b513391 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -25,7 +25,7 @@ try: - # pylint: disable=import-error, no-name-in-module + # pylint: disable=import-error, no-name-in-module, unused-import from pennylane_lightning.lightning_qubit_ops import ( StateVectorC64, StateVectorC128, From 910246b644251c43fe4930e3195228856acac913 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:20:10 -0500 Subject: [PATCH 043/105] format --- pennylane_lightning/lightning_qubit/_state_vector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index b116c5fa93..6a574bec20 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -37,6 +37,7 @@ StatePrep, ) + class LightningStateVector: """Lightning state-vector class. From af6cfefd4a9bfebfbc4a30cf0fe94624295c1790 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 08:25:47 -0500 Subject: [PATCH 044/105] pylint --- pennylane_lightning/lightning_qubit/_measurements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 3feba583ef..120a6696bb 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -15,6 +15,7 @@ Class implementation for state vector measurements. """ +# pylint: disable=import-error, no-name-in-module, ungrouped-imports try: from pennylane_lightning.lightning_qubit_ops import ( MeasurementsC64, From dd31965325141035de765c28cff784a420656420 Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Thu, 22 Feb 2024 08:27:48 -0500 Subject: [PATCH 045/105] Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_state_vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 6a574bec20..0fa7f24ec8 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -234,7 +234,7 @@ def _apply_state_vector(self, state, device_wires): if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: # Initialize the entire device state with the input state - state = self._reshape(state, output_shape).ravel(order="C") + state = np.reshape(state, output_shape).ravel(order="C") self._qubit_state.UpdateData(state) return From 44bc987b1b6b7b2b592f369332fe509b5a5502f2 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 09:29:57 -0500 Subject: [PATCH 046/105] remove old comment --- .../lightning_qubit/_state_vector.py | 2 - tests/lightning_qubit2/test_state_vector.py | 658 ++++++++++++++++++ tests/test_serialize.py | 2 +- 3 files changed, 659 insertions(+), 3 deletions(-) create mode 100644 tests/lightning_qubit2/test_state_vector.py diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 0fa7f24ec8..5d82aa6b34 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -198,8 +198,6 @@ def _get_basis_state_index(self, state, wires): Returns: int: basis state index """ - # translate to wire labels used by device - # length of basis state parameter n_basis_state = len(state) diff --git a/tests/lightning_qubit2/test_state_vector.py b/tests/lightning_qubit2/test_state_vector.py new file mode 100644 index 0000000000..e81f5c1783 --- /dev/null +++ b/tests/lightning_qubit2/test_state_vector.py @@ -0,0 +1,658 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the serialization helper functions. +""" +import pytest +from conftest import device_name, LightningDevice + +import numpy as np +import pennylane as qml +from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + +if device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) +elif device_name == "lightning.gpu": + from pennylane_lightning.lightning_gpu_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) +else: + from pennylane_lightning.lightning_qubit_ops.observables import ( + NamedObsC64, + NamedObsC128, + HermitianObsC64, + HermitianObsC128, + TensorProdObsC64, + TensorProdObsC128, + HamiltonianC64, + HamiltonianC128, + SparseHamiltonianC64, + SparseHamiltonianC128, + ) + + +def test_wrong_device_name(): + """Test the device name is not a valid option""" + + with pytest.raises(qml.DeviceError, match="The device name"): + QuantumScriptSerializer("thunder.qubit") + + +@pytest.mark.parametrize( + "obs,obs_type", + [ + (qml.PauliZ(0), NamedObsC128), + (qml.PauliZ(0) @ qml.PauliZ(1), TensorProdObsC128), + (qml.Hadamard(0), NamedObsC128), + (qml.Hermitian(np.eye(2), wires=0), HermitianObsC128), + ( + qml.PauliZ(0) @ qml.Hadamard(1) @ (0.1 * (qml.PauliZ(2) + qml.PauliX(3))), + HamiltonianC128, + ), + ( + ( + qml.Hermitian(np.eye(2), wires=0) + @ qml.Hermitian(np.eye(2), wires=1) + @ qml.Projector([0], wires=2) + ), + TensorProdObsC128, + ), + ( + qml.PauliZ(0) @ qml.Hermitian(np.eye(2), wires=1) @ qml.Projector([0], wires=2), + TensorProdObsC128, + ), + (qml.Projector([0], wires=0), HermitianObsC128), + (qml.Hamiltonian([1], [qml.PauliZ(0)]), HamiltonianC128), + (qml.sum(qml.Hadamard(0), qml.PauliX(1)), HermitianObsC128), + ( + qml.SparseHamiltonian(qml.Hamiltonian([1], [qml.PauliZ(0)]).sparse_matrix(), wires=[0]), + SparseHamiltonianC128, + ), + ], +) +def test_obs_returns_expected_type(obs, obs_type): + """Tests that observables get serialized to the expected type, with and without wires map""" + assert isinstance( + QuantumScriptSerializer(device_name)._ob(obs, dict(enumerate(obs.wires))), obs_type + ) + assert isinstance(QuantumScriptSerializer(device_name)._ob(obs), obs_type) + + +class TestSerializeObs: + """Tests for the _observables function""" + + wires_dict = {i: i for i in range(10)} + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_tensor_non_tensor_return(self, use_csingle, wires_map): + """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)) + + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + + s_expected = [ + tensor_prod_obs([named_obs("PauliZ", [0]), named_obs("PauliX", [1])]), + named_obs("Hadamard", [1]), + ] + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + assert s == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hermitian_return(self, use_csingle, wires_map): + """Test expected serialization for a Hermitian return""" + with qml.tape.QuantumTape() as tape: + qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) + + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + s_expected = hermitian_obs( + np.array( + [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], + dtype=c_dtype, + ), + [0, 1], + ) + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hermitian_tensor_return(self, use_csingle, wires_map): + """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])) + + c_dtype = np.complex64 if use_csingle else np.complex128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + + s_expected = tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [2]), + ] + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_mixed_tensor_return(self, use_csingle, wires_map): + """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)) + + c_dtype = np.complex64 if use_csingle else np.complex128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + + s_expected = tensor_prod_obs( + [hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), named_obs("PauliY", [2])] + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_return(self, use_csingle, wires_map): + """Test expected serialization for a Hamiltonian return""" + + ham = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + + with qml.tape.QuantumTape() as tape: + qml.expval(ham) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + + s_expected = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + ] + ), + tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ], + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_tensor_return(self, use_csingle, wires_map): + """Test expected serialization for a Hamiltonian return""" + + with qml.tape.QuantumTape() as tape: + ham = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + qml.expval(ham @ qml.PauliZ(3)) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + + # Expression (ham @ obs) is converted internally by Pennylane + # where obs is appended to each term of the ham + s_expected = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + named_obs("PauliZ", [3]), + ] + ), + tensor_prod_obs( + [named_obs("PauliX", [0]), named_obs("PauliY", [2]), named_obs("PauliZ", [3])] + ), + tensor_prod_obs( + [hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), named_obs("PauliZ", [3])] + ), + ], + ) + + assert s[0] == s_expected + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_hamiltonian_mix_return(self, use_csingle, wires_map): + """Test expected serialization for a Hamiltonian return""" + + ham1 = qml.Hamiltonian( + [0.3, 0.5, 0.4], + [ + qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), + qml.PauliX(0) @ qml.PauliY(2), + qml.Hermitian(np.ones((8, 8)), wires=range(3)), + ], + ) + ham2 = qml.Hamiltonian( + [0.7, 0.3], + [qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]), qml.PauliY(0) @ qml.PauliX(2)], + ) + + with qml.tape.QuantumTape() as tape: + qml.expval(ham1) + qml.expval(ham2) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 + tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + r_dtype = np.float32 if use_csingle else np.float64 + c_dtype = np.complex64 if use_csingle else np.complex128 + + s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + + s_expected1 = hamiltonian_obs( + np.array([0.3, 0.5, 0.4], dtype=r_dtype), + [ + tensor_prod_obs( + [ + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), + named_obs("PauliY", [2]), + ] + ), + tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), + hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), + ], + ) + s_expected2 = hamiltonian_obs( + np.array([0.7, 0.3], dtype=r_dtype), + [ + tensor_prod_obs( + [ + named_obs("PauliX", [0]), + hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]), + ] + ), + tensor_prod_obs([named_obs("PauliY", [0]), named_obs("PauliX", [2])]), + ], + ) + + assert s[0] == s_expected1 + assert s[1] == s_expected2 + + @pytest.mark.parametrize( + "obs,coeffs,terms", + [ + (qml.prod(qml.PauliZ(0), qml.PauliX(1)), [1], [[("PauliX", 1), ("PauliZ", 0)]]), + (qml.s_prod(0.1, qml.PauliX(0)), [0.1], ("PauliX", 0)), + ( + qml.sum( + 0.5 * qml.prod(qml.PauliX(0), qml.PauliZ(1)), + 0.1 * qml.prod(qml.PauliZ(0), qml.PauliY(1)), + ), + [0.5, 0.1], + [[("PauliZ", 1), ("PauliX", 0)], [("PauliY", 1), ("PauliZ", 0)]], + ), + ], + ) + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms, wires_map): + """Tests that an arithmetic obs with a PauliRep serializes as a Hamiltonian.""" + tape = qml.tape.QuantumTape(measurements=[qml.expval(obs)]) + res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + assert len(res) == 1 + assert isinstance(res[0], HamiltonianC64 if use_csingle else HamiltonianC128) + + hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 + tensor_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + rtype = np.float32 if use_csingle else np.float64 + term_shape = np.array(terms).shape + + if len(term_shape) == 1: # just a single pauli op + expected_terms = [named_obs(terms[0], [terms[1]])] + elif len(term_shape) == 3: # list of tensor products + expected_terms = [ + tensor_obs([named_obs(pauli, [wire]) for pauli, wire in term]) for term in terms + ] + + coeffs = np.array(coeffs).astype(rtype) + assert res[0] == hamiltonian_obs(coeffs, expected_terms) + + @pytest.mark.parametrize("use_csingle", [True, False]) + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_multi_wire_identity(self, use_csingle, wires_map): + """Tests that multi-wire Identity does not fail serialization.""" + tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Identity(wires=[1, 2]))]) + res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( + tape, wires_map + ) + assert len(res) == 1 + + named_obs = NamedObsC64 if use_csingle else NamedObsC128 + assert res[0] == named_obs("Identity", [1]) + + +class TestSerializeOps: + """Tests for the _ops function""" + + wires_dict = {i: i for i in range(10)} + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_basic_circuit(self, wires_map): + """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 = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [np.array([0.4]), np.array([0.6]), []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + [[], [], []], + [[], [], []], + ), + False, + ) + assert s == s_expected + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_basic_circuit_not_implemented_ctrl_ops(self, wires_map): + """Test expected serialization for a simple circuit""" + ops = qml.OrbitalRotation(0.1234, wires=range(4)) + with qml.tape.QuantumTape() as tape: + qml.RX(0.4, wires=0) + qml.RY(0.6, wires=1) + qml.ctrl(ops, [4, 5]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + s_expected = ( + ( + ["RX", "RY", "QubitUnitary"], + [np.array([0.4]), np.array([0.6]), [0.0]], + [[0], [1], list(ops.wires)], + [False, False, False], + [[], [], [qml.matrix(ops)]], + [[], [], [4, 5]], + ), + 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(s0, s1) for s0, s1 in zip(s[0][4], s_expected[0][4])) + assert s[0][5] == s_expected[0][5] + assert s[1] == s_expected[1] + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_multicontrolledx(self, wires_map): + """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.ctrl(qml.PauliX(wires=0), [1, 2, 3], control_values=[True, False, False]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + s_expected = ( + ( + ["RX", "RY", "PauliX"], + [np.array([0.4]), np.array([0.6]), []], + [[0], [1], [0]], + [False, False, False], + [[], [], []], + [[], [], [1, 2, 3]], + [[], [], [True, False, False]], + ), + False, + ) + assert s == s_expected + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + @pytest.mark.parametrize("stateprep", [qml.QubitStateVector, qml.StatePrep]) + def test_skips_prep_circuit(self, stateprep, wires_map): + """Test expected serialization for a simple circuit with state preparation, such that + the state preparation is skipped""" + with qml.tape.QuantumTape() as tape: + stateprep([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 = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + s_expected = ( + ( + ["RX", "RY", "CNOT"], + [[0.4], [0.6], []], + [[0], [1], [0, 1]], + [False, False, False], + [[], [], []], + [[], [], []], + [[], [], []], + ), + True, + ) + assert s == s_expected + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + def test_unsupported_kernel_circuit(self, wires_map): + """Test expected serialization for a circuit including gates that do not have a dedicated + kernel""" + with qml.tape.QuantumTape() as tape: + qml.CNOT(wires=[0, 1]) + qml.RZ(0.2, wires=2) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + s_expected = ( + ( + ["CNOT", "RZ"], + [[], [0.2]], + [[0, 1], [2]], + [False, False], + ), + False, + ) + assert s[0][0] == s_expected[0][0] + assert s[0][1] == s_expected[0][1] + + 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]) + qml.SingleExcitation(0.5, wires=["a", 3.2]) + qml.SingleExcitationPlus(0.4, wires=["a", 3.2]) + qml.adjoint(qml.SingleExcitationMinus(0.5, wires=["a", 3.2]), lazy=False) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_dict) + s_expected = ( + ( + [ + "RX", + "RY", + "CNOT", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + ], + [[0.4], [0.6], [], [0.5], [0.4], [-0.5]], + [[0], [1], [0, 1], [0, 1], [0, 1], [0, 1]], + [False, False, False, False, False, False], + [[], [], [], [], [], []], + [[], [], [], [], [], []], + [[], [], [], [], [], []], + ), + False, + ) + assert s == s_expected + + @pytest.mark.parametrize("wires_map", [wires_dict, None]) + @pytest.mark.parametrize("C", [True, False]) + def test_integration(self, C, wires_map): + """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) + qml.CNOT(wires=[0, 1]) + qml.QubitUnitary(np.eye(4), wires=[0, 1]) + qml.templates.QFT(wires=[0, 1, 2]) + qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) + qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) + qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) + + s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) + + dtype = np.complex64 if C else np.complex128 + s_expected = ( + ( + [ + "RX", + "RY", + "CNOT", + "QubitUnitary", + "QFT", + "DoubleExcitation", + "DoubleExcitationMinus", + "DoubleExcitationPlus", + ], + [[0.4], [0.6], [], [0.0], [], [0.555], [0.555], [0.555]], + [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]], + [False, False, False, False, False, False, False, False], + [ + [], + [], + [], + qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])), + qml.matrix(qml.templates.QFT(wires=[0, 1, 2])), + [], + [], + [], + ], + ), + 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 all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) + + +def check_global_phase_diagonal(par, wires, targets, controls, control_values): + op = qml.ctrl(qml.GlobalPhase(par, wires=targets), controls, control_values=control_values) + return np.diag(op.matrix(wires)) + + +def test_global_phase(): + """Validate global_phase_diagonal with various combinations of num_qubits, targets and controls.""" + import itertools + + nmax = 7 + par = 0.1 + for nq in range(2, nmax): + wires = range(nq) + for nw in range(nq, nmax): + wire_lists = list(itertools.permutations(wires, nw)) + for wire_list in wire_lists: + for i in range(len(wire_list) - 1): + targets = wire_list[0:i] + controls = wire_list[i:] + control_values = [i % 2 == 0 for i in controls] + D0 = check_global_phase_diagonal(par, wires, targets, controls, control_values) + D1 = global_phase_diagonal(par, wires, controls, control_values) + assert np.allclose(D0, D1) diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 01ba41dbbd..e81f5c1783 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -1,4 +1,4 @@ -# Copyright 2018-2023 Xanadu Quantum Technologies Inc. +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 5a5b20a5f87532eac9deae0465a851c76caa83c9 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 09:50:52 -0500 Subject: [PATCH 047/105] some review suggestions --- pennylane_lightning/lightning_qubit/lightning_qubit2.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index b35b513391..bbdc799400 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -25,11 +25,8 @@ try: - # pylint: disable=import-error, no-name-in-module, unused-import - from pennylane_lightning.lightning_qubit_ops import ( - StateVectorC64, - StateVectorC128, - ) + # pylint: disable=import-error, unused-import + import pennylane_lightning.lightning_qubit_ops LQ_CPP_BINARY_AVAILABLE = True except ImportError: From a64cf784d2c493c7edc5ce8b9222864360048cbe Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Thu, 22 Feb 2024 15:29:10 +0000 Subject: [PATCH 048/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index a223e801fa..92ab0f4480 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.35.0-dev16" +__version__ = "0.35.0-dev17" From ad1e3a45955656adef6748e2971199f4e899d591 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 14:38:18 -0500 Subject: [PATCH 049/105] remove print --- tests/test_execute.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_execute.py b/tests/test_execute.py index b8278be610..9efc2955dc 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -117,7 +117,6 @@ def circuit(omega): zip([i for i in range(len(index) - 1, -1, -1)], index), 0, ) - print(prob) assert np.allclose(np.sum(prob), 1.0) assert prob[index] > 0.95 assert np.sum(prob) - prob[index] < 0.05 From aec5c070932bf3e376b76597117e1b68eb7679ff Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 14:38:42 -0500 Subject: [PATCH 050/105] update state vector class --- .../lightning_qubit/_state_vector.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 5d82aa6b34..b864381c07 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -35,6 +35,7 @@ from pennylane import ( BasisState, StatePrep, + DeviceError, ) @@ -54,6 +55,13 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit" self.num_wires = num_wires self._wires = Wires(range(num_wires)) self._dtype = dtype + + if dtype not in [np.complex64, np.complex128]: + raise TypeError(f"Unsupported complex type: {dtype}") + + if device_name != "lightning.qubit": + raise DeviceError(f'The device name "{device_name}" is not a valid option.') + self._name = device_name self._qubit_state = self._state_dtype()(self.num_wires) @@ -165,9 +173,6 @@ def _preprocess_state_vector(self, state, device_wires): or broadcasted state of shape ``(batch_size, 2**len(wires))`` array[int]: indices for which the state is changed to input state vector elements """ - - # translate to wire labels used by device - # special case for integral types if state.dtype.kind == "i": state = qml.numpy.array(state, dtype=self.dtype) @@ -289,7 +294,7 @@ def _apply_lightning_controlled(self, operation): # To support older versions of PL method(operation.base.matrix, control_wires, control_values, target_wires, False) - def apply_lightning(self, operations): + def _apply_lightning(self, operations): """Apply a list of operations to the state tensor. Args: @@ -336,7 +341,7 @@ def apply_operations(self, operations): self._apply_basis_state(operations[0].parameters[0], operations[0].wires) operations = operations[1:] - self.apply_lightning(operations) + self._apply_lightning(operations) def get_final_state(self, circuit: QuantumScript): """ @@ -348,8 +353,7 @@ def get_final_state(self, circuit: QuantumScript): circuit (QuantumScript): The single circuit to simulate Returns: - Tuple: A tuple containing the Lightning final state handler of the quantum script and - whether the state has a batch dimension. + LightningStateVector: Lightning final state class. """ self.apply_operations(circuit.operations) From 286fd4ca44e1d47eb644a9b70046646207ae59cf Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 22 Feb 2024 14:39:01 -0500 Subject: [PATCH 051/105] add state vector class tests --- tests/lightning_qubit/test_state_vector.py | 417 +++++++++++++ tests/lightning_qubit2/test_state_vector.py | 658 -------------------- 2 files changed, 417 insertions(+), 658 deletions(-) create mode 100644 tests/lightning_qubit/test_state_vector.py delete mode 100644 tests/lightning_qubit2/test_state_vector.py diff --git a/tests/lightning_qubit/test_state_vector.py b/tests/lightning_qubit/test_state_vector.py new file mode 100644 index 0000000000..76527127ec --- /dev/null +++ b/tests/lightning_qubit/test_state_vector.py @@ -0,0 +1,417 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the serialization helper functions. +""" + +import pytest +from conftest import LightningDevice # tested device + +import math +import numpy as np +import pennylane as qml +from pennylane.wires import Wires +from pennylane.tape import QuantumScript + +from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +from pennylane_lightning.lightning_qubit import LightningQubit + +if not LightningQubit._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + +if LightningDevice != LightningQubit: + pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) + + +@pytest.mark.parametrize("num_wires", [3, 10]) +@pytest.mark.parametrize("dtype", [np.complex64, np.complex128]) +@pytest.mark.parametrize("device_name", ["lightning.qubit"]) +def test_device_name_and_init(num_wires, dtype, device_name): + """Test the class initialization and returned properties.""" + state_vector = LightningStateVector(num_wires, dtype=dtype, device_name=device_name) + assert state_vector.dtype == dtype + assert state_vector.name == device_name + assert state_vector.wires == Wires(range(num_wires)) + + +def test_wrong_device_name(): + """Test an invalid device name""" + with pytest.raises(qml.DeviceError, match="The device name"): + LightningStateVector(3, device_name="thunder.qubit") + + +@pytest.mark.parametrize("dtype", [np.double]) +def test_wrong_dtype(dtype): + """Test if the class returns a TypeError for a wrong dtype""" + with pytest.raises(TypeError, match="Unsupported complex type:"): + assert LightningStateVector(3, dtype=dtype) + + +@pytest.mark.parametrize( + "operation,expected_output,par", + [ + (qml.BasisState, [0, 0, 1, 0], [1, 0]), + (qml.BasisState, [0, 0, 0, 1], [1, 1]), + (qml.QubitStateVector, [0, 0, 1, 0], [0, 0, 1, 0]), + (qml.QubitStateVector, [0, 0, 0, 1], [0, 0, 0, 1]), + (qml.StatePrep, [0, 0, 1, 0], [0, 0, 1, 0]), + (qml.StatePrep, [0, 0, 0, 1], [0, 0, 0, 1]), + ( + qml.StatePrep, + [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], + [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], + ), + ( + qml.StatePrep, + [1 / math.sqrt(3), 0, -1 / math.sqrt(3), 1 / math.sqrt(3)], + [1 / math.sqrt(3), 0, -1 / math.sqrt(3), 1 / math.sqrt(3)], + ), + ], +) +def test_apply_operation_state_preparation(tol, operation, expected_output, par): + """Tests that applying an operation yields the expected output state for single wire + operations that have no parameters.""" + + wires = 2 + state_vector = LightningStateVector(wires) + state_vector.apply_operations([operation(np.array(par), Wires(range(wires)))]) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + + +test_data_no_parameters = [ + (qml.PauliX, [1, 0], [0, 1]), + (qml.PauliX, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1 / math.sqrt(2)]), + (qml.PauliY, [1, 0], [0, 1j]), + (qml.PauliY, [1 / math.sqrt(2), 1 / math.sqrt(2)], [-1j / math.sqrt(2), 1j / math.sqrt(2)]), + (qml.PauliZ, [1, 0], [1, 0]), + (qml.PauliZ, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), -1 / math.sqrt(2)]), + (qml.S, [1, 0], [1, 0]), + (qml.S, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1j / math.sqrt(2)]), + (qml.T, [1, 0], [1, 0]), + ( + qml.T, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / math.sqrt(2), np.exp(1j * np.pi / 4) / math.sqrt(2)], + ), + (qml.Hadamard, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)]), + (qml.Hadamard, [1 / math.sqrt(2), -1 / math.sqrt(2)], [0, 1]), + (qml.Identity, [1, 0], [1, 0]), + (qml.Identity, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1 / math.sqrt(2)]), +] + + +@pytest.mark.parametrize("operation,input,expected_output", test_data_no_parameters) +def test_apply_operation_single_wire_no_parameters(tol, operation, input, expected_output): + """Tests that applying an operation yields the expected output state for single wire + operations that have no parameters.""" + wires = 1 + state_vector = LightningStateVector(wires) + state_vector.apply_operations( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(Wires(range(wires)))] + ) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + assert state_vector.state.dtype == state_vector.dtype + + +test_data_two_wires_no_parameters = [ + (qml.CNOT, [1, 0, 0, 0], [1, 0, 0, 0]), + (qml.CNOT, [0, 0, 1, 0], [0, 0, 0, 1]), + ( + qml.CNOT, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [1 / math.sqrt(2), 0, 1 / math.sqrt(2), 0], + ), + (qml.SWAP, [1, 0, 0, 0], [1, 0, 0, 0]), + (qml.SWAP, [0, 0, 1, 0], [0, 1, 0, 0]), + ( + qml.SWAP, + [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0], + [1 / math.sqrt(2), -1 / math.sqrt(2), 0, 0], + ), + (qml.CZ, [1, 0, 0, 0], [1, 0, 0, 0]), + (qml.CZ, [0, 0, 0, 1], [0, 0, 0, -1]), + ( + qml.CZ, + [1 / math.sqrt(2), 0, 0, -1 / math.sqrt(2)], + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + ), +] + + +@pytest.mark.parametrize("operation,input,expected_output", test_data_two_wires_no_parameters) +def test_apply_operation_two_wires_no_parameters(tol, operation, input, expected_output): + """Tests that applying an operation yields the expected output state for two wire + operations that have no parameters.""" + wires = 2 + state_vector = LightningStateVector(wires) + state_vector.apply_operations( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(Wires(range(wires)))] + ) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + assert state_vector.state.dtype == state_vector.dtype + + +test_data_three_wires_no_parameters = [ + (qml.CSWAP, [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0]), + (qml.CSWAP, [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0]), + (qml.CSWAP, [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0]), + (qml.Toffoli, [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0]), + (qml.Toffoli, [0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0]), + (qml.Toffoli, [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1]), + (qml.Toffoli, [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0]), +] + + +@pytest.mark.parametrize("operation,input,expected_output", test_data_three_wires_no_parameters) +def test_apply_operation_three_wires_no_parameters(tol, operation, input, expected_output): + """Tests that applying an operation yields the expected output state for three wire + operations that have no parameters.""" + + wires = 3 + state_vector = LightningStateVector(wires) + state_vector.apply_operations( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(Wires(range(wires)))] + ) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + assert state_vector.state.dtype == state_vector.dtype + + +""" operation,input,expected_output,par """ +test_data_single_wire_with_parameters = [ + (qml.PhaseShift, [1, 0], [1, 0], [math.pi / 2]), + (qml.PhaseShift, [0, 1], [0, 1j], [math.pi / 2]), + ( + qml.PhaseShift, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / math.sqrt(2), 1 / 2 + 1j / 2], + [math.pi / 4], + ), + (qml.RX, [1, 0], [1 / math.sqrt(2), -1j * 1 / math.sqrt(2)], [math.pi / 2]), + (qml.RX, [1, 0], [0, -1j], [math.pi]), + ( + qml.RX, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 - 1j / 2, 1 / 2 - 1j / 2], + [math.pi / 2], + ), + (qml.RY, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)], [math.pi / 2]), + (qml.RY, [1, 0], [0, 1], [math.pi]), + (qml.RY, [1 / math.sqrt(2), 1 / math.sqrt(2)], [0, 1], [math.pi / 2]), + (qml.RZ, [1, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0], [math.pi / 2]), + (qml.RZ, [0, 1], [0, 1j], [math.pi]), + ( + qml.RZ, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 - 1j / 2, 1 / 2 + 1j / 2], + [math.pi / 2], + ), + (qml.MultiRZ, [1, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0], [math.pi / 2]), + (qml.MultiRZ, [0, 1], [0, 1j], [math.pi]), + ( + qml.MultiRZ, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 - 1j / 2, 1 / 2 + 1j / 2], + [math.pi / 2], + ), + (qml.Rot, [1, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0], [math.pi / 2, 0, 0]), + (qml.Rot, [1, 0], [1 / math.sqrt(2), 1 / math.sqrt(2)], [0, math.pi / 2, 0]), + ( + qml.Rot, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 - 1j / 2, 1 / 2 + 1j / 2], + [0, 0, math.pi / 2], + ), + ( + qml.Rot, + [1, 0], + [-1j / math.sqrt(2), -1 / math.sqrt(2)], + [math.pi / 2, -math.pi / 2, math.pi / 2], + ), + ( + qml.Rot, + [1 / math.sqrt(2), 1 / math.sqrt(2)], + [1 / 2 + 1j / 2, -1 / 2 + 1j / 2], + [-math.pi / 2, math.pi, math.pi], + ), +] + + +@pytest.mark.parametrize( + "operation,input,expected_output,par", test_data_single_wire_with_parameters +) +def test_apply_operation_single_wire_with_parameters(tol, operation, input, expected_output, par): + """Tests that applying an operation yields the expected output state for single wire + operations that have parameters.""" + + wires = 1 + state_vector = LightningStateVector(wires) + state_vector.apply_operations( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(*par, Wires(range(wires)))] + ) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + assert state_vector.state.dtype == state_vector.dtype + + +""" operation,input,expected_output,par """ +test_data_two_wires_with_parameters = [ + (qml.IsingXX, [1, 0, 0, 0], [1 / math.sqrt(2), 0, 0, -1j / math.sqrt(2)], [math.pi / 2]), + ( + qml.IsingXX, + [0, 1 / math.sqrt(2), 0, 1 / math.sqrt(2)], + [-0.5j, 0.5, -0.5j, 0.5], + [math.pi / 2], + ), + (qml.IsingXY, [1, 0, 0, 0], [1, 0, 0, 0], [math.pi / 2]), + ( + qml.IsingXY, + [0, 1 / math.sqrt(2), 0, 1 / math.sqrt(2)], + [0, 0.5, 0.5j, 1 / math.sqrt(2)], + [math.pi / 2], + ), + (qml.IsingYY, [1, 0, 0, 0], [1 / math.sqrt(2), 0, 0, 1j / math.sqrt(2)], [math.pi / 2]), + ( + qml.IsingYY, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 + 0.5j, 0, 0, 0.5 + 0.5j], + [math.pi / 2], + ), + (qml.IsingZZ, [1, 0, 0, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0, 0, 0], [math.pi / 2]), + ( + qml.IsingZZ, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 - 0.5j, 0, 0, 0.5 - 0.5j], + [math.pi / 2], + ), + (qml.MultiRZ, [1, 0, 0, 0], [1 / math.sqrt(2) - 1j / math.sqrt(2), 0, 0, 0], [math.pi / 2]), + ( + qml.MultiRZ, + [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)], + [0.5 - 0.5j, 0, 0, 0.5 - 0.5j], + [math.pi / 2], + ), + (qml.CRX, [0, 1, 0, 0], [0, 1, 0, 0], [math.pi / 2]), + (qml.CRX, [0, 0, 0, 1], [0, 0, -1j, 0], [math.pi]), + ( + qml.CRX, + [0, 1 / math.sqrt(2), 1 / math.sqrt(2), 0], + [0, 1 / math.sqrt(2), 1 / 2, -1j / 2], + [math.pi / 2], + ), + (qml.CRY, [0, 0, 0, 1], [0, 0, -1 / math.sqrt(2), 1 / math.sqrt(2)], [math.pi / 2]), + (qml.CRY, [0, 0, 0, 1], [0, 0, -1, 0], [math.pi]), + ( + qml.CRY, + [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0], + [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0], + [math.pi / 2], + ), + (qml.CRZ, [0, 0, 0, 1], [0, 0, 0, 1 / math.sqrt(2) + 1j / math.sqrt(2)], [math.pi / 2]), + (qml.CRZ, [0, 0, 0, 1], [0, 0, 0, 1j], [math.pi]), + ( + qml.CRZ, + [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0], + [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0], + [math.pi / 2], + ), + ( + qml.CRot, + [0, 0, 0, 1], + [0, 0, 0, 1 / math.sqrt(2) + 1j / math.sqrt(2)], + [math.pi / 2, 0, 0], + ), + (qml.CRot, [0, 0, 0, 1], [0, 0, -1 / math.sqrt(2), 1 / math.sqrt(2)], [0, math.pi / 2, 0]), + ( + qml.CRot, + [0, 0, 1 / math.sqrt(2), 1 / math.sqrt(2)], + [0, 0, 1 / 2 - 1j / 2, 1 / 2 + 1j / 2], + [0, 0, math.pi / 2], + ), + ( + qml.CRot, + [0, 0, 0, 1], + [0, 0, 1 / math.sqrt(2), 1j / math.sqrt(2)], + [math.pi / 2, -math.pi / 2, math.pi / 2], + ), + ( + qml.CRot, + [0, 1 / math.sqrt(2), 1 / math.sqrt(2), 0], + [0, 1 / math.sqrt(2), 0, -1 / 2 + 1j / 2], + [-math.pi / 2, math.pi, math.pi], + ), + ( + qml.ControlledPhaseShift, + [1, 0, 0, 0], + [1, 0, 0, 0], + [math.pi / 2], + ), + ( + qml.ControlledPhaseShift, + [0, 1, 0, 0], + [0, 1, 0, 0], + [math.pi / 2], + ), + ( + qml.ControlledPhaseShift, + [0, 0, 1, 0], + [0, 0, 1, 0], + [math.pi / 2], + ), + ( + qml.ControlledPhaseShift, + [0, 0, 0, 1], + [0, 0, 0, 1 / math.sqrt(2) + 1j / math.sqrt(2)], + [math.pi / 4], + ), + ( + qml.ControlledPhaseShift, + [1 / math.sqrt(4), 1 / math.sqrt(4), 1 / math.sqrt(4), 1 / math.sqrt(4)], + [0.5, 0.5, 0.5, 1 / math.sqrt(8) + 1j / math.sqrt(8)], + [math.pi / 4], + ), +] + + +@pytest.mark.parametrize("operation,input,expected_output,par", test_data_two_wires_with_parameters) +def test_apply_operation_two_wires_with_parameters(tol, operation, input, expected_output, par): + """Tests that applying an operation yields the expected output state for two wire + operations that have parameters.""" + wires = 2 + state_vector = LightningStateVector(wires) + state_vector.apply_operations( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(*par, Wires(range(wires)))] + ) + + assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) + assert state_vector.state.dtype == state_vector.dtype + + +@pytest.mark.parametrize("operation,input,expected_output,par", test_data_two_wires_with_parameters) +def test_get_final_state(tol, operation, input, expected_output, par): + """Tests that applying an operation yields the expected output state for two wire + operations that have parameters.""" + wires = 2 + state_vector = LightningStateVector(wires) + tape = QuantumScript( + [qml.StatePrep(np.array(input), Wires(range(wires))), operation(*par, Wires(range(wires)))] + ) + final_state = state_vector.get_final_state(tape) + + assert np.allclose(final_state.state, np.array(expected_output), atol=tol, rtol=0) + assert final_state.state.dtype == final_state.dtype + assert final_state == state_vector diff --git a/tests/lightning_qubit2/test_state_vector.py b/tests/lightning_qubit2/test_state_vector.py deleted file mode 100644 index e81f5c1783..0000000000 --- a/tests/lightning_qubit2/test_state_vector.py +++ /dev/null @@ -1,658 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the serialization helper functions. -""" -import pytest -from conftest import device_name, LightningDevice - -import numpy as np -import pennylane as qml -from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal - -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) -elif device_name == "lightning.gpu": - from pennylane_lightning.lightning_gpu_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) -else: - from pennylane_lightning.lightning_qubit_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, - HamiltonianC64, - HamiltonianC128, - SparseHamiltonianC64, - SparseHamiltonianC128, - ) - - -def test_wrong_device_name(): - """Test the device name is not a valid option""" - - with pytest.raises(qml.DeviceError, match="The device name"): - QuantumScriptSerializer("thunder.qubit") - - -@pytest.mark.parametrize( - "obs,obs_type", - [ - (qml.PauliZ(0), NamedObsC128), - (qml.PauliZ(0) @ qml.PauliZ(1), TensorProdObsC128), - (qml.Hadamard(0), NamedObsC128), - (qml.Hermitian(np.eye(2), wires=0), HermitianObsC128), - ( - qml.PauliZ(0) @ qml.Hadamard(1) @ (0.1 * (qml.PauliZ(2) + qml.PauliX(3))), - HamiltonianC128, - ), - ( - ( - qml.Hermitian(np.eye(2), wires=0) - @ qml.Hermitian(np.eye(2), wires=1) - @ qml.Projector([0], wires=2) - ), - TensorProdObsC128, - ), - ( - qml.PauliZ(0) @ qml.Hermitian(np.eye(2), wires=1) @ qml.Projector([0], wires=2), - TensorProdObsC128, - ), - (qml.Projector([0], wires=0), HermitianObsC128), - (qml.Hamiltonian([1], [qml.PauliZ(0)]), HamiltonianC128), - (qml.sum(qml.Hadamard(0), qml.PauliX(1)), HermitianObsC128), - ( - qml.SparseHamiltonian(qml.Hamiltonian([1], [qml.PauliZ(0)]).sparse_matrix(), wires=[0]), - SparseHamiltonianC128, - ), - ], -) -def test_obs_returns_expected_type(obs, obs_type): - """Tests that observables get serialized to the expected type, with and without wires map""" - assert isinstance( - QuantumScriptSerializer(device_name)._ob(obs, dict(enumerate(obs.wires))), obs_type - ) - assert isinstance(QuantumScriptSerializer(device_name)._ob(obs), obs_type) - - -class TestSerializeObs: - """Tests for the _observables function""" - - wires_dict = {i: i for i in range(10)} - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_tensor_non_tensor_return(self, use_csingle, wires_map): - """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)) - - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - - s_expected = [ - tensor_prod_obs([named_obs("PauliZ", [0]), named_obs("PauliX", [1])]), - named_obs("Hadamard", [1]), - ] - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - assert s == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hermitian_return(self, use_csingle, wires_map): - """Test expected serialization for a Hermitian return""" - with qml.tape.QuantumTape() as tape: - qml.expval(qml.Hermitian(np.eye(4), wires=[0, 1])) - - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - s_expected = hermitian_obs( - np.array( - [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], - dtype=c_dtype, - ), - [0, 1], - ) - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hermitian_tensor_return(self, use_csingle, wires_map): - """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])) - - c_dtype = np.complex64 if use_csingle else np.complex128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - - s_expected = tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - hermitian_obs(np.eye(2, dtype=c_dtype).ravel(), [2]), - ] - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_mixed_tensor_return(self, use_csingle, wires_map): - """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)) - - c_dtype = np.complex64 if use_csingle else np.complex128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - - s_expected = tensor_prod_obs( - [hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), named_obs("PauliY", [2])] - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_return(self, use_csingle, wires_map): - """Test expected serialization for a Hamiltonian return""" - - ham = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - - with qml.tape.QuantumTape() as tape: - qml.expval(ham) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - - s_expected = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - ] - ), - tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), - ], - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_tensor_return(self, use_csingle, wires_map): - """Test expected serialization for a Hamiltonian return""" - - with qml.tape.QuantumTape() as tape: - ham = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - qml.expval(ham @ qml.PauliZ(3)) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - - # Expression (ham @ obs) is converted internally by Pennylane - # where obs is appended to each term of the ham - s_expected = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - named_obs("PauliZ", [3]), - ] - ), - tensor_prod_obs( - [named_obs("PauliX", [0]), named_obs("PauliY", [2]), named_obs("PauliZ", [3])] - ), - tensor_prod_obs( - [hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), named_obs("PauliZ", [3])] - ), - ], - ) - - assert s[0] == s_expected - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_hamiltonian_mix_return(self, use_csingle, wires_map): - """Test expected serialization for a Hamiltonian return""" - - ham1 = qml.Hamiltonian( - [0.3, 0.5, 0.4], - [ - qml.Hermitian(np.eye(4), wires=[0, 1]) @ qml.PauliY(2), - qml.PauliX(0) @ qml.PauliY(2), - qml.Hermitian(np.ones((8, 8)), wires=range(3)), - ], - ) - ham2 = qml.Hamiltonian( - [0.7, 0.3], - [qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2]), qml.PauliY(0) @ qml.PauliX(2)], - ) - - with qml.tape.QuantumTape() as tape: - qml.expval(ham1) - qml.expval(ham2) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - hermitian_obs = HermitianObsC64 if use_csingle else HermitianObsC128 - tensor_prod_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - r_dtype = np.float32 if use_csingle else np.float64 - c_dtype = np.complex64 if use_csingle else np.complex128 - - s, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - - s_expected1 = hamiltonian_obs( - np.array([0.3, 0.5, 0.4], dtype=r_dtype), - [ - tensor_prod_obs( - [ - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [0, 1]), - named_obs("PauliY", [2]), - ] - ), - tensor_prod_obs([named_obs("PauliX", [0]), named_obs("PauliY", [2])]), - hermitian_obs(np.ones(64, dtype=c_dtype), [0, 1, 2]), - ], - ) - s_expected2 = hamiltonian_obs( - np.array([0.7, 0.3], dtype=r_dtype), - [ - tensor_prod_obs( - [ - named_obs("PauliX", [0]), - hermitian_obs(np.eye(4, dtype=c_dtype).ravel(), [1, 2]), - ] - ), - tensor_prod_obs([named_obs("PauliY", [0]), named_obs("PauliX", [2])]), - ], - ) - - assert s[0] == s_expected1 - assert s[1] == s_expected2 - - @pytest.mark.parametrize( - "obs,coeffs,terms", - [ - (qml.prod(qml.PauliZ(0), qml.PauliX(1)), [1], [[("PauliX", 1), ("PauliZ", 0)]]), - (qml.s_prod(0.1, qml.PauliX(0)), [0.1], ("PauliX", 0)), - ( - qml.sum( - 0.5 * qml.prod(qml.PauliX(0), qml.PauliZ(1)), - 0.1 * qml.prod(qml.PauliZ(0), qml.PauliY(1)), - ), - [0.5, 0.1], - [[("PauliZ", 1), ("PauliX", 0)], [("PauliY", 1), ("PauliZ", 0)]], - ), - ], - ) - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_op_arithmetic_uses_hamiltonian(self, use_csingle, obs, coeffs, terms, wires_map): - """Tests that an arithmetic obs with a PauliRep serializes as a Hamiltonian.""" - tape = qml.tape.QuantumTape(measurements=[qml.expval(obs)]) - res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - assert len(res) == 1 - assert isinstance(res[0], HamiltonianC64 if use_csingle else HamiltonianC128) - - hamiltonian_obs = HamiltonianC64 if use_csingle else HamiltonianC128 - tensor_obs = TensorProdObsC64 if use_csingle else TensorProdObsC128 - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - rtype = np.float32 if use_csingle else np.float64 - term_shape = np.array(terms).shape - - if len(term_shape) == 1: # just a single pauli op - expected_terms = [named_obs(terms[0], [terms[1]])] - elif len(term_shape) == 3: # list of tensor products - expected_terms = [ - tensor_obs([named_obs(pauli, [wire]) for pauli, wire in term]) for term in terms - ] - - coeffs = np.array(coeffs).astype(rtype) - assert res[0] == hamiltonian_obs(coeffs, expected_terms) - - @pytest.mark.parametrize("use_csingle", [True, False]) - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_multi_wire_identity(self, use_csingle, wires_map): - """Tests that multi-wire Identity does not fail serialization.""" - tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Identity(wires=[1, 2]))]) - res, _ = QuantumScriptSerializer(device_name, use_csingle).serialize_observables( - tape, wires_map - ) - assert len(res) == 1 - - named_obs = NamedObsC64 if use_csingle else NamedObsC128 - assert res[0] == named_obs("Identity", [1]) - - -class TestSerializeOps: - """Tests for the _ops function""" - - wires_dict = {i: i for i in range(10)} - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_basic_circuit(self, wires_map): - """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 = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - s_expected = ( - ( - ["RX", "RY", "CNOT"], - [np.array([0.4]), np.array([0.6]), []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - [[], [], []], - [[], [], []], - ), - False, - ) - assert s == s_expected - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_basic_circuit_not_implemented_ctrl_ops(self, wires_map): - """Test expected serialization for a simple circuit""" - ops = qml.OrbitalRotation(0.1234, wires=range(4)) - with qml.tape.QuantumTape() as tape: - qml.RX(0.4, wires=0) - qml.RY(0.6, wires=1) - qml.ctrl(ops, [4, 5]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - s_expected = ( - ( - ["RX", "RY", "QubitUnitary"], - [np.array([0.4]), np.array([0.6]), [0.0]], - [[0], [1], list(ops.wires)], - [False, False, False], - [[], [], [qml.matrix(ops)]], - [[], [], [4, 5]], - ), - 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(s0, s1) for s0, s1 in zip(s[0][4], s_expected[0][4])) - assert s[0][5] == s_expected[0][5] - assert s[1] == s_expected[1] - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_multicontrolledx(self, wires_map): - """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.ctrl(qml.PauliX(wires=0), [1, 2, 3], control_values=[True, False, False]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - s_expected = ( - ( - ["RX", "RY", "PauliX"], - [np.array([0.4]), np.array([0.6]), []], - [[0], [1], [0]], - [False, False, False], - [[], [], []], - [[], [], [1, 2, 3]], - [[], [], [True, False, False]], - ), - False, - ) - assert s == s_expected - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - @pytest.mark.parametrize("stateprep", [qml.QubitStateVector, qml.StatePrep]) - def test_skips_prep_circuit(self, stateprep, wires_map): - """Test expected serialization for a simple circuit with state preparation, such that - the state preparation is skipped""" - with qml.tape.QuantumTape() as tape: - stateprep([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 = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - s_expected = ( - ( - ["RX", "RY", "CNOT"], - [[0.4], [0.6], []], - [[0], [1], [0, 1]], - [False, False, False], - [[], [], []], - [[], [], []], - [[], [], []], - ), - True, - ) - assert s == s_expected - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - def test_unsupported_kernel_circuit(self, wires_map): - """Test expected serialization for a circuit including gates that do not have a dedicated - kernel""" - with qml.tape.QuantumTape() as tape: - qml.CNOT(wires=[0, 1]) - qml.RZ(0.2, wires=2) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - s_expected = ( - ( - ["CNOT", "RZ"], - [[], [0.2]], - [[0, 1], [2]], - [False, False], - ), - False, - ) - assert s[0][0] == s_expected[0][0] - assert s[0][1] == s_expected[0][1] - - 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]) - qml.SingleExcitation(0.5, wires=["a", 3.2]) - qml.SingleExcitationPlus(0.4, wires=["a", 3.2]) - qml.adjoint(qml.SingleExcitationMinus(0.5, wires=["a", 3.2]), lazy=False) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_dict) - s_expected = ( - ( - [ - "RX", - "RY", - "CNOT", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - ], - [[0.4], [0.6], [], [0.5], [0.4], [-0.5]], - [[0], [1], [0, 1], [0, 1], [0, 1], [0, 1]], - [False, False, False, False, False, False], - [[], [], [], [], [], []], - [[], [], [], [], [], []], - [[], [], [], [], [], []], - ), - False, - ) - assert s == s_expected - - @pytest.mark.parametrize("wires_map", [wires_dict, None]) - @pytest.mark.parametrize("C", [True, False]) - def test_integration(self, C, wires_map): - """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) - qml.CNOT(wires=[0, 1]) - qml.QubitUnitary(np.eye(4), wires=[0, 1]) - qml.templates.QFT(wires=[0, 1, 2]) - qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) - qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) - qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) - - s = QuantumScriptSerializer(device_name).serialize_ops(tape, wires_map) - - dtype = np.complex64 if C else np.complex128 - s_expected = ( - ( - [ - "RX", - "RY", - "CNOT", - "QubitUnitary", - "QFT", - "DoubleExcitation", - "DoubleExcitationMinus", - "DoubleExcitationPlus", - ], - [[0.4], [0.6], [], [0.0], [], [0.555], [0.555], [0.555]], - [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]], - [False, False, False, False, False, False, False, False], - [ - [], - [], - [], - qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])), - qml.matrix(qml.templates.QFT(wires=[0, 1, 2])), - [], - [], - [], - ], - ), - 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 all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4])) - - -def check_global_phase_diagonal(par, wires, targets, controls, control_values): - op = qml.ctrl(qml.GlobalPhase(par, wires=targets), controls, control_values=control_values) - return np.diag(op.matrix(wires)) - - -def test_global_phase(): - """Validate global_phase_diagonal with various combinations of num_qubits, targets and controls.""" - import itertools - - nmax = 7 - par = 0.1 - for nq in range(2, nmax): - wires = range(nq) - for nw in range(nq, nmax): - wire_lists = list(itertools.permutations(wires, nw)) - for wire_list in wire_lists: - for i in range(len(wire_list) - 1): - targets = wire_list[0:i] - controls = wire_list[i:] - control_values = [i % 2 == 0 for i in controls] - D0 = check_global_phase_diagonal(par, wires, targets, controls, control_values) - D1 = global_phase_diagonal(par, wires, controls, control_values) - assert np.allclose(D0, D1) From 4ee66095d4e3c3e2ecc2a89de65b3d5a15afc673 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 22 Feb 2024 17:44:36 -0500 Subject: [PATCH 052/105] adding measurement tests --- .../lightning_qubit/_measurements.py | 1 + tests/lightning_qubit2/test_measurements.py | 175 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 tests/lightning_qubit2/test_measurements.py diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 120a6696bb..8554e43ea0 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -50,6 +50,7 @@ def __init__(self, qubit_state: LightningStateVector) -> None: self.state = qubit_state.state_vector self.dtype = qubit_state.dtype self.measurement_lightning = self._measurement_dtype()(self.state) + def _measurement_dtype(self): """Binding to Lightning Managed state vector. diff --git a/tests/lightning_qubit2/test_measurements.py b/tests/lightning_qubit2/test_measurements.py new file mode 100644 index 0000000000..9ea81bdabb --- /dev/null +++ b/tests/lightning_qubit2/test_measurements.py @@ -0,0 +1,175 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import numpy as np + +import pennylane as qml + +try: + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) +except ImportError: + pass + +from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + + +class CustomStateMeasurment(qml.measurements.StateMeasurement): + + def process_state(self, state, wire_order): + return 1 + + +def test_initialization_complex64(): + """Tests for the initialization of the LightningMeasurements class with np.complex64.""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + + assert m._qubit_state is statevector + assert m.state is statevector.state_vector + assert m.dtype == np.complex64 + assert isinstance(m.measurement_lightning, MeasurementsC64) + + +def test_initialization_complex128(): + """Tests for the initialization of the LightningMeasurements class.""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex128) + m = LightningMeasurements(statevector) + + assert m._qubit_state is statevector + assert m.state is statevector.state_vector + assert m.dtype == np.complex128 + assert isinstance(m.measurement_lightning, MeasurementsC128) + +class TestGetMeasurementFunction: + """Tests for the get_measurement_function method.""" + + def test_only_support_state_measurements(self): + """Test than a NotImplementedError is raised if the measurement is not a state measurement.""" + + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + + mp = qml.counts(wires=(0,1)) + with pytest.raises(NotImplementedError): + m.get_measurement_function(mp) + + @pytest.mark.parametrize("mp", (qml.probs(wires=0), + qml.var(qml.Z(0)), + qml.vn_entropy(wires=0), + CustomStateMeasurment(), + qml.expval(qml.Identity(0)), + qml.expval(qml.Projector([1, 0], wires=(0,1))) + )) + def test_state_diagonalizing_gates_measurements(self, mp): + """Test that any non-expval measurement is""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + + assert m.get_measurement_function(mp) == m.state_diagonalizing_gates + + @pytest.mark.parametrize("obs", ( + qml.X(0), + qml.Y(0), + qml.Z(0), + qml.sum(qml.X(0), qml.Y(0)), + qml.prod(qml.X(0), qml.Y(1)), + qml.s_prod(2.0, qml.X(0)), + qml.Hamiltonian([1.0, 2.0], [qml.X(0), qml.Y(0)]), + qml.Hermitian(np.eye(2), wires=0), + qml.SparseHamiltonian(qml.X.compute_sparse_matrix(), wires=0), + )) + def test_expval_selected(self, obs): + """Test that expval is chosen for a variety of different expectation values.""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + mp = qml.expval(obs) + assert m.get_measurement_function(mp) == m.expval + + +def expected_entropy_ising_xx(param): + """ + Return the analytical entropy for the IsingXX. + """ + eig_1 = (1 + np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 + eig_2 = (1 - np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 + eigs = [eig_1, eig_2] + eigs = [eig for eig in eigs if eig > 0] + + expected_entropy = eigs * np.log(eigs) + + expected_entropy = -np.sum(expected_entropy) + return expected_entropy + + +@pytest.mark.parametrize("method_name", ("state_diagonalizing_gates", "measurement")) +class TestStateDiagonalizingGates: + """Tests for various measurements that go through state_diagonalizing_gates""" + + def test_vn_entropy(self, method_name): + """Test that state_diagonalizing_gates can handle an arbitrary measurement process.""" + phi = 0.5 + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector.apply_operations([qml.IsingXX(phi, wires=(0,1))]) + m = LightningMeasurements(statevector) + measurement = qml.vn_entropy(wires=0) + result = getattr(m, method_name)(measurement) + assert qml.math.allclose(result, expected_entropy_ising_xx(phi)) + + def test_custom_measurement(self, method_name): + """Test that LightningMeasurements can handle a custom state based measurement.""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + measurement = CustomStateMeasurment() + result = getattr(m, method_name)(measurement) + assert result == 1 + + def test_measurement_with_diagonalizing_gates(self, method_name): + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + m = LightningMeasurements(statevector) + measurement = qml.probs(op=qml.X(0)) + result = getattr(m, method_name)(measurement) + assert qml.math.allclose(result, [0.5, 0.5]) + + def test_identity_expval(self, method_name): + """Test that the expectation value of an identity is always one.""" + statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector.apply_operations([qml.Rot(0.5, 4.2, 6.8, wires=4)]) + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(qml.I(4))) + assert np.allclose(result, 1.0) + + def test_basis_state_projector_expval(self, method_name): + """Test expectation value for a basis state projector.""" + phi = 0.8 + statevector = LightningStateVector(num_wires=1, dtype=np.complex64) + statevector.apply_operations([qml.RX(phi, 0)]) + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(qml.Projector([0], wires=0))) + assert qml.math.allclose(result, np.cos(phi/2)**2) + + def test_state_vector_projector_expval(self, method_name): + """Test expectation value for a state vector projector.""" + phi = -0.6 + statevector = LightningStateVector(num_wires=1, dtype=np.complex64) + statevector.apply_operations([qml.RX(phi, 0)]) + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(qml.Projector([0, 1], wires=0))) + assert qml.math.allclose(result, np.sin(phi/2)**2) + From e8b606e8cbbcf33753eb3fe862183f1e07486ae1 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 08:43:35 -0500 Subject: [PATCH 053/105] update state vector and tests --- .../lightning_qubit/_state_vector.py | 50 ++++++++----------- tests/lightning_qubit/test_state_vector.py | 2 +- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index b864381c07..0351f8277c 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -52,18 +52,18 @@ class LightningStateVector: """ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit"): - self.num_wires = num_wires + self._num_wires = num_wires self._wires = Wires(range(num_wires)) self._dtype = dtype - if dtype not in [np.complex64, np.complex128]: + if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") if device_name != "lightning.qubit": raise DeviceError(f'The device name "{device_name}" is not a valid option.') - self._name = device_name - self._qubit_state = self._state_dtype()(self.num_wires) + self._device_name = device_name + self._qubit_state = self._state_dtype()(self._num_wires) @property def dtype(self): @@ -71,9 +71,9 @@ def dtype(self): return self._dtype @property - def name(self): + def device_name(self): """Returns the state vector device name.""" - return self._name + return self._device_name @property def wires(self): @@ -88,10 +88,6 @@ def _state_dtype(self): Returns: the state vector class """ - if self.dtype not in [np.complex128, np.complex64]: # pragma: no cover - raise ValueError( - f"Data type is not supported for state-vector computation: {self.dtype}" - ) return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 @staticmethod @@ -150,7 +146,7 @@ def state(self): >>> print(dev.state) [0.+0.j 1.+0.j] """ - state = np.zeros(2**self.num_wires, dtype=self.dtype) + state = np.zeros(2**self._num_wires, dtype=self.dtype) state = self._asarray(state, dtype=self.dtype) self._qubit_state.getState(state) return state @@ -175,21 +171,21 @@ def _preprocess_state_vector(self, state, device_wires): """ # special case for integral types if state.dtype.kind == "i": - state = qml.numpy.array(state, dtype=self.dtype) + state = np.array(state, dtype=self.dtype) state = self._asarray(state, dtype=self.dtype) - if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: + if len(device_wires) == self._num_wires and Wires(sorted(device_wires)) == device_wires: return None, state # generate basis states on subset of qubits via the cartesian product basis_states = np.array(list(product([0, 1], repeat=len(device_wires)))) # get basis states to alter on full set of qubits - unravelled_indices = np.zeros((2 ** len(device_wires), self.num_wires), dtype=int) + unravelled_indices = np.zeros((2 ** len(device_wires), self._num_wires), dtype=int) unravelled_indices[:, device_wires] = basis_states # get indices for which the state is changed to input state vector elements - ravelled_indices = np.ravel_multi_index(unravelled_indices.T, [2] * self.num_wires) + ravelled_indices = np.ravel_multi_index(unravelled_indices.T, [2] * self._num_wires) return ravelled_indices, state def _get_basis_state_index(self, state, wires): @@ -213,7 +209,7 @@ def _get_basis_state_index(self, state, wires): raise ValueError("BasisState parameter and wires must be of equal length.") # get computational basis state number - basis_states = 2 ** (self.num_wires - 1 - np.array(wires)) + basis_states = 2 ** (self._num_wires - 1 - np.array(wires)) basis_states = qml.math.convert_like(basis_states, state) return int(qml.math.dot(state, basis_states)) @@ -233,9 +229,9 @@ def _apply_state_vector(self, state, device_wires): ravelled_indices, state = self._preprocess_state_vector(state, device_wires) # translate to wire labels used by device - output_shape = [2] * self.num_wires + output_shape = [2] * self._num_wires - if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: + if len(device_wires) == self._num_wires and Wires(sorted(device_wires)) == device_wires: # Initialize the entire device state with the input state state = np.reshape(state, output_shape).ravel(order="C") self._qubit_state.UpdateData(state) @@ -282,17 +278,13 @@ def _apply_lightning_controlled(self, operation): else: # apply gate as an n-controlled matrix method = getattr(state, "applyControlledMatrix") target_wires = self.wires.indices(operation.target_wires) - try: - method( - qml.matrix(operation.base), - control_wires, - control_values, - target_wires, - False, - ) - except AttributeError: # pragma: no cover - # To support older versions of PL - method(operation.base.matrix, control_wires, control_values, target_wires, False) + method( + qml.matrix(operation.base), + control_wires, + control_values, + target_wires, + False, + ) def _apply_lightning(self, operations): """Apply a list of operations to the state tensor. diff --git a/tests/lightning_qubit/test_state_vector.py b/tests/lightning_qubit/test_state_vector.py index 76527127ec..b7ba9197ce 100644 --- a/tests/lightning_qubit/test_state_vector.py +++ b/tests/lightning_qubit/test_state_vector.py @@ -41,7 +41,7 @@ def test_device_name_and_init(num_wires, dtype, device_name): """Test the class initialization and returned properties.""" state_vector = LightningStateVector(num_wires, dtype=dtype, device_name=device_name) assert state_vector.dtype == dtype - assert state_vector.name == device_name + assert state_vector.device_name == device_name assert state_vector.wires == Wires(range(num_wires)) From 7ab3adeb84720f50eb3c6e5943781ad00cecbe10 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 08:58:48 -0500 Subject: [PATCH 054/105] move and rename test files, and format --- .../lightning_qubit/_measurements.py | 1 - .../test_measurements_class.py} | 60 ++++++++++--------- ...e_vector.py => test_state_vector_class.py} | 0 3 files changed, 33 insertions(+), 28 deletions(-) rename tests/{lightning_qubit2/test_measurements.py => lightning_qubit/test_measurements_class.py} (85%) rename tests/lightning_qubit/{test_state_vector.py => test_state_vector_class.py} (100%) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 8554e43ea0..120a6696bb 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -50,7 +50,6 @@ def __init__(self, qubit_state: LightningStateVector) -> None: self.state = qubit_state.state_vector self.dtype = qubit_state.dtype self.measurement_lightning = self._measurement_dtype()(self.state) - def _measurement_dtype(self): """Binding to Lightning Managed state vector. diff --git a/tests/lightning_qubit2/test_measurements.py b/tests/lightning_qubit/test_measurements_class.py similarity index 85% rename from tests/lightning_qubit2/test_measurements.py rename to tests/lightning_qubit/test_measurements_class.py index 9ea81bdabb..eb5512b9c7 100644 --- a/tests/lightning_qubit2/test_measurements.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -30,8 +30,7 @@ from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements -class CustomStateMeasurment(qml.measurements.StateMeasurement): - +class CustomStateMeasurement(qml.measurements.StateMeasurement): def process_state(self, state, wire_order): return 1 @@ -57,6 +56,7 @@ def test_initialization_complex128(): assert m.dtype == np.complex128 assert isinstance(m.measurement_lightning, MeasurementsC128) + class TestGetMeasurementFunction: """Tests for the get_measurement_function method.""" @@ -66,17 +66,21 @@ def test_only_support_state_measurements(self): statevector = LightningStateVector(num_wires=5, dtype=np.complex64) m = LightningMeasurements(statevector) - mp = qml.counts(wires=(0,1)) + mp = qml.counts(wires=(0, 1)) with pytest.raises(NotImplementedError): m.get_measurement_function(mp) - @pytest.mark.parametrize("mp", (qml.probs(wires=0), - qml.var(qml.Z(0)), - qml.vn_entropy(wires=0), - CustomStateMeasurment(), - qml.expval(qml.Identity(0)), - qml.expval(qml.Projector([1, 0], wires=(0,1))) - )) + @pytest.mark.parametrize( + "mp", + ( + qml.probs(wires=0), + qml.var(qml.Z(0)), + qml.vn_entropy(wires=0), + CustomStateMeasurement(), + qml.expval(qml.Identity(0)), + qml.expval(qml.Projector([1, 0], wires=(0, 1))), + ), + ) def test_state_diagonalizing_gates_measurements(self, mp): """Test that any non-expval measurement is""" statevector = LightningStateVector(num_wires=5, dtype=np.complex64) @@ -84,17 +88,20 @@ def test_state_diagonalizing_gates_measurements(self, mp): assert m.get_measurement_function(mp) == m.state_diagonalizing_gates - @pytest.mark.parametrize("obs", ( - qml.X(0), - qml.Y(0), - qml.Z(0), - qml.sum(qml.X(0), qml.Y(0)), - qml.prod(qml.X(0), qml.Y(1)), - qml.s_prod(2.0, qml.X(0)), - qml.Hamiltonian([1.0, 2.0], [qml.X(0), qml.Y(0)]), - qml.Hermitian(np.eye(2), wires=0), - qml.SparseHamiltonian(qml.X.compute_sparse_matrix(), wires=0), - )) + @pytest.mark.parametrize( + "obs", + ( + qml.X(0), + qml.Y(0), + qml.Z(0), + qml.sum(qml.X(0), qml.Y(0)), + qml.prod(qml.X(0), qml.Y(1)), + qml.s_prod(2.0, qml.X(0)), + qml.Hamiltonian([1.0, 2.0], [qml.X(0), qml.Y(0)]), + qml.Hermitian(np.eye(2), wires=0), + qml.SparseHamiltonian(qml.X.compute_sparse_matrix(), wires=0), + ), + ) def test_expval_selected(self, obs): """Test that expval is chosen for a variety of different expectation values.""" statevector = LightningStateVector(num_wires=5, dtype=np.complex64) @@ -126,7 +133,7 @@ def test_vn_entropy(self, method_name): """Test that state_diagonalizing_gates can handle an arbitrary measurement process.""" phi = 0.5 statevector = LightningStateVector(num_wires=5, dtype=np.complex64) - statevector.apply_operations([qml.IsingXX(phi, wires=(0,1))]) + statevector.apply_operations([qml.IsingXX(phi, wires=(0, 1))]) m = LightningMeasurements(statevector) measurement = qml.vn_entropy(wires=0) result = getattr(m, method_name)(measurement) @@ -136,10 +143,10 @@ def test_custom_measurement(self, method_name): """Test that LightningMeasurements can handle a custom state based measurement.""" statevector = LightningStateVector(num_wires=5, dtype=np.complex64) m = LightningMeasurements(statevector) - measurement = CustomStateMeasurment() + measurement = CustomStateMeasurement() result = getattr(m, method_name)(measurement) assert result == 1 - + def test_measurement_with_diagonalizing_gates(self, method_name): statevector = LightningStateVector(num_wires=5, dtype=np.complex64) m = LightningMeasurements(statevector) @@ -162,7 +169,7 @@ def test_basis_state_projector_expval(self, method_name): statevector.apply_operations([qml.RX(phi, 0)]) m = LightningMeasurements(statevector) result = getattr(m, method_name)(qml.expval(qml.Projector([0], wires=0))) - assert qml.math.allclose(result, np.cos(phi/2)**2) + assert qml.math.allclose(result, np.cos(phi / 2) ** 2) def test_state_vector_projector_expval(self, method_name): """Test expectation value for a state vector projector.""" @@ -171,5 +178,4 @@ def test_state_vector_projector_expval(self, method_name): statevector.apply_operations([qml.RX(phi, 0)]) m = LightningMeasurements(statevector) result = getattr(m, method_name)(qml.expval(qml.Projector([0, 1], wires=0))) - assert qml.math.allclose(result, np.sin(phi/2)**2) - + assert qml.math.allclose(result, np.sin(phi / 2) ** 2) diff --git a/tests/lightning_qubit/test_state_vector.py b/tests/lightning_qubit/test_state_vector_class.py similarity index 100% rename from tests/lightning_qubit/test_state_vector.py rename to tests/lightning_qubit/test_state_vector_class.py From 9aa3e359c84723bbebd7fc4de3382d2f45cad321 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 23 Feb 2024 15:24:17 +0000 Subject: [PATCH 055/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 92ab0f4480..1325db5058 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.35.0-dev17" +__version__ = "0.35.0-dev18" From b9fa21d8f4454c3092f09af45be8393b9a521158 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 10:38:31 -0500 Subject: [PATCH 056/105] skip measurements class for other devices and in the absence of binaries --- tests/lightning_qubit/test_measurements_class.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index eb5512b9c7..751f006354 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -13,6 +13,7 @@ # limitations under the License. import pytest +from conftest import LightningDevice # tested device import numpy as np @@ -29,6 +30,13 @@ from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements +from pennylane_lightning.lightning_qubit import LightningQubit + +if not LightningQubit._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + +if LightningDevice != LightningQubit: + pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) class CustomStateMeasurement(qml.measurements.StateMeasurement): def process_state(self, state, wire_order): From 48c67245a64eedd4951fac412de5de5d41baf537 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 10:39:31 -0500 Subject: [PATCH 057/105] format --- tests/lightning_qubit/test_measurements_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 751f006354..6ed4b2525b 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -38,6 +38,7 @@ if LightningDevice != LightningQubit: pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) + class CustomStateMeasurement(qml.measurements.StateMeasurement): def process_state(self, state, wire_order): return 1 From a7166119bed60a6b00a1430f4c590e382b30c2e9 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 15:01:41 -0500 Subject: [PATCH 058/105] update measurements class --- .../lightning_qubit/_measurements.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 120a6696bb..520bd402a8 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -47,9 +47,24 @@ class LightningMeasurements: def __init__(self, qubit_state: LightningStateVector) -> None: self._qubit_state = qubit_state - self.state = qubit_state.state_vector - self.dtype = qubit_state.dtype - self.measurement_lightning = self._measurement_dtype()(self.state) + self._state = qubit_state.state_vector + self._dtype = qubit_state.dtype + self._measurement_lightning = self._measurement_dtype()(self.state) + + @property + def qubit_state(self): + """Returns a handle to the LightningStateVector class.""" + return self._qubit_state + + @property + def state(self): + """Returns a handle to the Lightning internal data class.""" + return self._state + + @property + def dtype(self): + """Returns the simulation data type.""" + return self._dtype def _measurement_dtype(self): """Binding to Lightning Managed state vector. @@ -94,7 +109,7 @@ def expval(self, measurementprocess: MeasurementProcess): CSR_SparseHamiltonian = measurementprocess.obs.sparse_matrix( wire_order=list(range(self._qubit_state.num_wires)) ).tocsr(copy=False) - return self.measurement_lightning.expval( + return self._measurement_lightning.expval( CSR_SparseHamiltonian.indptr, CSR_SparseHamiltonian.indices, CSR_SparseHamiltonian.data, @@ -106,11 +121,11 @@ def expval(self, measurementprocess: MeasurementProcess): or isinstance(measurementprocess.obs.name, List) ): ob_serialized = QuantumScriptSerializer( - self._qubit_state.name, self.dtype == np.complex64 + self._qubit_state.device_name, self.dtype == np.complex64 )._ob(measurementprocess.obs) - return self.measurement_lightning.expval(ob_serialized) + return self._measurement_lightning.expval(ob_serialized) - return self.measurement_lightning.expval( + return self._measurement_lightning.expval( measurementprocess.obs.name, measurementprocess.obs.wires ) From 261cb6d780ea85a0d7f40ffb106df971df969b85 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 15:02:12 -0500 Subject: [PATCH 059/105] expand measurement class testing --- .../test_measurements_class.py | 368 +++++++++++++++--- 1 file changed, 317 insertions(+), 51 deletions(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 6ed4b2525b..52937ff643 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -16,8 +16,10 @@ from conftest import LightningDevice # tested device import numpy as np +import math import pennylane as qml +from pennylane.tape import QuantumScript try: from pennylane_lightning.lightning_qubit_ops import ( @@ -39,40 +41,40 @@ pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) -class CustomStateMeasurement(qml.measurements.StateMeasurement): - def process_state(self, state, wire_order): - return 1 +# General LightningStateVector fixture, for any number of wires. +@pytest.fixture( + scope="function", + params=[np.complex64, np.complex128], +) +def lightning_sv(request): + def _statevector(num_wires): + return LightningStateVector(num_wires=num_wires, dtype=request.param) + return _statevector -def test_initialization_complex64(): - """Tests for the initialization of the LightningMeasurements class with np.complex64.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) - m = LightningMeasurements(statevector) - assert m._qubit_state is statevector - assert m.state is statevector.state_vector - assert m.dtype == np.complex64 - assert isinstance(m.measurement_lightning, MeasurementsC64) +class CustomStateMeasurement(qml.measurements.StateMeasurement): + def process_state(self, state, wire_order): + return 1 -def test_initialization_complex128(): +def test_initialization(lightning_sv): """Tests for the initialization of the LightningMeasurements class.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex128) + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) - assert m._qubit_state is statevector + assert m.qubit_state is statevector assert m.state is statevector.state_vector - assert m.dtype == np.complex128 - assert isinstance(m.measurement_lightning, MeasurementsC128) + assert m.dtype == statevector.dtype class TestGetMeasurementFunction: """Tests for the get_measurement_function method.""" - def test_only_support_state_measurements(self): + def test_only_support_state_measurements(self, lightning_sv): """Test than a NotImplementedError is raised if the measurement is not a state measurement.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) mp = qml.counts(wires=(0, 1)) @@ -90,9 +92,9 @@ def test_only_support_state_measurements(self): qml.expval(qml.Projector([1, 0], wires=(0, 1))), ), ) - def test_state_diagonalizing_gates_measurements(self, mp): - """Test that any non-expval measurement is""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + def test_state_diagonalizing_gates_measurements(self, lightning_sv, mp): + """Test that any non-expval measurement calls the state_diagonalizing_gates method""" + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) assert m.get_measurement_function(mp) == m.state_diagonalizing_gates @@ -111,80 +113,344 @@ def test_state_diagonalizing_gates_measurements(self, mp): qml.SparseHamiltonian(qml.X.compute_sparse_matrix(), wires=0), ), ) - def test_expval_selected(self, obs): + def test_expval_selected(self, lightning_sv, obs): """Test that expval is chosen for a variety of different expectation values.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) mp = qml.expval(obs) assert m.get_measurement_function(mp) == m.expval -def expected_entropy_ising_xx(param): - """ - Return the analytical entropy for the IsingXX. - """ - eig_1 = (1 + np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eig_2 = (1 - np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eigs = [eig_1, eig_2] - eigs = [eig for eig in eigs if eig > 0] - - expected_entropy = eigs * np.log(eigs) - - expected_entropy = -np.sum(expected_entropy) - return expected_entropy - - @pytest.mark.parametrize("method_name", ("state_diagonalizing_gates", "measurement")) class TestStateDiagonalizingGates: """Tests for various measurements that go through state_diagonalizing_gates""" - def test_vn_entropy(self, method_name): + def expected_entropy_Ising_XX(self, param): + """ + Return the analytical entropy for the IsingXX. + """ + eig_1 = (1 + np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 + eig_2 = (1 - np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 + eigs = [eig_1, eig_2] + eigs = [eig for eig in eigs if eig > 0] + + expected_entropy = eigs * np.log(eigs) + + expected_entropy = -np.sum(expected_entropy) + return expected_entropy + + def test_vn_entropy(self, lightning_sv, method_name): """Test that state_diagonalizing_gates can handle an arbitrary measurement process.""" phi = 0.5 - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector = lightning_sv(num_wires=5) statevector.apply_operations([qml.IsingXX(phi, wires=(0, 1))]) m = LightningMeasurements(statevector) measurement = qml.vn_entropy(wires=0) result = getattr(m, method_name)(measurement) - assert qml.math.allclose(result, expected_entropy_ising_xx(phi)) + assert qml.math.allclose(result, self.expected_entropy_Ising_XX(phi)) - def test_custom_measurement(self, method_name): + def test_custom_measurement(self, lightning_sv, method_name): """Test that LightningMeasurements can handle a custom state based measurement.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) measurement = CustomStateMeasurement() result = getattr(m, method_name)(measurement) assert result == 1 - def test_measurement_with_diagonalizing_gates(self, method_name): - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + def test_measurement_with_diagonalizing_gates(self, lightning_sv, method_name): + statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) measurement = qml.probs(op=qml.X(0)) result = getattr(m, method_name)(measurement) assert qml.math.allclose(result, [0.5, 0.5]) - def test_identity_expval(self, method_name): + def test_identity_expval(self, lightning_sv, method_name): """Test that the expectation value of an identity is always one.""" - statevector = LightningStateVector(num_wires=5, dtype=np.complex64) + statevector = lightning_sv(num_wires=5) statevector.apply_operations([qml.Rot(0.5, 4.2, 6.8, wires=4)]) m = LightningMeasurements(statevector) result = getattr(m, method_name)(qml.expval(qml.I(4))) assert np.allclose(result, 1.0) - def test_basis_state_projector_expval(self, method_name): + def test_basis_state_projector_expval(self, lightning_sv, method_name): """Test expectation value for a basis state projector.""" phi = 0.8 - statevector = LightningStateVector(num_wires=1, dtype=np.complex64) + statevector = lightning_sv(num_wires=1) statevector.apply_operations([qml.RX(phi, 0)]) m = LightningMeasurements(statevector) result = getattr(m, method_name)(qml.expval(qml.Projector([0], wires=0))) assert qml.math.allclose(result, np.cos(phi / 2) ** 2) - def test_state_vector_projector_expval(self, method_name): + def test_state_vector_projector_expval(self, lightning_sv, method_name): """Test expectation value for a state vector projector.""" phi = -0.6 - statevector = LightningStateVector(num_wires=1, dtype=np.complex64) + statevector = lightning_sv(num_wires=1) statevector.apply_operations([qml.RX(phi, 0)]) m = LightningMeasurements(statevector) result = getattr(m, method_name)(qml.expval(qml.Projector([0, 1], wires=0))) assert qml.math.allclose(result, np.sin(phi / 2) ** 2) + + +@pytest.mark.parametrize("method_name", ("expval", "measurement")) +class TestExpval: + """Tests for the expval function""" + + wires = 2 + + @pytest.mark.parametrize( + "obs, expected", + [ + [qml.PauliX(0), -0.041892271271228736], + [qml.PauliX(1), 0.0], + [qml.PauliY(0), -0.5516350865364075], + [qml.PauliY(1), 0.0], + [qml.PauliZ(0), 0.8330328980789793], + [qml.PauliZ(1), 1.0], + ], + ) + def test_expval_qml_tape_wire0(self, obs, expected, tol, lightning_sv, method_name): + """Test expval with a circuit on wires=[0]""" + + x, y, z = [0.5, 0.3, -0.7] + statevector = lightning_sv(self.wires) + statevector.apply_operations( + [qml.RX(0.4, wires=[0]), qml.Rot(x, y, z, wires=[0]), qml.RY(-0.2, wires=[0])] + ) + + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(obs)) + + assert np.allclose(result, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize( + "obs, expected", + [ + [qml.PauliX(0), 0.0], + [qml.PauliX(1), -0.19866933079506122], + [qml.PauliY(0), -0.3894183423086505], + [qml.PauliY(1), 0.0], + [qml.PauliZ(0), 0.9210609940028852], + [qml.PauliZ(1), 0.9800665778412417], + ], + ) + def test_expval_wire01(self, obs, expected, tol, lightning_sv, method_name): + """Test expval with a circuit on wires=[0,1]""" + + statevector = lightning_sv(self.wires) + statevector.apply_operations([qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])]) + + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(obs)) + + assert np.allclose(result, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize( + "obs, coeffs, expected", + [ + ([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, expected, tol, lightning_sv, method_name): + """Test expval with Hamiltonian""" + ham = qml.Hamiltonian(coeffs, obs) + + statevector = lightning_sv(self.wires) + statevector.apply_operations([qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])]) + + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(ham)) + + assert np.allclose(result, expected, atol=tol, rtol=0) + + +# @pytest.mark.parametrize("operation,input,expected_output,par", test_data_two_wires_with_parameters) +# def measure_final_state(tol, operation, input, expected_output, par): +# """Tests that applying an operation yields the expected output state for two wire +# operations that have parameters.""" +# wires = 2 +# state_vector = LightningStateVector(wires) +# tape = QuantumScript( +# [qml.StatePrep(np.array(input), Wires(range(wires))), operation(*par, Wires(range(wires)))] +# ) +# final_state = state_vector.get_final_state(tape) + +# assert np.allclose(final_state.state, np.array(expected_output), atol=tol, rtol=0) +# assert final_state.state.dtype == final_state.dtype +# assert final_state == state_vector + + +THETA = np.linspace(0.11, 1, 3) +PHI = np.linspace(0.32, 1, 3) + + +@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) +class TestExpval: + """Test expectation value calculations""" + + def test_Identity(self, theta, phi, tol, lightning_sv): + """Tests applying identities.""" + + wires = 3 + ops = [ + qml.Identity(0), + qml.Identity((0, 1)), + qml.Identity((1, 2)), + qml.RX(theta, 0), + qml.RX(phi, 1), + ] + measurements = [qml.expval(qml.PauliZ(0))] + tape = qml.tape.QuantumScript(ops, measurements) + + statevector = lightning_sv(wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = np.cos(theta) + + assert np.allclose(result, expected, tol) + + def test_identity_expectation(self, theta, phi, tol, lightning_sv): + """Tests identity expectations.""" + + wires = 2 + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.Identity(wires=[0])), qml.expval(qml.Identity(wires=[1]))], + ) + statevector = lightning_sv(wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = 1.0 + + assert np.allclose(result, expected, tol) + + def test_multi_wire_identity_expectation(self, theta, phi, tol, lightning_sv): + """Tests multi-wire identity.""" + wires = 2 + tape = qml.tape.QuantumScript( + [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(qml.Identity(wires=[0, 1]))], + ) + statevector = lightning_sv(wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = 1.0 + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize( + "Obs, Op, expected_fn", + [ + ( + [qml.PauliX(wires=[0]), qml.PauliX(wires=[1])], + qml.RY, + lambda theta, phi: np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), + ), + ( + [qml.PauliY(wires=[0]), qml.PauliY(wires=[1])], + qml.RX, + lambda theta, phi: np.array([0, -np.cos(theta) * np.sin(phi)]), + ), + ( + [qml.PauliZ(wires=[0]), qml.PauliZ(wires=[1])], + qml.RX, + lambda theta, phi: np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), + ), + ( + [qml.Hadamard(wires=[0]), qml.Hadamard(wires=[1])], + qml.RY, + lambda theta, phi: np.array( + [ + np.sin(theta) * np.sin(phi) + np.cos(theta), + np.cos(theta) * np.cos(phi) + np.sin(phi), + ] + ) + / np.sqrt(2), + ), + ], + ) + def test_single_wire_observables_expectation( + self, Obs, Op, expected_fn, theta, phi, tol, lightning_sv + ): + """Test that expectation values for single wire observables is correct""" + wires = 3 + tape = qml.tape.QuantumScript( + [Op(theta, wires=[0]), Op(phi, wires=[1]), qml.CNOT(wires=[0, 1])], + [qml.expval(Obs[0]), qml.expval(Obs[1])], + ) + statevector = lightning_sv(wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = expected_fn(theta, phi) + + assert np.allclose(result, expected, tol) + + +@pytest.mark.parametrize("phi", PHI) +class TestExpOperatorArithmetic: + """Test integration with SProd, Prod, and Sum.""" + + wires = 2 + + def test_sprod(self, phi, lightning_sv, tol): + """Test the `SProd` class.""" + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=0)], + [qml.expval(qml.s_prod(0.5, qml.PauliZ(0)))], + ) + statevector = lightning_sv(self.wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = 0.5 * np.cos(phi) + + assert np.allclose(result, expected, tol) + + def test_prod(self, phi, lightning_sv, tol): + """Test the `Prod` class.""" + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=0), qml.Hadamard(1), qml.PauliZ(1)], + [qml.expval(qml.prod(qml.PauliZ(0), qml.PauliX(1)))], + ) + statevector = lightning_sv(self.wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = -np.cos(phi) + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize("theta", THETA) + def test_sum(self, phi, theta, lightning_sv, tol): + """Test the `Sum` class.""" + tape = qml.tape.QuantumScript( + [qml.RX(phi, wires=0), qml.RY(theta, wires=1)], + [qml.expval(qml.sum(qml.PauliZ(0), qml.PauliX(1)))], + ) + statevector = lightning_sv(self.wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = np.cos(phi) + np.sin(theta) + + assert np.allclose(result, expected, tol) From aca8468697ea43de911ab87545acf4ca529733a5 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 15:04:54 -0500 Subject: [PATCH 060/105] garbage collection --- tests/lightning_qubit/test_measurements_class.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 52937ff643..fa67cab7c7 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -280,22 +280,6 @@ def test_expval_hamiltonian(self, obs, coeffs, expected, tol, lightning_sv, meth assert np.allclose(result, expected, atol=tol, rtol=0) -# @pytest.mark.parametrize("operation,input,expected_output,par", test_data_two_wires_with_parameters) -# def measure_final_state(tol, operation, input, expected_output, par): -# """Tests that applying an operation yields the expected output state for two wire -# operations that have parameters.""" -# wires = 2 -# state_vector = LightningStateVector(wires) -# tape = QuantumScript( -# [qml.StatePrep(np.array(input), Wires(range(wires))), operation(*par, Wires(range(wires)))] -# ) -# final_state = state_vector.get_final_state(tape) - -# assert np.allclose(final_state.state, np.array(expected_output), atol=tol, rtol=0) -# assert final_state.state.dtype == final_state.dtype -# assert final_state == state_vector - - THETA = np.linspace(0.11, 1, 3) PHI = np.linspace(0.32, 1, 3) From 80e34942d107ff7cc5d9aac2b7acd2e3696248ef Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Fri, 23 Feb 2024 15:12:25 -0500 Subject: [PATCH 061/105] typo --- tests/lightning_qubit/test_measurements_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index fa67cab7c7..1e91c3f92e 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -375,7 +375,7 @@ def test_multi_wire_identity_expectation(self, theta, phi, tol, lightning_sv): def test_single_wire_observables_expectation( self, Obs, Op, expected_fn, theta, phi, tol, lightning_sv ): - """Test that expectation values for single wire observables is correct""" + """Test that expectation values for single wire observables are correct""" wires = 3 tape = qml.tape.QuantumScript( [Op(theta, wires=[0]), Op(phi, wires=[1]), qml.CNOT(wires=[0, 1])], From a284d7817cfa594b85c96d250ca9684cd5f72e80 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Mon, 26 Feb 2024 16:36:06 -0500 Subject: [PATCH 062/105] update coverage and StateVector class --- .../lightning_qubit/_state_vector.py | 5 ++++ .../test_state_vector_class.py | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 0351f8277c..70e9bc1ee5 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -80,6 +80,11 @@ def wires(self): """All wires that can be addressed on this device""" return self._wires + @property + def num_wires(self): + """Number of wires addressed on this device""" + return self._num_wires + def _state_dtype(self): """Binding to Lightning Managed state vector. diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index b7ba9197ce..7ff1eef23e 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -90,6 +90,31 @@ def test_apply_operation_state_preparation(tol, operation, expected_output, par) assert np.allclose(state_vector.state, np.array(expected_output), atol=tol, rtol=0) +@pytest.mark.parametrize( + "operation,par", + [ + (qml.BasisState, [1, 0]), + (qml.QubitStateVector, [0, 0, 1, 0]), + ( + qml.StatePrep, + [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], + ), + ], +) +def test_reset_state(tol, operation, par): + """Tests that applying an operation yields the expected output state for single wire + operations that have no parameters.""" + + wires = 2 + state_vector = LightningStateVector(wires) + state_vector.apply_operations([operation(np.array(par), Wires(range(wires)))]) + + state_vector.reset_state() + + expected_output = state_vector._asarray([1, 0, 0, 0]) + assert np.allclose(state_vector.state, expected_output, atol=tol, rtol=0) + + test_data_no_parameters = [ (qml.PauliX, [1, 0], [0, 1]), (qml.PauliX, [1 / math.sqrt(2), 1 / math.sqrt(2)], [1 / math.sqrt(2), 1 / math.sqrt(2)]), From 43f72f60f3ea73eea3d6ce65ef8c186cb9d009ce Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Mon, 26 Feb 2024 16:36:52 -0500 Subject: [PATCH 063/105] expand measurements class coverage --- .../test_measurements_class.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1e91c3f92e..e9fb6e482d 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -390,6 +390,43 @@ def test_single_wire_observables_expectation( assert np.allclose(result, expected, tol) +class TestSparseExpval: + """Tests for the expval function""" + + wires = 2 + + @pytest.mark.parametrize( + "ham_terms, expected", + [ + [qml.PauliX(0) @ qml.Identity(1), 0.00000000000000000], + [qml.Identity(0) @ qml.PauliX(1), -0.19866933079506122], + [qml.PauliY(0) @ qml.Identity(1), -0.38941834230865050], + [qml.Identity(0) @ qml.PauliY(1), 0.00000000000000000], + [qml.PauliZ(0) @ qml.Identity(1), 0.92106099400288520], + [qml.Identity(0) @ qml.PauliZ(1), 0.98006657784124170], + ], + ) + def test_sparse_Pauli_words(self, ham_terms, expected, tol, lightning_sv): + """Test expval of some simple sparse Hamiltonian""" + + ops = [qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])] + measurements = [ + qml.expval( + qml.SparseHamiltonian( + qml.Hamiltonian([1], [ham_terms]).sparse_matrix(), wires=[0, 1] + ) + ) + ] + tape = qml.tape.QuantumScript(ops, measurements) + + statevector = lightning_sv(self.wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize("phi", PHI) class TestExpOperatorArithmetic: """Test integration with SProd, Prod, and Sum.""" From 2a973d953444c2daa6f9e384b1a9bc197831f3ed Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Mon, 26 Feb 2024 21:37:18 +0000 Subject: [PATCH 064/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 1325db5058..690571c1e5 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.35.0-dev18" +__version__ = "0.35.0-dev19" From f42f2980e0878246c6c7893387d0ab0460a2b744 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 27 Feb 2024 11:04:12 -0500 Subject: [PATCH 065/105] add coverage for n-controlled operations --- .../test_measurements_class.py | 191 +++++++++++++++++- 1 file changed, 190 insertions(+), 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index e9fb6e482d..7e4a5ff01d 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -14,12 +14,13 @@ import pytest from conftest import LightningDevice # tested device +from pennylane.devices import DefaultQubit import numpy as np import math +import itertools import pennylane as qml -from pennylane.tape import QuantumScript try: from pennylane_lightning.lightning_qubit_ops import ( @@ -427,6 +428,194 @@ def test_sparse_Pauli_words(self, ham_terms, expected, tol, lightning_sv): assert np.allclose(result, expected, tol) +class TestControlledOps: + """Tests for controlled operations""" + + @staticmethod + def calculate_reference(tape): + dev = DefaultQubit(max_workers=1) + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + @pytest.mark.parametrize( + "operation", + [ + qml.PauliX, + qml.PauliY, + qml.PauliZ, + qml.Hadamard, + qml.S, + qml.T, + qml.PhaseShift, + qml.RX, + qml.RY, + qml.RZ, + qml.Rot, + qml.SWAP, + qml.IsingXX, + qml.IsingXY, + qml.IsingYY, + qml.IsingZZ, + qml.SingleExcitation, + qml.SingleExcitationMinus, + qml.SingleExcitationPlus, + qml.DoubleExcitation, + qml.DoubleExcitationMinus, + qml.DoubleExcitationPlus, + qml.MultiRZ, + qml.GlobalPhase, + ], + ) + @pytest.mark.parametrize("control_value", [False, True]) + @pytest.mark.parametrize("n_qubits", list(range(2, 5))) + def test_controlled_qubit_gates(self, operation, n_qubits, control_value, tol, lightning_sv): + """Test that multi-controlled gates are correctly applied to a state""" + threshold = 250 + num_wires = max(operation.num_wires, 1) + + for n_wires in range(num_wires + 1, num_wires + 4): + wire_lists = list(itertools.permutations(range(0, n_qubits), n_wires)) + n_perms = len(wire_lists) * n_wires + if n_perms > threshold: + wire_lists = wire_lists[0 :: (n_perms // threshold)] + for all_wires in wire_lists: + target_wires = all_wires[0:num_wires] + control_wires = all_wires[num_wires:] + init_state = np.random.rand(2**n_qubits) + 1.0j * np.random.rand(2**n_qubits) + init_state /= np.sqrt(np.dot(np.conj(init_state), init_state)) + + ops = [ + qml.StatePrep(init_state, wires=range(n_qubits)), + ] + + if operation.num_params == 0: + ops += [ + qml.ctrl( + operation(target_wires), + control_wires, + control_values=[ + control_value or bool(i % 2) for i, _ in enumerate(control_wires) + ], + ), + ] + else: + ops += [ + qml.ctrl( + operation(*tuple([0.1234] * operation.num_params), target_wires), + control_wires, + control_values=[ + control_value or bool(i % 2) for i, _ in enumerate(control_wires) + ], + ), + ] + + measurements = [qml.state()] + tape = qml.tape.QuantumScript(ops, measurements) + + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = self.calculate_reference(tape) + + assert np.allclose(result, expected, tol) + + def test_controlled_qubit_unitary_from_op(self, tol, lightning_sv): + n_qubits = 10 + par = 0.1234 + + tape = qml.tape.QuantumScript( + [ + qml.ControlledQubitUnitary( + qml.QubitUnitary(qml.RX.compute_matrix(par), wires=5), control_wires=range(5) + ) + ], + [qml.expval(qml.PauliX(0))], + ) + + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = self.calculate_reference(tape) + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize("control_wires", range(4)) + @pytest.mark.parametrize("target_wires", range(4)) + def test_cnot_controlled_qubit_unitary(self, control_wires, target_wires, tol, lightning_sv): + """Test that ControlledQubitUnitary is correctly applied to a state""" + if control_wires == target_wires: + return + n_qubits = 4 + control_wires = [control_wires] + target_wires = [target_wires] + wires = control_wires + target_wires + U = qml.matrix(qml.PauliX(target_wires)) + init_state = np.random.rand(2**n_qubits) + 1.0j * np.random.rand(2**n_qubits) + init_state /= np.sqrt(np.dot(np.conj(init_state), init_state)) + + tape = qml.tape.QuantumScript( + [ + qml.StatePrep(init_state, wires=range(n_qubits)), + qml.ControlledQubitUnitary(U, control_wires=control_wires, wires=target_wires), + ], + [qml.state()], + ) + tape_cnot = qml.tape.QuantumScript( + [qml.StatePrep(init_state, wires=range(n_qubits)), qml.CNOT(wires=wires)], [qml.state()] + ) + + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = self.calculate_reference(tape_cnot) + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize("control_value", [False, True]) + @pytest.mark.parametrize("n_qubits", list(range(2, 8))) + def test_controlled_globalphase(self, n_qubits, control_value, tol, lightning_sv): + """Test that multi-controlled gates are correctly applied to a state""" + threshold = 250 + operation = qml.GlobalPhase + num_wires = max(operation.num_wires, 1) + for n_wires in range(num_wires + 1, num_wires + 4): + wire_lists = list(itertools.permutations(range(0, n_qubits), n_wires)) + n_perms = len(wire_lists) * n_wires + if n_perms > threshold: + wire_lists = wire_lists[0 :: (n_perms // threshold)] + for all_wires in wire_lists: + target_wires = all_wires[0:num_wires] + control_wires = all_wires[num_wires:] + init_state = np.random.rand(2**n_qubits) + 1.0j * np.random.rand(2**n_qubits) + init_state /= np.sqrt(np.dot(np.conj(init_state), init_state)) + + tape = qml.tape.QuantumScript( + [ + qml.StatePrep(init_state, wires=range(n_qubits)), + qml.ctrl( + operation(0.1234, target_wires), + control_wires, + control_values=[ + control_value or bool(i % 2) for i, _ in enumerate(control_wires) + ], + ), + ], + [qml.state()], + ) + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = self.calculate_reference(tape) + + assert np.allclose(result, expected, tol) + + @pytest.mark.parametrize("phi", PHI) class TestExpOperatorArithmetic: """Test integration with SProd, Prod, and Sum.""" From 9cd54710d9cce2cc6d52a2c9bf7debf24b5dc0cb Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 27 Feb 2024 11:05:25 -0500 Subject: [PATCH 066/105] add map to standard wires to get_final_state for safety --- pennylane_lightning/lightning_qubit/_state_vector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 70e9bc1ee5..8084e981d8 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -353,6 +353,7 @@ def get_final_state(self, circuit: QuantumScript): LightningStateVector: Lightning final state class. """ + circuit = circuit.map_to_standard_wires() self.apply_operations(circuit.operations) return self From cd3096c8e6799d8cd57a1b545a179a449205e290 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 27 Feb 2024 11:19:26 -0500 Subject: [PATCH 067/105] update jax config import --- mpitests/test_adjoint_jacobian.py | 2 +- tests/test_adjoint_jacobian.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mpitests/test_adjoint_jacobian.py b/mpitests/test_adjoint_jacobian.py index 0c7407bb27..8d6cc8e219 100644 --- a/mpitests/test_adjoint_jacobian.py +++ b/mpitests/test_adjoint_jacobian.py @@ -872,7 +872,7 @@ def test_interface_jax(self, dev): jax = pytest.importorskip("jax") if dev.R_DTYPE == np.float64: - from jax.config import config # pylint: disable=import-outside-toplevel + from jax import config # pylint: disable=import-outside-toplevel config.update("jax_enable_x64", True) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index eaf6aa59eb..871a39e338 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -998,7 +998,7 @@ def test_interface_jax(self, dev): jax = pytest.importorskip("jax") if dev.R_DTYPE == np.float64: - from jax.config import config + from jax import config config.update("jax_enable_x64", True) From 98db9dbe10bff5604b07aea3ec5f1baec78dcdef Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Tue, 27 Feb 2024 16:55:15 +0000 Subject: [PATCH 068/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 690571c1e5..072fcd69e0 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.35.0-dev19" +__version__ = "0.35.0-dev20" From 4f746d0f5afbeabd16845a48b84753a1c57105c2 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 27 Feb 2024 11:57:08 -0500 Subject: [PATCH 069/105] trigger CI From 03959455ec6dd39cba1f9daf7ef52b5f7d9b02a5 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 28 Feb 2024 12:35:39 -0500 Subject: [PATCH 070/105] update state vector class and tests for improved coverage --- .../lightning_qubit/_state_vector.py | 7 ++--- .../test_state_vector_class.py | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 8084e981d8..3e041f34e1 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -218,7 +218,7 @@ def _get_basis_state_index(self, state, wires): basis_states = qml.math.convert_like(basis_states, state) return int(qml.math.dot(state, basis_states)) - def _apply_state_vector(self, state, device_wires): + def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: state (array[complex]): normalized input state of length ``2**len(wires)`` @@ -228,7 +228,7 @@ def _apply_state_vector(self, state, device_wires): if isinstance(state, self._qubit_state.__class__): state_data = allocate_aligned_array(state.size, np.dtype(self.dtype), True) - self._qubit_state.getState(state_data) + state.getState(state_data) state = state_data ravelled_indices, state = self._preprocess_state_vector(state, device_wires) @@ -270,8 +270,6 @@ def _apply_lightning_controlled(self, operation): state = self.state_vector basename = operation.base.name - if basename == "Identity": - return method = getattr(state, f"{basename}", None) control_wires = list(operation.control_wires) control_values = operation.control_values @@ -353,7 +351,6 @@ def get_final_state(self, circuit: QuantumScript): LightningStateVector: Lightning final state class. """ - circuit = circuit.map_to_standard_wires() self.apply_operations(circuit.operations) return self diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 7ff1eef23e..b1c4e3550d 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -58,6 +58,34 @@ def test_wrong_dtype(dtype): assert LightningStateVector(3, dtype=dtype) +@pytest.mark.parametrize("dtype", [np.double, np.complex64, None]) +@pytest.mark.parametrize("data", [1.0, [1.0], [1.0, 2.0]]) +def test_asarray(dtype, data, tol): + """Test _asarray returns the right values""" + wires = 2 + state_vector = LightningStateVector(wires) + assert np.allclose(data, state_vector._asarray(data, dtype), atol=tol) + + +def test_errors_basis_state(): + with pytest.raises(ValueError, match="BasisState parameter must consist of 0 or 1 integers."): + state_vector = LightningStateVector(2) + state_vector.apply_operations([qml.BasisState(np.array([-0.2, 4.2]), wires=[0, 1])]) + with pytest.raises(ValueError, match="BasisState parameter and wires must be of equal length."): + state_vector = LightningStateVector(1) + state_vector.apply_operations([qml.BasisState(np.array([0, 1]), wires=[0])]) + + +def test_apply_state_vector_with_lightning_handle(tol): + state_vector_1 = LightningStateVector(2) + state_vector_1.apply_operations([qml.BasisState(np.array([0, 1]), wires=[0, 1])]) + + state_vector_2 = LightningStateVector(2) + state_vector_2._apply_state_vector(state_vector_1.state_vector, Wires([0, 1])) + + assert np.allclose(state_vector_1.state, state_vector_2.state, atol=tol, rtol=0) + + @pytest.mark.parametrize( "operation,expected_output,par", [ From 4a863ba332a31a7b63bb24fcb84d4bfd4cf22cbf Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 28 Feb 2024 12:36:04 -0500 Subject: [PATCH 071/105] update measurement class tests --- .../test_measurements_class.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 7e4a5ff01d..dcb497b7c5 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -664,3 +664,29 @@ def test_sum(self, phi, theta, lightning_sv, tol): expected = np.cos(phi) + np.sin(theta) assert np.allclose(result, expected, tol) + + +@pytest.mark.parametrize( + "op,par,wires,expected", + [ + (qml.QubitStateVector, [0, 1], [1], [1, -1]), + (qml.QubitStateVector, [0, 1], [0], [-1, 1]), + (qml.QubitStateVector, [1.0 / np.sqrt(2), 1.0 / np.sqrt(2)], [1], [1, 0]), + (qml.QubitStateVector, [1j / 2.0, np.sqrt(3) / 2.0], [1], [1, -0.5]), + (qml.QubitStateVector, [(2 - 1j) / 3.0, 2j / 3.0], [0], [1 / 9.0, 1]), + ], +) +def test_state_vector_2_qubit_subset(tol, op, par, wires, expected, lightning_sv): + """Tests qubit state vector preparation and measure on subsets of 2 qubits""" + + tape = qml.tape.QuantumScript( + [op(par, wires=wires)], [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))] + ) + + statevector = lightning_sv(2) + statevector = statevector.get_final_state(tape) + + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + + assert np.allclose(result, expected, tol) From 4b44e4f78a5d0b3ef321d0441c26836b5ddec421 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 28 Feb 2024 13:30:51 -0500 Subject: [PATCH 072/105] update dev version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 072fcd69e0..b720300480 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.35.0-dev20" +__version__ = "0.35.0-dev21" From 62c43f0f9c150ecf036848a27686d5790bfef7c1 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 28 Feb 2024 14:13:22 -0500 Subject: [PATCH 073/105] remove device definition --- .../lightning_qubit/lightning_qubit2.py | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 pennylane_lightning/lightning_qubit/lightning_qubit2.py diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py deleted file mode 100644 index bbdc799400..0000000000 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -This module contains the LightningQubit2 class that inherits from the new device interface. - -""" -import numpy as np - -from pennylane.tape import QuantumScript -from pennylane.typing import Result - -from ._state_vector import LightningStateVector -from ._measurements import LightningMeasurements - - -try: - # pylint: disable=import-error, unused-import - import pennylane_lightning.lightning_qubit_ops - - LQ_CPP_BINARY_AVAILABLE = True -except ImportError: - LQ_CPP_BINARY_AVAILABLE = False - - -def simulate(circuit: QuantumScript, state: LightningStateVector) -> Result: - """Simulate a single quantum script. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector - - Returns: - tuple(TensorLike): The results of the simulation - - Note that this function can return measurements for non-commuting observables simultaneously. - - """ - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningMeasurements(final_state).measure_final_state(circuit) - - -def dummy_jacobian(circuit: QuantumScript): - return np.array(0.0) - - -def simulate_and_jacobian(circuit: QuantumScript): - return np.array(0.0), np.array(0.0) From 2d262888d9da7baee512ed9b015275a63430c3f1 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Wed, 28 Feb 2024 15:04:22 -0500 Subject: [PATCH 074/105] update dev version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index b720300480..60da66632b 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.35.0-dev21" +__version__ = "0.35.0-dev22" From 8a9b9a5bb18f685cf8a5f94e9edc93296b8b6b87 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 29 Feb 2024 10:47:48 -0500 Subject: [PATCH 075/105] clean test_measurements_class.py --- .../test_measurements_class.py | 156 ++++++------------ 1 file changed, 52 insertions(+), 104 deletions(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index dcb497b7c5..a9124901eb 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -12,28 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest -from conftest import LightningDevice # tested device -from pennylane.devices import DefaultQubit - -import numpy as np -import math import itertools +import math +import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice # tested device +from pennylane.devices import DefaultQubit try: - from pennylane_lightning.lightning_qubit_ops import ( - MeasurementsC64, - MeasurementsC128, - ) + from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 except ImportError: pass -from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector -from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements - from pennylane_lightning.lightning_qubit import LightningQubit +from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements +from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if not LightningQubit._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -192,104 +187,15 @@ def test_state_vector_projector_expval(self, lightning_sv, method_name): assert qml.math.allclose(result, np.sin(phi / 2) ** 2) -@pytest.mark.parametrize("method_name", ("expval", "measurement")) -class TestExpval: - """Tests for the expval function""" - - wires = 2 - - @pytest.mark.parametrize( - "obs, expected", - [ - [qml.PauliX(0), -0.041892271271228736], - [qml.PauliX(1), 0.0], - [qml.PauliY(0), -0.5516350865364075], - [qml.PauliY(1), 0.0], - [qml.PauliZ(0), 0.8330328980789793], - [qml.PauliZ(1), 1.0], - ], - ) - def test_expval_qml_tape_wire0(self, obs, expected, tol, lightning_sv, method_name): - """Test expval with a circuit on wires=[0]""" - - x, y, z = [0.5, 0.3, -0.7] - statevector = lightning_sv(self.wires) - statevector.apply_operations( - [qml.RX(0.4, wires=[0]), qml.Rot(x, y, z, wires=[0]), qml.RY(-0.2, wires=[0])] - ) - - m = LightningMeasurements(statevector) - result = getattr(m, method_name)(qml.expval(obs)) - - assert np.allclose(result, expected, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "obs, expected", - [ - [qml.PauliX(0), 0.0], - [qml.PauliX(1), -0.19866933079506122], - [qml.PauliY(0), -0.3894183423086505], - [qml.PauliY(1), 0.0], - [qml.PauliZ(0), 0.9210609940028852], - [qml.PauliZ(1), 0.9800665778412417], - ], - ) - def test_expval_wire01(self, obs, expected, tol, lightning_sv, method_name): - """Test expval with a circuit on wires=[0,1]""" - - statevector = lightning_sv(self.wires) - statevector.apply_operations([qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])]) - - m = LightningMeasurements(statevector) - result = getattr(m, method_name)(qml.expval(obs)) - - assert np.allclose(result, expected, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "obs, coeffs, expected", - [ - ([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, expected, tol, lightning_sv, method_name): - """Test expval with Hamiltonian""" - ham = qml.Hamiltonian(coeffs, obs) - - statevector = lightning_sv(self.wires) - statevector.apply_operations([qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])]) - - m = LightningMeasurements(statevector) - result = getattr(m, method_name)(qml.expval(ham)) - - assert np.allclose(result, expected, atol=tol, rtol=0) - - THETA = np.linspace(0.11, 1, 3) PHI = np.linspace(0.32, 1, 3) @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) class TestExpval: - """Test expectation value calculations""" + """Test expectation value calculations.""" - def test_Identity(self, theta, phi, tol, lightning_sv): + def test_identity(self, theta, phi, tol, lightning_sv): """Tests applying identities.""" wires = 3 @@ -391,6 +297,48 @@ def test_single_wire_observables_expectation( assert np.allclose(result, expected, tol) +@pytest.mark.parametrize("method_name", ("expval", "measurement")) +class TestExpvalHamiltonian: + """Tests expval for Hamiltonians""" + + wires = 2 + + @pytest.mark.parametrize( + "obs, coeffs, expected", + [ + ([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, expected, tol, lightning_sv, method_name): + """Test expval with Hamiltonian""" + ham = qml.Hamiltonian(coeffs, obs) + + statevector = lightning_sv(self.wires) + statevector.apply_operations([qml.RX(0.4, wires=[0]), qml.RY(-0.2, wires=[1])]) + + m = LightningMeasurements(statevector) + result = getattr(m, method_name)(qml.expval(ham)) + + assert np.allclose(result, expected, atol=tol, rtol=0) + + class TestSparseExpval: """Tests for the expval function""" From 36d810e5e9fd12d3ec1f3d1f0c540d4cdec81ad0 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 29 Feb 2024 10:49:32 -0500 Subject: [PATCH 076/105] isort+black --- .../lightning_qubit/_measurements.py | 12 +++++------- .../lightning_qubit/_state_vector.py | 14 +++++--------- tests/lightning_qubit/test_state_vector_class.py | 10 +++++----- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 520bd402a8..14ea6d26ca 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -17,22 +17,20 @@ # pylint: disable=import-error, no-name-in-module, ungrouped-imports try: - from pennylane_lightning.lightning_qubit_ops import ( - MeasurementsC64, - MeasurementsC128, - ) + from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 except ImportError: pass from typing import Callable, List -import numpy as np -from pennylane.measurements import StateMeasurement, MeasurementProcess, ExpectationMP -from pennylane.typing import TensorLike, Result +import numpy as np +from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement from pennylane.tape import QuantumScript +from pennylane.typing import Result, TensorLike from pennylane.wires import Wires from pennylane_lightning.core._serialize import QuantumScriptSerializer + from ._state_vector import LightningStateVector diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 3e041f34e1..d913379065 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -17,26 +17,22 @@ try: from pennylane_lightning.lightning_qubit_ops import ( - allocate_aligned_array, - get_alignment, - best_alignment, StateVectorC64, StateVectorC128, + allocate_aligned_array, + best_alignment, + get_alignment, ) except ImportError: pass from itertools import product -import numpy as np +import numpy as np import pennylane as qml +from pennylane import BasisState, DeviceError, StatePrep from pennylane.tape import QuantumScript from pennylane.wires import Wires -from pennylane import ( - BasisState, - StatePrep, - DeviceError, -) class LightningStateVector: diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index b1c4e3550d..f30f49dbe6 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -15,17 +15,17 @@ Unit tests for the serialization helper functions. """ -import pytest -from conftest import LightningDevice # tested device - import math + import numpy as np import pennylane as qml -from pennylane.wires import Wires +import pytest +from conftest import LightningDevice # tested device from pennylane.tape import QuantumScript +from pennylane.wires import Wires -from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector from pennylane_lightning.lightning_qubit import LightningQubit +from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if not LightningQubit._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) From 816955f8c1776d130c82bf910f40125424aec9d0 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 29 Feb 2024 11:03:33 -0500 Subject: [PATCH 077/105] review suggestion --- pennylane_lightning/lightning_qubit/_measurements.py | 3 +-- tests/lightning_qubit/test_state_vector_class.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 14ea6d26ca..bc79618363 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -87,8 +87,7 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) state_array = self._qubit_state.state - total_wires = int(np.log2(state_array.size)) - wires = Wires(range(total_wires)) + wires = Wires(range(self._qubit_state.num_wires)) return measurementprocess.process_state(state_array, wires) # pylint: disable=protected-access diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index f30f49dbe6..11a295e7c3 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -34,7 +34,7 @@ pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) -@pytest.mark.parametrize("num_wires", [3, 10]) +@pytest.mark.parametrize("num_wires", range(4)) @pytest.mark.parametrize("dtype", [np.complex64, np.complex128]) @pytest.mark.parametrize("device_name", ["lightning.qubit"]) def test_device_name_and_init(num_wires, dtype, device_name): From 940644c1ad948034b8ea99abd5240345e43d07f6 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 29 Feb 2024 15:15:57 -0500 Subject: [PATCH 078/105] fix docs --- pennylane_lightning/lightning_qubit/_measurements.py | 7 ++----- pennylane_lightning/lightning_qubit/_state_vector.py | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index bc79618363..8b14f8b674 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -65,12 +65,9 @@ def dtype(self): return self._dtype def _measurement_dtype(self): - """Binding to Lightning Managed state vector. + """Binding to Lightning Measurements C++ class. - Args: - dtype (complex): Data complex type - - Returns: the state vector class + Returns: the Measurements class """ return MeasurementsC64 if self.dtype == np.complex64 else MeasurementsC128 diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index d913379065..f33054c7da 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -82,10 +82,7 @@ def num_wires(self): return self._num_wires def _state_dtype(self): - """Binding to Lightning Managed state vector. - - Args: - dtype (complex): Data complex type + """Binding to Lightning Managed state vector C++ class. Returns: the state vector class """ From 3922c326115a50d0765d15c233c09c79419fa476 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Mon, 4 Mar 2024 19:48:31 +0000 Subject: [PATCH 079/105] Add qml.var support. --- .../lightning_qubit/_measurements.py | 46 ++++++++++- .../test_measurements_class.py | 80 ++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 8b14f8b674..dc3c5cdab3 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -24,7 +24,7 @@ from typing import Callable, List import numpy as np -from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement +from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement, VarianceMP from pennylane.tape import QuantumScript from pennylane.typing import Result, TensorLike from pennylane.wires import Wires @@ -123,6 +123,42 @@ def expval(self, measurementprocess: MeasurementProcess): measurementprocess.obs.name, measurementprocess.obs.wires ) + # pylint: disable=protected-access + def var(self, measurementprocess: MeasurementProcess): + """Variance of the supplied observable contained in the MeasurementProcess. + + Args: + measurementprocess (StateMeasurement): measurement to apply to the state + + Returns: + Variance of the observable + """ + + if measurementprocess.obs.name == "SparseHamiltonian": + # ensuring CSR sparse representation. + CSR_SparseHamiltonian = measurementprocess.obs.sparse_matrix( + wire_order=list(range(self._qubit_state.num_wires)) + ).tocsr(copy=False) + return self._measurement_lightning.var( + CSR_SparseHamiltonian.indptr, + CSR_SparseHamiltonian.indices, + CSR_SparseHamiltonian.data, + ) + + if ( + measurementprocess.obs.name in ["Hamiltonian", "Hermitian"] + or (measurementprocess.obs.arithmetic_depth > 0) + or isinstance(measurementprocess.obs.name, List) + ): + ob_serialized = QuantumScriptSerializer( + self._qubit_state.device_name, self.dtype == np.complex64 + )._ob(measurementprocess.obs) + return self._measurement_lightning.var(ob_serialized) + + return self._measurement_lightning.var( + measurementprocess.obs.name, measurementprocess.obs.wires + ) + def get_measurement_function( self, measurementprocess: MeasurementProcess ) -> Callable[[MeasurementProcess, TensorLike], TensorLike]: @@ -143,6 +179,14 @@ def get_measurement_function( return self.state_diagonalizing_gates return self.expval + if isinstance(measurementprocess, VarianceMP): + if measurementprocess.obs.name in [ + "Identity", + "Projector", + ]: + return self.state_diagonalizing_gates + return self.var + if measurementprocess.obs is None or measurementprocess.obs.has_diagonalizing_gates: return self.state_diagonalizing_gates diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index a9124901eb..b8dd6a68a1 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -20,6 +20,8 @@ import pytest from conftest import LightningDevice # tested device from pennylane.devices import DefaultQubit +from scipy.sparse import csr_matrix, random_array +from pennylane.measurements import VarianceMP try: from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 @@ -49,6 +51,17 @@ def _statevector(num_wires): return _statevector +def get_hermitian_matrix(n): + H = np.random.rand(n, n) + 1.0j * np.random.rand(n, n) + return H + np.conj(H).T + + +def get_sparse_hermitian_matrix(n): + H = random_array((n, n), density=0.15) + H = H + 1.0j * random_array((n, n), density=0.15) + return csr_matrix(H + H.conj().T) + + class CustomStateMeasurement(qml.measurements.StateMeasurement): def process_state(self, state, wire_order): return 1 @@ -81,7 +94,6 @@ def test_only_support_state_measurements(self, lightning_sv): "mp", ( qml.probs(wires=0), - qml.var(qml.Z(0)), qml.vn_entropy(wires=0), CustomStateMeasurement(), qml.expval(qml.Identity(0)), @@ -376,6 +388,72 @@ def test_sparse_Pauli_words(self, ham_terms, expected, tol, lightning_sv): assert np.allclose(result, expected, tol) +class TestMeasurements: + """Tests all measurements""" + + @staticmethod + def calculate_reference(tape, lightning_sv): + use_default = True + new_meas = [] + for m in tape.measurements: + # not supported by DefaultQubit + if isinstance(m, VarianceMP) and isinstance( + m.obs, (qml.Hamiltonian, qml.SparseHamiltonian) + ): + use_default = False + new_meas.append(m.__class__(qml.Hermitian(qml.matrix(m.obs), wires=m.obs.wires))) + continue + new_meas.append(m) + if use_default: + dev = DefaultQubit(max_workers=1) + program, _ = dev.preprocess() + tapes, transf_fn = program([tape]) + results = dev.execute(tapes) + return transf_fn(results) + + tape = qml.tape.QuantumScript(tape.operations, new_meas) + statevector = lightning_sv(tape.num_wires) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + return m.measure_final_state(tape) + + @pytest.mark.parametrize("measurement", [qml.expval, qml.var]) + @pytest.mark.parametrize( + "observable", + ( + qml.PauliX(0), + qml.PauliY(1), + qml.PauliZ(2), + qml.sum(qml.PauliX(0), qml.PauliY(0)), + qml.prod(qml.PauliX(0), qml.PauliY(1)), + qml.s_prod(2.0, qml.PauliX(0)), + qml.Hermitian(get_hermitian_matrix(2**2), wires=[0, 1]), + qml.Hermitian(get_hermitian_matrix(2**2), wires=[2, 3]), + qml.Hamiltonian( + [1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2) @ qml.PauliZ(3)] + ), + qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), + ), + ) + def test_single_return(self, measurement, observable, lightning_sv, tol): + n_qubits = 4 + n_layers = 1 + np.random.seed(0) + weights = np.random.rand(n_layers, n_qubits, 3) + ops = [qml.Hadamard(i) for i in range(n_qubits)] + ops += [qml.StronglyEntanglingLayers(weights, wires=range(n_qubits))] + measurements = [measurement(observable)] + tape = qml.tape.QuantumScript(ops, measurements) + + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + expected = self.calculate_reference(tape, lightning_sv) + + assert np.allclose(result, expected, tol) + + class TestControlledOps: """Tests for controlled operations""" From 5b31b076bc6708cc41db887cf72a3b5d451a8eca Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 01:25:02 +0000 Subject: [PATCH 080/105] Add probs support. --- .../lightning_qubit/_measurements.py | 38 ++++++- .../test_measurements_class.py | 102 +++++++++++++++--- 2 files changed, 124 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index dc3c5cdab3..43d700aa0c 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -24,7 +24,13 @@ from typing import Callable, List import numpy as np -from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement, VarianceMP +from pennylane.measurements import ( + ExpectationMP, + ProbabilityMP, + MeasurementProcess, + StateMeasurement, + VarianceMP, +) from pennylane.tape import QuantumScript from pennylane.typing import Result, TensorLike from pennylane.wires import Wires @@ -82,7 +88,6 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten TensorLike: the result of the measurement """ self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) - state_array = self._qubit_state.state wires = Wires(range(self._qubit_state.num_wires)) return measurementprocess.process_state(state_array, wires) @@ -123,6 +128,32 @@ def expval(self, measurementprocess: MeasurementProcess): measurementprocess.obs.name, measurementprocess.obs.wires ) + # pylint: disable=protected-access + def probs(self, measurementprocess: MeasurementProcess): + """Probabilities of the supplied observable or wires contained in the MeasurementProcess. + + Args: + measurementprocess (StateMeasurement): measurement to apply to the state + + Returns: + Probabilities of the supplied observable or wires + """ + diagonalizing_gates = measurementprocess.diagonalizing_gates() + if diagonalizing_gates: + qubit_state = LightningStateVector( + num_wires=self._qubit_state.num_wires, + dtype=self._qubit_state.dtype, + device_name=self._qubit_state.device_name, + ) + qubit_state._apply_state_vector(self._state, range(self._qubit_state.num_wires)) + self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) + results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) + if diagonalizing_gates: + self._qubit_state._apply_state_vector( + qubit_state.state_vector, range(self._qubit_state.num_wires) + ) + return results + # pylint: disable=protected-access def var(self, measurementprocess: MeasurementProcess): """Variance of the supplied observable contained in the MeasurementProcess. @@ -179,6 +210,9 @@ def get_measurement_function( return self.state_diagonalizing_gates return self.expval + if isinstance(measurementprocess, ProbabilityMP): + return self.probs + if isinstance(measurementprocess, VarianceMP): if measurementprocess.obs.name in [ "Identity", diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index b8dd6a68a1..bdb5d218cc 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -15,13 +15,14 @@ import itertools import math +from typing import Sequence import numpy as np import pennylane as qml import pytest from conftest import LightningDevice # tested device from pennylane.devices import DefaultQubit from scipy.sparse import csr_matrix, random_array -from pennylane.measurements import VarianceMP +from pennylane.measurements import ProbabilityMP, VarianceMP try: from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 @@ -110,15 +111,15 @@ def test_state_diagonalizing_gates_measurements(self, lightning_sv, mp): @pytest.mark.parametrize( "obs", ( - qml.X(0), - qml.Y(0), - qml.Z(0), - qml.sum(qml.X(0), qml.Y(0)), - qml.prod(qml.X(0), qml.Y(1)), - qml.s_prod(2.0, qml.X(0)), - qml.Hamiltonian([1.0, 2.0], [qml.X(0), qml.Y(0)]), + qml.PauliX(0), + qml.PauliY(0), + qml.PauliZ(0), + qml.sum(qml.PauliX(0), qml.PauliY(0)), + qml.prod(qml.PauliX(0), qml.PauliY(1)), + qml.s_prod(2.0, qml.PauliX(0)), + qml.Hamiltonian([1.0, 2.0], [qml.PauliX(0), qml.PauliY(0)]), qml.Hermitian(np.eye(2), wires=0), - qml.SparseHamiltonian(qml.X.compute_sparse_matrix(), wires=0), + qml.SparseHamiltonian(qml.PauliX.compute_sparse_matrix(), wires=0), ), ) def test_expval_selected(self, lightning_sv, obs): @@ -168,7 +169,7 @@ def test_custom_measurement(self, lightning_sv, method_name): def test_measurement_with_diagonalizing_gates(self, lightning_sv, method_name): statevector = lightning_sv(num_wires=5) m = LightningMeasurements(statevector) - measurement = qml.probs(op=qml.X(0)) + measurement = qml.probs(op=qml.PauliX(0)) result = getattr(m, method_name)(measurement) assert qml.math.allclose(result, [0.5, 0.5]) @@ -417,7 +418,7 @@ def calculate_reference(tape, lightning_sv): m = LightningMeasurements(statevector) return m.measure_final_state(tape) - @pytest.mark.parametrize("measurement", [qml.expval, qml.var]) + @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", ( @@ -435,24 +436,97 @@ def calculate_reference(tape, lightning_sv): qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), ), ) - def test_single_return(self, measurement, observable, lightning_sv, tol): + def test_single_return_value(self, measurement, observable, lightning_sv, tol): + if measurement is qml.probs and isinstance( + observable, + (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), + ): + return n_qubits = 4 n_layers = 1 np.random.seed(0) weights = np.random.rand(n_layers, n_qubits, 3) ops = [qml.Hadamard(i) for i in range(n_qubits)] ops += [qml.StronglyEntanglingLayers(weights, wires=range(n_qubits))] - measurements = [measurement(observable)] + measurements = [measurement(op=observable)] tape = qml.tape.QuantumScript(ops, measurements) + expected = self.calculate_reference(tape, lightning_sv) statevector = lightning_sv(n_qubits) statevector = statevector.get_final_state(tape) m = LightningMeasurements(statevector) result = m.measure_final_state(tape) - expected = self.calculate_reference(tape, lightning_sv) assert np.allclose(result, expected, tol) + @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) + @pytest.mark.parametrize( + "obs0_", + ( + qml.PauliX(0), + qml.PauliY(1), + qml.PauliZ(2), + qml.sum(qml.PauliX(0), qml.PauliY(0)), + qml.prod(qml.PauliX(0), qml.PauliY(1)), + qml.s_prod(2.0, qml.PauliX(0)), + qml.Hermitian(get_hermitian_matrix(2), wires=[0]), + qml.Hermitian(get_hermitian_matrix(2**2), wires=[2, 3]), + qml.Hamiltonian( + [1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2) @ qml.PauliZ(3)] + ), + qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), + ), + ) + @pytest.mark.parametrize( + "obs1_", + ( + qml.PauliX(0), + qml.PauliY(1), + qml.PauliZ(2), + qml.sum(qml.PauliX(0), qml.PauliY(0)), + qml.prod(qml.PauliX(0), qml.PauliY(1)), + qml.s_prod(2.0, qml.PauliX(0)), + qml.Hermitian(get_hermitian_matrix(2), wires=[0]), + qml.Hermitian(get_hermitian_matrix(2**2), wires=[2, 3]), + # qml.Hamiltonian( + # [1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2) @ qml.PauliZ(3)] + # ), + # qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), + ), + ) + def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol): + if measurement is qml.probs and isinstance( + obs0_, + (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), + ): + return + if measurement is qml.probs and isinstance( + obs1_, + (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), + ): + return + n_qubits = 4 + n_layers = 1 + np.random.seed(0) + weights = np.random.rand(n_layers, n_qubits, 3) + ops = [qml.Hadamard(i) for i in range(n_qubits)] + ops += [qml.StronglyEntanglingLayers(weights, wires=range(n_qubits))] + measurements = [measurement(op=obs0_), measurement(op=obs1_)] + tape = qml.tape.QuantumScript(ops, measurements) + + expected = self.calculate_reference(tape, lightning_sv) + if len(expected) == 1: + expected = expected[0] + statevector = lightning_sv(n_qubits) + statevector = statevector.get_final_state(tape) + m = LightningMeasurements(statevector) + result = m.measure_final_state(tape) + + assert isinstance(result, Sequence) + assert len(result) == len(expected) + for r, e in zip(result, expected): + assert np.allclose(r, e, rtol=1.0e-5, atol=0.0) + class TestControlledOps: """Tests for controlled operations""" From b54d3eaee5ab6902b3ee35f570d3db38cb91829b Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 08:33:08 -0500 Subject: [PATCH 081/105] increase tolerance --- tests/lightning_qubit/test_measurements_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index a9124901eb..8ad4cb1caa 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -468,7 +468,7 @@ def test_controlled_qubit_gates(self, operation, n_qubits, control_value, tol, l result = m.measure_final_state(tape) expected = self.calculate_reference(tape) - assert np.allclose(result, expected, tol) + assert np.allclose(result, expected, tol * 10) def test_controlled_qubit_unitary_from_op(self, tol, lightning_sv): n_qubits = 10 From 3703b94692f46c1aa30049eb8b149310944972aa Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Tue, 5 Mar 2024 14:59:58 +0000 Subject: [PATCH 082/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 512bfe6499..5ef6d062a5 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-dev1" +__version__ = "0.36.0-dev2" From 8519c1c798082bffbd8b2e0da88c717df7d70e16 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 10:05:45 -0500 Subject: [PATCH 083/105] isort --- pennylane_lightning/lightning_qubit/_measurements.py | 5 ++++- tests/lightning_qubit/test_measurements_class.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 8b14f8b674..79c40d681e 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -17,7 +17,10 @@ # pylint: disable=import-error, no-name-in-module, ungrouped-imports try: - from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) except ImportError: pass diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 8ad4cb1caa..ff951385f9 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -22,7 +22,10 @@ from pennylane.devices import DefaultQubit try: - from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) except ImportError: pass From 60aef32a58ede31cf03c390de42c8f4b0a27a98c Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 15:24:11 +0000 Subject: [PATCH 084/105] Add double-obs tests. --- .../lightning_qubit/_measurements.py | 13 ++++--------- tests/lightning_qubit/test_measurements_class.py | 12 ++++++------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 43d700aa0c..e5102a7e40 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -24,6 +24,7 @@ from typing import Callable, List import numpy as np +import pennylane as qml from pennylane.measurements import ( ExpectationMP, ProbabilityMP, @@ -140,17 +141,11 @@ def probs(self, measurementprocess: MeasurementProcess): """ diagonalizing_gates = measurementprocess.diagonalizing_gates() if diagonalizing_gates: - qubit_state = LightningStateVector( - num_wires=self._qubit_state.num_wires, - dtype=self._qubit_state.dtype, - device_name=self._qubit_state.device_name, - ) - qubit_state._apply_state_vector(self._state, range(self._qubit_state.num_wires)) - self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) + self._qubit_state.apply_operations(diagonalizing_gates) results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) if diagonalizing_gates: - self._qubit_state._apply_state_vector( - qubit_state.state_vector, range(self._qubit_state.num_wires) + self._qubit_state.apply_operations( + [qml.adjoint(g) for g in reversed(diagonalizing_gates)] ) return results diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index bdb5d218cc..e9ac005350 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -94,7 +94,6 @@ def test_only_support_state_measurements(self, lightning_sv): @pytest.mark.parametrize( "mp", ( - qml.probs(wires=0), qml.vn_entropy(wires=0), CustomStateMeasurement(), qml.expval(qml.Identity(0)), @@ -488,10 +487,10 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): qml.s_prod(2.0, qml.PauliX(0)), qml.Hermitian(get_hermitian_matrix(2), wires=[0]), qml.Hermitian(get_hermitian_matrix(2**2), wires=[2, 3]), - # qml.Hamiltonian( - # [1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2) @ qml.PauliZ(3)] - # ), - # qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), + qml.Hamiltonian( + [1.0, 2.0, 3.0], [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2) @ qml.PauliZ(3)] + ), + qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), ), ) def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol): @@ -524,8 +523,9 @@ def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol) assert isinstance(result, Sequence) assert len(result) == len(expected) + # a few tests fail in single precision, and hence we increase the tolerance for r, e in zip(result, expected): - assert np.allclose(r, e, rtol=1.0e-5, atol=0.0) + assert np.allclose(r, e, max(tol, 1.0e-5)) class TestControlledOps: From 6308ba2057a01241505b29d4b85e2f83fead5e33 Mon Sep 17 00:00:00 2001 From: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:39:20 -0500 Subject: [PATCH 085/105] Pin pytest version (#624) * update dev version * update changelog * pin pytest version in requirement files * add a requirements file for tests against Pennylane master * update wheels' workflows --- .github/CHANGELOG.md | 3 +++ .github/workflows/wheel_linux_aarch64.yml | 4 +--- .github/workflows/wheel_linux_x86_64.yml | 5 +---- .github/workflows/wheel_macos_arm64.yml | 5 +---- .github/workflows/wheel_macos_x86_64.yml | 7 ++----- .github/workflows/wheel_win_x86_64.yml | 5 +---- requirements-dev.txt | 2 +- requirements-tests.txt | 6 ++++++ requirements.txt | 2 +- 9 files changed, 17 insertions(+), 22 deletions(-) create mode 100644 requirements-tests.txt diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 289b5f73c2..4519b40f6d 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -84,6 +84,9 @@ * Fix apply state vector when using a Lightning handle. [(#622)](https://github.com/PennyLaneAI/pennylane-lightning/pull/622) +* Pinning Pytest to a version compatible with Flaky. + [(#624)](https://github.com/PennyLaneAI/pennylane-lightning/pull/624) + ### Contributors This release contains contributions from (in alphabetical order): diff --git a/.github/workflows/wheel_linux_aarch64.yml b/.github/workflows/wheel_linux_aarch64.yml index 8624ad129c..1a10b46a30 100644 --- a/.github/workflows/wheel_linux_aarch64.yml +++ b/.github/workflows/wheel_linux_aarch64.yml @@ -139,10 +139,8 @@ jobs: CIBW_BUILD_VERBOSITY: 3 - CIBW_TEST_REQUIRES: pytest pytest-cov pytest-mock flaky - CIBW_BEFORE_TEST: | - python -m pip install pytest-benchmark git+https://github.com/PennyLaneAI/pennylane.git@master + python -m pip install -r requirements-tests.txt if ${{ matrix.pl_backend == 'lightning_kokkos'}}; then SKIP_COMPILATION=True PL_BACKEND="lightning_qubit" pip install -e . -vv; fi CIBW_TEST_COMMAND: | diff --git a/.github/workflows/wheel_linux_x86_64.yml b/.github/workflows/wheel_linux_x86_64.yml index 9ce3c2d451..35e48a29f7 100644 --- a/.github/workflows/wheel_linux_x86_64.yml +++ b/.github/workflows/wheel_linux_x86_64.yml @@ -149,11 +149,8 @@ jobs: PATH="/opt/rh/devtoolset-11/root/usr/bin:$PATH" \ PL_BACKEND="${{ matrix.pl_backend }}" - # Testing of built wheels - CIBW_TEST_REQUIRES: pytest pytest-cov pytest-mock flaky - CIBW_BEFORE_TEST: | - python -m pip install pytest-benchmark git+https://github.com/PennyLaneAI/pennylane.git@master + python -m pip install -r requirements-tests.txt if ${{ matrix.pl_backend == 'lightning_kokkos'}}; then SKIP_COMPILATION=True PL_BACKEND="lightning_qubit" pip install -e . -vv; fi CIBW_TEST_COMMAND: | diff --git a/.github/workflows/wheel_macos_arm64.yml b/.github/workflows/wheel_macos_arm64.yml index b9dfa43d6b..a381e5305a 100644 --- a/.github/workflows/wheel_macos_arm64.yml +++ b/.github/workflows/wheel_macos_arm64.yml @@ -89,11 +89,8 @@ jobs: CMAKE_ARGS="-DCMAKE_CXX_COMPILER_TARGET=arm64-apple-macos11 -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DENABLE_OPENMP=OFF" \ PL_BACKEND="${{ matrix.pl_backend }}" - # Testing of built wheels - CIBW_TEST_REQUIRES: pytest pytest-cov pytest-mock flaky - CIBW_BEFORE_TEST: | - python -m pip install pytest-benchmark git+https://github.com/PennyLaneAI/pennylane.git@master + python -m pip install -r requirements-tests.txt if ${{ matrix.pl_backend == 'lightning_kokkos'}}; then SKIP_COMPILATION=True PL_BACKEND="lightning_qubit" pip install -e . -vv; fi CIBW_TEST_COMMAND: | diff --git a/.github/workflows/wheel_macos_x86_64.yml b/.github/workflows/wheel_macos_x86_64.yml index a7c68734c8..e727468fe4 100644 --- a/.github/workflows/wheel_macos_x86_64.yml +++ b/.github/workflows/wheel_macos_x86_64.yml @@ -21,7 +21,7 @@ concurrency: cancel-in-progress: true jobs: - set_wheel_build_matrix: + set_wheel_build_matrix: name: "Set wheel build matrix" uses: ./.github/workflows/set_wheel_build_matrix.yml with: @@ -137,11 +137,8 @@ jobs: PL_BACKEND: ${{ matrix.pl_backend }} - # Testing of built wheels - CIBW_TEST_REQUIRES: pytest pytest-cov pytest-mock flaky - CIBW_BEFORE_TEST: | - python -m pip install pytest-benchmark git+https://github.com/PennyLaneAI/pennylane.git@master + python -m pip install -r requirements-tests.txt if ${{ matrix.pl_backend == 'lightning_kokkos'}}; then SKIP_COMPILATION=True PL_BACKEND="lightning_qubit" pip install -e . -vv; fi CIBW_TEST_COMMAND: | diff --git a/.github/workflows/wheel_win_x86_64.yml b/.github/workflows/wheel_win_x86_64.yml index 4dc8ff4e48..7ba0704c0b 100644 --- a/.github/workflows/wheel_win_x86_64.yml +++ b/.github/workflows/wheel_win_x86_64.yml @@ -126,11 +126,8 @@ jobs: CIBW_BEFORE_BUILD: | python -m pip install pybind11 cmake~=3.24.0 build - # Testing of built wheels - CIBW_TEST_REQUIRES: pytest pytest-cov pytest-mock flaky - CIBW_BEFORE_TEST: | - python -m pip install pytest-benchmark git+https://github.com/PennyLaneAI/pennylane.git@master + python -m pip install -r requirements-tests.txt CIBW_TEST_COMMAND: | pl-device-test --device=lightning.qubit --skip-ops -x --tb=short --no-flaky-report diff --git a/requirements-dev.txt b/requirements-dev.txt index 94456d4e0f..7a0230999c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ git+https://github.com/PennyLaneAI/pennylane.git@master ninja flaky pybind11 -pytest +pytest~=8.0.0 pytest-benchmark pytest-cov pytest-mock diff --git a/requirements-tests.txt b/requirements-tests.txt new file mode 100644 index 0000000000..41c3a9f539 --- /dev/null +++ b/requirements-tests.txt @@ -0,0 +1,6 @@ +pytest~=8.0.0 +pytest-cov +pytest-mock +flaky +pytest-benchmark +git+https://github.com/PennyLaneAI/pennylane.git@master \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 607a6c007f..c888135fa8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,6 @@ ninja flaky pennylane>=0.34 pybind11 -pytest +pytest~=8.0.0 pytest-cov pytest-mock \ No newline at end of file From 3a08c6236e3c9a17d35280059a51f950516a357a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:34:24 -0500 Subject: [PATCH 086/105] Version Bump (#626) * post release version bump * trigger CI --------- Co-authored-by: AmintorDusko Co-authored-by: AmintorDusko --- .github/CHANGELOG.md | 20 +++++++++++++++++++- pennylane_lightning/core/_version.py | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 4519b40f6d..8cf14f3bf0 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1,4 +1,22 @@ -# Release 0.35.0-dev +# Release 0.36.0-dev + +### New features since last release + +### Breaking changes + +### Improvements + +### Documentation + +### Bug fixes + +### Contributors + +This release contains contributions from (in alphabetical order): + +--- + +# Release 0.35.0 ### New features since last release diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 60da66632b..89f11c0a16 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.35.0-dev22" +__version__ = "0.36.0-dev" From daab5ad1bc367f8d1481725fd9818c14af4b6236 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 08:33:08 -0500 Subject: [PATCH 087/105] increase tolerance --- tests/lightning_qubit/test_measurements_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index e9ac005350..a22c129e32 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -620,7 +620,7 @@ def test_controlled_qubit_gates(self, operation, n_qubits, control_value, tol, l result = m.measure_final_state(tape) expected = self.calculate_reference(tape) - assert np.allclose(result, expected, tol) + assert np.allclose(result, expected, tol * 10) def test_controlled_qubit_unitary_from_op(self, tol, lightning_sv): n_qubits = 10 From 078c8d8f3f80f34a1bcf78b43205ca8c7c2e0fc2 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 09:24:41 -0500 Subject: [PATCH 088/105] Introduce isort. (#623) * Introduce isort. * Auto update version * Update changelog * Auto update version * Update changelog. * trigger ci --------- Co-authored-by: Dev version update bot --- .github/CHANGELOG.md | 5 ++ Makefile | 1 + mpitests/conftest.py | 4 +- mpitests/test_adjoint_jacobian.py | 12 ++-- mpitests/test_apply.py | 8 +-- mpitests/test_device.py | 6 +- mpitests/test_expval.py | 5 +- mpitests/test_measurements_sparse.py | 8 +-- mpitests/test_probs.py | 13 ++--- pennylane_lightning/core/_serialize.py | 15 ++--- pennylane_lightning/core/_version.py | 2 +- pennylane_lightning/core/lightning_base.py | 14 ++--- pennylane_lightning/lightning_gpu/__init__.py | 1 + .../lightning_gpu/lightning_gpu.py | 56 ++++++++++--------- .../lightning_kokkos/__init__.py | 1 + .../lightning_kokkos/lightning_kokkos.py | 35 ++++++------ .../lightning_qubit/__init__.py | 1 + .../lightning_qubit/lightning_qubit.py | 32 +++++------ requirements-dev.txt | 1 + tests/conftest.py | 4 +- .../test_measurements_samples_MCMC.py | 6 +- tests/test_adjoint_jacobian.py | 14 ++--- tests/test_apply.py | 10 +++- tests/test_arrays.py | 3 +- tests/test_binary_info.py | 7 ++- tests/test_comparison.py | 9 +-- tests/test_decomposition.py | 5 +- tests/test_device.py | 6 +- tests/test_execute.py | 4 +- tests/test_expval.py | 4 +- tests/test_gates.py | 7 +-- tests/test_measurements.py | 13 ++--- tests/test_measurements_sparse.py | 6 +- tests/test_serialize.py | 47 ++++++++-------- tests/test_serialize_chunk_obs.py | 8 +-- tests/test_serialize_no_binaries.py | 2 +- tests/test_var.py | 5 +- tests/test_vjp.py | 7 ++- 38 files changed, 196 insertions(+), 191 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 8cf14f3bf0..a807815c9b 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -6,6 +6,9 @@ ### Improvements +* Add `isort` to `requirements-dev.txt` and run before `black` upon `make format` to sort Python imports. + [(#623)](https://github.com/PennyLaneAI/pennylane-lightning/pull/623) + ### Documentation ### Bug fixes @@ -14,6 +17,8 @@ This release contains contributions from (in alphabetical order): +Vincent Michaud-Rioux + --- # Release 0.35.0 diff --git a/Makefile b/Makefile index 8d42ba7a8f..eb9f9557e2 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,7 @@ format-cpp: ./bin/format $(CHECK) ./pennylane_lightning format-python: + isort --profile black ./pennylane_lightning/ ./mpitests ./tests $(CHECK) black -l 100 ./pennylane_lightning/ ./mpitests ./tests $(CHECK) .PHONY: check-tidy diff --git a/mpitests/conftest.py b/mpitests/conftest.py index 09ab802b05..a2084f2a5d 100644 --- a/mpitests/conftest.py +++ b/mpitests/conftest.py @@ -18,10 +18,10 @@ import itertools import os -import pytest -from pennylane import numpy as np import pennylane as qml +import pytest +from pennylane import numpy as np # Tuple passed to distributed device ctor # np.complex for data type and True or False diff --git a/mpitests/test_adjoint_jacobian.py b/mpitests/test_adjoint_jacobian.py index 8d6cc8e219..5272f72062 100644 --- a/mpitests/test_adjoint_jacobian.py +++ b/mpitests/test_adjoint_jacobian.py @@ -17,14 +17,16 @@ # pylint: disable=protected-access,cell-var-from-loop,c-extension-no-member import itertools import math -from mpi4py import MPI -import pytest -from conftest import device_name, LightningDevice as ld -from scipy.stats import unitary_group import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name +from mpi4py import MPI +from pennylane import QNode from pennylane import numpy as np -from pennylane import QNode, qnode +from pennylane import qnode +from scipy.stats import unitary_group I, X, Y, Z = ( np.eye(2), diff --git a/mpitests/test_apply.py b/mpitests/test_apply.py index ad9e474fb4..87152ee6fb 100644 --- a/mpitests/test_apply.py +++ b/mpitests/test_apply.py @@ -16,14 +16,12 @@ """ # pylint: disable=protected-access,cell-var-from-loop,c-extension-no-member import itertools -from mpi4py import MPI -import pytest - -from conftest import TOL_STOCHASTIC, device_name, fixture_params import numpy as np import pennylane as qml - +import pytest +from conftest import TOL_STOCHASTIC, device_name, fixture_params +from mpi4py import MPI numQubits = 8 diff --git a/mpitests/test_device.py b/mpitests/test_device.py index d9761bf148..03a1880114 100644 --- a/mpitests/test_device.py +++ b/mpitests/test_device.py @@ -16,10 +16,10 @@ """ # pylint: disable=protected-access,unused-variable,missing-function-docstring,c-extension-no-member -import pytest -from conftest import device_name, LightningDevice as ld - import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name from mpi4py import MPI if not ld._CPP_BINARY_AVAILABLE: diff --git a/mpitests/test_expval.py b/mpitests/test_expval.py index 9db1e76b31..d020471c03 100644 --- a/mpitests/test_expval.py +++ b/mpitests/test_expval.py @@ -16,11 +16,10 @@ """ # pylint: disable=protected-access,too-few-public-methods,unused-import,missing-function-docstring,too-many-arguments,c-extension-no-member -import pytest -from conftest import THETA, PHI, VARPHI, device_name - import numpy as np import pennylane as qml +import pytest +from conftest import PHI, THETA, VARPHI, device_name from mpi4py import MPI diff --git a/mpitests/test_measurements_sparse.py b/mpitests/test_measurements_sparse.py index 4ea2856289..7ca88867eb 100644 --- a/mpitests/test_measurements_sparse.py +++ b/mpitests/test_measurements_sparse.py @@ -16,12 +16,12 @@ """ # pylint: disable=protected-access,too-few-public-methods,unused-import,missing-function-docstring,too-many-arguments -import pytest -from conftest import device_name, LightningDevice as ld -from mpi4py import MPI - import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name +from mpi4py import MPI from pennylane import qchem if not ld._CPP_BINARY_AVAILABLE: diff --git a/mpitests/test_probs.py b/mpitests/test_probs.py index f07a00ba6f..b2f57f733a 100644 --- a/mpitests/test_probs.py +++ b/mpitests/test_probs.py @@ -12,16 +12,13 @@ """ Unit tests for the :mod:`pennylane_lightning.LightningGPU` device (MPI). """ -# pylint: disable=missing-function-docstring,unnecessary-comprehension,too-many-arguments,wrong-import-order,unused-variable,c-extension-no-member -from mpi4py import MPI -import pytest - -from conftest import ( - device_name, -) - import numpy as np import pennylane as qml +import pytest +from conftest import device_name + +# pylint: disable=missing-function-docstring,unnecessary-comprehension,too-many-arguments,wrong-import-order,unused-variable,c-extension-no-member +from mpi4py import MPI numQubits = 8 diff --git a/pennylane_lightning/core/_serialize.py b/pennylane_lightning/core/_serialize.py index 50810608c7..2c85333b05 100644 --- a/pennylane_lightning/core/_serialize.py +++ b/pennylane_lightning/core/_serialize.py @@ -15,25 +15,26 @@ Helper functions for serializing quantum tapes. """ from typing import List, Tuple + import numpy as np from pennylane import ( BasisState, + DeviceError, Hadamard, + Hamiltonian, + Identity, PauliX, PauliY, PauliZ, - Identity, - StatePrep, + QubitUnitary, Rot, - Hamiltonian, SparseHamiltonian, - QubitUnitary, + StatePrep, + matrix, ) +from pennylane.math import unwrap from pennylane.operation import Tensor from pennylane.tape import QuantumTape -from pennylane.math import unwrap - -from pennylane import matrix, DeviceError pauli_name_map = { "I": "Identity", diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 89f11c0a16..512bfe6499 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-dev" +__version__ = "0.36.0-dev1" diff --git a/pennylane_lightning/core/lightning_base.py b/pennylane_lightning/core/lightning_base.py index e17f83fe3b..36fa4c504f 100644 --- a/pennylane_lightning/core/lightning_base.py +++ b/pennylane_lightning/core/lightning_base.py @@ -16,25 +16,19 @@ This module contains the base class for all PennyLane Lightning simulator devices, and interfaces with C++ for improved performance. """ -from typing import List from itertools import islice, product -import numpy as np - +from typing import List +import numpy as np import pennylane as qml -from pennylane import ( - BasisState, - QubitDevice, - StatePrep, -) +from pennylane import BasisState, QubitDevice, StatePrep from pennylane.devices import DefaultQubitLegacy from pennylane.measurements import MeasurementProcess from pennylane.operation import Operation from pennylane.wires import Wires - -from ._version import __version__ from ._serialize import QuantumScriptSerializer +from ._version import __version__ def _chunk_iterable(iteration, num_chunks): diff --git a/pennylane_lightning/lightning_gpu/__init__.py b/pennylane_lightning/lightning_gpu/__init__.py index 54ce56655d..e88765aa28 100644 --- a/pennylane_lightning/lightning_gpu/__init__.py +++ b/pennylane_lightning/lightning_gpu/__init__.py @@ -14,4 +14,5 @@ """PennyLane lightning_gpu package.""" from pennylane_lightning.core import __version__ + from .lightning_gpu import LightningGPU diff --git a/pennylane_lightning/lightning_gpu/lightning_gpu.py b/pennylane_lightning/lightning_gpu/lightning_gpu.py index ca1c89b29b..be36ff548d 100644 --- a/pennylane_lightning/lightning_gpu/lightning_gpu.py +++ b/pennylane_lightning/lightning_gpu/lightning_gpu.py @@ -17,36 +17,34 @@ interfaces with the NVIDIA cuQuantum cuStateVec simulator library for GPU-enabled calculations. """ -from warnings import warn from pathlib import Path +from warnings import warn + import numpy as np -from pennylane_lightning.core.lightning_base import ( - LightningBase, - LightningBaseFallBack, -) +from pennylane_lightning.core.lightning_base import LightningBase, LightningBaseFallBack try: from pennylane_lightning.lightning_gpu_ops import ( - backend_info, - StateVectorC128, - StateVectorC64, - MeasurementsC128, + DevPool, MeasurementsC64, - is_gpu_supported, + MeasurementsC128, + StateVectorC64, + StateVectorC128, + backend_info, get_gpu_arch, - DevPool, + is_gpu_supported, ) try: # pylint: disable=no-name-in-module from pennylane_lightning.lightning_gpu_ops import ( - StateVectorMPIC128, - StateVectorMPIC64, - MeasurementsMPIC128, + DevTag, MeasurementsMPIC64, + MeasurementsMPIC128, MPIManager, - DevTag, + StateVectorMPIC64, + StateVectorMPIC128, ) MPI_SUPPORT = True @@ -75,40 +73,44 @@ LGPU_CPP_BINARY_AVAILABLE = False if LGPU_CPP_BINARY_AVAILABLE: - from typing import List, Union from itertools import product + from typing import List, Union + import pennylane as qml from pennylane import ( - math, BasisState, - StatePrep, DeviceError, Projector, - Rot, QuantumFunctionError, + Rot, + StatePrep, + math, ) + from pennylane.measurements import Expectation, MeasurementProcess, State from pennylane.operation import Tensor from pennylane.ops.op_math import Adjoint - from pennylane.measurements import Expectation, MeasurementProcess, State from pennylane.wires import Wires - import pennylane as qml - - # pylint: disable=import-error, no-name-in-module, ungrouped-imports - from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal - from pennylane_lightning.core._version import __version__ + # pylint: disable=no-name-in-module, ungrouped-imports from pennylane_lightning.lightning_gpu_ops.algorithms import ( AdjointJacobianC64, - create_ops_listC64, AdjointJacobianC128, + create_ops_listC64, create_ops_listC128, ) + # pylint: disable=import-error, no-name-in-module, ungrouped-imports + from pennylane_lightning.core._serialize import ( + QuantumScriptSerializer, + global_phase_diagonal, + ) + from pennylane_lightning.core._version import __version__ + if MPI_SUPPORT: from pennylane_lightning.lightning_gpu_ops.algorithmsMPI import ( AdjointJacobianMPIC64, - create_ops_listMPIC64, AdjointJacobianMPIC128, + create_ops_listMPIC64, create_ops_listMPIC128, ) diff --git a/pennylane_lightning/lightning_kokkos/__init__.py b/pennylane_lightning/lightning_kokkos/__init__.py index 73db3196de..7b1045d540 100644 --- a/pennylane_lightning/lightning_kokkos/__init__.py +++ b/pennylane_lightning/lightning_kokkos/__init__.py @@ -14,4 +14,5 @@ """PennyLane lightning_kokkos package.""" from pennylane_lightning.core import __version__ + from .lightning_kokkos import LightningKokkos diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 9be3a68808..9844d86ec9 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -17,8 +17,9 @@ interfaces with C++ for fast linear algebra calculations. """ -from warnings import warn from pathlib import Path +from warnings import warn + import numpy as np from pennylane_lightning.core.lightning_base import ( @@ -30,14 +31,14 @@ try: # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_kokkos_ops import ( - allocate_aligned_array, - backend_info, InitializationSettings, - MeasurementsC128, MeasurementsC64, - print_configuration, - StateVectorC128, + MeasurementsC128, StateVectorC64, + StateVectorC128, + allocate_aligned_array, + backend_info, + print_configuration, ) LK_CPP_BINARY_AVAILABLE = True @@ -45,32 +46,34 @@ LK_CPP_BINARY_AVAILABLE = False if LK_CPP_BINARY_AVAILABLE: - from typing import List from os import getenv + from typing import List + import pennylane as qml from pennylane import ( - math, BasisState, - StatePrep, - Projector, - Rot, DeviceError, + Projector, QuantumFunctionError, + Rot, + StatePrep, + math, ) + from pennylane.measurements import Expectation, MeasurementProcess, State from pennylane.operation import Tensor from pennylane.ops.op_math import Adjoint - from pennylane.measurements import MeasurementProcess, Expectation, State from pennylane.wires import Wires - import pennylane as qml - # pylint: disable=import-error, no-name-in-module, ungrouped-imports - from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal + from pennylane_lightning.core._serialize import ( + QuantumScriptSerializer, + global_phase_diagonal, + ) from pennylane_lightning.core._version import __version__ from pennylane_lightning.lightning_kokkos_ops.algorithms import ( AdjointJacobianC64, - create_ops_listC64, AdjointJacobianC128, + create_ops_listC64, create_ops_listC128, ) diff --git a/pennylane_lightning/lightning_qubit/__init__.py b/pennylane_lightning/lightning_qubit/__init__.py index 53e50cbf00..a1b792afde 100644 --- a/pennylane_lightning/lightning_qubit/__init__.py +++ b/pennylane_lightning/lightning_qubit/__init__.py @@ -14,4 +14,5 @@ """PennyLane lightning_qubit package.""" from pennylane_lightning.core import __version__ + from .lightning_qubit import LightningQubit diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 51fa9d4c14..f220653311 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -17,8 +17,9 @@ interfaces with C++ for fast linear algebra calculations. """ -from warnings import warn from pathlib import Path +from warnings import warn + import numpy as np from pennylane_lightning.core.lightning_base import ( @@ -30,14 +31,14 @@ try: # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_qubit_ops import ( - allocate_aligned_array, - get_alignment, - best_alignment, MeasurementsC64, - StateVectorC64, MeasurementsC128, + StateVectorC64, StateVectorC128, + allocate_aligned_array, backend_info, + best_alignment, + get_alignment, ) LQ_CPP_BINARY_AVAILABLE = True @@ -45,34 +46,33 @@ LQ_CPP_BINARY_AVAILABLE = False if LQ_CPP_BINARY_AVAILABLE: - from typing import List from os import getenv + from typing import List + import pennylane as qml from pennylane import ( - math, BasisState, - StatePrep, - Projector, - Rot, DeviceError, + Projector, QuantumFunctionError, + Rot, + StatePrep, + math, ) + from pennylane.measurements import Expectation, MeasurementProcess, State from pennylane.operation import Tensor - from pennylane.measurements import MeasurementProcess, Expectation, State from pennylane.wires import Wires - import pennylane as qml - # pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import QuantumScriptSerializer from pennylane_lightning.core._version import __version__ from pennylane_lightning.lightning_qubit_ops.algorithms import ( AdjointJacobianC64, - create_ops_listC64, - VectorJacobianProductC64, AdjointJacobianC128, - create_ops_listC128, + VectorJacobianProductC64, VectorJacobianProductC128, + create_ops_listC64, + create_ops_listC128, ) def _state_dtype(dtype): diff --git a/requirements-dev.txt b/requirements-dev.txt index 7a0230999c..aee5243438 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,6 +11,7 @@ pre-commit>=2.19.0 black==23.7.0 clang-tidy~=16.0 clang-format~=16.0 +isort cmake custatevec-cu12 pylint diff --git a/tests/conftest.py b/tests/conftest.py index e10a3c203c..96531bf421 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,10 +16,10 @@ """ import os -import pytest import numpy as np - import pennylane as qml +import pytest + import pennylane_lightning # defaults diff --git a/tests/lightning_qubit/test_measurements_samples_MCMC.py b/tests/lightning_qubit/test_measurements_samples_MCMC.py index 376cd31fd2..e376294353 100644 --- a/tests/lightning_qubit/test_measurements_samples_MCMC.py +++ b/tests/lightning_qubit/test_measurements_samples_MCMC.py @@ -14,17 +14,13 @@ """ Unit tests for MCMC sampling in lightning.qubit. """ -import pytest -from conftest import LightningDevice # tested device - import numpy as np import pennylane as qml - import pytest +from conftest import LightningDevice # tested device from pennylane_lightning.lightning_qubit import LightningQubit - if not LightningQubit._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 871a39e338..ad9db67239 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -15,16 +15,16 @@ Tests for ``adjoint_jacobian`` method on Lightning devices. """ import itertools -import pytest -from conftest import device_name, LightningDevice as ld - import math -from scipy.stats import unitary_group + import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name +from pennylane import QNode from pennylane import numpy as np -from pennylane import QNode, qnode -from pennylane import qchem - +from pennylane import qchem, qnode +from scipy.stats import unitary_group I, X, Y, Z = ( np.eye(2), diff --git a/tests/test_apply.py b/tests/test_apply.py index 9dd5861f99..5af7d4101a 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -15,12 +15,16 @@ Unit tests for Lightning devices. """ # pylint: disable=protected-access,cell-var-from-loop -import pytest -from conftest import THETA, PHI, TOL_STOCHASTIC, LightningDevice as ld, device_name - import math + import numpy as np import pennylane as qml + +# pylint: disable=protected-access,cell-var-from-loop +import pytest +from conftest import PHI, THETA, TOL_STOCHASTIC, VARPHI +from conftest import LightningDevice as ld +from conftest import device_name from pennylane import DeviceError from pennylane.operation import Operation from pennylane.wires import Wires diff --git a/tests/test_arrays.py b/tests/test_arrays.py index 2f3fccb8a7..c90d78026e 100644 --- a/tests/test_arrays.py +++ b/tests/test_arrays.py @@ -14,11 +14,10 @@ """ Array tests for Lightning devices. """ +import numpy as np import pytest from conftest import LightningDevice as ld -import numpy as np - try: from pennylane_lightning.lightning_qubit_ops import allocate_aligned_array except (ImportError, ModuleNotFoundError): diff --git a/tests/test_binary_info.py b/tests/test_binary_info.py index c6e269d3be..74d17c7875 100644 --- a/tests/test_binary_info.py +++ b/tests/test_binary_info.py @@ -14,17 +14,18 @@ """ Test binary information of Lightning devices. """ -import pytest import platform +import pytest + if platform.machine() != "x86_64": pytest.skip("Expected to fail on non x86 systems. Skipping.", allow_module_level=True) try: - from pennylane_lightning.lightning_qubit_ops import runtime_info, compile_info + from pennylane_lightning.lightning_qubit_ops import compile_info, runtime_info except (ImportError, ModuleNotFoundError): try: - from pennylane_lightning.lightning_kokkos_ops import runtime_info, compile_info + from pennylane_lightning.lightning_kokkos_ops import compile_info, runtime_info except (ImportError, ModuleNotFoundError): pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/test_comparison.py b/tests/test_comparison.py index bb6dfb935e..90bcc41128 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -15,13 +15,14 @@ Integration tests that compare the output states of the compiled Lightning device with the ``default.qubit``. """ -import pytest -from conftest import device_name, LightningDevice as ld - import itertools -import numpy as np import os + +import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name def lightning_backend_dev(wires): diff --git a/tests/test_decomposition.py b/tests/test_decomposition.py index a80e955cfd..29a7874871 100644 --- a/tests/test_decomposition.py +++ b/tests/test_decomposition.py @@ -14,11 +14,10 @@ """ Unit tests for operation decomposition with Lightning devices. """ -import pytest -from conftest import LightningDevice as ld - import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice as ld if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/test_device.py b/tests/test_device.py index 4039394276..98691b6fd1 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -14,11 +14,11 @@ """ Unit tests for Lightning devices creation. """ -import pytest -from conftest import device_name, LightningDevice as ld - import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/test_execute.py b/tests/test_execute.py index 9efc2955dc..6e1db8ebb0 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -15,10 +15,10 @@ Integration tests for the ``execute`` method of Lightning devices. """ import functools -import pytest -from conftest import device_name import pennylane as qml +import pytest +from conftest import device_name from pennylane import numpy as np diff --git a/tests/test_expval.py b/tests/test_expval.py index 561a9ed235..95a985c362 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -15,11 +15,11 @@ Unit tests for the expval method of Lightning devices. """ import itertools -import pytest -from conftest import THETA, PHI, VARPHI, device_name import numpy as np import pennylane as qml +import pytest +from conftest import PHI, THETA, VARPHI, device_name @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) diff --git a/tests/test_gates.py b/tests/test_gates.py index f0152a7ea1..847c3a845a 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -14,14 +14,13 @@ """ Unit tests for the correct application of gates with a Lightning device. """ -import pytest -from conftest import LightningDevice, device_name -from conftest import THETA, PHI - import copy import itertools + import numpy as np import pennylane as qml +import pytest +from conftest import PHI, THETA, LightningDevice, device_name @pytest.fixture diff --git a/tests/test_measurements.py b/tests/test_measurements.py index edc4b59fa6..62960f92fd 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -14,17 +14,14 @@ """ Unit tests for Measurements in Lightning devices. """ -import pytest -from conftest import device_name, LightningDevice as ld, lightning_ops - -import numpy as np import math +import numpy as np import pennylane as qml -from pennylane.measurements import ( - Variance, - Expectation, -) +import pytest +from conftest import LightningDevice as ld +from conftest import device_name, lightning_ops +from pennylane.measurements import Expectation, Variance if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/test_measurements_sparse.py b/tests/test_measurements_sparse.py index 4849c9a43e..f01e6dd711 100644 --- a/tests/test_measurements_sparse.py +++ b/tests/test_measurements_sparse.py @@ -14,11 +14,11 @@ """ Unit tests for Sparse Measurements Lightning devices. """ -import pytest -from conftest import device_name, LightningDevice as ld - import numpy as np import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name from pennylane import qchem if not ld._CPP_BINARY_AVAILABLE: diff --git a/tests/test_serialize.py b/tests/test_serialize.py index e81f5c1783..1f726a55ef 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -14,54 +14,57 @@ """ Unit tests for the serialization helper functions. """ -import pytest -from conftest import device_name, LightningDevice - import numpy as np import pennylane as qml -from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal +import pytest +from conftest import LightningDevice, device_name + +from pennylane_lightning.core._serialize import ( + QuantumScriptSerializer, + global_phase_diagonal, +) if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": from pennylane_lightning.lightning_kokkos_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, HamiltonianC64, HamiltonianC128, + HermitianObsC64, + HermitianObsC128, + NamedObsC64, + NamedObsC128, SparseHamiltonianC64, SparseHamiltonianC128, + TensorProdObsC64, + TensorProdObsC128, ) elif device_name == "lightning.gpu": from pennylane_lightning.lightning_gpu_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, HamiltonianC64, HamiltonianC128, + HermitianObsC64, + HermitianObsC128, + NamedObsC64, + NamedObsC128, SparseHamiltonianC64, SparseHamiltonianC128, + TensorProdObsC64, + TensorProdObsC128, ) else: from pennylane_lightning.lightning_qubit_ops.observables import ( - NamedObsC64, - NamedObsC128, - HermitianObsC64, - HermitianObsC128, - TensorProdObsC64, - TensorProdObsC128, HamiltonianC64, HamiltonianC128, + HermitianObsC64, + HermitianObsC128, + NamedObsC64, + NamedObsC128, SparseHamiltonianC64, SparseHamiltonianC128, + TensorProdObsC64, + TensorProdObsC128, ) diff --git a/tests/test_serialize_chunk_obs.py b/tests/test_serialize_chunk_obs.py index ec70b998c0..58562657c1 100644 --- a/tests/test_serialize_chunk_obs.py +++ b/tests/test_serialize_chunk_obs.py @@ -14,13 +14,13 @@ """ Unit tests for the serialization helper functions. """ +import numpy as np +import pennylane as qml import pytest -from conftest import device_name, LightningDevice as ld +from conftest import LightningDevice as ld +from conftest import device_name -import pennylane as qml -import numpy as np import pennylane_lightning - from pennylane_lightning.core._serialize import QuantumScriptSerializer if not ld._CPP_BINARY_AVAILABLE: diff --git a/tests/test_serialize_no_binaries.py b/tests/test_serialize_no_binaries.py index f15b53ea67..28f72fcb14 100644 --- a/tests/test_serialize_no_binaries.py +++ b/tests/test_serialize_no_binaries.py @@ -15,7 +15,7 @@ Unit tests for the serialization helper functions. """ import pytest -from conftest import device_name, LightningDevice +from conftest import LightningDevice, device_name from pennylane_lightning.core._serialize import QuantumScriptSerializer diff --git a/tests/test_var.py b/tests/test_var.py index ccee5cda20..bf0779da6f 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -14,11 +14,10 @@ """ Unit tests for the var method of the :mod:`pennylane_lightning.LightningQubit` device. """ -import pytest -from conftest import THETA, PHI, VARPHI - import numpy as np import pennylane as qml +import pytest +from conftest import PHI, THETA, VARPHI np.random.seed(42) diff --git a/tests/test_vjp.py b/tests/test_vjp.py index b4e28570db..d53b9b4135 100644 --- a/tests/test_vjp.py +++ b/tests/test_vjp.py @@ -14,11 +14,12 @@ """ Tests for the ``vjp`` method of LightningKokkos. """ -import pytest -from conftest import device_name, LightningDevice as ld - import math + import pennylane as qml +import pytest +from conftest import LightningDevice as ld +from conftest import device_name from pennylane import numpy as np if not ld._CPP_BINARY_AVAILABLE: From 4ecf2fbaa9174a03841c1fffdd9c065aabc5d66e Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Tue, 5 Mar 2024 14:59:58 +0000 Subject: [PATCH 089/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 512bfe6499..5ef6d062a5 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-dev1" +__version__ = "0.36.0-dev2" From 3bab48bd1097037bf1621c8594bb1925c5221cef Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 10:05:45 -0500 Subject: [PATCH 090/105] isort --- pennylane_lightning/lightning_qubit/_measurements.py | 5 ++++- tests/lightning_qubit/test_measurements_class.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index e5102a7e40..ad2fe74a57 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -17,7 +17,10 @@ # pylint: disable=import-error, no-name-in-module, ungrouped-imports try: - from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) except ImportError: pass diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index a22c129e32..1c4cbf3753 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -25,7 +25,10 @@ from pennylane.measurements import ProbabilityMP, VarianceMP try: - from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 + from pennylane_lightning.lightning_qubit_ops import ( + MeasurementsC64, + MeasurementsC128, + ) except ImportError: pass From 11f6a1b048fa2252467faad761b97ff83b7093c5 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Mon, 4 Mar 2024 19:48:31 +0000 Subject: [PATCH 091/105] Add qml.var support. --- pennylane_lightning/lightning_qubit/_measurements.py | 3 +++ tests/lightning_qubit/test_measurements_class.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index ad2fe74a57..467807722c 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -208,9 +208,12 @@ def get_measurement_function( return self.state_diagonalizing_gates return self.expval +<<<<<<< HEAD if isinstance(measurementprocess, ProbabilityMP): return self.probs +======= +>>>>>>> c1c03954 (Add qml.var support.) if isinstance(measurementprocess, VarianceMP): if measurementprocess.obs.name in [ "Identity", diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1c4cbf3753..f2718b6163 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -97,6 +97,10 @@ def test_only_support_state_measurements(self, lightning_sv): @pytest.mark.parametrize( "mp", ( +<<<<<<< HEAD +======= + qml.probs(wires=0), +>>>>>>> c1c03954 (Add qml.var support.) qml.vn_entropy(wires=0), CustomStateMeasurement(), qml.expval(qml.Identity(0)), From 186e53b05b0c24b5fee78796cf9c9e0c19f12fad Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 01:25:02 +0000 Subject: [PATCH 092/105] Add probs support. --- pennylane_lightning/lightning_qubit/_measurements.py | 6 ++++++ tests/lightning_qubit/test_measurements_class.py | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 467807722c..2725091fc0 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -208,12 +208,18 @@ def get_measurement_function( return self.state_diagonalizing_gates return self.expval +<<<<<<< HEAD <<<<<<< HEAD if isinstance(measurementprocess, ProbabilityMP): return self.probs ======= >>>>>>> c1c03954 (Add qml.var support.) +======= + if isinstance(measurementprocess, ProbabilityMP): + return self.probs + +>>>>>>> 383efdb1 (Add probs support.) if isinstance(measurementprocess, VarianceMP): if measurementprocess.obs.name in [ "Identity", diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index f2718b6163..1c4cbf3753 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -97,10 +97,6 @@ def test_only_support_state_measurements(self, lightning_sv): @pytest.mark.parametrize( "mp", ( -<<<<<<< HEAD -======= - qml.probs(wires=0), ->>>>>>> c1c03954 (Add qml.var support.) qml.vn_entropy(wires=0), CustomStateMeasurement(), qml.expval(qml.Identity(0)), From 0222336d2a3869ab3c134d509c1eb2099d6f324f Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 15:34:22 +0000 Subject: [PATCH 093/105] Add measurement tests with wires. --- .../lightning_gpu/lightning_gpu.py | 14 +++++++------- .../lightning_qubit/_measurements.py | 11 +---------- .../test_measurements_class.py | 19 ++++++++++++++----- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pennylane_lightning/lightning_gpu/lightning_gpu.py b/pennylane_lightning/lightning_gpu/lightning_gpu.py index be36ff548d..1ff0642620 100644 --- a/pennylane_lightning/lightning_gpu/lightning_gpu.py +++ b/pennylane_lightning/lightning_gpu/lightning_gpu.py @@ -91,6 +91,13 @@ from pennylane.ops.op_math import Adjoint from pennylane.wires import Wires + # pylint: disable=import-error, no-name-in-module, ungrouped-imports + from pennylane_lightning.core._serialize import ( + QuantumScriptSerializer, + global_phase_diagonal, + ) + from pennylane_lightning.core._version import __version__ + # pylint: disable=no-name-in-module, ungrouped-imports from pennylane_lightning.lightning_gpu_ops.algorithms import ( AdjointJacobianC64, @@ -99,13 +106,6 @@ create_ops_listC128, ) - # pylint: disable=import-error, no-name-in-module, ungrouped-imports - from pennylane_lightning.core._serialize import ( - QuantumScriptSerializer, - global_phase_diagonal, - ) - from pennylane_lightning.core._version import __version__ - if MPI_SUPPORT: from pennylane_lightning.lightning_gpu_ops.algorithmsMPI import ( AdjointJacobianMPIC64, diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 2725091fc0..8a6b083020 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -30,8 +30,8 @@ import pennylane as qml from pennylane.measurements import ( ExpectationMP, - ProbabilityMP, MeasurementProcess, + ProbabilityMP, StateMeasurement, VarianceMP, ) @@ -208,18 +208,9 @@ def get_measurement_function( return self.state_diagonalizing_gates return self.expval -<<<<<<< HEAD -<<<<<<< HEAD - if isinstance(measurementprocess, ProbabilityMP): - return self.probs - -======= ->>>>>>> c1c03954 (Add qml.var support.) -======= if isinstance(measurementprocess, ProbabilityMP): return self.probs ->>>>>>> 383efdb1 (Add probs support.) if isinstance(measurementprocess, VarianceMP): if measurementprocess.obs.name in [ "Identity", diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1c4cbf3753..faa8dedef0 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -14,15 +14,15 @@ import itertools import math - from typing import Sequence + import numpy as np import pennylane as qml import pytest from conftest import LightningDevice # tested device from pennylane.devices import DefaultQubit -from scipy.sparse import csr_matrix, random_array from pennylane.measurements import ProbabilityMP, VarianceMP +from scipy.sparse import csr_matrix, random_array try: from pennylane_lightning.lightning_qubit_ops import ( @@ -424,6 +424,8 @@ def calculate_reference(tape, lightning_sv): @pytest.mark.parametrize( "observable", ( + [0], + [1, 2], qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2), @@ -444,13 +446,19 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), ): return + if measurement is not qml.probs and isinstance(observable, list): + return n_qubits = 4 n_layers = 1 np.random.seed(0) weights = np.random.rand(n_layers, n_qubits, 3) ops = [qml.Hadamard(i) for i in range(n_qubits)] ops += [qml.StronglyEntanglingLayers(weights, wires=range(n_qubits))] - measurements = [measurement(op=observable)] + measurements = ( + [measurement(wires=observable)] + if isinstance(observable, list) + else [measurement(op=observable)] + ) tape = qml.tape.QuantumScript(ops, measurements) expected = self.calculate_reference(tape, lightning_sv) @@ -459,7 +467,8 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): m = LightningMeasurements(statevector) result = m.measure_final_state(tape) - assert np.allclose(result, expected, tol) + # a few tests may fail in single precision, and hence we increase the tolerance + assert np.allclose(result, expected, max(tol, 1.0e-5)) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( @@ -526,7 +535,7 @@ def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol) assert isinstance(result, Sequence) assert len(result) == len(expected) - # a few tests fail in single precision, and hence we increase the tolerance + # a few tests may fail in single precision, and hence we increase the tolerance for r, e in zip(result, expected): assert np.allclose(r, e, max(tol, 1.0e-5)) From bdc28f80c7378161f0638ec7cb672ebf075a5586 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 13:41:18 -0500 Subject: [PATCH 094/105] review suggestions --- .../lightning_qubit/_measurements.py | 2 +- .../lightning_qubit/_state_vector.py | 80 ++++++------------- .../test_measurements_class.py | 15 +--- .../test_state_vector_class.py | 11 +-- 4 files changed, 28 insertions(+), 80 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 79c40d681e..efe38a1758 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -43,7 +43,7 @@ class LightningMeasurements: Measures the state provided by the LightningStateVector class. Args: - qubit_state(LightningStateVector) + qubit_state(LightningStateVector): Lightning state-vector class containing the state vector to be measured. """ def __init__(self, qubit_state: LightningStateVector) -> None: diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index f33054c7da..6839d0aa6a 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -43,7 +43,7 @@ class LightningStateVector: Args: num_wires(int): the number of wires to initialize the device with dtype: Datatypes for state-vector representation. Must be one of - ``np.complex64`` or ``np.complex128``. + ``np.complex64`` or ``np.complex128``. Default is ``np.complex128`` device_name(string): state vector device name. Options: ["lightning.qubit"] """ @@ -81,6 +81,26 @@ def num_wires(self): """Number of wires addressed on this device""" return self._num_wires + @property + def state_vector(self): + """Returns a handle to the state vector.""" + return self._qubit_state + + @property + def state(self): + """Copy the state vector data to a numpy array. + + **Example** + + >>> dev = qml.device('lightning.qubit', wires=1) + >>> dev.apply([qml.PauliX(wires=[0])]) + >>> print(dev.state) + [0.+0.j 1.+0.j] + """ + state = np.zeros(2**self._num_wires, dtype=self.dtype) + self._qubit_state.getState(state) + return state + def _state_dtype(self): """Binding to Lightning Managed state vector C++ class. @@ -88,42 +108,10 @@ def _state_dtype(self): """ return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 - @staticmethod - def _asarray(arr, dtype=None): - """Verify data alignment and allocate aligned memory if needed. - - Args: - arr (numpy.array): data array - dtype (dtype, optional): if provided will convert the array data type. - - Returns: - np.array: numpy array with aligned data. - """ - arr = np.asarray(arr) # arr is not copied - - if arr.dtype.kind not in ["f", "c"]: - return arr - - if not dtype: - dtype = arr.dtype - - # We allocate a new aligned memory and copy data to there if alignment or dtype - # mismatches - # Note that get_alignment does not necessarily return CPUMemoryModel(Unaligned) - # numpy allocated memory as the memory location happens to be aligned. - if int(get_alignment(arr)) < int(best_alignment()) or arr.dtype != dtype: - new_arr = allocate_aligned_array(arr.size, np.dtype(dtype), False).reshape(arr.shape) - if len(arr.shape): - new_arr[:] = arr - else: - np.copyto(new_arr, arr) - arr = new_arr - return arr - def _create_basis_state(self, index): """Return a computational basis state over all wires. + Args: - _qubit_state: a handle to Lightning qubit state. index (int): integer representing the computational basis state. """ self._qubit_state.setBasisState(index) @@ -133,27 +121,6 @@ def reset_state(self): # init the state vector to |00..0> self._qubit_state.resetStateVector() - @property - def state(self): - """Copy the state vector data to a numpy array. - - **Example** - - >>> dev = qml.device('lightning.qubit', wires=1) - >>> dev.apply([qml.PauliX(wires=[0])]) - >>> print(dev.state) - [0.+0.j 1.+0.j] - """ - state = np.zeros(2**self._num_wires, dtype=self.dtype) - state = self._asarray(state, dtype=self.dtype) - self._qubit_state.getState(state) - return state - - @property - def state_vector(self): - """Returns a handle to the state vector.""" - return self._qubit_state - def _preprocess_state_vector(self, state, device_wires): """Initialize the internal state vector in a specified state. @@ -163,14 +130,13 @@ def _preprocess_state_vector(self, state, device_wires): device_wires (Wires): wires that get initialized in the state Returns: + array[int]: indices for which the state is changed to input state vector elements array[complex]: normalized input state of length ``2**len(wires)`` or broadcasted state of shape ``(batch_size, 2**len(wires))`` - array[int]: indices for which the state is changed to input state vector elements """ # special case for integral types if state.dtype.kind == "i": state = np.array(state, dtype=self.dtype) - state = self._asarray(state, dtype=self.dtype) if len(device_wires) == self._num_wires and Wires(sorted(device_wires)) == device_wires: return None, state diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index ff951385f9..ccf59eff32 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -21,14 +21,6 @@ from conftest import LightningDevice # tested device from pennylane.devices import DefaultQubit -try: - from pennylane_lightning.lightning_qubit_ops import ( - MeasurementsC64, - MeasurementsC128, - ) -except ImportError: - pass - from pennylane_lightning.lightning_qubit import LightningQubit from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector @@ -39,6 +31,9 @@ if LightningDevice != LightningQubit: pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) +THETA = np.linspace(0.11, 1, 3) +PHI = np.linspace(0.32, 1, 3) + # General LightningStateVector fixture, for any number of wires. @pytest.fixture( @@ -190,10 +185,6 @@ def test_state_vector_projector_expval(self, lightning_sv, method_name): assert qml.math.allclose(result, np.sin(phi / 2) ** 2) -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) - - @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) class TestExpval: """Test expectation value calculations.""" diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 11a295e7c3..63e8cd2114 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -58,15 +58,6 @@ def test_wrong_dtype(dtype): assert LightningStateVector(3, dtype=dtype) -@pytest.mark.parametrize("dtype", [np.double, np.complex64, None]) -@pytest.mark.parametrize("data", [1.0, [1.0], [1.0, 2.0]]) -def test_asarray(dtype, data, tol): - """Test _asarray returns the right values""" - wires = 2 - state_vector = LightningStateVector(wires) - assert np.allclose(data, state_vector._asarray(data, dtype), atol=tol) - - def test_errors_basis_state(): with pytest.raises(ValueError, match="BasisState parameter must consist of 0 or 1 integers."): state_vector = LightningStateVector(2) @@ -139,7 +130,7 @@ def test_reset_state(tol, operation, par): state_vector.reset_state() - expected_output = state_vector._asarray([1, 0, 0, 0]) + expected_output = np.array([1, 0, 0, 0], dtype=state_vector.dtype) assert np.allclose(state_vector.state, expected_output, atol=tol, rtol=0) From 45b5448dff1911ecd4719645117c341e7c00919b Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 13:47:46 -0500 Subject: [PATCH 095/105] remove unused imports --- pennylane_lightning/lightning_qubit/_state_vector.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 6839d0aa6a..c91f65f697 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -20,8 +20,6 @@ StateVectorC64, StateVectorC128, allocate_aligned_array, - best_alignment, - get_alignment, ) except ImportError: pass From 5e80afb64b4efba99141cd798ff36cb52403a90d Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Tue, 5 Mar 2024 16:52:58 -0500 Subject: [PATCH 096/105] remove diagonalization gate application from state vector --- pennylane_lightning/lightning_qubit/_measurements.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index efe38a1758..c5d43ebfee 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -27,6 +27,7 @@ from typing import Callable, List import numpy as np +import pennylane as qml from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement from pennylane.tape import QuantumScript from pennylane.typing import Result, TensorLike @@ -76,7 +77,7 @@ def _measurement_dtype(self): def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> TensorLike: """Apply a measurement to state when the measurement process has an observable with diagonalizing gates. - This method will is bypassing the measurement process to default.qubit implementation. + This method is bypassing the measurement process to default.qubit implementation. Args: measurementprocess (StateMeasurement): measurement to apply to the state @@ -84,11 +85,17 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten Returns: TensorLike: the result of the measurement """ + diagonalizing_gates = measurementprocess.diagonalizing_gates() self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) state_array = self._qubit_state.state wires = Wires(range(self._qubit_state.num_wires)) - return measurementprocess.process_state(state_array, wires) + + result = measurementprocess.process_state(state_array, wires) + + self._qubit_state.apply_operations([qml.adjoint(g) for g in reversed(diagonalizing_gates)]) + + return result # pylint: disable=protected-access def expval(self, measurementprocess: MeasurementProcess): From 96618ca817bceab50dc8439ec4445ba69dd6c771 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Tue, 5 Mar 2024 22:26:01 +0000 Subject: [PATCH 097/105] pytest.skip tests --- .../lightning_gpu/lightning_gpu.py | 14 +++++------ .../lightning_kokkos/lightning_kokkos.py | 12 +++++----- .../test_measurements_class.py | 23 +++++++++++++++---- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/pennylane_lightning/lightning_gpu/lightning_gpu.py b/pennylane_lightning/lightning_gpu/lightning_gpu.py index 1ff0642620..be36ff548d 100644 --- a/pennylane_lightning/lightning_gpu/lightning_gpu.py +++ b/pennylane_lightning/lightning_gpu/lightning_gpu.py @@ -91,13 +91,6 @@ from pennylane.ops.op_math import Adjoint from pennylane.wires import Wires - # pylint: disable=import-error, no-name-in-module, ungrouped-imports - from pennylane_lightning.core._serialize import ( - QuantumScriptSerializer, - global_phase_diagonal, - ) - from pennylane_lightning.core._version import __version__ - # pylint: disable=no-name-in-module, ungrouped-imports from pennylane_lightning.lightning_gpu_ops.algorithms import ( AdjointJacobianC64, @@ -106,6 +99,13 @@ create_ops_listC128, ) + # pylint: disable=import-error, no-name-in-module, ungrouped-imports + from pennylane_lightning.core._serialize import ( + QuantumScriptSerializer, + global_phase_diagonal, + ) + from pennylane_lightning.core._version import __version__ + if MPI_SUPPORT: from pennylane_lightning.lightning_gpu_ops.algorithmsMPI import ( AdjointJacobianMPIC64, diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 9844d86ec9..90c53cefe7 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -63,6 +63,12 @@ from pennylane.operation import Tensor from pennylane.ops.op_math import Adjoint from pennylane.wires import Wires + from pennylane_lightning.lightning_kokkos_ops.algorithms import ( + AdjointJacobianC64, + AdjointJacobianC128, + create_ops_listC64, + create_ops_listC128, + ) # pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import ( @@ -70,12 +76,6 @@ global_phase_diagonal, ) from pennylane_lightning.core._version import __version__ - from pennylane_lightning.lightning_kokkos_ops.algorithms import ( - AdjointJacobianC64, - AdjointJacobianC128, - create_ops_listC64, - create_ops_listC128, - ) def _kokkos_dtype(dtype): if dtype not in [np.complex128, np.complex64]: # pragma: no cover diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index faa8dedef0..3d902d2ef5 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -399,7 +399,8 @@ def calculate_reference(tape, lightning_sv): use_default = True new_meas = [] for m in tape.measurements: - # not supported by DefaultQubit + # NotImplementedError in DefaultQubit + # We therefore validate against `qml.Hermitian` if isinstance(m, VarianceMP) and isinstance( m.obs, (qml.Hamiltonian, qml.SparseHamiltonian) ): @@ -445,9 +446,15 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): observable, (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), ): - return + pytest.skip( + f"Observable of type {type(observable).__name__} is not supported for rotating probabilities." + ) + if measurement is not qml.probs and isinstance(observable, list): - return + pytest.skip( + f"Measurement of type {type(measurement).__name__} does not have a keyword argument 'wires'." + ) + n_qubits = 4 n_layers = 1 np.random.seed(0) @@ -510,12 +517,18 @@ def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol) obs0_, (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), ): - return + pytest.skip( + f"Observable of type {type(obs0_).__name__} is not supported for rotating probabilities." + ) + if measurement is qml.probs and isinstance( obs1_, (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), ): - return + pytest.skip( + f"Observable of type {type(obs1_).__name__} is not supported for rotating probabilities." + ) + n_qubits = 4 n_layers = 1 np.random.seed(0) From 24976a944e32f31cdf4278604e99a7e70a968003 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 6 Mar 2024 13:03:17 +0000 Subject: [PATCH 098/105] Auto update version --- pennylane_lightning/core/_version.py | 2 +- tests/lightning_qubit/test_measurements_class.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 5ef6d062a5..b8174c108a 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-dev2" +__version__ = "0.36.0-dev3" diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index f1fd78c32a..ebd394b604 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -92,8 +92,6 @@ def test_only_support_state_measurements(self, lightning_sv): @pytest.mark.parametrize( "mp", ( - qml.probs(wires=0), - qml.var(qml.Z(0)), qml.vn_entropy(wires=0), CustomStateMeasurement(), qml.expval(qml.Identity(0)), From fda2b3ef58c695efdb899b930c38a65aef43364e Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Wed, 6 Mar 2024 13:23:28 +0000 Subject: [PATCH 099/105] Fix format --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 2 ++ pennylane_lightning/lightning_qubit/_measurements.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 90c53cefe7..9953e2651c 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -63,6 +63,8 @@ from pennylane.operation import Tensor from pennylane.ops.op_math import Adjoint from pennylane.wires import Wires + + # pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.lightning_kokkos_ops.algorithms import ( AdjointJacobianC64, AdjointJacobianC128, diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 7b8e9af041..28e227c5d9 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -138,7 +138,7 @@ def expval(self, measurementprocess: MeasurementProcess): measurementprocess.obs.name, measurementprocess.obs.wires ) -# pylint: disable=protected-access + # pylint: disable=protected-access def probs(self, measurementprocess: MeasurementProcess): """Probabilities of the supplied observable or wires contained in the MeasurementProcess. From 4d15806aeb59dd6ae4b9cfc91209c3daca394c99 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Wed, 6 Mar 2024 11:14:40 -0500 Subject: [PATCH 100/105] Update tests/lightning_qubit/test_measurements_class.py Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_measurements_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index ebd394b604..f2faddcf54 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -21,7 +21,7 @@ import pytest from conftest import LightningDevice # tested device from pennylane.devices import DefaultQubit -from pennylane.measurements import ProbabilityMP, VarianceMP +from pennylane.measurements import VarianceMP from scipy.sparse import csr_matrix, random_array from pennylane_lightning.lightning_qubit import LightningQubit From d9939597fdd194b221c42b34cc60acfca8bbd2f6 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Wed, 6 Mar 2024 14:00:54 -0500 Subject: [PATCH 101/105] Update pennylane_lightning/lightning_qubit/_measurements.py Co-authored-by: Christina Lee --- pennylane_lightning/lightning_qubit/_measurements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 28e227c5d9..8fc7bc9a68 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -154,7 +154,7 @@ def probs(self, measurementprocess: MeasurementProcess): results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) if diagonalizing_gates: self._qubit_state.apply_operations( - [qml.adjoint(g) for g in reversed(diagonalizing_gates)] + [qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)] ) return results From 9d7b03a199feb0b2d24dbdbfcce7ba71ad38c012 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Wed, 6 Mar 2024 19:04:39 +0000 Subject: [PATCH 102/105] Remove pylint: disable=protected-access --- pennylane_lightning/lightning_qubit/_measurements.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 8fc7bc9a68..be60cc74b4 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -138,7 +138,6 @@ def expval(self, measurementprocess: MeasurementProcess): measurementprocess.obs.name, measurementprocess.obs.wires ) - # pylint: disable=protected-access def probs(self, measurementprocess: MeasurementProcess): """Probabilities of the supplied observable or wires contained in the MeasurementProcess. @@ -158,7 +157,6 @@ def probs(self, measurementprocess: MeasurementProcess): ) return results - # pylint: disable=protected-access def var(self, measurementprocess: MeasurementProcess): """Variance of the supplied observable contained in the MeasurementProcess. From 092977b9aa5cf402305f02dffe4952329f69cf9c Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Wed, 6 Mar 2024 15:05:37 -0500 Subject: [PATCH 103/105] trigger ci From a73b3c3dac232ec3998982b8403d3584e59ebd55 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Thu, 7 Mar 2024 13:27:51 +0000 Subject: [PATCH 104/105] Add var tests --- pennylane_lightning/lightning_qubit/_measurements.py | 3 --- tests/lightning_qubit/test_measurements_class.py | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index be60cc74b4..4a81b1c8e9 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -251,9 +251,6 @@ def measure_final_state(self, circuit: QuantumScript) -> Result: Tuple[TensorLike]: The measurement results """ - if circuit.shots: - raise NotImplementedError - # analytic case if len(circuit.measurements) == 1: return self.measurement(circuit.measurements[0]) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index f2faddcf54..23de856293 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -96,6 +96,8 @@ def test_only_support_state_measurements(self, lightning_sv): CustomStateMeasurement(), qml.expval(qml.Identity(0)), qml.expval(qml.Projector([1, 0], wires=(0, 1))), + qml.var(qml.Identity(0)), + qml.var(qml.Projector([1, 0], wires=(0, 1))), ), ) def test_state_diagonalizing_gates_measurements(self, lightning_sv, mp): From 5d305a0019276b840baef8b8ee57d3f7c0323de0 Mon Sep 17 00:00:00 2001 From: Vincent Michaud-Rioux Date: Thu, 7 Mar 2024 13:29:48 +0000 Subject: [PATCH 105/105] update changelog --- .github/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index a807815c9b..66cb3090c6 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,9 @@ ### New features since last release +* Add analytic-mode `qml.probs` and `qml.var` support in `lightning.qubit2`. + [(#627)](https://github.com/PennyLaneAI/pennylane-lightning/pull/627) + ### Breaking changes ### Improvements