From 93a52bfc76fd09f3685018f96fc3d944142d3ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 16:43:13 -0400 Subject: [PATCH 001/130] Create a base with mock simulate, jacobian, simulate_and_jacobian, vjp and simulate_and_vjp --- .../lightning_kokkos/_state_vector.py | 43 +++++ .../lightning_kokkos/lightning_kokkos.py | 171 +++++++++++++++++- 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 pennylane_lightning/lightning_kokkos/_state_vector.py diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py new file mode 100644 index 000000000..07b0b8142 --- /dev/null +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -0,0 +1,43 @@ +# 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 ( + StateVectorC64, + StateVectorC128, + allocate_aligned_array, + ) +except ImportError: + pass + +from itertools import product + +from ._measurements import LightningMeasurements + +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``. Default is ``np.complex128`` + device_name(string): state vector device name. Options: ["lightning.qubit"] + """ + + pass \ No newline at end of file diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index c3dae23ea..c3ce62940 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -17,9 +17,10 @@ interfaces with C++ for fast linear algebra calculations. """ +from numbers import Number from os import getenv from pathlib import Path -from typing import List +from typing import List, Tuple from warnings import warn import numpy as np @@ -28,12 +29,16 @@ from pennylane.measurements import Expectation, MidMeasureMP, State from pennylane.ops import Conditional from pennylane.ops.op_math import Adjoint +from pennylane.tape import QuantumScript, QuantumTape +from pennylane.typing import Result from pennylane.wires import Wires from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal from pennylane_lightning.core._version import __version__ from pennylane_lightning.core.lightning_base import LightningBase, _chunk_iterable +from ._state_vector import LightningStateVector + try: # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_kokkos_ops import ( @@ -71,6 +76,170 @@ def _kokkos_configuration(): return print_configuration() +def simulate( + circuit: QuantumScript, + state: LightningStateVector, + mcmc: dict = None, + postselect_mode: str = None, +) -> Result: + """Simulate a single quantum script. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (LightningStateVector): handle to Lightning state vector + mcmc (dict): Dictionary containing the Markov Chain Monte Carlo + parameters: mcmc, kernel_name, num_burnin. Descriptions of + these fields are found in :class:`~.LightningQubit`. + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + Tuple[TensorLike]: The results of the simulation + + Note that this function can return measurements for non-commuting observables simultaneously. + """ + return 0 + # if mcmc is None: + # mcmc = {} + # state.reset_state() + # has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) + # if circuit.shots and has_mcm: + # results = [] + # aux_circ = qml.tape.QuantumScript( + # circuit.operations, + # circuit.measurements, + # shots=[1], + # trainable_params=circuit.trainable_params, + # ) + # for _ in range(circuit.shots.total_shots): + # state.reset_state() + # mid_measurements = {} + # final_state = state.get_final_state( + # aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode + # ) + # results.append( + # LightningMeasurements(final_state, **mcmc).measure_final_state( + # aux_circ, mid_measurements=mid_measurements + # ) + # ) + # return tuple(results) + # final_state = state.get_final_state(circuit) + # return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) + +def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): + """Compute the Jacobian for a single quantum script. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (LightningStateVector): handle to Lightning state vector + 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. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + TensorLike: The Jacobian of the quantum script + """ + return 0 + # if wire_map is not None: + # [circuit], _ = qml.map_wires(circuit, wire_map) + # state.reset_state() + # final_state = state.get_final_state(circuit) + # return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) + + +def simulate_and_jacobian( + circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None +): + """Simulate a single quantum script and compute its Jacobian. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (LightningStateVector): handle to Lightning state vector + 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. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + Tuple[TensorLike]: The results of the simulation and the calculated Jacobian + + Note that this function can return measurements for non-commuting observables simultaneously. + """ + return 0 + # if wire_map is not None: + # [circuit], _ = qml.map_wires(circuit, wire_map) + # res = simulate(circuit, state) + # jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) + # return res, jac + + +def vjp( + circuit: QuantumTape, + cotangents: Tuple[Number], + state: LightningStateVector, + batch_obs=False, + wire_map=None, +): + """Compute the Vector-Jacobian Product (VJP) for a single quantum script. + Args: + circuit (QuantumTape): The single circuit to simulate + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must + have shape matching the output shape of the corresponding circuit. If + the circuit has a single output, ``cotangents`` may be a single number, + not an iterable of numbers. + state (LightningStateVector): handle to Lightning state vector + batch_obs (bool): Determine whether we process observables in parallel when + computing the VJP. This value is only relevant when the lightning + qubit is built with OpenMP. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + TensorLike: The VJP of the quantum script + """ + return 0 + # if wire_map is not None: + # [circuit], _ = qml.map_wires(circuit, wire_map) + # state.reset_state() + # final_state = state.get_final_state(circuit) + # return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( + # circuit, cotangents + # ) + + +def simulate_and_vjp( + circuit: QuantumTape, + cotangents: Tuple[Number], + state: LightningStateVector, + batch_obs=False, + wire_map=None, +): + """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). + Args: + circuit (QuantumTape): The single circuit to simulate + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must + have shape matching the output shape of the corresponding circuit. If + the circuit has a single output, ``cotangents`` may be a single number, + not an iterable of numbers. + state (LightningStateVector): handle to Lightning state vector + 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. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + Tuple[TensorLike]: The results of the simulation and the calculated VJP + Note that this function can return measurements for non-commuting observables simultaneously. + """ + return 0 + # if wire_map is not None: + # [circuit], _ = qml.map_wires(circuit, wire_map) + # res = simulate(circuit, state) + # _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) + # return res, _vjp + + allowed_operations = { "Identity", "BasisState", From feca9148d4e9aea351693145f25efdafb9fd2a51 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 22 Jul 2024 20:55:03 +0000 Subject: [PATCH 002/130] Auto update version from '0.38.0-dev12' to '0.38.0-dev13' --- 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 52575c828..c1ad83f4a 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev12" +__version__ = "0.38.0-dev13" From 5ccf20c01573b180e48b7ca9ac6eea6896d087c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 16:57:32 -0400 Subject: [PATCH 003/130] Apply format --- pennylane_lightning/lightning_kokkos/_state_vector.py | 3 ++- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 07b0b8142..f9951880a 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -28,6 +28,7 @@ from ._measurements import LightningMeasurements + class LightningStateVector: """Lightning state-vector class. @@ -40,4 +41,4 @@ class LightningStateVector: device_name(string): state vector device name. Options: ["lightning.qubit"] """ - pass \ No newline at end of file + pass diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index c3ce62940..831c1d02e 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -127,6 +127,7 @@ def simulate( # final_state = state.get_final_state(circuit) # return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) + def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): """Compute the Jacobian for a single quantum script. From bcf961e19b54622d30492625878f75e392f2855c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 17:03:50 -0400 Subject: [PATCH 004/130] Solve issue with measurenment class --- pennylane_lightning/lightning_kokkos/_state_vector.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index f9951880a..68bf16713 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -26,8 +26,6 @@ from itertools import product -from ._measurements import LightningMeasurements - class LightningStateVector: """Lightning state-vector class. From 2e4e6686b08a0a85f2b6628bbdc4e9efdf3fc524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 17:29:02 -0400 Subject: [PATCH 005/130] solve warnings from Pylint --- .../lightning_kokkos/_state_vector.py | 11 ----------- .../lightning_kokkos/lightning_kokkos.py | 12 +++++++----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 68bf16713..eefedfca5 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -15,17 +15,6 @@ Class implementation for state-vector manipulation. """ -try: - from pennylane_lightning.lightning_qubit_ops import ( - StateVectorC64, - StateVectorC128, - allocate_aligned_array, - ) -except ImportError: - pass - -from itertools import product - class LightningStateVector: """Lightning state-vector class. diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 831c1d02e..42ee141d1 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -76,7 +76,7 @@ def _kokkos_configuration(): return print_configuration() -def simulate( +def simulate( # pylint: disable=unused-argument circuit: QuantumScript, state: LightningStateVector, mcmc: dict = None, @@ -128,7 +128,9 @@ def simulate( # return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) -def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): +def jacobian( # pylint: disable=unused-argument + circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None +): """Compute the Jacobian for a single quantum script. Args: @@ -150,7 +152,7 @@ def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, # return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) -def simulate_and_jacobian( +def simulate_and_jacobian( # pylint: disable=unused-argument circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None ): """Simulate a single quantum script and compute its Jacobian. @@ -176,7 +178,7 @@ def simulate_and_jacobian( # return res, jac -def vjp( +def vjp( # pylint: disable=unused-argument circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, @@ -209,7 +211,7 @@ def vjp( # ) -def simulate_and_vjp( +def simulate_and_vjp( # pylint: disable=unused-argument circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, From 3ee9669af2bbb3749ddc32fa9535bebfdf674f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 17:31:57 -0400 Subject: [PATCH 006/130] solve warnings from Pylint --- pennylane_lightning/lightning_kokkos/_state_vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index eefedfca5..ca9e7801c 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -16,7 +16,7 @@ """ -class LightningStateVector: +class LightningStateVector: # pylint: disable=too-few-public-methods """Lightning state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -28,4 +28,4 @@ class LightningStateVector: device_name(string): state vector device name. Options: ["lightning.qubit"] """ - pass + pass # pylint: disable=unnecessary-pass From 0e717f62bfcebee6f82522abad708da79e55b42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 17:34:23 -0400 Subject: [PATCH 007/130] Apply format --- .../lightning_qubit/measurements/MeasurementsLQubit.hpp | 2 +- pennylane_lightning/lightning_kokkos/_state_vector.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp b/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp index 2d642f0b8..6d69fcdc6 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp @@ -124,7 +124,7 @@ class Measurements final const ComplexT *arr_data = this->_statevector.getData(); - // Templated 1-4 wire cases; return probs + // Templated 1-4 wire cases; return probs PROBS_SPECIAL_CASE(1); PROBS_SPECIAL_CASE(2); PROBS_SPECIAL_CASE(3); diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index ca9e7801c..de2f092ea 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -16,7 +16,7 @@ """ -class LightningStateVector: # pylint: disable=too-few-public-methods +class LightningStateVector: # pylint: disable=too-few-public-methods """Lightning state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -28,4 +28,4 @@ class LightningStateVector: # pylint: disable=too-few-public-methods device_name(string): state vector device name. Options: ["lightning.qubit"] """ - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass From 42b6714b1beff5ee0a4f22be5a5aac93245aff36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 22 Jul 2024 17:48:55 -0400 Subject: [PATCH 008/130] Skip test native mcm for Kokkos because has a bug with reference to old API --- tests/test_native_mcm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index f7b9c030f..6b4fa698c 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -28,6 +28,13 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: # pylint: disable=protected-access pytest.skip("No binary module found. Skipping.", allow_module_level=True) +# TODO: remove this after the new device API implementation for Kokkos is implemented +if device_name == "lightning.kokkos": + pytest.skip( + "Native Kokkos device has compatible issues with the new device API. Skipping.", + allow_module_level=True, + ) + def get_device(wires, **kwargs): kwargs.setdefault("shots", None) From 27c7ebc1b968519652672da56ec2bdbfaa7d9143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 23 Jul 2024 14:12:20 -0400 Subject: [PATCH 009/130] Set the base class for LightningDevice and skip the tests --- .../lightning_kokkos/lightning_kokkos.py | 1194 +++++++---------- .../test_adjoint_jacobian_class.py | 3 + .../test_measurements_class.py | 3 + .../test_measurements_samples_MCMC.py | 3 + .../test_state_vector_class.py | 3 + tests/new_api/test_device.py | 22 +- tests/new_api/test_expval.py | 3 + tests/new_api/test_var.py | 3 + tests/test_adjoint_jacobian.py | 3 + tests/test_apply.py | 2 + tests/test_comparison.py | 2 + tests/test_device.py | 2 + tests/test_execute.py | 2 + tests/test_expval.py | 2 + tests/test_gates.py | 2 + tests/test_measurements.py | 2 + tests/test_measurements_sparse.py | 2 + tests/test_native_mcm.py | 3 + tests/test_templates.py | 2 + tests/test_var.py | 2 + tests/test_vjp.py | 2 + 21 files changed, 548 insertions(+), 714 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 42ee141d1..60d7db64f 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -13,67 +13,53 @@ # limitations under the License. r""" -This module contains the :class:`~.LightningQubit` class, a PennyLane simulator device that +This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that interfaces with C++ for fast linear algebra calculations. """ - +from dataclasses import replace from numbers import Number -from os import getenv from pathlib import Path -from typing import List, Tuple -from warnings import warn +from typing import Callable, Optional, Sequence, Tuple, Union import numpy as np import pennylane as qml -from pennylane import BasisState, DeviceError, QuantumFunctionError, Rot, StatePrep, math -from pennylane.measurements import Expectation, MidMeasureMP, State -from pennylane.ops import Conditional -from pennylane.ops.op_math import Adjoint +from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices.default_qubit import adjoint_ops +from pennylane.devices.modifiers import simulator_tracking, single_tape_support +from pennylane.devices.preprocess import ( + decompose, + mid_circuit_measurements, + no_sampling, + validate_adjoint_trainable_params, + validate_device_wires, + validate_measurements, + validate_observables, +) +from pennylane.measurements import MidMeasureMP +from pennylane.operation import DecompositionUndefinedError, Operator, Tensor +from pennylane.ops import Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumTape -from pennylane.typing import Result -from pennylane.wires import Wires - -from pennylane_lightning.core._serialize import QuantumScriptSerializer, global_phase_diagonal -from pennylane_lightning.core._version import __version__ -from pennylane_lightning.core.lightning_base import LightningBase, _chunk_iterable +from pennylane.transforms.core import TransformProgram +from pennylane.typing import Result, ResultBatch from ._state_vector import LightningStateVector try: # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_kokkos_ops import ( - InitializationSettings, - MeasurementsC64, - MeasurementsC128, - StateVectorC64, - StateVectorC128, - allocate_aligned_array, backend_info, print_configuration, ) - # pylint: disable=import-error, no-name-in-module, ungrouped-imports - from pennylane_lightning.lightning_kokkos_ops.algorithms import ( - AdjointJacobianC64, - AdjointJacobianC128, - create_ops_listC64, - create_ops_listC128, - ) - LK_CPP_BINARY_AVAILABLE = True except ImportError: LK_CPP_BINARY_AVAILABLE = False backend_info = None - -def _kokkos_dtype(dtype): - if dtype not in [np.complex128, np.complex64]: # pragma: no cover - raise ValueError(f"Data type is not supported for state-vector computation: {dtype}") - return StateVectorC128 if dtype == np.complex128 else StateVectorC64 - - -def _kokkos_configuration(): - return print_configuration() +Result_or_ResultBatch = Union[Result, ResultBatch] +QuantumTapeBatch = Sequence[QuantumTape] +QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] +PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] def simulate( # pylint: disable=unused-argument @@ -89,7 +75,7 @@ def simulate( # pylint: disable=unused-argument state (LightningStateVector): handle to Lightning state vector mcmc (dict): Dictionary containing the Markov Chain Monte Carlo parameters: mcmc, kernel_name, num_burnin. Descriptions of - these fields are found in :class:`~.LightningQubit`. + these fields are found in :class:`~.LightningKokkos`. postselect_mode (str): Configuration for handling shots with mid-circuit measurement postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to keep the same number of shots. Default is ``None``. @@ -138,7 +124,7 @@ def jacobian( # pylint: disable=unused-argument state (LightningStateVector): handle to Lightning state vector 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. Default is False. + kokkos is built with OpenMP. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -162,7 +148,7 @@ def simulate_and_jacobian( # pylint: disable=unused-argument state (LightningStateVector): handle to Lightning state vector 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. Default is False. + kokkos is built with OpenMP. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -195,7 +181,7 @@ def vjp( # pylint: disable=unused-argument state (LightningStateVector): handle to Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the VJP. This value is only relevant when the lightning - qubit is built with OpenMP. + kokkos is built with OpenMP. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -228,7 +214,7 @@ def simulate_and_vjp( # pylint: disable=unused-argument state (LightningStateVector): handle to Lightning state vector 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. + kokkos is built with OpenMP. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -242,89 +228,195 @@ def simulate_and_vjp( # pylint: disable=unused-argument # _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) # return res, _vjp +_operations = frozenset( + { + "Identity", + "BasisState", + "QubitStateVector", + "StatePrep", + "QubitUnitary", + "ControlledQubitUnitary", + "MultiControlledX", + "DiagonalQubitUnitary", + "PauliX", + "PauliY", + "PauliZ", + "MultiRZ", + "GlobalPhase", + "C(GlobalPhase)", + "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", + "RX", + "RY", + "RZ", + "Rot", + "CRX", + "CRY", + "CRZ", + "CRot", + "IsingXX", + "IsingYY", + "IsingZZ", + "IsingXY", + "SingleExcitation", + "SingleExcitationPlus", + "SingleExcitationMinus", + "DoubleExcitation", + "DoubleExcitationPlus", + "DoubleExcitationMinus", + "QubitCarry", + "QubitSum", + "OrbitalRotation", + "QFT", + "ECR", + "BlockEncode", + "C(BlockEncode)", + } +) +# The set of supported operations. + +_observables = frozenset( + { + "PauliX", + "PauliY", + "PauliZ", + "Hadamard", + "Hermitian", + "Identity", + "Projector", + "SparseHamiltonian", + "Hamiltonian", + "LinearCombination", + "Sum", + "SProd", + "Prod", + "Exp", + } +) +# The set of supported observables. + +def stopping_condition(op: Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" + # These thresholds are adapted from `lightning_base.py` + # To avoid building matrices beyond the given thresholds. + # This should reduce runtime overheads for larger systems. + if isinstance(op, qml.QFT): + return len(op.wires) < 10 + if isinstance(op, qml.GroverOperator): + return len(op.wires) < 13 + + # As ControlledQubitUnitary == C(QubitUnitrary), + # it can be removed from `_operations` to keep + # consistency with `lightning_kokkos.toml` + if isinstance(op, qml.ControlledQubitUnitary): + return True + + return op.name in _operations + + +def stopping_condition_shots(op: Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.kokkos`` + with finite shots.""" + return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional)) + + +def accepted_observables(obs: Operator) -> bool: + """A function that determines whether or not an observable is supported by ``lightning.kokkos``.""" + return obs.name in _observables + + +def adjoint_observables(obs: Operator) -> bool: + """A function that determines whether or not an observable is supported by ``lightning.kokkos`` + when using the adjoint differentiation method.""" + if isinstance(obs, qml.Projector): + return False + + if isinstance(obs, Tensor): + if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs): + return False + return True + + if isinstance(obs, SProd): + return adjoint_observables(obs.base) + + if isinstance(obs, (Sum, Prod)): + return all(adjoint_observables(o) for o in obs) + + return obs.name in _observables + + +def adjoint_measurements(mp: qml.measurements.MeasurementProcess) -> bool: + """Specifies whether or not an observable is compatible with adjoint differentiation on DefaultQubit.""" + return isinstance(mp, qml.measurements.ExpectationMP) + + +def _supports_adjoint(circuit): + if circuit is None: + return True + + prog = TransformProgram() + _add_adjoint_transforms(prog) + + try: + prog((circuit,)) + except (DecompositionUndefinedError, qml.DeviceError, AttributeError): + return False + return True + + +def _add_adjoint_transforms(program: TransformProgram) -> None: + """Private helper function for ``preprocess`` that adds the transforms specific + for adjoint differentiation. + + Args: + program (TransformProgram): where we will add the adjoint differentiation transforms + + Side Effects: + Adds transforms to the input program. + + """ + + name = "adjoint + lightning.kokkos" + program.add_transform(no_sampling, name=name) + program.add_transform( + decompose, + stopping_condition=adjoint_ops, + stopping_condition_shots=stopping_condition_shots, + name=name, + skip_initial_state_prep=False, + ) + program.add_transform(validate_observables, accepted_observables, name=name) + program.add_transform( + validate_measurements, analytic_measurements=adjoint_measurements, name=name + ) + program.add_transform(qml.transforms.broadcast_expand) + program.add_transform(validate_adjoint_trainable_params) + +def _kokkos_configuration(): + return print_configuration() -allowed_operations = { - "Identity", - "BasisState", - "QubitStateVector", - "StatePrep", - "QubitUnitary", - "ControlledQubitUnitary", - "MultiControlledX", - "DiagonalQubitUnitary", - "PauliX", - "PauliY", - "PauliZ", - "MultiRZ", - "GlobalPhase", - "C(GlobalPhase)", - "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", - "RX", - "RY", - "RZ", - "Rot", - "CRX", - "CRY", - "CRZ", - "CRot", - "IsingXX", - "IsingYY", - "IsingZZ", - "IsingXY", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - "DoubleExcitation", - "DoubleExcitationPlus", - "DoubleExcitationMinus", - "QubitCarry", - "QubitSum", - "OrbitalRotation", - "QFT", - "ECR", - "BlockEncode", - "C(BlockEncode)", -} - -allowed_observables = { - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "Hermitian", - "Identity", - "Projector", - "SparseHamiltonian", - "Hamiltonian", - "LinearCombination", - "Sum", - "SProd", - "Prod", - "Exp", -} - - -class LightningKokkos(LightningBase): +@simulator_tracking +@single_tape_support +class LightningKokkos(Device): """PennyLane Lightning Kokkos device. A device that interfaces with C++ to perform fast linear algebra calculations. @@ -344,665 +436,341 @@ class LightningKokkos(LightningBase): to ``None`` results in computing statistics like expectation values and variances analytically. """ + + _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") + _new_API = True - name = "Lightning Kokkos PennyLane plugin" - short_name = "lightning.kokkos" + # Device specific options + _CPP_BINARY_AVAILABLE = LK_CPP_BINARY_AVAILABLE + _backend_info = backend_info if LK_CPP_BINARY_AVAILABLE else None kokkos_config = {} - operations = allowed_operations - observables = allowed_observables - _backend_info = backend_info + + # This `config` is used in Catalyst-Frontend config = Path(__file__).parent / "lightning_kokkos.toml" - _CPP_BINARY_AVAILABLE = LK_CPP_BINARY_AVAILABLE - def __init__( + # TODO: Move supported ops/obs to TOML file + operations = _operations + # The names of the supported operations. + + observables = _observables + # The names of the supported observables. + + def __init__( # pylint: disable=too-many-arguments self, wires, *, - sync=True, c_dtype=np.complex128, shots=None, + seed="global", + mcmc=False, + kernel_name="Local", + num_burnin=100, batch_obs=False, + # Kokkos arguments + sync=True, kokkos_args=None, - ): # pylint: disable=unused-argument, too-many-arguments - super().__init__(wires, shots=shots, c_dtype=c_dtype) + ): + if not self._CPP_BINARY_AVAILABLE: + raise ImportError( + "Pre-compiled binaries for lightning.kokkos are not available. " + "To manually compile from source, follow the instructions at " + "https://pennylane-lightning.readthedocs.io/en/latest/installation.html." + ) + + super().__init__(wires=wires, shots=shots) - if kokkos_args is None: - self._kokkos_state = _kokkos_dtype(c_dtype)(self.num_wires) - elif isinstance(kokkos_args, InitializationSettings): - self._kokkos_state = _kokkos_dtype(c_dtype)(self.num_wires, kokkos_args) + if isinstance(wires, int): + self._wire_map = None # should just use wires as is else: - type0 = type(InitializationSettings()) - raise TypeError( - f"Argument kokkos_args must be of type {type0} but it is of {type(kokkos_args)}." - ) + self._wire_map = {w: i for i, w in enumerate(self.wires)} + + self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) + + # TODO: Investigate usefulness of creating numpy random generator + 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." + ) + shots = shots if isinstance(shots, Sequence) else [shots] + if any(num_burnin >= s for s in 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 = 0 + + # Kokkos specific options + self._kokkos_args = kokkos_args self._sync = sync - if not LightningKokkos.kokkos_config: LightningKokkos.kokkos_config = _kokkos_configuration() - @property - def stopping_condition(self): - """.BooleanFn: Returns the stopping condition for the device. The returned - function accepts a queueable object (including a PennyLane operation - and observable) and returns ``True`` if supported by the device.""" - fun = super().stopping_condition - - def accepts_obj(obj): - return fun(obj) or isinstance(obj, (qml.measurements.MidMeasureMP, qml.ops.Conditional)) - - return qml.BooleanFn(accepts_obj) - - # pylint: disable=missing-function-docstring - @classmethod - def capabilities(cls): - capabilities = super().capabilities().copy() - capabilities.update(supports_mid_measure=True) - return capabilities - - @staticmethod - def _asarray(arr, dtype=None): - 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) even for - # numpy allocated memory as the memory location happens to be aligned. - if arr.dtype != dtype: - new_arr = allocate_aligned_array(arr.size, np.dtype(dtype), False).reshape(arr.shape) - 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: - index (int): integer representing the computational basis state - Returns: - array[complex]: complex array of shape ``[2]*self.num_wires`` - representing the statevector of the basis state - Note: This function does not support broadcasted inputs yet. - """ - self._kokkos_state.setBasisState(index) - - def reset(self): - """Reset the device""" - super().reset() - - # init the state vector to |00..0> - self._kokkos_state.resetStateVector() # Sync reset - - def sync_h2d(self, state_vector): - """Copy the state vector data on host provided by the user to the state - vector on the device - - Args: - state_vector(array[complex]): the state vector array on host. - - - **Example** - - >>> dev = qml.device('lightning.kokkos', wires=3) - >>> obs = qml.Identity(0) @ qml.PauliX(1) @ qml.PauliY(2) - >>> obs1 = qml.Identity(1) - >>> H = qml.Hamiltonian([1.0, 1.0], [obs1, obs]) - >>> state_vector = np.array([0.0 + 0.0j, 0.0 + 0.1j, 0.1 + 0.1j, 0.1 + 0.2j, 0.2 + 0.2j, 0.3 + 0.3j, 0.3 + 0.4j, 0.4 + 0.5j,], dtype=np.complex64) - >>> dev.sync_h2d(state_vector) - >>> res = dev.expval(H) - >>> print(res) - 1.0 - """ - self._kokkos_state.HostToDevice(state_vector.ravel(order="C")) - - def sync_d2h(self, state_vector): - """Copy the state vector data on device to a state vector on the host provided - by the user - - Args: - state_vector(array[complex]): the state vector array on host - - - **Example** - - >>> dev = qml.device('lightning.kokkos', wires=1) - >>> dev.apply([qml.PauliX(wires=[0])]) - >>> state_vector = np.zeros(2**dev.num_wires).astype(dev.C_DTYPE) - >>> dev.sync_d2h(state_vector) - >>> print(state_vector) - [0.+0.j 1.+0.j] - """ - self._kokkos_state.DeviceToHost(state_vector.ravel(order="C")) @property - def create_ops_list(self): - """Returns create_ops_list function of the matching precision.""" - return create_ops_listC64 if self.use_csingle else create_ops_listC128 + def name(self): + """The name of the device.""" + return "lightning.kokkos" @property - def measurements(self): - """Returns Measurements constructor of the matching precision.""" - state_vector = self.state_vector - return MeasurementsC64(state_vector) if self.use_csingle else MeasurementsC128(state_vector) + def c_dtype(self): + """State vector complex data type.""" + return self._c_dtype - @property - def state(self): - """Copy the state vector data from the device to the host. - - A state vector Numpy array is explicitly allocated on the host to store and return - the data. - - **Example** + dtype = c_dtype - >>> dev = qml.device('lightning.kokkos', wires=1) - >>> dev.apply([qml.PauliX(wires=[0])]) - >>> print(dev.state) - [0.+0.j 1.+0.j] + def _setup_execution_config(self, config): """ - state = np.zeros(2**self.num_wires, dtype=self.C_DTYPE) - state = self._asarray(state, dtype=self.C_DTYPE) - self.sync_d2h(state) - return state - - @property - def state_vector(self): - """Returns a handle to the statevector.""" - return self._kokkos_state - - 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 + Update the execution config with choices for how the device should be used and the device options. """ + 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 - if isinstance(state, self._kokkos_state.__class__): - state_data = allocate_aligned_array(state.size, np.dtype(self.C_DTYPE), True) - state.DeviceToHost(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 + 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) - if len(device_wires) == self.num_wires and Wires(sorted(device_wires)) == device_wires: - # Initialize the entire device state with the input state - self.sync_h2d(self._reshape(state, output_shape)) - return + return replace(config, **updated_values, device_options=new_device_options) - self._kokkos_state.setStateVector(ravelled_indices, state) # this operation on device - - def _apply_basis_state(self, state, wires): - """Initialize the state vector in a specified computational basis state. + def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): + """This function defines the device transform program to be applied and an updated device configuration. 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_midmeasure( - self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str - ): - """Execute a MidMeasureMP operation and return the sample in mid_measurements. - - Args: - operation (~pennylane.operation.Operation): mid-circuit measurement + execution_config (Union[ExecutionConfig, Sequence[ExecutionConfig]]): A data structure describing the + parameters needed to fully describe the execution. Returns: - None - """ - wires = self.wires.indices(operation.wires) - wire = list(wires)[0] - if postselect_mode == "fill-shots" and operation.postselect is not None: - sample = operation.postselect - else: - sample = qml.math.reshape(self.generate_samples(shots=1), (-1,))[wire] - mid_measurements[operation] = sample - getattr(self.state_vector, "collapse")(wire, bool(sample)) - if operation.reset and bool(sample): - self.apply([qml.PauliX(operation.wires)], mid_measurements=mid_measurements) + TransformProgram, ExecutionConfig: A transform program that when called returns :class:`~.QuantumTape`'s that the + device can natively execute as well as a postprocessing function to be called after execution, and a configuration + with unset specifications filled in. - def apply_lightning(self, operations, mid_measurements=None, postselect_mode=None): - """Apply a list of operations to the state tensor. + This device: - Args: - operations (list[~pennylane.operation.Operation]): operations to apply - dtype (type): Type of numpy ``complex`` to be used. Can be important - to specify for large systems for memory allocation purposes. + * Supports any qubit operations that provide a matrix + * Currently does not support finite shots + * Currently does not intrinsically support parameter broadcasting - Returns: - array[complex]: the output state tensor """ - # Skip over identity operations instead of performing - # matrix multiplication with the identity. - state = self.state_vector - - for ops in operations: - if isinstance(ops, Adjoint): - name = ops.base.name - invert_param = True - else: - name = ops.name - invert_param = False - if isinstance(ops, qml.Identity): - continue - method = getattr(state, name, None) - wires = self.wires.indices(ops.wires) - - if isinstance(ops, Conditional): - if ops.meas_val.concretize(mid_measurements): - self.apply_lightning([ops.base]) - elif isinstance(ops, MidMeasureMP): - self._apply_lightning_midmeasure(ops, mid_measurements, postselect_mode) - elif isinstance(ops, qml.ops.op_math.Controlled) and isinstance( - ops.base, qml.GlobalPhase - ): - controls = ops.control_wires - control_values = ops.control_values - param = ops.base.parameters[0] - matrix = global_phase_diagonal(param, self.wires, controls, control_values) - state.apply(name, wires, False, [[param]], matrix) - elif method is None: - # Inverse can be set to False since qml.matrix(ops) is already in inverted form - try: - mat = qml.matrix(ops) - except AttributeError: # pragma: no cover - # To support older versions of PL - mat = ops.matrix - - if len(mat) == 0: - raise ValueError("Unsupported operation") - state.apply( - name, - wires, - False, - [], - mat.ravel(order="C"), # inv = False: Matrix already in correct form; - ) # Parameters can be ignored for explicit matrices; F-order for cuQuantum - else: - param = ops.parameters - method(wires, invert_param, param) - - # pylint: disable=unused-argument - def apply(self, operations, rotations=None, mid_measurements=None, **kwargs): - """Applies a list of operations to the state tensor.""" - # 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:] - - postselect_mode = kwargs.get("postselect_mode", None) - - 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, mid_measurements=mid_measurements, postselect_mode=postselect_mode + exec_config = self._setup_execution_config(execution_config) + program = TransformProgram() + + program.add_transform(validate_measurements, name=self.name) + program.add_transform(validate_observables, accepted_observables, name=self.name) + program.add_transform(validate_device_wires, self.wires, name=self.name) + program.add_transform( + mid_circuit_measurements, + device=self, + mcm_config=exec_config.mcm_config, + interface=exec_config.interface, + ) + program.add_transform( + decompose, + stopping_condition=stopping_condition, + stopping_condition_shots=stopping_condition_shots, + skip_initial_state_prep=True, + name=self.name, ) + program.add_transform(qml.transforms.broadcast_expand) + + if exec_config.gradient_method == "adjoint": + _add_adjoint_transforms(program) + return program, exec_config - # pylint: disable=protected-access - def expval(self, observable, shot_range=None, bin_size=None): - """Expectation value of the supplied observable. + # pylint: disable=unused-argument + def execute( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ) -> Result_or_ResultBatch: + """Execute a circuit or a batch of circuits and turn it into results. Args: - observable: A PennyLane observable. - shot_range (tuple[int]): 2-tuple of integers specifying the range of samples - to use. If not specified, all samples are used. - bin_size (int): Divides the shot range into bins of size ``bin_size``, and - returns the measurement statistic separately over each bin. If not - provided, the entire shot range is treated as a single bin. + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the quantum circuits to be executed + execution_config (ExecutionConfig): a datastructure with additional information required for execution Returns: - Expectation value of the observable + TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - if isinstance(observable, qml.Projector): - diagonalizing_gates = observable.diagonalizing_gates() - if self.shots is None and diagonalizing_gates: - self.apply(diagonalizing_gates) - results = super().expval(observable, shot_range=shot_range, bin_size=bin_size) - if self.shots is None and diagonalizing_gates: - self.apply([qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)]) - return results - - if self.shots is not None: - # estimate the expectation value - # LightningQubit doesn't support sampling yet - samples = self.sample(observable, shot_range=shot_range, bin_size=bin_size) - return np.squeeze(np.mean(samples, axis=0)) - - # Initialization of state - measure = ( - MeasurementsC64(self.state_vector) - if self.use_csingle - else MeasurementsC128(self.state_vector) - ) - if isinstance(observable, qml.SparseHamiltonian): - csr_hamiltonian = observable.sparse_matrix(wire_order=self.wires).tocsr(copy=False) - return measure.expval( - csr_hamiltonian.indptr, - csr_hamiltonian.indices, - csr_hamiltonian.data, - ) - - # use specialized functors to compute expval(Hermitian) - if isinstance(observable, qml.Hermitian): - observable_wires = self.map_wires(observable.wires) - matrix = observable.matrix() - return measure.expval(matrix, observable_wires) - - if ( - isinstance(observable, qml.ops.Hamiltonian) - or (observable.arithmetic_depth > 0) - or isinstance(observable.name, List) - ): - ob_serialized = QuantumScriptSerializer(self.short_name, self.use_csingle)._ob( - observable, self.wire_map + mcmc = { + "mcmc": self._mcmc, + "kernel_name": self._kernel_name, + "num_burnin": self._num_burnin, + } + results = [] + for circuit in circuits: + if self._wire_map is not None: + [circuit], _ = qml.map_wires(circuit, self._wire_map) + results.append( + simulate( + circuit, + self._statevector, + mcmc=mcmc, + postselect_mode=execution_config.mcm_config.postselect_mode, + ) ) - return measure.expval(ob_serialized) - # translate to wire labels used by device - observable_wires = self.map_wires(observable.wires) + return tuple(results) - return measure.expval(observable.name, observable_wires) + def supports_derivatives( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[qml.tape.QuantumTape] = None, + ) -> bool: + """Check whether or not derivatives are available for a given configuration and circuit. - def var(self, observable, shot_range=None, bin_size=None): - """Variance of the supplied observable. + ``LightningKokkos`` supports adjoint differentiation with analytic results. Args: - observable: A PennyLane observable. - shot_range (tuple[int]): 2-tuple of integers specifying the range of samples - to use. If not specified, all samples are used. - bin_size (int): Divides the shot range into bins of size ``bin_size``, and - returns the measurement statistic separately over each bin. If not - provided, the entire shot range is treated as a single bin. + execution_config (ExecutionConfig): The configuration of the desired derivative calculation + circuit (QuantumTape): An optional circuit to check derivatives support for. Returns: - Variance of the observable - """ - if isinstance(observable, qml.Projector): - diagonalizing_gates = observable.diagonalizing_gates() - if self.shots is None and diagonalizing_gates: - self.apply(diagonalizing_gates) - results = super().var(observable, shot_range=shot_range, bin_size=bin_size) - if self.shots is None and diagonalizing_gates: - self.apply([qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)]) - return results - - if self.shots is not None: - # estimate the var - # LightningKokkos doesn't support sampling yet - samples = self.sample(observable, shot_range=shot_range, bin_size=bin_size) - return np.squeeze(np.var(samples, axis=0)) - - # Initialization of state - measure = ( - MeasurementsC64(self.state_vector) - if self.use_csingle - else MeasurementsC128(self.state_vector) - ) - - if isinstance(observable, qml.SparseHamiltonian): - csr_hamiltonian = observable.sparse_matrix(wire_order=self.wires).tocsr(copy=False) - return measure.var( - csr_hamiltonian.indptr, - csr_hamiltonian.indices, - csr_hamiltonian.data, - ) - - if ( - isinstance(observable, (qml.Hamiltonian, qml.Hermitian)) - or (observable.arithmetic_depth > 0) - or isinstance(observable.name, List) - ): - ob_serialized = QuantumScriptSerializer(self.short_name, self.use_csingle)._ob( - observable, self.wire_map - ) - return measure.var(ob_serialized) - - # translate to wire labels used by device - observable_wires = self.map_wires(observable.wires) + Bool: Whether or not a derivative can be calculated provided the given information - return measure.var(observable.name, observable_wires) + """ + 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 _supports_adjoint(circuit=circuit) + + def compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Calculate the jacobian of either a single or a batch of circuits on the device. - def generate_samples(self, shots=None): - """Generate samples + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for + execution_config (ExecutionConfig): a datastructure with all additional information required for execution Returns: - array[int]: array of samples in binary representation with shape - ``(dev.shots, dev.num_wires)`` + Tuple: The jacobian for each trainable parameter """ - shots = self.shots if shots is None else shots - measure = ( - MeasurementsC64(self._kokkos_state) - if self.use_csingle - else MeasurementsC128(self._kokkos_state) + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + + return tuple( + jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit in circuits ) - return measure.generate_samples(len(self.wires), shots).astype(int, copy=False) - def probability_lightning(self, wires): - """Return the probability of each computational basis state. + def execute_and_compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Compute the results and jacobians of circuits at the same time. Args: - wires (Iterable[Number, str], Number, str, Wires): wires to return - marginal probabilities for. Wires not provided are traced out of the system. + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits + execution_config (ExecutionConfig): a datastructure with all additional information required for execution Returns: - array[float]: list of the probabilities + tuple: A numeric result of the computation and the gradient. """ - return self.measurements.probs(wires) - - # pylint: disable=attribute-defined-outside-init - def sample(self, observable, shot_range=None, bin_size=None, counts=False): - """Return samples of an observable.""" - diagonalizing_gates = observable.diagonalizing_gates() - if diagonalizing_gates: - self.apply(diagonalizing_gates) - if not isinstance(observable, qml.PauliZ): - self._samples = self.generate_samples() - results = super().sample( - observable, shot_range=shot_range, bin_size=bin_size, counts=counts + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + simulate_and_jacobian( + c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for c in circuits ) - if diagonalizing_gates: - self.apply([qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)]) - return results - - @staticmethod - def _check_adjdiff_supported_operations(operations): - """Check Lightning adjoint differentiation method support for a tape. - - Raise ``QuantumFunctionError`` if ``tape`` contains not supported measurements, - observables, or operations by the Lightning adjoint differentiation method. + return tuple(zip(*results)) + def supports_vjp( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[QuantumTape] = None, + ) -> bool: + """Whether or not this device defines a custom vector jacobian product. + ``LightningKokkos`` supports adjoint differentiation with analytic results. Args: - tape (.QuantumTape): quantum tape to differentiate. - """ - for operation in operations: - if operation.num_params > 1 and not isinstance(operation, Rot): - raise QuantumFunctionError( - f"The {operation.name} operation is not supported using " - 'the "adjoint" differentiation method' - ) - - def _init_process_jacobian_tape(self, tape, starting_state, use_device_state): - """Generate an initial state vector for ``_process_jacobian_tape``.""" - if starting_state is not None: - if starting_state.size != 2 ** len(self.wires): - raise QuantumFunctionError( - "The number of qubits of starting_state must be the same as " - "that of the device." - ) - self._apply_state_vector(starting_state, self.wires) - elif not use_device_state: - self.reset() - self.apply(tape.operations) - return self.state_vector - - def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): - """Implements the adjoint method outlined in - `Jones and Gacon `__ to differentiate an input tape. - - After a forward pass, the circuit is reversed by iteratively applying adjoint - gates to scan backwards through the circuit. + execution_config (ExecutionConfig): The configuration of the desired derivative calculation + circuit (QuantumTape): An optional circuit to check derivatives support for. + Returns: + Bool: Whether or not a derivative can be calculated provided the given information """ - if self.shots is not None: - warn( - "Requested adjoint differentiation to be computed with finite shots." - " The derivative is always exact when using the adjoint " - "differentiation method.", - UserWarning, - ) - - tape_return_type = self._check_adjdiff_supported_measurements(tape.measurements) - - if not tape_return_type: # the tape does not have measurements - return np.array([], dtype=self.state.dtype) - - if tape_return_type is State: # pragma: no cover - raise QuantumFunctionError( - "Adjoint differentiation method does not support measurement StateMP." - "Use vjp method instead for this purpose." - ) - - self._check_adjdiff_supported_operations(tape.operations) - - processed_data = self._process_jacobian_tape(tape, starting_state, use_device_state) - - if not processed_data: # training_params is empty - return np.array([], dtype=self.state.dtype) - - trainable_params = processed_data["tp_shift"] - - # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. - # This will allow use of Lightning with adjoint for large-qubit numbers AND large - # numbers of observables, enabling choice between compute time and memory use. - requested_threads = int(getenv("OMP_NUM_THREADS", "1")) - - adjoint_jacobian = AdjointJacobianC64() if self.use_csingle else AdjointJacobianC128() - - if self._batch_obs and requested_threads > 1: # pragma: no cover - obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) - jac = [] - for obs_chunk in obs_partitions: - jac_local = adjoint_jacobian( - processed_data["state_vector"], - obs_chunk, - processed_data["ops_serialized"], - trainable_params, - ) - jac.extend(jac_local) - else: - jac = adjoint_jacobian( - processed_data["state_vector"], - processed_data["obs_serialized"], - processed_data["ops_serialized"], - trainable_params, - ) - jac = np.array(jac) - jac = jac.reshape(-1, len(trainable_params)) - jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) - jac_r[:, processed_data["record_tp_rows"]] = jac - if hasattr(qml, "active_return"): # pragma: no cover - return self._adjoint_jacobian_processing(jac_r) if qml.active_return() else jac_r - return self._adjoint_jacobian_processing(jac_r) - - # pylint: disable=inconsistent-return-statements, line-too-long - def vjp(self, measurements, grad_vec, starting_state=None, use_device_state=False): - """Generate the processing function required to compute the vector-Jacobian products - of a tape. - - This function can be used with multiple expectation values or a quantum state. - When a quantum state is given, - - .. code-block:: python - - vjp_f = dev.vjp([qml.state()], grad_vec) - vjp = vjp_f(tape) - - computes :math:`w = (w_1,\\cdots,w_m)` where + return self.supports_derivatives(execution_config, circuit) + def compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + r"""The vector jacobian product used in reverse-mode differentiation. ``LightningKokkos`` uses the + adjoint differentiation method to compute the VJP. + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + of numbers. + execution_config (ExecutionConfig): a datastructure with all additional information required for execution + Returns: + tensor-like: A numeric result of computing the vector jacobian product + **Definition of vjp:** + If we have a function with jacobian: .. math:: + \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} + The vector jacobian product is the inner product of the derivatives of the output ``y`` with the + Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. + .. math:: + \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} + **Shape of cotangents:** + The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, + the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following + shapes. ``batch_size`` below refers to the number of entries in the Jacobian: + * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` + * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, + then the shape must be ``(batch_size,)``. + """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + return tuple( + vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit, cots in zip(circuits, cotangents) + ) - w_k = \\langle v| \\frac{\\partial}{\\partial \\theta_k} | \\psi_{\\pmb{\\theta}} \\rangle. - - Here, :math:`m` is the total number of trainable parameters, - :math:`\\pmb{\\theta}` is the vector of trainable parameters and - :math:`\\psi_{\\pmb{\\theta}}` is the output quantum state. - + def execute_and_compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. Args: - measurements (list): List of measurement processes for vector-Jacobian product. - Now it must be expectation values or a quantum state. - grad_vec (tensor_like): Gradient-output vector. Must have shape matching the output - shape of the corresponding tape, i.e. number of measurements if the return - type is expectation or :math:`2^N` if the return type is statevector - starting_state (tensor_like): post-forward pass state to start execution with. - It should be complex-valued. Takes precedence over ``use_device_state``. - use_device_state (bool): use current device state to initialize. - A forward pass of the same circuit should be the last thing the device - has executed. If a ``starting_state`` is provided, that takes precedence. - + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + of numbers. + execution_config (ExecutionConfig): a datastructure with all additional information required for execution Returns: - The processing function required to compute the vector-Jacobian products of a tape. + Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product """ - if self.shots is not None: - warn( - "Requested adjoint differentiation to be computed with finite shots." - " The derivative is always exact when using the adjoint " - "differentiation method.", - UserWarning, + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + simulate_and_vjp( + circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map ) - - tape_return_type = self._check_adjdiff_supported_measurements(measurements) - - if math.allclose(grad_vec, 0) or tape_return_type is None: - return lambda tape: math.convert_like(np.zeros(len(tape.trainable_params)), grad_vec) - - if tape_return_type is Expectation: - if len(grad_vec) != len(measurements): - raise ValueError( - "Number of observables in the tape must be the same as the " - "length of grad_vec in the vjp method" - ) - - if np.iscomplexobj(grad_vec): - raise ValueError( - "The vjp method only works with a real-valued grad_vec when " - "the tape is returning an expectation value" - ) - - ham = qml.Hamiltonian(grad_vec, [m.obs for m in measurements]) - - # pylint: disable=protected-access - def processing_fn(tape): - nonlocal ham - num_params = len(tape.trainable_params) - - if num_params == 0: - return np.array([], dtype=self.state.dtype) - - new_tape = tape.copy() - new_tape._measurements = [qml.expval(ham)] - - return self.adjoint_jacobian(new_tape, starting_state, use_device_state) - - return processing_fn + for circuit, cots in zip(circuits, cotangents) + ) + return tuple(zip(*results)) diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index ed161259e..f79c671ca 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -26,6 +26,9 @@ from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1d0bac988..444970ec9 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -33,6 +33,9 @@ from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_measurements_samples_MCMC.py b/tests/lightning_qubit/test_measurements_samples_MCMC.py index 562c705db..477f7dc06 100644 --- a/tests/lightning_qubit/test_measurements_samples_MCMC.py +++ b/tests/lightning_qubit/test_measurements_samples_MCMC.py @@ -20,6 +20,9 @@ from conftest import LightningDevice as ld from conftest import device_name +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index d192c8c34..dfd204fba 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -26,6 +26,9 @@ from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index d318795aa..7321e52ac 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -42,6 +42,26 @@ validate_observables, ) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( + _add_adjoint_transforms, + _supports_adjoint, + accepted_observables, + adjoint_measurements, + adjoint_observables, + decompose, + mid_circuit_measurements, + no_sampling, + stopping_condition, + stopping_condition_shots, + validate_adjoint_trainable_params, + validate_device_wires, + validate_measurements, + validate_observables, + ) + + if device_name == "lightning.tensor": from pennylane_lightning.lightning_tensor.lightning_tensor import ( accepted_observables, @@ -119,7 +139,7 @@ def test_add_adjoint_transforms(self): """Test that the correct transforms are added to the program by _add_adjoint_transforms""" expected_program = qml.transforms.core.TransformProgram() - name = "adjoint + lightning.qubit" + name = f"adjoint + {device_name}" expected_program.add_transform(no_sampling, name=name) expected_program.add_transform( decompose, diff --git a/tests/new_api/test_expval.py b/tests/new_api/test_expval.py index 22e15dd26..09fd82e1c 100644 --- a/tests/new_api/test_expval.py +++ b/tests/new_api/test_expval.py @@ -21,6 +21,9 @@ from conftest import PHI, THETA, VARPHI, LightningDevice, device_name from pennylane.devices import DefaultQubit +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/new_api/test_var.py b/tests/new_api/test_var.py index 8feb620c8..f00d2f408 100644 --- a/tests/new_api/test_var.py +++ b/tests/new_api/test_var.py @@ -25,6 +25,9 @@ if device_name == "lightning.tensor": pytest.skip("lightning.tensor does not support qml.var()", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index e7dc7e252..9420cd1c8 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -26,6 +26,9 @@ from pennylane import qchem, qnode from scipy.stats import unitary_group +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + I, X, Y, Z = ( np.eye(2), qml.X.compute_matrix(), diff --git a/tests/test_apply.py b/tests/test_apply.py index 9b89c60f4..c140674bf 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -29,6 +29,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) @pytest.mark.skipif( ld._new_API or device_name == "lightning.tensor", diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 6cacca8c8..8816896b1 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -27,6 +27,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) def lightning_backend_dev(wires): """Loads the lightning backend""" diff --git a/tests/test_device.py b/tests/test_device.py index 0619546ae..9e086fe85 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -26,6 +26,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) def test_create_device(): dev = qml.device(device_name, wires=1) diff --git a/tests/test_execute.py b/tests/test_execute.py index 02a6bfa1f..2c0ab49c2 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -24,6 +24,8 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_expval.py b/tests/test_expval.py index dfdc2a72d..2367ebb11 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -26,6 +26,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) class TestExpval: diff --git a/tests/test_gates.py b/tests/test_gates.py index d4e99ebad..972c7dcf5 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -26,6 +26,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) if device_name == "lightning.tensor": pytest.skip( diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 927354dd1..d021f8cfb 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -28,6 +28,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) @pytest.mark.skipif(ld._new_API, reason="Old API required") def test_measurements(): diff --git a/tests/test_measurements_sparse.py b/tests/test_measurements_sparse.py index 16c69903c..882c77a89 100644 --- a/tests/test_measurements_sparse.py +++ b/tests/test_measurements_sparse.py @@ -24,6 +24,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index 6b4fa698c..261a165cc 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -22,6 +22,9 @@ from flaky import flaky from pennylane._device import DeviceError +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + if device_name not in ("lightning.qubit", "lightning.kokkos"): pytest.skip("Native MCM not supported. Skipping.", allow_module_level=True) diff --git a/tests/test_templates.py b/tests/test_templates.py index 69dc85f6f..18c1a7d90 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -26,6 +26,8 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) class TestGrover: """Test Grover's algorithm (multi-controlled gates, decomposition, etc.)""" diff --git a/tests/test_var.py b/tests/test_var.py index 560151eca..ff11d8294 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -23,6 +23,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) if device_name == "lightning.tensor": pytest.skip("lightning.tensor doesn't support var.", allow_module_level=True) diff --git a/tests/test_vjp.py b/tests/test_vjp.py index 63ec51664..7d30293a3 100644 --- a/tests/test_vjp.py +++ b/tests/test_vjp.py @@ -24,6 +24,8 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) +if device_name == "lightning.kokkos": + pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) if device_name == "lightning.tensor": pytest.skip("lightning.tensor doesn't support vjp.", allow_module_level=True) From fd0221403d74b442709963647842a5a7d6f84394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 23 Jul 2024 15:52:50 -0400 Subject: [PATCH 010/130] Apply format --- tests/lightning_qubit/test_adjoint_jacobian_class.py | 2 +- tests/lightning_qubit/test_measurements_class.py | 2 +- tests/lightning_qubit/test_measurements_samples_MCMC.py | 2 +- tests/lightning_qubit/test_state_vector_class.py | 2 +- tests/new_api/test_device.py | 2 +- tests/new_api/test_expval.py | 2 +- tests/new_api/test_var.py | 2 +- tests/test_adjoint_jacobian.py | 2 +- tests/test_apply.py | 3 ++- tests/test_comparison.py | 3 ++- tests/test_device.py | 3 ++- tests/test_execute.py | 3 ++- tests/test_expval.py | 3 ++- tests/test_gates.py | 2 +- tests/test_measurements.py | 3 ++- tests/test_measurements_sparse.py | 3 ++- tests/test_native_mcm.py | 2 +- tests/test_templates.py | 3 ++- tests/test_var.py | 2 +- tests/test_vjp.py | 2 +- 20 files changed, 28 insertions(+), 20 deletions(-) diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index f79c671ca..3562e59ea 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -27,7 +27,7 @@ from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 444970ec9..7fc142973 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -34,7 +34,7 @@ from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_measurements_samples_MCMC.py b/tests/lightning_qubit/test_measurements_samples_MCMC.py index 477f7dc06..12edaae0f 100644 --- a/tests/lightning_qubit/test_measurements_samples_MCMC.py +++ b/tests/lightning_qubit/test_measurements_samples_MCMC.py @@ -21,7 +21,7 @@ from conftest import device_name if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index dfd204fba..0cc8fe303 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -27,7 +27,7 @@ from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name != "lightning.qubit": pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 7321e52ac..bcd70a4fb 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -43,7 +43,7 @@ ) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( _add_adjoint_transforms, _supports_adjoint, diff --git a/tests/new_api/test_expval.py b/tests/new_api/test_expval.py index 09fd82e1c..1e205836c 100644 --- a/tests/new_api/test_expval.py +++ b/tests/new_api/test_expval.py @@ -22,7 +22,7 @@ from pennylane.devices import DefaultQubit if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/new_api/test_var.py b/tests/new_api/test_var.py index f00d2f408..c7d5442bc 100644 --- a/tests/new_api/test_var.py +++ b/tests/new_api/test_var.py @@ -26,7 +26,7 @@ pytest.skip("lightning.tensor does not support qml.var()", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 9420cd1c8..1ece8ff15 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -27,7 +27,7 @@ from scipy.stats import unitary_group if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) I, X, Y, Z = ( np.eye(2), diff --git a/tests/test_apply.py b/tests/test_apply.py index c140674bf..b95052ad3 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -30,7 +30,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + @pytest.mark.skipif( ld._new_API or device_name == "lightning.tensor", diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 8816896b1..2cb3a4db9 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -28,7 +28,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + def lightning_backend_dev(wires): """Loads the lightning backend""" diff --git a/tests/test_device.py b/tests/test_device.py index 9e086fe85..60577dd9e 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -27,7 +27,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + def test_create_device(): dev = qml.device(device_name, wires=1) diff --git a/tests/test_execute.py b/tests/test_execute.py index 2c0ab49c2..7c4cd05b4 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -25,7 +25,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_expval.py b/tests/test_expval.py index 2367ebb11..5d48050f3 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -27,7 +27,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) class TestExpval: diff --git a/tests/test_gates.py b/tests/test_gates.py index 972c7dcf5..c0694ba0f 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -27,7 +27,7 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name == "lightning.tensor": pytest.skip( diff --git a/tests/test_measurements.py b/tests/test_measurements.py index d021f8cfb..067046c75 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -29,7 +29,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + @pytest.mark.skipif(ld._new_API, reason="Old API required") def test_measurements(): diff --git a/tests/test_measurements_sparse.py b/tests/test_measurements_sparse.py index 882c77a89..21f78a9ff 100644 --- a/tests/test_measurements_sparse.py +++ b/tests/test_measurements_sparse.py @@ -25,7 +25,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index 261a165cc..ae4cebea1 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -23,7 +23,7 @@ from pennylane._device import DeviceError if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name not in ("lightning.qubit", "lightning.kokkos"): pytest.skip("Native MCM not supported. Skipping.", allow_module_level=True) diff --git a/tests/test_templates.py b/tests/test_templates.py index 18c1a7d90..b3e02d251 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -27,7 +27,8 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + class TestGrover: """Test Grover's algorithm (multi-controlled gates, decomposition, etc.)""" diff --git a/tests/test_var.py b/tests/test_var.py index ff11d8294..a2dad6d70 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -24,7 +24,7 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name == "lightning.tensor": pytest.skip("lightning.tensor doesn't support var.", allow_module_level=True) diff --git a/tests/test_vjp.py b/tests/test_vjp.py index 7d30293a3..6c9939d3c 100644 --- a/tests/test_vjp.py +++ b/tests/test_vjp.py @@ -25,7 +25,7 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) + pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name == "lightning.tensor": pytest.skip("lightning.tensor doesn't support vjp.", allow_module_level=True) From 14b496419e06360a379419212419d59866816d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 24 Jul 2024 10:27:23 -0400 Subject: [PATCH 011/130] apply format --- .../lightning_kokkos/lightning_kokkos.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 60d7db64f..1ae668bb0 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -46,10 +46,7 @@ try: # pylint: disable=import-error, no-name-in-module - from pennylane_lightning.lightning_kokkos_ops import ( - backend_info, - print_configuration, - ) + from pennylane_lightning.lightning_kokkos_ops import backend_info, print_configuration LK_CPP_BINARY_AVAILABLE = True except ImportError: @@ -228,6 +225,7 @@ def simulate_and_vjp( # pylint: disable=unused-argument # _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) # return res, _vjp + _operations = frozenset( { "Identity", @@ -314,6 +312,7 @@ def simulate_and_vjp( # pylint: disable=unused-argument ) # The set of supported observables. + def stopping_condition(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" # These thresholds are adapted from `lightning_base.py` @@ -411,9 +410,11 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: program.add_transform(qml.transforms.broadcast_expand) program.add_transform(validate_adjoint_trainable_params) + def _kokkos_configuration(): return print_configuration() + @simulator_tracking @single_tape_support class LightningKokkos(Device): @@ -436,7 +437,9 @@ class LightningKokkos(Device): to ``None`` results in computing statistics like expectation values and variances analytically. """ - + + # pylint: disable=too-many-instance-attributes + _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") _new_API = True @@ -510,14 +513,13 @@ def __init__( # pylint: disable=too-many-arguments else: self._kernel_name = None self._num_burnin = 0 - + # Kokkos specific options self._kokkos_args = kokkos_args self._sync = sync if not LightningKokkos.kokkos_config: LightningKokkos.kokkos_config = _kokkos_configuration() - @property def name(self): """The name of the device.""" From 04283f9425e89d8dd7960b2511e326446291323e Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 24 Jul 2024 15:08:11 +0000 Subject: [PATCH 012/130] Auto update version from '0.38.0-dev13' to '0.38.0-dev14' --- 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 c1ad83f4a..1e0a3eaed 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev13" +__version__ = "0.38.0-dev14" From 8d40c141297c115d247ab47a7595fe6ef3530dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 24 Jul 2024 15:26:30 -0400 Subject: [PATCH 013/130] Apply suggestions from code review Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- .../lightning_kokkos/_state_vector.py | 6 +++--- .../lightning_kokkos/lightning_kokkos.py | 21 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index de2f092ea..387b29a53 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Class implementation for state-vector manipulation. +Class implementation for lightning_kokkos state-vector manipulation. """ -class LightningStateVector: # pylint: disable=too-few-public-methods - """Lightning state-vector class. +class LightningKokkosStateVector: # pylint: disable=too-few-public-methods + """Lightning Kokkos state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 1ae668bb0..62db335df 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -61,7 +61,7 @@ def simulate( # pylint: disable=unused-argument circuit: QuantumScript, - state: LightningStateVector, + state: LightningKokkosStateVector, mcmc: dict = None, postselect_mode: str = None, ) -> Result: @@ -70,9 +70,6 @@ def simulate( # pylint: disable=unused-argument Args: circuit (QuantumTape): The single circuit to simulate state (LightningStateVector): handle to Lightning state vector - mcmc (dict): Dictionary containing the Markov Chain Monte Carlo - parameters: mcmc, kernel_name, num_burnin. Descriptions of - these fields are found in :class:`~.LightningKokkos`. postselect_mode (str): Configuration for handling shots with mid-circuit measurement postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to keep the same number of shots. Default is ``None``. @@ -112,7 +109,7 @@ def simulate( # pylint: disable=unused-argument def jacobian( # pylint: disable=unused-argument - circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None + circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None ): """Compute the Jacobian for a single quantum script. @@ -136,13 +133,13 @@ def jacobian( # pylint: disable=unused-argument def simulate_and_jacobian( # pylint: disable=unused-argument - circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None + circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None ): """Simulate a single quantum script and compute its Jacobian. Args: circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector + state (LightningKokkosStateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning kokkos is built with OpenMP. Default is False. @@ -164,7 +161,7 @@ def simulate_and_jacobian( # pylint: disable=unused-argument def vjp( # pylint: disable=unused-argument circuit: QuantumTape, cotangents: Tuple[Number], - state: LightningStateVector, + state: LightningKokkosStateVector, batch_obs=False, wire_map=None, ): @@ -175,7 +172,7 @@ def vjp( # pylint: disable=unused-argument have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (LightningStateVector): handle to Lightning state vector + state (LightningKokkosStateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the VJP. This value is only relevant when the lightning kokkos is built with OpenMP. @@ -197,7 +194,7 @@ def vjp( # pylint: disable=unused-argument def simulate_and_vjp( # pylint: disable=unused-argument circuit: QuantumTape, cotangents: Tuple[Number], - state: LightningStateVector, + state: LightningKokkosStateVector, batch_obs=False, wire_map=None, ): @@ -208,7 +205,7 @@ def simulate_and_vjp( # pylint: disable=unused-argument have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (LightningStateVector): handle to Lightning state vector + state (LightningKokkosStateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning kokkos is built with OpenMP. @@ -464,8 +461,6 @@ def __init__( # pylint: disable=too-many-arguments *, c_dtype=np.complex128, shots=None, - seed="global", - mcmc=False, kernel_name="Local", num_burnin=100, batch_obs=False, From 44751ee0cfee1425835406a60132e6eacdc15abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 24 Jul 2024 15:29:06 -0400 Subject: [PATCH 014/130] Fix spelling error Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 62db335df..9be3a429c 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -115,7 +115,7 @@ def jacobian( # pylint: disable=unused-argument Args: circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector + state (LightningKokkosStateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning kokkos is built with OpenMP. Default is False. From d17059d0782b10a3c6d5bf9895437aa0f326529e Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 24 Jul 2024 19:30:12 +0000 Subject: [PATCH 015/130] Auto update version from '0.38.0-dev14' to '0.38.0-dev17' --- 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 1e0a3eaed..8240b085d 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev14" +__version__ = "0.38.0-dev17" From 79b6bbe6fa13089d569eab8f5d5816edb5737b92 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 24 Jul 2024 20:15:55 +0000 Subject: [PATCH 016/130] Auto update version from '0.38.0-dev16' to '0.38.0-dev17' --- 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 25c726385..8240b085d 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev16" +__version__ = "0.38.0-dev17" From f34100baf50026832724aea65fc6f40fab803410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 24 Jul 2024 16:20:01 -0400 Subject: [PATCH 017/130] Delete the unnecessary code for the backend --- .../lightning_kokkos/_state_vector.py | 2 +- .../lightning_kokkos/lightning_kokkos.py | 253 ++---------------- 2 files changed, 20 insertions(+), 235 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 387b29a53..e54ff76fb 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -25,7 +25,7 @@ class LightningKokkosStateVector: # pylint: disable=too-few-public-methods 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``. Default is ``np.complex128`` - device_name(string): state vector device name. Options: ["lightning.qubit"] + device_name(string): state vector device name. Options: ["lightning.kokkos"] """ pass # pylint: disable=unnecessary-pass diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 9be3a429c..a8921813f 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -16,7 +16,6 @@ This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that interfaces with C++ for fast linear algebra calculations. """ -from dataclasses import replace from numbers import Number from pathlib import Path from typing import Callable, Optional, Sequence, Tuple, Union @@ -24,25 +23,13 @@ import numpy as np import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig -from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support -from pennylane.devices.preprocess import ( - decompose, - mid_circuit_measurements, - no_sampling, - validate_adjoint_trainable_params, - validate_device_wires, - validate_measurements, - validate_observables, -) -from pennylane.measurements import MidMeasureMP -from pennylane.operation import DecompositionUndefinedError, Operator, Tensor -from pennylane.ops import Prod, SProd, Sum +from pennylane.operation import Operator from pennylane.tape import QuantumScript, QuantumTape from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -from ._state_vector import LightningStateVector +from ._state_vector import LightningKokkosStateVector try: # pylint: disable=import-error, no-name-in-module @@ -62,7 +49,6 @@ def simulate( # pylint: disable=unused-argument circuit: QuantumScript, state: LightningKokkosStateVector, - mcmc: dict = None, postselect_mode: str = None, ) -> Result: """Simulate a single quantum script. @@ -80,32 +66,6 @@ def simulate( # pylint: disable=unused-argument Note that this function can return measurements for non-commuting observables simultaneously. """ return 0 - # if mcmc is None: - # mcmc = {} - # state.reset_state() - # has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) - # if circuit.shots and has_mcm: - # results = [] - # aux_circ = qml.tape.QuantumScript( - # circuit.operations, - # circuit.measurements, - # shots=[1], - # trainable_params=circuit.trainable_params, - # ) - # for _ in range(circuit.shots.total_shots): - # state.reset_state() - # mid_measurements = {} - # final_state = state.get_final_state( - # aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode - # ) - # results.append( - # LightningMeasurements(final_state, **mcmc).measure_final_state( - # aux_circ, mid_measurements=mid_measurements - # ) - # ) - # return tuple(results) - # final_state = state.get_final_state(circuit) - # return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) def jacobian( # pylint: disable=unused-argument @@ -125,11 +85,6 @@ def jacobian( # pylint: disable=unused-argument TensorLike: The Jacobian of the quantum script """ return 0 - # if wire_map is not None: - # [circuit], _ = qml.map_wires(circuit, wire_map) - # state.reset_state() - # final_state = state.get_final_state(circuit) - # return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) def simulate_and_jacobian( # pylint: disable=unused-argument @@ -151,11 +106,6 @@ def simulate_and_jacobian( # pylint: disable=unused-argument Note that this function can return measurements for non-commuting observables simultaneously. """ return 0 - # if wire_map is not None: - # [circuit], _ = qml.map_wires(circuit, wire_map) - # res = simulate(circuit, state) - # jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) - # return res, jac def vjp( # pylint: disable=unused-argument @@ -182,13 +132,6 @@ def vjp( # pylint: disable=unused-argument TensorLike: The VJP of the quantum script """ return 0 - # if wire_map is not None: - # [circuit], _ = qml.map_wires(circuit, wire_map) - # state.reset_state() - # final_state = state.get_final_state(circuit) - # return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( - # circuit, cotangents - # ) def simulate_and_vjp( # pylint: disable=unused-argument @@ -216,11 +159,6 @@ def simulate_and_vjp( # pylint: disable=unused-argument Note that this function can return measurements for non-commuting observables simultaneously. """ return 0 - # if wire_map is not None: - # [circuit], _ = qml.map_wires(circuit, wire_map) - # res = simulate(circuit, state) - # _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) - # return res, _vjp _operations = frozenset( @@ -312,27 +250,13 @@ def simulate_and_vjp( # pylint: disable=unused-argument def stopping_condition(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" - # These thresholds are adapted from `lightning_base.py` - # To avoid building matrices beyond the given thresholds. - # This should reduce runtime overheads for larger systems. - if isinstance(op, qml.QFT): - return len(op.wires) < 10 - if isinstance(op, qml.GroverOperator): - return len(op.wires) < 13 - - # As ControlledQubitUnitary == C(QubitUnitrary), - # it can be removed from `_operations` to keep - # consistency with `lightning_kokkos.toml` - if isinstance(op, qml.ControlledQubitUnitary): - return True - - return op.name in _operations + return 0 def stopping_condition_shots(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.kokkos`` with finite shots.""" - return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional)) + return 0 def accepted_observables(obs: Operator) -> bool: @@ -343,40 +267,16 @@ def accepted_observables(obs: Operator) -> bool: def adjoint_observables(obs: Operator) -> bool: """A function that determines whether or not an observable is supported by ``lightning.kokkos`` when using the adjoint differentiation method.""" - if isinstance(obs, qml.Projector): - return False - - if isinstance(obs, Tensor): - if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs): - return False - return True - - if isinstance(obs, SProd): - return adjoint_observables(obs.base) - - if isinstance(obs, (Sum, Prod)): - return all(adjoint_observables(o) for o in obs) - - return obs.name in _observables + return 0 def adjoint_measurements(mp: qml.measurements.MeasurementProcess) -> bool: """Specifies whether or not an observable is compatible with adjoint differentiation on DefaultQubit.""" - return isinstance(mp, qml.measurements.ExpectationMP) + return 0 def _supports_adjoint(circuit): - if circuit is None: - return True - - prog = TransformProgram() - _add_adjoint_transforms(prog) - - try: - prog((circuit,)) - except (DecompositionUndefinedError, qml.DeviceError, AttributeError): - return False - return True + return 0 def _add_adjoint_transforms(program: TransformProgram) -> None: @@ -392,20 +292,7 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: """ name = "adjoint + lightning.kokkos" - program.add_transform(no_sampling, name=name) - program.add_transform( - decompose, - stopping_condition=adjoint_ops, - stopping_condition_shots=stopping_condition_shots, - name=name, - skip_initial_state_prep=False, - ) - program.add_transform(validate_observables, accepted_observables, name=name) - program.add_transform( - validate_measurements, analytic_measurements=adjoint_measurements, name=name - ) - program.add_transform(qml.transforms.broadcast_expand) - program.add_transform(validate_adjoint_trainable_params) + return 0 def _kokkos_configuration(): @@ -437,7 +324,7 @@ class LightningKokkos(Device): # pylint: disable=too-many-instance-attributes - _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") + _device_options = ("rng", "c_dtype", "batch_obs", "kernel_name") _new_API = True # Device specific options @@ -461,8 +348,6 @@ def __init__( # pylint: disable=too-many-arguments *, c_dtype=np.complex128, shots=None, - kernel_name="Local", - num_burnin=100, batch_obs=False, # Kokkos arguments sync=True, @@ -482,7 +367,7 @@ def __init__( # pylint: disable=too-many-arguments else: self._wire_map = {w: i for i, w in enumerate(self.wires)} - self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) + self._statevector = LightningKokkosStateVector(num_wires=len(self.wires), dtype=c_dtype) # TODO: Investigate usefulness of creating numpy random generator seed = np.random.randint(0, high=10000000) if seed == "global" else seed @@ -490,24 +375,6 @@ def __init__( # pylint: disable=too-many-arguments 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." - ) - shots = shots if isinstance(shots, Sequence) else [shots] - if any(num_burnin >= s for s in 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 = 0 # Kokkos specific options self._kokkos_args = kokkos_args @@ -531,20 +398,7 @@ def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. """ - 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) + return 0 def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): """This function defines the device transform program to be applied and an updated device configuration. @@ -565,30 +419,7 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) * Currently does not intrinsically support parameter broadcasting """ - exec_config = self._setup_execution_config(execution_config) - program = TransformProgram() - - program.add_transform(validate_measurements, name=self.name) - program.add_transform(validate_observables, accepted_observables, name=self.name) - program.add_transform(validate_device_wires, self.wires, name=self.name) - program.add_transform( - mid_circuit_measurements, - device=self, - mcm_config=exec_config.mcm_config, - interface=exec_config.interface, - ) - program.add_transform( - decompose, - stopping_condition=stopping_condition, - stopping_condition_shots=stopping_condition_shots, - skip_initial_state_prep=True, - name=self.name, - ) - program.add_transform(qml.transforms.broadcast_expand) - - if exec_config.gradient_method == "adjoint": - _add_adjoint_transforms(program) - return program, exec_config + return 0 # pylint: disable=unused-argument def execute( @@ -605,25 +436,7 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - mcmc = { - "mcmc": self._mcmc, - "kernel_name": self._kernel_name, - "num_burnin": self._num_burnin, - } - results = [] - for circuit in circuits: - if self._wire_map is not None: - [circuit], _ = qml.map_wires(circuit, self._wire_map) - results.append( - simulate( - circuit, - self._statevector, - mcmc=mcmc, - postselect_mode=execution_config.mcm_config.postselect_mode, - ) - ) - - return tuple(results) + return 0 def supports_derivatives( self, @@ -642,13 +455,7 @@ def supports_derivatives( Bool: Whether or not a derivative can be calculated provided the given information """ - 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 _supports_adjoint(circuit=circuit) + return 0 def compute_derivatives( self, @@ -664,12 +471,8 @@ def compute_derivatives( Returns: Tuple: The jacobian for each trainable parameter """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - return tuple( - jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit in circuits - ) + return 0 def execute_and_compute_derivatives( self, @@ -685,14 +488,7 @@ def execute_and_compute_derivatives( Returns: tuple: A numeric result of the computation and the gradient. """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_jacobian( - c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - ) - for c in circuits - ) - return tuple(zip(*results)) + return 0 def supports_vjp( self, @@ -707,7 +503,7 @@ def supports_vjp( Returns: Bool: Whether or not a derivative can be calculated provided the given information """ - return self.supports_derivatives(execution_config, circuit) + return 0 def compute_vjp( self, @@ -741,11 +537,7 @@ def compute_vjp( * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, then the shape must be ``(batch_size,)``. """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - return tuple( - vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit, cots in zip(circuits, cotangents) - ) + return 0 def execute_and_compute_vjp( self, @@ -763,11 +555,4 @@ def execute_and_compute_vjp( Returns: Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_vjp( - circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - ) - for circuit, cots in zip(circuits, cotangents) - ) - return tuple(zip(*results)) + return 0 From bcd323e9e682128344395064537ebb1625287514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 24 Jul 2024 16:24:48 -0400 Subject: [PATCH 018/130] Delete the unnecessary functions up to now --- .../lightning_kokkos/lightning_kokkos.py | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index a8921813f..e91722afb 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -24,9 +24,7 @@ import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig from pennylane.devices.modifiers import simulator_tracking, single_tape_support -from pennylane.operation import Operator from pennylane.tape import QuantumScript, QuantumTape -from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch from ._state_vector import LightningKokkosStateVector @@ -248,53 +246,6 @@ def simulate_and_vjp( # pylint: disable=unused-argument # The set of supported observables. -def stopping_condition(op: Operator) -> bool: - """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" - return 0 - - -def stopping_condition_shots(op: Operator) -> bool: - """A function that determines whether or not an operation is supported by ``lightning.kokkos`` - with finite shots.""" - return 0 - - -def accepted_observables(obs: Operator) -> bool: - """A function that determines whether or not an observable is supported by ``lightning.kokkos``.""" - return obs.name in _observables - - -def adjoint_observables(obs: Operator) -> bool: - """A function that determines whether or not an observable is supported by ``lightning.kokkos`` - when using the adjoint differentiation method.""" - return 0 - - -def adjoint_measurements(mp: qml.measurements.MeasurementProcess) -> bool: - """Specifies whether or not an observable is compatible with adjoint differentiation on DefaultQubit.""" - return 0 - - -def _supports_adjoint(circuit): - return 0 - - -def _add_adjoint_transforms(program: TransformProgram) -> None: - """Private helper function for ``preprocess`` that adds the transforms specific - for adjoint differentiation. - - Args: - program (TransformProgram): where we will add the adjoint differentiation transforms - - Side Effects: - Adds transforms to the input program. - - """ - - name = "adjoint + lightning.kokkos" - return 0 - - def _kokkos_configuration(): return print_configuration() From 39fb65f89b0343920d85cc5789041fed342833c0 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 26 Jul 2024 18:48:39 +0000 Subject: [PATCH 019/130] Auto update version from '0.38.0-dev18' to '0.38.0-dev19' --- 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 67dc4a35b..e08dc521d 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev18" +__version__ = "0.38.0-dev19" From 90d1d6e061b15b800ea841e8955312d8499080f3 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 29 Jul 2024 12:56:39 +0000 Subject: [PATCH 020/130] Auto update version from '0.38.0-dev19' to '0.38.0-dev20' --- 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 e08dc521d..2ee21e257 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev19" +__version__ = "0.38.0-dev20" From 80277d9893778247f18eeeb74d9a29c8e3375360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 30 Jul 2024 10:34:27 -0400 Subject: [PATCH 021/130] Add the simulate class for the new device (#817) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Before submitting Please complete the following checklist when submitting a PR: - [X] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [X] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [X] Ensure that the test suite passes, by running `make test`. - [ ] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [X] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** Migrate the lightning.kokkos device to the new device API. **Description of the Change:** The 'simulate' method is necessary to achieve full functionality with the new device API. We are going to follow the same recipe used with LightningQubit. **Benefits:** Add one of the essential methods for the new device API into LightningKokkos. **Possible Drawbacks:** **Related GitHub Issues:** ## Partial / Freezzed PR ⚠️ ❄️ To make a smooth integration of LightningKokkos with the new device API, we set the branch `kokkosNewAPI_backend` as the base branch target for this development. The branch `kokkosNewAPI_backend` has the mock of all classes and methods necessary for the new API. Also, several tests were disabled with ``` python if device_name == "lightning.kokkos": pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) ``` Additionally, the CI testing from PennyLane for LKokkos is temporally disabled through commenting the following lines in `.github/workflows` files ``` yml : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append ``` However, these tests will unblocked as the implementation progresses. After all the developments for integrating LightningKokkos with the new API have been completed then the PR will be open to merge to master [sc-68801] --------- Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .github/workflows/tests_lkcpu_python.yml | 4 +- .github/workflows/tests_lkcuda_python.yml | 7 +- .github/workflows/wheel_linux_aarch64.yml | 3 +- .github/workflows/wheel_linux_x86_64.yml | 3 +- .github/workflows/wheel_macos_arm64.yml | 3 +- .github/workflows/wheel_macos_x86_64.yml | 3 +- .../lightning_kokkos/_measurements.py | 463 ++++++++++++++++++ .../lightning_kokkos/_state_vector.py | 416 +++++++++++++++- .../lightning_kokkos/lightning_kokkos.py | 33 +- .../test_measurements_class.py | 34 +- tests/lightning_qubit/test_simulate_method.py | 139 ++++++ .../test_state_vector_class.py | 18 +- 12 files changed, 1094 insertions(+), 32 deletions(-) create mode 100644 pennylane_lightning/lightning_kokkos/_measurements.py create mode 100644 tests/lightning_qubit/test_simulate_method.py diff --git a/.github/workflows/tests_lkcpu_python.yml b/.github/workflows/tests_lkcpu_python.yml index 0e211f2e9..83c4193e4 100644 --- a/.github/workflows/tests_lkcpu_python.yml +++ b/.github/workflows/tests_lkcpu_python.yml @@ -249,8 +249,8 @@ jobs: PL_DEVICE=${DEVICENAME} python -m pytest tests/ $COVERAGE_FLAGS --splits 7 --group ${{ matrix.group }} \ --store-durations --durations-path='.github/workflows/python_lightning_kokkos_test_durations.json' --splitting-algorithm=least_duration mv .github/workflows/python_lightning_kokkos_test_durations.json ${{ github.workspace }}/.test_durations-${{ matrix.exec_model }}-${{ matrix.group }} - pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append - pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append + : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append + : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }}-${{ matrix.group }} - name: Upload test durations diff --git a/.github/workflows/tests_lkcuda_python.yml b/.github/workflows/tests_lkcuda_python.yml index 45b012175..2f5795ba5 100644 --- a/.github/workflows/tests_lkcuda_python.yml +++ b/.github/workflows/tests_lkcuda_python.yml @@ -254,8 +254,8 @@ jobs: cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS - pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append - pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append + : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append + : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml - name: Install all backend devices @@ -275,7 +275,8 @@ jobs: OMP_PROC_BIND: false run: | cd main/ - for device in lightning.qubit lightning.kokkos; do + # for device in lightning.qubit lightning.kokkos; do + for device in lightning.qubit; do pl-device-test --device ${device} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device ${device} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append done diff --git a/.github/workflows/wheel_linux_aarch64.yml b/.github/workflows/wheel_linux_aarch64.yml index ec090d9f7..9b938a6d6 100644 --- a/.github/workflows/wheel_linux_aarch64.yml +++ b/.github/workflows/wheel_linux_aarch64.yml @@ -92,7 +92,8 @@ jobs: matrix: os: [ubuntu-latest] arch: [aarch64] - pl_backend: ["lightning_kokkos", "lightning_qubit"] + : # pl_backend: ["lightning_kokkos", "lightning_qubit"] + pl_backend: ["lightning_qubit"] cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/.github/workflows/wheel_linux_x86_64.yml b/.github/workflows/wheel_linux_x86_64.yml index f91ff34e8..c3b066146 100644 --- a/.github/workflows/wheel_linux_x86_64.yml +++ b/.github/workflows/wheel_linux_x86_64.yml @@ -100,7 +100,8 @@ jobs: fail-fast: false matrix: arch: [x86_64] - pl_backend: ["lightning_kokkos", "lightning_qubit"] + : # pl_backend: ["lightning_kokkos", "lightning_qubit"] + pl_backend: ["lightning_qubit"] cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/.github/workflows/wheel_macos_arm64.yml b/.github/workflows/wheel_macos_arm64.yml index 5b1b20d3d..7c30b0eb1 100644 --- a/.github/workflows/wheel_macos_arm64.yml +++ b/.github/workflows/wheel_macos_arm64.yml @@ -60,7 +60,8 @@ jobs: matrix: os: [macos-12] arch: [arm64] - pl_backend: ["lightning_kokkos", "lightning_qubit"] + : # pl_backend: ["lightning_kokkos", "lightning_qubit"] + pl_backend: ["lightning_qubit"] cibw_build: ${{fromJson(needs.mac-set-matrix-arm.outputs.python_version)}} timeout-minutes: 30 name: macos-latest::arm64 - ${{ matrix.pl_backend }} (Python ${{ fromJson('{ "cp39-*":"3.9","cp310-*":"3.10","cp311-*":"3.11","cp312-*":"3.12" }')[matrix.cibw_build] }}) diff --git a/.github/workflows/wheel_macos_x86_64.yml b/.github/workflows/wheel_macos_x86_64.yml index f3aa0d900..a1e6b2395 100644 --- a/.github/workflows/wheel_macos_x86_64.yml +++ b/.github/workflows/wheel_macos_x86_64.yml @@ -95,7 +95,8 @@ jobs: matrix: os: [macos-12] arch: [x86_64] - pl_backend: ["lightning_kokkos", "lightning_qubit"] + : # pl_backend: ["lightning_kokkos", "lightning_qubit"] + pl_backend: ["lightning_qubit"] cibw_build: ${{fromJson(needs.set_wheel_build_matrix.outputs.python_version)}} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py new file mode 100644 index 000000000..23dc3aa70 --- /dev/null +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -0,0 +1,463 @@ +# 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. +""" + +# pylint: disable=import-error, no-name-in-module, ungrouped-imports +try: + from pennylane_lightning.lightning_kokkos_ops import MeasurementsC64, MeasurementsC128 +except ImportError: + pass + +from typing import Callable, List, Union + +import numpy as np +import pennylane as qml +from pennylane.devices.qubit.sampling import _group_measurements +from pennylane.measurements import ( + ClassicalShadowMP, + CountsMP, + ExpectationMP, + MeasurementProcess, + ProbabilityMP, + SampleMeasurement, + ShadowExpvalMP, + Shots, + StateMeasurement, + VarianceMP, +) +from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum +from pennylane.tape import QuantumScript +from pennylane.typing import Result, TensorLike +from pennylane.wires import Wires + +from pennylane_lightning.core._serialize import QuantumScriptSerializer + + +class LightningKokkosMeasurements: + """Lightning Kokkos Measurements class + + Measures the state provided by the LightningKokkosStateVector class. + + Args: + qubit_state(LightningKokkosStateVector): Lightning state-vector class containing the state vector to be measured. + """ + + def __init__( + self, + kokkos_state, + ) -> None: + self._qubit_state = kokkos_state + self._dtype = kokkos_state.dtype + self._measurement_lightning = self._measurement_dtype()(kokkos_state.state_vector) + + @property + def qubit_state(self): + """Returns a handle to the LightningKokkosStateVector object.""" + return self._qubit_state + + @property + def dtype(self): + """Returns the simulation data type.""" + return self._dtype + + def _measurement_dtype(self): + """Binding to Lightning Kokkos Measurements C++ class. + + Returns: the Measurements 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 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 + """ + diagonalizing_gates = measurementprocess.diagonalizing_gates() + self._qubit_state.apply_operations(diagonalizing_gates) + state_array = self._qubit_state.state + wires = Wires(range(self._qubit_state.num_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): + """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 isinstance(measurementprocess.obs, qml.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 ( + isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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.expval(ob_serialized) + + return self._measurement_lightning.expval( + measurementprocess.obs.name, measurementprocess.obs.wires + ) + + 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: + self._qubit_state.apply_operations(diagonalizing_gates) + results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) + if diagonalizing_gates: + self._qubit_state.apply_operations( + [qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)] + ) + return results + + 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 isinstance(measurementprocess.obs, qml.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 ( + isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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]: + """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 isinstance(measurementprocess.obs, (qml.Identity, qml.Projector)): + return self.state_diagonalizing_gates + return self.expval + + if isinstance(measurementprocess, ProbabilityMP): + return self.probs + + if isinstance(measurementprocess, VarianceMP): + if isinstance(measurementprocess.obs, (qml.Identity, qml.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 + + 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, mid_measurements=None) -> 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.kokkos``. + + Args: + circuit (QuantumScript): The single circuit to simulate + mid_measurements (None, dict): Dictionary of mid-circuit measurements + + Returns: + Tuple[TensorLike]: The measurement results + """ + + if not circuit.shots: + # analytic case + if len(circuit.measurements) == 1: + return self.measurement(circuit.measurements[0]) + + return tuple(self.measurement(mp) for mp in circuit.measurements) + + # finite-shot case + results = self.measure_with_samples( + circuit.measurements, + shots=circuit.shots, + mid_measurements=mid_measurements, + ) + + if len(circuit.measurements) == 1: + if circuit.shots.has_partitioned_shots: + return tuple(res[0] for res in results) + + return results[0] + + return results + + def measure_with_samples( + self, + measurements: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], + shots: Shots, + mid_measurements=None, + ) -> List[TensorLike]: + """ + Returns the samples of the measurement process performed on the given state. + This function assumes that the user-defined wire labels in the measurement process + have already been mapped to integer wires used in the device. + + Args: + measurements (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]): + The sample measurements to perform + shots (Shots): The number of samples to take + mid_measurements (None, dict): Dictionary of mid-circuit measurements + + Returns: + List[TensorLike[Any]]: Sample measurement results + """ + # last N measurements are sampling MCMs in ``dynamic_one_shot`` execution mode + mps = measurements[0 : -len(mid_measurements)] if mid_measurements else measurements + groups, indices = _group_measurements(mps) + + all_res = [] + for group in groups: + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( + group[0].obs, SparseHamiltonian + ): + raise TypeError( + "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." + ) + if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, (Hamiltonian, Sum)): + raise TypeError("VarianceMP(Hamiltonian/Sum) cannot be computed with samples.") + if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)): + raise TypeError( + "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." + ) + if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): + all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) + elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): + all_res.extend(self._measure_sum_with_samples(group, shots)) + else: + all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) + + # reorder results + flat_indices = [] + for row in indices: + flat_indices += row + sorted_res = tuple( + res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]]) + ) + + # append MCM samples + if mid_measurements: + sorted_res += tuple(mid_measurements.values()) + + # put the shot vector axis before the measurement axis + if shots.has_partitioned_shots: + sorted_res = tuple(zip(*sorted_res)) + + return sorted_res + + def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False): + if len(mps) == 1: + diagonalizing_gates = mps[0].diagonalizing_gates() + elif all(mp.obs for mp in mps): + diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0] + else: + diagonalizing_gates = [] + + if adjoint: + diagonalizing_gates = [ + qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates) + ] + + self._qubit_state.apply_operations(diagonalizing_gates) + + def _measure_with_samples_diagonalizing_gates( + self, + mps: List[SampleMeasurement], + shots: Shots, + ) -> TensorLike: + """ + Returns the samples of the measurement process performed on the given state, + by rotating the state into the measurement basis using the diagonalizing gates + given by the measurement process. + + Args: + mps (~.measurements.SampleMeasurement): The sample measurements to perform + shots (~.measurements.Shots): The number of samples to take + + Returns: + TensorLike[Any]: Sample measurement results + """ + # apply diagonalizing gates + self._apply_diagonalizing_gates(mps) + + # ---------------------------------------- + # Original: + # if self._mcmc: + # total_indices = self._qubit_state.num_wires + # wires = qml.wires.Wires(range(total_indices)) + # else: + # wires = reduce(sum, (mp.wires for mp in mps)) + + # Specific for Kokkos: + total_indices = self._qubit_state.num_wires + wires = qml.wires.Wires(range(total_indices)) + # ---------------------------------------- + + def _process_single_shot(samples): + processed = [] + for mp in mps: + res = mp.process_samples(samples, wires) + if not isinstance(mp, CountsMP): + res = qml.math.squeeze(res) + + processed.append(res) + + return tuple(processed) + + try: + # ---------------------------------------- + # Original: + # if self._mcmc: + # samples = self._measurement_lightning.generate_mcmc_samples( + # len(wires), self._kernel_name, self._num_burnin, shots.total_shots + # ).astype(int, copy=False) + # else: + # samples = self._measurement_lightning.generate_samples( + # list(wires), shots.total_shots + # ).astype(int, copy=False) + + # Specific for Kokkos: + samples = self._measurement_lightning.generate_samples( + len(wires), shots.total_shots + ).astype(int, copy=False) + # ---------------------------------------- + + except ValueError as e: + if str(e) != "probabilities contain NaN": + raise e + samples = qml.math.full((shots.total_shots, len(wires)), 0) + + self._apply_diagonalizing_gates(mps, adjoint=True) + + # if there is a shot vector, use the shots.bins generator to + # split samples w.r.t. the shots + processed_samples = [] + for lower, upper in shots.bins(): + result = _process_single_shot(samples[..., lower:upper, :]) + processed_samples.append(result) + + return ( + tuple(zip(*processed_samples)) if shots.has_partitioned_shots else processed_samples[0] + ) + + def _measure_hamiltonian_with_samples( + self, + mp: List[SampleMeasurement], + shots: Shots, + ): + # the list contains only one element based on how we group measurements + mp = mp[0] + + # if the measurement process involves a Hamiltonian, measure each + # of the terms separately and sum + def _sum_for_single_shot(s): + results = self.measure_with_samples( + [ExpectationMP(t) for t in mp.obs.terms()[1]], + s, + ) + return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) + + unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) + return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] + + def _measure_sum_with_samples( + self, + mp: List[SampleMeasurement], + shots: Shots, + ): + # the list contains only one element based on how we group measurements + mp = mp[0] + + # if the measurement process involves a Sum, measure each + # of the terms separately and sum + def _sum_for_single_shot(s): + results = self.measure_with_samples( + [ExpectationMP(t) for t in mp.obs], + s, + ) + return sum(results) + + unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) + return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index e54ff76fb..d1fd75a48 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -15,6 +15,32 @@ Class implementation for lightning_kokkos state-vector manipulation. """ +try: + from pennylane_lightning.lightning_kokkos_ops import ( + InitializationSettings, + StateVectorC64, + StateVectorC128, + allocate_aligned_array, + print_configuration, + ) +except ImportError: + pass # Should be a complaint when kokkos_ops module is not available. + +from itertools import product + +import numpy as np +import pennylane as qml +from pennylane import BasisState, DeviceError, StatePrep +from pennylane.measurements import MidMeasureMP +from pennylane.ops import Conditional +from pennylane.ops.op_math import Adjoint +from pennylane.tape import QuantumScript +from pennylane.wires import Wires + +from pennylane_lightning.core._serialize import global_phase_diagonal + +from ._measurements import LightningKokkosMeasurements + class LightningKokkosStateVector: # pylint: disable=too-few-public-methods """Lightning Kokkos state-vector class. @@ -26,6 +52,394 @@ class LightningKokkosStateVector: # pylint: disable=too-few-public-methods dtype: Datatypes for state-vector representation. Must be one of ``np.complex64`` or ``np.complex128``. Default is ``np.complex128`` device_name(string): state vector device name. Options: ["lightning.kokkos"] + kokkos_args(InitializationSettings): binding for Kokkos::InitializationSettings + (threading parameters). + sync(bool): immediately sync with host-sv after applying operations + """ - pass # pylint: disable=unnecessary-pass + def __init__( + self, + num_wires, + dtype=np.complex128, + device_name="lightning.kokkos", + kokkos_args=None, + sync=True, + ): # pylint: disable=too-many-arguments + self._num_wires = num_wires + self._wires = Wires(range(num_wires)) + self._dtype = dtype + + self._kokkos_config = {} + self._sync = sync + + if dtype not in [np.complex64, np.complex128]: # pragma: no cover + raise TypeError(f"Unsupported complex type: {dtype}") + + if device_name != "lightning.kokkos": + raise DeviceError(f'The device name "{device_name}" is not a valid option.') + + self._device_name = device_name + + # self._qubit_state = self._state_dtype()(self._num_wires) + if kokkos_args is None: + self._kokkos_state = self._state_dtype()(self.num_wires) + elif isinstance(kokkos_args, InitializationSettings): + self._kokkos_state = self._state_dtype()(self.num_wires, kokkos_args) + else: + raise TypeError( + f"Argument kokkos_args must be of type {type(InitializationSettings())} but it is of {type(kokkos_args)}." + ) + + if not self._kokkos_config: + self._kokkos_config = self._kokkos_configuration() + + @property + def dtype(self): + """Returns the state vector data type.""" + return self._dtype + + @property + def device_name(self): + """Returns the state vector device name.""" + return self._device_name + + @property + 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 + + @property + def state_vector(self): + """Returns a handle to the state vector.""" + return self._kokkos_state + + @property + def state(self): + """Copy the state vector data from the device to the host. + + A state vector Numpy array is explicitly allocated on the host to store and return + the data. + + **Example** + + >>> dev = qml.device('lightning.kokkos', 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.sync_d2h(state) + return state + + def sync_h2d(self, state_vector): + """Copy the state vector data on host provided by the user to the state + vector on the device + + Args: + state_vector(array[complex]): the state vector array on host. + + + **Example** + + >>> dev = qml.device('lightning.kokkos', wires=3) + >>> obs = qml.Identity(0) @ qml.PauliX(1) @ qml.PauliY(2) + >>> obs1 = qml.Identity(1) + >>> H = qml.Hamiltonian([1.0, 1.0], [obs1, obs]) + >>> state_vector = np.array([0.0 + 0.0j, 0.0 + 0.1j, 0.1 + 0.1j, 0.1 + 0.2j, 0.2 + 0.2j, 0.3 + 0.3j, 0.3 + 0.4j, 0.4 + 0.5j,], dtype=np.complex64) + >>> dev.sync_h2d(state_vector) + >>> res = dev.expval(H) + >>> print(res) + 1.0 + """ + self._kokkos_state.HostToDevice(state_vector.ravel(order="C")) + + def sync_d2h(self, state_vector): + """Copy the state vector data on device to a state vector on the host provided + by the user + + Args: + state_vector(array[complex]): the state vector array on device + + + **Example** + + >>> dev = qml.device('lightning.kokkos', wires=1) + >>> dev.apply([qml.PauliX(wires=[0])]) + >>> state_vector = np.zeros(2**dev.num_wires).astype(dev.C_DTYPE) + >>> dev.sync_d2h(state_vector) + >>> print(state_vector) + [0.+0.j 1.+0.j] + """ + self._kokkos_state.DeviceToHost(state_vector.ravel(order="C")) + + def _kokkos_configuration(self): + """Get the default configuration of the kokkos device. + + Returns: The `lightning.kokkos` device configuration + """ + return print_configuration() + + def _state_dtype(self): + """Binding to Lightning Managed state vector C++ class. + + Returns: the state vector class + """ + return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 + + def reset_state(self): + """Reset the device's state""" + # init the state vector to |00..0> + self._kokkos_state.resetStateVector() + + 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[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))`` + """ + # special case for integral types + if state.dtype.kind == "i": + state = np.array(state, dtype=self.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 + """ + # 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(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 = qml.math.convert_like(basis_states, state) + return int(qml.math.dot(state, basis_states)) + + 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)`` + or broadcasted state of shape ``(batch_size, 2**len(wires))`` + device_wires (Wires): wires that get initialized in the state + """ + + if isinstance(state, self._kokkos_state.__class__): + state_data = allocate_aligned_array(state.size, np.dtype(self.dtype), True) + state.DeviceToHost(state_data) + state = state_data + + ravelled_indices, state = self._preprocess_state_vector(state, device_wires) + + # translate to wire labels used by device + 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 + self.sync_h2d(np.reshape(state, output_shape)) + return + + self._kokkos_state.setStateVector(ravelled_indices, state) # this operation on device + + 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) + # Return a computational basis state over all wires. + self._kokkos_state.setBasisState(num) + + def _apply_lightning_controlled(self, operation): + """Apply an arbitrary controlled operation to the state tensor. + + Args: + operation (~pennylane.operation.Operation): controlled operation to apply + + Returns: + None + """ + state = self.state_vector + + control_wires = list(operation.control_wires) + control_values = operation.control_values + name = operation.name + # Apply GlobalPhase + inv = False + param = operation.parameters[0] + wires = self.wires.indices(operation.wires) + matrix = global_phase_diagonal(param, self.wires, control_wires, control_values) + state.apply(name, wires, inv, [[param]], matrix) + + def _apply_lightning_midmeasure( + self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str + ): + """Execute a MidMeasureMP operation and return the sample in mid_measurements. + + Args: + operation (~pennylane.operation.Operation): mid-circuit measurement + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. + + Returns: + None + """ + wires = self.wires.indices(operation.wires) + wire = list(wires)[0] + circuit = QuantumScript([], [qml.sample(wires=operation.wires)], shots=1) + if postselect_mode == "fill-shots" and operation.postselect is not None: + sample = operation.postselect + else: + sample = LightningKokkosMeasurements(self).measure_final_state(circuit) + sample = np.squeeze(sample) + mid_measurements[operation] = sample + getattr(self.state_vector, "collapse")(wire, bool(sample)) + if operation.reset and bool(sample): + self.apply_operations([qml.PauliX(operation.wires)], mid_measurements=mid_measurements) + + def _apply_lightning( + self, operations, mid_measurements: dict = None, postselect_mode: str = None + ): + """Apply a list of operations to the state tensor. + + Args: + operations (list[~pennylane.operation.Operation]): operations to apply + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + None + """ + state = self.state_vector + + # Skip over identity operations instead of performing + # matrix multiplication with it. + for operation in operations: + if isinstance(operation, qml.Identity): + continue + if isinstance(operation, Adjoint): + name = operation.base.name + invert_param = True + else: + name = operation.name + invert_param = False + method = getattr(state, name, None) + wires = list(operation.wires) + + if isinstance(operation, Conditional): + if operation.meas_val.concretize(mid_measurements): + self._apply_lightning([operation.base]) + elif isinstance(operation, MidMeasureMP): + self._apply_lightning_midmeasure( + operation, mid_measurements, postselect_mode=postselect_mode + ) + elif method is not None: # apply specialized gate + param = operation.parameters + method(wires, invert_param, param) + elif isinstance(operation, qml.ops.Controlled) and isinstance( + operation.base, qml.GlobalPhase + ): # apply n-controlled gate + + # Kokkos do not support the controlled gates except for GlobalPhase + 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, mid_measurements: dict = None, postselect_mode: str = None + ): + """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:] + + self._apply_lightning( + operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + + def get_final_state( + self, + circuit: QuantumScript, + mid_measurements: dict = None, + postselect_mode: str = 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 + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + LightningStateVector: Lightning final state class. + + """ + self.apply_operations( + circuit.operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + + return self diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index e91722afb..8917566c6 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -24,9 +24,11 @@ import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig from pennylane.devices.modifiers import simulator_tracking, single_tape_support +from pennylane.measurements import MidMeasureMP from pennylane.tape import QuantumScript, QuantumTape from pennylane.typing import Result, ResultBatch +from ._measurements import LightningKokkosMeasurements from ._state_vector import LightningKokkosStateVector try: @@ -44,7 +46,7 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -def simulate( # pylint: disable=unused-argument +def simulate( circuit: QuantumScript, state: LightningKokkosStateVector, postselect_mode: str = None, @@ -63,7 +65,30 @@ def simulate( # pylint: disable=unused-argument Note that this function can return measurements for non-commuting observables simultaneously. """ - return 0 + has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) + if circuit.shots and has_mcm: + results = [] + aux_circ = qml.tape.QuantumScript( + circuit.operations, + circuit.measurements, + shots=[1], + trainable_params=circuit.trainable_params, + ) + for _ in range(circuit.shots.total_shots): + state.reset_state() + mid_measurements = {} + final_state = state.get_final_state( + aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + results.append( + LightningKokkosMeasurements(final_state).measure_final_state( + aux_circ, mid_measurements=mid_measurements + ) + ) + return tuple(results) + state.reset_state() + final_state = state.get_final_state(circuit) + return LightningKokkosMeasurements(final_state).measure_final_state(circuit) def jacobian( # pylint: disable=unused-argument @@ -320,10 +345,6 @@ def __init__( # pylint: disable=too-many-arguments self._statevector = LightningKokkosStateVector(num_wires=len(self.wires), dtype=c_dtype) - # TODO: Investigate usefulness of creating numpy random generator - 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 diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 7fc142973..afbaa6dbe 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -19,7 +19,7 @@ import numpy as np import pennylane as qml import pytest -from conftest import LightningDevice, device_name # tested device +from conftest import PHI, THETA, LightningDevice, device_name # tested device from flaky import flaky from pennylane.devices import DefaultQubit from pennylane.measurements import VarianceMP @@ -30,21 +30,28 @@ except ImportError: pass -from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements -from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + from pennylane_lightning.lightning_kokkos._measurements import ( + LightningKokkosMeasurements as LightningMeasurements, + ) + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) + +if device_name not in ("lightning.qubit", "lightning.kokkos"): + pytest.skip( + "Exclusive tests for lightning.qubit or lightning.kokkos. Skipping.", + allow_module_level=True, + ) -if device_name != "lightning.qubit": - pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. 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( @@ -480,7 +487,7 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): assert np.allclose(result, expected, max(tol, 1.0e-4)) @flaky(max_runs=5) - @pytest.mark.parametrize("shots", [None, 1000000]) + @pytest.mark.parametrize("shots", [None, 1000000, (900000, 900000)]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "obs0_", @@ -575,6 +582,9 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s # a few tests may fail in single precision, and hence we increase the tolerance dtol = tol if shots is None else max(tol, 1.0e-2) for r, e in zip(result, expected): + if isinstance(shots, tuple) and isinstance(r[0], np.ndarray): + r = np.concatenate(r) + e = np.concatenate(e) assert np.allclose(r, e, atol=dtol, rtol=dtol) @pytest.mark.parametrize( @@ -584,6 +594,10 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s [[1, 0], [0, 1]], ], ) + @pytest.mark.skipif( + device_name == "lightning.kokkos", + reason="Kokkos new API in WIP. Skipping.", + ) def test_probs_tape_unordered_wires(self, cases, tol): """Test probs with a circuit on wires=[0] fails for out-of-order wires passed to probs.""" diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py new file mode 100644 index 000000000..8fffc721e --- /dev/null +++ b/tests/lightning_qubit/test_simulate_method.py @@ -0,0 +1,139 @@ +# 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 itertools +import math +from typing import Sequence + +import numpy as np +import pennylane as qml +import pytest +from conftest import PHI, THETA, LightningDevice, device_name # tested device +from flaky import flaky +from pennylane.devices import DefaultExecutionConfig, DefaultQubit +from pennylane.measurements import VarianceMP +from scipy.sparse import csr_matrix, random_array + +if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector + from pennylane_lightning.lightning_qubit.lightning_qubit import simulate + + +if device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) + from pennylane_lightning.lightning_kokkos.lightning_kokkos import simulate + + +if device_name != "lightning.qubit" and device_name != "lightning.kokkos": + pytest.skip( + "Exclusive tests for lightning.qubit and lightning.kokkos. Skipping.", + allow_module_level=True, + ) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +# General LightningStateVector fixture, for any number of wires. +@pytest.fixture( + scope="module", + params=[np.complex64, np.complex128], +) +def lightning_sv(request): + def _statevector(num_wires): + return LightningStateVector(num_wires=num_wires, dtype=request.param) + + return _statevector + + +class TestSimulate: + """Tests for the simulate method.""" + + @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) + + def test_simple_circuit(self, lightning_sv, tol): + """Tests the simulate method for a simple circuit.""" + tape = qml.tape.QuantumScript( + [qml.RX(THETA[0], wires=0), qml.RY(PHI[0], wires=1)], + [qml.expval(qml.PauliX(0))], + shots=None, + ) + statevector = lightning_sv(num_wires=2) + result = simulate(circuit=tape, state=statevector) + reference = self.calculate_reference(tape) + + assert np.allclose(result, reference, tol) + + test_data_no_parameters = [ + (100, qml.PauliZ(wires=[0]), 100), + (110, qml.PauliZ(wires=[1]), 110), + (120, qml.PauliX(0) @ qml.PauliZ(1), 120), + ] + + @pytest.mark.parametrize("num_shots,operation,shape", test_data_no_parameters) + def test_sample_dimensions(self, lightning_sv, num_shots, operation, shape): + """Tests if the samples returned by simulate have the correct dimensions""" + ops = [qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])] + tape = qml.tape.QuantumScript(ops, [qml.sample(op=operation)], shots=num_shots) + + statevector = lightning_sv(num_wires=2) + result = simulate(circuit=tape, state=statevector) + + assert np.array_equal(result.shape, (shape,)) + + def test_sample_values(self, lightning_sv, tol): + """Tests if the samples returned by simulate have the correct values""" + ops = [qml.RX(1.5708, wires=[0])] + tape = qml.tape.QuantumScript(ops, [qml.sample(op=qml.PauliZ(0))], shots=1000) + + statevector = lightning_sv(num_wires=1) + + result = simulate(circuit=tape, state=statevector) + + assert np.allclose(result**2, 1, atol=tol, rtol=0) + + @pytest.mark.skipif( + device_name != "lightning.qubit", + reason=f"Device {device_name} does not have an mcmc option.", + ) + @pytest.mark.parametrize("mcmc", [True, False]) + @pytest.mark.parametrize("kernel", ["Local", "NonZeroRandom"]) + def test_sample_values_with_mcmc(self, lightning_sv, tol, mcmc, kernel): + """Tests if the samples returned by simulate have the correct values""" + ops = [qml.RX(1.5708, wires=[0])] + tape = qml.tape.QuantumScript(ops, [qml.sample(op=qml.PauliZ(0))], shots=1000) + + statevector = lightning_sv(num_wires=1) + + mcmc_param = { + "mcmc": mcmc, + "kernel_name": kernel, + "num_burnin": 100, + } + + execution_config = DefaultExecutionConfig + + result = simulate( + circuit=tape, state=statevector, mcmc=mcmc_param, postselect_mode=execution_config + ) + + assert np.allclose(result**2, 1, atol=tol, rtol=0) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 0cc8fe303..f859a0022 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -24,13 +24,20 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires -from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) + -if device_name != "lightning.qubit": - pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) +if device_name not in ("lightning.qubit", "lightning.kokkos"): + pytest.skip( + "Exclusive tests for lightning.qubit or lightning.kokkos. Skipping.", + allow_module_level=True, + ) if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -38,8 +45,7 @@ @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): +def test_device_name_and_init(num_wires, dtype): """Test the class initialization and returned properties.""" state_vector = LightningStateVector(num_wires, dtype=dtype, device_name=device_name) assert state_vector.dtype == dtype From a15b018b3525b4a8366a5e74bb37d12da3fe838c Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 30 Jul 2024 14:34:49 +0000 Subject: [PATCH 022/130] Auto update version from '0.38.0-dev20' to '0.38.0-dev21' --- 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 2ee21e257..b0db6142f 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev20" +__version__ = "0.38.0-dev21" From 06a8d2b21c0d4d1866be5d0463bc1b08f3089563 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 30 Jul 2024 14:37:41 +0000 Subject: [PATCH 023/130] Auto update version from '0.38.0-dev20' to '0.38.0-dev21' --- 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 2ee21e257..b0db6142f 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev20" +__version__ = "0.38.0-dev21" From 180a0659737e1bdaedf43238582c7bd46e1dd42f Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 1 Aug 2024 22:27:08 +0000 Subject: [PATCH 024/130] Auto update version from '0.38.0-dev21' to '0.38.0-dev22' --- 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 b0db6142f..69ef0e05e 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev21" +__version__ = "0.38.0-dev22" From 319cacc45c5deab23649d43fe6106250a5c3c7bc Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 2 Aug 2024 14:38:26 +0000 Subject: [PATCH 025/130] Auto update version from '0.38.0-dev22' to '0.38.0-dev23' --- 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 69ef0e05e..2231d11ae 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev22" +__version__ = "0.38.0-dev23" From 5b99da74146a94bcdfeabba9bbada48d4e83241c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 2 Aug 2024 13:25:49 -0400 Subject: [PATCH 026/130] Add the support class for the Adjoint Jacobian to the new device (#829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Before submitting Please complete the following checklist when submitting a PR: - [X] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [X] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [X] Ensure that the test suite passes, by running `make test`. - [ ] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [X] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** Migrate the lightning.kokkos device to the new device API. **Description of the Change:** The 'jacobian' and vjp` methods are necessary to achieve full functionality with the new device API. We are going to follow the same recipe used with LightningQubit. **Benefits:** Add two of the essential methods for the new device API into LightningKokkos. **Possible Drawbacks:** **Related GitHub Issues:** ## Partial / Freezzed PR ⚠️ ❄️ To make a smooth integration of LightningKokkos with the new device API, we set the branch kokkosNewAPI_backend as the base branch target for this development. The branch kokkosNewAPI_backend has the mock of all classes and methods necessary for the new API. Also, several tests were disabled with ```python if device_name == "lightning.kokkos": pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) ``` Additionally, the CI testing from PennyLane for LKokkos is temporally disabled through commenting on the following lines in .github/workflows files ```bash : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append ``` However, these tests will unblocked as the implementation progresses. After all the developments for integrating LightningKokkos with the new API have been completed then the PR will be open to merging to master [sc-68808] [sc-68814] --------- Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .../lightning_kokkos/_adjoint_jacobian.py | 338 ++++++++++++++++++ .../lightning_kokkos/_state_vector.py | 2 +- .../lightning_kokkos/lightning_kokkos.py | 39 +- .../test_adjoint_jacobian_class.py | 74 +++- tests/lightning_qubit/test_jacobian_method.py | 215 +++++++++++ 5 files changed, 654 insertions(+), 14 deletions(-) create mode 100644 pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py create mode 100644 tests/lightning_qubit/test_jacobian_method.py diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py new file mode 100644 index 000000000..14eda6ab0 --- /dev/null +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -0,0 +1,338 @@ +# 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""" +Internal methods for adjoint Jacobian differentiation method. +""" + +try: + from pennylane_lightning.lightning_kokkos_ops.algorithms import ( + AdjointJacobianC64, + AdjointJacobianC128, + create_ops_listC64, + create_ops_listC128, + ) +except ImportError: + pass + +from os import getenv +from typing import List + +import numpy as np +import pennylane as qml +from pennylane import BasisState, QuantumFunctionError, StatePrep +from pennylane.measurements import Expectation, MeasurementProcess, State +from pennylane.operation import Operation +from pennylane.tape import QuantumTape + +from pennylane_lightning.core._serialize import QuantumScriptSerializer +from pennylane_lightning.core.lightning_base import _chunk_iterable + +from ._state_vector import LightningKokkosStateVector + + +class LightningKokkosAdjointJacobian: + """Check and execute the adjoint Jacobian differentiation method. + + Args: + qubit_state(LightningKokkosStateVector): State Vector to calculate the adjoint Jacobian with. + batch_obs(bool): If serialized tape is to be batched or not. + """ + + def __init__(self, kokkos_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: + self._qubit_state = kokkos_state + self._state = kokkos_state.state_vector + self._dtype = kokkos_state.dtype + self._jacobian_lightning = ( + AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() + ) + self._create_ops_list_lightning = ( + create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 + ) + self._batch_obs = batch_obs + + @property + def qubit_state(self): + """Returns a handle to the LightningKokkosStateVector object.""" + return self._qubit_state + + @property + def state(self): + """Returns a handle to the Lightning internal data object.""" + return self._state + + @property + def dtype(self): + """Returns the simulation data type.""" + return self._dtype + + @staticmethod + def _get_return_type( + measurements: List[MeasurementProcess], + ): + """Get the measurement return type. + + Args: + measurements (List[MeasurementProcess]): a list of measurement processes to check. + + Returns: + None, Expectation or State: a common return type of measurements. + """ + if not measurements: + return None + + if len(measurements) == 1 and measurements[0].return_type is State: + return State + + return Expectation + + def _process_jacobian_tape( + self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False + ): + """Process a tape, serializing and building a dictionary proper for + the adjoint Jacobian calculation in the C++ layer. + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. + split_obs (bool, optional): If splitting the observables in a list. Defaults to False. + + Returns: + dictionary: dictionary providing serialized data for Jacobian calculation. + """ + use_csingle = self._dtype == np.complex64 + + obs_serialized, obs_idx_offsets = QuantumScriptSerializer( + self._qubit_state.device_name, use_csingle, use_mpi, split_obs + ).serialize_observables(tape) + + ops_serialized, use_sp = QuantumScriptSerializer( + self._qubit_state.device_name, use_csingle, use_mpi, split_obs + ).serialize_ops(tape) + + ops_serialized = self._create_ops_list_lightning(*ops_serialized) + + # We need to filter out indices in trainable_params which do not + # correspond to operators. + trainable_params = sorted(tape.trainable_params) + if len(trainable_params) == 0: + return None + + tp_shift = [] + record_tp_rows = [] + all_params = 0 + + for op_idx, trainable_param in enumerate(trainable_params): + # get op_idx-th operator among differentiable operators + operation, _, _ = tape.get_operation(op_idx) + if isinstance(operation, Operation) and not isinstance( + operation, (BasisState, StatePrep) + ): + # We now just ignore non-op or state preps + tp_shift.append(trainable_param) + record_tp_rows.append(all_params) + all_params += 1 + + if use_sp: + # When the first element of the tape is state preparation. Still, I am not sure + # whether there must be only one state preparation... + tp_shift = [i - 1 for i in tp_shift] + + return { + "state_vector": self.state, + "obs_serialized": obs_serialized, + "ops_serialized": ops_serialized, + "tp_shift": tp_shift, + "record_tp_rows": record_tp_rows, + "all_params": all_params, + "obs_idx_offsets": obs_idx_offsets, + } + + @staticmethod + def _adjoint_jacobian_processing(jac): + """ + Post-process the Jacobian matrix returned by ``adjoint_jacobian`` for + the new return type system. + """ + jac = np.squeeze(jac) + + if jac.ndim == 0: + return np.array(jac) + + if jac.ndim == 1: + return tuple(np.array(j) for j in jac) + + # must be 2-dimensional + return tuple(tuple(np.array(j_) for j_ in j) for j in jac) + + def _handle_raises(self, tape: QuantumTape, is_jacobian: bool, grad_vec=None): + """Handle the raises related with the tape for computing the Jacobian with the adjoint method or the vector-Jacobian products.""" + + if tape.shots: + raise QuantumFunctionError( + "Requested adjoint differentiation to be computed with finite shots. " + "The derivative is always exact when using the adjoint " + "differentiation method." + ) + + tape_return_type = self._get_return_type(tape.measurements) + + if is_jacobian: + if not tape_return_type: + # the tape does not have measurements + return True + + if tape_return_type is State: + raise QuantumFunctionError( + "Adjoint differentiation method does not support measurement StateMP." + ) + + if not is_jacobian: + if qml.math.allclose(grad_vec, 0.0) or not tape_return_type: + # the tape does not have measurements or the gradient is 0.0 + return True + + if tape_return_type is State: + raise QuantumFunctionError( + "Adjoint differentiation does not support State measurements." + ) + + if any(m.return_type is not Expectation for m in tape.measurements): + raise QuantumFunctionError( + "Adjoint differentiation method does not support expectation return type " + "mixed with other return types" + ) + + return False + + def calculate_jacobian(self, tape: QuantumTape): + """Computes the Jacobian with the adjoint method. + + .. code-block:: python + + statevector = LightningKokkosStateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + jacobian = LightningKokkosAdjointJacobian(statevector).calculate_jacobian(tape) + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + + Returns: + The Jacobian of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=True) + + if empty_array: + return np.array([], dtype=self._dtype) + + processed_data = self._process_jacobian_tape(tape) + + if not processed_data: # training_params is empty + return np.array([], dtype=self._dtype) + + trainable_params = processed_data["tp_shift"] + + # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. + # This will allow use of Lightning with adjoint for large-qubit numbers AND large + # numbers of observables, enabling choice between compute time and memory use. + requested_threads = int(getenv("OMP_NUM_THREADS", "1")) + + if self._batch_obs and requested_threads > 1: + obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) + jac = [] + for obs_chunk in obs_partitions: + jac_local = self._jacobian_lightning( + processed_data["state_vector"], + obs_chunk, + processed_data["ops_serialized"], + trainable_params, + ) + jac.extend(jac_local) + else: + jac = self._jacobian_lightning( + processed_data["state_vector"], + processed_data["obs_serialized"], + processed_data["ops_serialized"], + trainable_params, + ) + jac = np.array(jac) + jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac + jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) + jac_r[:, processed_data["record_tp_rows"]] = jac + + return self._adjoint_jacobian_processing(jac_r) + + # pylint: disable=inconsistent-return-statements + def calculate_vjp(self, tape: QuantumTape, grad_vec): + """Compute the vector-Jacobian products of a tape. + + .. code-block:: python + + statevector = LightningKokkosStateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + vjp = LightningKokkosAdjointJacobian(statevector).calculate_vjp(tape, grad_vec) + + computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where + + .. math:: + + w_k = dy_k \\cdot J_{k,j} + + Here, :math:`dy` is the workflow cotangent (grad_vec), and :math:`J` the Jacobian. + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + grad_vec (tensor_like): Gradient-output vector, also called `dy` or cotangent. Must have shape matching the output + shape of the corresponding tape, i.e. number of measurements if the return type is expectation. + + Returns: + The vector-Jacobian products of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=False, grad_vec=grad_vec) + + if empty_array: + return qml.math.convert_like(np.zeros(len(tape.trainable_params)), grad_vec) + + # Proceed, because tape_return_type is Expectation. + if qml.math.ndim(grad_vec) == 0: + grad_vec = (grad_vec,) + + if len(grad_vec) != len(tape.measurements): + raise ValueError( + "Number of observables in the tape must be the same as the " + "length of grad_vec in the vjp method" + ) + + if np.iscomplexobj(grad_vec): + raise ValueError( + "The vjp method only works with a real-valued grad_vec when the " + "tape is returning an expectation value" + ) + + ham = qml.simplify(qml.dot(grad_vec, [m.obs for m in tape.measurements])) + + num_params = len(tape.trainable_params) + + if num_params == 0: + return np.array([], dtype=self.qubit_state.dtype) + + new_tape = qml.tape.QuantumScript( + tape.operations, + [qml.expval(ham)], + shots=tape.shots, + trainable_params=tape.trainable_params, + ) + + return self.calculate_jacobian(new_tape) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index d1fd75a48..9c16e79bf 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -24,7 +24,7 @@ print_configuration, ) except ImportError: - pass # Should be a complaint when kokkos_ops module is not available. + pass from itertools import product diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 8917566c6..7f6666837 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -28,6 +28,7 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.typing import Result, ResultBatch +from ._adjoint_jacobian import LightningKokkosAdjointJacobian from ._measurements import LightningKokkosMeasurements from ._state_vector import LightningKokkosStateVector @@ -91,7 +92,7 @@ def simulate( return LightningKokkosMeasurements(final_state).measure_final_state(circuit) -def jacobian( # pylint: disable=unused-argument +def jacobian( circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None ): """Compute the Jacobian for a single quantum script. @@ -107,10 +108,16 @@ def jacobian( # pylint: disable=unused-argument Returns: TensorLike: The Jacobian of the quantum script """ - return 0 + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + state.reset_state() + final_state = state.get_final_state(circuit) + return LightningKokkosAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian( + circuit + ) -def simulate_and_jacobian( # pylint: disable=unused-argument +def simulate_and_jacobian( circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None ): """Simulate a single quantum script and compute its Jacobian. @@ -128,10 +135,14 @@ def simulate_and_jacobian( # pylint: disable=unused-argument Note that this function can return measurements for non-commuting observables simultaneously. """ - return 0 + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + res = simulate(circuit, state) + jac = LightningKokkosAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) + return res, jac -def vjp( # pylint: disable=unused-argument +def vjp( circuit: QuantumTape, cotangents: Tuple[Number], state: LightningKokkosStateVector, @@ -154,10 +165,16 @@ def vjp( # pylint: disable=unused-argument Returns: TensorLike: The VJP of the quantum script """ - return 0 + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + state.reset_state() + final_state = state.get_final_state(circuit) + return LightningKokkosAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( + circuit, cotangents + ) -def simulate_and_vjp( # pylint: disable=unused-argument +def simulate_and_vjp( circuit: QuantumTape, cotangents: Tuple[Number], state: LightningKokkosStateVector, @@ -181,7 +198,13 @@ def simulate_and_vjp( # pylint: disable=unused-argument Tuple[TensorLike]: The results of the simulation and the calculated VJP Note that this function can return measurements for non-commuting observables simultaneously. """ - return 0 + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + res = simulate(circuit, state) + _vjp = LightningKokkosAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp( + circuit, cotangents + ) + return res, _vjp _operations = frozenset( diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index 3562e59ea..85c78d790 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -23,14 +23,23 @@ from pennylane.tape import QuantumScript from scipy.stats import unitary_group -from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian -from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector +if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) + from pennylane_lightning.lightning_kokkos._adjoint_jacobian import ( + LightningKokkosAdjointJacobian as LightningAdjointJacobian, + ) + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) -if device_name != "lightning.qubit": - pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) +if device_name not in ("lightning.qubit", "lightning.kokkos"): + pytest.skip( + "Exclusive tests for new API backends lightning.qubit and lightning.kokkos for LightningAdjointJacobian class. Skipping.", + allow_module_level=True, + ) if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -43,6 +52,12 @@ qml.PauliZ.compute_matrix(), ) +kokkos_args = [None] +if device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos_ops import InitializationSettings + + kokkos_args += [InitializationSettings().set_num_threads(2)] + # General LightningStateVector fixture, for any number of wires. @pytest.fixture( @@ -141,6 +156,35 @@ def test_empty_measurements(self, lightning_sv): jac = self.calculate_jacobian(lightning_sv(num_wires=3), tape) assert len(jac) == 0 + def test_empty_trainable_params(self, lightning_sv): + """Tests if an empty array is returned when the number trainable params is zero.""" + + with qml.tape.QuantumTape() as tape: + qml.X(wires=[0]) + qml.expval(qml.PauliZ(0)) + + jac = self.calculate_jacobian(lightning_sv(num_wires=3), tape) + assert len(jac) == 0 + + def test_not_expectation_return_type(self, lightning_sv): + """Tests if an empty array is returned when the number trainable params is zero.""" + + with qml.tape.QuantumTape() as tape: + qml.X(wires=[0]) + qml.RX(0.4, wires=[0]) + qml.var(qml.PauliZ(1)) + + with pytest.raises( + qml.QuantumFunctionError, + match="Adjoint differentiation method does not support expectation return type " + "mixed with other return types", + ): + self.calculate_jacobian(lightning_sv(num_wires=1), tape) + + @pytest.mark.skipif( + device_name != "lightning.qubit", + reason="N-controlled operations only implemented in lightning.qubit.", + ) @pytest.mark.parametrize("n_qubits", [1, 2, 3, 4]) @pytest.mark.parametrize("par", [-np.pi / 7, np.pi / 5, 2 * np.pi / 3]) def test_phaseshift_gradient(self, n_qubits, par, tol, lightning_sv): @@ -531,6 +575,26 @@ def test_zero_dy(self, lightning_sv): assert np.all(vjp == np.zeros([len(tape.trainable_params)])) + def test_empty_dy(self, tol, lightning_sv): + """A zero dy vector will return no tapes and a zero matrix""" + statevector = lightning_sv(num_wires=2) + x = 0.543 + y = -0.654 + + with qml.tape.QuantumTape() as tape: + qml.RX(x, wires=[0]) + qml.RY(y, wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) + + tape.trainable_params = {0, 1} + dy = np.array(1.0) + + vjp = self.calculate_vjp(statevector, tape, dy) + + expected = np.array([-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]) + assert np.allclose(vjp, expected, atol=tol, rtol=0) + def test_single_expectation_value(self, tol, lightning_sv): """Tests correct output shape and evaluation for a tape with a single expval output""" diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py new file mode 100644 index 000000000..44da006fc --- /dev/null +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -0,0 +1,215 @@ +# 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 numpy as np +import pennylane as qml +import pytest +from conftest import PHI, THETA, LightningDevice, device_name # tested device +from pennylane.devices import DefaultExecutionConfig, DefaultQubit, ExecutionConfig +from pennylane.tape import QuantumScript + +if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector + from pennylane_lightning.lightning_qubit.lightning_qubit import ( + jacobian, + simulate, + simulate_and_jacobian, + simulate_and_vjp, + vjp, + ) + + +if device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) + from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( + jacobian, + simulate, + simulate_and_jacobian, + simulate_and_vjp, + vjp, + ) + + +if device_name not in ("lightning.qubit", "lightning.kokkos"): + pytest.skip( + "Exclusive tests for new API backends lightning.qubit and lightning.kokkos for LightningAdjointJacobian class. Skipping.", + allow_module_level=True, + ) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +# General LightningStateVector fixture, for any number of wires. +@pytest.fixture( + scope="module", + params=[np.complex64, np.complex128], +) +def lightning_sv(request): + def _statevector(num_wires): + return LightningStateVector(num_wires=num_wires, dtype=request.param) + + return _statevector + + +class TestJacobian: + """Unit tests for the jacobian method with the new device API.""" + + @staticmethod + def calculate_reference(tape, execute_and_derivatives=False): + device = DefaultQubit(max_workers=1) + program, config = device.preprocess(ExecutionConfig(gradient_method="adjoint")) + tapes, transf_fn = program([tape]) + + if execute_and_derivatives: + results, jac = device.execute_and_compute_derivatives(tapes, config) + else: + results = device.execute(tapes, config) + jac = device.compute_derivatives(tapes, config) + return transf_fn(results), jac + + @staticmethod + def process_and_execute(statevector, tape, execute_and_derivatives=False): + + if execute_and_derivatives: + results, jac = simulate_and_jacobian(tape, statevector) + else: + results = simulate(tape, statevector) + jac = jacobian(tape, statevector) + return results, jac + + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) + @pytest.mark.parametrize( + "obs", + [ + qml.Z(1), + 2.5 * qml.Z(0), + qml.Z(0) @ qml.X(1), + qml.Z(1) + qml.X(1), + qml.Hamiltonian([-1.0, 1.5], [qml.Z(1), qml.X(1)]), + qml.Hermitian(qml.Hadamard.compute_matrix(), 0), + qml.Projector([1], 1), + ], + ) + @pytest.mark.parametrize("execute_and_derivatives", [True, False]) + def test_derivatives_single_expval( + self, theta, phi, obs, execute_and_derivatives, lightning_sv + ): + """Test that the jacobian is correct when a tape has a single expectation value""" + if isinstance(obs, qml.ops.LinearCombination) and not qml.operation.active_new_opmath(): + obs = qml.operation.convert_to_legacy_H(obs) + + qs = QuantumScript( + [qml.RX(theta, 0), qml.CNOT([0, 1]), qml.RY(phi, 1)], + [qml.expval(obs)], + trainable_params=[0, 1], + ) + + statevector = lightning_sv(num_wires=3) + res, jac = self.process_and_execute(statevector, qs, execute_and_derivatives) + + if isinstance(obs, qml.Hamiltonian): + qs = QuantumScript( + qs.operations, + [qml.expval(qml.Hermitian(qml.matrix(obs), wires=obs.wires))], + trainable_params=qs.trainable_params, + ) + expected, expected_jac = self.calculate_reference( + qs, execute_and_derivatives=execute_and_derivatives + ) + + tol = 1e-5 if statevector.dtype == np.complex64 else 1e-7 + assert np.allclose(res, expected, atol=tol, rtol=0) + assert np.allclose(jac, expected_jac, atol=tol, rtol=0) + + +@pytest.mark.skipif( + device_name == "lightning.tensor", + reason="lightning.tensor does not support vjp", +) +class TestVJP: + """Unit tests for the vjp method with the new device API.""" + + @staticmethod + def calculate_reference(tape, dy, execute_and_derivatives=False): + device = DefaultQubit(max_workers=1) + program, config = device.preprocess(ExecutionConfig(gradient_method="adjoint")) + tapes, transf_fn = program([tape]) + dy = [dy] + + if execute_and_derivatives: + results, jac = device.execute_and_compute_vjp(tapes, dy, config) + else: + results = device.execute(tapes, config) + jac = device.compute_vjp(tapes, dy, config) + return transf_fn(results), jac + + @staticmethod + def process_and_execute(statevector, tape, dy, execute_and_derivatives=False): + dy = [dy] + + if execute_and_derivatives: + results, jac = simulate_and_vjp(tape, dy, statevector) + else: + results = simulate(tape, statevector) + jac = vjp(tape, dy, statevector) + return results, jac + + @pytest.mark.usefixtures("use_legacy_and_new_opmath") + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) + @pytest.mark.parametrize( + "obs", + [ + qml.Z(1), + 2.5 * qml.Z(0), + qml.Z(0) @ qml.X(1), + qml.Z(1) + qml.X(1), + qml.Hamiltonian([-1.0, 1.5], [qml.Z(1), qml.X(1)]), + qml.Hermitian(qml.Hadamard.compute_matrix(), 0), + qml.Projector([1], 1), + ], + ) + @pytest.mark.parametrize("execute_and_derivatives", [True, False]) + def test_vjp_single_expval(self, theta, phi, obs, execute_and_derivatives, lightning_sv): + """Test that the VJP is correct when a tape has a single expectation value""" + if isinstance(obs, qml.ops.LinearCombination) and not qml.operation.active_new_opmath(): + obs = qml.operation.convert_to_legacy_H(obs) + + qs = QuantumScript( + [qml.RX(theta, 0), qml.CNOT([0, 1]), qml.RY(phi, 1)], + [qml.expval(obs)], + trainable_params=[0, 1], + ) + + dy = 1.0 + statevector = lightning_sv(num_wires=3) + res, jac = self.process_and_execute( + statevector, qs, dy, execute_and_derivatives=execute_and_derivatives + ) + if isinstance(obs, qml.Hamiltonian): + qs = QuantumScript( + qs.operations, + [qml.expval(qml.Hermitian(qml.matrix(obs), wires=obs.wires))], + trainable_params=qs.trainable_params, + ) + expected, expected_jac = self.calculate_reference( + qs, dy, execute_and_derivatives=execute_and_derivatives + ) + + tol = 1e-5 if statevector.dtype == np.complex64 else 1e-7 + assert np.allclose(res, expected, atol=tol, rtol=0) + assert np.allclose(jac, expected_jac, atol=tol, rtol=0) From 3eea0d147d1f3ad72b9872411cd8a28824d0b179 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 2 Aug 2024 17:26:15 +0000 Subject: [PATCH 027/130] Auto update version from '0.38.0-dev23' to '0.38.0-dev25' --- 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 2231d11ae..3961045c0 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev23" +__version__ = "0.38.0-dev25" From 3588b9d04f126a8a2d65408404e27770cc98023d Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 5 Aug 2024 22:06:21 +0000 Subject: [PATCH 028/130] Auto update version from '0.38.0-dev25' to '0.38.0-dev26' --- 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 3961045c0..444de99a5 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev25" +__version__ = "0.38.0-dev26" From f9ab86fc7061dfe36fa28417426e1628baf54c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 9 Aug 2024 12:21:18 -0400 Subject: [PATCH 029/130] Add (check) full support for sampling in full parity with Lightning (#836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Before submitting Please complete the following checklist when submitting a PR: - [X] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [X] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [X] Ensure that the test suite passes, by running `make test`. - [ ] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [X] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** Migrate the lightning.kokkos device to the new device API. **Description of the Change:** Add the necessary method for the LightningKokkos that allows the use of new device API. It should have a similar structure as the LightningQubit class. Note: Lightning Kokkos does not support MCMC sampling. **Benefits:** Integration of LighntingKokkos with the new device API. **Possible Drawbacks:** **Related GitHub Issues:** ## Partial / Freezzed PR ⚠️ ❄️ To make a smooth integration of LightningKokkos with the new device API, we set the branch `kokkosNewAPI_backend` as the base branch target for this development. The branch `kokkosNewAPI_backend` has the mock of all classes and methods necessary for the new API. Also, several tests were disabled with ```python if device_name == "lightning.kokkos": pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) ``` Additionally, the CI testing from PennyLane for LKokkos is temporally disabled through commenting on the following lines in .github/workflows files ``` : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append ``` However, these tests will unblocked as the implementation progresses. After all the developments for integrating LightningKokkos with the new API have been completed then the PR will be open to merging to master [sc-68820] --------- Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- .../lightning_kokkos/_state_vector.py | 2 +- .../lightning_kokkos/lightning_kokkos.py | 210 ++++++++++++++++-- .../test_measurements_class.py | 48 ++-- .../test_measurements_samples_MCMC.py | 7 +- tests/lightning_qubit/test_simulate_method.py | 2 +- .../test_state_vector_class.py | 22 ++ tests/new_api/test_device.py | 66 +++--- tests/new_api/test_expval.py | 3 - tests/new_api/test_var.py | 2 - tests/test_native_mcm.py | 20 +- 10 files changed, 292 insertions(+), 90 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 9c16e79bf..a9a20add6 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -73,7 +73,7 @@ def __init__( self._kokkos_config = {} self._sync = sync - if dtype not in [np.complex64, np.complex128]: # pragma: no cover + if dtype not in [np.complex64, np.complex128]: raise TypeError(f"Unsupported complex type: {dtype}") if device_name != "lightning.kokkos": diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 7f6666837..2b4e79d98 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -16,6 +16,7 @@ This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that interfaces with C++ for fast linear algebra calculations. """ +from dataclasses import replace from numbers import Number from pathlib import Path from typing import Callable, Optional, Sequence, Tuple, Union @@ -23,9 +24,22 @@ import numpy as np import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support +from pennylane.devices.preprocess import ( + decompose, + mid_circuit_measurements, + no_sampling, + validate_adjoint_trainable_params, + validate_device_wires, + validate_measurements, + validate_observables, +) from pennylane.measurements import MidMeasureMP +from pennylane.operation import DecompositionUndefinedError, Operator, Tensor +from pennylane.ops import Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumTape +from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch from ._adjoint_jacobian import LightningKokkosAdjointJacobian @@ -210,9 +224,7 @@ def simulate_and_vjp( _operations = frozenset( { "Identity", - "BasisState", "QubitStateVector", - "StatePrep", "QubitUnitary", "ControlledQubitUnitary", "MultiControlledX", @@ -294,6 +306,98 @@ def simulate_and_vjp( # The set of supported observables. +def stopping_condition(op: Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" + # These thresholds are adapted from `lightning_base.py` + # To avoid building matrices beyond the given thresholds. + # This should reduce runtime overheads for larger systems. + if isinstance(op, qml.QFT): + return len(op.wires) < 10 + if isinstance(op, qml.GroverOperator): + return len(op.wires) < 13 + + return op.name in _operations + + +def stopping_condition_shots(op: Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.kokkos`` + with finite shots.""" + return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional)) + + +def accepted_observables(obs: Operator) -> bool: + """A function that determines whether or not an observable is supported by ``lightning.kokkos``.""" + return obs.name in _observables + + +def adjoint_observables(obs: Operator) -> bool: + """A function that determines whether or not an observable is supported by ``lightning.kokkos`` + when using the adjoint differentiation method.""" + if isinstance(obs, qml.Projector): + return False + + if isinstance(obs, Tensor): + if any(isinstance(o, qml.Projector) for o in obs.non_identity_obs): + return False + return True + + if isinstance(obs, SProd): + return adjoint_observables(obs.base) + + if isinstance(obs, (Sum, Prod)): + return all(adjoint_observables(o) for o in obs) + + return obs.name in _observables + + +def adjoint_measurements(mp: qml.measurements.MeasurementProcess) -> bool: + """Specifies whether or not an observable is compatible with adjoint differentiation on DefaultQubit.""" + return isinstance(mp, qml.measurements.ExpectationMP) + + +def _supports_adjoint(circuit): + if circuit is None: + return True + + prog = TransformProgram() + _add_adjoint_transforms(prog) + + try: + prog((circuit,)) + except (DecompositionUndefinedError, qml.DeviceError, AttributeError): + return False + return True + + +def _add_adjoint_transforms(program: TransformProgram) -> None: + """Private helper function for ``preprocess`` that adds the transforms specific + for adjoint differentiation. + + Args: + program (TransformProgram): where we will add the adjoint differentiation transforms + + Side Effects: + Adds transforms to the input program. + + """ + + name = "adjoint + lightning.kokkos" + program.add_transform(no_sampling, name=name) + program.add_transform( + decompose, + stopping_condition=adjoint_ops, + stopping_condition_shots=stopping_condition_shots, + name=name, + skip_initial_state_prep=False, + ) + program.add_transform(validate_observables, accepted_observables, name=name) + program.add_transform( + validate_measurements, analytic_measurements=adjoint_measurements, name=name + ) + program.add_transform(qml.transforms.broadcast_expand) + program.add_transform(validate_adjoint_trainable_params) + + def _kokkos_configuration(): return print_configuration() @@ -313,17 +417,18 @@ class LightningKokkos(Device): sync (bool): immediately sync with host-sv after applying operations c_dtype: Datatypes for statevector representation. Must be one of ``np.complex64`` or ``np.complex128``. - kokkos_args (InitializationSettings): binding for Kokkos::InitializationSettings - (threading parameters). 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. + kokkos_args (InitializationSettings): binding for Kokkos::InitializationSettings + (threading parameters). """ # pylint: disable=too-many-instance-attributes - _device_options = ("rng", "c_dtype", "batch_obs", "kernel_name") + # General device options + _device_options = ("rng", "c_dtype", "batch_obs") _new_API = True # Device specific options @@ -393,7 +498,24 @@ def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. """ - return 0 + 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) + + # add this to fit the Execute configuration + mcmc_default = {"mcmc": False, "kernel_name": None, "num_burnin": 0} + new_device_options.update(mcmc_default) + + return replace(config, **updated_values, device_options=new_device_options) def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): """This function defines the device transform program to be applied and an updated device configuration. @@ -414,7 +536,27 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) * Currently does not intrinsically support parameter broadcasting """ - return 0 + exec_config = self._setup_execution_config(execution_config) + program = TransformProgram() + + program.add_transform(validate_measurements, name=self.name) + program.add_transform(validate_observables, accepted_observables, name=self.name) + program.add_transform(validate_device_wires, self.wires, name=self.name) + program.add_transform( + mid_circuit_measurements, device=self, mcm_config=exec_config.mcm_config + ) + program.add_transform( + decompose, + stopping_condition=stopping_condition, + stopping_condition_shots=stopping_condition_shots, + skip_initial_state_prep=True, + name=self.name, + ) + program.add_transform(qml.transforms.broadcast_expand) + + if exec_config.gradient_method == "adjoint": + _add_adjoint_transforms(program) + return program, exec_config # pylint: disable=unused-argument def execute( @@ -431,7 +573,19 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - return 0 + results = [] + for circuit in circuits: + if self._wire_map is not None: + [circuit], _ = qml.map_wires(circuit, self._wire_map) + results.append( + simulate( + circuit, + self._statevector, + postselect_mode=execution_config.mcm_config.postselect_mode, + ) + ) + + return tuple(results) def supports_derivatives( self, @@ -450,7 +604,13 @@ def supports_derivatives( Bool: Whether or not a derivative can be calculated provided the given information """ - return 0 + 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 _supports_adjoint(circuit=circuit) def compute_derivatives( self, @@ -466,8 +626,12 @@ def compute_derivatives( Returns: Tuple: The jacobian for each trainable parameter """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - return 0 + return tuple( + jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit in circuits + ) def execute_and_compute_derivatives( self, @@ -483,7 +647,14 @@ def execute_and_compute_derivatives( Returns: tuple: A numeric result of the computation and the gradient. """ - return 0 + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + simulate_and_jacobian( + c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for c in circuits + ) + return tuple(zip(*results)) def supports_vjp( self, @@ -498,7 +669,7 @@ def supports_vjp( Returns: Bool: Whether or not a derivative can be calculated provided the given information """ - return 0 + return self.supports_derivatives(execution_config, circuit) def compute_vjp( self, @@ -532,7 +703,11 @@ def compute_vjp( * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, then the shape must be ``(batch_size,)``. """ - return 0 + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + return tuple( + vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit, cots in zip(circuits, cotangents) + ) def execute_and_compute_vjp( self, @@ -550,4 +725,11 @@ def execute_and_compute_vjp( Returns: Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product """ - return 0 + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + simulate_and_vjp( + circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for circuit, cots in zip(circuits, cotangents) + ) + return tuple(zip(*results)) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index afbaa6dbe..a292bd5ff 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -25,11 +25,6 @@ from pennylane.measurements import VarianceMP from scipy.sparse import csr_matrix, random_array -try: - from pennylane_lightning.lightning_qubit_ops import MeasurementsC64, MeasurementsC128 -except ImportError: - pass - if device_name == "lightning.qubit": from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector @@ -429,6 +424,7 @@ def calculate_reference(tape, lightning_sv): return m.measure_final_state(tape) @flaky(max_runs=5) + @pytest.mark.parametrize("shots", [None, 1000000, (900000, 900000)]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", @@ -450,10 +446,15 @@ def calculate_reference(tape, lightning_sv): qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), ), ) - def test_single_return_value(self, measurement, observable, lightning_sv, tol): + def test_single_return_value(self, shots, 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), + ( + qml.ops.Sum, + qml.ops.SProd, + qml.ops.Prod, + qml.SparseHamiltonian, + ), ): pytest.skip( f"Observable of type {type(observable).__name__} is not supported for rotating probabilities." @@ -475,16 +476,37 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): if isinstance(observable, list) else [measurement(op=observable)] ) - tape = qml.tape.QuantumScript(ops, measurements) + tape = qml.tape.QuantumScript(ops, measurements, shots=shots) - 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) + + skip_list = ( + qml.ops.Sum, + # qml.Hamiltonian, + qml.SparseHamiltonian, + ) + do_skip = measurement is qml.var and isinstance(observable, skip_list) + do_skip = do_skip or ( + measurement is qml.expval and isinstance(observable, qml.SparseHamiltonian) + ) + do_skip = do_skip and shots is not None + if do_skip: + with pytest.raises(TypeError): + _ = m.measure_final_state(tape) + return + else: + result = m.measure_final_state(tape) + + expected = self.calculate_reference(tape, lightning_sv) # a few tests may fail in single precision, and hence we increase the tolerance - assert np.allclose(result, expected, max(tol, 1.0e-4)) + if shots is None: + assert np.allclose(result, expected, max(tol, 1.0e-4)) + else: + dtol = max(tol, 1.0e-2) + assert np.allclose(result, expected, rtol=dtol, atol=dtol) @flaky(max_runs=5) @pytest.mark.parametrize("shots", [None, 1000000, (900000, 900000)]) @@ -594,10 +616,6 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s [[1, 0], [0, 1]], ], ) - @pytest.mark.skipif( - device_name == "lightning.kokkos", - reason="Kokkos new API in WIP. Skipping.", - ) def test_probs_tape_unordered_wires(self, cases, tol): """Test probs with a circuit on wires=[0] fails for out-of-order wires passed to probs.""" diff --git a/tests/lightning_qubit/test_measurements_samples_MCMC.py b/tests/lightning_qubit/test_measurements_samples_MCMC.py index 12edaae0f..1eac49900 100644 --- a/tests/lightning_qubit/test_measurements_samples_MCMC.py +++ b/tests/lightning_qubit/test_measurements_samples_MCMC.py @@ -20,11 +20,10 @@ from conftest import LightningDevice as ld from conftest import device_name -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - if device_name != "lightning.qubit": - pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) + pytest.skip( + f"Device {device_name} does not have an mcmc option. Skipping.", allow_module_level=True + ) if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index 8fffc721e..60cecc4c4 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -118,7 +118,7 @@ def test_sample_values(self, lightning_sv, tol): @pytest.mark.parametrize("mcmc", [True, False]) @pytest.mark.parametrize("kernel", ["Local", "NonZeroRandom"]) def test_sample_values_with_mcmc(self, lightning_sv, tol, mcmc, kernel): - """Tests if the samples returned by simulate have the correct values""" + """Tests if the samples returned by simulate have the correct values using mcmc""" ops = [qml.RX(1.5708, wires=[0])] tape = qml.tape.QuantumScript(ops, [qml.sample(op=qml.PauliZ(0))], shots=1000) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index f859a0022..6aa857e47 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -32,6 +32,11 @@ LightningKokkosStateVector as LightningStateVector, ) + try: + from pennylane_lightning.lightning_kokkos_ops import InitializationSettings + except ImportError: + pass + if device_name not in ("lightning.qubit", "lightning.kokkos"): pytest.skip( @@ -52,6 +57,23 @@ def test_device_name_and_init(num_wires, dtype): assert state_vector.device_name == device_name assert state_vector.wires == Wires(range(num_wires)) + if device_name == "lightning.kokkos": + bad_kokkos_args = np.array([33]) + with pytest.raises( + TypeError, + match=f"Argument kokkos_args must be of type {type(InitializationSettings())} but it is of {type(bad_kokkos_args)}.", + ): + assert LightningStateVector( + num_wires, dtype=dtype, device_name=device_name, kokkos_args=bad_kokkos_args + ) + + set_kokkos_args = InitializationSettings().set_num_threads(2) + state_vector_3 = LightningStateVector( + num_wires, dtype=dtype, device_name=device_name, kokkos_args=set_kokkos_args + ) + + assert type(state_vector) == type(state_vector_3) + def test_wrong_device_name(): """Test an invalid device name""" diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index f8666a6ed..171657539 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -43,7 +43,6 @@ ) if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( _add_adjoint_transforms, _supports_adjoint, @@ -181,8 +180,8 @@ def test_supports_adjoint(self, circuit, expected): @pytest.mark.skipif( - device_name == "lightning.tensor", - reason="lightning.tensor does not support shots or mcmc", + device_name != "lightning.qubit", + reason=f"The device {device_name} does not support mcmc", ) class TestInitialization: """Unit tests for device initialization""" @@ -257,7 +256,7 @@ def process_and_execute(device, tape): device_options=_default_device_options, ), ), - ( + pytest.param( ExecutionConfig( device_options={ "c_dtype": np.complex64, @@ -275,6 +274,10 @@ def process_and_execute(device, tape): "num_burnin": 0, }, ), + marks=pytest.mark.skipif( + device_name != "lightning.qubit", + reason=f"The device {device_name} does not support mcmc", + ), ), ( ExecutionConfig( @@ -319,7 +322,7 @@ def test_preprocess(self, adjoint): expected_program.add_transform(qml.transforms.broadcast_expand) if adjoint: - name = "adjoint + lightning.qubit" + name = f"adjoint + {device_name}" expected_program.add_transform(no_sampling, name=name) expected_program.add_transform( decompose, @@ -819,11 +822,11 @@ def test_derivatives_tape_batch(self, phi, execute_and_derivatives, batch_obs): tapes.""" device = LightningDevice(wires=4, batch_obs=batch_obs) - ops = [ - qml.X(0), - qml.X(1), - qml.ctrl(qml.RX(phi, 2), (0, 1, 3), control_values=[1, 1, 0]), - ] + ops = [qml.X(0), qml.X(1)] + if device_name == "lightning.qubit": + ops.append(qml.ctrl(qml.RX(phi, 2), (0, 1, 3), control_values=[1, 1, 0])) + else: + ops.append(qml.RX(phi, 2)) qs1 = QuantumScript( ops, @@ -837,19 +840,20 @@ def test_derivatives_tape_batch(self, phi, execute_and_derivatives, batch_obs): ops = [qml.Hadamard(0), qml.IsingXX(phi, wires=(0, 1))] qs2 = QuantumScript(ops, [qml.expval(qml.prod(qml.Z(0), qml.Z(1)))], trainable_params=[0]) + expected_device = DefaultQubit(wires=4, max_workers=1) + if execute_and_derivatives: results, jacs = device.execute_and_compute_derivatives((qs1, qs2)) + + expected, expected_jac = expected_device.execute_and_compute_derivatives((qs1, qs2)) else: results = device.execute((qs1, qs2)) jacs = device.compute_derivatives((qs1, qs2)) - # Assert results - expected1 = (-np.sin(phi) - 1, 3 * np.cos(phi)) - x1 = np.cos(phi / 2) ** 2 / 2 - x2 = np.sin(phi / 2) ** 2 / 2 - expected2 = sum([x1, -x2, -x1, x2]) # zero - expected = (expected1, expected2) + expected = expected_device.execute((qs1, qs2)) + expected_jac = expected_device.compute_derivatives((qs1, qs2)) + # Assert results assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) assert np.allclose(results[0][0], expected[0][0]) @@ -857,12 +861,6 @@ def test_derivatives_tape_batch(self, phi, execute_and_derivatives, batch_obs): assert np.allclose(results[1], expected[1]) # Assert derivatives - expected_jac1 = (-np.cos(phi), -3 * np.sin(phi)) - x1_jac = -np.cos(phi / 2) * np.sin(phi / 2) / 2 - x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 - expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero - expected_jac = (expected_jac1, expected_jac2) - assert len(jacs) == len(expected_jac) assert len(jacs[0]) == len(expected_jac[0]) assert np.allclose(jacs[0], expected_jac[0]) @@ -1160,8 +1158,11 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): ops = [ qml.X(0), qml.X(1), - qml.ctrl(qml.RX(phi, 2), (0, 1, 3), control_values=[1, 1, 0]), ] + if device_name == "lightning.qubit": + ops.append(qml.ctrl(qml.RX(phi, 2), (0, 1, 3), control_values=[1, 1, 0])) + else: + ops.append(qml.RX(phi, 2)) qs1 = QuantumScript( ops, @@ -1176,18 +1177,18 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): qs2 = QuantumScript(ops, [qml.expval(qml.prod(qml.Z(0), qml.Z(1)))], trainable_params=[0]) dy = [(1.5, 2.5), 1.0] + expected_device = DefaultQubit(wires=4, max_workers=1) + if execute_and_derivatives: results, jacs = device.execute_and_compute_vjp((qs1, qs2), dy) + + expected, expected_jac = expected_device.execute_and_compute_vjp((qs1, qs2), dy) else: results = device.execute((qs1, qs2)) jacs = device.compute_vjp((qs1, qs2), dy) - # Assert results - expected1 = (-np.sin(phi) - 1, 3 * np.cos(phi)) - x1 = np.cos(phi / 2) ** 2 / 2 - x2 = np.sin(phi / 2) ** 2 / 2 - expected2 = sum([x1, -x2, -x1, x2]) # zero - expected = (expected1, expected2) + expected = expected_device.execute((qs1, qs2)) + expected_jac = expected_device.compute_vjp((qs1, qs2), dy) assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) @@ -1195,13 +1196,6 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): assert np.allclose(results[0][1], expected[0][1]) assert np.allclose(results[1], expected[1]) - # Assert derivatives - expected_jac1 = -1.5 * np.cos(phi) - 2.5 * 3 * np.sin(phi) - x1_jac = -np.cos(phi / 2) * np.sin(phi / 2) / 2 - x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 - expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero - expected_jac = (expected_jac1, expected_jac2) - assert len(jacs) == len(expected_jac) == 2 assert np.allclose(jacs[0], expected_jac[0]) assert np.allclose(jacs[1], expected_jac[1]) diff --git a/tests/new_api/test_expval.py b/tests/new_api/test_expval.py index 1e205836c..22e15dd26 100644 --- a/tests/new_api/test_expval.py +++ b/tests/new_api/test_expval.py @@ -21,9 +21,6 @@ from conftest import PHI, THETA, VARPHI, LightningDevice, device_name from pennylane.devices import DefaultQubit -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/new_api/test_var.py b/tests/new_api/test_var.py index c7d5442bc..14f2e5295 100644 --- a/tests/new_api/test_var.py +++ b/tests/new_api/test_var.py @@ -25,8 +25,6 @@ if device_name == "lightning.tensor": pytest.skip("lightning.tensor does not support qml.var()", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index ae4cebea1..725749c47 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -22,22 +22,12 @@ from flaky import flaky from pennylane._device import DeviceError -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - if device_name not in ("lightning.qubit", "lightning.kokkos"): pytest.skip("Native MCM not supported. Skipping.", allow_module_level=True) if not LightningDevice._CPP_BINARY_AVAILABLE: # pylint: disable=protected-access pytest.skip("No binary module found. Skipping.", allow_module_level=True) -# TODO: remove this after the new device API implementation for Kokkos is implemented -if device_name == "lightning.kokkos": - pytest.skip( - "Native Kokkos device has compatible issues with the new device API. Skipping.", - allow_module_level=True, - ) - def get_device(wires, **kwargs): kwargs.setdefault("shots", None) @@ -82,7 +72,7 @@ def circuit_mcm(): def test_unsupported_measurement(): - """Test unsupported ``qml.classical_shadow`` measurement on ``lightning.qubit``.""" + """Test unsupported ``qml.classical_shadow`` measurement on ``lightning.qubit`` or ``lightning.kokkos`` .""" dev = qml.device(device_name, wires=2, shots=1000) params = np.pi / 4 * np.ones(2) @@ -100,10 +90,12 @@ def func(x, y): match=f"not accepted with finite shots on lightning.qubit", ): func(*params) - else: + if device_name == "lightning.kokkos": + with pytest.raises( - TypeError, - match=f"Native mid-circuit measurement mode does not support ClassicalShadowMP measurements.", + DeviceError, + match=r"Measurement shadow\(wires=\[0\]\) not accepted with finite shots on " + + device_name, ): func(*params) From 0d1dd710e65a4d3ac5745b0a9a680dfa40bb6046 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 9 Aug 2024 16:21:40 +0000 Subject: [PATCH 030/130] Auto update version from '0.38.0-dev26' to '0.38.0-dev27' --- 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 444de99a5..974470cd4 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev26" +__version__ = "0.38.0-dev27" From 9a20ee1b66b99ff5fc775bbd744b46f02522bf36 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 9 Aug 2024 16:25:06 +0000 Subject: [PATCH 031/130] Auto update version from '0.38.0-dev26' to '0.38.0-dev27' --- 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 444de99a5..974470cd4 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev26" +__version__ = "0.38.0-dev27" From 873656c3072cba66864549467385cbb5d1653a47 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 12 Aug 2024 15:34:52 +0000 Subject: [PATCH 032/130] Auto update version from '0.38.0-dev28' to '0.38.0-dev29' --- 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 1e0e19350..68d119cc2 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev28" +__version__ = "0.38.0-dev29" From fe0954ae3e597bb632101d4026531a0141fc526a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 13 Aug 2024 15:21:13 -0400 Subject: [PATCH 033/130] Update unit/integration tests for the new device (#840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Before submitting Please complete the following checklist when submitting a PR: - [X] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the [`tests`](../tests) directory! - [X] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running `make docs`. - [X] Ensure that the test suite passes, by running `make test`. - [ ] Add a new entry to the `.github/CHANGELOG.md` file, summarizing the change, and including a link back to the PR. - [X] Ensure that code is properly formatted by running `make format`. When all the above are checked, delete everything above the dashed line and fill in the pull request template. ------------------------------------------------------------------------------------------------------------ **Context:** Migrate the lightning.kokkos device to the new device API. **Description of the Change:** Unlock and modify all the unit tests for `Lightning Kokkos` and pass all the CI. **Benefits:** Integration of LighntingKokkos with the new device API. **Possible Drawbacks:** **Related GitHub Issues:** ## Partial / Freezzed PR ⚠️ ❄️ To make a smooth integration of LightningKokkos with the new device API, we set the branch `kokkosNewAPI_backend` as the base branch target for this development. The branch `kokkosNewAPI_backend` has the mock of all classes and methods necessary for the new API. Also, several tests were disabled with ```python if device_name == "lightning.kokkos": pytest.skip("Kokkos new API in WIP. Skipping.",allow_module_level=True) ``` Additionally, the CI testing from PennyLane for LKokkos is temporally disabled through commenting on the following lines in .github/workflows files ``` : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append ``` However, these tests will unblocked as the implementation progresses. After all the developments for integrating LightningKokkos with the new API have been completed then the PR will be open to merging to master [sc-68823] --------- Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- .github/workflows/tests_lkcpu_python.yml | 4 ++-- .github/workflows/tests_lkcuda_python.yml | 11 +++++------ .github/workflows/wheel_linux_aarch64.yml | 3 +-- .github/workflows/wheel_linux_x86_64.yml | 3 +-- .github/workflows/wheel_macos_arm64.yml | 3 +-- .github/workflows/wheel_macos_x86_64.yml | 3 +-- .../lightning_kokkos/lightning_kokkos.py | 15 +++++++++------ tests/test_adjoint_jacobian.py | 10 +++------- tests/test_apply.py | 3 --- tests/test_comparison.py | 3 --- tests/test_device.py | 5 +---- tests/test_execute.py | 3 --- tests/test_expval.py | 3 --- tests/test_gates.py | 2 -- tests/test_measurements.py | 3 --- tests/test_measurements_sparse.py | 3 --- tests/test_templates.py | 3 --- tests/test_var.py | 2 -- tests/test_vjp.py | 2 -- 19 files changed, 24 insertions(+), 60 deletions(-) diff --git a/.github/workflows/tests_lkcpu_python.yml b/.github/workflows/tests_lkcpu_python.yml index 63fa4e1f5..1dbad0070 100644 --- a/.github/workflows/tests_lkcpu_python.yml +++ b/.github/workflows/tests_lkcpu_python.yml @@ -250,8 +250,8 @@ jobs: PL_DEVICE=${DEVICENAME} pytest tests/ $COVERAGE_FLAGS --splits 7 --group ${{ matrix.group }} \ --store-durations --durations-path='.github/workflows/python_lightning_kokkos_test_durations.json' --splitting-algorithm=least_duration mv .github/workflows/python_lightning_kokkos_test_durations.json ${{ github.workspace }}/.test_durations-${{ matrix.exec_model }}-${{ matrix.group }} - : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append - : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append + pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append + pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }}-${{ matrix.group }} - name: Upload test durations diff --git a/.github/workflows/tests_lkcuda_python.yml b/.github/workflows/tests_lkcuda_python.yml index 2f5795ba5..5605c0249 100644 --- a/.github/workflows/tests_lkcuda_python.yml +++ b/.github/workflows/tests_lkcuda_python.yml @@ -254,8 +254,8 @@ jobs: cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS - : # pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append - : # pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append + pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append + pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml - name: Install all backend devices @@ -275,10 +275,9 @@ jobs: OMP_PROC_BIND: false run: | cd main/ - # for device in lightning.qubit lightning.kokkos; do - for device in lightning.qubit; do - pl-device-test --device ${device} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append - pl-device-test --device ${device} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append + for device in lightning.qubit lightning.kokkos; do + pl-device-test --device ${device} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append -vvv + pl-device-test --device ${device} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append -vvv done mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml diff --git a/.github/workflows/wheel_linux_aarch64.yml b/.github/workflows/wheel_linux_aarch64.yml index 9b938a6d6..ec090d9f7 100644 --- a/.github/workflows/wheel_linux_aarch64.yml +++ b/.github/workflows/wheel_linux_aarch64.yml @@ -92,8 +92,7 @@ jobs: matrix: os: [ubuntu-latest] arch: [aarch64] - : # pl_backend: ["lightning_kokkos", "lightning_qubit"] - pl_backend: ["lightning_qubit"] + pl_backend: ["lightning_kokkos", "lightning_qubit"] cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/.github/workflows/wheel_linux_x86_64.yml b/.github/workflows/wheel_linux_x86_64.yml index c3b066146..f91ff34e8 100644 --- a/.github/workflows/wheel_linux_x86_64.yml +++ b/.github/workflows/wheel_linux_x86_64.yml @@ -100,8 +100,7 @@ jobs: fail-fast: false matrix: arch: [x86_64] - : # pl_backend: ["lightning_kokkos", "lightning_qubit"] - pl_backend: ["lightning_qubit"] + pl_backend: ["lightning_kokkos", "lightning_qubit"] cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/.github/workflows/wheel_macos_arm64.yml b/.github/workflows/wheel_macos_arm64.yml index 7c30b0eb1..5b1b20d3d 100644 --- a/.github/workflows/wheel_macos_arm64.yml +++ b/.github/workflows/wheel_macos_arm64.yml @@ -60,8 +60,7 @@ jobs: matrix: os: [macos-12] arch: [arm64] - : # pl_backend: ["lightning_kokkos", "lightning_qubit"] - pl_backend: ["lightning_qubit"] + pl_backend: ["lightning_kokkos", "lightning_qubit"] cibw_build: ${{fromJson(needs.mac-set-matrix-arm.outputs.python_version)}} timeout-minutes: 30 name: macos-latest::arm64 - ${{ matrix.pl_backend }} (Python ${{ fromJson('{ "cp39-*":"3.9","cp310-*":"3.10","cp311-*":"3.11","cp312-*":"3.12" }')[matrix.cibw_build] }}) diff --git a/.github/workflows/wheel_macos_x86_64.yml b/.github/workflows/wheel_macos_x86_64.yml index a1e6b2395..f3aa0d900 100644 --- a/.github/workflows/wheel_macos_x86_64.yml +++ b/.github/workflows/wheel_macos_x86_64.yml @@ -95,8 +95,7 @@ jobs: matrix: os: [macos-12] arch: [x86_64] - : # pl_backend: ["lightning_kokkos", "lightning_qubit"] - pl_backend: ["lightning_qubit"] + pl_backend: ["lightning_kokkos", "lightning_qubit"] cibw_build: ${{fromJson(needs.set_wheel_build_matrix.outputs.python_version)}} exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }} kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }} diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index d06f7b4ef..17045d83b 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -18,9 +18,9 @@ """ import os import sys -from os import getenv from dataclasses import replace from numbers import Number +from os import getenv from pathlib import Path from typing import Callable, Optional, Sequence, Tuple, Union @@ -431,7 +431,7 @@ class LightningKokkos(Device): # pylint: disable=too-many-instance-attributes # General device options - _device_options = ("rng", "c_dtype", "batch_obs") + _device_options = ("c_dtype", "batch_obs") _new_API = True # Device specific options @@ -474,14 +474,17 @@ def __init__( # pylint: disable=too-many-arguments else: self._wire_map = {w: i for i, w in enumerate(self.wires)} - self._statevector = LightningKokkosStateVector(num_wires=len(self.wires), dtype=c_dtype) - self._c_dtype = c_dtype self._batch_obs = batch_obs # Kokkos specific options self._kokkos_args = kokkos_args self._sync = sync + + self._statevector = LightningKokkosStateVector( + num_wires=len(self.wires), dtype=c_dtype, kokkos_args=kokkos_args, sync=sync + ) + if not LightningKokkos.kokkos_config: LightningKokkos.kokkos_config = _kokkos_configuration() @@ -514,8 +517,8 @@ def _setup_execution_config(self, config): if option not in new_device_options: new_device_options[option] = getattr(self, f"_{option}", None) - # add this to fit the Execute configuration - mcmc_default = {"mcmc": False, "kernel_name": None, "num_burnin": 0} + # It is necessary to set the mcmc default configuration to complete the requirements of ExecuteConfig + mcmc_default = {"mcmc": False, "kernel_name": None, "num_burnin": 0, "rng": None} new_device_options.update(mcmc_default) return replace(config, **updated_values, device_options=new_device_options) diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 1ece8ff15..d54df8a36 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -26,9 +26,6 @@ from pennylane import qchem, qnode from scipy.stats import unitary_group -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - I, X, Y, Z = ( np.eye(2), qml.X.compute_matrix(), @@ -128,12 +125,11 @@ def test_not_expval(self, dev): qml.RX(0.1, wires=0) qml.state() - if device_name == "lightning.kokkos": - message = "Adjoint differentiation does not support State measurements." + if device_name in ("lightning.qubit", "lightning.kokkos"): + message = "Adjoint differentiation method does not support measurement StateMP." elif device_name == "lightning.gpu": message = "Adjoint differentiation does not support State measurements." - else: - message = "Adjoint differentiation method does not support measurement StateMP." + with pytest.raises( qml.QuantumFunctionError, match=message, diff --git a/tests/test_apply.py b/tests/test_apply.py index b95052ad3..9b89c60f4 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -29,9 +29,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - @pytest.mark.skipif( ld._new_API or device_name == "lightning.tensor", diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 2cb3a4db9..6cacca8c8 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -27,9 +27,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - def lightning_backend_dev(wires): """Loads the lightning backend""" diff --git a/tests/test_device.py b/tests/test_device.py index 15ebd8d19..178a0e020 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -26,9 +26,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - def test_create_device(): dev = qml.device(device_name, wires=1) @@ -52,7 +49,7 @@ def test_create_device_with_unsupported_dtype(): reason="Only lightning.kokkos has a kwarg kokkos_args.", ) def test_create_device_with_unsupported_kokkos_args(): - with pytest.raises(TypeError, match="Argument kokkos_args must be of type"): + with pytest.raises(TypeError, match="Argument kokkos_args must be of type .* but it is of .*."): dev = qml.device(device_name, wires=1, kokkos_args=np.complex256) diff --git a/tests/test_execute.py b/tests/test_execute.py index 7c4cd05b4..02a6bfa1f 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -24,9 +24,6 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_expval.py b/tests/test_expval.py index 5d48050f3..dfdc2a72d 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -26,9 +26,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) class TestExpval: diff --git a/tests/test_gates.py b/tests/test_gates.py index c0694ba0f..d4e99ebad 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -26,8 +26,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name == "lightning.tensor": pytest.skip( diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 1995ca18a..88459209b 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -28,9 +28,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - @pytest.mark.skipif(ld._new_API, reason="Old API required") def test_measurements(): diff --git a/tests/test_measurements_sparse.py b/tests/test_measurements_sparse.py index 21f78a9ff..16c69903c 100644 --- a/tests/test_measurements_sparse.py +++ b/tests/test_measurements_sparse.py @@ -24,9 +24,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - @pytest.mark.skipif( device_name == "lightning.tensor", diff --git a/tests/test_templates.py b/tests/test_templates.py index b3e02d251..69dc85f6f 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -26,9 +26,6 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) - class TestGrover: """Test Grover's algorithm (multi-controlled gates, decomposition, etc.)""" diff --git a/tests/test_var.py b/tests/test_var.py index c739620cf..efc6fa836 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -23,8 +23,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) np.random.seed(42) diff --git a/tests/test_vjp.py b/tests/test_vjp.py index 6c9939d3c..63ec51664 100644 --- a/tests/test_vjp.py +++ b/tests/test_vjp.py @@ -24,8 +24,6 @@ if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) -if device_name == "lightning.kokkos": - pytest.skip("Kokkos new API in WIP. Skipping.", allow_module_level=True) if device_name == "lightning.tensor": pytest.skip("lightning.tensor doesn't support vjp.", allow_module_level=True) From 3cb37be4e719abee07a4e65f83977627740f4d4b Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 13 Aug 2024 20:08:39 +0000 Subject: [PATCH 034/130] Auto update version from '0.38.0-dev32' to '0.38.0-dev33' --- 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 1e786a92c..281ac4807 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev32" +__version__ = "0.38.0-dev33" From 1bd9adce7c83c6f57fc73a779b9d7ab460666809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 13 Aug 2024 17:49:12 -0400 Subject: [PATCH 035/130] Solve conflict with master --- .../lightning_kokkos/_state_vector.py | 76 +++---------------- 1 file changed, 10 insertions(+), 66 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index a9a20add6..fcf4b618d 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -26,8 +26,6 @@ except ImportError: pass -from itertools import product - import numpy as np import pennylane as qml from pennylane import BasisState, DeviceError, StatePrep @@ -197,62 +195,6 @@ def reset_state(self): # init the state vector to |00..0> self._kokkos_state.resetStateVector() - 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[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))`` - """ - # special case for integral types - if state.dtype.kind == "i": - state = np.array(state, dtype=self.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 - """ - # 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(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 = qml.math.convert_like(basis_states, state) - return int(qml.math.dot(state, basis_states)) - def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: @@ -266,17 +208,14 @@ def _apply_state_vector(self, state, device_wires: Wires): state.DeviceToHost(state_data) state = state_data - ravelled_indices, state = self._preprocess_state_vector(state, device_wires) - - # translate to wire labels used by device - 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 + output_shape = (2,) * self._num_wires + state = np.reshape(state, output_shape).ravel(order="C") self.sync_h2d(np.reshape(state, output_shape)) return - self._kokkos_state.setStateVector(ravelled_indices, state) # this operation on device + self._kokkos_state.setStateVector(state, list(device_wires)) # this operation on device def _apply_basis_state(self, state, wires): """Initialize the state vector in a specified computational basis state. @@ -289,9 +228,14 @@ def _apply_basis_state(self, state, wires): Note: This function does not support broadcasted inputs yet. """ - num = self._get_basis_state_index(state, wires) + if not set(state.tolist()).issubset({0, 1}): + raise ValueError("BasisState parameter must consist of 0 or 1 integers.") + + if len(state) != len(wires): + raise ValueError("BasisState parameter and wires must be of equal length.") + # Return a computational basis state over all wires. - self._kokkos_state.setBasisState(num) + self._kokkos_state.setBasisState(list(state), list(wires)) def _apply_lightning_controlled(self, operation): """Apply an arbitrary controlled operation to the state tensor. From 9ebbefcefdb1b57a907aa2aa52e0272e3b2cc5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 13 Aug 2024 18:14:09 -0400 Subject: [PATCH 036/130] check pylint --- pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 1 + pennylane_lightning/lightning_kokkos/_state_vector.py | 1 + pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 14eda6ab0..bc79ade35 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -35,6 +35,7 @@ from pennylane.operation import Operation from pennylane.tape import QuantumTape +# pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import QuantumScriptSerializer from pennylane_lightning.core.lightning_base import _chunk_iterable diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index fcf4b618d..9a6115288 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -35,6 +35,7 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires +# pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import global_phase_diagonal from ._measurements import LightningKokkosMeasurements diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 17045d83b..5d6a0a4a0 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -20,7 +20,6 @@ import sys from dataclasses import replace from numbers import Number -from os import getenv from pathlib import Path from typing import Callable, Optional, Sequence, Tuple, Union From 86316bc5f35f6277b632f902a7d9f8e035cfeb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 13 Aug 2024 18:34:29 -0400 Subject: [PATCH 037/130] trigger CIs From 5c6b97d2218817ff50191b2d4c51595a295388dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 14 Aug 2024 08:48:08 -0400 Subject: [PATCH 038/130] trigger CIs From 0293b29eb3ddb6b522dd18c48d5e107d3ab89c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 14 Aug 2024 10:02:27 -0400 Subject: [PATCH 039/130] remove verbose from CI kcuda --- .github/workflows/tests_lkcuda_python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests_lkcuda_python.yml b/.github/workflows/tests_lkcuda_python.yml index 1810e1d42..ac0646898 100644 --- a/.github/workflows/tests_lkcuda_python.yml +++ b/.github/workflows/tests_lkcuda_python.yml @@ -283,8 +283,8 @@ jobs: run: | cd main/ for device in lightning.qubit lightning.kokkos; do - pl-device-test --device ${device} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append -vvv - pl-device-test --device ${device} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append -vvv + pl-device-test --device ${device} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append + pl-device-test --device ${device} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append done mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml From e310fefd4cea6f0cdab4259c4db8ae77df6326d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 14 Aug 2024 11:55:53 -0400 Subject: [PATCH 040/130] Update CHANGELOG --- .github/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index fc02db7d1..141f37fa5 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -25,6 +25,9 @@ * Update python packaging to follow PEP 517/518/621/660 standards. [(#832)](https://github.com/PennyLaneAI/pennylane-lightning/pull/832) +* Lightning Kokkos migrated to new device API. + [(#810)](https://github.com/PennyLaneAI/pennylane-lightning/pull/810) + ### Improvements * The `setBasisState` and `setStateVector` methods of `StateVectorLQubit` and `StateVectorKokkos` are overloaded to support PennyLane-like parameters. @@ -120,7 +123,7 @@ This release contains contributions from (in alphabetical order): -Ali Asadi, Astral Cai, Amintor Dusko, Vincent Michaud-Rioux, Erick Ochoa Lopez, Lee J. O'Riordan, Mudit Pandey, Shuli Shu, Paul Haochen Wang +Ali Asadi, Astral Cai, Amintor Dusko, Vincent Michaud-Rioux, Luis Alfredo Nuñez Meneses, Erick Ochoa Lopez, Lee J. O'Riordan, Mudit Pandey, Shuli Shu, Paul Haochen Wang --- From bbc6448cc5afa76ddf2674b7df0153198ca2af4b Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 14 Aug 2024 21:12:34 +0000 Subject: [PATCH 041/130] Auto update version from '0.38.0-dev33' to '0.38.0-dev34' --- 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 281ac4807..d25ea38a7 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev33" +__version__ = "0.38.0-dev34" From b586e21835b3bedfd3505e15d20858970bd56a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 15 Aug 2024 10:56:35 -0400 Subject: [PATCH 042/130] Apply suggestions from code review - remove debug comments Co-authored-by: Vincent Michaud-Rioux --- .../lightning_kokkos/_measurements.py | 22 ------------------- .../lightning_kokkos/_state_vector.py | 1 - 2 files changed, 23 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index 23dc3aa70..cba0bd708 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -362,18 +362,9 @@ def _measure_with_samples_diagonalizing_gates( # apply diagonalizing gates self._apply_diagonalizing_gates(mps) - # ---------------------------------------- - # Original: - # if self._mcmc: - # total_indices = self._qubit_state.num_wires - # wires = qml.wires.Wires(range(total_indices)) - # else: - # wires = reduce(sum, (mp.wires for mp in mps)) - # Specific for Kokkos: total_indices = self._qubit_state.num_wires wires = qml.wires.Wires(range(total_indices)) - # ---------------------------------------- def _process_single_shot(samples): processed = [] @@ -387,22 +378,9 @@ def _process_single_shot(samples): return tuple(processed) try: - # ---------------------------------------- - # Original: - # if self._mcmc: - # samples = self._measurement_lightning.generate_mcmc_samples( - # len(wires), self._kernel_name, self._num_burnin, shots.total_shots - # ).astype(int, copy=False) - # else: - # samples = self._measurement_lightning.generate_samples( - # list(wires), shots.total_shots - # ).astype(int, copy=False) - - # Specific for Kokkos: samples = self._measurement_lightning.generate_samples( len(wires), shots.total_shots ).astype(int, copy=False) - # ---------------------------------------- except ValueError as e: if str(e) != "probabilities contain NaN": diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 9a6115288..72b83592c 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -80,7 +80,6 @@ def __init__( self._device_name = device_name - # self._qubit_state = self._state_dtype()(self._num_wires) if kokkos_args is None: self._kokkos_state = self._state_dtype()(self.num_wires) elif isinstance(kokkos_args, InitializationSettings): From 00b6eb68bdb539e672b625d6b565c592d3eb3086 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 15 Aug 2024 14:57:34 +0000 Subject: [PATCH 043/130] Auto update version from '0.38.0-dev34' to '0.38.0-dev35' --- 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 d25ea38a7..84a10ab1b 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev34" +__version__ = "0.38.0-dev35" From 84795f5aece085fe12a741d46e270fe45e694698 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 15 Aug 2024 16:14:27 +0000 Subject: [PATCH 044/130] Auto update version from '0.38.0-dev34' to '0.38.0-dev35' --- 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 d25ea38a7..84a10ab1b 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev34" +__version__ = "0.38.0-dev35" From 620a0218435613cc65a07b9045ffc3387c1290d7 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 19 Aug 2024 18:17:28 +0000 Subject: [PATCH 045/130] Auto update version from '0.38.0-dev37' to '0.38.0-dev38' --- 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 3d04670e9..358f5c7cc 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev37" +__version__ = "0.38.0-dev38" From 7ae3360f9f0402363ab204c39276acf2133d5902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 19 Aug 2024 14:44:28 -0400 Subject: [PATCH 046/130] Apply Amintor's commests --- .../lightning_kokkos/_measurements.py | 4 +- tests/new_api/test_device.py | 107 +++++++++++++++--- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index cba0bd708..a09d3296a 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -290,8 +290,8 @@ def measure_with_samples( all_res = [] for group in groups: - if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( - group[0].obs, SparseHamiltonian + if ( isinstance(group[0], (ExpectationMP, VarianceMP)) + and isinstance(group[0].obs, SparseHamiltonian ) ): raise TypeError( "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 0298adac0..54b5b8a03 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -512,7 +512,7 @@ def test_execute_multi_measurement(self, theta, phi, dev, mp1, mp2): @pytest.mark.parametrize("phi, theta", list(zip(PHI, THETA))) @pytest.mark.parametrize("wires", (["a", "b", -3], [0, "target", "other_target"])) - def test_custom_wires(self, phi, theta, wires): + def test_custom_wires_execute(self, phi, theta, wires): """Test execution with custom wires""" device = LightningDevice(wires=wires) qs = QuantumScript( @@ -533,6 +533,8 @@ def test_custom_wires(self, phi, theta, wires): assert np.allclose(result[0], np.cos(phi)) assert np.allclose(result[1], np.cos(phi) * np.cos(theta)) + + @pytest.mark.parametrize( "wires, wire_order", [(3, (0, 1, 2)), (("a", "b", "c"), ("a", "b", "c"))] ) @@ -865,31 +867,68 @@ def test_derivatives_tape_batch(self, phi, execute_and_derivatives, batch_obs): ops = [qml.Hadamard(0), qml.IsingXX(phi, wires=(0, 1))] qs2 = QuantumScript(ops, [qml.expval(qml.prod(qml.Z(0), qml.Z(1)))], trainable_params=[0]) - expected_device = DefaultQubit(wires=4, max_workers=1) - if execute_and_derivatives: results, jacs = device.execute_and_compute_derivatives((qs1, qs2)) - expected, expected_jac = expected_device.execute_and_compute_derivatives((qs1, qs2)) else: results = device.execute((qs1, qs2)) jacs = device.compute_derivatives((qs1, qs2)) - expected = expected_device.execute((qs1, qs2)) - expected_jac = expected_device.compute_derivatives((qs1, qs2)) - # Assert results + expected1 = (-np.sin(phi) - 1, 3 * np.cos(phi)) + x1 = np.cos(phi / 2) ** 2 / 2 + x2 = np.sin(phi / 2) ** 2 / 2 + expected2 = sum([x1, -x2, -x1, x2]) # zero + expected = (expected1, expected2) + assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) assert np.allclose(results[0][0], expected[0][0]) assert np.allclose(results[0][1], expected[0][1]) assert np.allclose(results[1], expected[1]) - + # Assert derivatives + expected_jac1 = (-np.cos(phi), -3 * np.sin(phi)) + x1_jac = -np.cos(phi / 2) * np.sin(phi / 2) / 2 + x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 + expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero + expected_jac = (expected_jac1, expected_jac2) + assert len(jacs) == len(expected_jac) assert len(jacs[0]) == len(expected_jac[0]) assert np.allclose(jacs[0], expected_jac[0]) assert np.allclose(jacs[1], expected_jac[1]) + + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) + @pytest.mark.parametrize("execute_and_derivatives", [True, False]) + @pytest.mark.parametrize("wires", (["a", "b", -3], [0, "target", "other_target"])) + def test_derivatives_custom_wires( + self, theta, phi, dev, execute_and_derivatives, batch_obs, wires + ): + """Test that the jacobian is correct when set custom wires""" + device = LightningDevice(wires=wires) + + qs = QuantumScript( + [ + qml.RX(theta, wires[0]), + qml.CNOT([wires[0], wires[1]]), + qml.RY(phi, wires[1]) + ], + [qml.expval(qml.Z(wires[1]))], + trainable_params=[0, 1], + ) + + res, jac = self.process_and_execute( + device, qs, execute_and_derivatives=execute_and_derivatives, obs_batch=batch_obs + ) + expected, expected_jac = self.calculate_reference( + qs, execute_and_derivatives=execute_and_derivatives + ) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + assert len(res) == len(jac) == 1 + assert np.allclose(res, expected, atol=tol, rtol=0) + assert np.allclose(jac, expected_jac, atol=tol, rtol=0) @pytest.mark.skipif( @@ -1202,25 +1241,65 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): qs2 = QuantumScript(ops, [qml.expval(qml.prod(qml.Z(0), qml.Z(1)))], trainable_params=[0]) dy = [(1.5, 2.5), 1.0] - expected_device = DefaultQubit(wires=4, max_workers=1) - if execute_and_derivatives: results, jacs = device.execute_and_compute_vjp((qs1, qs2), dy) - expected, expected_jac = expected_device.execute_and_compute_vjp((qs1, qs2), dy) else: results = device.execute((qs1, qs2)) jacs = device.compute_vjp((qs1, qs2), dy) - expected = expected_device.execute((qs1, qs2)) - expected_jac = expected_device.compute_vjp((qs1, qs2), dy) - + # Assert results + expected1 = (-np.sin(phi) - 1, 3 * np.cos(phi)) + x1 = np.cos(phi / 2) ** 2 / 2 + x2 = np.sin(phi / 2) ** 2 / 2 + expected2 = sum([x1, -x2, -x1, x2]) # zero + expected = (expected1, expected2) + assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) assert np.allclose(results[0][0], expected[0][0]) assert np.allclose(results[0][1], expected[0][1]) assert np.allclose(results[1], expected[1]) + # Assert derivatives + expected_jac1 = -1.5 * np.cos(phi) - 2.5 * 3 * np.sin(phi) + x1_jac = -np.cos(phi / 2) * np.sin(phi / 2) / 2 + x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 + expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero + expected_jac = (expected_jac1, expected_jac2) + assert len(jacs) == len(expected_jac) == 2 assert np.allclose(jacs[0], expected_jac[0]) assert np.allclose(jacs[1], expected_jac[1]) + + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) + @pytest.mark.parametrize("execute_and_derivatives", [True, False]) + @pytest.mark.parametrize("wires", (["a", "b", -3], [0, "target", "other_target"])) + def test_vjp_custom_wires(self, theta, phi, dev, wires, execute_and_derivatives, batch_obs): + """Test that the VJP is correct when set a custom wires""" + + device = LightningDevice(wires=wires) + + qs = QuantumScript( + [ + qml.RX(theta, wires[0]), + qml.CNOT([wires[0], wires[1]]), + qml.RY(phi, wires[1]) + ], + [qml.expval(qml.Z(wires[1]))], + trainable_params=[0, 1], + ) + + dy = 1.0 + res, jac = self.process_and_execute( + device, qs, dy, execute_and_derivatives=execute_and_derivatives, obs_batch=batch_obs + ) + + expected, expected_jac = self.calculate_reference( + qs, dy, execute_and_derivatives=execute_and_derivatives + ) + + tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 + assert len(res) == len(jac) == 1 + assert np.allclose(res, expected, atol=tol, rtol=0) + assert np.allclose(jac, expected_jac, atol=tol, rtol=0) From 4a91c6f3eb56de36e2909d8213fa0dd98c68ae19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 19 Aug 2024 14:45:11 -0400 Subject: [PATCH 047/130] Apply format --- .../lightning_kokkos/_measurements.py | 4 +-- tests/new_api/test_device.py | 30 +++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index a09d3296a..cba0bd708 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -290,8 +290,8 @@ def measure_with_samples( all_res = [] for group in groups: - if ( isinstance(group[0], (ExpectationMP, VarianceMP)) - and isinstance(group[0].obs, SparseHamiltonian ) + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( + group[0].obs, SparseHamiltonian ): raise TypeError( "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 54b5b8a03..26bdaed81 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -533,8 +533,6 @@ def test_custom_wires_execute(self, phi, theta, wires): assert np.allclose(result[0], np.cos(phi)) assert np.allclose(result[1], np.cos(phi) * np.cos(theta)) - - @pytest.mark.parametrize( "wires, wire_order", [(3, (0, 1, 2)), (("a", "b", "c"), ("a", "b", "c"))] ) @@ -880,25 +878,25 @@ def test_derivatives_tape_batch(self, phi, execute_and_derivatives, batch_obs): x2 = np.sin(phi / 2) ** 2 / 2 expected2 = sum([x1, -x2, -x1, x2]) # zero expected = (expected1, expected2) - + assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) assert np.allclose(results[0][0], expected[0][0]) assert np.allclose(results[0][1], expected[0][1]) assert np.allclose(results[1], expected[1]) - + # Assert derivatives expected_jac1 = (-np.cos(phi), -3 * np.sin(phi)) x1_jac = -np.cos(phi / 2) * np.sin(phi / 2) / 2 x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero expected_jac = (expected_jac1, expected_jac2) - + assert len(jacs) == len(expected_jac) assert len(jacs[0]) == len(expected_jac[0]) assert np.allclose(jacs[0], expected_jac[0]) assert np.allclose(jacs[1], expected_jac[1]) - + @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) @pytest.mark.parametrize("execute_and_derivatives", [True, False]) @pytest.mark.parametrize("wires", (["a", "b", -3], [0, "target", "other_target"])) @@ -909,11 +907,7 @@ def test_derivatives_custom_wires( device = LightningDevice(wires=wires) qs = QuantumScript( - [ - qml.RX(theta, wires[0]), - qml.CNOT([wires[0], wires[1]]), - qml.RY(phi, wires[1]) - ], + [qml.RX(theta, wires[0]), qml.CNOT([wires[0], wires[1]]), qml.RY(phi, wires[1])], [qml.expval(qml.Z(wires[1]))], trainable_params=[0, 1], ) @@ -1248,13 +1242,13 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): results = device.execute((qs1, qs2)) jacs = device.compute_vjp((qs1, qs2), dy) - # Assert results + # Assert results expected1 = (-np.sin(phi) - 1, 3 * np.cos(phi)) x1 = np.cos(phi / 2) ** 2 / 2 x2 = np.sin(phi / 2) ** 2 / 2 expected2 = sum([x1, -x2, -x1, x2]) # zero expected = (expected1, expected2) - + assert len(results) == len(expected) assert len(results[0]) == len(expected[0]) assert np.allclose(results[0][0], expected[0][0]) @@ -1267,7 +1261,7 @@ def test_vjp_tape_batch(self, phi, execute_and_derivatives, batch_obs): x2_jac = np.sin(phi / 2) * np.cos(phi / 2) / 2 expected_jac2 = sum([x1_jac, -x2_jac, -x1_jac, x2_jac]) # zero expected_jac = (expected_jac1, expected_jac2) - + assert len(jacs) == len(expected_jac) == 2 assert np.allclose(jacs[0], expected_jac[0]) assert np.allclose(jacs[1], expected_jac[1]) @@ -1281,11 +1275,7 @@ def test_vjp_custom_wires(self, theta, phi, dev, wires, execute_and_derivatives, device = LightningDevice(wires=wires) qs = QuantumScript( - [ - qml.RX(theta, wires[0]), - qml.CNOT([wires[0], wires[1]]), - qml.RY(phi, wires[1]) - ], + [qml.RX(theta, wires[0]), qml.CNOT([wires[0], wires[1]]), qml.RY(phi, wires[1])], [qml.expval(qml.Z(wires[1]))], trainable_params=[0, 1], ) @@ -1294,7 +1284,7 @@ def test_vjp_custom_wires(self, theta, phi, dev, wires, execute_and_derivatives, res, jac = self.process_and_execute( device, qs, dy, execute_and_derivatives=execute_and_derivatives, obs_batch=batch_obs ) - + expected, expected_jac = self.calculate_reference( qs, dy, execute_and_derivatives=execute_and_derivatives ) From c93f1c5ebb2e249a38acc5e151304630afb4f88d Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 20 Aug 2024 14:04:17 +0000 Subject: [PATCH 048/130] Auto update version from '0.38.0-dev38' to '0.38.0-dev39' --- 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 358f5c7cc..b4c1ef439 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev38" +__version__ = "0.38.0-dev39" From 794c16625c9239bd202361e746a97ec3f59599bf Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 21 Aug 2024 14:12:13 +0000 Subject: [PATCH 049/130] Auto update version from '0.38.0-dev39' to '0.38.0-dev40' --- 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 b4c1ef439..d6ffb292a 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev39" +__version__ = "0.38.0-dev40" From 63ee6fd0f459ed50ed72c1033fc27f7a4e5044bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 12:18:20 -0400 Subject: [PATCH 050/130] fix issues with the unitary tests --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 6 +++++- tests/new_api/test_device.py | 1 + tests/test_native_mcm.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 5d6a0a4a0..8e32a20d0 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -370,6 +370,10 @@ def _supports_adjoint(circuit): return False return True +def _adjoint_ops(op: qml.operation.Operator) -> bool: + """Specify whether or not an Operator is supported by adjoint differentiation.""" + return adjoint_ops(op) + def _add_adjoint_transforms(program: TransformProgram) -> None: """Private helper function for ``preprocess`` that adds the transforms specific @@ -387,7 +391,7 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: program.add_transform(no_sampling, name=name) program.add_transform( decompose, - stopping_condition=adjoint_ops, + stopping_condition=_adjoint_ops, stopping_condition_shots=stopping_condition_shots, name=name, skip_initial_state_prep=False, diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 8f3de959c..de87dcf07 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -47,6 +47,7 @@ if device_name == "lightning.kokkos": from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( _add_adjoint_transforms, + _adjoint_ops, _supports_adjoint, accepted_observables, adjoint_measurements, diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index d17ab28a9..df5ba6cb4 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -92,7 +92,7 @@ def func(x, y): if device_name == "lightning.kokkos": with pytest.raises( - DeviceError, + qml.DeviceError, match=r"Measurement shadow\(wires=\[0\]\) not accepted with finite shots on " + device_name, ): From 360894599734e61a794c65df6e6e251e34b01386 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 21 Aug 2024 16:18:50 +0000 Subject: [PATCH 051/130] Auto update version from '0.38.0-dev40' to '0.38.0-dev41' --- 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 d6ffb292a..a8ba10998 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev40" +__version__ = "0.38.0-dev41" From 2ce52c39fa6381866bef3cda8d8a512ee20e984d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 13:02:47 -0400 Subject: [PATCH 052/130] Apply format --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 8e32a20d0..1b84c8a17 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -370,6 +370,7 @@ def _supports_adjoint(circuit): return False return True + def _adjoint_ops(op: qml.operation.Operator) -> bool: """Specify whether or not an Operator is supported by adjoint differentiation.""" return adjoint_ops(op) From a9b253f524f01a07ce725134a4fda9d00bdb56e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 15:28:33 -0400 Subject: [PATCH 053/130] first verion of unify devices clases --- .../core/_adjoint_jacobian_base.py | 337 +++++++++++++++ .../core/_measurements_base.py | 402 ++++++++++++++++++ .../core/_state_vector_base.py | 214 ++++++++++ .../lightning_kokkos/_adjoint_jacobian.py | 314 +------------- .../lightning_kokkos/_measurements.py | 337 +-------------- .../lightning_kokkos/_state_vector.py | 129 +----- .../lightning_qubit/_adjoint_jacobian.py | 309 +------------- .../lightning_qubit/_measurements.py | 339 +-------------- .../lightning_qubit/_state_vector.py | 107 +---- 9 files changed, 1014 insertions(+), 1474 deletions(-) create mode 100644 pennylane_lightning/core/_adjoint_jacobian_base.py create mode 100644 pennylane_lightning/core/_measurements_base.py create mode 100644 pennylane_lightning/core/_state_vector_base.py diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py new file mode 100644 index 000000000..48f2bf4ce --- /dev/null +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -0,0 +1,337 @@ +# 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""" +Internal methods for adjoint Jacobian differentiation method. +""" + +try: + from pennylane_lightning.lightning_kokkos_ops.algorithms import ( + AdjointJacobianC64, + AdjointJacobianC128, + create_ops_listC64, + create_ops_listC128, + ) +except ImportError: + pass + +from os import getenv +from typing import Any, List + +import numpy as np +import pennylane as qml +from pennylane import BasisState, QuantumFunctionError, StatePrep +from pennylane.measurements import Expectation, MeasurementProcess, State +from pennylane.operation import Operation +from pennylane.tape import QuantumTape + +# pylint: disable=import-error, no-name-in-module, ungrouped-imports +from pennylane_lightning.core._serialize import QuantumScriptSerializer +from pennylane_lightning.core.lightning_base import _chunk_iterable + + +class LightningBaseAdjointJacobian: + """Check and execute the adjoint Jacobian differentiation method. + + Args: + qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + batch_obs(bool): If serialized tape is to be batched or not. + """ + + def __init__(self, qubit_state: Any, batch_obs: bool = False) -> None: + self._qubit_state = qubit_state + self._state = qubit_state.state_vector + self._dtype = qubit_state.dtype + self._batch_obs = batch_obs + + self._jacobian_lightning = None + + @property + def qubit_state(self): + """Returns a handle to the Lightning[Device]StateVector object.""" + return self._qubit_state + + @property + def state(self): + """Returns a handle to the Lightning internal data object.""" + return self._state + + @property + def dtype(self): + """Returns the simulation data type.""" + return self._dtype + + @staticmethod + def _get_return_type( + measurements: List[MeasurementProcess], + ): + """Get the measurement return type. + + Args: + measurements (List[MeasurementProcess]): a list of measurement processes to check. + + Returns: + None, Expectation or State: a common return type of measurements. + """ + if not measurements: + return None + + if len(measurements) == 1 and measurements[0].return_type is State: + return State + + return Expectation + + def _set_jacobian_lightning(self): + """Virtual method to create the C++ frontend _jacobian_lightning.""" + pass + + def _process_jacobian_tape( + self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False + ): + """Process a tape, serializing and building a dictionary proper for + the adjoint Jacobian calculation in the C++ layer. + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. + split_obs (bool, optional): If splitting the observables in a list. Defaults to False. + + Returns: + dictionary: dictionary providing serialized data for Jacobian calculation. + """ + use_csingle = self._dtype == np.complex64 + + obs_serialized, obs_idx_offsets = QuantumScriptSerializer( + self._qubit_state.device_name, use_csingle, use_mpi, split_obs + ).serialize_observables(tape) + + ops_serialized, use_sp = QuantumScriptSerializer( + self._qubit_state.device_name, use_csingle, use_mpi, split_obs + ).serialize_ops(tape) + + ops_serialized = self._create_ops_list_lightning(*ops_serialized) + + # We need to filter out indices in trainable_params which do not + # correspond to operators. + trainable_params = sorted(tape.trainable_params) + if len(trainable_params) == 0: + return None + + tp_shift = [] + record_tp_rows = [] + all_params = 0 + + for op_idx, trainable_param in enumerate(trainable_params): + # get op_idx-th operator among differentiable operators + operation, _, _ = tape.get_operation(op_idx) + if isinstance(operation, Operation) and not isinstance( + operation, (BasisState, StatePrep) + ): + # We now just ignore non-op or state preps + tp_shift.append(trainable_param) + record_tp_rows.append(all_params) + all_params += 1 + + if use_sp: + # When the first element of the tape is state preparation. Still, I am not sure + # whether there must be only one state preparation... + tp_shift = [i - 1 for i in tp_shift] + + return { + "state_vector": self.state, + "obs_serialized": obs_serialized, + "ops_serialized": ops_serialized, + "tp_shift": tp_shift, + "record_tp_rows": record_tp_rows, + "all_params": all_params, + "obs_idx_offsets": obs_idx_offsets, + } + + @staticmethod + def _adjoint_jacobian_processing(jac): + """ + Post-process the Jacobian matrix returned by ``adjoint_jacobian`` for + the new return type system. + """ + jac = np.squeeze(jac) + + if jac.ndim == 0: + return np.array(jac) + + if jac.ndim == 1: + return tuple(np.array(j) for j in jac) + + # must be 2-dimensional + return tuple(tuple(np.array(j_) for j_ in j) for j in jac) + + def _handle_raises(self, tape: QuantumTape, is_jacobian: bool, grad_vec=None): + """Handle the raises related with the tape for computing the Jacobian with the adjoint method or the vector-Jacobian products.""" + + if tape.shots: + raise QuantumFunctionError( + "Requested adjoint differentiation to be computed with finite shots. " + "The derivative is always exact when using the adjoint " + "differentiation method." + ) + + tape_return_type = self._get_return_type(tape.measurements) + + if is_jacobian: + if not tape_return_type: + # the tape does not have measurements + return True + + if tape_return_type is State: + raise QuantumFunctionError( + "Adjoint differentiation method does not support measurement StateMP." + ) + + if not is_jacobian: + if qml.math.allclose(grad_vec, 0.0) or not tape_return_type: + # the tape does not have measurements or the gradient is 0.0 + return True + + if tape_return_type is State: + raise QuantumFunctionError( + "Adjoint differentiation does not support State measurements." + ) + + if any(m.return_type is not Expectation for m in tape.measurements): + raise QuantumFunctionError( + "Adjoint differentiation method does not support expectation return type " + "mixed with other return types" + ) + + return False + + def calculate_jacobian(self, tape: QuantumTape): + """Computes the Jacobian with the adjoint method. + + .. code-block:: python + + statevector = Lightning[Device]StateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + jacobian = LightningKokkosAdjointJacobian(statevector).calculate_jacobian(tape) + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + + Returns: + The Jacobian of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=True) + + if empty_array: + return np.array([], dtype=self._dtype) + + processed_data = self._process_jacobian_tape(tape) + + if not processed_data: # training_params is empty + return np.array([], dtype=self._dtype) + + trainable_params = processed_data["tp_shift"] + + # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. + # This will allow use of Lightning with adjoint for large-qubit numbers AND large + # numbers of observables, enabling choice between compute time and memory use. + requested_threads = int(getenv("OMP_NUM_THREADS", "1")) + + if self._batch_obs and requested_threads > 1: + obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) + jac = [] + for obs_chunk in obs_partitions: + jac_local = self._jacobian_lightning( + processed_data["state_vector"], + obs_chunk, + processed_data["ops_serialized"], + trainable_params, + ) + jac.extend(jac_local) + else: + jac = self._jacobian_lightning( + processed_data["state_vector"], + processed_data["obs_serialized"], + processed_data["ops_serialized"], + trainable_params, + ) + jac = np.array(jac) + jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac + jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) + jac_r[:, processed_data["record_tp_rows"]] = jac + + return self._adjoint_jacobian_processing(jac_r) + + # pylint: disable=inconsistent-return-statements + def calculate_vjp(self, tape: QuantumTape, grad_vec): + """Compute the vector-Jacobian products of a tape. + + .. code-block:: python + + statevector = Lightning[Device]StateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + vjp = LightningKokkosAdjointJacobian(statevector).calculate_vjp(tape, grad_vec) + + computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where + + .. math:: + + w_k = dy_k \\cdot J_{k,j} + + Here, :math:`dy` is the workflow cotangent (grad_vec), and :math:`J` the Jacobian. + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + grad_vec (tensor_like): Gradient-output vector, also called `dy` or cotangent. Must have shape matching the output + shape of the corresponding tape, i.e. number of measurements if the return type is expectation. + + Returns: + The vector-Jacobian products of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=False, grad_vec=grad_vec) + + if empty_array: + return qml.math.convert_like(np.zeros(len(tape.trainable_params)), grad_vec) + + # Proceed, because tape_return_type is Expectation. + if qml.math.ndim(grad_vec) == 0: + grad_vec = (grad_vec,) + + if len(grad_vec) != len(tape.measurements): + raise ValueError( + "Number of observables in the tape must be the same as the " + "length of grad_vec in the vjp method" + ) + + if np.iscomplexobj(grad_vec): + raise ValueError( + "The vjp method only works with a real-valued grad_vec when the " + "tape is returning an expectation value" + ) + + ham = qml.simplify(qml.dot(grad_vec, [m.obs for m in tape.measurements])) + + num_params = len(tape.trainable_params) + + if num_params == 0: + return np.array([], dtype=self.qubit_state.dtype) + + new_tape = qml.tape.QuantumScript( + tape.operations, + [qml.expval(ham)], + shots=tape.shots, + trainable_params=tape.trainable_params, + ) + + return self.calculate_jacobian(new_tape) diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py new file mode 100644 index 000000000..92504415d --- /dev/null +++ b/pennylane_lightning/core/_measurements_base.py @@ -0,0 +1,402 @@ +# 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. +""" + +# pylint: disable=import-error, no-name-in-module, ungrouped-imports +try: + from pennylane_lightning.lightning_kokkos_ops import MeasurementsC64, MeasurementsC128 +except ImportError: + pass + +from typing import Callable, List, Union, Any + +import numpy as np +import pennylane as qml +from pennylane.devices.qubit.sampling import _group_measurements +from pennylane.measurements import ( + ClassicalShadowMP, + CountsMP, + ExpectationMP, + MeasurementProcess, + ProbabilityMP, + SampleMeasurement, + ShadowExpvalMP, + Shots, + StateMeasurement, + VarianceMP, +) +from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum +from pennylane.tape import QuantumScript +from pennylane.typing import Result, TensorLike +from pennylane.wires import Wires + +from pennylane_lightning.core._serialize import QuantumScriptSerializer + + +class LightningBaseMeasurements: + """Lightning Kokkos Measurements class + + Measures the state provided by the Lightning[Device]StateVector class. + + Args: + qubit_state(Lightning[Device]StateVector): Lightning state-vector class containing the state vector to be measured. + """ + + def __init__( + self, + qubit_state: Any, + ) -> None: + self._qubit_state = qubit_state + self._dtype = qubit_state.dtype + # self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) + + self._measurement_lightning = None + + @property + def qubit_state(self): + """Returns a handle to the Lightning[Device]StateVector object.""" + return self._qubit_state + + @property + def dtype(self): + """Returns the simulation data type.""" + return self._dtype + + def _set_measurement_lightning(self): + """Virtual method to create the C++ frontend _measurement_lightning.""" + pass + + + 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 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 + """ + diagonalizing_gates = measurementprocess.diagonalizing_gates() + self._qubit_state.apply_operations(diagonalizing_gates) + state_array = self._qubit_state.state + wires = Wires(range(self._qubit_state.num_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): + """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 isinstance(measurementprocess.obs, qml.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 ( + isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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.expval(ob_serialized) + + return self._measurement_lightning.expval( + measurementprocess.obs.name, measurementprocess.obs.wires + ) + + 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: + self._qubit_state.apply_operations(diagonalizing_gates) + results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) + if diagonalizing_gates: + self._qubit_state.apply_operations( + [qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)] + ) + return results + + 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 isinstance(measurementprocess.obs, qml.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 ( + isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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]: + """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 isinstance(measurementprocess.obs, (qml.Identity, qml.Projector)): + return self.state_diagonalizing_gates + return self.expval + + if isinstance(measurementprocess, ProbabilityMP): + return self.probs + + if isinstance(measurementprocess, VarianceMP): + if isinstance(measurementprocess.obs, (qml.Identity, qml.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 + + 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, mid_measurements=None) -> 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.kokkos``. + + Args: + circuit (QuantumScript): The single circuit to simulate + mid_measurements (None, dict): Dictionary of mid-circuit measurements + + Returns: + Tuple[TensorLike]: The measurement results + """ + + if not circuit.shots: + # analytic case + if len(circuit.measurements) == 1: + return self.measurement(circuit.measurements[0]) + + return tuple(self.measurement(mp) for mp in circuit.measurements) + + # finite-shot case + results = self.measure_with_samples( + circuit.measurements, + shots=circuit.shots, + mid_measurements=mid_measurements, + ) + + if len(circuit.measurements) == 1: + if circuit.shots.has_partitioned_shots: + return tuple(res[0] for res in results) + + return results[0] + + return results + + def measure_with_samples( + self, + measurements: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], + shots: Shots, + mid_measurements=None, + ) -> List[TensorLike]: + """ + Returns the samples of the measurement process performed on the given state. + This function assumes that the user-defined wire labels in the measurement process + have already been mapped to integer wires used in the device. + + Args: + measurements (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]): + The sample measurements to perform + shots (Shots): The number of samples to take + mid_measurements (None, dict): Dictionary of mid-circuit measurements + + Returns: + List[TensorLike[Any]]: Sample measurement results + """ + # last N measurements are sampling MCMs in ``dynamic_one_shot`` execution mode + mps = measurements[0 : -len(mid_measurements)] if mid_measurements else measurements + groups, indices = _group_measurements(mps) + + all_res = [] + for group in groups: + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( + group[0].obs, SparseHamiltonian + ): + raise TypeError( + "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." + ) + if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, (Hamiltonian, Sum)): + raise TypeError("VarianceMP(Hamiltonian/Sum) cannot be computed with samples.") + if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)): + raise TypeError( + "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." + ) + if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): + all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) + elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): + all_res.extend(self._measure_sum_with_samples(group, shots)) + else: + all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) + + # reorder results + flat_indices = [] + for row in indices: + flat_indices += row + sorted_res = tuple( + res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]]) + ) + + # append MCM samples + if mid_measurements: + sorted_res += tuple(mid_measurements.values()) + + # put the shot vector axis before the measurement axis + if shots.has_partitioned_shots: + sorted_res = tuple(zip(*sorted_res)) + + return sorted_res + + def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False): + if len(mps) == 1: + diagonalizing_gates = mps[0].diagonalizing_gates() + elif all(mp.obs for mp in mps): + diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0] + else: + diagonalizing_gates = [] + + if adjoint: + diagonalizing_gates = [ + qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates) + ] + + self._qubit_state.apply_operations(diagonalizing_gates) + + def _measure_with_samples_diagonalizing_gates( + self, + mps: List[SampleMeasurement], + shots: Shots, + ) -> TensorLike: + """ + Returns the samples of the measurement process performed on the given state, + by rotating the state into the measurement basis using the diagonalizing gates + given by the measurement process. + + Args: + mps (~.measurements.SampleMeasurement): The sample measurements to perform + shots (~.measurements.Shots): The number of samples to take + + Returns: + TensorLike[Any]: Sample measurement results + """ + pass + + def _measure_hamiltonian_with_samples( + self, + mp: List[SampleMeasurement], + shots: Shots, + ): + # the list contains only one element based on how we group measurements + mp = mp[0] + + # if the measurement process involves a Hamiltonian, measure each + # of the terms separately and sum + def _sum_for_single_shot(s): + results = self.measure_with_samples( + [ExpectationMP(t) for t in mp.obs.terms()[1]], + s, + ) + return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) + + unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) + return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] + + def _measure_sum_with_samples( + self, + mp: List[SampleMeasurement], + shots: Shots, + ): + # the list contains only one element based on how we group measurements + mp = mp[0] + + # if the measurement process involves a Sum, measure each + # of the terms separately and sum + def _sum_for_single_shot(s): + results = self.measure_with_samples( + [ExpectationMP(t) for t in mp.obs], + s, + ) + return sum(results) + + unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) + return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py new file mode 100644 index 000000000..d16512c54 --- /dev/null +++ b/pennylane_lightning/core/_state_vector_base.py @@ -0,0 +1,214 @@ +# 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. +""" + +import numpy as np +from pennylane import BasisState, StatePrep +from pennylane.measurements import MidMeasureMP +from pennylane.tape import QuantumScript +from pennylane.wires import Wires + + +class LightningBaseStateVector: + """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``. Default is ``np.complex128`` + device_name(string): state vector device name. Options: ["lightning.qubit"] + """ + + def __init__(self, num_wires, dtype=np.complex128): + + if dtype not in [np.complex64, np.complex128]: + raise TypeError(f"Unsupported complex type: {dtype}") + + self._num_wires = num_wires + self._wires = Wires(range(num_wires)) + self._dtype = dtype + + self._device_name = None + self._qubit_state = None + + @property + def dtype(self): + """Returns the state vector data type.""" + return self._dtype + + @property + def device_name(self): + """Returns the state vector device name.""" + return self._device_name + + @property + 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 + + @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] + """ + pass + def _state_dtype(self): + """Binding to Lightning Managed state vector C++ class. + + Returns: the state vector class + """ + pass + + def reset_state(self): + """Reset the device's state""" + # init the state vector to |00..0> + self._qubit_state.resetStateVector() + + 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)`` + or broadcasted state of shape ``(batch_size, 2**len(wires))`` + device_wires (Wires): wires that get initialized in the state + """ + pass + + 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. + """ + if not set(state.tolist()).issubset({0, 1}): + raise ValueError("BasisState parameter must consist of 0 or 1 integers.") + + if len(state) != len(wires): + raise ValueError("BasisState parameter and wires must be of equal length.") + + # Return a computational basis state over all wires. + self._qubit_state.setBasisState(list(state), list(wires)) + + def _apply_lightning_controlled(self, operation): + """Apply an arbitrary controlled operation to the state tensor. + + Args: + operation (~pennylane.operation.Operation): controlled operation to apply + + Returns: + None + """ + pass + + def _apply_lightning_midmeasure( + self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str + ): + """Execute a MidMeasureMP operation and return the sample in mid_measurements. + + Args: + operation (~pennylane.operation.Operation): mid-circuit measurement + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. + + Returns: + None + """ + pass + + def _apply_lightning( + self, operations, mid_measurements: dict = None, postselect_mode: str = None + ): + """Apply a list of operations to the state tensor. + + Args: + operations (list[~pennylane.operation.Operation]): operations to apply + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + None + """ + pass + + def apply_operations( + self, operations, mid_measurements: dict = None, postselect_mode: str = None + ): + """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:] + self._apply_lightning( + operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + + def get_final_state( + self, + circuit: QuantumScript, + mid_measurements: dict = None, + postselect_mode: str = 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 + mid_measurements (None, dict): Dictionary of mid-circuit measurements + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + LightningStateVector: Lightning final state class. + + """ + self.apply_operations( + circuit.operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + + return self diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index bc79ade35..c5dd18d82 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -25,315 +25,23 @@ except ImportError: pass -from os import getenv -from typing import List - import numpy as np -import pennylane as qml -from pennylane import BasisState, QuantumFunctionError, StatePrep -from pennylane.measurements import Expectation, MeasurementProcess, State -from pennylane.operation import Operation -from pennylane.tape import QuantumTape - -# pylint: disable=import-error, no-name-in-module, ungrouped-imports -from pennylane_lightning.core._serialize import QuantumScriptSerializer -from pennylane_lightning.core.lightning_base import _chunk_iterable from ._state_vector import LightningKokkosStateVector - - -class LightningKokkosAdjointJacobian: - """Check and execute the adjoint Jacobian differentiation method. - - Args: - qubit_state(LightningKokkosStateVector): State Vector to calculate the adjoint Jacobian with. - batch_obs(bool): If serialized tape is to be batched or not. - """ - - def __init__(self, kokkos_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: - self._qubit_state = kokkos_state - self._state = kokkos_state.state_vector - self._dtype = kokkos_state.dtype +from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian + +class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): + + def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: + super().__init__(qubit_state, batch_obs) + + # Initialize the C++ bind + self._set_jacobian_lightning() + + def _set_jacobian_lightning(self): self._jacobian_lightning = ( AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() ) self._create_ops_list_lightning = ( create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 ) - self._batch_obs = batch_obs - - @property - def qubit_state(self): - """Returns a handle to the LightningKokkosStateVector object.""" - return self._qubit_state - - @property - def state(self): - """Returns a handle to the Lightning internal data object.""" - return self._state - - @property - def dtype(self): - """Returns the simulation data type.""" - return self._dtype - - @staticmethod - def _get_return_type( - measurements: List[MeasurementProcess], - ): - """Get the measurement return type. - - Args: - measurements (List[MeasurementProcess]): a list of measurement processes to check. - - Returns: - None, Expectation or State: a common return type of measurements. - """ - if not measurements: - return None - - if len(measurements) == 1 and measurements[0].return_type is State: - return State - - return Expectation - - def _process_jacobian_tape( - self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False - ): - """Process a tape, serializing and building a dictionary proper for - the adjoint Jacobian calculation in the C++ layer. - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. - split_obs (bool, optional): If splitting the observables in a list. Defaults to False. - - Returns: - dictionary: dictionary providing serialized data for Jacobian calculation. - """ - use_csingle = self._dtype == np.complex64 - - obs_serialized, obs_idx_offsets = QuantumScriptSerializer( - self._qubit_state.device_name, use_csingle, use_mpi, split_obs - ).serialize_observables(tape) - - ops_serialized, use_sp = QuantumScriptSerializer( - self._qubit_state.device_name, use_csingle, use_mpi, split_obs - ).serialize_ops(tape) - - ops_serialized = self._create_ops_list_lightning(*ops_serialized) - - # We need to filter out indices in trainable_params which do not - # correspond to operators. - trainable_params = sorted(tape.trainable_params) - if len(trainable_params) == 0: - return None - - tp_shift = [] - record_tp_rows = [] - all_params = 0 - - for op_idx, trainable_param in enumerate(trainable_params): - # get op_idx-th operator among differentiable operators - operation, _, _ = tape.get_operation(op_idx) - if isinstance(operation, Operation) and not isinstance( - operation, (BasisState, StatePrep) - ): - # We now just ignore non-op or state preps - tp_shift.append(trainable_param) - record_tp_rows.append(all_params) - all_params += 1 - - if use_sp: - # When the first element of the tape is state preparation. Still, I am not sure - # whether there must be only one state preparation... - tp_shift = [i - 1 for i in tp_shift] - - return { - "state_vector": self.state, - "obs_serialized": obs_serialized, - "ops_serialized": ops_serialized, - "tp_shift": tp_shift, - "record_tp_rows": record_tp_rows, - "all_params": all_params, - "obs_idx_offsets": obs_idx_offsets, - } - - @staticmethod - def _adjoint_jacobian_processing(jac): - """ - Post-process the Jacobian matrix returned by ``adjoint_jacobian`` for - the new return type system. - """ - jac = np.squeeze(jac) - - if jac.ndim == 0: - return np.array(jac) - - if jac.ndim == 1: - return tuple(np.array(j) for j in jac) - - # must be 2-dimensional - return tuple(tuple(np.array(j_) for j_ in j) for j in jac) - - def _handle_raises(self, tape: QuantumTape, is_jacobian: bool, grad_vec=None): - """Handle the raises related with the tape for computing the Jacobian with the adjoint method or the vector-Jacobian products.""" - - if tape.shots: - raise QuantumFunctionError( - "Requested adjoint differentiation to be computed with finite shots. " - "The derivative is always exact when using the adjoint " - "differentiation method." - ) - - tape_return_type = self._get_return_type(tape.measurements) - - if is_jacobian: - if not tape_return_type: - # the tape does not have measurements - return True - - if tape_return_type is State: - raise QuantumFunctionError( - "Adjoint differentiation method does not support measurement StateMP." - ) - - if not is_jacobian: - if qml.math.allclose(grad_vec, 0.0) or not tape_return_type: - # the tape does not have measurements or the gradient is 0.0 - return True - - if tape_return_type is State: - raise QuantumFunctionError( - "Adjoint differentiation does not support State measurements." - ) - - if any(m.return_type is not Expectation for m in tape.measurements): - raise QuantumFunctionError( - "Adjoint differentiation method does not support expectation return type " - "mixed with other return types" - ) - - return False - - def calculate_jacobian(self, tape: QuantumTape): - """Computes the Jacobian with the adjoint method. - - .. code-block:: python - - statevector = LightningKokkosStateVector(num_wires=num_wires) - statevector = statevector.get_final_state(tape) - jacobian = LightningKokkosAdjointJacobian(statevector).calculate_jacobian(tape) - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - - Returns: - The Jacobian of a tape. - """ - - empty_array = self._handle_raises(tape, is_jacobian=True) - - if empty_array: - return np.array([], dtype=self._dtype) - - processed_data = self._process_jacobian_tape(tape) - - if not processed_data: # training_params is empty - return np.array([], dtype=self._dtype) - - trainable_params = processed_data["tp_shift"] - - # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. - # This will allow use of Lightning with adjoint for large-qubit numbers AND large - # numbers of observables, enabling choice between compute time and memory use. - requested_threads = int(getenv("OMP_NUM_THREADS", "1")) - - if self._batch_obs and requested_threads > 1: - obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) - jac = [] - for obs_chunk in obs_partitions: - jac_local = self._jacobian_lightning( - processed_data["state_vector"], - obs_chunk, - processed_data["ops_serialized"], - trainable_params, - ) - jac.extend(jac_local) - else: - jac = self._jacobian_lightning( - processed_data["state_vector"], - processed_data["obs_serialized"], - processed_data["ops_serialized"], - trainable_params, - ) - jac = np.array(jac) - jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac - jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) - jac_r[:, processed_data["record_tp_rows"]] = jac - - return self._adjoint_jacobian_processing(jac_r) - - # pylint: disable=inconsistent-return-statements - def calculate_vjp(self, tape: QuantumTape, grad_vec): - """Compute the vector-Jacobian products of a tape. - - .. code-block:: python - - statevector = LightningKokkosStateVector(num_wires=num_wires) - statevector = statevector.get_final_state(tape) - vjp = LightningKokkosAdjointJacobian(statevector).calculate_vjp(tape, grad_vec) - - computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where - - .. math:: - - w_k = dy_k \\cdot J_{k,j} - - Here, :math:`dy` is the workflow cotangent (grad_vec), and :math:`J` the Jacobian. - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - grad_vec (tensor_like): Gradient-output vector, also called `dy` or cotangent. Must have shape matching the output - shape of the corresponding tape, i.e. number of measurements if the return type is expectation. - - Returns: - The vector-Jacobian products of a tape. - """ - - empty_array = self._handle_raises(tape, is_jacobian=False, grad_vec=grad_vec) - - if empty_array: - return qml.math.convert_like(np.zeros(len(tape.trainable_params)), grad_vec) - - # Proceed, because tape_return_type is Expectation. - if qml.math.ndim(grad_vec) == 0: - grad_vec = (grad_vec,) - - if len(grad_vec) != len(tape.measurements): - raise ValueError( - "Number of observables in the tape must be the same as the " - "length of grad_vec in the vjp method" - ) - - if np.iscomplexobj(grad_vec): - raise ValueError( - "The vjp method only works with a real-valued grad_vec when the " - "tape is returning an expectation value" - ) - - ham = qml.simplify(qml.dot(grad_vec, [m.obs for m in tape.measurements])) - - num_params = len(tape.trainable_params) - - if num_params == 0: - return np.array([], dtype=self.qubit_state.dtype) - - new_tape = qml.tape.QuantumScript( - tape.operations, - [qml.expval(ham)], - shots=tape.shots, - trainable_params=tape.trainable_params, - ) - - return self.calculate_jacobian(new_tape) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index cba0bd708..de83455e5 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -21,32 +21,20 @@ except ImportError: pass -from typing import Callable, List, Union +from typing import List import numpy as np import pennylane as qml -from pennylane.devices.qubit.sampling import _group_measurements from pennylane.measurements import ( - ClassicalShadowMP, CountsMP, - ExpectationMP, - MeasurementProcess, - ProbabilityMP, SampleMeasurement, - ShadowExpvalMP, Shots, - StateMeasurement, - VarianceMP, ) -from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum -from pennylane.tape import QuantumScript -from pennylane.typing import Result, TensorLike -from pennylane.wires import Wires +from pennylane.typing import TensorLike +from pennylane_lightning.core._measurements_base import LightningBaseMeasurements -from pennylane_lightning.core._serialize import QuantumScriptSerializer - -class LightningKokkosMeasurements: +class LightningKokkosMeasurements(LightningBaseMeasurements): """Lightning Kokkos Measurements class Measures the state provided by the LightningKokkosStateVector class. @@ -59,20 +47,11 @@ def __init__( self, kokkos_state, ) -> None: - self._qubit_state = kokkos_state - self._dtype = kokkos_state.dtype + + super().__init__(kokkos_state) + self._measurement_lightning = self._measurement_dtype()(kokkos_state.state_vector) - @property - def qubit_state(self): - """Returns a handle to the LightningKokkosStateVector object.""" - return self._qubit_state - - @property - def dtype(self): - """Returns the simulation data type.""" - return self._dtype - def _measurement_dtype(self): """Binding to Lightning Kokkos Measurements C++ class. @@ -80,268 +59,6 @@ def _measurement_dtype(self): """ 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 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 - """ - diagonalizing_gates = measurementprocess.diagonalizing_gates() - self._qubit_state.apply_operations(diagonalizing_gates) - state_array = self._qubit_state.state - wires = Wires(range(self._qubit_state.num_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): - """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 isinstance(measurementprocess.obs, qml.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 ( - isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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.expval(ob_serialized) - - return self._measurement_lightning.expval( - measurementprocess.obs.name, measurementprocess.obs.wires - ) - - 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: - self._qubit_state.apply_operations(diagonalizing_gates) - results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) - if diagonalizing_gates: - self._qubit_state.apply_operations( - [qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)] - ) - return results - - 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 isinstance(measurementprocess.obs, qml.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 ( - isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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]: - """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 isinstance(measurementprocess.obs, (qml.Identity, qml.Projector)): - return self.state_diagonalizing_gates - return self.expval - - if isinstance(measurementprocess, ProbabilityMP): - return self.probs - - if isinstance(measurementprocess, VarianceMP): - if isinstance(measurementprocess.obs, (qml.Identity, qml.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 - - 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, mid_measurements=None) -> 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.kokkos``. - - Args: - circuit (QuantumScript): The single circuit to simulate - mid_measurements (None, dict): Dictionary of mid-circuit measurements - - Returns: - Tuple[TensorLike]: The measurement results - """ - - if not circuit.shots: - # analytic case - if len(circuit.measurements) == 1: - return self.measurement(circuit.measurements[0]) - - return tuple(self.measurement(mp) for mp in circuit.measurements) - - # finite-shot case - results = self.measure_with_samples( - circuit.measurements, - shots=circuit.shots, - mid_measurements=mid_measurements, - ) - - if len(circuit.measurements) == 1: - if circuit.shots.has_partitioned_shots: - return tuple(res[0] for res in results) - - return results[0] - - return results - - def measure_with_samples( - self, - measurements: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], - shots: Shots, - mid_measurements=None, - ) -> List[TensorLike]: - """ - Returns the samples of the measurement process performed on the given state. - This function assumes that the user-defined wire labels in the measurement process - have already been mapped to integer wires used in the device. - - Args: - measurements (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]): - The sample measurements to perform - shots (Shots): The number of samples to take - mid_measurements (None, dict): Dictionary of mid-circuit measurements - - Returns: - List[TensorLike[Any]]: Sample measurement results - """ - # last N measurements are sampling MCMs in ``dynamic_one_shot`` execution mode - mps = measurements[0 : -len(mid_measurements)] if mid_measurements else measurements - groups, indices = _group_measurements(mps) - - all_res = [] - for group in groups: - if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( - group[0].obs, SparseHamiltonian - ): - raise TypeError( - "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." - ) - if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, (Hamiltonian, Sum)): - raise TypeError("VarianceMP(Hamiltonian/Sum) cannot be computed with samples.") - if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)): - raise TypeError( - "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." - ) - if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): - all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) - elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): - all_res.extend(self._measure_sum_with_samples(group, shots)) - else: - all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) - - # reorder results - flat_indices = [] - for row in indices: - flat_indices += row - sorted_res = tuple( - res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]]) - ) - - # append MCM samples - if mid_measurements: - sorted_res += tuple(mid_measurements.values()) - - # put the shot vector axis before the measurement axis - if shots.has_partitioned_shots: - sorted_res = tuple(zip(*sorted_res)) - - return sorted_res - - def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False): - if len(mps) == 1: - diagonalizing_gates = mps[0].diagonalizing_gates() - elif all(mp.obs for mp in mps): - diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0] - else: - diagonalizing_gates = [] - - if adjoint: - diagonalizing_gates = [ - qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates) - ] - - self._qubit_state.apply_operations(diagonalizing_gates) - def _measure_with_samples_diagonalizing_gates( self, mps: List[SampleMeasurement], @@ -399,43 +116,3 @@ def _process_single_shot(samples): return ( tuple(zip(*processed_samples)) if shots.has_partitioned_shots else processed_samples[0] ) - - def _measure_hamiltonian_with_samples( - self, - mp: List[SampleMeasurement], - shots: Shots, - ): - # the list contains only one element based on how we group measurements - mp = mp[0] - - # if the measurement process involves a Hamiltonian, measure each - # of the terms separately and sum - def _sum_for_single_shot(s): - results = self.measure_with_samples( - [ExpectationMP(t) for t in mp.obs.terms()[1]], - s, - ) - return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) - - unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) - return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] - - def _measure_sum_with_samples( - self, - mp: List[SampleMeasurement], - shots: Shots, - ): - # the list contains only one element based on how we group measurements - mp = mp[0] - - # if the measurement process involves a Sum, measure each - # of the terms separately and sum - def _sum_for_single_shot(s): - results = self.measure_with_samples( - [ExpectationMP(t) for t in mp.obs], - s, - ) - return sum(results) - - unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) - return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 72b83592c..ba0ebbe1d 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -28,20 +28,22 @@ import numpy as np import pennylane as qml -from pennylane import BasisState, DeviceError, StatePrep +from pennylane import DeviceError from pennylane.measurements import MidMeasureMP from pennylane.ops import Conditional from pennylane.ops.op_math import Adjoint from pennylane.tape import QuantumScript from pennylane.wires import Wires -# pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import global_phase_diagonal from ._measurements import LightningKokkosMeasurements +from pennylane_lightning.core._state_vector_base import LightningBaseStateVector -class LightningKokkosStateVector: # pylint: disable=too-few-public-methods + + +class LightningKokkosStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods """Lightning Kokkos state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -65,25 +67,21 @@ def __init__( kokkos_args=None, sync=True, ): # pylint: disable=too-many-arguments - self._num_wires = num_wires - self._wires = Wires(range(num_wires)) - self._dtype = dtype - - self._kokkos_config = {} - self._sync = sync - - if dtype not in [np.complex64, np.complex128]: - raise TypeError(f"Unsupported complex type: {dtype}") if device_name != "lightning.kokkos": raise DeviceError(f'The device name "{device_name}" is not a valid option.') + super().__init__(num_wires, dtype) + self._device_name = device_name + self._kokkos_config = {} + self._sync = sync + if kokkos_args is None: - self._kokkos_state = self._state_dtype()(self.num_wires) + self._qubit_state = self._state_dtype()(self.num_wires) elif isinstance(kokkos_args, InitializationSettings): - self._kokkos_state = self._state_dtype()(self.num_wires, kokkos_args) + self._qubit_state = self._state_dtype()(self.num_wires, kokkos_args) else: raise TypeError( f"Argument kokkos_args must be of type {type(InitializationSettings())} but it is of {type(kokkos_args)}." @@ -92,30 +90,6 @@ def __init__( if not self._kokkos_config: self._kokkos_config = self._kokkos_configuration() - @property - def dtype(self): - """Returns the state vector data type.""" - return self._dtype - - @property - def device_name(self): - """Returns the state vector device name.""" - return self._device_name - - @property - 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 - - @property - def state_vector(self): - """Returns a handle to the state vector.""" - return self._kokkos_state @property def state(self): @@ -155,7 +129,7 @@ def sync_h2d(self, state_vector): >>> print(res) 1.0 """ - self._kokkos_state.HostToDevice(state_vector.ravel(order="C")) + self._qubit_state.HostToDevice(state_vector.ravel(order="C")) def sync_d2h(self, state_vector): """Copy the state vector data on device to a state vector on the host provided @@ -174,7 +148,7 @@ def sync_d2h(self, state_vector): >>> print(state_vector) [0.+0.j 1.+0.j] """ - self._kokkos_state.DeviceToHost(state_vector.ravel(order="C")) + self._qubit_state.DeviceToHost(state_vector.ravel(order="C")) def _kokkos_configuration(self): """Get the default configuration of the kokkos device. @@ -190,11 +164,6 @@ def _state_dtype(self): """ return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 - def reset_state(self): - """Reset the device's state""" - # init the state vector to |00..0> - self._kokkos_state.resetStateVector() - def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: @@ -203,7 +172,7 @@ def _apply_state_vector(self, state, device_wires: Wires): device_wires (Wires): wires that get initialized in the state """ - if isinstance(state, self._kokkos_state.__class__): + if isinstance(state, self._qubit_state.__class__): state_data = allocate_aligned_array(state.size, np.dtype(self.dtype), True) state.DeviceToHost(state_data) state = state_data @@ -215,27 +184,7 @@ def _apply_state_vector(self, state, device_wires: Wires): self.sync_h2d(np.reshape(state, output_shape)) return - self._kokkos_state.setStateVector(state, list(device_wires)) # this operation on device - - 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. - """ - if not set(state.tolist()).issubset({0, 1}): - raise ValueError("BasisState parameter must consist of 0 or 1 integers.") - - if len(state) != len(wires): - raise ValueError("BasisState parameter and wires must be of equal length.") - - # Return a computational basis state over all wires. - self._kokkos_state.setBasisState(list(state), list(wires)) + self._qubit_state.setStateVector(state, list(device_wires)) # this operation on device def _apply_lightning_controlled(self, operation): """Apply an arbitrary controlled operation to the state tensor. @@ -275,10 +224,10 @@ def _apply_lightning_midmeasure( """ wires = self.wires.indices(operation.wires) wire = list(wires)[0] - circuit = QuantumScript([], [qml.sample(wires=operation.wires)], shots=1) if postselect_mode == "fill-shots" and operation.postselect is not None: sample = operation.postselect else: + circuit = QuantumScript([], [qml.sample(wires=operation.wires)], shots=1) sample = LightningKokkosMeasurements(self).measure_final_state(circuit) sample = np.squeeze(sample) mid_measurements[operation] = sample @@ -343,47 +292,3 @@ def _apply_lightning( # To support older versions of PL method(operation.matrix, wires, False) - def apply_operations( - self, operations, mid_measurements: dict = None, postselect_mode: str = None - ): - """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:] - - self._apply_lightning( - operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - - def get_final_state( - self, - circuit: QuantumScript, - mid_measurements: dict = None, - postselect_mode: str = 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 - mid_measurements (None, dict): Dictionary of mid-circuit measurements - postselect_mode (str): Configuration for handling shots with mid-circuit measurement - postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to - keep the same number of shots. Default is ``None``. - - Returns: - LightningStateVector: Lightning final state class. - - """ - self.apply_operations( - circuit.operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - - return self diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 5aecfcbb6..9cfc59779 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -14,20 +14,7 @@ r""" Internal methods for adjoint Jacobian differentiation method. """ -from os import getenv -from typing import List -import numpy as np -import pennylane as qml -from pennylane import BasisState, QuantumFunctionError, StatePrep -from pennylane.measurements import Expectation, MeasurementProcess, State -from pennylane.operation import Operation -from pennylane.tape import QuantumTape - -from pennylane_lightning.core._serialize import QuantumScriptSerializer -from pennylane_lightning.core.lightning_base import _chunk_iterable - -# pylint: disable=import-error, no-name-in-module, ungrouped-imports try: from pennylane_lightning.lightning_qubit_ops.algorithms import ( AdjointJacobianC64, @@ -38,297 +25,23 @@ except ImportError: pass -from ._state_vector import LightningStateVector - - -class LightningAdjointJacobian: - """Check and execute the adjoint Jacobian differentiation method. +import numpy as np - Args: - qubit_state(LightningStateVector): State Vector to calculate the adjoint Jacobian with. - batch_obs(bool): If serialized tape is to be batched or not. - """ +from ._state_vector import LightningStateVector +from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian +class LightningAdjointJacobian(LightningBaseAdjointJacobian): + def __init__(self, qubit_state: LightningStateVector, batch_obs: bool = False) -> None: - self._qubit_state = qubit_state - self._state = qubit_state.state_vector - self._dtype = qubit_state.dtype + super().__init__(qubit_state, batch_obs) + + # Initialize the C++ bind + self._set_jacobian_lightning() + + def _set_jacobian_lightning(self): self._jacobian_lightning = ( AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() ) self._create_ops_list_lightning = ( create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 ) - self._batch_obs = batch_obs - - @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 - - @staticmethod - def _get_return_type( - measurements: List[MeasurementProcess], - ): - """Get the measurement return type. - - Args: - measurements (List[MeasurementProcess]): a list of measurement processes to check. - - Returns: - None, Expectation or State: a common return type of measurements. - """ - if not measurements: - return None - - if len(measurements) == 1 and measurements[0].return_type is State: - return State - - return Expectation - - def _process_jacobian_tape( - self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False - ): - """Process a tape, serializing and building a dictionary proper for - the adjoint Jacobian calculation in the C++ layer. - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. - split_obs (bool, optional): If splitting the observables in a list. Defaults to False. - - Returns: - dictionary: dictionary providing serialized data for Jacobian calculation. - """ - use_csingle = self._dtype == np.complex64 - - obs_serialized, obs_idx_offsets = QuantumScriptSerializer( - self._qubit_state.device_name, use_csingle, use_mpi, split_obs - ).serialize_observables(tape) - - ops_serialized, use_sp = QuantumScriptSerializer( - self._qubit_state.device_name, use_csingle, use_mpi, split_obs - ).serialize_ops(tape) - - ops_serialized = self._create_ops_list_lightning(*ops_serialized) - - # We need to filter out indices in trainable_params which do not - # correspond to operators. - trainable_params = sorted(tape.trainable_params) - if len(trainable_params) == 0: - return None - - tp_shift = [] - record_tp_rows = [] - all_params = 0 - - for op_idx, trainable_param in enumerate(trainable_params): - # get op_idx-th operator among differentiable operators - operation, _, _ = tape.get_operation(op_idx) - if isinstance(operation, Operation) and not isinstance( - operation, (BasisState, StatePrep) - ): - # We now just ignore non-op or state preps - tp_shift.append(trainable_param) - record_tp_rows.append(all_params) - all_params += 1 - - if use_sp: - # When the first element of the tape is state preparation. Still, I am not sure - # whether there must be only one state preparation... - tp_shift = [i - 1 for i in tp_shift] - - return { - "state_vector": self.state, - "obs_serialized": obs_serialized, - "ops_serialized": ops_serialized, - "tp_shift": tp_shift, - "record_tp_rows": record_tp_rows, - "all_params": all_params, - "obs_idx_offsets": obs_idx_offsets, - } - - @staticmethod - def _adjoint_jacobian_processing(jac): - """ - Post-process the Jacobian matrix returned by ``adjoint_jacobian`` for - the new return type system. - """ - jac = np.squeeze(jac) - - if jac.ndim == 0: - return np.array(jac) - - if jac.ndim == 1: - return tuple(np.array(j) for j in jac) - - # must be 2-dimensional - return tuple(tuple(np.array(j_) for j_ in j) for j in jac) - - def calculate_jacobian(self, tape: QuantumTape): - """Computes the Jacobian with the adjoint method. - - .. code-block:: python - - statevector = LightningStateVector(num_wires=num_wires) - statevector = statevector.get_final_state(tape) - jacobian = LightningAdjointJacobian(statevector).calculate_jacobian(tape) - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - - Returns: - The Jacobian of a tape. - """ - - if tape.shots: - raise QuantumFunctionError( - "Requested adjoint differentiation to be computed with finite shots. " - "The derivative is always exact when using the adjoint " - "differentiation method." - ) - - tape_return_type = self._get_return_type(tape.measurements) - - if not tape_return_type: # the tape does not have measurements - return np.array([], dtype=self._dtype) - - if tape_return_type is State: - raise QuantumFunctionError( - "Adjoint differentiation method does not support measurement StateMP." - ) - - if any(m.return_type is not Expectation for m in tape.measurements): - raise QuantumFunctionError( - "Adjoint differentiation method does not support expectation return type " - "mixed with other return types" - ) - - processed_data = self._process_jacobian_tape(tape) - - if not processed_data: # training_params is empty - return np.array([], dtype=self._dtype) - - trainable_params = processed_data["tp_shift"] - - # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. - # This will allow use of Lightning with adjoint for large-qubit numbers AND large - # numbers of observables, enabling choice between compute time and memory use. - requested_threads = int(getenv("OMP_NUM_THREADS", "1")) - - if self._batch_obs and requested_threads > 1: - obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) - jac = [] - for obs_chunk in obs_partitions: - jac_local = self._jacobian_lightning( - processed_data["state_vector"], - obs_chunk, - processed_data["ops_serialized"], - trainable_params, - ) - jac.extend(jac_local) - else: - jac = self._jacobian_lightning( - processed_data["state_vector"], - processed_data["obs_serialized"], - processed_data["ops_serialized"], - trainable_params, - ) - jac = np.array(jac) - jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac - jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) - jac_r[:, processed_data["record_tp_rows"]] = jac - - return self._adjoint_jacobian_processing(jac_r) - - # pylint: disable=inconsistent-return-statements - def calculate_vjp(self, tape: QuantumTape, grad_vec): - """Compute the vector-Jacobian products of a tape. - - .. code-block:: python - - statevector = LightningStateVector(num_wires=num_wires) - statevector = statevector.get_final_state(tape) - vjp = LightningAdjointJacobian(statevector).calculate_vjp(tape, grad_vec) - - computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where - - .. math:: - - w_k = dy_k \\cdot J_{k,j} - - Here, :math:`dy` is the workflow cotangent (grad_vec), and :math:`J` the Jacobian. - - Args: - tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - grad_vec (tensor_like): Gradient-output vector, also called dy or cotangent. Must have shape matching the output - shape of the corresponding tape, i.e. number of measurements if the return type is expectation. - - Returns: - The vector-Jacobian products of a tape. - """ - if tape.shots: - raise QuantumFunctionError( - "Requested adjoint differentiation to be computed with finite shots. " - "The derivative is always exact when using the adjoint differentiation " - "method." - ) - - measurements = tape.measurements - tape_return_type = self._get_return_type(measurements) - - if qml.math.allclose(grad_vec, 0) or tape_return_type is None: - return qml.math.convert_like(np.zeros(len(tape.trainable_params)), grad_vec) - - if tape_return_type is State: - raise QuantumFunctionError( - "Adjoint differentiation does not support State measurements." - ) - - if any(m.return_type is not Expectation for m in tape.measurements): - raise QuantumFunctionError( - "Adjoint differentiation method does not support expectation return type " - "mixed with other return types" - ) - - # Proceed, because tape_return_type is Expectation. - if qml.math.ndim(grad_vec) == 0: - grad_vec = (grad_vec,) - - if len(grad_vec) != len(measurements): - raise ValueError( - "Number of observables in the tape must be the same as the " - "length of grad_vec in the vjp method" - ) - - if np.iscomplexobj(grad_vec): - raise ValueError( - "The vjp method only works with a real-valued grad_vec when the " - "tape is returning an expectation value" - ) - - ham = qml.simplify(qml.dot(grad_vec, [m.obs for m in measurements])) - - num_params = len(tape.trainable_params) - - if num_params == 0: - return np.array([], dtype=self.qubit_state.dtype) - - new_tape = qml.tape.QuantumScript( - tape.operations, - [qml.expval(ham)], - shots=tape.shots, - trainable_params=tape.trainable_params, - ) - - return self.calculate_jacobian(new_tape) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 3ef56878a..e1d0039bb 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -21,33 +21,21 @@ except ImportError: pass +from typing import List from functools import reduce -from typing import Callable, List, Union import numpy as np import pennylane as qml -from pennylane.devices.qubit.sampling import _group_measurements from pennylane.measurements import ( - ClassicalShadowMP, CountsMP, - ExpectationMP, - MeasurementProcess, - ProbabilityMP, SampleMeasurement, - ShadowExpvalMP, Shots, - StateMeasurement, - VarianceMP, ) -from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum -from pennylane.tape import QuantumScript -from pennylane.typing import Result, TensorLike -from pennylane.wires import Wires +from pennylane.typing import TensorLike +from pennylane_lightning.core._measurements_base import LightningBaseMeasurements -from pennylane_lightning.core._serialize import QuantumScriptSerializer - -class LightningMeasurements: +class LightningMeasurements(LightningBaseMeasurements): """Lightning Measurements class Measures the state provided by the LightningStateVector class. @@ -73,9 +61,9 @@ def __init__( kernel_name: str = None, num_burnin: int = None, ) -> None: - self._qubit_state = qubit_state - self._dtype = qubit_state.dtype - self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) + + super().__init__(qubit_state) + self._mcmc = mcmc self._kernel_name = kernel_name self._num_burnin = num_burnin @@ -84,15 +72,7 @@ def __init__( if self._mcmc and not self._num_burnin: self._num_burnin = 100 - @property - def qubit_state(self): - """Returns a handle to the LightningStateVector class.""" - return self._qubit_state - - @property - def dtype(self): - """Returns the simulation data type.""" - return self._dtype + self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) def _measurement_dtype(self): """Binding to Lightning Measurements C++ class. @@ -101,269 +81,6 @@ def _measurement_dtype(self): """ 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 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 - """ - diagonalizing_gates = measurementprocess.diagonalizing_gates() - self._qubit_state.apply_operations(diagonalizing_gates) - state_array = self._qubit_state.state - wires = Wires(range(self._qubit_state.num_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): - """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 isinstance(measurementprocess.obs, qml.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 ( - isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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.expval(ob_serialized) - - return self._measurement_lightning.expval( - measurementprocess.obs.name, measurementprocess.obs.wires - ) - - 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: - self._qubit_state.apply_operations(diagonalizing_gates) - results = self._measurement_lightning.probs(measurementprocess.wires.tolist()) - if diagonalizing_gates: - self._qubit_state.apply_operations( - [qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)] - ) - return results - - 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 isinstance(measurementprocess.obs, qml.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 ( - isinstance(measurementprocess.obs, (qml.ops.Hamiltonian, qml.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]: - """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 isinstance(measurementprocess.obs, (qml.Identity, qml.Projector)): - return self.state_diagonalizing_gates - return self.expval - - if isinstance(measurementprocess, ProbabilityMP): - return self.probs - - if isinstance(measurementprocess, VarianceMP): - if isinstance(measurementprocess.obs, (qml.Identity, qml.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 - - 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, mid_measurements=None) -> 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 - mid_measurements (None, dict): Dictionary of mid-circuit measurements - - Returns: - Tuple[TensorLike]: The measurement results - """ - - if not circuit.shots: - # analytic case - if len(circuit.measurements) == 1: - return self.measurement(circuit.measurements[0]) - - return tuple(self.measurement(mp) for mp in circuit.measurements) - - # finite-shot case - results = self.measure_with_samples( - circuit.measurements, - shots=circuit.shots, - mid_measurements=mid_measurements, - ) - - if len(circuit.measurements) == 1: - if circuit.shots.has_partitioned_shots: - return tuple(res[0] for res in results) - - return results[0] - - return results - - # pylint:disable = too-many-arguments - def measure_with_samples( - self, - measurements: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], - shots: Shots, - mid_measurements=None, - ) -> List[TensorLike]: - """ - Returns the samples of the measurement process performed on the given state. - This function assumes that the user-defined wire labels in the measurement process - have already been mapped to integer wires used in the device. - - Args: - measurements (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]): - The sample measurements to perform - shots (Shots): The number of samples to take - mid_measurements (None, dict): Dictionary of mid-circuit measurements - - Returns: - List[TensorLike[Any]]: Sample measurement results - """ - # last N measurements are sampling MCMs in ``dynamic_one_shot`` execution mode - mps = measurements[0 : -len(mid_measurements)] if mid_measurements else measurements - groups, indices = _group_measurements(mps) - - all_res = [] - for group in groups: - if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( - group[0].obs, SparseHamiltonian - ): - raise TypeError( - "ExpectationMP/VarianceMP(SparseHamiltonian) cannot be computed with samples." - ) - if isinstance(group[0], VarianceMP) and isinstance(group[0].obs, (Hamiltonian, Sum)): - raise TypeError("VarianceMP(Hamiltonian/Sum) cannot be computed with samples.") - if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)): - raise TypeError( - "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." - ) - if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): - all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) - elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): - all_res.extend(self._measure_sum_with_samples(group, shots)) - else: - all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) - - # reorder results - flat_indices = [] - for row in indices: - flat_indices += row - sorted_res = tuple( - res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]]) - ) - - # append MCM samples - if mid_measurements: - sorted_res += tuple(mid_measurements.values()) - - # put the shot vector axis before the measurement axis - if shots.has_partitioned_shots: - sorted_res = tuple(zip(*sorted_res)) - - return sorted_res - - def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False): - if len(mps) == 1: - diagonalizing_gates = mps[0].diagonalizing_gates() - elif all(mp.obs for mp in mps): - diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0] - else: - diagonalizing_gates = [] - - if adjoint: - diagonalizing_gates = [ - qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates) - ] - - self._qubit_state.apply_operations(diagonalizing_gates) - def _measure_with_samples_diagonalizing_gates( self, mps: List[SampleMeasurement], @@ -427,43 +144,3 @@ def _process_single_shot(samples): return ( tuple(zip(*processed_samples)) if shots.has_partitioned_shots else processed_samples[0] ) - - def _measure_hamiltonian_with_samples( - self, - mp: List[SampleMeasurement], - shots: Shots, - ): - # the list contains only one element based on how we group measurements - mp = mp[0] - - # if the measurement process involves a Hamiltonian, measure each - # of the terms separately and sum - def _sum_for_single_shot(s): - results = self.measure_with_samples( - [ExpectationMP(t) for t in mp.obs.terms()[1]], - s, - ) - return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) - - unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) - return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] - - def _measure_sum_with_samples( - self, - mp: List[SampleMeasurement], - shots: Shots, - ): - # the list contains only one element based on how we group measurements - mp = mp[0] - - # if the measurement process involves a Sum, measure each - # of the terms separately and sum - def _sum_for_single_shot(s): - results = self.measure_with_samples( - [ExpectationMP(t) for t in mp.obs], - s, - ) - return sum(results) - - unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) - return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index ac2774f20..27c6050ba 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -26,7 +26,7 @@ import numpy as np import pennylane as qml -from pennylane import BasisState, DeviceError, StatePrep +from pennylane import DeviceError from pennylane.measurements import MidMeasureMP from pennylane.ops import Conditional from pennylane.ops.op_math import Adjoint @@ -34,9 +34,11 @@ from pennylane.wires import Wires from ._measurements import LightningMeasurements +from pennylane_lightning.core._state_vector_base import LightningBaseStateVector -class LightningStateVector: + +class LightningStateVector(LightningBaseStateVector): """Lightning state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -49,43 +51,15 @@ class LightningStateVector: """ 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]: # 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._device_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 + super().__init__(num_wires, dtype) - @property - def device_name(self): - """Returns the state vector device name.""" - return self._device_name - - @property - 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 + self._device_name = device_name + self._qubit_state = self._state_dtype()(self._num_wires) - @property - def state_vector(self): - """Returns a handle to the state vector.""" - return self._qubit_state @property def state(self): @@ -109,11 +83,6 @@ def _state_dtype(self): """ return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 - def reset_state(self): - """Reset the device's state""" - # init the state vector to |00..0> - self._qubit_state.resetStateVector() - def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: @@ -136,25 +105,6 @@ def _apply_state_vector(self, state, device_wires: Wires): self._qubit_state.setStateVector(state, list(device_wires)) - 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. - """ - if not set(state.tolist()).issubset({0, 1}): - raise ValueError("BasisState parameter must consist of 0 or 1 integers.") - - if len(state) != len(wires): - raise ValueError("BasisState parameter and wires must be of equal length.") - - self._qubit_state.setBasisState(list(state), list(wires)) - def _apply_lightning_controlled(self, operation): """Apply an arbitrary controlled operation to the state tensor. @@ -272,46 +222,3 @@ def _apply_lightning( # To support older versions of PL method(operation.matrix, wires, False) - def apply_operations( - self, operations, mid_measurements: dict = None, postselect_mode: str = None - ): - """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:] - self._apply_lightning( - operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - - def get_final_state( - self, - circuit: QuantumScript, - mid_measurements: dict = None, - postselect_mode: str = 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 - mid_measurements (None, dict): Dictionary of mid-circuit measurements - postselect_mode (str): Configuration for handling shots with mid-circuit measurement - postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to - keep the same number of shots. Default is ``None``. - - Returns: - LightningStateVector: Lightning final state class. - - """ - self.apply_operations( - circuit.operations, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - - return self From a8f4ec207e29bbe9eb053ab3e438fad8a8929ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 16:59:00 -0400 Subject: [PATCH 054/130] Using the attribute new_API Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_adjoint_jacobian_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index 85c78d790..cb054b557 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -35,9 +35,9 @@ LightningKokkosStateVector as LightningStateVector, ) -if device_name not in ("lightning.qubit", "lightning.kokkos"): +if not LightningDevice._new_API: pytest.skip( - "Exclusive tests for new API backends lightning.qubit and lightning.kokkos for LightningAdjointJacobian class. Skipping.", + "Exclusive tests for new API backends LightningAdjointJacobian class. Skipping.", allow_module_level=True, ) From cc67c16242db35d1a343d89b680f728d55a2ec38 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 21 Aug 2024 20:59:17 +0000 Subject: [PATCH 055/130] Auto update version from '0.38.0-dev41' to '0.38.0-dev42' --- 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 a8ba10998..f68075d10 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev41" +__version__ = "0.38.0-dev42" From c0d6e1132d2ff4c1cec592ab0f8efb5d05698682 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 21 Aug 2024 21:39:39 +0000 Subject: [PATCH 056/130] Auto update version from '0.38.0-dev41' to '0.38.0-dev42' --- 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 a8ba10998..f68075d10 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev41" +__version__ = "0.38.0-dev42" From 330cc9cd8fd1383b364ad923a082b2f57a79e7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 18:04:48 -0400 Subject: [PATCH 057/130] Using the attribute new_API Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_jacobian_method.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index 44da006fc..075d35a8a 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -44,9 +44,9 @@ ) -if device_name not in ("lightning.qubit", "lightning.kokkos"): +if not LightningDevice._new_API: pytest.skip( - "Exclusive tests for new API backends lightning.qubit and lightning.kokkos for LightningAdjointJacobian class. Skipping.", + "Exclusive tests for new API backends LightningAdjointJacobian class. Skipping.", allow_module_level=True, ) From e59baa130c3dab5d7dd06f384ef687023e8b2ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 21 Aug 2024 18:05:46 -0400 Subject: [PATCH 058/130] Using the attribute new_API Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_measurements_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1c7842bb8..1d4080440 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -37,9 +37,9 @@ LightningKokkosStateVector as LightningStateVector, ) -if device_name not in ("lightning.qubit", "lightning.kokkos"): +if not LightningDevice._new_API: pytest.skip( - "Exclusive tests for lightning.qubit or lightning.kokkos. Skipping.", + "Exclusive tests for new API devices. Skipping.", allow_module_level=True, ) From c41b69a431ac91ea2f1b4ba41a3624553be8f8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 22 Aug 2024 09:03:55 -0400 Subject: [PATCH 059/130] Delete commented line Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_measurements_class.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 1d4080440..5ec92938f 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -484,7 +484,6 @@ def test_single_return_value(self, shots, measurement, observable, lightning_sv, skip_list = ( qml.ops.Sum, - # qml.Hamiltonian, qml.SparseHamiltonian, ) do_skip = measurement is qml.var and isinstance(observable, skip_list) From ee2c01aaa687ef5dad4364222fd0645cbd2c05a4 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 22 Aug 2024 13:04:15 +0000 Subject: [PATCH 060/130] Auto update version from '0.38.0-dev42' to '0.38.0-dev43' --- 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 f68075d10..33a207e9f 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev42" +__version__ = "0.38.0-dev43" From b1d4cad63005cb5f5e26698f620d3c6ba9115154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 22 Aug 2024 10:41:57 -0400 Subject: [PATCH 061/130] Using the attribute new_API Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_simulate_method.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index 60cecc4c4..db3df75d2 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -37,9 +37,9 @@ from pennylane_lightning.lightning_kokkos.lightning_kokkos import simulate -if device_name != "lightning.qubit" and device_name != "lightning.kokkos": +if not LightningDevice._new_API: pytest.skip( - "Exclusive tests for lightning.qubit and lightning.kokkos. Skipping.", + "Exclusive tests for new API devices. Skipping.", allow_module_level=True, ) From 32cf4a6dffdc29721ae2370a6593bb8d943a3354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 22 Aug 2024 11:12:10 -0400 Subject: [PATCH 062/130] Using the attribute new_API Co-authored-by: Amintor Dusko <87949283+AmintorDusko@users.noreply.github.com> --- tests/lightning_qubit/test_state_vector_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 6aa857e47..b1a828d51 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -38,9 +38,9 @@ pass -if device_name not in ("lightning.qubit", "lightning.kokkos"): +if not LightningDevice._new_API: pytest.skip( - "Exclusive tests for lightning.qubit or lightning.kokkos. Skipping.", + "Exclusive tests for new API devices. Skipping.", allow_module_level=True, ) From d046ad9fecff9f3685adf4dd5a4d179e998644b5 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 22 Aug 2024 15:40:53 +0000 Subject: [PATCH 063/130] Auto update version from '0.38.0-dev42' to '0.38.0-dev43' --- 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 f68075d10..33a207e9f 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev42" +__version__ = "0.38.0-dev43" From c2079f92c52795932b38b1e03296959fab6ec549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 22 Aug 2024 11:47:28 -0400 Subject: [PATCH 064/130] Changes from PR review --- .../lightning_kokkos/_measurements.py | 22 ------------------- .../lightning_kokkos/lightning_kokkos.py | 6 ++--- tests/test_adjoint_jacobian.py | 2 +- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index cba0bd708..3a6c07e72 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -302,8 +302,6 @@ def measure_with_samples( raise TypeError( "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." ) - if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): - all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): all_res.extend(self._measure_sum_with_samples(group, shots)) else: @@ -400,26 +398,6 @@ def _process_single_shot(samples): tuple(zip(*processed_samples)) if shots.has_partitioned_shots else processed_samples[0] ) - def _measure_hamiltonian_with_samples( - self, - mp: List[SampleMeasurement], - shots: Shots, - ): - # the list contains only one element based on how we group measurements - mp = mp[0] - - # if the measurement process involves a Hamiltonian, measure each - # of the terms separately and sum - def _sum_for_single_shot(s): - results = self.measure_with_samples( - [ExpectationMP(t) for t in mp.obs.terms()[1]], - s, - ) - return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) - - unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) - return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] - def _measure_sum_with_samples( self, mp: List[SampleMeasurement], diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 1b84c8a17..fb35d3a16 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -72,7 +72,7 @@ def simulate( Args: circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector + state (LightningKokkosStateVector): handle to Lightning state vector postselect_mode (str): Configuration for handling shots with mid-circuit measurement postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to keep the same number of shots. Default is ``None``. @@ -82,8 +82,8 @@ def simulate( Note that this function can return measurements for non-commuting observables simultaneously. """ - has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) - if circuit.shots and has_mcm: + has_mcm = False + if circuit.shots and (has_mcm:=any(isinstance(op, MidMeasureMP) for op in circuit.operations)): results = [] aux_circ = qml.tape.QuantumScript( circuit.operations, diff --git a/tests/test_adjoint_jacobian.py b/tests/test_adjoint_jacobian.py index 89117bccb..90fc168e5 100644 --- a/tests/test_adjoint_jacobian.py +++ b/tests/test_adjoint_jacobian.py @@ -125,7 +125,7 @@ def test_not_expval(self, dev): qml.RX(0.1, wires=0) qml.state() - if device_name in ("lightning.qubit", "lightning.kokkos"): + if dev._new_API: message = "Adjoint differentiation method does not support measurement StateMP." elif device_name == "lightning.gpu": message = "Adjoint differentiation does not support State measurements." From 3402e101191689a0afc45f9053f5dd8bc5dc6c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 22 Aug 2024 12:01:42 -0400 Subject: [PATCH 065/130] apply format --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index fb35d3a16..5fc81ac2c 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -83,7 +83,9 @@ def simulate( Note that this function can return measurements for non-commuting observables simultaneously. """ has_mcm = False - if circuit.shots and (has_mcm:=any(isinstance(op, MidMeasureMP) for op in circuit.operations)): + if circuit.shots and ( + has_mcm := any(isinstance(op, MidMeasureMP) for op in circuit.operations) + ): results = [] aux_circ = qml.tape.QuantumScript( circuit.operations, From cc0f209e69aefec920d541970199fb49afa5b773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 23 Aug 2024 15:57:32 -0400 Subject: [PATCH 066/130] reduce the number of shots for testing and add class definitons on conftest --- tests/conftest.py | 21 ++++++ .../test_adjoint_jacobian_class.py | 26 +------ tests/lightning_qubit/test_jacobian_method.py | 20 +----- .../test_measurements_class.py | 71 ++++++++++--------- tests/lightning_qubit/test_simulate_method.py | 19 +---- .../test_state_vector_class.py | 8 +-- 6 files changed, 61 insertions(+), 104 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d7dbf4649..428831206 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -127,6 +127,9 @@ def get_device(): if device_name == "lightning.kokkos": from pennylane_lightning.lightning_kokkos import LightningKokkos as LightningDevice + from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector + from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements + from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian if hasattr(pennylane_lightning, "lightning_kokkos_ops"): import pennylane_lightning.lightning_kokkos_ops as lightning_ops @@ -145,6 +148,12 @@ def get_device(): from pennylane_lightning.lightning_tensor_ops import LightningException else: from pennylane_lightning.lightning_qubit import LightningQubit as LightningDevice + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector + from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian + + + if hasattr(pennylane_lightning, "lightning_qubit_ops"): import pennylane_lightning.lightning_qubit_ops as lightning_ops @@ -165,6 +174,18 @@ def _device(wires, shots=None): return _device +# 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 + + ####################################################################### # Fixtures for testing under new and old opmath diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index cb054b557..3449a3d82 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -18,23 +18,11 @@ import pennylane as qml import pytest -from conftest import LightningDevice, device_name # tested device +from conftest import LightningDevice, LightningStateVector, LightningAdjointJacobian, device_name # tested device from pennylane import numpy as np from pennylane.tape import QuantumScript from scipy.stats import unitary_group -if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._adjoint_jacobian import ( - LightningKokkosAdjointJacobian as LightningAdjointJacobian, - ) - from pennylane_lightning.lightning_kokkos._state_vector import ( - LightningKokkosStateVector as LightningStateVector, - ) - if not LightningDevice._new_API: pytest.skip( "Exclusive tests for new API backends LightningAdjointJacobian class. Skipping.", @@ -59,18 +47,6 @@ kokkos_args += [InitializationSettings().set_num_threads(2)] -# 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 Rx(theta): r"""One-qubit rotation about the x axis. diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index 075d35a8a..ee2878cf0 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -16,12 +16,11 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, device_name # tested device +from conftest import PHI, THETA, LightningDevice, LightningStateVector, device_name # tested device from pennylane.devices import DefaultExecutionConfig, DefaultQubit, ExecutionConfig from pennylane.tape import QuantumScript if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector from pennylane_lightning.lightning_qubit.lightning_qubit import ( jacobian, simulate, @@ -30,11 +29,7 @@ vjp, ) - if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._state_vector import ( - LightningKokkosStateVector as LightningStateVector, - ) from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( jacobian, simulate, @@ -43,7 +38,6 @@ vjp, ) - if not LightningDevice._new_API: pytest.skip( "Exclusive tests for new API backends LightningAdjointJacobian class. Skipping.", @@ -54,18 +48,6 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) -# General LightningStateVector fixture, for any number of wires. -@pytest.fixture( - scope="module", - params=[np.complex64, np.complex128], -) -def lightning_sv(request): - def _statevector(num_wires): - return LightningStateVector(num_wires=num_wires, dtype=request.param) - - return _statevector - - class TestJacobian: """Unit tests for the jacobian method with the new device API.""" diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 5ec92938f..e8f3a72a7 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -19,24 +19,12 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, device_name # tested device +from conftest import PHI, THETA, LightningDevice, LightningStateVector, LightningMeasurements, device_name # tested device from flaky import flaky from pennylane.devices import DefaultQubit from pennylane.measurements import VarianceMP from scipy.sparse import csr_matrix, random_array -if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._measurements import ( - LightningKokkosMeasurements as LightningMeasurements, - ) - from pennylane_lightning.lightning_kokkos._state_vector import ( - LightningKokkosStateVector as LightningStateVector, - ) - if not LightningDevice._new_API: pytest.skip( "Exclusive tests for new API devices. Skipping.", @@ -47,19 +35,6 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) - -# 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 get_hermitian_matrix(n): H = np.random.rand(n, n) + 1.0j * np.random.rand(n, n) return H + np.conj(H).T @@ -423,8 +398,8 @@ def calculate_reference(tape, lightning_sv): m = LightningMeasurements(statevector) return m.measure_final_state(tape) - @flaky(max_runs=5) - @pytest.mark.parametrize("shots", [None, 1000000, (900000, 900000)]) + @flaky(max_runs=15) + @pytest.mark.parametrize("shots", [None, 100_000, [90_000,90_000]]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", @@ -464,6 +439,16 @@ def test_single_return_value(self, shots, measurement, observable, lightning_sv, pytest.skip( f"Measurement of type {type(measurement).__name__} does not have a keyword argument 'wires'." ) + rtol = 1.0e-2 # 1% of expected value as tolerance + if shots != None and measurement is qml.expval: + # Increase the number of shots + if isinstance(shots, int): + shots *= 10 + else: + shots = [i * 10 for i in shots] + + # Extra tolerance + rtol = 5.0e-2 # 5% of expected value as tolerance n_qubits = 4 n_layers = 1 @@ -504,11 +489,14 @@ def test_single_return_value(self, shots, measurement, observable, lightning_sv, if shots is None: assert np.allclose(result, expected, max(tol, 1.0e-4)) else: - dtol = max(tol, 1.0e-2) - assert np.allclose(result, expected, rtol=dtol, atol=dtol) - - @flaky(max_runs=5) - @pytest.mark.parametrize("shots", [None, 1000000, (900000, 900000)]) + atol = max(tol, 1.0e-2) if statevector.dtype == np.complex64 else max(tol, 1.0e-3) + rtol = max(tol, rtol) # % of expected value as tolerance + + # allclose -> absolute(a - b) <= (atol + rtol * absolute(b)) + assert np.allclose(result, expected, rtol=rtol, atol=atol) + + @flaky(max_runs=10) + @pytest.mark.parametrize("shots", [None, 100_000, (90_000, 90_000)]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "obs0_", @@ -559,6 +547,17 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s f"Observable of type {type(obs0_).__name__} is not supported for rotating probabilities." ) + rtol = 1.0e-2 # 1% of expected value as tolerance + if shots != None and measurement is qml.expval: + # Increase the number of shots + if isinstance(shots, int): + shots *= 10 + else: + shots = [i * 10 for i in shots] + + # Extra tolerance + rtol = 5.0e-2 # 5% of expected value as tolerance + n_qubits = 4 n_layers = 1 np.random.seed(0) @@ -601,12 +600,14 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s assert isinstance(result, Sequence) assert len(result) == len(expected) # a few tests may fail in single precision, and hence we increase the tolerance - dtol = tol if shots is None else max(tol, 1.0e-2) + atol = tol if shots is None else max(tol, 1.0e-2) + rtol = max(tol, rtol) # % of expected value as tolerance for r, e in zip(result, expected): if isinstance(shots, tuple) and isinstance(r[0], np.ndarray): r = np.concatenate(r) e = np.concatenate(e) - assert np.allclose(r, e, atol=dtol, rtol=dtol) + # allclose -> absolute(r - e) <= (atol + rtol * absolute(e)) + assert np.allclose(r, e, atol=atol, rtol=rtol) @pytest.mark.parametrize( "cases", diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index db3df75d2..2f2917bf2 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -19,24 +19,18 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, device_name # tested device +from conftest import PHI, THETA, LightningDevice, LightningStateVector, device_name # tested device from flaky import flaky from pennylane.devices import DefaultExecutionConfig, DefaultQubit from pennylane.measurements import VarianceMP from scipy.sparse import csr_matrix, random_array if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector from pennylane_lightning.lightning_qubit.lightning_qubit import simulate - if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._state_vector import ( - LightningKokkosStateVector as LightningStateVector, - ) from pennylane_lightning.lightning_kokkos.lightning_kokkos import simulate - if not LightningDevice._new_API: pytest.skip( "Exclusive tests for new API devices. Skipping.", @@ -47,17 +41,6 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) -# General LightningStateVector fixture, for any number of wires. -@pytest.fixture( - scope="module", - params=[np.complex64, np.complex128], -) -def lightning_sv(request): - def _statevector(num_wires): - return LightningStateVector(num_wires=num_wires, dtype=request.param) - - return _statevector - class TestSimulate: """Tests for the simulate method.""" diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index b1a828d51..f09a176cb 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -20,18 +20,12 @@ import numpy as np import pennylane as qml import pytest -from conftest import LightningDevice, device_name # tested device +from conftest import LightningDevice, LightningStateVector, device_name # tested device from pennylane.tape import QuantumScript from pennylane.wires import Wires -if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._state_vector import ( - LightningKokkosStateVector as LightningStateVector, - ) - try: from pennylane_lightning.lightning_kokkos_ops import InitializationSettings except ImportError: From aa98b93d0e5338b63a573e94a34a0c21091ccd50 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 23 Aug 2024 19:58:27 +0000 Subject: [PATCH 067/130] Auto update version from '0.38.0-dev47' to '0.38.0-dev48' --- 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 43d2822de..f05b2da8b 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -17,4 +17,4 @@ """ -__version__ = "0.38.0-dev47" +__version__ = "0.38.0-dev48" From 5aced15a8bd9f25ca3c5c34301a4b455c7dab7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 23 Aug 2024 16:01:18 -0400 Subject: [PATCH 068/130] apply format --- tests/conftest.py | 19 ++++++---- .../test_adjoint_jacobian_class.py | 7 +++- .../test_measurements_class.py | 38 +++++++++++-------- tests/lightning_qubit/test_simulate_method.py | 1 - .../test_state_vector_class.py | 1 - 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 428831206..68adccf3f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -127,9 +127,15 @@ def get_device(): if device_name == "lightning.kokkos": from pennylane_lightning.lightning_kokkos import LightningKokkos as LightningDevice - from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector - from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements - from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian + from pennylane_lightning.lightning_kokkos._adjoint_jacobian import ( + LightningKokkosAdjointJacobian as LightningAdjointJacobian, + ) + from pennylane_lightning.lightning_kokkos._measurements import ( + LightningKokkosMeasurements as LightningMeasurements, + ) + from pennylane_lightning.lightning_kokkos._state_vector import ( + LightningKokkosStateVector as LightningStateVector, + ) if hasattr(pennylane_lightning, "lightning_kokkos_ops"): import pennylane_lightning.lightning_kokkos_ops as lightning_ops @@ -148,12 +154,9 @@ def get_device(): from pennylane_lightning.lightning_tensor_ops import LightningException else: from pennylane_lightning.lightning_qubit import LightningQubit as LightningDevice - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector - from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian - - - + from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector if hasattr(pennylane_lightning, "lightning_qubit_ops"): import pennylane_lightning.lightning_qubit_ops as lightning_ops diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index 3449a3d82..80bebf262 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -18,7 +18,12 @@ import pennylane as qml import pytest -from conftest import LightningDevice, LightningStateVector, LightningAdjointJacobian, device_name # tested device +from conftest import ( # tested device + LightningAdjointJacobian, + LightningDevice, + LightningStateVector, + device_name, +) from pennylane import numpy as np from pennylane.tape import QuantumScript from scipy.stats import unitary_group diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index e8f3a72a7..5b5068563 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -19,7 +19,14 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, LightningStateVector, LightningMeasurements, device_name # tested device +from conftest import ( # tested device + PHI, + THETA, + LightningDevice, + LightningMeasurements, + LightningStateVector, + device_name, +) from flaky import flaky from pennylane.devices import DefaultQubit from pennylane.measurements import VarianceMP @@ -35,6 +42,7 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) + def get_hermitian_matrix(n): H = np.random.rand(n, n) + 1.0j * np.random.rand(n, n) return H + np.conj(H).T @@ -399,7 +407,7 @@ def calculate_reference(tape, lightning_sv): return m.measure_final_state(tape) @flaky(max_runs=15) - @pytest.mark.parametrize("shots", [None, 100_000, [90_000,90_000]]) + @pytest.mark.parametrize("shots", [None, 100_000, [90_000, 90_000]]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", @@ -439,16 +447,16 @@ def test_single_return_value(self, shots, measurement, observable, lightning_sv, pytest.skip( f"Measurement of type {type(measurement).__name__} does not have a keyword argument 'wires'." ) - rtol = 1.0e-2 # 1% of expected value as tolerance + rtol = 1.0e-2 # 1% of expected value as tolerance if shots != None and measurement is qml.expval: # Increase the number of shots - if isinstance(shots, int): - shots *= 10 + if isinstance(shots, int): + shots *= 10 else: shots = [i * 10 for i in shots] - + # Extra tolerance - rtol = 5.0e-2 # 5% of expected value as tolerance + rtol = 5.0e-2 # 5% of expected value as tolerance n_qubits = 4 n_layers = 1 @@ -490,8 +498,8 @@ def test_single_return_value(self, shots, measurement, observable, lightning_sv, assert np.allclose(result, expected, max(tol, 1.0e-4)) else: atol = max(tol, 1.0e-2) if statevector.dtype == np.complex64 else max(tol, 1.0e-3) - rtol = max(tol, rtol) # % of expected value as tolerance - + rtol = max(tol, rtol) # % of expected value as tolerance + # allclose -> absolute(a - b) <= (atol + rtol * absolute(b)) assert np.allclose(result, expected, rtol=rtol, atol=atol) @@ -547,16 +555,16 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s f"Observable of type {type(obs0_).__name__} is not supported for rotating probabilities." ) - rtol = 1.0e-2 # 1% of expected value as tolerance + rtol = 1.0e-2 # 1% of expected value as tolerance if shots != None and measurement is qml.expval: # Increase the number of shots - if isinstance(shots, int): - shots *= 10 + if isinstance(shots, int): + shots *= 10 else: shots = [i * 10 for i in shots] - + # Extra tolerance - rtol = 5.0e-2 # 5% of expected value as tolerance + rtol = 5.0e-2 # 5% of expected value as tolerance n_qubits = 4 n_layers = 1 @@ -601,7 +609,7 @@ def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_s assert len(result) == len(expected) # a few tests may fail in single precision, and hence we increase the tolerance atol = tol if shots is None else max(tol, 1.0e-2) - rtol = max(tol, rtol) # % of expected value as tolerance + rtol = max(tol, rtol) # % of expected value as tolerance for r, e in zip(result, expected): if isinstance(shots, tuple) and isinstance(r[0], np.ndarray): r = np.concatenate(r) diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index 2f2917bf2..323ba8d73 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -41,7 +41,6 @@ pytest.skip("No binary module found. Skipping.", allow_module_level=True) - class TestSimulate: """Tests for the simulate method.""" diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 6706e224a..6fe677da1 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -24,7 +24,6 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires - if device_name == "lightning.kokkos": try: from pennylane_lightning.lightning_kokkos_ops import InitializationSettings From 4ed563a3e72d3075718c05ee49226c9fa3f9cf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 23 Aug 2024 16:14:20 -0400 Subject: [PATCH 069/130] solve complains about CodeFactor --- pennylane_lightning/lightning_kokkos/_measurements.py | 2 +- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index 3a6c07e72..03d55a9d5 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -302,7 +302,7 @@ def measure_with_samples( raise TypeError( "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." ) - elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): + if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): all_res.extend(self._measure_sum_with_samples(group, shots)) else: all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 5fc81ac2c..cb02480c3 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -82,10 +82,7 @@ def simulate( Note that this function can return measurements for non-commuting observables simultaneously. """ - has_mcm = False - if circuit.shots and ( - has_mcm := any(isinstance(op, MidMeasureMP) for op in circuit.operations) - ): + if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)): results = [] aux_circ = qml.tape.QuantumScript( circuit.operations, From 53633611f867824cf60f61a6f685b109a9f6bceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 23 Aug 2024 17:19:57 -0400 Subject: [PATCH 070/130] Fix issue with the Hamiltonians --- .../lightning_kokkos/_measurements.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index 03d55a9d5..cba0bd708 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -302,7 +302,9 @@ def measure_with_samples( raise TypeError( "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." ) - if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): + if isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Hamiltonian): + all_res.extend(self._measure_hamiltonian_with_samples(group, shots)) + elif isinstance(group[0], ExpectationMP) and isinstance(group[0].obs, Sum): all_res.extend(self._measure_sum_with_samples(group, shots)) else: all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) @@ -398,6 +400,26 @@ def _process_single_shot(samples): tuple(zip(*processed_samples)) if shots.has_partitioned_shots else processed_samples[0] ) + def _measure_hamiltonian_with_samples( + self, + mp: List[SampleMeasurement], + shots: Shots, + ): + # the list contains only one element based on how we group measurements + mp = mp[0] + + # if the measurement process involves a Hamiltonian, measure each + # of the terms separately and sum + def _sum_for_single_shot(s): + results = self.measure_with_samples( + [ExpectationMP(t) for t in mp.obs.terms()[1]], + s, + ) + return sum(c * res for c, res in zip(mp.obs.terms()[0], results)) + + unsqueezed_results = tuple(_sum_for_single_shot(type(shots)(s)) for s in shots) + return [unsqueezed_results] if shots.has_partitioned_shots else [unsqueezed_results[0]] + def _measure_sum_with_samples( self, mp: List[SampleMeasurement], From 37fdbe2d8b274991cda77b74d355f92f0dff948e Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 23 Aug 2024 21:26:42 +0000 Subject: [PATCH 071/130] Auto update version from '0.38.0-dev49' to '0.38.0-dev50' --- 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 8c281f63a..970e51686 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev49" +__version__ = "0.38.0-dev50" From 818093e50e6da1d220ecfde808a39c0fcab2b8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 09:05:50 -0400 Subject: [PATCH 072/130] Fix error of lgpu importError --- tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 68adccf3f..71ea3f11e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -142,6 +142,9 @@ def get_device(): from pennylane_lightning.lightning_kokkos_ops import LightningException elif device_name == "lightning.gpu": from pennylane_lightning.lightning_gpu import LightningGPU as LightningDevice + LightningAdjointJacobian = None + LightningMeasurements = None + LightningStateVector = None if hasattr(pennylane_lightning, "lightning_gpu_ops"): import pennylane_lightning.lightning_gpu_ops as lightning_ops From bd5ebf8b6ea88f33be5be4d1e74b795860fd198f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 09:29:59 -0400 Subject: [PATCH 073/130] fix ImportError tensor --- tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 71ea3f11e..69a101fc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -151,6 +151,9 @@ def get_device(): from pennylane_lightning.lightning_gpu_ops import LightningException elif device_name == "lightning.tensor": from pennylane_lightning.lightning_tensor import LightningTensor as LightningDevice + LightningAdjointJacobian = None + LightningMeasurements = None + LightningStateVector = None if hasattr(pennylane_lightning, "lightning_tensor_ops"): import pennylane_lightning.lightning_tensor_ops as lightning_ops From f64edf0ff422195ec7247681a3ff8988e0322117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 09:55:04 -0400 Subject: [PATCH 074/130] apply format --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 69a101fc8..b5ddf416c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -142,6 +142,7 @@ def get_device(): from pennylane_lightning.lightning_kokkos_ops import LightningException elif device_name == "lightning.gpu": from pennylane_lightning.lightning_gpu import LightningGPU as LightningDevice + LightningAdjointJacobian = None LightningMeasurements = None LightningStateVector = None @@ -151,6 +152,7 @@ def get_device(): from pennylane_lightning.lightning_gpu_ops import LightningException elif device_name == "lightning.tensor": from pennylane_lightning.lightning_tensor import LightningTensor as LightningDevice + LightningAdjointJacobian = None LightningMeasurements = None LightningStateVector = None From bef41b64720c80b3861e239ce583a404f9f07103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 10:26:23 -0400 Subject: [PATCH 075/130] fix importError in test for lTensor --- tests/lightning_qubit/test_adjoint_jacobian_class.py | 3 +++ tests/lightning_qubit/test_jacobian_method.py | 3 +++ tests/lightning_qubit/test_measurements_class.py | 2 ++ tests/lightning_qubit/test_simulate_method.py | 3 +++ tests/lightning_qubit/test_state_vector_class.py | 2 ++ 5 files changed, 13 insertions(+) diff --git a/tests/lightning_qubit/test_adjoint_jacobian_class.py b/tests/lightning_qubit/test_adjoint_jacobian_class.py index 80bebf262..236c697f1 100644 --- a/tests/lightning_qubit/test_adjoint_jacobian_class.py +++ b/tests/lightning_qubit/test_adjoint_jacobian_class.py @@ -34,6 +34,9 @@ allow_module_level=True, ) +if device_name == "lightning.tensor": + pytest.skip("Skipping tests for the LightningTensor class.", allow_module_level=True) + if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index ee2878cf0..b91667a6d 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -44,6 +44,9 @@ allow_module_level=True, ) +if device_name == "lightning.tensor": + pytest.skip("Skipping tests for the LightningTensor class.", allow_module_level=True) + if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 5b5068563..03730cbc7 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -38,6 +38,8 @@ allow_module_level=True, ) +if device_name == "lightning.tensor": + pytest.skip("Skipping tests for the LightningTensor class.", allow_module_level=True) if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index 323ba8d73..eb7ef501c 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -37,6 +37,9 @@ allow_module_level=True, ) +if device_name == "lightning.tensor": + pytest.skip("Skipping tests for the LightningTensor class.", allow_module_level=True) + if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 6fe677da1..3e0905bb3 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -30,6 +30,8 @@ except ImportError: pass +if device_name == "lightning.tensor": + pytest.skip("Skipping tests for the LightningTensor class.", allow_module_level=True) if not LightningDevice._new_API: pytest.skip( From 3e609487241281e0009b5cefd7a5523e2d9a6834 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 26 Aug 2024 14:26:45 +0000 Subject: [PATCH 076/130] Auto update version from '0.38.0-dev50' to '0.38.0-dev51' --- 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 970e51686..2531895cc 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev50" +__version__ = "0.38.0-dev51" From 6e9a6a3f286f38aaeaaff5409852ecf7948313c6 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 26 Aug 2024 14:28:41 +0000 Subject: [PATCH 077/130] Auto update version from '0.38.0-dev50' to '0.38.0-dev51' --- 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 970e51686..2531895cc 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev50" +__version__ = "0.38.0-dev51" From 2dc1bc86f675c04b637cfdf1412c14b0f7fc00d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 11:19:33 -0400 Subject: [PATCH 078/130] check flaky on CI --- .github/workflows/tests_lkcpu_python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_lkcpu_python.yml b/.github/workflows/tests_lkcpu_python.yml index 5aa836dd2..c2148be2d 100644 --- a/.github/workflows/tests_lkcpu_python.yml +++ b/.github/workflows/tests_lkcpu_python.yml @@ -34,7 +34,7 @@ on: env: TF_VERSION: 2.10.0 TORCH_VERSION: 1.11.0+cpu - COVERAGE_FLAGS: "--cov=pennylane_lightning --cov-report=term-missing --no-flaky-report -p no:warnings --tb=native" + COVERAGE_FLAGS: "--cov=pennylane_lightning --cov-report=term-missing -p no:warnings --tb=native" GCC_VERSION: 11 OMP_NUM_THREADS: "2" OMP_PROC_BIND: "false" From 7e57ec2dade7fd0c26b64a8eb844b61375691bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 26 Aug 2024 13:52:29 -0400 Subject: [PATCH 079/130] trigger CIs From 956538eaa697171f9dcdbb53bd0acffc4012706f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 27 Aug 2024 11:49:22 -0400 Subject: [PATCH 080/130] Simple unification of Lightning devices LK and LQ --- .../core/lightning_newAPI_base_test.py | 378 +++++++++++ .../lightning_kokkos/lightning_kokkos.py | 334 ++-------- .../lightning_qubit/lightning_qubit.py | 613 ++++++++++-------- tests/lightning_qubit/test_jacobian_method.py | 44 +- tests/lightning_qubit/test_simulate_method.py | 25 +- 5 files changed, 770 insertions(+), 624 deletions(-) create mode 100644 pennylane_lightning/core/lightning_newAPI_base_test.py diff --git a/pennylane_lightning/core/lightning_newAPI_base_test.py b/pennylane_lightning/core/lightning_newAPI_base_test.py new file mode 100644 index 000000000..b669e0edb --- /dev/null +++ b/pennylane_lightning/core/lightning_newAPI_base_test.py @@ -0,0 +1,378 @@ +# Copyright 2018-2023 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""" +This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that +interfaces with C++ for fast linear algebra calculations. +""" +import os +import sys +from dataclasses import replace +from numbers import Number +from pathlib import Path +from typing import Callable, Optional, Sequence, Tuple, Union + +import numpy as np +import pennylane as qml +from pennylane import DeviceError +from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices.default_qubit import adjoint_ops +from pennylane.devices.modifiers import simulator_tracking, single_tape_support +from pennylane.devices.preprocess import ( + decompose, + mid_circuit_measurements, + no_sampling, + validate_adjoint_trainable_params, + validate_device_wires, + validate_measurements, + validate_observables, +) +from pennylane.measurements import MidMeasureMP +from pennylane.operation import DecompositionUndefinedError, Operator, Tensor +from pennylane.ops import Prod, SProd, Sum +from pennylane.tape import QuantumScript, QuantumTape +from pennylane.transforms.core import TransformProgram +from pennylane.typing import Result, ResultBatch + + +# from ._adjoint_jacobian import LightningKokkosAdjointJacobian +# from ._measurements import LightningKokkosMeasurements +# from ._state_vector import LightningKokkosStateVector + + +Result_or_ResultBatch = Union[Result, ResultBatch] +QuantumTapeBatch = Sequence[QuantumTape] +QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] +PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] + +@simulator_tracking +@single_tape_support +class LightningBase(Device): + """PennyLane Lightning Kokkos 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_kokkos/installation` guide for more details. + + Args: + wires (int): the number of wires to initialize the device with + sync (bool): immediately sync with host-sv after applying operations + 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. + kokkos_args (InitializationSettings): binding for Kokkos::InitializationSettings + (threading parameters). + """ + + # pylint: disable=too-many-instance-attributes + + # General device options + _new_API = True + + def __init__( # pylint: disable=too-many-arguments + self, + device_name, + wires, + *, + c_dtype=np.complex128, + shots=None, + batch_obs=False, + ): + super().__init__(wires=wires, shots=shots) + + self._c_dtype = c_dtype + self._batch_obs = batch_obs + + if isinstance(wires, int): + self._wire_map = None # should just use wires as is + else: + self._wire_map = {w: i for i, w in enumerate(self.wires)} + + if device_name == "lightning.qubit": + from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian + from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector + + elif device_name == "lightning.kokkos": + from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian + from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements + from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector + + elif device_name == "lightning.gpu": + pass + + elif device_name == "lightning.tensor": + pass + else: + raise DeviceError(f'The device name "{device_name}" is not a valid option.') + + self.LightningStateVector = LightningStateVector + self.LightningAdjointJacobian = LightningAdjointJacobian + + + + @property + def c_dtype(self): + """State vector complex data type.""" + return self._c_dtype + + dtype = c_dtype + + def simulate(self, + circuit: QuantumScript, + state, + postselect_mode: str = None, + ) -> Result: + pass + + def jacobian(self, + circuit: QuantumTape, state, batch_obs=False, wire_map=None + ): + """Compute the Jacobian for a single quantum script. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (LightningKokkosStateVector): handle to the Lightning state vector + batch_obs (bool): Determine whether we process observables in parallel when + computing the jacobian. This value is only relevant when the lightning + kokkos is built with OpenMP. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + TensorLike: The Jacobian of the quantum script + """ + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + state.reset_state() + final_state = state.get_final_state(circuit) + return self.LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian( + circuit + ) + + + def simulate_and_jacobian(self, + circuit: QuantumTape, state, batch_obs=False, wire_map=None + ): + """Simulate a single quantum script and compute its Jacobian. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (LightningKokkosStateVector): handle to the Lightning state vector + batch_obs (bool): Determine whether we process observables in parallel when + computing the jacobian. This value is only relevant when the lightning + kokkos is built with OpenMP. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + Tuple[TensorLike]: The results of the simulation and the calculated Jacobian + + Note that this function can return measurements for non-commuting observables simultaneously. + """ + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + res = self.simulate(circuit, state) + jac = self.LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) + return res, jac + + + def vjp(self, + circuit: QuantumTape, + cotangents: Tuple[Number], + state, + batch_obs=False, + wire_map=None, + ): + """Compute the Vector-Jacobian Product (VJP) for a single quantum script. + Args: + circuit (QuantumTape): The single circuit to simulate + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must + have shape matching the output shape of the corresponding circuit. If + the circuit has a single output, ``cotangents`` may be a single number, + not an iterable of numbers. + state (LightningKokkosStateVector): handle to the Lightning state vector + batch_obs (bool): Determine whether we process observables in parallel when + computing the VJP. This value is only relevant when the lightning + kokkos is built with OpenMP. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + TensorLike: The VJP of the quantum script + """ + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + state.reset_state() + final_state = state.get_final_state(circuit) + return self.LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( + circuit, cotangents + ) + + + def simulate_and_vjp(self, + circuit: QuantumTape, + cotangents: Tuple[Number], + state, + batch_obs=False, + wire_map=None, + ): + """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). + Args: + circuit (QuantumTape): The single circuit to simulate + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must + have shape matching the output shape of the corresponding circuit. If + the circuit has a single output, ``cotangents`` may be a single number, + not an iterable of numbers. + state (LightningKokkosStateVector): handle to the Lightning state vector + batch_obs (bool): Determine whether we process observables in parallel when + computing the jacobian. This value is only relevant when the lightning + kokkos is built with OpenMP. + wire_map (Optional[dict]): a map from wire labels to simulation indices + + Returns: + Tuple[TensorLike]: The results of the simulation and the calculated VJP + Note that this function can return measurements for non-commuting observables simultaneously. + """ + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) + res = self.simulate(circuit, state) + _vjp = self.LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp( + circuit, cotangents + ) + return res, _vjp + + def compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Calculate the jacobian of either a single or a batch of circuits on the device. + + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for + execution_config (ExecutionConfig): a datastructure with all additional information required for execution + + Returns: + Tuple: The jacobian for each trainable parameter + """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + + return tuple( + self.jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit in circuits + ) + + def execute_and_compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Compute the results and jacobians of circuits at the same time. + + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits + execution_config (ExecutionConfig): a datastructure with all additional information required for execution + + Returns: + tuple: A numeric result of the computation and the gradient. + """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + self.simulate_and_jacobian( + c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for c in circuits + ) + return tuple(zip(*results)) + + def supports_vjp( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[QuantumTape] = None, + ) -> bool: + """Whether or not this device defines a custom vector jacobian product. + ``LightningKokkos`` supports adjoint differentiation with analytic results. + Args: + execution_config (ExecutionConfig): The configuration of the desired derivative calculation + circuit (QuantumTape): An optional circuit to check derivatives support for. + Returns: + Bool: Whether or not a derivative can be calculated provided the given information + """ + return self.supports_derivatives(execution_config, circuit) + + def compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + r"""The vector jacobian product used in reverse-mode differentiation. ``LightningKokkos`` uses the + adjoint differentiation method to compute the VJP. + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + of numbers. + execution_config (ExecutionConfig): a datastructure with all additional information required for execution + Returns: + tensor-like: A numeric result of computing the vector jacobian product + **Definition of vjp:** + If we have a function with jacobian: + .. math:: + \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} + The vector jacobian product is the inner product of the derivatives of the output ``y`` with the + Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. + .. math:: + \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} + **Shape of cotangents:** + The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, + the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following + shapes. ``batch_size`` below refers to the number of entries in the Jacobian: + * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` + * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, + then the shape must be ``(batch_size,)``. + """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + return tuple( + self.vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit, cots in zip(circuits, cotangents) + ) + + def execute_and_compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. + Args: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed + cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + of numbers. + execution_config (ExecutionConfig): a datastructure with all additional information required for execution + Returns: + Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product + """ + batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + results = tuple( + self.simulate_and_vjp( + circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for circuit, cots in zip(circuits, cotangents) + ) + return tuple(zip(*results)) + diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index cb02480c3..b44ecde6b 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -48,6 +48,8 @@ from ._measurements import LightningKokkosMeasurements from ._state_vector import LightningKokkosStateVector +from pennylane_lightning.core.lightning_newAPI_base_test import LightningBase + try: # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_kokkos_ops import backend_info, print_configuration @@ -62,166 +64,6 @@ QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] - -def simulate( - circuit: QuantumScript, - state: LightningKokkosStateVector, - postselect_mode: str = None, -) -> Result: - """Simulate a single quantum script. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningKokkosStateVector): handle to Lightning state vector - postselect_mode (str): Configuration for handling shots with mid-circuit measurement - postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to - keep the same number of shots. Default is ``None``. - - Returns: - Tuple[TensorLike]: The results of the simulation - - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)): - results = [] - aux_circ = qml.tape.QuantumScript( - circuit.operations, - circuit.measurements, - shots=[1], - trainable_params=circuit.trainable_params, - ) - for _ in range(circuit.shots.total_shots): - state.reset_state() - mid_measurements = {} - final_state = state.get_final_state( - aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - results.append( - LightningKokkosMeasurements(final_state).measure_final_state( - aux_circ, mid_measurements=mid_measurements - ) - ) - return tuple(results) - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningKokkosMeasurements(final_state).measure_final_state(circuit) - - -def jacobian( - circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None -): - """Compute the Jacobian for a single quantum script. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningKokkosStateVector): handle to the Lightning state vector - batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. Default is False. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - TensorLike: The Jacobian of the quantum script - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningKokkosAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian( - circuit - ) - - -def simulate_and_jacobian( - circuit: QuantumTape, state: LightningKokkosStateVector, batch_obs=False, wire_map=None -): - """Simulate a single quantum script and compute its Jacobian. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningKokkosStateVector): handle to the Lightning state vector - batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. Default is False. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - Tuple[TensorLike]: The results of the simulation and the calculated Jacobian - - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - res = simulate(circuit, state) - jac = LightningKokkosAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) - return res, jac - - -def vjp( - circuit: QuantumTape, - cotangents: Tuple[Number], - state: LightningKokkosStateVector, - batch_obs=False, - wire_map=None, -): - """Compute the Vector-Jacobian Product (VJP) for a single quantum script. - Args: - circuit (QuantumTape): The single circuit to simulate - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must - have shape matching the output shape of the corresponding circuit. If - the circuit has a single output, ``cotangents`` may be a single number, - not an iterable of numbers. - state (LightningKokkosStateVector): handle to the Lightning state vector - batch_obs (bool): Determine whether we process observables in parallel when - computing the VJP. This value is only relevant when the lightning - kokkos is built with OpenMP. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - TensorLike: The VJP of the quantum script - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningKokkosAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( - circuit, cotangents - ) - - -def simulate_and_vjp( - circuit: QuantumTape, - cotangents: Tuple[Number], - state: LightningKokkosStateVector, - batch_obs=False, - wire_map=None, -): - """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). - Args: - circuit (QuantumTape): The single circuit to simulate - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must - have shape matching the output shape of the corresponding circuit. If - the circuit has a single output, ``cotangents`` may be a single number, - not an iterable of numbers. - state (LightningKokkosStateVector): handle to the Lightning state vector - batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - Tuple[TensorLike]: The results of the simulation and the calculated VJP - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - res = simulate(circuit, state) - _vjp = LightningKokkosAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp( - circuit, cotangents - ) - return res, _vjp - - _operations = frozenset( { "Identity", @@ -410,7 +252,8 @@ def _kokkos_configuration(): @simulator_tracking @single_tape_support -class LightningKokkos(Device): +class LightningKokkos(LightningBase): + """PennyLane Lightning Kokkos device. A device that interfaces with C++ to perform fast linear algebra calculations. @@ -435,7 +278,6 @@ class LightningKokkos(Device): # General device options _device_options = ("c_dtype", "batch_obs") - _new_API = True # Device specific options _CPP_BINARY_AVAILABLE = LK_CPP_BINARY_AVAILABLE @@ -470,20 +312,13 @@ def __init__( # pylint: disable=too-many-arguments "https://pennylane-lightning.readthedocs.io/en/latest/installation.html." ) - super().__init__(wires=wires, shots=shots) - - if isinstance(wires, int): - self._wire_map = None # should just use wires as is - else: - self._wire_map = {w: i for i, w in enumerate(self.wires)} - - self._c_dtype = c_dtype - self._batch_obs = batch_obs + super().__init__(device_name='lightning.kokkos' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) # Kokkos specific options self._kokkos_args = kokkos_args self._sync = sync + # Creating the state vector self._statevector = LightningKokkosStateVector( num_wires=len(self.wires), dtype=c_dtype, kokkos_args=kokkos_args, sync=sync ) @@ -496,13 +331,6 @@ def name(self): """The name of the device.""" return "lightning.kokkos" - @property - def c_dtype(self): - """State vector complex data type.""" - return self._c_dtype - - dtype = c_dtype - def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. @@ -587,7 +415,7 @@ def execute( if self._wire_map is not None: [circuit], _ = qml.map_wires(circuit, self._wire_map) results.append( - simulate( + self.simulate( circuit, self._statevector, postselect_mode=execution_config.mcm_config.postselect_mode, @@ -621,127 +449,49 @@ def supports_derivatives( return True return _supports_adjoint(circuit=circuit) - def compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Calculate the jacobian of either a single or a batch of circuits on the device. + def simulate(self, + circuit: QuantumScript, + state, + postselect_mode: str = None, + ) -> Result: + """Simulate a single quantum script. Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for - execution_config (ExecutionConfig): a datastructure with all additional information required for execution + circuit (QuantumTape): The single circuit to simulate + state (LightningKokkosStateVector): handle to Lightning state vector + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. Returns: - Tuple: The jacobian for each trainable parameter - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - - return tuple( - jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit in circuits - ) - - def execute_and_compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Compute the results and jacobians of circuits at the same time. - - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits - execution_config (ExecutionConfig): a datastructure with all additional information required for execution + Tuple[TensorLike]: The results of the simulation - Returns: - tuple: A numeric result of the computation and the gradient. + Note that this function can return measurements for non-commuting observables simultaneously. """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_jacobian( - c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)): + results = [] + aux_circ = qml.tape.QuantumScript( + circuit.operations, + circuit.measurements, + shots=[1], + trainable_params=circuit.trainable_params, ) - for c in circuits - ) - return tuple(zip(*results)) - - def supports_vjp( - self, - execution_config: Optional[ExecutionConfig] = None, - circuit: Optional[QuantumTape] = None, - ) -> bool: - """Whether or not this device defines a custom vector jacobian product. - ``LightningKokkos`` supports adjoint differentiation with analytic results. - Args: - execution_config (ExecutionConfig): The configuration of the desired derivative calculation - circuit (QuantumTape): An optional circuit to check derivatives support for. - Returns: - Bool: Whether or not a derivative can be calculated provided the given information - """ - return self.supports_derivatives(execution_config, circuit) - - def compute_vjp( - self, - circuits: QuantumTape_or_Batch, - cotangents: Tuple[Number], - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - r"""The vector jacobian product used in reverse-mode differentiation. ``LightningKokkos`` uses the - adjoint differentiation method to compute the VJP. - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - of numbers. - execution_config (ExecutionConfig): a datastructure with all additional information required for execution - Returns: - tensor-like: A numeric result of computing the vector jacobian product - **Definition of vjp:** - If we have a function with jacobian: - .. math:: - \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} - The vector jacobian product is the inner product of the derivatives of the output ``y`` with the - Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. - .. math:: - \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} - **Shape of cotangents:** - The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, - the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following - shapes. ``batch_size`` below refers to the number of entries in the Jacobian: - * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` - * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, - then the shape must be ``(batch_size,)``. - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - return tuple( - vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit, cots in zip(circuits, cotangents) - ) + for _ in range(circuit.shots.total_shots): + state.reset_state() + mid_measurements = {} + final_state = state.get_final_state( + aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + results.append( + LightningKokkosMeasurements(final_state).measure_final_state( + aux_circ, mid_measurements=mid_measurements + ) + ) + return tuple(results) + state.reset_state() + final_state = state.get_final_state(circuit) + return LightningKokkosMeasurements(final_state).measure_final_state(circuit) - def execute_and_compute_vjp( - self, - circuits: QuantumTape_or_Batch, - cotangents: Tuple[Number], - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - of numbers. - execution_config (ExecutionConfig): a datastructure with all additional information required for execution - Returns: - Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_vjp( - circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - ) - for circuit, cots in zip(circuits, cotangents) - ) - return tuple(zip(*results)) @staticmethod def get_c_interface(): diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 0496b1b23..82a9e4976 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -45,6 +45,8 @@ from ._measurements import LightningMeasurements from ._state_vector import LightningStateVector +from pennylane_lightning.core.lightning_newAPI_base_test import LightningBase + try: # pylint: disable=import-error, unused-import from pennylane_lightning.lightning_qubit_ops import backend_info @@ -59,164 +61,164 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -def simulate( - circuit: QuantumScript, - state: LightningStateVector, - mcmc: dict = None, - postselect_mode: str = None, -) -> Result: - """Simulate a single quantum script. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector - mcmc (dict): Dictionary containing the Markov Chain Monte Carlo - parameters: mcmc, kernel_name, num_burnin. Descriptions of - these fields are found in :class:`~.LightningQubit`. - postselect_mode (str): Configuration for handling shots with mid-circuit measurement - postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to - keep the same number of shots. Default is ``None``. - - Returns: - Tuple[TensorLike]: The results of the simulation - - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if mcmc is None: - mcmc = {} - state.reset_state() - has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) - if circuit.shots and has_mcm: - results = [] - aux_circ = qml.tape.QuantumScript( - circuit.operations, - circuit.measurements, - shots=[1], - trainable_params=circuit.trainable_params, - ) - for _ in range(circuit.shots.total_shots): - state.reset_state() - mid_measurements = {} - final_state = state.get_final_state( - aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode - ) - results.append( - LightningMeasurements(final_state, **mcmc).measure_final_state( - aux_circ, mid_measurements=mid_measurements - ) - ) - return tuple(results) - final_state = state.get_final_state(circuit) - return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) - - -def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): - """Compute the Jacobian for a single quantum script. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector - 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. Default is False. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - TensorLike: The Jacobian of the quantum script - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) - - -def simulate_and_jacobian( - circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None -): - """Simulate a single quantum script and compute its Jacobian. - - Args: - circuit (QuantumTape): The single circuit to simulate - state (LightningStateVector): handle to Lightning state vector - 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. Default is False. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - Tuple[TensorLike]: The results of the simulation and the calculated Jacobian - - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - res = simulate(circuit, state) - jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) - return res, jac - - -def vjp( - circuit: QuantumTape, - cotangents: Tuple[Number], - state: LightningStateVector, - batch_obs=False, - wire_map=None, -): - """Compute the Vector-Jacobian Product (VJP) for a single quantum script. - Args: - circuit (QuantumTape): The single circuit to simulate - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must - have shape matching the output shape of the corresponding circuit. If - the circuit has a single output, ``cotangents`` may be a single number, - not an iterable of numbers. - state (LightningStateVector): handle to Lightning state vector - batch_obs (bool): Determine whether we process observables in parallel when - computing the VJP. This value is only relevant when the lightning - qubit is built with OpenMP. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - TensorLike: The VJP of the quantum script - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - state.reset_state() - final_state = state.get_final_state(circuit) - return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( - circuit, cotangents - ) - - -def simulate_and_vjp( - circuit: QuantumTape, - cotangents: Tuple[Number], - state: LightningStateVector, - batch_obs=False, - wire_map=None, -): - """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). - Args: - circuit (QuantumTape): The single circuit to simulate - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must - have shape matching the output shape of the corresponding circuit. If - the circuit has a single output, ``cotangents`` may be a single number, - not an iterable of numbers. - state (LightningStateVector): handle to Lightning state vector - 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. - wire_map (Optional[dict]): a map from wire labels to simulation indices - - Returns: - Tuple[TensorLike]: The results of the simulation and the calculated VJP - Note that this function can return measurements for non-commuting observables simultaneously. - """ - if wire_map is not None: - [circuit], _ = qml.map_wires(circuit, wire_map) - res = simulate(circuit, state) - _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) - return res, _vjp +# def simulate( +# circuit: QuantumScript, +# state: LightningStateVector, +# mcmc: dict = None, +# postselect_mode: str = None, +# ) -> Result: +# """Simulate a single quantum script. + +# Args: +# circuit (QuantumTape): The single circuit to simulate +# state (LightningStateVector): handle to Lightning state vector +# mcmc (dict): Dictionary containing the Markov Chain Monte Carlo +# parameters: mcmc, kernel_name, num_burnin. Descriptions of +# these fields are found in :class:`~.LightningQubit`. +# postselect_mode (str): Configuration for handling shots with mid-circuit measurement +# postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to +# keep the same number of shots. Default is ``None``. + +# Returns: +# Tuple[TensorLike]: The results of the simulation + +# Note that this function can return measurements for non-commuting observables simultaneously. +# """ +# if mcmc is None: +# mcmc = {} +# state.reset_state() +# has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) +# if circuit.shots and has_mcm: +# results = [] +# aux_circ = qml.tape.QuantumScript( +# circuit.operations, +# circuit.measurements, +# shots=[1], +# trainable_params=circuit.trainable_params, +# ) +# for _ in range(circuit.shots.total_shots): +# state.reset_state() +# mid_measurements = {} +# final_state = state.get_final_state( +# aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode +# ) +# results.append( +# LightningMeasurements(final_state, **mcmc).measure_final_state( +# aux_circ, mid_measurements=mid_measurements +# ) +# ) +# return tuple(results) +# final_state = state.get_final_state(circuit) +# return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) + + +# def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): +# """Compute the Jacobian for a single quantum script. + +# Args: +# circuit (QuantumTape): The single circuit to simulate +# state (LightningStateVector): handle to Lightning state vector +# 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. Default is False. +# wire_map (Optional[dict]): a map from wire labels to simulation indices + +# Returns: +# TensorLike: The Jacobian of the quantum script +# """ +# if wire_map is not None: +# [circuit], _ = qml.map_wires(circuit, wire_map) +# state.reset_state() +# final_state = state.get_final_state(circuit) +# return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) + + +# def simulate_and_jacobian( +# circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None +# ): +# """Simulate a single quantum script and compute its Jacobian. + +# Args: +# circuit (QuantumTape): The single circuit to simulate +# state (LightningStateVector): handle to Lightning state vector +# 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. Default is False. +# wire_map (Optional[dict]): a map from wire labels to simulation indices + +# Returns: +# Tuple[TensorLike]: The results of the simulation and the calculated Jacobian + +# Note that this function can return measurements for non-commuting observables simultaneously. +# """ +# if wire_map is not None: +# [circuit], _ = qml.map_wires(circuit, wire_map) +# res = simulate(circuit, state) +# jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) +# return res, jac + + +# def vjp( +# circuit: QuantumTape, +# cotangents: Tuple[Number], +# state: LightningStateVector, +# batch_obs=False, +# wire_map=None, +# ): +# """Compute the Vector-Jacobian Product (VJP) for a single quantum script. +# Args: +# circuit (QuantumTape): The single circuit to simulate +# cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must +# have shape matching the output shape of the corresponding circuit. If +# the circuit has a single output, ``cotangents`` may be a single number, +# not an iterable of numbers. +# state (LightningStateVector): handle to Lightning state vector +# batch_obs (bool): Determine whether we process observables in parallel when +# computing the VJP. This value is only relevant when the lightning +# qubit is built with OpenMP. +# wire_map (Optional[dict]): a map from wire labels to simulation indices + +# Returns: +# TensorLike: The VJP of the quantum script +# """ +# if wire_map is not None: +# [circuit], _ = qml.map_wires(circuit, wire_map) +# state.reset_state() +# final_state = state.get_final_state(circuit) +# return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( +# circuit, cotangents +# ) + + +# def simulate_and_vjp( +# circuit: QuantumTape, +# cotangents: Tuple[Number], +# state: LightningStateVector, +# batch_obs=False, +# wire_map=None, +# ): +# """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). +# Args: +# circuit (QuantumTape): The single circuit to simulate +# cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must +# have shape matching the output shape of the corresponding circuit. If +# the circuit has a single output, ``cotangents`` may be a single number, +# not an iterable of numbers. +# state (LightningStateVector): handle to Lightning state vector +# 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. +# wire_map (Optional[dict]): a map from wire labels to simulation indices + +# Returns: +# Tuple[TensorLike]: The results of the simulation and the calculated VJP +# Note that this function can return measurements for non-commuting observables simultaneously. +# """ +# if wire_map is not None: +# [circuit], _ = qml.map_wires(circuit, wire_map) +# res = simulate(circuit, state) +# _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) +# return res, _vjp _operations = frozenset( @@ -435,7 +437,7 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: @simulator_tracking @single_tape_support -class LightningQubit(Device): +class LightningQubit(LightningBase): """PennyLane Lightning Qubit device. A device that interfaces with C++ to perform fast linear algebra calculations. @@ -507,12 +509,7 @@ def __init__( # pylint: disable=too-many-arguments "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) - super().__init__(wires=wires, shots=shots) - - if isinstance(wires, int): - self._wire_map = None # should just use wires as is - else: - self._wire_map = {w: i for i, w in enumerate(self.wires)} + super().__init__(device_name='lightning.qubit' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) @@ -546,13 +543,6 @@ def name(self): """The name of the device.""" return "lightning.qubit" - @property - def c_dtype(self): - """State vector complex data type.""" - return self._c_dtype - - dtype = c_dtype - def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. @@ -639,7 +629,7 @@ def execute( if self._wire_map is not None: [circuit], _ = qml.map_wires(circuit, self._wire_map) results.append( - simulate( + self.simulate( circuit, self._statevector, mcmc=mcmc, @@ -674,124 +664,173 @@ def supports_derivatives( return True return _supports_adjoint(circuit=circuit) - def compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Calculate the jacobian of either a single or a batch of circuits on the device. + def simulate(self, + circuit: QuantumScript, + state: LightningStateVector, + mcmc: dict = None, + postselect_mode: str = None, + ) -> Result: + """Simulate a single quantum script. Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for - execution_config (ExecutionConfig): a datastructure with all additional information required for execution + circuit (QuantumTape): The single circuit to simulate + state (LightningStateVector): handle to Lightning state vector + mcmc (dict): Dictionary containing the Markov Chain Monte Carlo + parameters: mcmc, kernel_name, num_burnin. Descriptions of + these fields are found in :class:`~.LightningQubit`. + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. Returns: - Tuple: The jacobian for each trainable parameter - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - - return tuple( - jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit in circuits - ) - - def execute_and_compute_derivatives( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Compute the results and jacobians of circuits at the same time. - - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits - execution_config (ExecutionConfig): a datastructure with all additional information required for execution + Tuple[TensorLike]: The results of the simulation - Returns: - tuple: A numeric result of the computation and the gradient. + Note that this function can return measurements for non-commuting observables simultaneously. """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_jacobian( - c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + if mcmc is None: + mcmc = {} + state.reset_state() + if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)): + results = [] + aux_circ = qml.tape.QuantumScript( + circuit.operations, + circuit.measurements, + shots=[1], + trainable_params=circuit.trainable_params, ) - for c in circuits - ) - return tuple(zip(*results)) - - def supports_vjp( - self, - execution_config: Optional[ExecutionConfig] = None, - circuit: Optional[QuantumTape] = None, - ) -> bool: - """Whether or not this device defines a custom vector jacobian product. - ``LightningQubit`` supports adjoint differentiation with analytic results. - Args: - execution_config (ExecutionConfig): The configuration of the desired derivative calculation - circuit (QuantumTape): An optional circuit to check derivatives support for. - Returns: - Bool: Whether or not a derivative can be calculated provided the given information - """ - return self.supports_derivatives(execution_config, circuit) - - def compute_vjp( - self, - circuits: QuantumTape_or_Batch, - cotangents: Tuple[Number], - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - r"""The vector jacobian product used in reverse-mode differentiation. ``LightningQubit`` uses the - adjoint differentiation method to compute the VJP. - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - of numbers. - execution_config (ExecutionConfig): a datastructure with all additional information required for execution - Returns: - tensor-like: A numeric result of computing the vector jacobian product - **Definition of vjp:** - If we have a function with jacobian: - .. math:: - \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} - The vector jacobian product is the inner product of the derivatives of the output ``y`` with the - Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. - .. math:: - \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} - **Shape of cotangents:** - The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, - the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following - shapes. ``batch_size`` below refers to the number of entries in the Jacobian: - * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` - * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, - then the shape must be ``(batch_size,)``. - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - return tuple( - vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - for circuit, cots in zip(circuits, cotangents) - ) - - def execute_and_compute_vjp( - self, - circuits: QuantumTape_or_Batch, - cotangents: Tuple[Number], - execution_config: ExecutionConfig = DefaultExecutionConfig, - ): - """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed - cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - of numbers. - execution_config (ExecutionConfig): a datastructure with all additional information required for execution - Returns: - Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product - """ - batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - results = tuple( - simulate_and_vjp( - circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - ) - for circuit, cots in zip(circuits, cotangents) - ) - return tuple(zip(*results)) + for _ in range(circuit.shots.total_shots): + state.reset_state() + mid_measurements = {} + final_state = state.get_final_state( + aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode + ) + results.append( + LightningMeasurements(final_state, **mcmc).measure_final_state( + aux_circ, mid_measurements=mid_measurements + ) + ) + return tuple(results) + final_state = state.get_final_state(circuit) + return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) + + # def compute_derivatives( + # self, + # circuits: QuantumTape_or_Batch, + # execution_config: ExecutionConfig = DefaultExecutionConfig, + # ): + # """Calculate the jacobian of either a single or a batch of circuits on the device. + + # Args: + # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for + # execution_config (ExecutionConfig): a datastructure with all additional information required for execution + + # Returns: + # Tuple: The jacobian for each trainable parameter + # """ + # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + + # return tuple( + # jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + # for circuit in circuits + # ) + + # def execute_and_compute_derivatives( + # self, + # circuits: QuantumTape_or_Batch, + # execution_config: ExecutionConfig = DefaultExecutionConfig, + # ): + # """Compute the results and jacobians of circuits at the same time. + + # Args: + # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits + # execution_config (ExecutionConfig): a datastructure with all additional information required for execution + + # Returns: + # tuple: A numeric result of the computation and the gradient. + # """ + # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + # results = tuple( + # simulate_and_jacobian( + # c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + # ) + # for c in circuits + # ) + # return tuple(zip(*results)) + + # def supports_vjp( + # self, + # execution_config: Optional[ExecutionConfig] = None, + # circuit: Optional[QuantumTape] = None, + # ) -> bool: + # """Whether or not this device defines a custom vector jacobian product. + # ``LightningQubit`` supports adjoint differentiation with analytic results. + # Args: + # execution_config (ExecutionConfig): The configuration of the desired derivative calculation + # circuit (QuantumTape): An optional circuit to check derivatives support for. + # Returns: + # Bool: Whether or not a derivative can be calculated provided the given information + # """ + # return self.supports_derivatives(execution_config, circuit) + + # def compute_vjp( + # self, + # circuits: QuantumTape_or_Batch, + # cotangents: Tuple[Number], + # execution_config: ExecutionConfig = DefaultExecutionConfig, + # ): + # r"""The vector jacobian product used in reverse-mode differentiation. ``LightningQubit`` uses the + # adjoint differentiation method to compute the VJP. + # Args: + # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits + # cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + # corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + # of numbers. + # execution_config (ExecutionConfig): a datastructure with all additional information required for execution + # Returns: + # tensor-like: A numeric result of computing the vector jacobian product + # **Definition of vjp:** + # If we have a function with jacobian: + # .. math:: + # \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} + # The vector jacobian product is the inner product of the derivatives of the output ``y`` with the + # Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. + # .. math:: + # \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} + # **Shape of cotangents:** + # The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, + # the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following + # shapes. ``batch_size`` below refers to the number of entries in the Jacobian: + # * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` + # * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, + # then the shape must be ``(batch_size,)``. + # """ + # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + # return tuple( + # vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + # for circuit, cots in zip(circuits, cotangents) + # ) + + # def execute_and_compute_vjp( + # self, + # circuits: QuantumTape_or_Batch, + # cotangents: Tuple[Number], + # execution_config: ExecutionConfig = DefaultExecutionConfig, + # ): + # """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. + # Args: + # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed + # cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the + # corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable + # of numbers. + # execution_config (ExecutionConfig): a datastructure with all additional information required for execution + # Returns: + # Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product + # """ + # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + # results = tuple( + # simulate_and_vjp( + # circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + # ) + # for circuit, cots in zip(circuits, cotangents) + # ) + # return tuple(zip(*results)) diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index b91667a6d..b12ca047e 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -16,28 +16,10 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, LightningStateVector, device_name # tested device +from conftest import PHI, THETA, LightningDevice, device_name # tested device from pennylane.devices import DefaultExecutionConfig, DefaultQubit, ExecutionConfig from pennylane.tape import QuantumScript -if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit.lightning_qubit import ( - jacobian, - simulate, - simulate_and_jacobian, - simulate_and_vjp, - vjp, - ) - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos.lightning_kokkos import ( - jacobian, - simulate, - simulate_and_jacobian, - simulate_and_vjp, - vjp, - ) - if not LightningDevice._new_API: pytest.skip( "Exclusive tests for new API backends LightningAdjointJacobian class. Skipping.", @@ -49,8 +31,6 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) - - class TestJacobian: """Unit tests for the jacobian method with the new device API.""" @@ -68,13 +48,14 @@ def calculate_reference(tape, execute_and_derivatives=False): return transf_fn(results), jac @staticmethod - def process_and_execute(statevector, tape, execute_and_derivatives=False): + def process_and_execute(wires, statevector, tape, execute_and_derivatives=False): + device = LightningDevice(wires) if execute_and_derivatives: - results, jac = simulate_and_jacobian(tape, statevector) + results, jac = device.simulate_and_jacobian(tape, statevector) else: - results = simulate(tape, statevector) - jac = jacobian(tape, statevector) + results = device.simulate(tape, statevector) + jac = device.jacobian(tape, statevector) return results, jac @pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) @@ -105,7 +86,7 @@ def test_derivatives_single_expval( ) statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute(statevector, qs, execute_and_derivatives) + res, jac = self.process_and_execute(3,statevector, qs, execute_and_derivatives) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( @@ -144,14 +125,15 @@ def calculate_reference(tape, dy, execute_and_derivatives=False): return transf_fn(results), jac @staticmethod - def process_and_execute(statevector, tape, dy, execute_and_derivatives=False): + def process_and_execute(wires, statevector, tape, dy, execute_and_derivatives=False): dy = [dy] + device = LightningDevice(wires) if execute_and_derivatives: - results, jac = simulate_and_vjp(tape, dy, statevector) + results, jac = device.simulate_and_vjp(tape, dy, statevector) else: - results = simulate(tape, statevector) - jac = vjp(tape, dy, statevector) + results = device.simulate(tape, statevector) + jac = device.vjp(tape, dy, statevector) return results, jac @pytest.mark.usefixtures("use_legacy_and_new_opmath") @@ -182,7 +164,7 @@ def test_vjp_single_expval(self, theta, phi, obs, execute_and_derivatives, light dy = 1.0 statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute( + res, jac = self.process_and_execute(3, statevector, qs, dy, execute_and_derivatives=execute_and_derivatives ) if isinstance(obs, qml.Hamiltonian): diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index eb7ef501c..dd496169e 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -19,17 +19,8 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, LightningStateVector, device_name # tested device -from flaky import flaky +from conftest import PHI, THETA, LightningDevice, device_name # tested device from pennylane.devices import DefaultExecutionConfig, DefaultQubit -from pennylane.measurements import VarianceMP -from scipy.sparse import csr_matrix, random_array - -if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit.lightning_qubit import simulate - -if device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos.lightning_kokkos import simulate if not LightningDevice._new_API: pytest.skip( @@ -54,6 +45,11 @@ def calculate_reference(tape): tapes, transf_fn = program([tape]) results = dev.execute(tapes) return transf_fn(results) + + @staticmethod + def calculate_result(wires, tape, statevector): + dev = LightningDevice(wires) + return dev.simulate(circuit=tape, state=statevector) def test_simple_circuit(self, lightning_sv, tol): """Tests the simulate method for a simple circuit.""" @@ -63,7 +59,7 @@ def test_simple_circuit(self, lightning_sv, tol): shots=None, ) statevector = lightning_sv(num_wires=2) - result = simulate(circuit=tape, state=statevector) + result = self.calculate_result(2, tape, statevector) reference = self.calculate_reference(tape) assert np.allclose(result, reference, tol) @@ -81,7 +77,7 @@ def test_sample_dimensions(self, lightning_sv, num_shots, operation, shape): tape = qml.tape.QuantumScript(ops, [qml.sample(op=operation)], shots=num_shots) statevector = lightning_sv(num_wires=2) - result = simulate(circuit=tape, state=statevector) + result = self.calculate_result(2, tape, statevector) assert np.array_equal(result.shape, (shape,)) @@ -92,7 +88,7 @@ def test_sample_values(self, lightning_sv, tol): statevector = lightning_sv(num_wires=1) - result = simulate(circuit=tape, state=statevector) + result = self.calculate_result(1, tape, statevector) assert np.allclose(result**2, 1, atol=tol, rtol=0) @@ -117,7 +113,8 @@ def test_sample_values_with_mcmc(self, lightning_sv, tol, mcmc, kernel): execution_config = DefaultExecutionConfig - result = simulate( + dev = LightningDevice(wires=1) + result = dev.simulate( circuit=tape, state=statevector, mcmc=mcmc_param, postselect_mode=execution_config ) From 43b372440c5c8ec04c1636b694eeae8cc042a17a Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 27 Aug 2024 19:10:51 +0000 Subject: [PATCH 081/130] Auto update version from '0.38.0-dev51' to '0.38.0-dev52' --- 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 2531895cc..30103c25a 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev51" +__version__ = "0.38.0-dev52" From c57f78fd2aa1698e6d8e23b3dcfaa23f09bd9e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 27 Aug 2024 16:19:40 -0400 Subject: [PATCH 082/130] Define LBase without dispatcher --- .../core/_adjoint_jacobian_base.py | 24 ++++------ .../core/_measurements_base.py | 8 +--- .../core/_state_vector_base.py | 2 +- .../core/lightning_newAPI_base_test.py | 47 ++++++++++--------- .../lightning_kokkos/_adjoint_jacobian.py | 17 +++++-- .../lightning_kokkos/_state_vector.py | 7 +-- .../lightning_kokkos/lightning_kokkos.py | 10 +++- .../lightning_qubit/_adjoint_jacobian.py | 17 +++++-- .../lightning_qubit/lightning_qubit.py | 13 ++++- 9 files changed, 83 insertions(+), 62 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 48f2bf4ce..0624b5283 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -15,16 +15,6 @@ Internal methods for adjoint Jacobian differentiation method. """ -try: - from pennylane_lightning.lightning_kokkos_ops.algorithms import ( - AdjointJacobianC64, - AdjointJacobianC128, - create_ops_listC64, - create_ops_listC128, - ) -except ImportError: - pass - from os import getenv from typing import Any, List @@ -35,7 +25,6 @@ from pennylane.operation import Operation from pennylane.tape import QuantumTape -# pylint: disable=import-error, no-name-in-module, ungrouped-imports from pennylane_lightning.core._serialize import QuantumScriptSerializer from pennylane_lightning.core.lightning_base import _chunk_iterable @@ -54,7 +43,9 @@ def __init__(self, qubit_state: Any, batch_obs: bool = False) -> None: self._dtype = qubit_state.dtype self._batch_obs = batch_obs + # Dummy for the C++ bindings self._jacobian_lightning = None + self._create_ops_list_lightning = None @property def qubit_state(self): @@ -71,6 +62,13 @@ def dtype(self): """Returns the simulation data type.""" return self._dtype + def _adjoint_jacobian_dtype(self): + """Binding to Lightning [Device] Adjoint Jacobian C++ class. + + Returns: the AdjointJacobian class + """ + pass + @staticmethod def _get_return_type( measurements: List[MeasurementProcess], @@ -91,10 +89,6 @@ def _get_return_type( return Expectation - def _set_jacobian_lightning(self): - """Virtual method to create the C++ frontend _jacobian_lightning.""" - pass - def _process_jacobian_tape( self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False ): diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index 92504415d..b2a4b8b67 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -15,12 +15,6 @@ Class implementation for state vector measurements. """ -# pylint: disable=import-error, no-name-in-module, ungrouped-imports -try: - from pennylane_lightning.lightning_kokkos_ops import MeasurementsC64, MeasurementsC128 -except ImportError: - pass - from typing import Callable, List, Union, Any import numpy as np @@ -61,8 +55,8 @@ def __init__( ) -> None: self._qubit_state = qubit_state self._dtype = qubit_state.dtype - # self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) + # self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) self._measurement_lightning = None @property diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index d16512c54..b5961ba12 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -21,7 +21,6 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires - class LightningBaseStateVector: """Lightning state-vector class. @@ -83,6 +82,7 @@ def state(self): [0.+0.j 1.+0.j] """ pass + def _state_dtype(self): """Binding to Lightning Managed state vector C++ class. diff --git a/pennylane_lightning/core/lightning_newAPI_base_test.py b/pennylane_lightning/core/lightning_newAPI_base_test.py index b669e0edb..bc9319d63 100644 --- a/pennylane_lightning/core/lightning_newAPI_base_test.py +++ b/pennylane_lightning/core/lightning_newAPI_base_test.py @@ -103,38 +103,43 @@ def __init__( # pylint: disable=too-many-arguments else: self._wire_map = {w: i for i, w in enumerate(self.wires)} - if device_name == "lightning.qubit": - from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian - from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements - from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector - - elif device_name == "lightning.kokkos": - from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian - from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements - from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector - - elif device_name == "lightning.gpu": - pass - - elif device_name == "lightning.tensor": - pass - else: - raise DeviceError(f'The device name "{device_name}" is not a valid option.') + # if device_name == "lightning.qubit": + # from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian + # from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements + # from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector + + # elif device_name == "lightning.kokkos": + # from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian + # from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements + # from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector + + # elif device_name == "lightning.gpu": + # pass + + # elif device_name == "lightning.tensor": + # pass + # else: + # raise DeviceError(f'The device name "{device_name}" is not a valid option.') - self.LightningStateVector = LightningStateVector - self.LightningAdjointJacobian = LightningAdjointJacobian + self.LightningStateVector = None + self.LightningMeasurements = None + self.LightningAdjointJacobian = None - - + @property def c_dtype(self): """State vector complex data type.""" return self._c_dtype dtype = c_dtype + + def _set_Lightning_classes(self): + pass + def simulate(self, circuit: QuantumScript, + # state: LightningStateVector, state, postselect_mode: str = None, ) -> Result: diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index c5dd18d82..acca24557 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -28,6 +28,7 @@ import numpy as np from ._state_vector import LightningKokkosStateVector + from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): @@ -35,13 +36,19 @@ class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) - # Initialize the C++ bind - self._set_jacobian_lightning() + # Initialize the C++ binds + self._jacobian_lightning, self._create_ops_list_lightning = self._adjoint_jacobian_dtype() + - def _set_jacobian_lightning(self): - self._jacobian_lightning = ( + def _adjoint_jacobian_dtype(self): + """Binding to Lightning Kokkos Adjoint Jacobian C++ class. + + Returns: the AdjointJacobian class + """ + jacobian_lightning = ( AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() ) - self._create_ops_list_lightning = ( + create_ops_list_lightning = ( create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 ) + return jacobian_lightning, create_ops_list_lightning diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index ba0ebbe1d..df34b787a 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -40,10 +40,7 @@ from ._measurements import LightningKokkosMeasurements from pennylane_lightning.core._state_vector_base import LightningBaseStateVector - - - -class LightningKokkosStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods +class LightningKokkosStateVector(LightningBaseStateVector): """Lightning Kokkos state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -66,7 +63,7 @@ def __init__( device_name="lightning.kokkos", kokkos_args=None, sync=True, - ): # pylint: disable=too-many-arguments + ): if device_name != "lightning.kokkos": raise DeviceError(f'The device name "{device_name}" is not a valid option.') diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index b44ecde6b..4edc60d1f 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -313,13 +313,16 @@ def __init__( # pylint: disable=too-many-arguments ) super().__init__(device_name='lightning.kokkos' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) + + # Set the attributes to call the Lightning classes + self._set_Lightning_classes() # Kokkos specific options self._kokkos_args = kokkos_args self._sync = sync # Creating the state vector - self._statevector = LightningKokkosStateVector( + self._statevector = self.LightningStateVector( num_wires=len(self.wires), dtype=c_dtype, kokkos_args=kokkos_args, sync=sync ) @@ -331,6 +334,11 @@ def name(self): """The name of the device.""" return "lightning.kokkos" + def _set_Lightning_classes(self): + self.LightningStateVector = LightningKokkosStateVector + self.LightningMeasurements = LightningKokkosMeasurements + self.LightningAdjointJacobian = LightningKokkosAdjointJacobian + def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 9cfc59779..2c0953783 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -28,6 +28,7 @@ import numpy as np from ._state_vector import LightningStateVector + from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian class LightningAdjointJacobian(LightningBaseAdjointJacobian): @@ -35,13 +36,19 @@ class LightningAdjointJacobian(LightningBaseAdjointJacobian): def __init__(self, qubit_state: LightningStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) - # Initialize the C++ bind - self._set_jacobian_lightning() + # Initialize the C++ binds + self._jacobian_lightning, self._create_ops_list_lightning = self._adjoint_jacobian_dtype() + - def _set_jacobian_lightning(self): - self._jacobian_lightning = ( + def _adjoint_jacobian_dtype(self): + """Binding to Lightning Qubit Adjoint Jacobian C++ class. + + Returns: the AdjointJacobian class + """ + jacobian_lightning = ( AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() ) - self._create_ops_list_lightning = ( + create_ops_list_lightning = ( create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 ) + return jacobian_lightning, create_ops_list_lightning diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 82a9e4976..6b4711466 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -510,8 +510,11 @@ def __init__( # pylint: disable=too-many-arguments ) super().__init__(device_name='lightning.qubit' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) - - self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) + + # Set the attributes to call the Lightning classes + self._set_Lightning_classes() + + self._statevector = self.LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) # TODO: Investigate usefulness of creating numpy random generator seed = np.random.randint(0, high=10000000) if seed == "global" else seed @@ -542,6 +545,12 @@ def __init__( # pylint: disable=too-many-arguments def name(self): """The name of the device.""" return "lightning.qubit" + + def _set_Lightning_classes(self): + self.LightningStateVector = LightningStateVector + self.LightningMeasurements = LightningMeasurements + self.LightningAdjointJacobian = LightningAdjointJacobian + def _setup_execution_config(self, config): """ From d2f415f460affe8c42f3cd55b363e764c53b5816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 27 Aug 2024 17:20:52 -0400 Subject: [PATCH 083/130] add code style and doctrings --- .../core/_measurements_base.py | 15 +- .../core/_state_vector_base.py | 6 +- ..._base_test.py => lightning_newAPI_base.py} | 44 +-- .../lightning_kokkos/_state_vector.py | 19 +- .../lightning_kokkos/lightning_kokkos.py | 43 ++- .../lightning_qubit/_measurements.py | 2 +- .../lightning_qubit/_state_vector.py | 9 +- .../lightning_qubit/lightning_qubit.py | 340 ++---------------- 8 files changed, 87 insertions(+), 391 deletions(-) rename pennylane_lightning/core/{lightning_newAPI_base_test.py => lightning_newAPI_base.py} (88%) diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index b2a4b8b67..3f22806dd 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -22,7 +22,6 @@ from pennylane.devices.qubit.sampling import _group_measurements from pennylane.measurements import ( ClassicalShadowMP, - CountsMP, ExpectationMP, MeasurementProcess, ProbabilityMP, @@ -41,7 +40,7 @@ class LightningBaseMeasurements: - """Lightning Kokkos Measurements class + """Lightning [Device] Measurements class Measures the state provided by the Lightning[Device]StateVector class. @@ -56,7 +55,7 @@ def __init__( self._qubit_state = qubit_state self._dtype = qubit_state.dtype - # self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) + # Dummy for the C++ bindings self._measurement_lightning = None @property @@ -68,11 +67,13 @@ def qubit_state(self): def dtype(self): """Returns the simulation data type.""" return self._dtype - - def _set_measurement_lightning(self): - """Virtual method to create the C++ frontend _measurement_lightning.""" - pass + + def _measurement_dtype(self): + """Binding to Lightning Kokkos Measurements C++ class. + Returns: the Measurements class + """ + pass def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> TensorLike: """Apply a measurement to state when the measurement process has an observable with diagonalizing gates. diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index b5961ba12..67573b304 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -16,6 +16,7 @@ """ import numpy as np +import pennylane as qml from pennylane import BasisState, StatePrep from pennylane.measurements import MidMeasureMP from pennylane.tape import QuantumScript @@ -30,7 +31,6 @@ class LightningBaseStateVector: 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``. Default is ``np.complex128`` - device_name(string): state vector device name. Options: ["lightning.qubit"] """ def __init__(self, num_wires, dtype=np.complex128): @@ -42,7 +42,9 @@ def __init__(self, num_wires, dtype=np.complex128): self._wires = Wires(range(num_wires)) self._dtype = dtype + # Dummy for the device name self._device_name = None + # Dummy for the C++ bindings self._qubit_state = None @property @@ -204,7 +206,7 @@ def get_final_state( keep the same number of shots. Default is ``None``. Returns: - LightningStateVector: Lightning final state class. + Lightning[Device]StateVector: Lightning final state class. """ self.apply_operations( diff --git a/pennylane_lightning/core/lightning_newAPI_base_test.py b/pennylane_lightning/core/lightning_newAPI_base.py similarity index 88% rename from pennylane_lightning/core/lightning_newAPI_base_test.py rename to pennylane_lightning/core/lightning_newAPI_base.py index bc9319d63..935a7f17f 100644 --- a/pennylane_lightning/core/lightning_newAPI_base_test.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -16,41 +16,17 @@ This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that interfaces with C++ for fast linear algebra calculations. """ -import os -import sys -from dataclasses import replace from numbers import Number -from pathlib import Path from typing import Callable, Optional, Sequence, Tuple, Union import numpy as np import pennylane as qml -from pennylane import DeviceError from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig -from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support -from pennylane.devices.preprocess import ( - decompose, - mid_circuit_measurements, - no_sampling, - validate_adjoint_trainable_params, - validate_device_wires, - validate_measurements, - validate_observables, -) -from pennylane.measurements import MidMeasureMP -from pennylane.operation import DecompositionUndefinedError, Operator, Tensor -from pennylane.ops import Prod, SProd, Sum from pennylane.tape import QuantumScript, QuantumTape -from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -# from ._adjoint_jacobian import LightningKokkosAdjointJacobian -# from ._measurements import LightningKokkosMeasurements -# from ._state_vector import LightningKokkosStateVector - - Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] @@ -103,24 +79,7 @@ def __init__( # pylint: disable=too-many-arguments else: self._wire_map = {w: i for i, w in enumerate(self.wires)} - # if device_name == "lightning.qubit": - # from pennylane_lightning.lightning_qubit._adjoint_jacobian import LightningAdjointJacobian - # from pennylane_lightning.lightning_qubit._measurements import LightningMeasurements - # from pennylane_lightning.lightning_qubit._state_vector import LightningStateVector - - # elif device_name == "lightning.kokkos": - # from pennylane_lightning.lightning_kokkos._adjoint_jacobian import LightningKokkosAdjointJacobian as LightningAdjointJacobian - # from pennylane_lightning.lightning_kokkos._measurements import LightningKokkosMeasurements as LightningMeasurements - # from pennylane_lightning.lightning_kokkos._state_vector import LightningKokkosStateVector as LightningStateVector - - # elif device_name == "lightning.gpu": - # pass - - # elif device_name == "lightning.tensor": - # pass - # else: - # raise DeviceError(f'The device name "{device_name}" is not a valid option.') - + # Dummy for LightningStateVector, LightningMeasurements, LightningAdjointJacobian self.LightningStateVector = None self.LightningMeasurements = None self.LightningAdjointJacobian = None @@ -134,6 +93,7 @@ def c_dtype(self): dtype = c_dtype def _set_Lightning_classes(self): + """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" pass diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index df34b787a..3dc87b74f 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -69,12 +69,13 @@ def __init__( raise DeviceError(f'The device name "{device_name}" is not a valid option.') super().__init__(num_wires, dtype) - + self._device_name = device_name self._kokkos_config = {} self._sync = sync + # Initialize the state vector if kokkos_args is None: self._qubit_state = self._state_dtype()(self.num_wires) elif isinstance(kokkos_args, InitializationSettings): @@ -106,6 +107,13 @@ def state(self): self.sync_d2h(state) return state + def _state_dtype(self): + """Binding to Lightning Managed state vector C++ class. + + Returns: the state vector class + """ + return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 + def sync_h2d(self, state_vector): """Copy the state vector data on host provided by the user to the state vector on the device @@ -154,12 +162,6 @@ def _kokkos_configuration(self): """ return print_configuration() - def _state_dtype(self): - """Binding to Lightning Managed state vector C++ class. - - Returns: the state vector class - """ - return StateVectorC128 if self.dtype == np.complex128 else StateVectorC64 def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. @@ -181,7 +183,8 @@ def _apply_state_vector(self, state, device_wires: Wires): self.sync_h2d(np.reshape(state, output_shape)) return - self._qubit_state.setStateVector(state, list(device_wires)) # this operation on device + # This operate on device + self._qubit_state.setStateVector(state, list(device_wires)) def _apply_lightning_controlled(self, operation): """Apply an arbitrary controlled operation to the state tensor. diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 4edc60d1f..c765f665d 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -11,7 +11,6 @@ # 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""" This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that interfaces with C++ for fast linear algebra calculations. @@ -19,13 +18,12 @@ import os import sys from dataclasses import replace -from numbers import Number from pathlib import Path -from typing import Callable, Optional, Sequence, Tuple, Union +from typing import Optional, Sequence import numpy as np import pennylane as qml -from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices import DefaultExecutionConfig, ExecutionConfig from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support from pennylane.devices.preprocess import ( @@ -48,10 +46,16 @@ from ._measurements import LightningKokkosMeasurements from ._state_vector import LightningKokkosStateVector -from pennylane_lightning.core.lightning_newAPI_base_test import LightningBase +from pennylane_lightning.core.lightning_newAPI_base import ( + LightningBase, + Result_or_ResultBatch, + QuantumTapeBatch, + QuantumTape_or_Batch, + PostprocessingFn, +) + try: - # pylint: disable=import-error, no-name-in-module from pennylane_lightning.lightning_kokkos_ops import backend_info, print_configuration LK_CPP_BINARY_AVAILABLE = True @@ -59,11 +63,12 @@ LK_CPP_BINARY_AVAILABLE = False backend_info = None -Result_or_ResultBatch = Union[Result, ResultBatch] -QuantumTapeBatch = Sequence[QuantumTape] -QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] -PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] +# Result_or_ResultBatch = Union[Result, ResultBatch] +# QuantumTapeBatch = Sequence[QuantumTape] +# QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] +# PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] +# The set of supported operations. _operations = frozenset( { "Identity", @@ -126,8 +131,9 @@ "C(BlockEncode)", } ) -# The set of supported operations. +# End the set of supported operations. +# The set of supported observables. _observables = frozenset( { "PauliX", @@ -146,12 +152,10 @@ "Exp", } ) -# The set of supported observables. def stopping_condition(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.kokkos``.""" - # These thresholds are adapted from `lightning_base.py` # To avoid building matrices beyond the given thresholds. # This should reduce runtime overheads for larger systems. if isinstance(op, qml.QFT): @@ -245,11 +249,10 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: program.add_transform(qml.transforms.broadcast_expand) program.add_transform(validate_adjoint_trainable_params) - +# Kokkos specific methods def _kokkos_configuration(): return print_configuration() - @simulator_tracking @single_tape_support class LightningKokkos(LightningBase): @@ -263,18 +266,17 @@ class LightningKokkos(LightningBase): Args: wires (int): the number of wires to initialize the device with - sync (bool): immediately sync with host-sv after applying operations 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. + sync (bool): immediately sync with host-sv after applying operations kokkos_args (InitializationSettings): binding for Kokkos::InitializationSettings (threading parameters). """ - # pylint: disable=too-many-instance-attributes # General device options _device_options = ("c_dtype", "batch_obs") @@ -309,7 +311,7 @@ def __init__( # pylint: disable=too-many-arguments raise ImportError( "Pre-compiled binaries for lightning.kokkos are not available. " "To manually compile from source, follow the instructions at " - "https://pennylane-lightning.readthedocs.io/en/latest/installation.html." + "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) super().__init__(device_name='lightning.kokkos' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) @@ -335,6 +337,7 @@ def name(self): return "lightning.kokkos" def _set_Lightning_classes(self): + """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" self.LightningStateVector = LightningKokkosStateVector self.LightningMeasurements = LightningKokkosMeasurements self.LightningAdjointJacobian = LightningKokkosAdjointJacobian @@ -390,6 +393,7 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) program.add_transform( mid_circuit_measurements, device=self, mcm_config=exec_config.mcm_config ) + program.add_transform( decompose, stopping_condition=stopping_condition, @@ -459,7 +463,7 @@ def supports_derivatives( def simulate(self, circuit: QuantumScript, - state, + state: LightningKokkosStateVector, postselect_mode: str = None, ) -> Result: """Simulate a single quantum script. @@ -496,6 +500,7 @@ def simulate(self, ) ) return tuple(results) + state.reset_state() final_state = state.get_final_state(circuit) return LightningKokkosMeasurements(final_state).measure_final_state(circuit) diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index e1d0039bb..94ba554a7 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -36,7 +36,7 @@ class LightningMeasurements(LightningBaseMeasurements): - """Lightning Measurements class + """Lightning Qubit Measurements class Measures the state provided by the LightningStateVector class. diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 27c6050ba..ccb8a5b35 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Class implementation for state-vector manipulation. +Class implementation for lightning_qubit state-vector manipulation. """ try: @@ -36,10 +36,8 @@ from ._measurements import LightningMeasurements from pennylane_lightning.core._state_vector_base import LightningBaseStateVector - - class LightningStateVector(LightningBaseStateVector): - """Lightning state-vector class. + """Lightning Qubit state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -56,8 +54,9 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit" super().__init__(num_wires, dtype) - self._device_name = device_name + + # Initialize the state vector self._qubit_state = self._state_dtype()(self._num_wires) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 6b4711466..3a72efb7d 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -11,18 +11,18 @@ # 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 LightningQubit class that inherits from the new device interface. +r""" +This module contains the LightningQubit class, a PennyLane simulator device that +interfaces with C++ for fast linear algebra calculations. """ from dataclasses import replace from functools import reduce -from numbers import Number from pathlib import Path -from typing import Callable, Optional, Sequence, Tuple, Union +from typing import Optional, Sequence import numpy as np import pennylane as qml -from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices import DefaultExecutionConfig, ExecutionConfig from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support from pennylane.devices.preprocess import ( @@ -45,182 +45,28 @@ from ._measurements import LightningMeasurements from ._state_vector import LightningStateVector -from pennylane_lightning.core.lightning_newAPI_base_test import LightningBase +from pennylane_lightning.core.lightning_newAPI_base import ( + LightningBase, + Result_or_ResultBatch, + QuantumTapeBatch, + QuantumTape_or_Batch, + PostprocessingFn, +) + try: - # pylint: disable=import-error, unused-import from pennylane_lightning.lightning_qubit_ops import backend_info LQ_CPP_BINARY_AVAILABLE = True except ImportError: LQ_CPP_BINARY_AVAILABLE = False -Result_or_ResultBatch = Union[Result, ResultBatch] -QuantumTapeBatch = Sequence[QuantumTape] -QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] -PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] - - -# def simulate( -# circuit: QuantumScript, -# state: LightningStateVector, -# mcmc: dict = None, -# postselect_mode: str = None, -# ) -> Result: -# """Simulate a single quantum script. - -# Args: -# circuit (QuantumTape): The single circuit to simulate -# state (LightningStateVector): handle to Lightning state vector -# mcmc (dict): Dictionary containing the Markov Chain Monte Carlo -# parameters: mcmc, kernel_name, num_burnin. Descriptions of -# these fields are found in :class:`~.LightningQubit`. -# postselect_mode (str): Configuration for handling shots with mid-circuit measurement -# postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to -# keep the same number of shots. Default is ``None``. - -# Returns: -# Tuple[TensorLike]: The results of the simulation - -# Note that this function can return measurements for non-commuting observables simultaneously. -# """ -# if mcmc is None: -# mcmc = {} -# state.reset_state() -# has_mcm = any(isinstance(op, MidMeasureMP) for op in circuit.operations) -# if circuit.shots and has_mcm: -# results = [] -# aux_circ = qml.tape.QuantumScript( -# circuit.operations, -# circuit.measurements, -# shots=[1], -# trainable_params=circuit.trainable_params, -# ) -# for _ in range(circuit.shots.total_shots): -# state.reset_state() -# mid_measurements = {} -# final_state = state.get_final_state( -# aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode -# ) -# results.append( -# LightningMeasurements(final_state, **mcmc).measure_final_state( -# aux_circ, mid_measurements=mid_measurements -# ) -# ) -# return tuple(results) -# final_state = state.get_final_state(circuit) -# return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) - - -# def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): -# """Compute the Jacobian for a single quantum script. - -# Args: -# circuit (QuantumTape): The single circuit to simulate -# state (LightningStateVector): handle to Lightning state vector -# 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. Default is False. -# wire_map (Optional[dict]): a map from wire labels to simulation indices - -# Returns: -# TensorLike: The Jacobian of the quantum script -# """ -# if wire_map is not None: -# [circuit], _ = qml.map_wires(circuit, wire_map) -# state.reset_state() -# final_state = state.get_final_state(circuit) -# return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) - - -# def simulate_and_jacobian( -# circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None -# ): -# """Simulate a single quantum script and compute its Jacobian. - -# Args: -# circuit (QuantumTape): The single circuit to simulate -# state (LightningStateVector): handle to Lightning state vector -# 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. Default is False. -# wire_map (Optional[dict]): a map from wire labels to simulation indices - -# Returns: -# Tuple[TensorLike]: The results of the simulation and the calculated Jacobian - -# Note that this function can return measurements for non-commuting observables simultaneously. -# """ -# if wire_map is not None: -# [circuit], _ = qml.map_wires(circuit, wire_map) -# res = simulate(circuit, state) -# jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) -# return res, jac - - -# def vjp( -# circuit: QuantumTape, -# cotangents: Tuple[Number], -# state: LightningStateVector, -# batch_obs=False, -# wire_map=None, -# ): -# """Compute the Vector-Jacobian Product (VJP) for a single quantum script. -# Args: -# circuit (QuantumTape): The single circuit to simulate -# cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must -# have shape matching the output shape of the corresponding circuit. If -# the circuit has a single output, ``cotangents`` may be a single number, -# not an iterable of numbers. -# state (LightningStateVector): handle to Lightning state vector -# batch_obs (bool): Determine whether we process observables in parallel when -# computing the VJP. This value is only relevant when the lightning -# qubit is built with OpenMP. -# wire_map (Optional[dict]): a map from wire labels to simulation indices - -# Returns: -# TensorLike: The VJP of the quantum script -# """ -# if wire_map is not None: -# [circuit], _ = qml.map_wires(circuit, wire_map) -# state.reset_state() -# final_state = state.get_final_state(circuit) -# return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( -# circuit, cotangents -# ) - - -# def simulate_and_vjp( -# circuit: QuantumTape, -# cotangents: Tuple[Number], -# state: LightningStateVector, -# batch_obs=False, -# wire_map=None, -# ): -# """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). -# Args: -# circuit (QuantumTape): The single circuit to simulate -# cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must -# have shape matching the output shape of the corresponding circuit. If -# the circuit has a single output, ``cotangents`` may be a single number, -# not an iterable of numbers. -# state (LightningStateVector): handle to Lightning state vector -# 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. -# wire_map (Optional[dict]): a map from wire labels to simulation indices - -# Returns: -# Tuple[TensorLike]: The results of the simulation and the calculated VJP -# Note that this function can return measurements for non-commuting observables simultaneously. -# """ -# if wire_map is not None: -# [circuit], _ = qml.map_wires(circuit, wire_map) -# res = simulate(circuit, state) -# _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) -# return res, _vjp - +# Result_or_ResultBatch = Union[Result, ResultBatch] +# QuantumTapeBatch = Sequence[QuantumTape] +# QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] +# PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] +# The set of supported operations. _operations = frozenset( { "Identity", @@ -305,9 +151,9 @@ "C(BlockEncode)", } ) -# The set of supported operations. - +# End the set of supported operations. +# The set of supported observables. _observables = frozenset( { "PauliX", @@ -326,12 +172,10 @@ "Exp", } ) -# The set of supported observables. def stopping_condition(op: Operator) -> bool: """A function that determines whether or not an operation is supported by ``lightning.qubit``.""" - # These thresholds are adapted from `lightning_base.py` # To avoid building matrices beyond the given thresholds. # This should reduce runtime overheads for larger systems. if isinstance(op, qml.QFT): @@ -473,11 +317,11 @@ class LightningQubit(LightningBase): qubit is built with OpenMP. """ - # pylint: disable=too-many-instance-attributes + # General device options _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") + # Device specific options _CPP_BINARY_AVAILABLE = LQ_CPP_BINARY_AVAILABLE - _new_API = True _backend_info = backend_info if LQ_CPP_BINARY_AVAILABLE else None # This `config` is used in Catalyst-Frontend @@ -496,11 +340,12 @@ def __init__( # pylint: disable=too-many-arguments *, c_dtype=np.complex128, shots=None, + batch_obs=False, + # Markov Chain Monte Carlo (MCMC) sampling method arguments seed="global", mcmc=False, kernel_name="Local", num_burnin=100, - batch_obs=False, ): if not self._CPP_BINARY_AVAILABLE: raise ImportError( @@ -513,15 +358,12 @@ def __init__( # pylint: disable=too-many-arguments # Set the attributes to call the Lightning classes self._set_Lightning_classes() - - self._statevector = self.LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) + # Markov Chain Monte Carlo (MCMC) sampling method specific options # TODO: Investigate usefulness of creating numpy random generator 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 [ @@ -541,17 +383,20 @@ def __init__( # pylint: disable=too-many-arguments self._kernel_name = None self._num_burnin = 0 + # Creating the state vector + self._statevector = self.LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) + @property def name(self): """The name of the device.""" return "lightning.qubit" - + def _set_Lightning_classes(self): + """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" self.LightningStateVector = LightningStateVector self.LightningMeasurements = LightningMeasurements self.LightningAdjointJacobian = LightningAdjointJacobian - def _setup_execution_config(self, config): """ Update the execution config with choices for how the device should be used and the device options. @@ -698,7 +543,6 @@ def simulate(self, """ if mcmc is None: mcmc = {} - state.reset_state() if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)): results = [] aux_circ = qml.tape.QuantumScript( @@ -719,127 +563,9 @@ def simulate(self, ) ) return tuple(results) + + state.reset_state() final_state = state.get_final_state(circuit) return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) - # def compute_derivatives( - # self, - # circuits: QuantumTape_or_Batch, - # execution_config: ExecutionConfig = DefaultExecutionConfig, - # ): - # """Calculate the jacobian of either a single or a batch of circuits on the device. - - # Args: - # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits to calculate derivatives for - # execution_config (ExecutionConfig): a datastructure with all additional information required for execution - - # Returns: - # Tuple: The jacobian for each trainable parameter - # """ - # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - - # return tuple( - # jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - # for circuit in circuits - # ) - - # def execute_and_compute_derivatives( - # self, - # circuits: QuantumTape_or_Batch, - # execution_config: ExecutionConfig = DefaultExecutionConfig, - # ): - # """Compute the results and jacobians of circuits at the same time. - - # Args: - # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuits or batch of circuits - # execution_config (ExecutionConfig): a datastructure with all additional information required for execution - - # Returns: - # tuple: A numeric result of the computation and the gradient. - # """ - # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - # results = tuple( - # simulate_and_jacobian( - # c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - # ) - # for c in circuits - # ) - # return tuple(zip(*results)) - - # def supports_vjp( - # self, - # execution_config: Optional[ExecutionConfig] = None, - # circuit: Optional[QuantumTape] = None, - # ) -> bool: - # """Whether or not this device defines a custom vector jacobian product. - # ``LightningQubit`` supports adjoint differentiation with analytic results. - # Args: - # execution_config (ExecutionConfig): The configuration of the desired derivative calculation - # circuit (QuantumTape): An optional circuit to check derivatives support for. - # Returns: - # Bool: Whether or not a derivative can be calculated provided the given information - # """ - # return self.supports_derivatives(execution_config, circuit) - - # def compute_vjp( - # self, - # circuits: QuantumTape_or_Batch, - # cotangents: Tuple[Number], - # execution_config: ExecutionConfig = DefaultExecutionConfig, - # ): - # r"""The vector jacobian product used in reverse-mode differentiation. ``LightningQubit`` uses the - # adjoint differentiation method to compute the VJP. - # Args: - # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits - # cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - # corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - # of numbers. - # execution_config (ExecutionConfig): a datastructure with all additional information required for execution - # Returns: - # tensor-like: A numeric result of computing the vector jacobian product - # **Definition of vjp:** - # If we have a function with jacobian: - # .. math:: - # \vec{y} = f(\vec{x}) \qquad J_{i,j} = \frac{\partial y_i}{\partial x_j} - # The vector jacobian product is the inner product of the derivatives of the output ``y`` with the - # Jacobian matrix. The derivatives of the output vector are sometimes called the **cotangents**. - # .. math:: - # \text{d}x_i = \Sigma_{i} \text{d}y_i J_{i,j} - # **Shape of cotangents:** - # The value provided to ``cotangents`` should match the output of :meth:`~.execute`. For computing the full Jacobian, - # the cotangents can be batched to vectorize the computation. In this case, the cotangents can have the following - # shapes. ``batch_size`` below refers to the number of entries in the Jacobian: - # * For a state measurement, the cotangents must have shape ``(batch_size, 2 ** n_wires)`` - # * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``. If ``n = 1``, - # then the shape must be ``(batch_size,)``. - # """ - # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - # return tuple( - # vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) - # for circuit, cots in zip(circuits, cotangents) - # ) - - # def execute_and_compute_vjp( - # self, - # circuits: QuantumTape_or_Batch, - # cotangents: Tuple[Number], - # execution_config: ExecutionConfig = DefaultExecutionConfig, - # ): - # """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. - # Args: - # circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed - # cotangents (Tuple[Number, Tuple[Number]]): Gradient-output vector. Must have shape matching the output shape of the - # corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable - # of numbers. - # execution_config (ExecutionConfig): a datastructure with all additional information required for execution - # Returns: - # Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product - # """ - # batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) - # results = tuple( - # simulate_and_vjp( - # circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map - # ) - # for circuit, cots in zip(circuits, cotangents) - # ) - # return tuple(zip(*results)) + From cca6de28499b2663d439a14428546083e9be7ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 28 Aug 2024 09:13:48 -0400 Subject: [PATCH 084/130] apply format --- .../core/_adjoint_jacobian_base.py | 2 +- .../core/_measurements_base.py | 8 ++--- .../core/_state_vector_base.py | 13 +++---- .../core/lightning_newAPI_base.py | 35 ++++++++----------- .../lightning_kokkos/_adjoint_jacobian.py | 8 ++--- .../lightning_kokkos/_measurements.py | 11 +++--- .../lightning_kokkos/_state_vector.py | 10 +++--- .../lightning_kokkos/lightning_kokkos.py | 35 +++++++++++-------- .../lightning_qubit/_adjoint_jacobian.py | 8 ++--- .../lightning_qubit/_measurements.py | 13 +++---- .../lightning_qubit/_state_vector.py | 8 ++--- .../lightning_qubit/lightning_qubit.py | 33 +++++++++-------- tests/lightning_qubit/test_jacobian_method.py | 8 +++-- tests/lightning_qubit/test_simulate_method.py | 2 +- 14 files changed, 95 insertions(+), 99 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 0624b5283..cf00b4f67 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -43,7 +43,7 @@ def __init__(self, qubit_state: Any, batch_obs: bool = False) -> None: self._dtype = qubit_state.dtype self._batch_obs = batch_obs - # Dummy for the C++ bindings + # Dummy for the C++ bindings self._jacobian_lightning = None self._create_ops_list_lightning = None diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index 3f22806dd..81e7ff87c 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -15,7 +15,7 @@ Class implementation for state vector measurements. """ -from typing import Callable, List, Union, Any +from typing import Any, Callable, List, Union import numpy as np import pennylane as qml @@ -54,8 +54,8 @@ def __init__( ) -> None: self._qubit_state = qubit_state self._dtype = qubit_state.dtype - - # Dummy for the C++ bindings + + # Dummy for the C++ bindings self._measurement_lightning = None @property @@ -67,7 +67,7 @@ def qubit_state(self): def dtype(self): """Returns the simulation data type.""" return self._dtype - + def _measurement_dtype(self): """Binding to Lightning Kokkos Measurements C++ class. diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index 67573b304..dfebf8fde 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -22,6 +22,7 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires + class LightningBaseStateVector: """Lightning state-vector class. @@ -44,7 +45,7 @@ def __init__(self, num_wires, dtype=np.complex128): # Dummy for the device name self._device_name = None - # Dummy for the C++ bindings + # Dummy for the C++ bindings self._qubit_state = None @property @@ -84,7 +85,7 @@ def state(self): [0.+0.j 1.+0.j] """ pass - + def _state_dtype(self): """Binding to Lightning Managed state vector C++ class. @@ -105,7 +106,7 @@ def _apply_state_vector(self, state, device_wires: Wires): device_wires (Wires): wires that get initialized in the state """ pass - + def _apply_basis_state(self, state, wires): """Initialize the state vector in a specified computational basis state. @@ -136,7 +137,7 @@ def _apply_lightning_controlled(self, operation): None """ pass - + def _apply_lightning_midmeasure( self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str ): @@ -152,8 +153,8 @@ def _apply_lightning_midmeasure( Returns: None """ - pass - + pass + def _apply_lightning( self, operations, mid_measurements: dict = None, postselect_mode: str = None ): diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 935a7f17f..4153c20fd 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -26,12 +26,12 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.typing import Result, ResultBatch - Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] + @simulator_tracking @single_tape_support class LightningBase(Device): @@ -70,7 +70,7 @@ def __init__( # pylint: disable=too-many-arguments batch_obs=False, ): super().__init__(wires=wires, shots=shots) - + self._c_dtype = c_dtype self._batch_obs = batch_obs @@ -79,35 +79,32 @@ def __init__( # pylint: disable=too-many-arguments else: self._wire_map = {w: i for i, w in enumerate(self.wires)} - # Dummy for LightningStateVector, LightningMeasurements, LightningAdjointJacobian + # Dummy for LightningStateVector, LightningMeasurements, LightningAdjointJacobian self.LightningStateVector = None self.LightningMeasurements = None self.LightningAdjointJacobian = None - - + @property def c_dtype(self): """State vector complex data type.""" return self._c_dtype - + dtype = c_dtype def _set_Lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" pass - - def simulate(self, + def simulate( + self, circuit: QuantumScript, # state: LightningStateVector, state, postselect_mode: str = None, ) -> Result: pass - - def jacobian(self, - circuit: QuantumTape, state, batch_obs=False, wire_map=None - ): + + def jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): """Compute the Jacobian for a single quantum script. Args: @@ -129,10 +126,7 @@ def jacobian(self, circuit ) - - def simulate_and_jacobian(self, - circuit: QuantumTape, state, batch_obs=False, wire_map=None - ): + def simulate_and_jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): """Simulate a single quantum script and compute its Jacobian. Args: @@ -154,8 +148,8 @@ def simulate_and_jacobian(self, jac = self.LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) return res, jac - - def vjp(self, + def vjp( + self, circuit: QuantumTape, cotangents: Tuple[Number], state, @@ -186,8 +180,8 @@ def vjp(self, circuit, cotangents ) - - def simulate_and_vjp(self, + def simulate_and_vjp( + self, circuit: QuantumTape, cotangents: Tuple[Number], state, @@ -340,4 +334,3 @@ def execute_and_compute_vjp( for circuit, cots in zip(circuits, cotangents) ) return tuple(zip(*results)) - diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index acca24557..bf9970dee 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -27,19 +27,19 @@ import numpy as np +from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian + from ._state_vector import LightningKokkosStateVector -from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): - + def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) - + # Initialize the C++ binds self._jacobian_lightning, self._create_ops_list_lightning = self._adjoint_jacobian_dtype() - def _adjoint_jacobian_dtype(self): """Binding to Lightning Kokkos Adjoint Jacobian C++ class. diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index de83455e5..efe514027 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -25,12 +25,9 @@ import numpy as np import pennylane as qml -from pennylane.measurements import ( - CountsMP, - SampleMeasurement, - Shots, -) +from pennylane.measurements import CountsMP, SampleMeasurement, Shots from pennylane.typing import TensorLike + from pennylane_lightning.core._measurements_base import LightningBaseMeasurements @@ -47,9 +44,9 @@ def __init__( self, kokkos_state, ) -> None: - + super().__init__(kokkos_state) - + self._measurement_lightning = self._measurement_dtype()(kokkos_state.state_vector) def _measurement_dtype(self): diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 3dc87b74f..1f9f28b04 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -36,9 +36,10 @@ from pennylane.wires import Wires from pennylane_lightning.core._serialize import global_phase_diagonal +from pennylane_lightning.core._state_vector_base import LightningBaseStateVector from ._measurements import LightningKokkosMeasurements -from pennylane_lightning.core._state_vector_base import LightningBaseStateVector + class LightningKokkosStateVector(LightningBaseStateVector): """Lightning Kokkos state-vector class. @@ -63,7 +64,7 @@ def __init__( device_name="lightning.kokkos", kokkos_args=None, sync=True, - ): + ): if device_name != "lightning.kokkos": raise DeviceError(f'The device name "{device_name}" is not a valid option.') @@ -75,7 +76,7 @@ def __init__( self._kokkos_config = {} self._sync = sync - # Initialize the state vector + # Initialize the state vector if kokkos_args is None: self._qubit_state = self._state_dtype()(self.num_wires) elif isinstance(kokkos_args, InitializationSettings): @@ -88,7 +89,6 @@ def __init__( if not self._kokkos_config: self._kokkos_config = self._kokkos_configuration() - @property def state(self): """Copy the state vector data from the device to the host. @@ -162,7 +162,6 @@ def _kokkos_configuration(self): """ return print_configuration() - def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: @@ -291,4 +290,3 @@ def _apply_lightning( except AttributeError: # pragma: no cover # To support older versions of PL method(operation.matrix, wires, False) - diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index c765f665d..0f59104ec 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -42,18 +42,17 @@ from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -from ._adjoint_jacobian import LightningKokkosAdjointJacobian -from ._measurements import LightningKokkosMeasurements -from ._state_vector import LightningKokkosStateVector - from pennylane_lightning.core.lightning_newAPI_base import ( LightningBase, - Result_or_ResultBatch, - QuantumTapeBatch, - QuantumTape_or_Batch, PostprocessingFn, + QuantumTape_or_Batch, + QuantumTapeBatch, + Result_or_ResultBatch, ) +from ._adjoint_jacobian import LightningKokkosAdjointJacobian +from ._measurements import LightningKokkosMeasurements +from ._state_vector import LightningKokkosStateVector try: from pennylane_lightning.lightning_kokkos_ops import backend_info, print_configuration @@ -249,14 +248,15 @@ def _add_adjoint_transforms(program: TransformProgram) -> None: program.add_transform(qml.transforms.broadcast_expand) program.add_transform(validate_adjoint_trainable_params) + # Kokkos specific methods def _kokkos_configuration(): return print_configuration() + @simulator_tracking @single_tape_support class LightningKokkos(LightningBase): - """PennyLane Lightning Kokkos device. A device that interfaces with C++ to perform fast linear algebra calculations. @@ -277,7 +277,6 @@ class LightningKokkos(LightningBase): (threading parameters). """ - # General device options _device_options = ("c_dtype", "batch_obs") @@ -314,9 +313,15 @@ def __init__( # pylint: disable=too-many-arguments "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) - super().__init__(device_name='lightning.kokkos' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) - - # Set the attributes to call the Lightning classes + super().__init__( + device_name="lightning.kokkos", + wires=wires, + c_dtype=c_dtype, + shots=shots, + batch_obs=batch_obs, + ) + + # Set the attributes to call the Lightning classes self._set_Lightning_classes() # Kokkos specific options @@ -461,7 +466,8 @@ def supports_derivatives( return True return _supports_adjoint(circuit=circuit) - def simulate(self, + def simulate( + self, circuit: QuantumScript, state: LightningKokkosStateVector, postselect_mode: str = None, @@ -500,12 +506,11 @@ def simulate(self, ) ) return tuple(results) - + state.reset_state() final_state = state.get_final_state(circuit) return LightningKokkosMeasurements(final_state).measure_final_state(circuit) - @staticmethod def get_c_interface(): """Returns a tuple consisting of the device name, and diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 2c0953783..528a33752 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -27,19 +27,19 @@ import numpy as np +from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian + from ._state_vector import LightningStateVector -from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian class LightningAdjointJacobian(LightningBaseAdjointJacobian): - + def __init__(self, qubit_state: LightningStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) - + # Initialize the C++ binds self._jacobian_lightning, self._create_ops_list_lightning = self._adjoint_jacobian_dtype() - def _adjoint_jacobian_dtype(self): """Binding to Lightning Qubit Adjoint Jacobian C++ class. diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 94ba554a7..f521956a1 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -21,17 +21,14 @@ except ImportError: pass -from typing import List from functools import reduce +from typing import List import numpy as np import pennylane as qml -from pennylane.measurements import ( - CountsMP, - SampleMeasurement, - Shots, -) +from pennylane.measurements import CountsMP, SampleMeasurement, Shots from pennylane.typing import TensorLike + from pennylane_lightning.core._measurements_base import LightningBaseMeasurements @@ -61,9 +58,9 @@ def __init__( kernel_name: str = None, num_burnin: int = None, ) -> None: - + super().__init__(qubit_state) - + self._mcmc = mcmc self._kernel_name = kernel_name self._num_burnin = num_burnin diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index ccb8a5b35..0034fb4d6 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -33,9 +33,11 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires -from ._measurements import LightningMeasurements from pennylane_lightning.core._state_vector_base import LightningBaseStateVector +from ._measurements import LightningMeasurements + + class LightningStateVector(LightningBaseStateVector): """Lightning Qubit state-vector class. @@ -56,10 +58,9 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit" self._device_name = device_name - # Initialize the state vector + # Initialize the state vector self._qubit_state = self._state_dtype()(self._num_wires) - @property def state(self): """Copy the state vector data to a numpy array. @@ -220,4 +221,3 @@ def _apply_lightning( except AttributeError: # pragma: no cover # To support older versions of PL method(operation.matrix, wires, False) - diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 3a72efb7d..4ec806511 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -41,18 +41,17 @@ from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -from ._adjoint_jacobian import LightningAdjointJacobian -from ._measurements import LightningMeasurements -from ._state_vector import LightningStateVector - from pennylane_lightning.core.lightning_newAPI_base import ( LightningBase, - Result_or_ResultBatch, - QuantumTapeBatch, - QuantumTape_or_Batch, PostprocessingFn, + QuantumTape_or_Batch, + QuantumTapeBatch, + Result_or_ResultBatch, ) +from ._adjoint_jacobian import LightningAdjointJacobian +from ._measurements import LightningMeasurements +from ._state_vector import LightningStateVector try: from pennylane_lightning.lightning_qubit_ops import backend_info @@ -317,7 +316,6 @@ class LightningQubit(LightningBase): qubit is built with OpenMP. """ - # General device options _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") # Device specific options @@ -354,9 +352,15 @@ def __init__( # pylint: disable=too-many-arguments "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) - super().__init__(device_name='lightning.qubit' ,wires=wires,c_dtype=c_dtype, shots=shots,batch_obs=batch_obs) - - # Set the attributes to call the Lightning classes + super().__init__( + device_name="lightning.qubit", + wires=wires, + c_dtype=c_dtype, + shots=shots, + batch_obs=batch_obs, + ) + + # Set the attributes to call the Lightning classes self._set_Lightning_classes() # Markov Chain Monte Carlo (MCMC) sampling method specific options @@ -518,7 +522,8 @@ def supports_derivatives( return True return _supports_adjoint(circuit=circuit) - def simulate(self, + def simulate( + self, circuit: QuantumScript, state: LightningStateVector, mcmc: dict = None, @@ -563,9 +568,7 @@ def simulate(self, ) ) return tuple(results) - + state.reset_state() final_state = state.get_final_state(circuit) return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) - - diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index b12ca047e..62a5ee384 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -31,6 +31,8 @@ if not LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + class TestJacobian: """Unit tests for the jacobian method with the new device API.""" @@ -86,7 +88,7 @@ def test_derivatives_single_expval( ) statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute(3,statevector, qs, execute_and_derivatives) + res, jac = self.process_and_execute(3, statevector, qs, execute_and_derivatives) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( @@ -164,8 +166,8 @@ def test_vjp_single_expval(self, theta, phi, obs, execute_and_derivatives, light dy = 1.0 statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute(3, - statevector, qs, dy, execute_and_derivatives=execute_and_derivatives + res, jac = self.process_and_execute( + 3, statevector, qs, dy, execute_and_derivatives=execute_and_derivatives ) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( diff --git a/tests/lightning_qubit/test_simulate_method.py b/tests/lightning_qubit/test_simulate_method.py index dd496169e..ff5536846 100644 --- a/tests/lightning_qubit/test_simulate_method.py +++ b/tests/lightning_qubit/test_simulate_method.py @@ -45,7 +45,7 @@ def calculate_reference(tape): tapes, transf_fn = program([tape]) results = dev.execute(tapes) return transf_fn(results) - + @staticmethod def calculate_result(wires, tape, statevector): dev = LightningDevice(wires) From 2f1d9d769c8193077f8feca5ae822084cdb3e161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 28 Aug 2024 10:37:16 -0400 Subject: [PATCH 085/130] Pass local pylint --- .../core/_adjoint_jacobian_base.py | 6 ++-- .../core/lightning_newAPI_base.py | 28 +++++++++++++------ .../lightning_kokkos/_adjoint_jacobian.py | 6 ++++ .../lightning_kokkos/_state_vector.py | 2 +- .../lightning_kokkos/lightning_kokkos.py | 9 ++---- .../lightning_qubit/_adjoint_jacobian.py | 6 ++++ .../lightning_qubit/lightning_qubit.py | 8 ++---- 7 files changed, 42 insertions(+), 23 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index cf00b4f67..92127a1b8 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -16,7 +16,7 @@ """ from os import getenv -from typing import Any, List +from typing import Any, Callable, List import numpy as np import pennylane as qml @@ -44,8 +44,8 @@ def __init__(self, qubit_state: Any, batch_obs: bool = False) -> None: self._batch_obs = batch_obs # Dummy for the C++ bindings - self._jacobian_lightning = None - self._create_ops_list_lightning = None + self._jacobian_lightning: Callable = None + self._create_ops_list_lightning: Callable = None @property def qubit_state(self): diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 4153c20fd..71a34a9de 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -62,7 +62,6 @@ class LightningBase(Device): def __init__( # pylint: disable=too-many-arguments self, - device_name, wires, *, c_dtype=np.complex128, @@ -80,9 +79,9 @@ def __init__( # pylint: disable=too-many-arguments self._wire_map = {w: i for i, w in enumerate(self.wires)} # Dummy for LightningStateVector, LightningMeasurements, LightningAdjointJacobian - self.LightningStateVector = None - self.LightningMeasurements = None - self.LightningAdjointJacobian = None + self.LightningStateVector: Callable = None + self.LightningMeasurements: Callable = None + self.LightningAdjointJacobian: Callable = None @property def c_dtype(self): @@ -98,10 +97,23 @@ def _set_Lightning_classes(self): def simulate( self, circuit: QuantumScript, - # state: LightningStateVector, - state, + state, # LightningStateVector postselect_mode: str = None, ) -> Result: + """Simulate a single quantum script. + + Args: + circuit (QuantumTape): The single circuit to simulate + state (Lightning[Device]StateVector): handle to Lightning state vector + postselect_mode (str): Configuration for handling shots with mid-circuit measurement + postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to + keep the same number of shots. Default is ``None``. + + Returns: + Tuple[TensorLike]: The results of the simulation + + Note that this function can return measurements for non-commuting observables simultaneously. + """ pass def jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): @@ -148,7 +160,7 @@ def simulate_and_jacobian(self, circuit: QuantumTape, state, batch_obs=False, wi jac = self.LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) return res, jac - def vjp( + def vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], @@ -180,7 +192,7 @@ def vjp( circuit, cotangents ) - def simulate_and_vjp( + def simulate_and_vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index bf9970dee..88c414c51 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -33,6 +33,12 @@ class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): + """Check and execute the adjoint Jacobian differentiation method. + + Args: + qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + batch_obs(bool): If serialized tape is to be batched or not. + """ def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 1f9f28b04..6852dc64a 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -57,7 +57,7 @@ class LightningKokkosStateVector(LightningBaseStateVector): """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, num_wires, dtype=np.complex128, diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 0f59104ec..acf1b0f31 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -19,7 +19,7 @@ import sys from dataclasses import replace from pathlib import Path -from typing import Optional, Sequence +from typing import Optional import numpy as np import pennylane as qml @@ -38,15 +38,13 @@ from pennylane.measurements import MidMeasureMP from pennylane.operation import DecompositionUndefinedError, Operator, Tensor from pennylane.ops import Prod, SProd, Sum -from pennylane.tape import QuantumScript, QuantumTape +from pennylane.tape import QuantumScript from pennylane.transforms.core import TransformProgram -from pennylane.typing import Result, ResultBatch +from pennylane.typing import Result from pennylane_lightning.core.lightning_newAPI_base import ( LightningBase, - PostprocessingFn, QuantumTape_or_Batch, - QuantumTapeBatch, Result_or_ResultBatch, ) @@ -314,7 +312,6 @@ def __init__( # pylint: disable=too-many-arguments ) super().__init__( - device_name="lightning.kokkos", wires=wires, c_dtype=c_dtype, shots=shots, diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 528a33752..7d3539a6f 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -33,6 +33,12 @@ class LightningAdjointJacobian(LightningBaseAdjointJacobian): + """Check and execute the adjoint Jacobian differentiation method. + + Args: + qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + batch_obs(bool): If serialized tape is to be batched or not. + """ def __init__(self, qubit_state: LightningStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 4ec806511..00569d9a6 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -37,15 +37,13 @@ from pennylane.measurements import MidMeasureMP from pennylane.operation import DecompositionUndefinedError, Operator, Tensor from pennylane.ops import Prod, SProd, Sum -from pennylane.tape import QuantumScript, QuantumTape +from pennylane.tape import QuantumScript from pennylane.transforms.core import TransformProgram -from pennylane.typing import Result, ResultBatch +from pennylane.typing import Result from pennylane_lightning.core.lightning_newAPI_base import ( LightningBase, - PostprocessingFn, QuantumTape_or_Batch, - QuantumTapeBatch, Result_or_ResultBatch, ) @@ -315,6 +313,7 @@ class LightningQubit(LightningBase): computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. """ + # pylint: disable=too-many-instance-attributes # General device options _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") @@ -353,7 +352,6 @@ def __init__( # pylint: disable=too-many-arguments ) super().__init__( - device_name="lightning.qubit", wires=wires, c_dtype=c_dtype, shots=shots, From bdbc180b15e6afb412f7b732631b03c36081cf3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 28 Aug 2024 10:38:20 -0400 Subject: [PATCH 086/130] Apply format --- pennylane_lightning/core/lightning_newAPI_base.py | 6 +++--- pennylane_lightning/lightning_kokkos/_state_vector.py | 2 +- pennylane_lightning/lightning_qubit/lightning_qubit.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 71a34a9de..37d545eef 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -97,7 +97,7 @@ def _set_Lightning_classes(self): def simulate( self, circuit: QuantumScript, - state, # LightningStateVector + state, # LightningStateVector postselect_mode: str = None, ) -> Result: """Simulate a single quantum script. @@ -160,7 +160,7 @@ def simulate_and_jacobian(self, circuit: QuantumTape, state, batch_obs=False, wi jac = self.LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) return res, jac - def vjp( # pylint: disable=too-many-arguments + def vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], @@ -192,7 +192,7 @@ def vjp( # pylint: disable=too-many-arguments circuit, cotangents ) - def simulate_and_vjp( # pylint: disable=too-many-arguments + def simulate_and_vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 6852dc64a..6e100982f 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -57,7 +57,7 @@ class LightningKokkosStateVector(LightningBaseStateVector): """ - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments self, num_wires, dtype=np.complex128, diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 00569d9a6..d6c4ffae8 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -313,6 +313,7 @@ class LightningQubit(LightningBase): computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. """ + # pylint: disable=too-many-instance-attributes # General device options From 73b5fcc4a9b205b4fb3eb18aec766f3db82a8c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 28 Aug 2024 11:17:24 -0400 Subject: [PATCH 087/130] update docstring --- pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 2 +- pennylane_lightning/lightning_qubit/_adjoint_jacobian.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 88c414c51..fbd5df998 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -36,7 +36,7 @@ class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): """Check and execute the adjoint Jacobian differentiation method. Args: - qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + qubit_state(LightningKokkosStateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 7d3539a6f..d94679e7e 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -36,7 +36,7 @@ class LightningAdjointJacobian(LightningBaseAdjointJacobian): """Check and execute the adjoint Jacobian differentiation method. Args: - qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + qubit_state(LightningStateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ From 73530645a14040f20a198e119202c0107776c6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 28 Aug 2024 16:53:39 -0400 Subject: [PATCH 088/130] Increase shots number for measurement --- 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 03730cbc7..340732142 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -409,7 +409,7 @@ def calculate_reference(tape, lightning_sv): return m.measure_final_state(tape) @flaky(max_runs=15) - @pytest.mark.parametrize("shots", [None, 100_000, [90_000, 90_000]]) + @pytest.mark.parametrize("shots", [None, 100_000, [190_000, 190_000]]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", From e967baf9bc0a276e3634e89dd3ee09dbc2b535b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 29 Aug 2024 22:30:35 -0400 Subject: [PATCH 089/130] add abstractmethod Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- pennylane_lightning/core/_adjoint_jacobian_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 92127a1b8..defa2b843 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -62,6 +62,7 @@ def dtype(self): """Returns the simulation data type.""" return self._dtype + @abstractmethod def _adjoint_jacobian_dtype(self): """Binding to Lightning [Device] Adjoint Jacobian C++ class. From d922d26a89b229728f27b97326a6aa0c55c23118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 29 Aug 2024 22:39:18 -0400 Subject: [PATCH 090/130] Update year of licence Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 37d545eef..52bae57ff 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.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 c415d260ebc0acad139d02e293d95e28f43f032c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 29 Aug 2024 22:40:24 -0400 Subject: [PATCH 091/130] Update lightning docstring with proper device names Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 52bae57ff..c3fe70f8d 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -13,7 +13,7 @@ # limitations under the License. r""" -This module contains the :class:`~.LightningKokkos` class, a PennyLane simulator device that +This module contains the :class:`~.LightningBase` class, that serves as a base class for Lightning simulator devices that interfaces with C++ for fast linear algebra calculations. """ from numbers import Number From 32dac957fe058a637da840bcb1404c802865f24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 29 Aug 2024 22:44:26 -0400 Subject: [PATCH 092/130] Apply suggestions from code review: update doctring with correct names Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- pennylane_lightning/core/lightning_newAPI_base.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index c3fe70f8d..6f63b89b4 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -35,12 +35,10 @@ @simulator_tracking @single_tape_support class LightningBase(Device): - """PennyLane Lightning Kokkos device. + """PennyLane Lightning Base device. - A device that interfaces with C++ to perform fast linear algebra calculations. + A class that serves as a base class for Lightning state-vector simulators. - Use of this device requires pre-built binaries or compilation from source. Check out the - :doc:`/lightning_kokkos/installation` guide for more details. Args: wires (int): the number of wires to initialize the device with @@ -51,8 +49,6 @@ class LightningBase(Device): the expectation values. Defaults to ``None`` if not specified. Setting to ``None`` results in computing statistics like expectation values and variances analytically. - kokkos_args (InitializationSettings): binding for Kokkos::InitializationSettings - (threading parameters). """ # pylint: disable=too-many-instance-attributes From cd26966a642473fd1b099680f8ce2fd0b141e897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 30 Aug 2024 14:22:22 -0400 Subject: [PATCH 093/130] correct docstring --- .../core/_adjoint_jacobian_base.py | 28 +++---- .../core/_measurements_base.py | 15 ++-- .../core/_state_vector_base.py | 21 +++-- .../core/lightning_newAPI_base.py | 78 +++++++++++-------- .../lightning_kokkos/_adjoint_jacobian.py | 4 +- .../lightning_qubit/_adjoint_jacobian.py | 4 +- 6 files changed, 88 insertions(+), 62 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index defa2b843..c673e9b44 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -15,6 +15,7 @@ Internal methods for adjoint Jacobian differentiation method. """ +from abc import ABC, abstractmethod from os import getenv from typing import Any, Callable, List @@ -29,18 +30,19 @@ from pennylane_lightning.core.lightning_base import _chunk_iterable -class LightningBaseAdjointJacobian: - """Check and execute the adjoint Jacobian differentiation method. +class LightningBaseAdjointJacobian(ABC): + """ Lightning [Device] Adjoint Jacobian class + + A class that serves as a base class for Lightning state-vector simulators. + Check and execute the adjoint Jacobian differentiation method. Args: qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ - def __init__(self, qubit_state: Any, batch_obs: bool = False) -> None: + def __init__(self, qubit_state: Any, batch_obs: bool) -> None: self._qubit_state = qubit_state - self._state = qubit_state.state_vector - self._dtype = qubit_state.dtype self._batch_obs = batch_obs # Dummy for the C++ bindings @@ -55,12 +57,12 @@ def qubit_state(self): @property def state(self): """Returns a handle to the Lightning internal data object.""" - return self._state + return self._qubit_state.state_vector @property def dtype(self): """Returns the simulation data type.""" - return self._dtype + return self._qubit_state.dtype @abstractmethod def _adjoint_jacobian_dtype(self): @@ -69,7 +71,7 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ pass - + @staticmethod def _get_return_type( measurements: List[MeasurementProcess], @@ -104,7 +106,7 @@ def _process_jacobian_tape( Returns: dictionary: dictionary providing serialized data for Jacobian calculation. """ - use_csingle = self._dtype == np.complex64 + use_csingle = self._qubit_state.dtype == np.complex64 obs_serialized, obs_idx_offsets = QuantumScriptSerializer( self._qubit_state.device_name, use_csingle, use_mpi, split_obs @@ -216,7 +218,7 @@ def calculate_jacobian(self, tape: QuantumTape): statevector = Lightning[Device]StateVector(num_wires=num_wires) statevector = statevector.get_final_state(tape) - jacobian = LightningKokkosAdjointJacobian(statevector).calculate_jacobian(tape) + jacobian = Lightning[Device]AdjointJacobian(statevector).calculate_jacobian(tape) Args: tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. @@ -228,12 +230,12 @@ def calculate_jacobian(self, tape: QuantumTape): empty_array = self._handle_raises(tape, is_jacobian=True) if empty_array: - return np.array([], dtype=self._dtype) + return np.array([], dtype=self._qubit_state.dtype) processed_data = self._process_jacobian_tape(tape) if not processed_data: # training_params is empty - return np.array([], dtype=self._dtype) + return np.array([], dtype=self._qubit_state.dtype) trainable_params = processed_data["tp_shift"] @@ -275,7 +277,7 @@ def calculate_vjp(self, tape: QuantumTape, grad_vec): statevector = Lightning[Device]StateVector(num_wires=num_wires) statevector = statevector.get_final_state(tape) - vjp = LightningKokkosAdjointJacobian(statevector).calculate_vjp(tape, grad_vec) + vjp = Lightning[Device]AdjointJacobian(statevector).calculate_vjp(tape, grad_vec) computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index 81e7ff87c..31c3bef32 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -15,6 +15,7 @@ Class implementation for state vector measurements. """ +from abc import ABC, abstractmethod from typing import Any, Callable, List, Union import numpy as np @@ -39,9 +40,10 @@ from pennylane_lightning.core._serialize import QuantumScriptSerializer -class LightningBaseMeasurements: +class LightningBaseMeasurements(ABC): """Lightning [Device] Measurements class + A class that serves as a base class for Lightning state-vector simulators. Measures the state provided by the Lightning[Device]StateVector class. Args: @@ -53,7 +55,6 @@ def __init__( qubit_state: Any, ) -> None: self._qubit_state = qubit_state - self._dtype = qubit_state.dtype # Dummy for the C++ bindings self._measurement_lightning = None @@ -66,10 +67,11 @@ def qubit_state(self): @property def dtype(self): """Returns the simulation data type.""" - return self._dtype + return self._qubit_state.dtype + @abstractmethod def _measurement_dtype(self): - """Binding to Lightning Kokkos Measurements C++ class. + """Binding to Lightning [Device] Measurements C++ class. Returns: the Measurements class """ @@ -227,7 +229,7 @@ def measure_final_state(self, circuit: QuantumScript, mid_measurements=None) -> """ 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.kokkos``. + This is an internal function that will be called by the successor to ``lightning.[device]``. Args: circuit (QuantumScript): The single circuit to simulate @@ -263,7 +265,7 @@ def measure_with_samples( self, measurements: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], shots: Shots, - mid_measurements=None, + mid_measurements: dict = None, ) -> List[TensorLike]: """ Returns the samples of the measurement process performed on the given state. @@ -337,6 +339,7 @@ def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool self._qubit_state.apply_operations(diagonalizing_gates) + @abstractmethod def _measure_with_samples_diagonalizing_gates( self, mps: List[SampleMeasurement], diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index dfebf8fde..5c9ed8173 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -15,6 +15,10 @@ Class implementation for state-vector manipulation. """ +from abc import ABC, abstractmethod +from typing import Union + + import numpy as np import pennylane as qml from pennylane import BasisState, StatePrep @@ -23,9 +27,10 @@ from pennylane.wires import Wires -class LightningBaseStateVector: - """Lightning state-vector class. +class LightningBaseStateVector(ABC): + """Lightning [Device] state-vector class. + A class that serves as a base class for Lightning state-vector simulators. Interfaces with C++ python binding methods for state-vector manipulation. Args: @@ -34,7 +39,7 @@ class LightningBaseStateVector: ``np.complex64`` or ``np.complex128``. Default is ``np.complex128`` """ - def __init__(self, num_wires, dtype=np.complex128): + def __init__(self, num_wires: int, dtype: Union[np.complex128,np.complex64]): if dtype not in [np.complex64, np.complex128]: raise TypeError(f"Unsupported complex type: {dtype}") @@ -74,18 +79,20 @@ def state_vector(self): return self._qubit_state @property + @abstractmethod def state(self): """Copy the state vector data to a numpy array. **Example** - >>> dev = qml.device('lightning.qubit', wires=1) + >>> dev = qml.device('lightning.[Device]', wires=1) >>> dev.apply([qml.PauliX(wires=[0])]) >>> print(dev.state) [0.+0.j 1.+0.j] """ pass + @abstractmethod def _state_dtype(self): """Binding to Lightning Managed state vector C++ class. @@ -98,6 +105,7 @@ def reset_state(self): # init the state vector to |00..0> self._qubit_state.resetStateVector() + @abstractmethod def _apply_state_vector(self, state, device_wires: Wires): """Initialize the internal state vector in a specified state. Args: @@ -127,6 +135,7 @@ def _apply_basis_state(self, state, wires): # Return a computational basis state over all wires. self._qubit_state.setBasisState(list(state), list(wires)) + @abstractmethod def _apply_lightning_controlled(self, operation): """Apply an arbitrary controlled operation to the state tensor. @@ -138,6 +147,7 @@ def _apply_lightning_controlled(self, operation): """ pass + @abstractmethod def _apply_lightning_midmeasure( self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str ): @@ -155,6 +165,7 @@ def _apply_lightning_midmeasure( """ pass + @abstractmethod def _apply_lightning( self, operations, mid_measurements: dict = None, postselect_mode: str = None ): @@ -197,7 +208,7 @@ def get_final_state( """ 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``. + This is an internal function that will be called by the successor to ``lightning.[Device]``. Args: circuit (QuantumScript): The single circuit to simulate diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 6f63b89b4..c19ae4a87 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -16,8 +16,9 @@ This module contains the :class:`~.LightningBase` class, that serves as a base class for Lightning simulator devices that interfaces with C++ for fast linear algebra calculations. """ +from abc import ABC, abstractmethod from numbers import Number -from typing import Callable, Optional, Sequence, Tuple, Union +from typing import Callable, Optional, Sequence, Tuple, Union, List import numpy as np import pennylane as qml @@ -39,13 +40,12 @@ class LightningBase(Device): A class that serves as a base class for Lightning state-vector simulators. - Args: - wires (int): the number of wires to initialize the device with + wires (int or list): number or list of wires to initialize the device with sync (bool): immediately sync with host-sv after applying operations 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 + shots (int or list): 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. @@ -58,11 +58,11 @@ class LightningBase(Device): def __init__( # pylint: disable=too-many-arguments self, - wires, + wires: Union[int, List], *, - c_dtype=np.complex128, - shots=None, - batch_obs=False, + c_dtype: Union[np.complex64, np.complex128], + shots: Union[int, List], + batch_obs: bool, ): super().__init__(wires=wires, shots=shots) @@ -86,14 +86,16 @@ def c_dtype(self): dtype = c_dtype + @abstractmethod def _set_Lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" pass + @abstractmethod def simulate( self, circuit: QuantumScript, - state, # LightningStateVector + state, # Lightning[Device]StateVector postselect_mode: str = None, ) -> Result: """Simulate a single quantum script. @@ -112,15 +114,20 @@ def simulate( """ pass - def jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): + def jacobian( + self, + circuit: QuantumTape, + state, # Lightning[Device]StateVector + batch_obs:bool=False, + wire_map:dict=None + ): """Compute the Jacobian for a single quantum script. Args: circuit (QuantumTape): The single circuit to simulate - state (LightningKokkosStateVector): handle to the Lightning state vector + state (Lightning[Device]StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. Default is False. + computing the jacobian. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -134,15 +141,20 @@ def jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): circuit ) - def simulate_and_jacobian(self, circuit: QuantumTape, state, batch_obs=False, wire_map=None): + def simulate_and_jacobian( + self, + circuit: QuantumTape, + state, # Lightning[Device]StateVector + batch_obs:bool=False, + wire_map:dict=None + )->Tuple: """Simulate a single quantum script and compute its Jacobian. Args: circuit (QuantumTape): The single circuit to simulate - state (LightningKokkosStateVector): handle to the Lightning state vector + state (Lightning[Device]StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. Default is False. + computing the jacobian. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -160,9 +172,9 @@ def vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], - state, - batch_obs=False, - wire_map=None, + state, # Lightning[Device]StateVector + batch_obs:bool=False, + wire_map:dict=None, ): """Compute the Vector-Jacobian Product (VJP) for a single quantum script. Args: @@ -171,10 +183,9 @@ def vjp( # pylint: disable=too-many-arguments have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (LightningKokkosStateVector): handle to the Lightning state vector + state (Lightning[Device]StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when - computing the VJP. This value is only relevant when the lightning - kokkos is built with OpenMP. + computing the VJP. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -193,9 +204,9 @@ def simulate_and_vjp( # pylint: disable=too-many-arguments circuit: QuantumTape, cotangents: Tuple[Number], state, - batch_obs=False, - wire_map=None, - ): + batch_obs:bool=False, + wire_map:dict=None, + ) -> Tuple: """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). Args: circuit (QuantumTape): The single circuit to simulate @@ -203,10 +214,9 @@ def simulate_and_vjp( # pylint: disable=too-many-arguments have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (LightningKokkosStateVector): handle to the Lightning state vector + state (Lightning[Device]StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when - computing the jacobian. This value is only relevant when the lightning - kokkos is built with OpenMP. + computing the jacobian. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -246,7 +256,7 @@ def execute_and_compute_derivatives( self, circuits: QuantumTape_or_Batch, execution_config: ExecutionConfig = DefaultExecutionConfig, - ): + )->Tuple: """Compute the results and jacobians of circuits at the same time. Args: @@ -254,7 +264,7 @@ def execute_and_compute_derivatives( execution_config (ExecutionConfig): a datastructure with all additional information required for execution Returns: - tuple: A numeric result of the computation and the gradient. + Tuple: A numeric result of the computation and the gradient. """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) results = tuple( @@ -271,7 +281,7 @@ def supports_vjp( circuit: Optional[QuantumTape] = None, ) -> bool: """Whether or not this device defines a custom vector jacobian product. - ``LightningKokkos`` supports adjoint differentiation with analytic results. + ``Lightning[Device]`` will check if supports adjoint differentiation with analytic results. Args: execution_config (ExecutionConfig): The configuration of the desired derivative calculation circuit (QuantumTape): An optional circuit to check derivatives support for. @@ -286,7 +296,7 @@ def compute_vjp( cotangents: Tuple[Number], execution_config: ExecutionConfig = DefaultExecutionConfig, ): - r"""The vector jacobian product used in reverse-mode differentiation. ``LightningKokkos`` uses the + r"""The vector jacobian product used in reverse-mode differentiation. ``Lightning[Device]`` uses the adjoint differentiation method to compute the VJP. Args: circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits @@ -323,7 +333,7 @@ def execute_and_compute_vjp( circuits: QuantumTape_or_Batch, cotangents: Tuple[Number], execution_config: ExecutionConfig = DefaultExecutionConfig, - ): + )->Tuple: """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. Args: circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index fbd5df998..9053dfd70 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -52,9 +52,9 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ jacobian_lightning = ( - AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() + AdjointJacobianC64() if self.dtype == np.complex64 else AdjointJacobianC128() ) create_ops_list_lightning = ( - create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 + create_ops_listC64 if self.dtype == np.complex64 else create_ops_listC128 ) return jacobian_lightning, create_ops_list_lightning diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index d94679e7e..72592b15f 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -52,9 +52,9 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ jacobian_lightning = ( - AdjointJacobianC64() if self._dtype == np.complex64 else AdjointJacobianC128() + AdjointJacobianC64() if self.dtype == np.complex64 else AdjointJacobianC128() ) create_ops_list_lightning = ( - create_ops_listC64 if self._dtype == np.complex64 else create_ops_listC128 + create_ops_listC64 if self.dtype == np.complex64 else create_ops_listC128 ) return jacobian_lightning, create_ops_list_lightning From 9c04fb062d6c60bfc7e64c8e72cd03eaff5ebf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 30 Aug 2024 14:33:43 -0400 Subject: [PATCH 094/130] apply format --- .../core/_adjoint_jacobian_base.py | 6 +-- .../core/_state_vector_base.py | 3 +- .../core/lightning_newAPI_base.py | 40 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index c673e9b44..d69b08c53 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -31,8 +31,8 @@ class LightningBaseAdjointJacobian(ABC): - """ Lightning [Device] Adjoint Jacobian class - + """Lightning [Device] Adjoint Jacobian class + A class that serves as a base class for Lightning state-vector simulators. Check and execute the adjoint Jacobian differentiation method. @@ -71,7 +71,7 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ pass - + @staticmethod def _get_return_type( measurements: List[MeasurementProcess], diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index 5c9ed8173..0b7a50164 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -18,7 +18,6 @@ from abc import ABC, abstractmethod from typing import Union - import numpy as np import pennylane as qml from pennylane import BasisState, StatePrep @@ -39,7 +38,7 @@ class LightningBaseStateVector(ABC): ``np.complex64`` or ``np.complex128``. Default is ``np.complex128`` """ - def __init__(self, num_wires: int, dtype: Union[np.complex128,np.complex64]): + def __init__(self, num_wires: int, dtype: Union[np.complex128, np.complex64]): if dtype not in [np.complex64, np.complex128]: raise TypeError(f"Unsupported complex type: {dtype}") diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index c19ae4a87..025d52442 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -18,7 +18,7 @@ """ from abc import ABC, abstractmethod from numbers import Number -from typing import Callable, Optional, Sequence, Tuple, Union, List +from typing import Callable, List, Optional, Sequence, Tuple, Union import numpy as np import pennylane as qml @@ -115,11 +115,11 @@ def simulate( pass def jacobian( - self, - circuit: QuantumTape, - state, # Lightning[Device]StateVector - batch_obs:bool=False, - wire_map:dict=None + self, + circuit: QuantumTape, + state, # Lightning[Device]StateVector + batch_obs: bool = False, + wire_map: dict = None, ): """Compute the Jacobian for a single quantum script. @@ -142,12 +142,12 @@ def jacobian( ) def simulate_and_jacobian( - self, - circuit: QuantumTape, - state, # Lightning[Device]StateVector - batch_obs:bool=False, - wire_map:dict=None - )->Tuple: + self, + circuit: QuantumTape, + state, # Lightning[Device]StateVector + batch_obs: bool = False, + wire_map: dict = None, + ) -> Tuple: """Simulate a single quantum script and compute its Jacobian. Args: @@ -172,9 +172,9 @@ def vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], - state, # Lightning[Device]StateVector - batch_obs:bool=False, - wire_map:dict=None, + state, # Lightning[Device]StateVector + batch_obs: bool = False, + wire_map: dict = None, ): """Compute the Vector-Jacobian Product (VJP) for a single quantum script. Args: @@ -185,7 +185,7 @@ def vjp( # pylint: disable=too-many-arguments not an iterable of numbers. state (Lightning[Device]StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when - computing the VJP. + computing the VJP. wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: @@ -204,8 +204,8 @@ def simulate_and_vjp( # pylint: disable=too-many-arguments circuit: QuantumTape, cotangents: Tuple[Number], state, - batch_obs:bool=False, - wire_map:dict=None, + batch_obs: bool = False, + wire_map: dict = None, ) -> Tuple: """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). Args: @@ -256,7 +256,7 @@ def execute_and_compute_derivatives( self, circuits: QuantumTape_or_Batch, execution_config: ExecutionConfig = DefaultExecutionConfig, - )->Tuple: + ) -> Tuple: """Compute the results and jacobians of circuits at the same time. Args: @@ -333,7 +333,7 @@ def execute_and_compute_vjp( circuits: QuantumTape_or_Batch, cotangents: Tuple[Number], execution_config: ExecutionConfig = DefaultExecutionConfig, - )->Tuple: + ) -> Tuple: """Calculate both the results and the vector jacobian product used in reverse-mode differentiation. Args: circuits (Union[QuantumTape, Sequence[QuantumTape]]): the circuit or batch of circuits to be executed From 4af7af910f912be80dd65ec2f929f24c282afa74 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 3 Sep 2024 12:52:13 +0000 Subject: [PATCH 095/130] Auto update version from '0.38.0-dev53' to '0.38.0-dev54' --- 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 d7b122382..409a481c2 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.38.0-dev53" +__version__ = "0.38.0-dev54" From b1703e1ed28824a9974a7a1d9af11dc33fefc7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 3 Sep 2024 09:09:51 -0400 Subject: [PATCH 096/130] trigger CIs From 47b48607d8a6f040a0e474e1a513e9aefd0807ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 3 Sep 2024 13:23:41 -0400 Subject: [PATCH 097/130] remove temporary change --- .github/workflows/tests_lkcpu_python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_lkcpu_python.yml b/.github/workflows/tests_lkcpu_python.yml index c2148be2d..5aa836dd2 100644 --- a/.github/workflows/tests_lkcpu_python.yml +++ b/.github/workflows/tests_lkcpu_python.yml @@ -34,7 +34,7 @@ on: env: TF_VERSION: 2.10.0 TORCH_VERSION: 1.11.0+cpu - COVERAGE_FLAGS: "--cov=pennylane_lightning --cov-report=term-missing -p no:warnings --tb=native" + COVERAGE_FLAGS: "--cov=pennylane_lightning --cov-report=term-missing --no-flaky-report -p no:warnings --tb=native" GCC_VERSION: 11 OMP_NUM_THREADS: "2" OMP_PROC_BIND: "false" From cf9302b9ce04a527a3b1c8bf1b3f1d6cbe5566cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Tue, 3 Sep 2024 13:25:11 -0400 Subject: [PATCH 098/130] Update .github/CHANGELOG.md Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 730fffe31..28db385fa 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -34,7 +34,7 @@ * Update python packaging to follow PEP 517/518/621/660 standards. [(#832)](https://github.com/PennyLaneAI/pennylane-lightning/pull/832) -* Lightning Kokkos migrated to new device API. +* Lightning-Kokkos migrated to the new device API. [(#810)](https://github.com/PennyLaneAI/pennylane-lightning/pull/810) ### Improvements From 0252f8a0e856d6364c2689565f9921d58eba66d1 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 4 Sep 2024 13:16:50 +0000 Subject: [PATCH 099/130] Auto update version from '0.39.0-dev0' to '0.39.0-dev1' --- 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 b4a6b1cfa..66700dfc1 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.39.0-dev0" +__version__ = "0.39.0-dev1" From 55612773820701afec77cc6bdec65248ba058ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 09:59:12 -0400 Subject: [PATCH 100/130] Increase the number of shots for measurenment test --- 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 03730cbc7..dc5531faa 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -409,7 +409,7 @@ def calculate_reference(tape, lightning_sv): return m.measure_final_state(tape) @flaky(max_runs=15) - @pytest.mark.parametrize("shots", [None, 100_000, [90_000, 90_000]]) + @pytest.mark.parametrize("shots", [None, 200_000, [190_000, 190_000]]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "observable", From 47d0af3bf7fd3a39b7ac7f6a9da82f6d9fff5059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 11:52:26 -0400 Subject: [PATCH 101/130] trigger CIs From 31ff5da22371bdebde81fafd75953e0a041cba2e Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 4 Sep 2024 17:58:35 +0000 Subject: [PATCH 102/130] Auto update version from '0.39.0-dev1' to '0.39.0-dev2' --- 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 66700dfc1..f1ff62163 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.39.0-dev1" +__version__ = "0.39.0-dev2" From c5a3e4c9ba401bec701a76ffa6797b8a477e1c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:13:46 -0400 Subject: [PATCH 103/130] remove pass from abstract methods --- pennylane_lightning/core/_adjoint_jacobian_base.py | 2 +- pennylane_lightning/core/_measurements_base.py | 2 -- pennylane_lightning/core/_state_vector_base.py | 6 ------ pennylane_lightning/core/lightning_newAPI_base.py | 2 -- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index d69b08c53..7a60194e9 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -70,7 +70,7 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ - pass + @staticmethod def _get_return_type( diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index 31c3bef32..fbd3d023d 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -75,7 +75,6 @@ def _measurement_dtype(self): Returns: the Measurements class """ - pass def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> TensorLike: """Apply a measurement to state when the measurement process has an observable with diagonalizing gates. @@ -357,7 +356,6 @@ def _measure_with_samples_diagonalizing_gates( Returns: TensorLike[Any]: Sample measurement results """ - pass def _measure_hamiltonian_with_samples( self, diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index 0b7a50164..cc37fb943 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -89,7 +89,6 @@ def state(self): >>> print(dev.state) [0.+0.j 1.+0.j] """ - pass @abstractmethod def _state_dtype(self): @@ -97,7 +96,6 @@ def _state_dtype(self): Returns: the state vector class """ - pass def reset_state(self): """Reset the device's state""" @@ -112,7 +110,6 @@ def _apply_state_vector(self, state, device_wires: Wires): or broadcasted state of shape ``(batch_size, 2**len(wires))`` device_wires (Wires): wires that get initialized in the state """ - pass def _apply_basis_state(self, state, wires): """Initialize the state vector in a specified computational basis state. @@ -144,7 +141,6 @@ def _apply_lightning_controlled(self, operation): Returns: None """ - pass @abstractmethod def _apply_lightning_midmeasure( @@ -162,7 +158,6 @@ def _apply_lightning_midmeasure( Returns: None """ - pass @abstractmethod def _apply_lightning( @@ -180,7 +175,6 @@ def _apply_lightning( Returns: None """ - pass def apply_operations( self, operations, mid_measurements: dict = None, postselect_mode: str = None diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 025d52442..875dbccfb 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -89,7 +89,6 @@ def c_dtype(self): @abstractmethod def _set_Lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" - pass @abstractmethod def simulate( @@ -112,7 +111,6 @@ def simulate( Note that this function can return measurements for non-commuting observables simultaneously. """ - pass def jacobian( self, From 465e0fe92f95f277a936578bfbd84c7d2c1c8a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:22:24 -0400 Subject: [PATCH 104/130] solve issues with pylint --- pennylane_lightning/core/_adjoint_jacobian_base.py | 1 - pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 2 +- pennylane_lightning/lightning_kokkos/_state_vector.py | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 7a60194e9..762432f64 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -70,7 +70,6 @@ def _adjoint_jacobian_dtype(self): Returns: the AdjointJacobian class """ - @staticmethod def _get_return_type( diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 9053dfd70..f3fb32a31 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -32,7 +32,7 @@ from ._state_vector import LightningKokkosStateVector -class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): +class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): # pylint: too-few-public-methods """Check and execute the adjoint Jacobian differentiation method. Args: diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 1f9f28b04..bd2f546b9 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -35,6 +35,7 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires +# pylint: ungrouped-imports from pennylane_lightning.core._serialize import global_phase_diagonal from pennylane_lightning.core._state_vector_base import LightningBaseStateVector @@ -64,7 +65,7 @@ def __init__( device_name="lightning.kokkos", kokkos_args=None, sync=True, - ): + ): # pylint: disable=too-many-arguments if device_name != "lightning.kokkos": raise DeviceError(f'The device name "{device_name}" is not a valid option.') From 2df631af11cb50bade34195b1ab418ff4cb04a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:29:34 -0400 Subject: [PATCH 105/130] solve issues with pylint --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 2 +- pennylane_lightning/lightning_kokkos/_state_vector.py | 2 +- pennylane_lightning/lightning_qubit/_state_vector.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 875dbccfb..6929c2688 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -35,7 +35,7 @@ @simulator_tracking @single_tape_support -class LightningBase(Device): +class LightningBase(ABC, Device): """PennyLane Lightning Base device. A class that serves as a base class for Lightning state-vector simulators. diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index f3fb32a31..9650472ba 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -32,7 +32,7 @@ from ._state_vector import LightningKokkosStateVector -class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): # pylint: too-few-public-methods +class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): # pylint: disable=too-few-public-methods """Check and execute the adjoint Jacobian differentiation method. Args: diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index bd2f546b9..fb1c0b901 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -35,7 +35,7 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires -# pylint: ungrouped-imports +# pylint: disable=ungrouped-imports from pennylane_lightning.core._serialize import global_phase_diagonal from pennylane_lightning.core._state_vector_base import LightningBaseStateVector diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 0034fb4d6..a250d7927 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -38,7 +38,7 @@ from ._measurements import LightningMeasurements -class LightningStateVector(LightningBaseStateVector): +class LightningStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods """Lightning Qubit state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. From 2730a01c6c7dca0ea3f44aef5a5a20551a1df159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:36:40 -0400 Subject: [PATCH 106/130] solve issues with pylint --- pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 5 ++++- pennylane_lightning/lightning_kokkos/_state_vector.py | 2 +- pennylane_lightning/lightning_qubit/_adjoint_jacobian.py | 1 + pennylane_lightning/lightning_qubit/_state_vector.py | 8 ++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 9650472ba..588593a89 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -27,12 +27,15 @@ import numpy as np +# pylint: disable=ungrouped-imports from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian from ._state_vector import LightningKokkosStateVector -class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): # pylint: disable=too-few-public-methods +class LightningKokkosAdjointJacobian( + LightningBaseAdjointJacobian +): # pylint: disable=too-few-public-methods """Check and execute the adjoint Jacobian differentiation method. Args: diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index fb1c0b901..02db7ad01 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -65,7 +65,7 @@ def __init__( device_name="lightning.kokkos", kokkos_args=None, sync=True, - ): # pylint: disable=too-many-arguments + ): # pylint: disable=too-many-arguments if device_name != "lightning.kokkos": raise DeviceError(f'The device name "{device_name}" is not a valid option.') diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 72592b15f..94c63a856 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -27,6 +27,7 @@ import numpy as np +# pylint: disable=ungrouped-imports from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian from ._state_vector import LightningStateVector diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index a250d7927..7dab42def 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -33,12 +33,12 @@ from pennylane.tape import QuantumScript from pennylane.wires import Wires +# pylint: disable=ungrouped-imports from pennylane_lightning.core._state_vector_base import LightningBaseStateVector from ._measurements import LightningMeasurements - -class LightningStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods +class LightningStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods """Lightning Qubit state-vector class. Interfaces with C++ python binding methods for state-vector manipulation. @@ -203,9 +203,9 @@ def _apply_lightning( ) elif isinstance(operation, qml.PauliRot): method = getattr(state, "applyPauliRot") - paulis = operation._hyperparameters["pauli_word"] + paulis = operation._hyperparameters["pauli_word"] # pylint: disable=protected-access wires = [i for i, w in zip(wires, paulis) if w != "I"] - word = "".join(p for p in paulis if p != "I") # pylint: disable=protected-access + word = "".join(p for p in paulis if p != "I") method(wires, invert_param, operation.parameters, word) elif method is not None: # apply specialized gate param = operation.parameters From cac4474dc18b45026ca127edd8778187f052478c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:42:06 -0400 Subject: [PATCH 107/130] solve issues with pylint --- pennylane_lightning/core/_state_vector_base.py | 1 - pennylane_lightning/lightning_kokkos/_measurements.py | 4 +++- pennylane_lightning/lightning_qubit/_adjoint_jacobian.py | 4 +++- pennylane_lightning/lightning_qubit/_measurements.py | 2 +- pennylane_lightning/lightning_qubit/_state_vector.py | 7 +++++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index cc37fb943..28736b6c1 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -19,7 +19,6 @@ from typing import Union import numpy as np -import pennylane as qml from pennylane import BasisState, StatePrep from pennylane.measurements import MidMeasureMP from pennylane.tape import QuantumScript diff --git a/pennylane_lightning/lightning_kokkos/_measurements.py b/pennylane_lightning/lightning_kokkos/_measurements.py index efe514027..6e706614b 100644 --- a/pennylane_lightning/lightning_kokkos/_measurements.py +++ b/pennylane_lightning/lightning_kokkos/_measurements.py @@ -31,7 +31,9 @@ from pennylane_lightning.core._measurements_base import LightningBaseMeasurements -class LightningKokkosMeasurements(LightningBaseMeasurements): +class LightningKokkosMeasurements( + LightningBaseMeasurements +): # pylint: disable=too-few-public-methods """Lightning Kokkos Measurements class Measures the state provided by the LightningKokkosStateVector class. diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index 94c63a856..ab3bab1d4 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -33,7 +33,9 @@ from ._state_vector import LightningStateVector -class LightningAdjointJacobian(LightningBaseAdjointJacobian): +class LightningAdjointJacobian( + LightningBaseAdjointJacobian +): # pylint: disable=too-few-public-methods """Check and execute the adjoint Jacobian differentiation method. Args: diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index f521956a1..f762fcb7e 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -32,7 +32,7 @@ from pennylane_lightning.core._measurements_base import LightningBaseMeasurements -class LightningMeasurements(LightningBaseMeasurements): +class LightningMeasurements(LightningBaseMeasurements): # pylint: disable=too-few-public-methods """Lightning Qubit Measurements class Measures the state provided by the LightningStateVector class. diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 7dab42def..3cfead1d8 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -38,6 +38,7 @@ from ._measurements import LightningMeasurements + class LightningStateVector(LightningBaseStateVector): # pylint: disable=too-few-public-methods """Lightning Qubit state-vector class. @@ -203,9 +204,11 @@ def _apply_lightning( ) elif isinstance(operation, qml.PauliRot): method = getattr(state, "applyPauliRot") - paulis = operation._hyperparameters["pauli_word"] # pylint: disable=protected-access + paulis = operation._hyperparameters[ + "pauli_word" + ] # pylint: disable=protected-access wires = [i for i, w in zip(wires, paulis) if w != "I"] - word = "".join(p for p in paulis if p != "I") + word = "".join(p for p in paulis if p != "I") method(wires, invert_param, operation.parameters, word) elif method is not None: # apply specialized gate param = operation.parameters From 36e064cdad25fcaef6705b1d47ddad3621262505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:44:30 -0400 Subject: [PATCH 108/130] solve issues with pylint --- 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 3cfead1d8..4e47b2fb6 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -204,9 +204,9 @@ def _apply_lightning( ) elif isinstance(operation, qml.PauliRot): method = getattr(state, "applyPauliRot") - paulis = operation._hyperparameters[ + paulis = operation._hyperparameters[ # pylint: disable=protected-access "pauli_word" - ] # pylint: disable=protected-access + ] wires = [i for i, w in zip(wires, paulis) if w != "I"] word = "".join(p for p in paulis if p != "I") method(wires, invert_param, operation.parameters, word) From 6007e80a418634d65ad23e8c06f79b58eda02541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 14:49:02 -0400 Subject: [PATCH 109/130] fix bug of inheritance order --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 6929c2688..56e0dcca6 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -35,7 +35,7 @@ @simulator_tracking @single_tape_support -class LightningBase(ABC, Device): +class LightningBase(Device, ABC): """PennyLane Lightning Base device. A class that serves as a base class for Lightning state-vector simulators. From 96e0a844bab70648a2ca7e974cf111dd21696a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Wed, 4 Sep 2024 15:11:59 -0400 Subject: [PATCH 110/130] remove double parent class --- pennylane_lightning/core/lightning_newAPI_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 56e0dcca6..f0236ecc6 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -16,7 +16,7 @@ This module contains the :class:`~.LightningBase` class, that serves as a base class for Lightning simulator devices that interfaces with C++ for fast linear algebra calculations. """ -from abc import ABC, abstractmethod +from abc import abstractmethod from numbers import Number from typing import Callable, List, Optional, Sequence, Tuple, Union @@ -35,7 +35,7 @@ @simulator_tracking @single_tape_support -class LightningBase(Device, ABC): +class LightningBase(Device): """PennyLane Lightning Base device. A class that serves as a base class for Lightning state-vector simulators. From d34b7e9cba9a61bff4bece0650f6c87cee166fc0 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Wed, 4 Sep 2024 21:16:21 +0000 Subject: [PATCH 111/130] Auto update version from '0.39.0-dev2' to '0.39.0-dev3' --- 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 f1ff62163..6eae0c1c8 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.39.0-dev2" +__version__ = "0.39.0-dev3" From dfd8d1d169cf9052b733df9714207f10c0ecd491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 5 Sep 2024 15:01:29 -0400 Subject: [PATCH 112/130] solve conflict --- .../core/_adjoint_jacobian_base.py | 48 +------------- .../lightning_kokkos/_adjoint_jacobian.py | 43 ++++++++++++- .../lightning_qubit/_adjoint_jacobian.py | 63 +++++++++++++++++++ 3 files changed, 108 insertions(+), 46 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 762432f64..2d75248c7 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -27,7 +27,6 @@ from pennylane.tape import QuantumTape from pennylane_lightning.core._serialize import QuantumScriptSerializer -from pennylane_lightning.core.lightning_base import _chunk_iterable class LightningBaseAdjointJacobian(ABC): @@ -107,7 +106,7 @@ def _process_jacobian_tape( """ use_csingle = self._qubit_state.dtype == np.complex64 - obs_serialized, obs_idx_offsets = QuantumScriptSerializer( + obs_serialized, obs_indices = QuantumScriptSerializer( self._qubit_state.device_name, use_csingle, use_mpi, split_obs ).serialize_observables(tape) @@ -150,7 +149,7 @@ def _process_jacobian_tape( "tp_shift": tp_shift, "record_tp_rows": record_tp_rows, "all_params": all_params, - "obs_idx_offsets": obs_idx_offsets, + "obs_indices": obs_indices, } @staticmethod @@ -210,6 +209,7 @@ def _handle_raises(self, tape: QuantumTape, is_jacobian: bool, grad_vec=None): return False + @abstractmethod def calculate_jacobian(self, tape: QuantumTape): """Computes the Jacobian with the adjoint method. @@ -226,48 +226,6 @@ def calculate_jacobian(self, tape: QuantumTape): The Jacobian of a tape. """ - empty_array = self._handle_raises(tape, is_jacobian=True) - - if empty_array: - return np.array([], dtype=self._qubit_state.dtype) - - processed_data = self._process_jacobian_tape(tape) - - if not processed_data: # training_params is empty - return np.array([], dtype=self._qubit_state.dtype) - - trainable_params = processed_data["tp_shift"] - - # If requested batching over observables, chunk into OMP_NUM_THREADS sized chunks. - # This will allow use of Lightning with adjoint for large-qubit numbers AND large - # numbers of observables, enabling choice between compute time and memory use. - requested_threads = int(getenv("OMP_NUM_THREADS", "1")) - - if self._batch_obs and requested_threads > 1: - obs_partitions = _chunk_iterable(processed_data["obs_serialized"], requested_threads) - jac = [] - for obs_chunk in obs_partitions: - jac_local = self._jacobian_lightning( - processed_data["state_vector"], - obs_chunk, - processed_data["ops_serialized"], - trainable_params, - ) - jac.extend(jac_local) - else: - jac = self._jacobian_lightning( - processed_data["state_vector"], - processed_data["obs_serialized"], - processed_data["ops_serialized"], - trainable_params, - ) - jac = np.array(jac) - jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac - jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) - jac_r[:, processed_data["record_tp_rows"]] = jac - - return self._adjoint_jacobian_processing(jac_r) - # pylint: disable=inconsistent-return-statements def calculate_vjp(self, tape: QuantumTape, grad_vec): """Compute the vector-Jacobian products of a tape. diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 588593a89..9ad1d0a34 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -26,6 +26,7 @@ pass import numpy as np +from pennylane.tape import QuantumTape # pylint: disable=ungrouped-imports from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian @@ -35,7 +36,7 @@ class LightningKokkosAdjointJacobian( LightningBaseAdjointJacobian -): # pylint: disable=too-few-public-methods +): """Check and execute the adjoint Jacobian differentiation method. Args: @@ -61,3 +62,43 @@ def _adjoint_jacobian_dtype(self): create_ops_listC64 if self.dtype == np.complex64 else create_ops_listC128 ) return jacobian_lightning, create_ops_list_lightning + + def calculate_jacobian(self, tape: QuantumTape): + """Computes the Jacobian with the adjoint method. + + .. code-block:: python + + statevector = LightningKokkosStateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + jacobian = LightningKokkosAdjointJacobian(statevector).calculate_jacobian(tape) + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + + Returns: + The Jacobian of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=True) + + if empty_array: + return np.array([], dtype=self.dtype) + + processed_data = self._process_jacobian_tape(tape) + + if not processed_data: # training_params is empty + return np.array([], dtype=self.dtype) + + trainable_params = processed_data["tp_shift"] + jac = self._jacobian_lightning( + processed_data["state_vector"], + processed_data["obs_serialized"], + processed_data["ops_serialized"], + trainable_params, + ) + jac = np.array(jac) + jac = jac.reshape(-1, len(trainable_params)) if len(jac) else jac + jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) + jac_r[:, processed_data["record_tp_rows"]] = jac + + return self._adjoint_jacobian_processing(jac_r) diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index ab3bab1d4..ef83c00dd 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -25,7 +25,11 @@ except ImportError: pass +from os import getenv + import numpy as np +from scipy.sparse import csr_matrix +from pennylane.tape import QuantumTape # pylint: disable=ungrouped-imports from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian @@ -61,3 +65,62 @@ def _adjoint_jacobian_dtype(self): create_ops_listC64 if self.dtype == np.complex64 else create_ops_listC128 ) return jacobian_lightning, create_ops_list_lightning + + def calculate_jacobian(self, tape: QuantumTape): + """Computes the Jacobian with the adjoint method. + + .. code-block:: python + + statevector = LightningStateVector(num_wires=num_wires) + statevector = statevector.get_final_state(tape) + jacobian = LightningAdjointJacobian(statevector).calculate_jacobian(tape) + + Args: + tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. + + Returns: + The Jacobian of a tape. + """ + + empty_array = self._handle_raises(tape, is_jacobian=True) + + if empty_array: + return np.array([], dtype=self.dtype) + + split_obs = True if len(tape.measurements) > 1 else False + # lightning already parallelizes applying a single Hamiltonian + + if split_obs: + # split linear combinations into num_threads + # this isn't the best load-balance in general, but well-rounded enough + split_obs = getenv("OMP_NUM_THREADS", None) if self._batch_obs else False + split_obs = int(split_obs) if split_obs else False + processed_data = self._process_jacobian_tape(tape, split_obs=split_obs) + + if not processed_data: # training_params is empty + return np.array([], dtype=self.dtype) + + trainable_params = processed_data["tp_shift"] + + jac = self._jacobian_lightning( + processed_data["state_vector"], + processed_data["obs_serialized"], + processed_data["ops_serialized"], + trainable_params, + ) + + jac = np.array(jac) + has_shape0 = True if len(jac) > 0 else False + + num_obs = len(np.unique(processed_data["obs_indices"])) + + rows = processed_data["obs_indices"] + cols = np.arange(len(rows), dtype=int) + data = np.ones(len(rows)) + red_mat = csr_matrix((data, (rows, cols)), shape=(num_obs, len(rows))) + + jac = red_mat @ jac.reshape((len(rows), -1)) + jac = jac.reshape(-1, len(trainable_params)) if has_shape0 else jac + jac_r = np.zeros((jac.shape[0], processed_data["all_params"])) + jac_r[:, processed_data["record_tp_rows"]] = jac + return self._adjoint_jacobian_processing(jac_r) From 634ea1dac889053413e9a7c65a6061e3c6217060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 5 Sep 2024 15:02:52 -0400 Subject: [PATCH 113/130] apply format --- .../lightning_kokkos/_adjoint_jacobian.py | 4 +--- .../lightning_qubit/_adjoint_jacobian.py | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 9ad1d0a34..8aef7fb82 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -34,9 +34,7 @@ from ._state_vector import LightningKokkosStateVector -class LightningKokkosAdjointJacobian( - LightningBaseAdjointJacobian -): +class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): """Check and execute the adjoint Jacobian differentiation method. Args: diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index ef83c00dd..e310d17f9 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -28,8 +28,8 @@ from os import getenv import numpy as np -from scipy.sparse import csr_matrix from pennylane.tape import QuantumTape +from scipy.sparse import csr_matrix # pylint: disable=ungrouped-imports from pennylane_lightning.core._adjoint_jacobian_base import LightningBaseAdjointJacobian @@ -65,7 +65,7 @@ def _adjoint_jacobian_dtype(self): create_ops_listC64 if self.dtype == np.complex64 else create_ops_listC128 ) return jacobian_lightning, create_ops_list_lightning - + def calculate_jacobian(self, tape: QuantumTape): """Computes the Jacobian with the adjoint method. @@ -87,9 +87,9 @@ def calculate_jacobian(self, tape: QuantumTape): if empty_array: return np.array([], dtype=self.dtype) - split_obs = True if len(tape.measurements) > 1 else False + split_obs = True if len(tape.measurements) > 1 else False # lightning already parallelizes applying a single Hamiltonian - + if split_obs: # split linear combinations into num_threads # this isn't the best load-balance in general, but well-rounded enough @@ -108,7 +108,7 @@ def calculate_jacobian(self, tape: QuantumTape): processed_data["ops_serialized"], trainable_params, ) - + jac = np.array(jac) has_shape0 = True if len(jac) > 0 else False From fe4c8d911b39ac42f26fb91483fc1f74241d5be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 5 Sep 2024 15:11:18 -0400 Subject: [PATCH 114/130] solve issues with pylint --- pennylane_lightning/core/_adjoint_jacobian_base.py | 1 - pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 1 + pennylane_lightning/lightning_qubit/_adjoint_jacobian.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 2d75248c7..18a7b87c1 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -16,7 +16,6 @@ """ from abc import ABC, abstractmethod -from os import getenv from typing import Any, Callable, List import numpy as np diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 8aef7fb82..960e3dde8 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -41,6 +41,7 @@ class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): qubit_state(LightningKokkosStateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ + # pylint: disable=too-few-public-methods def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: super().__init__(qubit_state, batch_obs) diff --git a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py index e310d17f9..0abc7f72f 100644 --- a/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_qubit/_adjoint_jacobian.py @@ -87,7 +87,7 @@ def calculate_jacobian(self, tape: QuantumTape): if empty_array: return np.array([], dtype=self.dtype) - split_obs = True if len(tape.measurements) > 1 else False + split_obs: bool = len(tape.measurements) > 1 # lightning already parallelizes applying a single Hamiltonian if split_obs: @@ -110,7 +110,7 @@ def calculate_jacobian(self, tape: QuantumTape): ) jac = np.array(jac) - has_shape0 = True if len(jac) > 0 else False + has_shape0: bool = len(jac) > 0 num_obs = len(np.unique(processed_data["obs_indices"])) From 28fc8cbbb4aba9302ea8df64781c9d8bb621c4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Thu, 5 Sep 2024 15:18:23 -0400 Subject: [PATCH 115/130] apply format --- pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py index 960e3dde8..4338a5b87 100644 --- a/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py +++ b/pennylane_lightning/lightning_kokkos/_adjoint_jacobian.py @@ -41,6 +41,7 @@ class LightningKokkosAdjointJacobian(LightningBaseAdjointJacobian): qubit_state(LightningKokkosStateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ + # pylint: disable=too-few-public-methods def __init__(self, qubit_state: LightningKokkosStateVector, batch_obs: bool = False) -> None: From 1927480923fc150a0cb54ea73aac833ea2ac19e3 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 5 Sep 2024 19:36:04 +0000 Subject: [PATCH 116/130] Auto update version from '0.39.0-dev4' to '0.39.0-dev5' --- 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 c4edd4139..8d7e4ed0a 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.39.0-dev4" +__version__ = "0.39.0-dev5" From 20a6920eb3f248be26d633868b6c437502107115 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 5 Sep 2024 22:19:19 +0000 Subject: [PATCH 117/130] Auto update version from '0.39.0-dev5' to '0.39.0-dev6' --- 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 8d7e4ed0a..d9ab971e7 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.39.0-dev5" +__version__ = "0.39.0-dev6" From d9488d0a4ca56770de607769655bb8927502ebcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 12:18:39 -0400 Subject: [PATCH 118/130] Apply suggestions from Vincent's code review Co-authored-by: Vincent Michaud-Rioux --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index acf1b0f31..4c98e73f8 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -315,11 +315,11 @@ def __init__( # pylint: disable=too-many-arguments wires=wires, c_dtype=c_dtype, shots=shots, - batch_obs=batch_obs, + batch_obs=False, ) # Set the attributes to call the Lightning classes - self._set_Lightning_classes() + self._set_lightning_classes() # Kokkos specific options self._kokkos_args = kokkos_args From 442fed501054be4145547fb95143c4bcf1e522ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 13:31:46 -0400 Subject: [PATCH 119/130] apply review comments --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- .../lightning_kokkos/_state_vector.py | 6 +----- .../lightning_kokkos/lightning_kokkos.py | 7 +------ .../lightning_qubit/_state_vector.py | 9 +++------ .../lightning_qubit/lightning_qubit.py | 9 ++------- tests/lightning_qubit/test_jacobian_method.py | 11 ++++++----- tests/lightning_qubit/test_state_vector_class.py | 14 +++----------- 7 files changed, 17 insertions(+), 41 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index f0236ecc6..06df42d44 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -87,7 +87,7 @@ def c_dtype(self): dtype = c_dtype @abstractmethod - def _set_Lightning_classes(self): + def _set_lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" @abstractmethod diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 02db7ad01..5ba5343f5 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -62,17 +62,13 @@ def __init__( self, num_wires, dtype=np.complex128, - device_name="lightning.kokkos", kokkos_args=None, sync=True, ): # pylint: disable=too-many-arguments - if device_name != "lightning.kokkos": - raise DeviceError(f'The device name "{device_name}" is not a valid option.') - super().__init__(num_wires, dtype) - self._device_name = device_name + self._device_name = "lightning.kokkos" self._kokkos_config = {} self._sync = sync diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 4c98e73f8..8d7f14ee8 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -60,11 +60,6 @@ LK_CPP_BINARY_AVAILABLE = False backend_info = None -# Result_or_ResultBatch = Union[Result, ResultBatch] -# QuantumTapeBatch = Sequence[QuantumTape] -# QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] -# PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] - # The set of supported operations. _operations = frozenset( { @@ -338,7 +333,7 @@ def name(self): """The name of the device.""" return "lightning.kokkos" - def _set_Lightning_classes(self): + def _set_lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" self.LightningStateVector = LightningKokkosStateVector self.LightningMeasurements = LightningKokkosMeasurements diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index faef361c4..cc7ed491d 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -26,7 +26,6 @@ import numpy as np import pennylane as qml -from pennylane import DeviceError from pennylane.measurements import MidMeasureMP from pennylane.ops import Conditional from pennylane.ops.op_math import Adjoint @@ -51,14 +50,12 @@ class LightningStateVector(LightningBaseStateVector): # pylint: disable=too-few device_name(string): state vector device name. Options: ["lightning.qubit"] """ - def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.qubit"): - if device_name != "lightning.qubit": - raise DeviceError(f'The device name "{device_name}" is not a valid option.') + def __init__(self, num_wires, dtype=np.complex128): super().__init__(num_wires, dtype) - self._device_name = device_name - + self._device_name = "lightning.qubit" + # Initialize the state vector self._qubit_state = self._state_dtype()(self._num_wires) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index d6c4ffae8..5fd958f03 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -58,11 +58,6 @@ except ImportError: LQ_CPP_BINARY_AVAILABLE = False -# Result_or_ResultBatch = Union[Result, ResultBatch] -# QuantumTapeBatch = Sequence[QuantumTape] -# QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] -# PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] - # The set of supported operations. _operations = frozenset( { @@ -360,7 +355,7 @@ def __init__( # pylint: disable=too-many-arguments ) # Set the attributes to call the Lightning classes - self._set_Lightning_classes() + self._set_lightning_classes() # Markov Chain Monte Carlo (MCMC) sampling method specific options # TODO: Investigate usefulness of creating numpy random generator @@ -394,7 +389,7 @@ def name(self): """The name of the device.""" return "lightning.qubit" - def _set_Lightning_classes(self): + def _set_lightning_classes(self): """Load the LightningStateVector, LightningMeasurements, LightningAdjointJacobian as class attribute""" self.LightningStateVector = LightningStateVector self.LightningMeasurements = LightningMeasurements diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index 62a5ee384..93d02324e 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -50,8 +50,9 @@ def calculate_reference(tape, execute_and_derivatives=False): return transf_fn(results), jac @staticmethod - def process_and_execute(wires, statevector, tape, execute_and_derivatives=False): + def process_and_execute(statevector, tape, execute_and_derivatives=False): + wires = statevector.num_wires device = LightningDevice(wires) if execute_and_derivatives: results, jac = device.simulate_and_jacobian(tape, statevector) @@ -88,7 +89,7 @@ def test_derivatives_single_expval( ) statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute(3, statevector, qs, execute_and_derivatives) + res, jac = self.process_and_execute(statevector, qs, execute_and_derivatives) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( @@ -127,9 +128,10 @@ def calculate_reference(tape, dy, execute_and_derivatives=False): return transf_fn(results), jac @staticmethod - def process_and_execute(wires, statevector, tape, dy, execute_and_derivatives=False): + def process_and_execute(statevector, tape, dy, execute_and_derivatives=False): dy = [dy] + wires = statevector.num_wires device = LightningDevice(wires) if execute_and_derivatives: results, jac = device.simulate_and_vjp(tape, dy, statevector) @@ -166,8 +168,7 @@ def test_vjp_single_expval(self, theta, phi, obs, execute_and_derivatives, light dy = 1.0 statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute( - 3, statevector, qs, dy, execute_and_derivatives=execute_and_derivatives + res, jac = self.process_and_execute(statevector, qs, dy, execute_and_derivatives=execute_and_derivatives ) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index 3e0905bb3..df2b0e88c 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -47,7 +47,7 @@ @pytest.mark.parametrize("dtype", [np.complex64, np.complex128]) def test_device_name_and_init(num_wires, dtype): """Test the class initialization and returned properties.""" - state_vector = LightningStateVector(num_wires, dtype=dtype, device_name=device_name) + state_vector = LightningStateVector(num_wires, dtype=dtype) assert state_vector.dtype == dtype assert state_vector.device_name == device_name assert state_vector.wires == Wires(range(num_wires)) @@ -59,23 +59,15 @@ def test_device_name_and_init(num_wires, dtype): match=f"Argument kokkos_args must be of type {type(InitializationSettings())} but it is of {type(bad_kokkos_args)}.", ): assert LightningStateVector( - num_wires, dtype=dtype, device_name=device_name, kokkos_args=bad_kokkos_args + num_wires, dtype=dtype, kokkos_args=bad_kokkos_args ) set_kokkos_args = InitializationSettings().set_num_threads(2) - state_vector_3 = LightningStateVector( - num_wires, dtype=dtype, device_name=device_name, kokkos_args=set_kokkos_args - ) + state_vector_3 = LightningStateVector(num_wires, dtype=dtype, kokkos_args=set_kokkos_args) assert type(state_vector) == type(state_vector_3) -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""" From 74df35db506f8ac8e75b22b5ef82c9d5c361c887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 13:32:47 -0400 Subject: [PATCH 120/130] Apply format --- pennylane_lightning/lightning_qubit/_state_vector.py | 2 +- tests/lightning_qubit/test_jacobian_method.py | 3 ++- tests/lightning_qubit/test_state_vector_class.py | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index cc7ed491d..0a8b0b7d6 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -55,7 +55,7 @@ def __init__(self, num_wires, dtype=np.complex128): super().__init__(num_wires, dtype) self._device_name = "lightning.qubit" - + # Initialize the state vector self._qubit_state = self._state_dtype()(self._num_wires) diff --git a/tests/lightning_qubit/test_jacobian_method.py b/tests/lightning_qubit/test_jacobian_method.py index 93d02324e..745feee50 100644 --- a/tests/lightning_qubit/test_jacobian_method.py +++ b/tests/lightning_qubit/test_jacobian_method.py @@ -168,7 +168,8 @@ def test_vjp_single_expval(self, theta, phi, obs, execute_and_derivatives, light dy = 1.0 statevector = lightning_sv(num_wires=3) - res, jac = self.process_and_execute(statevector, qs, dy, execute_and_derivatives=execute_and_derivatives + res, jac = self.process_and_execute( + statevector, qs, dy, execute_and_derivatives=execute_and_derivatives ) if isinstance(obs, qml.Hamiltonian): qs = QuantumScript( diff --git a/tests/lightning_qubit/test_state_vector_class.py b/tests/lightning_qubit/test_state_vector_class.py index df2b0e88c..3918afcd5 100644 --- a/tests/lightning_qubit/test_state_vector_class.py +++ b/tests/lightning_qubit/test_state_vector_class.py @@ -58,9 +58,7 @@ def test_device_name_and_init(num_wires, dtype): TypeError, match=f"Argument kokkos_args must be of type {type(InitializationSettings())} but it is of {type(bad_kokkos_args)}.", ): - assert LightningStateVector( - num_wires, dtype=dtype, kokkos_args=bad_kokkos_args - ) + assert LightningStateVector(num_wires, dtype=dtype, kokkos_args=bad_kokkos_args) set_kokkos_args = InitializationSettings().set_num_threads(2) state_vector_3 = LightningStateVector(num_wires, dtype=dtype, kokkos_args=set_kokkos_args) From 4d6b70f5efb0c05cf739a59f11ed099ce830b512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 13:44:02 -0400 Subject: [PATCH 121/130] Added raise for batch_obs in LK --- pennylane_lightning/core/lightning_newAPI_base.py | 2 +- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index 06df42d44..a527a2f8d 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -293,7 +293,7 @@ def compute_vjp( circuits: QuantumTape_or_Batch, cotangents: Tuple[Number], execution_config: ExecutionConfig = DefaultExecutionConfig, - ): + ) -> Tuple: r"""The vector jacobian product used in reverse-mode differentiation. ``Lightning[Device]`` uses the adjoint differentiation method to compute the VJP. Args: diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 8d7f14ee8..de04e3092 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -23,6 +23,7 @@ import numpy as np import pennylane as qml +from pennylane import DeviceError from pennylane.devices import DefaultExecutionConfig, ExecutionConfig from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support @@ -305,12 +306,15 @@ def __init__( # pylint: disable=too-many-arguments "To manually compile from source, follow the instructions at " "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) + + if batch_obs: + raise DeviceError("Lightning Kokkos does not support batch observables option. 'batch_obs' should be False") super().__init__( wires=wires, c_dtype=c_dtype, shots=shots, - batch_obs=False, + batch_obs=batch_obs, ) # Set the attributes to call the Lightning classes From 6b6146739a71048cc05054f777b6a8bf5f9bfb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 13:45:18 -0400 Subject: [PATCH 122/130] apply format --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index de04e3092..82c9dcee7 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -306,9 +306,11 @@ def __init__( # pylint: disable=too-many-arguments "To manually compile from source, follow the instructions at " "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) - + if batch_obs: - raise DeviceError("Lightning Kokkos does not support batch observables option. 'batch_obs' should be False") + raise DeviceError( + "Lightning Kokkos does not support batch observables option. 'batch_obs' should be False" + ) super().__init__( wires=wires, From e00ae2f67d4251a9f65e1e32f76d4ba86d9fcbd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 13:56:43 -0400 Subject: [PATCH 123/130] remove use_mpi from docstrings Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- pennylane_lightning/core/_adjoint_jacobian_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 18a7b87c1..f40b25710 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -97,7 +97,6 @@ def _process_jacobian_tape( Args: tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. split_obs (bool, optional): If splitting the observables in a list. Defaults to False. Returns: From 3ca8b9ac6e83adda70044acce0dcd8d18008c7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 14:35:48 -0400 Subject: [PATCH 124/130] Ali's review comments --- .../core/_adjoint_jacobian_base.py | 18 ++++++++---------- pennylane_lightning/core/_measurements_base.py | 6 +++--- pennylane_lightning/core/_state_vector_base.py | 2 +- .../core/lightning_newAPI_base.py | 18 +++++++++--------- .../lightning_kokkos/_state_vector.py | 1 - 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/pennylane_lightning/core/_adjoint_jacobian_base.py b/pennylane_lightning/core/_adjoint_jacobian_base.py index 18a7b87c1..50046d5f9 100644 --- a/pennylane_lightning/core/_adjoint_jacobian_base.py +++ b/pennylane_lightning/core/_adjoint_jacobian_base.py @@ -35,7 +35,7 @@ class LightningBaseAdjointJacobian(ABC): Check and execute the adjoint Jacobian differentiation method. Args: - qubit_state(Lightning[Device]StateVector): State Vector to calculate the adjoint Jacobian with. + qubit_state(Lightning [Device] StateVector): State Vector to calculate the adjoint Jacobian with. batch_obs(bool): If serialized tape is to be batched or not. """ @@ -49,7 +49,7 @@ def __init__(self, qubit_state: Any, batch_obs: bool) -> None: @property def qubit_state(self): - """Returns a handle to the Lightning[Device]StateVector object.""" + """Returns a handle to the Lightning [Device] StateVector object.""" return self._qubit_state @property @@ -89,15 +89,12 @@ def _get_return_type( return Expectation - def _process_jacobian_tape( - self, tape: QuantumTape, use_mpi: bool = False, split_obs: bool = False - ): + def _process_jacobian_tape(self, tape: QuantumTape, split_obs: bool = False): """Process a tape, serializing and building a dictionary proper for the adjoint Jacobian calculation in the C++ layer. Args: tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. - use_mpi (bool, optional): If using MPI to accelerate calculation. Defaults to False. split_obs (bool, optional): If splitting the observables in a list. Defaults to False. Returns: @@ -105,6 +102,7 @@ def _process_jacobian_tape( """ use_csingle = self._qubit_state.dtype == np.complex64 + use_mpi = False obs_serialized, obs_indices = QuantumScriptSerializer( self._qubit_state.device_name, use_csingle, use_mpi, split_obs ).serialize_observables(tape) @@ -214,9 +212,9 @@ def calculate_jacobian(self, tape: QuantumTape): .. code-block:: python - statevector = Lightning[Device]StateVector(num_wires=num_wires) + statevector = Lightning [Device] StateVector(num_wires=num_wires) statevector = statevector.get_final_state(tape) - jacobian = Lightning[Device]AdjointJacobian(statevector).calculate_jacobian(tape) + jacobian = Lightning [Device] AdjointJacobian(statevector).calculate_jacobian(tape) Args: tape (QuantumTape): Operations and measurements that represent instructions for execution on Lightning. @@ -231,9 +229,9 @@ def calculate_vjp(self, tape: QuantumTape, grad_vec): .. code-block:: python - statevector = Lightning[Device]StateVector(num_wires=num_wires) + statevector = Lightning [Device] StateVector(num_wires=num_wires) statevector = statevector.get_final_state(tape) - vjp = Lightning[Device]AdjointJacobian(statevector).calculate_vjp(tape, grad_vec) + vjp = Lightning [Device] AdjointJacobian(statevector).calculate_vjp(tape, grad_vec) computes :math:`\\pmb{w} = (w_1,\\cdots,w_m)` where diff --git a/pennylane_lightning/core/_measurements_base.py b/pennylane_lightning/core/_measurements_base.py index fbd3d023d..06ae87889 100644 --- a/pennylane_lightning/core/_measurements_base.py +++ b/pennylane_lightning/core/_measurements_base.py @@ -44,10 +44,10 @@ class LightningBaseMeasurements(ABC): """Lightning [Device] Measurements class A class that serves as a base class for Lightning state-vector simulators. - Measures the state provided by the Lightning[Device]StateVector class. + Measures the state provided by the Lightning [Device] StateVector class. Args: - qubit_state(Lightning[Device]StateVector): Lightning state-vector class containing the state vector to be measured. + qubit_state(Lightning [Device] StateVector): Lightning state-vector class containing the state vector to be measured. """ def __init__( @@ -61,7 +61,7 @@ def __init__( @property def qubit_state(self): - """Returns a handle to the Lightning[Device]StateVector object.""" + """Returns a handle to the Lightning [Device] StateVector object.""" return self._qubit_state @property diff --git a/pennylane_lightning/core/_state_vector_base.py b/pennylane_lightning/core/_state_vector_base.py index 28736b6c1..b2ba3a066 100644 --- a/pennylane_lightning/core/_state_vector_base.py +++ b/pennylane_lightning/core/_state_vector_base.py @@ -210,7 +210,7 @@ def get_final_state( keep the same number of shots. Default is ``None``. Returns: - Lightning[Device]StateVector: Lightning final state class. + Lightning [Device] StateVector: Lightning final state class. """ self.apply_operations( diff --git a/pennylane_lightning/core/lightning_newAPI_base.py b/pennylane_lightning/core/lightning_newAPI_base.py index a527a2f8d..dcee73fd5 100644 --- a/pennylane_lightning/core/lightning_newAPI_base.py +++ b/pennylane_lightning/core/lightning_newAPI_base.py @@ -94,14 +94,14 @@ def _set_lightning_classes(self): def simulate( self, circuit: QuantumScript, - state, # Lightning[Device]StateVector + state, # Lightning [Device] StateVector postselect_mode: str = None, ) -> Result: """Simulate a single quantum script. Args: circuit (QuantumTape): The single circuit to simulate - state (Lightning[Device]StateVector): handle to Lightning state vector + state (Lightning [Device] StateVector): handle to Lightning state vector postselect_mode (str): Configuration for handling shots with mid-circuit measurement postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to keep the same number of shots. Default is ``None``. @@ -115,7 +115,7 @@ def simulate( def jacobian( self, circuit: QuantumTape, - state, # Lightning[Device]StateVector + state, # Lightning [Device] StateVector batch_obs: bool = False, wire_map: dict = None, ): @@ -123,7 +123,7 @@ def jacobian( Args: circuit (QuantumTape): The single circuit to simulate - state (Lightning[Device]StateVector): handle to the Lightning state vector + state (Lightning [Device] StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices @@ -142,7 +142,7 @@ def jacobian( def simulate_and_jacobian( self, circuit: QuantumTape, - state, # Lightning[Device]StateVector + state, # Lightning [Device] StateVector batch_obs: bool = False, wire_map: dict = None, ) -> Tuple: @@ -150,7 +150,7 @@ def simulate_and_jacobian( Args: circuit (QuantumTape): The single circuit to simulate - state (Lightning[Device]StateVector): handle to the Lightning state vector + state (Lightning [Device] StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. Default is False. wire_map (Optional[dict]): a map from wire labels to simulation indices @@ -170,7 +170,7 @@ def vjp( # pylint: disable=too-many-arguments self, circuit: QuantumTape, cotangents: Tuple[Number], - state, # Lightning[Device]StateVector + state, # Lightning [Device] StateVector batch_obs: bool = False, wire_map: dict = None, ): @@ -181,7 +181,7 @@ def vjp( # pylint: disable=too-many-arguments have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (Lightning[Device]StateVector): handle to the Lightning state vector + state (Lightning [Device] StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the VJP. wire_map (Optional[dict]): a map from wire labels to simulation indices @@ -212,7 +212,7 @@ def simulate_and_vjp( # pylint: disable=too-many-arguments have shape matching the output shape of the corresponding circuit. If the circuit has a single output, ``cotangents`` may be a single number, not an iterable of numbers. - state (Lightning[Device]StateVector): handle to the Lightning state vector + state (Lightning [Device] StateVector): handle to the Lightning state vector batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. wire_map (Optional[dict]): a map from wire labels to simulation indices diff --git a/pennylane_lightning/lightning_kokkos/_state_vector.py b/pennylane_lightning/lightning_kokkos/_state_vector.py index 5ba5343f5..d9cd12811 100644 --- a/pennylane_lightning/lightning_kokkos/_state_vector.py +++ b/pennylane_lightning/lightning_kokkos/_state_vector.py @@ -28,7 +28,6 @@ import numpy as np import pennylane as qml -from pennylane import DeviceError from pennylane.measurements import MidMeasureMP from pennylane.ops import Conditional from pennylane.ops.op_math import Adjoint From 7fd4f2fb389223744b83c1d402b9b108e1e9a966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 16:24:23 -0400 Subject: [PATCH 125/130] skip test for batch_obs in LK --- tests/test_comparison.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 2c16862ae..0fac6bea9 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -78,6 +78,9 @@ def test_one_qubit_circuit( ): """Test a single-qubit circuit""" + if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + pytest.skip(reason="Lightning Kokkos does not support batch_obs") + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -117,6 +120,11 @@ def test_two_qubit_circuit( self, monkeypatch, wires, lightning_dev_version, basis_state, num_threads ): """Test a two-qubit circuit""" + + if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + pytest.skip(reason="Lightning Kokkos does not support batch_obs") + + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -165,6 +173,10 @@ def test_three_qubit_circuit( self, monkeypatch, wires, lightning_dev_version, basis_state, num_threads ): """Test a three-qubit circuit""" + + if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + pytest.skip(reason="Lightning Kokkos does not support batch_obs") + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -221,6 +233,11 @@ def test_four_qubit_circuit( self, monkeypatch, wires, lightning_dev_version, basis_state, num_threads ): """Test a four-qubit circuit""" + + if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + pytest.skip(reason="Lightning Kokkos does not support batch_obs") + + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -281,6 +298,9 @@ def test_n_qubit_circuit( self, monkeypatch, stateprep, wires, lightning_dev_version, num_threads ): """Test an n-qubit circuit""" + if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + pytest.skip(reason="Lightning Kokkos does not support batch_obs") + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) vec = np.array([1] * (2**wires)) / np.sqrt(2**wires) From 3a3f59dd0ef139a7b09c055556faf42e2216fa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Fri, 6 Sep 2024 16:32:10 -0400 Subject: [PATCH 126/130] apply format --- tests/test_comparison.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/test_comparison.py b/tests/test_comparison.py index 0fac6bea9..acec30b67 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -78,9 +78,12 @@ def test_one_qubit_circuit( ): """Test a single-qubit circuit""" - if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + if ( + lightning_dev_version == lightning_backend_batch_obs_dev + and device_name == "lightning.kokkos" + ): pytest.skip(reason="Lightning Kokkos does not support batch_obs") - + monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -121,10 +124,12 @@ def test_two_qubit_circuit( ): """Test a two-qubit circuit""" - if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + if ( + lightning_dev_version == lightning_backend_batch_obs_dev + and device_name == "lightning.kokkos" + ): pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -173,8 +178,11 @@ def test_three_qubit_circuit( self, monkeypatch, wires, lightning_dev_version, basis_state, num_threads ): """Test a three-qubit circuit""" - - if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + + if ( + lightning_dev_version == lightning_backend_batch_obs_dev + and device_name == "lightning.kokkos" + ): pytest.skip(reason="Lightning Kokkos does not support batch_obs") monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) @@ -233,11 +241,13 @@ def test_four_qubit_circuit( self, monkeypatch, wires, lightning_dev_version, basis_state, num_threads ): """Test a four-qubit circuit""" - - if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + + if ( + lightning_dev_version == lightning_backend_batch_obs_dev + and device_name == "lightning.kokkos" + ): pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -298,7 +308,10 @@ def test_n_qubit_circuit( self, monkeypatch, stateprep, wires, lightning_dev_version, num_threads ): """Test an n-qubit circuit""" - if lightning_dev_version == lightning_backend_batch_obs_dev and device_name == "lightning.kokkos": + if ( + lightning_dev_version == lightning_backend_batch_obs_dev + and device_name == "lightning.kokkos" + ): pytest.skip(reason="Lightning Kokkos does not support batch_obs") monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) From a1cac8ea90d98f30bf11ce80b3b5d26b7b947de3 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 6 Sep 2024 20:41:27 +0000 Subject: [PATCH 127/130] Auto update version from '0.39.0-dev6' to '0.39.0-dev7' --- 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 d9ab971e7..2918ecaa8 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.39.0-dev6" +__version__ = "0.39.0-dev7" From fbba32683550c095ebac5ec4ae6d052350dd8033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 9 Sep 2024 09:06:33 -0400 Subject: [PATCH 128/130] revert batch_obs False --- .../lightning_kokkos/lightning_kokkos.py | 5 ---- tests/test_comparison.py | 29 ------------------- 2 files changed, 34 deletions(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 82c9dcee7..69bcf4a09 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -307,11 +307,6 @@ def __init__( # pylint: disable=too-many-arguments "https://docs.pennylane.ai/projects/lightning/en/stable/dev/installation.html." ) - if batch_obs: - raise DeviceError( - "Lightning Kokkos does not support batch observables option. 'batch_obs' should be False" - ) - super().__init__( wires=wires, c_dtype=c_dtype, diff --git a/tests/test_comparison.py b/tests/test_comparison.py index acec30b67..5fbf3a037 100644 --- a/tests/test_comparison.py +++ b/tests/test_comparison.py @@ -78,12 +78,6 @@ def test_one_qubit_circuit( ): """Test a single-qubit circuit""" - if ( - lightning_dev_version == lightning_backend_batch_obs_dev - and device_name == "lightning.kokkos" - ): - pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -124,12 +118,6 @@ def test_two_qubit_circuit( ): """Test a two-qubit circuit""" - if ( - lightning_dev_version == lightning_backend_batch_obs_dev - and device_name == "lightning.kokkos" - ): - pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -179,12 +167,6 @@ def test_three_qubit_circuit( ): """Test a three-qubit circuit""" - if ( - lightning_dev_version == lightning_backend_batch_obs_dev - and device_name == "lightning.kokkos" - ): - pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -242,12 +224,6 @@ def test_four_qubit_circuit( ): """Test a four-qubit circuit""" - if ( - lightning_dev_version == lightning_backend_batch_obs_dev - and device_name == "lightning.kokkos" - ): - pytest.skip(reason="Lightning Kokkos does not support batch_obs") - monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) def circuit(measurement): @@ -308,11 +284,6 @@ def test_n_qubit_circuit( self, monkeypatch, stateprep, wires, lightning_dev_version, num_threads ): """Test an n-qubit circuit""" - if ( - lightning_dev_version == lightning_backend_batch_obs_dev - and device_name == "lightning.kokkos" - ): - pytest.skip(reason="Lightning Kokkos does not support batch_obs") monkeypatch.setenv("OMP_NUM_THREADS", str(num_threads)) From ba67cca89dabe3593125c44fb0eabd7a30fe757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 9 Sep 2024 09:08:31 -0400 Subject: [PATCH 129/130] remove unused-import --- pennylane_lightning/lightning_kokkos/lightning_kokkos.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index 69bcf4a09..47f501446 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -23,7 +23,6 @@ import numpy as np import pennylane as qml -from pennylane import DeviceError from pennylane.devices import DefaultExecutionConfig, ExecutionConfig from pennylane.devices.default_qubit import adjoint_ops from pennylane.devices.modifiers import simulator_tracking, single_tape_support From efa300573c0b5b019588af277ecd6f89252725f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Alfredo=20Nu=C3=B1ez=20Meneses?= Date: Mon, 9 Sep 2024 10:35:07 -0400 Subject: [PATCH 130/130] Added changelog --- .github/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index c07dde009..e4f135601 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -24,6 +24,9 @@ * Smarter defaults for the `split_obs` argument in the serializer. The serializer splits linear combinations into chunks instead of all their terms. [(#873)](https://github.com/PennyLaneAI/pennylane-lightning/pull/873/) +* Unify Lightning Kokkos device and Lightning Qubit device under a Lightning Base device + [(#876)](https://github.com/PennyLaneAI/pennylane-lightning/pull/876) + ### Documentation ### Bug fixes