From 06f1285d744b536c2bbc934b6675852de41671a3 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 3 Apr 2024 15:19:00 -0400 Subject: [PATCH 01/88] empty commit (triggering CI) From 6ffe0be15d1ef1f00ce655e3b0b1f66a2bfce137 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 3 Apr 2024 19:20:42 +0000 Subject: [PATCH 02/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 58da0aa862..19067082da 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev20" +__version__ = "0.36.0-dev21" From 95d2bf73ff7d0c2f6945ee5a2bc683628d9fcb62 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 3 Apr 2024 17:19:14 -0400 Subject: [PATCH 03/88] Definition of the two front-end classes --- .../lightning_tensor/_tensor_vector.py | 25 +++++++++++++++++++ .../lightning_tensor/lightning_tensor.py | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 pennylane_lightning/lightning_tensor/_tensor_vector.py create mode 100644 pennylane_lightning/lightning_tensor/lightning_tensor.py diff --git a/pennylane_lightning/lightning_tensor/_tensor_vector.py b/pennylane_lightning/lightning_tensor/_tensor_vector.py new file mode 100644 index 0000000000..02e73a4c45 --- /dev/null +++ b/pennylane_lightning/lightning_tensor/_tensor_vector.py @@ -0,0 +1,25 @@ +# 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-tensor manipulation. +""" + + +class LightningStateTensor: + """Lightning state-tensor class. + + Interfaces with C++ python binding methods for state-vector manipulation. + """ + + pass diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py new file mode 100644 index 0000000000..9bc1759534 --- /dev/null +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -0,0 +1,25 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This module contains the LightningTensor class that inherits from the new device interface. +""" + + +class LightningTensor(Device): + """PennyLane Lightning Kokkos device. + + A device that interfaces with C++ to perform fast linear algebra calculations. + """ + + pass From 3a35a09d8b6b76fe47486d7d3871f2297350b3cf Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 3 Apr 2024 18:17:23 -0400 Subject: [PATCH 04/88] adding the `lightning_tensor` string to the supported backends in `setup.py` --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 977f753559..072e7e3f25 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ from setuptools.command.build_ext import build_ext default_backend = "lightning_qubit" -supported_backends = {"lightning_kokkos", "lightning_qubit", "lightning_gpu"} +supported_backends = {"lightning_kokkos", "lightning_qubit", "lightning_gpu", "lightning_tensor"} supported_backends.update({sb.replace("_", ".") for sb in supported_backends}) @@ -29,7 +29,7 @@ def get_backend(): """Return backend. The backend is ``lightning_qubit`` by default. - Allowed values are: "lightning_kokkos", "lightning_qubit" and "lightning_gpu". + Allowed values are: "lightning_kokkos", "lightning_qubit", "lightning_gpu" and "lightning_tensor". A dot can also be used instead of an underscore. If the environment variable ``PL_BACKEND`` is defined, its value is used. Otherwise, if the environment variable ``CMAKE_ARGS`` is defined and it @@ -211,6 +211,7 @@ def build_extension(self, ext: CMakeExtension): "extras_require": { "gpu": ["pennylane-lightning-gpu"], "kokkos": ["pennylane-lightning-kokkos"], + "tensor": ["pennylane-lightning-tensor"], }, } From 568df4ca0f8486ded1f7dac3fea9bda60fcae887 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 4 Apr 2024 11:13:36 -0400 Subject: [PATCH 05/88] adding `__init__.py` file to directly import the `lightning_tensor` device class --- .../lightning_tensor/__init__.py | 18 ++++++++++++++++++ .../lightning_tensor/_tensor_vector.py | 4 +--- .../lightning_tensor/lightning_tensor.py | 12 +++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 pennylane_lightning/lightning_tensor/__init__.py diff --git a/pennylane_lightning/lightning_tensor/__init__.py b/pennylane_lightning/lightning_tensor/__init__.py new file mode 100644 index 0000000000..767b402b9f --- /dev/null +++ b/pennylane_lightning/lightning_tensor/__init__.py @@ -0,0 +1,18 @@ +# 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. +"""PennyLane lightning_tensor package.""" + +from pennylane_lightning.core import __version__ + +from .lightning_tensor import LightningTensor diff --git a/pennylane_lightning/lightning_tensor/_tensor_vector.py b/pennylane_lightning/lightning_tensor/_tensor_vector.py index 02e73a4c45..8f4ed31141 100644 --- a/pennylane_lightning/lightning_tensor/_tensor_vector.py +++ b/pennylane_lightning/lightning_tensor/_tensor_vector.py @@ -19,7 +19,5 @@ class LightningStateTensor: """Lightning state-tensor class. - Interfaces with C++ python binding methods for state-vector manipulation. + Interfaces with C++ python binding methods for state-tensor manipulation. """ - - pass diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 9bc1759534..2eab688d4f 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -15,11 +15,17 @@ This module contains the LightningTensor class that inherits from the new device interface. """ +from dataclasses import replace +from pathlib import Path +from typing import Callable, Optional, Sequence, Union + +import numpy as np +import pennylane as qml +from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig + class LightningTensor(Device): - """PennyLane Lightning Kokkos device. + """PennyLane Lightning Tensor device. A device that interfaces with C++ to perform fast linear algebra calculations. """ - - pass From 2fcf200a8cc2321478fc7ac842d26f85e108289c Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 4 Apr 2024 11:18:30 -0400 Subject: [PATCH 06/88] re-naming file --- .../lightning_tensor/{_tensor_vector.py => _state_tensor.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pennylane_lightning/lightning_tensor/{_tensor_vector.py => _state_tensor.py} (100%) diff --git a/pennylane_lightning/lightning_tensor/_tensor_vector.py b/pennylane_lightning/lightning_tensor/_state_tensor.py similarity index 100% rename from pennylane_lightning/lightning_tensor/_tensor_vector.py rename to pennylane_lightning/lightning_tensor/_state_tensor.py From 2a6cabd8fb906714e4bccfe3e5dc0ccd3ec73e5d Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Thu, 4 Apr 2024 15:19:50 +0000 Subject: [PATCH 07/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 19067082da..86e85a246a 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev21" +__version__ = "0.36.0-dev22" From f3b8e8f9adb53ebd9667171e21b1720241f4a255 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 4 Apr 2024 18:37:24 -0400 Subject: [PATCH 08/88] Creating the first prototype of initial MPS state tensor using `quimb` --- .../lightning_tensor/_state_tensor.py | 83 +++++++++++++++++++ .../lightning_tensor/lightning_tensor.py | 48 +++++++++-- 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/_state_tensor.py b/pennylane_lightning/lightning_tensor/_state_tensor.py index 8f4ed31141..a98fef6dc5 100644 --- a/pennylane_lightning/lightning_tensor/_state_tensor.py +++ b/pennylane_lightning/lightning_tensor/_state_tensor.py @@ -15,9 +15,92 @@ Class implementation for state-tensor manipulation. """ +from typing import Iterable, Union +import quimb.tensor as qtn + +import numpy as np +import pennylane as qml +from pennylane import DeviceError + +from pennylane.wires import Wires + class LightningStateTensor: """Lightning state-tensor class. Interfaces with C++ python binding methods for state-tensor manipulation. """ + + def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): + 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.tensor": + raise DeviceError(f'The device name "{device_name}" is not a valid option.') + + self._device_name = device_name + # TODO: add binding to Lightning Managed state tensor C++ class. + # self._tensor_state = self._state_dtype()(self._num_wires) + + # TODO: change name + self._quimb_state = self._create_initial_state(self._wires) + + @property + def dtype(self): + """Returns the state tensor data type.""" + return self._dtype + + @property + def device_name(self): + """Returns the state tensor 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_tensor(self): + """Returns a handle to the state tensor.""" + return self._tensor_state + + # TODO implement + @property + def state(self): + """Copy the state tensor data to a numpy array.""" + pass + + # TODO implement + def _state_dtype(self): + """Binding to Lightning Managed state tensor C++ class. + + Returns: the state tensor class + """ + pass + + def _create_initial_state(self, wires: Union[qml.wires.Wires, Iterable]): + r""" + Returns an initial state to :math:`\ket{0}`. + + Args: + wires (Union[Wires, Iterable]): The wires to be present in the initial state. + + Returns: + array: The initial state of a circuit. + """ + + return qtn.MPS_computational_state( + "0" * max(1, len(wires)), + dtype=self._dtype.__name__, + tags=[str(l) for l in wires.labels], + ) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 2eab688d4f..61128b01fe 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -15,13 +15,12 @@ This module contains the LightningTensor class that inherits from the new device interface. """ -from dataclasses import replace -from pathlib import Path -from typing import Callable, Optional, Sequence, Union - import numpy as np import pennylane as qml -from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices import Device + + +from ._state_tensor import LightningStateTensor class LightningTensor(Device): @@ -29,3 +28,42 @@ class LightningTensor(Device): A device that interfaces with C++ to perform fast linear algebra calculations. """ + + _new_API = True + + def __init__( + self, + wires, + *, + c_dtype=np.complex128, + shots=None, + ): + + # TODO: should we accept cases in which shots=0, shots=1? + if shots is not None: + raise ValueError( + "LightningTensor does not support a finite number of shots." + ) + + super().__init__(wires=wires, shots=shots) + + self._statetensor = LightningStateTensor( + num_wires=len(self.wires), dtype=c_dtype + ) + + self._c_dtype = c_dtype + + @property + def name(self): + """The name of the device.""" + return "lightning.tensor" + + @property + def c_dtype(self): + """State vector complex data type.""" + return self._c_dtype + + dtype = c_dtype + + def execute(): + pass From 98891cb38d1fa4f089121f9ce4fac7e9a88474ed Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 5 Apr 2024 14:43:45 -0400 Subject: [PATCH 09/88] providing the `backend`, `method` parameters and making `wires` optional --- .../lightning_tensor/_state_tensor.py | 7 +++- .../lightning_tensor/lightning_tensor.py | 42 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/_state_tensor.py b/pennylane_lightning/lightning_tensor/_state_tensor.py index a98fef6dc5..cece499551 100644 --- a/pennylane_lightning/lightning_tensor/_state_tensor.py +++ b/pennylane_lightning/lightning_tensor/_state_tensor.py @@ -25,6 +25,9 @@ from pennylane.wires import Wires +# Let's just focus on `quimb` as backend and `MPS` as method so far + + class LightningStateTensor: """Lightning state-tensor class. @@ -74,11 +77,11 @@ def state_tensor(self): """Returns a handle to the state tensor.""" return self._tensor_state - # TODO implement + # TODO understand better what's happening @property def state(self): """Copy the state tensor data to a numpy array.""" - pass + return self._quimb_state.to_dense() # TODO implement def _state_dtype(self): diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 61128b01fe..9c36db92c3 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -19,6 +19,8 @@ import pennylane as qml from pennylane.devices import Device +from pennylane.wires import Wires + from ._state_tensor import LightningStateTensor @@ -31,33 +33,57 @@ class LightningTensor(Device): _new_API = True + # TODO: add `max_bond_dim` parameter def __init__( self, - wires, *, + wires=None, + backend="quimb", + method="mps", c_dtype=np.complex128, shots=None, ): - # TODO: should we accept cases in which shots=0, shots=1? - if shots is not None: - raise ValueError( - "LightningTensor does not support a finite number of shots." + if backend not in ["quimb", "cutensornet"]: + raise TypeError(f"Unsupported backend: {backend}") + + if backend == "cutensornet": + raise NotImplementedError( + f"The cutensornet backend has not yet been implemented." + ) + + if method not in ["mps", "tn"]: + raise TypeError(f"Unsupported method: {method}") + + if method == "tn": + raise NotImplementedError( + f"The tensor network method has not yet been implemented." ) + # Should we accept cases in which shots=0, shots=1? + if shots is not None: + raise ValueError("LightningTensor does not support the `shots` parameter.") + super().__init__(wires=wires, shots=shots) - self._statetensor = LightningStateTensor( - num_wires=len(self.wires), dtype=c_dtype - ) + self._num_wires = len(self.wires) if self.wires else 0 self._c_dtype = c_dtype + self._statetensor = LightningStateTensor( + num_wires=self.num_wires, dtype=self._c_dtype + ) + @property def name(self): """The name of the device.""" return "lightning.tensor" + @property + def num_wires(self): + """Number of wires addressed on this device""" + return self._num_wires + @property def c_dtype(self): """State vector complex data type.""" From a63241e27e4bceb36d8b7ccb065221043aab5d39 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 8 Apr 2024 14:41:27 -0400 Subject: [PATCH 10/88] Changing names and structure --- .../{_state_tensor.py => _mps.py} | 39 ++++---------- .../lightning_tensor/lightning_tensor.py | 53 ++++++++++--------- 2 files changed, 37 insertions(+), 55 deletions(-) rename pennylane_lightning/lightning_tensor/{_state_tensor.py => _mps.py} (68%) diff --git a/pennylane_lightning/lightning_tensor/_state_tensor.py b/pennylane_lightning/lightning_tensor/_mps.py similarity index 68% rename from pennylane_lightning/lightning_tensor/_state_tensor.py rename to pennylane_lightning/lightning_tensor/_mps.py index cece499551..0eea597c00 100644 --- a/pennylane_lightning/lightning_tensor/_state_tensor.py +++ b/pennylane_lightning/lightning_tensor/_mps.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Class implementation for state-tensor manipulation. +Class implementation for MPS manipulation based on `quimb`. """ from typing import Iterable, Union @@ -25,13 +25,10 @@ from pennylane.wires import Wires -# Let's just focus on `quimb` as backend and `MPS` as method so far +class QuimbMPS: + """Quimb MPS class. - -class LightningStateTensor: - """Lightning state-tensor class. - - Interfaces with C++ python binding methods for state-tensor manipulation. + Interfaces with `quimb` for MPS manipulation. """ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): @@ -46,11 +43,7 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor raise DeviceError(f'The device name "{device_name}" is not a valid option.') self._device_name = device_name - # TODO: add binding to Lightning Managed state tensor C++ class. - # self._tensor_state = self._state_dtype()(self._num_wires) - - # TODO: change name - self._quimb_state = self._create_initial_state(self._wires) + self._mps = self._initial_mps(self._wires) @property def dtype(self): @@ -64,34 +57,22 @@ def device_name(self): @property def wires(self): - """All wires that can be addressed on this device""" + """All wires that can be addressed on this device.""" return self._wires @property def num_wires(self): - """Number of wires addressed on this device""" + """Number of wires addressed on this device.""" return self._num_wires - @property - def state_tensor(self): - """Returns a handle to the state tensor.""" - return self._tensor_state - # TODO understand better what's happening @property def state(self): """Copy the state tensor data to a numpy array.""" - return self._quimb_state.to_dense() - - # TODO implement - def _state_dtype(self): - """Binding to Lightning Managed state tensor C++ class. - - Returns: the state tensor class - """ - pass + return self._mps.to_dense() - def _create_initial_state(self, wires: Union[qml.wires.Wires, Iterable]): + # TODO modify this later on + def _initial_mps(self, wires: Union[qml.wires.Wires, Iterable]): r""" Returns an initial state to :math:`\ket{0}`. diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 9c36db92c3..87d117e479 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -19,12 +19,13 @@ import pennylane as qml from pennylane.devices import Device -from pennylane.wires import Wires +from ._mps import QuimbMPS - -from ._state_tensor import LightningStateTensor +supported_backends = ["quimb", "cutensornet"] +supported_methods = ["mps", "tn"] +# TODO: add class docs class LightningTensor(Device): """PennyLane Lightning Tensor device. @@ -44,35 +45,25 @@ def __init__( shots=None, ): - if backend not in ["quimb", "cutensornet"]: - raise TypeError(f"Unsupported backend: {backend}") - - if backend == "cutensornet": - raise NotImplementedError( - f"The cutensornet backend has not yet been implemented." - ) + if backend not in supported_backends: + raise ValueError(f"Unsupported backend: {backend}") - if method not in ["mps", "tn"]: - raise TypeError(f"Unsupported method: {method}") + if method not in supported_methods: + raise ValueError(f"Unsupported method: {method}") - if method == "tn": - raise NotImplementedError( - f"The tensor network method has not yet been implemented." - ) - - # Should we accept cases in which shots=0, shots=1? if shots is not None: raise ValueError("LightningTensor does not support the `shots` parameter.") super().__init__(wires=wires, shots=shots) - self._num_wires = len(self.wires) if self.wires else 0 - + self._backend = backend + self._method = method self._c_dtype = c_dtype + self._num_wires = len(self.wires) if self.wires else 0 + self._statetensor = None - self._statetensor = LightningStateTensor( - num_wires=self.num_wires, dtype=self._c_dtype - ) + if backend == "quimb" and method == "mps": + self._statetensor = QuimbMPS(num_wires=self.num_wires, dtype=self._c_dtype) @property def name(self): @@ -80,15 +71,25 @@ def name(self): return "lightning.tensor" @property - def num_wires(self): - """Number of wires addressed on this device""" - return self._num_wires + def backend(self): + """Supported backend.""" + return self._backend + + @property + def method(self): + """Supported method.""" + return self._method @property def c_dtype(self): """State vector complex data type.""" return self._c_dtype + @property + def num_wires(self): + """Number of wires addressed on this device.""" + return self._num_wires + dtype = c_dtype def execute(): From 10e5d22f1a08437c12a1fd75195e68928bf7e769 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Mon, 8 Apr 2024 18:44:32 +0000 Subject: [PATCH 11/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 86e85a246a..88f51b9d67 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev22" +__version__ = "0.36.0-dev23" From 68f4296b79b20a21230633837fcecaa952e81794 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 8 Apr 2024 20:09:06 -0400 Subject: [PATCH 12/88] adding method required by the new device API design --- .../lightning_tensor/lightning_tensor.py | 77 ++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 87d117e479..24800fea13 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -14,24 +14,40 @@ """ This module contains the LightningTensor class that inherits from the new device interface. """ +from dataclasses import replace +from numbers import Number +from typing import Callable, Optional, Sequence, Tuple, Union + import numpy as np import pennylane as qml -from pennylane.devices import Device +from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig +from pennylane.devices.modifiers import simulator_tracking, single_tape_support +from pennylane.tape import QuantumScript, QuantumTape +from pennylane.typing import Result, ResultBatch from ._mps import QuimbMPS supported_backends = ["quimb", "cutensornet"] supported_methods = ["mps", "tn"] +Result_or_ResultBatch = Union[Result, ResultBatch] +QuantumTapeBatch = Sequence[QuantumTape] +QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] +PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] + # TODO: add class docs +@simulator_tracking +@single_tape_support class LightningTensor(Device): """PennyLane Lightning Tensor device. - A device that interfaces with C++ to perform fast linear algebra calculations. + A device to perform fast linear algebra and tensor network calculations. """ + _device_options = ("backend", "c_dtype", "method", "max_bond_dim") + _new_API = True # TODO: add `max_bond_dim` parameter @@ -92,5 +108,60 @@ def num_wires(self): dtype = c_dtype - def execute(): + # should `backend` and `method` be inserted here? + def _setup_execution_config(self, config): + pass + + def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): + pass + + def execute( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ) -> Result_or_ResultBatch: + pass + + def supports_derivatives( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[qml.tape.QuantumTape] = None, + ) -> bool: + pass + + def compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + pass + + def execute_and_compute_derivatives( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + pass + + def supports_vjp( + self, + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[QuantumTape] = None, + ) -> bool: + pass + + def compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): + pass + + def execute_and_compute_vjp( + self, + circuits: QuantumTape_or_Batch, + cotangents: Tuple[Number], + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): pass From f78c2f869d747b7c81892fb5fdeedd8a3636187b Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Tue, 9 Apr 2024 00:09:25 +0000 Subject: [PATCH 13/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 88f51b9d67..b0da18e929 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev23" +__version__ = "0.36.0-dev24" From 5f6458228968ecabb0c868d52dd4118868bfcc8b Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Tue, 9 Apr 2024 13:16:48 -0400 Subject: [PATCH 14/88] using the `kwargs` parameter in `LightningTensor` and `CircuitMPS` in `quimb` --- .../lightning_tensor/__init__.py | 2 +- pennylane_lightning/lightning_tensor/_mps.py | 47 ++++++----- .../lightning_tensor/lightning_tensor.py | 77 ++++++++++++++++--- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/__init__.py b/pennylane_lightning/lightning_tensor/__init__.py index 767b402b9f..48cc140c46 100644 --- a/pennylane_lightning/lightning_tensor/__init__.py +++ b/pennylane_lightning/lightning_tensor/__init__.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. diff --git a/pennylane_lightning/lightning_tensor/_mps.py b/pennylane_lightning/lightning_tensor/_mps.py index 0eea597c00..e8204a1991 100644 --- a/pennylane_lightning/lightning_tensor/_mps.py +++ b/pennylane_lightning/lightning_tensor/_mps.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Class implementation for MPS manipulation based on `quimb`. +Class implementation for MPS manipulation based on the `quimb` Python package. """ from typing import Iterable, Union @@ -32,9 +32,6 @@ class QuimbMPS: """ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): - 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}") @@ -43,36 +40,44 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor raise DeviceError(f'The device name "{device_name}" is not a valid option.') self._device_name = device_name - self._mps = self._initial_mps(self._wires) + self._num_wires = num_wires + self._wires = Wires(range(num_wires)) + self._dtype = dtype - @property - def dtype(self): - """Returns the state tensor data type.""" - return self._dtype + # TODO: allows users to specify initial state + self._mps = qtn.CircuitMPS(psi0=self._set_initial_mps()) @property def device_name(self): - """Returns the state tensor device name.""" + """Returns the device name.""" return self._device_name + @property + def num_wires(self): + """Number of wires addressed on this device.""" + return self._num_wires + @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 + def dtype(self): + """Returns the mps data type.""" + return self._dtype + + @property + def mps(self): + """MPS on this device.""" + return self._mps.psi - # TODO understand better what's happening @property - def state(self): - """Copy the state tensor data to a numpy array.""" - return self._mps.to_dense() + def state(self, digits: int = 5): + """Contract the MPS into a dense array.""" + return self._mps.psi.to_dense().round(digits) - # TODO modify this later on - def _initial_mps(self, wires: Union[qml.wires.Wires, Iterable]): + def _set_initial_mps(self): r""" Returns an initial state to :math:`\ket{0}`. @@ -84,7 +89,7 @@ def _initial_mps(self, wires: Union[qml.wires.Wires, Iterable]): """ return qtn.MPS_computational_state( - "0" * max(1, len(wires)), + "0" * max(1, self._num_wires), dtype=self._dtype.__name__, - tags=[str(l) for l in wires.labels], + tags=[str(l) for l in self._wires.labels], ) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 24800fea13..92f52f5c75 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -46,19 +46,33 @@ class LightningTensor(Device): A device to perform fast linear algebra and tensor network calculations. """ - _device_options = ("backend", "c_dtype", "method", "max_bond_dim") + _device_options = ( + "backend", + "method", + "c_dtype", + "contraction_optimizer", + "local_simplify", + "sample_qubits", + "max_bond_dim", + "cutoff", + "measure_algorithm", + "apply_reverse_lightcone", + "return_tn", + "rehearse", + ) _new_API = True - # TODO: add `max_bond_dim` parameter + # should `backend` and `method` be keyword args as well? def __init__( self, *, wires=None, backend="quimb", method="mps", - c_dtype=np.complex128, shots=None, + c_dtype=np.complex128, + **kwargs, ): if backend not in supported_backends: @@ -72,13 +86,28 @@ def __init__( super().__init__(wires=wires, shots=shots) + self._num_wires = len(self.wires) if self.wires else 0 self._backend = backend self._method = method self._c_dtype = c_dtype - self._num_wires = len(self.wires) if self.wires else 0 + + # options for Tensor Network Simulator + self._contraction_optimizer = kwargs.get("contraction_optimizer", None) + self._local_simplify = kwargs.get("local_simplify", None) + self._sample_qubits = kwargs.get("sample_qubits", None) + # options for MPS + self._max_bond_dim = kwargs.get("max_bond_dim", None) + self._cutoff = kwargs.get("cutoff", 1e-16) + self._measure_algorithm = kwargs.get("measure_algorithm", None) + # common options + self._apply_reverse_lightcone = kwargs.get("apply_reverse_lightcone", None) + self._return_tn = kwargs.get("return_tn", None) + self._rehearse = kwargs.get("rehearse", None) + self._statetensor = None if backend == "quimb" and method == "mps": + # TODO: pass the options for MPS to the class self._statetensor = QuimbMPS(num_wires=self.num_wires, dtype=self._c_dtype) @property @@ -86,6 +115,11 @@ def name(self): """The name of the device.""" return "lightning.tensor" + @property + def num_wires(self): + """Number of wires addressed on this device.""" + return self._num_wires + @property def backend(self): """Supported backend.""" @@ -101,19 +135,38 @@ def c_dtype(self): """State vector complex data type.""" return self._c_dtype - @property - def num_wires(self): - """Number of wires addressed on this device.""" - return self._num_wires - dtype = c_dtype - # should `backend` and `method` be inserted here? def _setup_execution_config(self, config): - pass + """ + 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) def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): - pass + """ + ... + """ + + config = self._setup_execution_config(execution_config) + + return config def execute( self, From 621082cd1aca5af0dba515b68bc7f8f321a32a7b Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Tue, 9 Apr 2024 17:30:20 -0400 Subject: [PATCH 15/88] taking some further inputs from the new device API --- .../lightning_tensor/lightning_tensor.py | 63 +++++++++++++++---- .../lightning_tensor/{ => quimb}/_mps.py | 2 +- 2 files changed, 53 insertions(+), 12 deletions(-) rename pennylane_lightning/lightning_tensor/{ => quimb}/_mps.py (98%) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 92f52f5c75..f1147b298c 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -26,10 +26,8 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.typing import Result, ResultBatch -from ._mps import QuimbMPS +from .quimb._mps import QuimbMPS -supported_backends = ["quimb", "cutensornet"] -supported_methods = ["mps", "tn"] Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] @@ -37,13 +35,50 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -# TODO: add class docs +# TODO: add all docs to class and functions + +# TODO: question: how do we expose methods for qml.expval? + + +_backends = frozenset({"quimb", "cutensornet"}) +# The set of supported backends. + +_methods = frozenset({"mps", "tn"}) +# The set of supported methods. + +_operations = frozenset({}) +# The set of supported operations. + +_observables = frozenset({}) +# The set of supported observables. + + +def accepted_backends(backend: str) -> bool: + """A function that determines whether or not a backend is supported by ``lightning.tensor``.""" + return backend in _backends + + +def accepted_methods(method: str) -> bool: + """A function that determines whether or not a method is supported by ``lightning.tensor``.""" + return method in _methods + + +def accepted_operations(op: qml.operation.Operator) -> bool: + """A function that determines whether or not an operation is supported by ``lightning.tensor``.""" + return op.name in _operations + + +def accepted_observables(obs: qml.operation.Operator) -> bool: + """A function that determines whether or not an observable is supported by ``lightning.tensor``.""" + return obs.name in _observables + + @simulator_tracking @single_tape_support class LightningTensor(Device): """PennyLane Lightning Tensor device. - A device to perform fast linear algebra and tensor network calculations. + A device to perform tensor network operations on a quantum circuit. """ _device_options = ( @@ -75,10 +110,10 @@ def __init__( **kwargs, ): - if backend not in supported_backends: + if not accepted_backends(backend): raise ValueError(f"Unsupported backend: {backend}") - if method not in supported_methods: + if not accepted_methods(method): raise ValueError(f"Unsupported method: {method}") if shots is not None: @@ -104,11 +139,12 @@ def __init__( self._return_tn = kwargs.get("return_tn", None) self._rehearse = kwargs.get("rehearse", None) - self._statetensor = None + self._state = None - if backend == "quimb" and method == "mps": - # TODO: pass the options for MPS to the class - self._statetensor = QuimbMPS(num_wires=self.num_wires, dtype=self._c_dtype) + if self.backend == "quimb" and self.method == "mps": + self._state = QuimbMPS( + num_wires=self.num_wires, dtype=self._c_dtype, **kwargs + ) @property def name(self): @@ -130,6 +166,11 @@ def method(self): """Supported method.""" return self._method + @property + def state(self): + """The state on the device.""" + return self._state + @property def c_dtype(self): """State vector complex data type.""" diff --git a/pennylane_lightning/lightning_tensor/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py similarity index 98% rename from pennylane_lightning/lightning_tensor/_mps.py rename to pennylane_lightning/lightning_tensor/quimb/_mps.py index e8204a1991..b68256de41 100644 --- a/pennylane_lightning/lightning_tensor/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -31,7 +31,7 @@ class QuimbMPS: Interfaces with `quimb` for MPS manipulation. """ - def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): + def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor", **kwargs): if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") From 6395940cd74d18b87181eeeb90658b7a8a1808b7 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Tue, 9 Apr 2024 20:47:43 -0400 Subject: [PATCH 16/88] Perhaps decided the overall structure of `LIghtningTensor` --- .../lightning_tensor/lightning_tensor.py | 15 ++++++++++++--- .../lightning_tensor/quimb/_mps.py | 15 ++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index f1147b298c..140b7215cf 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -126,23 +126,28 @@ def __init__( self._method = method self._c_dtype = c_dtype + # TODO: decide whether to move some of the attributes in interfaces classes + # options for Tensor Network Simulator self._contraction_optimizer = kwargs.get("contraction_optimizer", None) self._local_simplify = kwargs.get("local_simplify", None) self._sample_qubits = kwargs.get("sample_qubits", None) + # options for MPS self._max_bond_dim = kwargs.get("max_bond_dim", None) self._cutoff = kwargs.get("cutoff", 1e-16) self._measure_algorithm = kwargs.get("measure_algorithm", None) + # common options self._apply_reverse_lightcone = kwargs.get("apply_reverse_lightcone", None) self._return_tn = kwargs.get("return_tn", None) self._rehearse = kwargs.get("rehearse", None) - self._state = None + self._interface = None + # TODO: implement the remaining combs of `backend` and `interface` if self.backend == "quimb" and self.method == "mps": - self._state = QuimbMPS( + self._interface = QuimbMPS( num_wires=self.num_wires, dtype=self._c_dtype, **kwargs ) @@ -169,7 +174,7 @@ def method(self): @property def state(self): """The state on the device.""" - return self._state + return self._interface.state @property def c_dtype(self): @@ -178,6 +183,10 @@ def c_dtype(self): dtype = c_dtype + def state_to_array(self, digits: int = 5): + """Copy the state tensor data to a numpy array.""" + return self._interface.state_to_array(digits) + 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_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index b68256de41..b3eb249d69 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -31,7 +31,9 @@ class QuimbMPS: Interfaces with `quimb` for MPS manipulation. """ - def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor", **kwargs): + def __init__( + self, num_wires, dtype=np.complex128, device_name="lightning.tensor", **kwargs + ): if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") @@ -45,7 +47,7 @@ def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor self._dtype = dtype # TODO: allows users to specify initial state - self._mps = qtn.CircuitMPS(psi0=self._set_initial_mps()) + self._circuit = qtn.CircuitMPS(psi0=self._set_initial_mps()) @property def device_name(self): @@ -68,14 +70,13 @@ def dtype(self): return self._dtype @property - def mps(self): + def state(self): """MPS on this device.""" - return self._mps.psi + return self._circuit.psi - @property - def state(self, digits: int = 5): + def state_to_array(self, digits): """Contract the MPS into a dense array.""" - return self._mps.psi.to_dense().round(digits) + return self._circuit.to_dense().round(digits) def _set_initial_mps(self): r""" From 3e89306a78227e5ea378ce10b17833479288b3f1 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 10 Apr 2024 00:48:42 +0000 Subject: [PATCH 17/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index b0da18e929..bdedc820ba 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev24" +__version__ = "0.36.0-dev25" From 9510eb21ded15059714e77cfc2cbdd2b9e8a2d79 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 08:40:21 -0400 Subject: [PATCH 18/88] adding docs to methods --- .../lightning_tensor/lightning_tensor.py | 96 +++++++++++++++++-- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 140b7215cf..046f7cce19 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -35,11 +35,6 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -# TODO: add all docs to class and functions - -# TODO: question: how do we expose methods for qml.expval? - - _backends = frozenset({"quimb", "cutensornet"}) # The set of supported backends. @@ -79,6 +74,18 @@ class LightningTensor(Device): """PennyLane Lightning Tensor device. A device to perform tensor network operations on a quantum circuit. + + Args: + wires (int): The number of wires to initialize the device with. + Defaults to ``None`` if not specified. + backend (str): Supported backend. Must be one of ``quimb`` or ``cutensornet``. + method (str): Supported method. Must be one of ``mps`` or ``tn``. + shots (int): How many times the circuit should be evaluated (or sampled) to estimate + the expectation values. Currently, it can only be ``None``, so that computation of + statistics like expectation values and variances is performed analytically. + c_dtype: Datatypes for statevector representation. Must be one of + ``np.complex64`` or ``np.complex128``. + **kwargs: keyword arguments. """ _device_options = ( @@ -210,8 +217,14 @@ def _setup_execution_config(self, config): 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. + + Args: + execution_config (Union[ExecutionConfig, Sequence[ExecutionConfig]]): A data structure describing the + parameters needed to fully describe the execution. + + Returns: + ... """ config = self._setup_execution_config(execution_config) @@ -223,6 +236,15 @@ def execute( 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: + circuits (Union[QuantumTape, Sequence[QuantumTape]]): the quantum circuits to be executed. + execution_config (ExecutionConfig): a datastructure with additional information required for execution. + + Returns: + TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. + """ pass def supports_derivatives( @@ -230,6 +252,16 @@ def supports_derivatives( 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. + + 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. + + """ pass def compute_derivatives( @@ -237,6 +269,15 @@ def compute_derivatives( 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. + """ pass def execute_and_compute_derivatives( @@ -244,6 +285,15 @@ def execute_and_compute_derivatives( 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. + """ pass def supports_vjp( @@ -251,6 +301,15 @@ def supports_vjp( execution_config: Optional[ExecutionConfig] = None, circuit: Optional[QuantumTape] = None, ) -> bool: + """Whether or not this device defines a custom vector jacobian product. + + 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. + """ pass def compute_vjp( @@ -259,6 +318,18 @@ def compute_vjp( cotangents: Tuple[Number], execution_config: ExecutionConfig = DefaultExecutionConfig, ): + r"""The vector jacobian product used in reverse-mode differentiation. + + 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. + """ pass def execute_and_compute_vjp( @@ -267,4 +338,15 @@ def execute_and_compute_vjp( 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. + 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 + """ pass From a16f7dec11a0adc1f72bed5798499e32f2932406 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 08:48:20 -0400 Subject: [PATCH 19/88] temporary changes so that `pylint` does not complain at this stage --- .../lightning_tensor/lightning_tensor.py | 19 +++++++++++-------- .../lightning_tensor/quimb/_mps.py | 11 ++++++++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 046f7cce19..eb4f7869ae 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -23,7 +23,7 @@ import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig from pennylane.devices.modifiers import simulator_tracking, single_tape_support -from pennylane.tape import QuantumScript, QuantumTape +from pennylane.tape import QuantumTape from pennylane.typing import Result, ResultBatch from .quimb._mps import QuimbMPS @@ -88,6 +88,8 @@ class LightningTensor(Device): **kwargs: keyword arguments. """ + # pylint: disable=too-many-instance-attributes + _device_options = ( "backend", "method", @@ -106,6 +108,7 @@ class LightningTensor(Device): _new_API = True # should `backend` and `method` be keyword args as well? + # pylint: disable=too-many-arguments def __init__( self, *, @@ -245,7 +248,7 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - pass + # TODO: call the function implemented in the appropriate interface def supports_derivatives( self, @@ -262,7 +265,7 @@ def supports_derivatives( Bool: Whether or not a derivative can be calculated provided the given information. """ - pass + # TODO: call the function implemented in the appropriate interface def compute_derivatives( self, @@ -278,7 +281,7 @@ def compute_derivatives( Returns: Tuple: The jacobian for each trainable parameter. """ - pass + # TODO: call the function implemented in the appropriate interface def execute_and_compute_derivatives( self, @@ -294,7 +297,7 @@ def execute_and_compute_derivatives( Returns: tuple: A numeric result of the computation and the gradient. """ - pass + # TODO: call the function implemented in the appropriate interface def supports_vjp( self, @@ -310,7 +313,7 @@ def supports_vjp( Returns: Bool: Whether or not a derivative can be calculated provided the given information. """ - pass + # TODO: call the function implemented in the appropriate interface def compute_vjp( self, @@ -330,7 +333,7 @@ def compute_vjp( Returns: tensor-like: A numeric result of computing the vector jacobian product. """ - pass + # TODO: call the function implemented in the appropriate interface def execute_and_compute_vjp( self, @@ -349,4 +352,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 """ - pass + # TODO: call the function implemented in the appropriate interface diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index b3eb249d69..60717092f9 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -15,11 +15,12 @@ Class implementation for MPS manipulation based on the `quimb` Python package. """ -from typing import Iterable, Union +# from typing import Iterable, Union import quimb.tensor as qtn import numpy as np -import pennylane as qml + +# import pennylane as qml from pennylane import DeviceError from pennylane.wires import Wires @@ -32,7 +33,11 @@ class QuimbMPS: """ def __init__( - self, num_wires, dtype=np.complex128, device_name="lightning.tensor", **kwargs + self, + num_wires, + dtype=np.complex128, + device_name="lightning.tensor", + **kwargs, # pylint: disable=unused-argument ): if dtype not in [np.complex64, np.complex128]: # pragma: no cover From 295aee7c7cacd062da7ebddc3c2b84638a52e79f Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 08:54:52 -0400 Subject: [PATCH 20/88] running `isort` --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 7 ++----- pennylane_lightning/lightning_tensor/quimb/_mps.py | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index eb4f7869ae..67d105dc7c 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -18,7 +18,6 @@ from numbers import Number from typing import Callable, Optional, Sequence, Tuple, Union - import numpy as np import pennylane as qml from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig @@ -28,7 +27,6 @@ from .quimb._mps import QuimbMPS - Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] @@ -88,6 +86,7 @@ class LightningTensor(Device): **kwargs: keyword arguments. """ + # TODO: decide whether to move some of the attributes in interfaces classes # pylint: disable=too-many-instance-attributes _device_options = ( @@ -107,7 +106,7 @@ class LightningTensor(Device): _new_API = True - # should `backend` and `method` be keyword args as well? + # TODO: decide if `backend` and `method` should be keyword args as well # pylint: disable=too-many-arguments def __init__( self, @@ -136,8 +135,6 @@ def __init__( self._method = method self._c_dtype = c_dtype - # TODO: decide whether to move some of the attributes in interfaces classes - # options for Tensor Network Simulator self._contraction_optimizer = kwargs.get("contraction_optimizer", None) self._local_simplify = kwargs.get("local_simplify", None) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 60717092f9..2a2d13ece1 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -15,14 +15,11 @@ Class implementation for MPS manipulation based on the `quimb` Python package. """ +import numpy as np # from typing import Iterable, Union import quimb.tensor as qtn - -import numpy as np - # import pennylane as qml from pennylane import DeviceError - from pennylane.wires import Wires From 710dffd9b7e64ee7ce6047815846751c44a0a53f Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 09:00:15 -0400 Subject: [PATCH 21/88] re-running formatter after `isort` --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 2a2d13ece1..91fcae2c08 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -16,8 +16,10 @@ """ import numpy as np + # from typing import Iterable, Union import quimb.tensor as qtn + # import pennylane as qml from pennylane import DeviceError from pennylane.wires import Wires From 870e0e4a892f4fbaab68923252b544c4e89ff2f1 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 09:03:56 -0400 Subject: [PATCH 22/88] re-running formatter after `isort` --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 91fcae2c08..095261bc1f 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -16,11 +16,7 @@ """ import numpy as np - -# from typing import Iterable, Union import quimb.tensor as qtn - -# import pennylane as qml from pennylane import DeviceError from pennylane.wires import Wires From 34b23a9c0bc5ac714aa4cfdc45948fbf37b27054 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 09:13:14 -0400 Subject: [PATCH 23/88] Applying suggested formatting change from CI --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 67d105dc7c..91922d0a18 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -154,9 +154,7 @@ def __init__( # TODO: implement the remaining combs of `backend` and `interface` if self.backend == "quimb" and self.method == "mps": - self._interface = QuimbMPS( - num_wires=self.num_wires, dtype=self._c_dtype, **kwargs - ) + self._interface = QuimbMPS(num_wires=self.num_wires, dtype=self._c_dtype, **kwargs) @property def name(self): From f5eb35a1da4c9be20dd323ddb2cc94e20599713a Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 11:18:54 -0400 Subject: [PATCH 24/88] adding tmp unit tests --- tests/lightning_tensor/__init__.py | 0 .../lightning_tensor/test_lightning_tensor.py | 55 +++++++++++++++++++ tests/lightning_tensor/test_quimb_mps.py | 43 +++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 tests/lightning_tensor/__init__.py create mode 100644 tests/lightning_tensor/test_lightning_tensor.py create mode 100644 tests/lightning_tensor/test_quimb_mps.py diff --git a/tests/lightning_tensor/__init__.py b/tests/lightning_tensor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py new file mode 100644 index 0000000000..b2f0a97029 --- /dev/null +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -0,0 +1,55 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the generic lightning tensor class. +""" + + +import numpy as np +import pytest +from conftest import LightningDevice # tested device +from pennylane.wires import Wires + +from pennylane_lightning.lightning_tensor import LightningTensor + +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +@pytest.mark.parametrize("num_wires", [None, 4]) +@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) +@pytest.mark.parametrize("device_name", ["lightning.tensor"]) +def test_device_name_and_init(num_wires, c_dtype, device_name): + """Test the class initialization and returned properties.""" + wires = Wires(range(num_wires)) if num_wires else None + dev = LightningTensor(wires=wires, c_dtype=c_dtype) + assert dev.name == device_name + assert dev.c_dtype == c_dtype + assert dev.wires == wires + + +# def test_wrong_device_name(): +# """Test an invalid device name""" +# with pytest.raises(qml.DeviceError, match="The device name"): +# LightningTensor(3, device_name="thunder.tensor") + + +# @pytest.mark.parametrize("dtype", [np.double]) +# def test_wrong_dtype(dtype): +# """Test if the class returns a TypeError for a wrong dtype""" +# with pytest.raises(TypeError, match="Unsupported complex type:"): +# assert LightningTensor(wires=3, dtype=dtype) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py new file mode 100644 index 0000000000..ddaf02a46e --- /dev/null +++ b/tests/lightning_tensor/test_quimb_mps.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. +""" +Unit tests for the ``quimb`` interface. +""" + + +import numpy as np +import pytest +from conftest import LightningDevice # tested device +import quimb.tensor as qtn +from pennylane.wires import Wires + +from pennylane_lightning.lightning_tensor import LightningTensor + +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +@pytest.mark.parametrize("num_wires", [None, 4]) +@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) +@pytest.mark.parametrize("backend", ["quimb"]) +@pytest.mark.parametrize("method", ["mps"]) +def test_device_init(num_wires, c_dtype, backend, method): + """Test the class initialization and returned properties.""" + wires = Wires(range(num_wires)) if num_wires else None + dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) + assert isinstance(dev.state, qtn.MatrixProductState) + assert isinstance(dev.state_to_array(), np.ndarray) From 95839b9a5eeed1021764a5f3b67c4d5774a5cc3d Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 12:31:47 -0400 Subject: [PATCH 25/88] Adding `quimb` in `requirements.txt` --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 6df7868929..51a440262d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ pytest~=8.0.0 pytest-cov pytest-mock scipy +quimb==1.7.3 pytest-xdist From 72a54d46393cc775eafb6fe343819393b4383524 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 12:35:17 -0400 Subject: [PATCH 26/88] runing `isort` on mps test --- tests/lightning_tensor/test_quimb_mps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index ddaf02a46e..8895057501 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -18,8 +18,8 @@ import numpy as np import pytest -from conftest import LightningDevice # tested device import quimb.tensor as qtn +from conftest import LightningDevice # tested device from pennylane.wires import Wires from pennylane_lightning.lightning_tensor import LightningTensor From a23559242edb194316e66e4a9c73c6aa771a0e87 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 13:27:32 -0400 Subject: [PATCH 27/88] removing `quimb` from requirement and deleting unit tests for `lightning.tensor` --- requirements.txt | 1 - tests/lightning_tensor/__init__.py | 0 .../lightning_tensor/test_lightning_tensor.py | 55 ------------------- tests/lightning_tensor/test_quimb_mps.py | 43 --------------- 4 files changed, 99 deletions(-) delete mode 100644 tests/lightning_tensor/__init__.py delete mode 100644 tests/lightning_tensor/test_lightning_tensor.py delete mode 100644 tests/lightning_tensor/test_quimb_mps.py diff --git a/requirements.txt b/requirements.txt index 51a440262d..6df7868929 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,4 @@ pytest~=8.0.0 pytest-cov pytest-mock scipy -quimb==1.7.3 pytest-xdist diff --git a/tests/lightning_tensor/__init__.py b/tests/lightning_tensor/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py deleted file mode 100644 index b2f0a97029..0000000000 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the generic lightning tensor class. -""" - - -import numpy as np -import pytest -from conftest import LightningDevice # tested device -from pennylane.wires import Wires - -from pennylane_lightning.lightning_tensor import LightningTensor - -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - - -@pytest.mark.parametrize("num_wires", [None, 4]) -@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) -@pytest.mark.parametrize("device_name", ["lightning.tensor"]) -def test_device_name_and_init(num_wires, c_dtype, device_name): - """Test the class initialization and returned properties.""" - wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor(wires=wires, c_dtype=c_dtype) - assert dev.name == device_name - assert dev.c_dtype == c_dtype - assert dev.wires == wires - - -# def test_wrong_device_name(): -# """Test an invalid device name""" -# with pytest.raises(qml.DeviceError, match="The device name"): -# LightningTensor(3, device_name="thunder.tensor") - - -# @pytest.mark.parametrize("dtype", [np.double]) -# def test_wrong_dtype(dtype): -# """Test if the class returns a TypeError for a wrong dtype""" -# with pytest.raises(TypeError, match="Unsupported complex type:"): -# assert LightningTensor(wires=3, dtype=dtype) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py deleted file mode 100644 index 8895057501..0000000000 --- a/tests/lightning_tensor/test_quimb_mps.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the ``quimb`` interface. -""" - - -import numpy as np -import pytest -import quimb.tensor as qtn -from conftest import LightningDevice # tested device -from pennylane.wires import Wires - -from pennylane_lightning.lightning_tensor import LightningTensor - -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - - -@pytest.mark.parametrize("num_wires", [None, 4]) -@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) -@pytest.mark.parametrize("backend", ["quimb"]) -@pytest.mark.parametrize("method", ["mps"]) -def test_device_init(num_wires, c_dtype, backend, method): - """Test the class initialization and returned properties.""" - wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) - assert isinstance(dev.state, qtn.MatrixProductState) - assert isinstance(dev.state_to_array(), np.ndarray) From 4139412a954cde0179e9e793ebc354342a66698e Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 10 Apr 2024 19:50:31 +0000 Subject: [PATCH 28/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index bdedc820ba..3180e9d655 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev25" +__version__ = "0.36.0-dev26" From b0a55b3c5d87a0289637c2cbea16d671cb18ea44 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:03:57 -0400 Subject: [PATCH 29/88] re-inserting unit tests with an additional `yml` file --- .github/workflows/tests_lightning_tensor.yml | 136 ++++++++++++++++++ tests/lightning_tensor/__init__.py | 0 .../lightning_tensor/test_lightning_tensor.py | 55 +++++++ tests/lightning_tensor/test_quimb_mps.py | 43 ++++++ 4 files changed, 234 insertions(+) create mode 100644 .github/workflows/tests_lightning_tensor.yml create mode 100644 tests/lightning_tensor/__init__.py create mode 100644 tests/lightning_tensor/test_lightning_tensor.py create mode 100644 tests/lightning_tensor/test_quimb_mps.py diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml new file mode 100644 index 0000000000..115fc300b9 --- /dev/null +++ b/.github/workflows/tests_lightning_tensor.yml @@ -0,0 +1,136 @@ +name: Testing (Linux) +on: + workflow_call: + inputs: + lightning-version: + type: string + required: true + description: The version of Lightning to use. Valid values are either 'release' (most recent release candidate), 'stable' (most recent git-tag) or 'latest' (most recent commit from master) + pennylane-version: + type: string + required: true + description: The version of PennyLane to use. Valid values are either 'release' (most recent release candidate), 'stable' (most recent git-tag) or 'latest' (most recent commit from master) + pull_request: + push: + branches: + - master + +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" + GCC_VERSION: 11 + OMP_NUM_THREADS: "2" + +concurrency: + group: tests_linux-${{ github.ref }}-${{ inputs.lightning-version }}-${{ inputs.pennylane-version }} + cancel-in-progress: true + +jobs: + + pythontests: + strategy: + matrix: + os: [ubuntu-22.04] + pl_backend: ["lightning_qubit"] + timeout-minutes: 60 + name: Python tests + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/setup-python@v4 + name: Install Python + with: + python-version: '3.9' + + - name: Checkout PennyLane-Lightning + uses: actions/checkout@v4 + with: + fetch-tags: true + path: main + + - name: Switch to release build of Lightning + if: inputs.lightning-version == 'release' + run: | + cd main + git fetch --all + git checkout $(git branch -a --list "origin/v*rc*" | tail -1) + + - name: Switch to stable build of Lightning + if: inputs.lightning-version == 'stable' + run: | + cd main + git fetch --tags --force + git checkout $(git tag | sort -V | tail -1) + git log -1 --format='%H' + git status + + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: '3.9' + + - name: Install dependencies + run: | + sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION + python -m pip install scipy + python -m pip install quimb + + - name: Get required Python packages + run: | + cd main + python -m pip install -r requirements-dev.txt + python -m pip install openfermionpyscf + + - name: Checkout PennyLane for release build + if: inputs.pennylane-version == 'release' + uses: actions/checkout@v4 + with: + path: pennylane + repository: PennyLaneAI/pennylane + + - name: Switch to release build of PennyLane + if: inputs.pennylane-version == 'release' + run: | + cd pennylane + git fetch --all + git checkout $(git branch -a --list "origin/v*rc*" | tail -1) + python -m pip uninstall -y pennylane && python -m pip install . -vv --no-deps + + - name: Install Stable PennyLane + if: inputs.pennylane-version == 'stable' + run: | + cd main + python -m pip uninstall -y pennylane && python -m pip install -U pennylane + + - name: Install ML libraries for interfaces + run: | + python -m pip install --upgrade torch==$TORCH_VERSION -f https://download.pytorch.org/whl/cpu/torch_stable.html + python -m pip install --upgrade "jax[cpu]" # This also installs jaxlib + python -m pip install --upgrade tensorflow~=$TF_VERSION keras~=$TF_VERSION + + - name: Install backend device + run: | + cd main + CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ + python -m pip install -e . -vv + + - name: Run PennyLane-Lightning unit tests + run: | + # TODO: Remove installing pytest-xdist with release v0.36.0 + python -m pip install pytest-xdist + cd main/ + DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` + OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS + PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct" $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 }} + + - name: Upload code coverage results + uses: actions/upload-artifact@v3 + with: + name: ubuntu-codecov-results-python + path: | + ./main/.coverage-${{ github.job }}-${{ matrix.pl_backend }} + if-no-files-found: error \ No newline at end of file diff --git a/tests/lightning_tensor/__init__.py b/tests/lightning_tensor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py new file mode 100644 index 0000000000..b2f0a97029 --- /dev/null +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -0,0 +1,55 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the generic lightning tensor class. +""" + + +import numpy as np +import pytest +from conftest import LightningDevice # tested device +from pennylane.wires import Wires + +from pennylane_lightning.lightning_tensor import LightningTensor + +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +@pytest.mark.parametrize("num_wires", [None, 4]) +@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) +@pytest.mark.parametrize("device_name", ["lightning.tensor"]) +def test_device_name_and_init(num_wires, c_dtype, device_name): + """Test the class initialization and returned properties.""" + wires = Wires(range(num_wires)) if num_wires else None + dev = LightningTensor(wires=wires, c_dtype=c_dtype) + assert dev.name == device_name + assert dev.c_dtype == c_dtype + assert dev.wires == wires + + +# def test_wrong_device_name(): +# """Test an invalid device name""" +# with pytest.raises(qml.DeviceError, match="The device name"): +# LightningTensor(3, device_name="thunder.tensor") + + +# @pytest.mark.parametrize("dtype", [np.double]) +# def test_wrong_dtype(dtype): +# """Test if the class returns a TypeError for a wrong dtype""" +# with pytest.raises(TypeError, match="Unsupported complex type:"): +# assert LightningTensor(wires=3, dtype=dtype) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py new file mode 100644 index 0000000000..ddaf02a46e --- /dev/null +++ b/tests/lightning_tensor/test_quimb_mps.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. +""" +Unit tests for the ``quimb`` interface. +""" + + +import numpy as np +import pytest +from conftest import LightningDevice # tested device +import quimb.tensor as qtn +from pennylane.wires import Wires + +from pennylane_lightning.lightning_tensor import LightningTensor + +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + +if not LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("No binary module found. Skipping.", allow_module_level=True) + + +@pytest.mark.parametrize("num_wires", [None, 4]) +@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) +@pytest.mark.parametrize("backend", ["quimb"]) +@pytest.mark.parametrize("method", ["mps"]) +def test_device_init(num_wires, c_dtype, backend, method): + """Test the class initialization and returned properties.""" + wires = Wires(range(num_wires)) if num_wires else None + dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) + assert isinstance(dev.state, qtn.MatrixProductState) + assert isinstance(dev.state_to_array(), np.ndarray) From ed1ba39afb3c2803b2032aec30984e606da3fd92 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:07:03 -0400 Subject: [PATCH 30/88] running isort on quimb test --- tests/lightning_tensor/test_quimb_mps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index ddaf02a46e..8895057501 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -18,8 +18,8 @@ import numpy as np import pytest -from conftest import LightningDevice # tested device import quimb.tensor as qtn +from conftest import LightningDevice # tested device from pennylane.wires import Wires from pennylane_lightning.lightning_tensor import LightningTensor From 6da1c944e408dec28fbf6f9204ec22e781d4855b Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:20:20 -0400 Subject: [PATCH 31/88] changing name of yml file --- .github/workflows/tests_lightning_tensor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml index 115fc300b9..43e7cf48a4 100644 --- a/.github/workflows/tests_lightning_tensor.yml +++ b/.github/workflows/tests_lightning_tensor.yml @@ -1,4 +1,4 @@ -name: Testing (Linux) +name: Testing Lightning Tensor (Linux) on: workflow_call: inputs: From 54d430de4f3c3e5b925fc60a4b8599d6e928c8c2 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:24:33 -0400 Subject: [PATCH 32/88] preventing error in import --- tests/lightning_tensor/test_quimb_mps.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 8895057501..3564beb2f3 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -15,10 +15,13 @@ Unit tests for the ``quimb`` interface. """ +try: + import quimb.tensor as qtn +except ImportError as e: + pytest.skip("No quimb package.", allow_module_level=True) import numpy as np import pytest -import quimb.tensor as qtn from conftest import LightningDevice # tested device from pennylane.wires import Wires From 943af7b08ffd475774004de02e4c086f883d0e98 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:31:47 -0400 Subject: [PATCH 33/88] updating yml file --- .github/workflows/tests_lightning_tensor.yml | 17 +++++++++-------- tests/lightning_tensor/test_quimb_mps.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml index 43e7cf48a4..6eda0dbd65 100644 --- a/.github/workflows/tests_lightning_tensor.yml +++ b/.github/workflows/tests_lightning_tensor.yml @@ -32,7 +32,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - pl_backend: ["lightning_qubit"] + pl_backend: ["lightning_tensor"] timeout-minutes: 60 name: Python tests runs-on: ${{ matrix.os }} @@ -74,13 +74,13 @@ jobs: run: | sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION python -m pip install scipy - python -m pip install quimb - name: Get required Python packages run: | cd main python -m pip install -r requirements-dev.txt python -m pip install openfermionpyscf + python -m pip install quimb - name: Checkout PennyLane for release build if: inputs.pennylane-version == 'release' @@ -112,7 +112,7 @@ jobs: - name: Install backend device run: | cd main - CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ + # CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ python -m pip install -e . -vv - name: Run PennyLane-Lightning unit tests @@ -120,11 +120,12 @@ jobs: # TODO: Remove installing pytest-xdist with release v0.36.0 python -m pip install pytest-xdist cd main/ - DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS - PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct" $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 + # DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` + # OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS + # PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct" $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 + OMP_NUM_THREADS=1 python -m pytest -n auto tests/lightning_tensor/ $COVERAGE_FLAGS mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} - name: Upload code coverage results diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 3564beb2f3..56eea68a3c 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -17,7 +17,7 @@ try: import quimb.tensor as qtn -except ImportError as e: +except ModuleNotFoundError as e: pytest.skip("No quimb package.", allow_module_level=True) import numpy as np From 9c6d5e64ff15cafb7e3115a47c60219970873eb8 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:45:04 -0400 Subject: [PATCH 34/88] inserting `quimb` package in requirements-dev --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index c87c9154a4..9cf81969f1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,6 +14,7 @@ clang-tidy~=16.0 clang-format~=16.0 isort==5.13.2 click==8.0.4 +quimb==1.7.3 cmake custatevec-cu12 pylint From a7e7327ba968a2b79dfa6075b63134c1c865f25e Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 16:51:34 -0400 Subject: [PATCH 35/88] strange error with `quimb` --- requirements-dev.txt | 2 +- tests/lightning_tensor/test_quimb_mps.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9cf81969f1..a1b4878da4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,8 +14,8 @@ clang-tidy~=16.0 clang-format~=16.0 isort==5.13.2 click==8.0.4 -quimb==1.7.3 cmake custatevec-cu12 pylint scipy +quimb diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 56eea68a3c..8895057501 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -15,13 +15,10 @@ Unit tests for the ``quimb`` interface. """ -try: - import quimb.tensor as qtn -except ModuleNotFoundError as e: - pytest.skip("No quimb package.", allow_module_level=True) import numpy as np import pytest +import quimb.tensor as qtn from conftest import LightningDevice # tested device from pennylane.wires import Wires From 73428a6b84692a6e9ed739991b573fa52fe061f7 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 17:09:38 -0400 Subject: [PATCH 36/88] strange error with `quimb` --- .github/workflows/tests_lightning_tensor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml index 6eda0dbd65..191d18590c 100644 --- a/.github/workflows/tests_lightning_tensor.yml +++ b/.github/workflows/tests_lightning_tensor.yml @@ -80,7 +80,6 @@ jobs: cd main python -m pip install -r requirements-dev.txt python -m pip install openfermionpyscf - python -m pip install quimb - name: Checkout PennyLane for release build if: inputs.pennylane-version == 'release' @@ -113,7 +112,7 @@ jobs: run: | cd main # CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ - python -m pip install -e . -vv + # python -m pip install -e . -vv - name: Run PennyLane-Lightning unit tests run: | From 972944079a65f0a9c468b4d517e9d13fe306adf1 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 17:17:11 -0400 Subject: [PATCH 37/88] specifying scipy version --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a1b4878da4..711b5bd5bb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,5 +17,5 @@ click==8.0.4 cmake custatevec-cu12 pylint -scipy -quimb +scipy==1.12.0 +quimb==1.7.3 From 1d0bce759224d88c1fcc213b98ade6c071d06327 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 17:17:55 -0400 Subject: [PATCH 38/88] removing installation of scipy from yml file --- .github/workflows/tests_lightning_tensor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml index 191d18590c..19a08d67cd 100644 --- a/.github/workflows/tests_lightning_tensor.yml +++ b/.github/workflows/tests_lightning_tensor.yml @@ -73,7 +73,6 @@ jobs: - name: Install dependencies run: | sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION - python -m pip install scipy - name: Get required Python packages run: | From 2a4b1cd06ca0fef4db998795422a1de421647e4d Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 17:53:10 -0400 Subject: [PATCH 39/88] removing the new `yml` file --- .github/workflows/tests_lightning_tensor.yml | 135 ------------------- 1 file changed, 135 deletions(-) delete mode 100644 .github/workflows/tests_lightning_tensor.yml diff --git a/.github/workflows/tests_lightning_tensor.yml b/.github/workflows/tests_lightning_tensor.yml deleted file mode 100644 index 19a08d67cd..0000000000 --- a/.github/workflows/tests_lightning_tensor.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: Testing Lightning Tensor (Linux) -on: - workflow_call: - inputs: - lightning-version: - type: string - required: true - description: The version of Lightning to use. Valid values are either 'release' (most recent release candidate), 'stable' (most recent git-tag) or 'latest' (most recent commit from master) - pennylane-version: - type: string - required: true - description: The version of PennyLane to use. Valid values are either 'release' (most recent release candidate), 'stable' (most recent git-tag) or 'latest' (most recent commit from master) - pull_request: - push: - branches: - - master - -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" - GCC_VERSION: 11 - OMP_NUM_THREADS: "2" - -concurrency: - group: tests_linux-${{ github.ref }}-${{ inputs.lightning-version }}-${{ inputs.pennylane-version }} - cancel-in-progress: true - -jobs: - - pythontests: - strategy: - matrix: - os: [ubuntu-22.04] - pl_backend: ["lightning_tensor"] - timeout-minutes: 60 - name: Python tests - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: '3.9' - - - name: Checkout PennyLane-Lightning - uses: actions/checkout@v4 - with: - fetch-tags: true - path: main - - - name: Switch to release build of Lightning - if: inputs.lightning-version == 'release' - run: | - cd main - git fetch --all - git checkout $(git branch -a --list "origin/v*rc*" | tail -1) - - - name: Switch to stable build of Lightning - if: inputs.lightning-version == 'stable' - run: | - cd main - git fetch --tags --force - git checkout $(git tag | sort -V | tail -1) - git log -1 --format='%H' - git status - - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.9' - - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get -y -q install cmake gcc-$GCC_VERSION g++-$GCC_VERSION - - - name: Get required Python packages - run: | - cd main - python -m pip install -r requirements-dev.txt - python -m pip install openfermionpyscf - - - name: Checkout PennyLane for release build - if: inputs.pennylane-version == 'release' - uses: actions/checkout@v4 - with: - path: pennylane - repository: PennyLaneAI/pennylane - - - name: Switch to release build of PennyLane - if: inputs.pennylane-version == 'release' - run: | - cd pennylane - git fetch --all - git checkout $(git branch -a --list "origin/v*rc*" | tail -1) - python -m pip uninstall -y pennylane && python -m pip install . -vv --no-deps - - - name: Install Stable PennyLane - if: inputs.pennylane-version == 'stable' - run: | - cd main - python -m pip uninstall -y pennylane && python -m pip install -U pennylane - - - name: Install ML libraries for interfaces - run: | - python -m pip install --upgrade torch==$TORCH_VERSION -f https://download.pytorch.org/whl/cpu/torch_stable.html - python -m pip install --upgrade "jax[cpu]" # This also installs jaxlib - python -m pip install --upgrade tensorflow~=$TF_VERSION keras~=$TF_VERSION - - - name: Install backend device - run: | - cd main - # CMAKE_ARGS="-DPL_BACKEND=${{ matrix.pl_backend }} -DLQ_ENABLE_KERNEL_OMP=ON -DENABLE_PYTHON=ON -DCMAKE_CXX_COMPILER=$(which g++-$GCC_VERSION)" \ - # python -m pip install -e . -vv - - - name: Run PennyLane-Lightning unit tests - run: | - # TODO: Remove installing pytest-xdist with release v0.36.0 - python -m pip install pytest-xdist - cd main/ - # DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - # OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS - # PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct" $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 - OMP_NUM_THREADS=1 python -m pytest -n auto tests/lightning_tensor/ $COVERAGE_FLAGS - mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} - - - name: Upload code coverage results - uses: actions/upload-artifact@v3 - with: - name: ubuntu-codecov-results-python - path: | - ./main/.coverage-${{ github.job }}-${{ matrix.pl_backend }} - if-no-files-found: error \ No newline at end of file From 706dc93f1e8c40169f3c2dfc9388d9712e4f356c Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 18:15:58 -0400 Subject: [PATCH 40/88] testing if tests are tested --- tests/lightning_tensor/test_lightning_tensor.py | 6 ------ tests/lightning_tensor/test_quimb_mps.py | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index b2f0a97029..48ce70156a 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -23,12 +23,6 @@ from pennylane_lightning.lightning_tensor import LightningTensor -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) - @pytest.mark.parametrize("num_wires", [None, 4]) @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 8895057501..450f5a6829 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,11 +24,11 @@ from pennylane_lightning.lightning_tensor import LightningTensor -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) +# if not LightningDevice._new_API: +# pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) -if not LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("No binary module found. Skipping.", allow_module_level=True) +# if not LightningDevice._CPP_BINARY_AVAILABLE: +# pytest.skip("No binary module found. Skipping.", allow_module_level=True) @pytest.mark.parametrize("num_wires", [None, 4]) From b5c0a63655cb9848c7a1fb151be5a0d32ede77b3 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:03:07 -0400 Subject: [PATCH 41/88] Covering all lines in tests --- .../lightning_tensor/lightning_tensor.py | 19 ++++------- .../lightning_tensor/quimb/_mps.py | 20 ----------- .../lightning_tensor/test_lightning_tensor.py | 34 ++++++++++++++----- tests/lightning_tensor/test_quimb_mps.py | 6 ---- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 91922d0a18..c079065faf 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -39,10 +39,13 @@ _methods = frozenset({"mps", "tn"}) # The set of supported methods. -_operations = frozenset({}) +# TODO: understand if supporting all operations and observables is feasible for the first release +# I comment the following lines since otherwise Codecov complaints + +# _operations = frozenset({}) # The set of supported operations. -_observables = frozenset({}) +# _observables = frozenset({}) # The set of supported observables. @@ -56,16 +59,6 @@ def accepted_methods(method: str) -> bool: return method in _methods -def accepted_operations(op: qml.operation.Operator) -> bool: - """A function that determines whether or not an operation is supported by ``lightning.tensor``.""" - return op.name in _operations - - -def accepted_observables(obs: qml.operation.Operator) -> bool: - """A function that determines whether or not an observable is supported by ``lightning.tensor``.""" - return obs.name in _observables - - @simulator_tracking @single_tape_support class LightningTensor(Device): @@ -154,7 +147,7 @@ def __init__( # TODO: implement the remaining combs of `backend` and `interface` if self.backend == "quimb" and self.method == "mps": - self._interface = QuimbMPS(num_wires=self.num_wires, dtype=self._c_dtype, **kwargs) + self._interface = QuimbMPS(self.num_wires, self._c_dtype, **kwargs) @property def name(self): diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 095261bc1f..173fb44fba 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -49,26 +49,6 @@ def __init__( # TODO: allows users to specify initial state self._circuit = qtn.CircuitMPS(psi0=self._set_initial_mps()) - @property - def device_name(self): - """Returns the device name.""" - return self._device_name - - @property - def num_wires(self): - """Number of wires addressed on this device.""" - return self._num_wires - - @property - def wires(self): - """All wires that can be addressed on this device.""" - return self._wires - - @property - def dtype(self): - """Returns the mps data type.""" - return self._dtype - @property def state(self): """MPS on this device.""" diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 48ce70156a..3954d5b02b 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -17,6 +17,7 @@ import numpy as np +import pennylane as qml import pytest from conftest import LightningDevice # tested device from pennylane.wires import Wires @@ -36,14 +37,29 @@ def test_device_name_and_init(num_wires, c_dtype, device_name): assert dev.wires == wires -# def test_wrong_device_name(): -# """Test an invalid device name""" -# with pytest.raises(qml.DeviceError, match="The device name"): -# LightningTensor(3, device_name="thunder.tensor") +def test_wrong_device_name(): + """Test an invalid device name""" + with pytest.raises(qml.DeviceError, match="The device name"): + LightningTensor(device_name="thunder.tensor") -# @pytest.mark.parametrize("dtype", [np.double]) -# def test_wrong_dtype(dtype): -# """Test if the class returns a TypeError for a wrong dtype""" -# with pytest.raises(TypeError, match="Unsupported complex type:"): -# assert LightningTensor(wires=3, dtype=dtype) +@pytest.mark.parametrize("backend", ["fake_backend"]) +def test_invalid_backend(backend): + """Test an invalid backend.""" + with pytest.raises(ValueError, match=f"Unsupported backend: {backend}"): + LightningTensor(backend=backend) + + +@pytest.mark.parametrize("method", ["fake_method"]) +def test_invalid_method(method): + """Test an invalid method.""" + with pytest.raises(ValueError, match=f"Unsupported method: {method}"): + LightningTensor(method=method) + + +def test_invalid_shots(): + """Test that an error is raised if finite number of shots are requestd.""" + with pytest.raises( + ValueError, match="LightningTensor does not support the `shots` parameter." + ): + LightningTensor(shots=5) \ No newline at end of file diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 450f5a6829..3dcae6c65f 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,12 +24,6 @@ from pennylane_lightning.lightning_tensor import LightningTensor -# if not LightningDevice._new_API: -# pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -# if not LightningDevice._CPP_BINARY_AVAILABLE: -# pytest.skip("No binary module found. Skipping.", allow_module_level=True) - @pytest.mark.parametrize("num_wires", [None, 4]) @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) From 50928ad367de597c843ba2aa77b74ac0706e0ccc Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:05:52 -0400 Subject: [PATCH 42/88] forgot final line for formatter --- tests/lightning_tensor/test_lightning_tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 3954d5b02b..b53b8d63a2 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -62,4 +62,4 @@ def test_invalid_shots(): with pytest.raises( ValueError, match="LightningTensor does not support the `shots` parameter." ): - LightningTensor(shots=5) \ No newline at end of file + LightningTensor(shots=5) From 1ed59a311db3a0445ea527c27397021c54e6fad2 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:09:26 -0400 Subject: [PATCH 43/88] Python formatter on CI complaints --- tests/lightning_tensor/test_lightning_tensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index b53b8d63a2..2a91690fef 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -59,7 +59,5 @@ def test_invalid_method(method): def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises( - ValueError, match="LightningTensor does not support the `shots` parameter." - ): + with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): LightningTensor(shots=5) From 8108cfd9071d88103f55ebc3ebf961cedafac8da Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:32:58 -0400 Subject: [PATCH 44/88] covering missing lines --- .../lightning_tensor/test_lightning_tensor.py | 3 ++ tests/lightning_tensor/test_quimb_mps.py | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 2a91690fef..8540262fb0 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -24,6 +24,9 @@ from pennylane_lightning.lightning_tensor import LightningTensor +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) + @pytest.mark.parametrize("num_wires", [None, 4]) @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 3dcae6c65f..557c5acda9 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,14 +24,27 @@ from pennylane_lightning.lightning_tensor import LightningTensor +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) + -@pytest.mark.parametrize("num_wires", [None, 4]) -@pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) @pytest.mark.parametrize("backend", ["quimb"]) @pytest.mark.parametrize("method", ["mps"]) -def test_device_init(num_wires, c_dtype, backend, method): - """Test the class initialization and returned properties.""" - wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) - assert isinstance(dev.state, qtn.MatrixProductState) - assert isinstance(dev.state_to_array(), np.ndarray) +class QuimbMPS: + """Tests for the MPS method.""" + + @pytest.mark.parametrize("num_wires", [None, 4]) + @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) + def test_device_init(num_wires, c_dtype, backend, method): + """Test the class initialization and returned properties.""" + + wires = Wires(range(num_wires)) if num_wires else None + dev = LightningTensor( + wires=wires, backend=backend, method=method, c_dtype=c_dtype + ) + assert isinstance(dev.state, qtn.MatrixProductState) + assert isinstance(dev.state_to_array(), np.ndarray) + + config = dev.preprocess() + assert config.device_options["backend"] == backend + assert config.device_options["method"] == method From c9c3cb275196a7c2382e9df2f6d7e3864d8cc04d Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:34:46 -0400 Subject: [PATCH 45/88] formatter on CI complaints --- tests/lightning_tensor/test_quimb_mps.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 557c5acda9..581049541b 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -39,9 +39,7 @@ def test_device_init(num_wires, c_dtype, backend, method): """Test the class initialization and returned properties.""" wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor( - wires=wires, backend=backend, method=method, c_dtype=c_dtype - ) + dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) assert isinstance(dev.state, qtn.MatrixProductState) assert isinstance(dev.state_to_array(), np.ndarray) From 69f9ce01408a46c06152fcff50aa380400b496df Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 19:55:44 -0400 Subject: [PATCH 46/88] Trying not to skip test if Cpp is enabled --- tests/lightning_tensor/test_lightning_tensor.py | 4 ++-- tests/lightning_tensor/test_quimb_mps.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 8540262fb0..10c629191a 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -24,8 +24,8 @@ from pennylane_lightning.lightning_tensor import LightningTensor -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +# if LightningDevice._CPP_BINARY_AVAILABLE: +# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("num_wires", [None, 4]) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 581049541b..ca8781f1e9 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,8 +24,8 @@ from pennylane_lightning.lightning_tensor import LightningTensor -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +# if LightningDevice._CPP_BINARY_AVAILABLE: +# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("backend", ["quimb"]) From 159418b482e8ecc995d9f091cda863c21b0b9950 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 20:19:51 -0400 Subject: [PATCH 47/88] skipping tests if Cpp is enabled --- tests/lightning_tensor/test_lightning_tensor.py | 4 ++-- tests/lightning_tensor/test_quimb_mps.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 10c629191a..8540262fb0 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -24,8 +24,8 @@ from pennylane_lightning.lightning_tensor import LightningTensor -# if LightningDevice._CPP_BINARY_AVAILABLE: -# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("num_wires", [None, 4]) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index ca8781f1e9..42b2c666dd 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,18 +24,18 @@ from pennylane_lightning.lightning_tensor import LightningTensor -# if LightningDevice._CPP_BINARY_AVAILABLE: -# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("backend", ["quimb"]) @pytest.mark.parametrize("method", ["mps"]) -class QuimbMPS: +class TestQuimbMPS: """Tests for the MPS method.""" @pytest.mark.parametrize("num_wires", [None, 4]) @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) - def test_device_init(num_wires, c_dtype, backend, method): + def test_device_init(self, num_wires, c_dtype, backend, method): """Test the class initialization and returned properties.""" wires = Wires(range(num_wires)) if num_wires else None From 8789d5c983f3246e41253caa625dd4ae96ae92ef Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 10 Apr 2024 20:38:48 -0400 Subject: [PATCH 48/88] removing the only line not covered by tests so far --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index c079065faf..890ca58009 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -190,8 +190,6 @@ 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", From 2df5486a1bb2f767e50999f68d690cb71b279c6f Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 12 Apr 2024 12:50:43 +0000 Subject: [PATCH 49/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 3180e9d655..9be711cbdb 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev26" +__version__ = "0.36.0-dev27" From b470af1aee12bc77318b97523382bd73b382ec94 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 17:58:51 -0400 Subject: [PATCH 50/88] Applying suggestions from code review and making the `state` attribute private (new API design) --- .github/CHANGELOG.md | 3 ++ .../lightning_tensor/lightning_tensor.py | 39 ++++--------------- .../lightning_tensor/quimb/_mps.py | 21 ++++++++-- .../lightning_tensor/test_lightning_tensor.py | 5 +-- tests/lightning_tensor/test_quimb_mps.py | 4 +- 5 files changed, 32 insertions(+), 40 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 7a5ab5a78e..86891baf6f 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,9 @@ ### New features since last release +* Add Python class for the `lightning.tensor` device which uses the new device API. + [(#671)](https://github.com/PennyLaneAI/pennylane-lightning/pull/671) + * Add dynamic linking to LAPACK/OpenBlas shared objects in scipy.libs for both C++ and Python layer. [(#653)](https://github.com/PennyLaneAI/pennylane-lightning/pull/651) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 890ca58009..1501bdb702 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -33,21 +33,12 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -_backends = frozenset({"quimb", "cutensornet"}) +_backends = frozenset({"quimb"}) # The set of supported backends. -_methods = frozenset({"mps", "tn"}) +_methods = frozenset({"mps"}) # The set of supported methods. -# TODO: understand if supporting all operations and observables is feasible for the first release -# I comment the following lines since otherwise Codecov complaints - -# _operations = frozenset({}) -# The set of supported operations. - -# _observables = frozenset({}) -# The set of supported observables. - def accepted_backends(backend: str) -> bool: """A function that determines whether or not a backend is supported by ``lightning.tensor``.""" @@ -82,17 +73,15 @@ class LightningTensor(Device): # TODO: decide whether to move some of the attributes in interfaces classes # pylint: disable=too-many-instance-attributes + # So far we just insert the options for MPS simulator _device_options = ( + "apply_reverse_lightcone", "backend", - "method", "c_dtype", - "contraction_optimizer", - "local_simplify", - "sample_qubits", - "max_bond_dim", "cutoff", + "method", + "max_bond_dim", "measure_algorithm", - "apply_reverse_lightcone", "return_tn", "rehearse", ) @@ -128,17 +117,12 @@ def __init__( self._method = method self._c_dtype = c_dtype - # options for Tensor Network Simulator - self._contraction_optimizer = kwargs.get("contraction_optimizer", None) - self._local_simplify = kwargs.get("local_simplify", None) - self._sample_qubits = kwargs.get("sample_qubits", None) - # options for MPS self._max_bond_dim = kwargs.get("max_bond_dim", None) self._cutoff = kwargs.get("cutoff", 1e-16) self._measure_algorithm = kwargs.get("measure_algorithm", None) - # common options + # common options (MPS and TN) self._apply_reverse_lightcone = kwargs.get("apply_reverse_lightcone", None) self._return_tn = kwargs.get("return_tn", None) self._rehearse = kwargs.get("rehearse", None) @@ -169,11 +153,6 @@ def method(self): """Supported method.""" return self._method - @property - def state(self): - """The state on the device.""" - return self._interface.state - @property def c_dtype(self): """State vector complex data type.""" @@ -181,10 +160,6 @@ def c_dtype(self): dtype = c_dtype - def state_to_array(self, digits: int = 5): - """Copy the state tensor data to a numpy array.""" - return self._interface.state_to_array(digits) - 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_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 173fb44fba..40f8abf239 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -21,6 +21,16 @@ from pennylane.wires import Wires +# TODO: understand if supporting all operations and observables is feasible for the first release +# I comment the following lines since otherwise Codecov complaints + +# _operations = frozenset({}) +# The set of supported operations. + +# _observables = frozenset({}) +# The set of supported observables. + + class QuimbMPS: """Quimb MPS class. @@ -32,7 +42,7 @@ def __init__( num_wires, dtype=np.complex128, device_name="lightning.tensor", - **kwargs, # pylint: disable=unused-argument + **kwargs, ): if dtype not in [np.complex64, np.complex128]: # pragma: no cover @@ -46,15 +56,20 @@ def __init__( self._wires = Wires(range(num_wires)) self._dtype = dtype + # options for MPS + self._max_bond_dim = kwargs.get("max_bond_dim", None) + self._cutoff = kwargs.get("cutoff", 1e-16) + self._measure_algorithm = kwargs.get("measure_algorithm", None) + # TODO: allows users to specify initial state self._circuit = qtn.CircuitMPS(psi0=self._set_initial_mps()) @property def state(self): - """MPS on this device.""" + """Current MPS handled by the device.""" return self._circuit.psi - def state_to_array(self, digits): + def state_to_array(self, digits: int = 5): """Contract the MPS into a dense array.""" return self._circuit.to_dense().round(digits) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 8540262fb0..c0130295f7 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -30,12 +30,11 @@ @pytest.mark.parametrize("num_wires", [None, 4]) @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) -@pytest.mark.parametrize("device_name", ["lightning.tensor"]) -def test_device_name_and_init(num_wires, c_dtype, device_name): +def test_device_name_and_init(num_wires, c_dtype): """Test the class initialization and returned properties.""" wires = Wires(range(num_wires)) if num_wires else None dev = LightningTensor(wires=wires, c_dtype=c_dtype) - assert dev.name == device_name + assert dev.name == "lightning.tensor" assert dev.c_dtype == c_dtype assert dev.wires == wires diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 42b2c666dd..1c9b0af0a5 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -40,8 +40,8 @@ def test_device_init(self, num_wires, c_dtype, backend, method): wires = Wires(range(num_wires)) if num_wires else None dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) - assert isinstance(dev.state, qtn.MatrixProductState) - assert isinstance(dev.state_to_array(), np.ndarray) + assert isinstance(dev._interface.state, qtn.MatrixProductState) + assert isinstance(dev._interface.state_to_array(), np.ndarray) config = dev.preprocess() assert config.device_options["backend"] == backend From eb348b31a4e39404cca11d82b931153465cc013f Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 18:14:39 -0400 Subject: [PATCH 51/88] Python formatter --- .github/CHANGELOG.md | 2 +- pennylane_lightning/lightning_tensor/quimb/_mps.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 86891baf6f..6a85999dbd 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -81,7 +81,7 @@ This release contains contributions from (in alphabetical order): -Ali Asadi, Amintor Dusko, Christina Lee, Vincent Michaud-Rioux, Mudit Pandey, Shuli Shu +Ali Asadi, Amintor Dusko, Pietropaolo Frisoni, Christina Lee, Vincent Michaud-Rioux, Mudit Pandey, Shuli Shu --- diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 40f8abf239..5011206211 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -20,7 +20,6 @@ from pennylane import DeviceError from pennylane.wires import Wires - # TODO: understand if supporting all operations and observables is feasible for the first release # I comment the following lines since otherwise Codecov complaints From f9dc84e66edcd6720e24ddcdd43c6214283180ae Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 18:20:44 -0400 Subject: [PATCH 52/88] removing params from `QuimbMPS` --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 5011206211..e721417afa 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -55,11 +55,6 @@ def __init__( self._wires = Wires(range(num_wires)) self._dtype = dtype - # options for MPS - self._max_bond_dim = kwargs.get("max_bond_dim", None) - self._cutoff = kwargs.get("cutoff", 1e-16) - self._measure_algorithm = kwargs.get("measure_algorithm", None) - # TODO: allows users to specify initial state self._circuit = qtn.CircuitMPS(psi0=self._set_initial_mps()) From 651730d3d0e348ccb9ef08672114b78033f0e246 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 12 Apr 2024 22:21:59 +0000 Subject: [PATCH 53/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 3da22800fb..547a79e452 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev28" +__version__ = "0.36.0-dev29" From eb84dedebd16ab14a05733e0cb82980e0a8b23ec Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 18:25:01 -0400 Subject: [PATCH 54/88] removing `**kwargs` from `QuimbMPS` --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 2 +- pennylane_lightning/lightning_tensor/quimb/_mps.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 1501bdb702..5a30ea1e06 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -131,7 +131,7 @@ def __init__( # TODO: implement the remaining combs of `backend` and `interface` if self.backend == "quimb" and self.method == "mps": - self._interface = QuimbMPS(self.num_wires, self._c_dtype, **kwargs) + self._interface = QuimbMPS(self._num_wires, self._c_dtype) @property def name(self): diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index e721417afa..90cb6e40d2 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -36,13 +36,7 @@ class QuimbMPS: Interfaces with `quimb` for MPS manipulation. """ - def __init__( - self, - num_wires, - dtype=np.complex128, - device_name="lightning.tensor", - **kwargs, - ): + def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") From 3925d376b65a4baaee295a79913ebb2cdeccd506 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 19:06:18 -0400 Subject: [PATCH 55/88] removing unnecessary param at this stage --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 6 +----- tests/lightning_tensor/test_lightning_tensor.py | 6 ------ 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 90cb6e40d2..5d7e17e9ef 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -36,15 +36,11 @@ class QuimbMPS: Interfaces with `quimb` for MPS manipulation. """ - def __init__(self, num_wires, dtype=np.complex128, device_name="lightning.tensor"): + def __init__(self, num_wires, dtype=np.complex128): if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") - if device_name != "lightning.tensor": - raise DeviceError(f'The device name "{device_name}" is not a valid option.') - - self._device_name = device_name self._num_wires = num_wires self._wires = Wires(range(num_wires)) self._dtype = dtype diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index c0130295f7..3fc1c4119c 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -39,12 +39,6 @@ def test_device_name_and_init(num_wires, c_dtype): assert dev.wires == wires -def test_wrong_device_name(): - """Test an invalid device name""" - with pytest.raises(qml.DeviceError, match="The device name"): - LightningTensor(device_name="thunder.tensor") - - @pytest.mark.parametrize("backend", ["fake_backend"]) def test_invalid_backend(backend): """Test an invalid backend.""" From da0518b877d88826d716c0a52f10e66d03513023 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 19:34:39 -0400 Subject: [PATCH 56/88] covering test line --- tests/lightning_tensor/test_lightning_tensor.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 3fc1c4119c..0482b1a9a9 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -37,6 +37,10 @@ def test_device_name_and_init(num_wires, c_dtype): assert dev.name == "lightning.tensor" assert dev.c_dtype == c_dtype assert dev.wires == wires + if num_wires is None: + assert dev.num_wires == 0 + else: + assert dev.num_wires == num_wires @pytest.mark.parametrize("backend", ["fake_backend"]) @@ -55,5 +59,7 @@ def test_invalid_method(method): def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): + with pytest.raises( + ValueError, match="LightningTensor does not support the `shots` parameter." + ): LightningTensor(shots=5) From df2350dc7398e3383c641c2a6a043cfbf0ae6751 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 12 Apr 2024 19:41:12 -0400 Subject: [PATCH 57/88] formatter... --- tests/lightning_tensor/test_lightning_tensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index 0482b1a9a9..adb1671f94 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -59,7 +59,5 @@ def test_invalid_method(method): def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises( - ValueError, match="LightningTensor does not support the `shots` parameter." - ): + with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): LightningTensor(shots=5) From 017a924c1cf3f592765f818be035f8052d005a19 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 15 Apr 2024 08:37:39 -0400 Subject: [PATCH 58/88] removing param description --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 8 ++++++-- pennylane_lightning/lightning_tensor/quimb/_mps.py | 3 --- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 5a30ea1e06..d99414aa5f 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -180,7 +180,11 @@ def _setup_execution_config(self, config): return replace(config, **updated_values, device_options=new_device_options) - def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig): + def preprocess( + self, + circuits: QuantumTape_or_Batch, + execution_config: ExecutionConfig = DefaultExecutionConfig, + ): """This function defines the device transform program to be applied and an updated device configuration. Args: @@ -188,7 +192,7 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) parameters needed to fully describe the execution. Returns: - ... + # TODO: decide """ config = self._setup_execution_config(execution_config) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 5d7e17e9ef..2122a15c17 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -61,9 +61,6 @@ def _set_initial_mps(self): r""" Returns an initial state to :math:`\ket{0}`. - Args: - wires (Union[Wires, Iterable]): The wires to be present in the initial state. - Returns: array: The initial state of a circuit. """ From ba89c13adadd02a76bae719e42379e3b175cf9c8 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 15 Apr 2024 08:40:51 -0400 Subject: [PATCH 59/88] Making `pylint` happy --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 2 +- pennylane_lightning/lightning_tensor/quimb/_mps.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index d99414aa5f..3fbfe0f11f 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -182,7 +182,7 @@ def _setup_execution_config(self, config): def preprocess( self, - circuits: QuantumTape_or_Batch, + circuits: QuantumTape_or_Batch, # pylint: disable=unused-argument execution_config: ExecutionConfig = DefaultExecutionConfig, ): """This function defines the device transform program to be applied and an updated device configuration. diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 2122a15c17..8ab9231eb8 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -17,7 +17,6 @@ import numpy as np import quimb.tensor as qtn -from pennylane import DeviceError from pennylane.wires import Wires # TODO: understand if supporting all operations and observables is feasible for the first release From 505e54a4e4850cdf404a0e8ed76005965efc6730 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 15 Apr 2024 08:49:33 -0400 Subject: [PATCH 60/88] forgot new arg in test --- tests/lightning_tensor/test_quimb_mps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 1c9b0af0a5..6ac65a1e8d 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -43,6 +43,6 @@ def test_device_init(self, num_wires, c_dtype, backend, method): assert isinstance(dev._interface.state, qtn.MatrixProductState) assert isinstance(dev._interface.state_to_array(), np.ndarray) - config = dev.preprocess() + config = dev.preprocess(circuits=None) assert config.device_options["backend"] == backend assert config.device_options["method"] == method From c0a9df95806448ad8bc2fc69edaa2616a81fae0b Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Tue, 16 Apr 2024 15:13:48 -0400 Subject: [PATCH 61/88] Updating base class and `preprocess` function --- .../lightning_tensor/lightning_tensor.py | 25 ++++++++++++++++--- tests/lightning_tensor/test_quimb_mps.py | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 3fbfe0f11f..91395aaa66 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -23,6 +23,7 @@ from pennylane.devices import DefaultExecutionConfig, Device, ExecutionConfig from pennylane.devices.modifiers import simulator_tracking, single_tape_support from pennylane.tape import QuantumTape +from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch from .quimb._mps import QuimbMPS @@ -182,7 +183,6 @@ def _setup_execution_config(self, config): def preprocess( self, - circuits: QuantumTape_or_Batch, # pylint: disable=unused-argument execution_config: ExecutionConfig = DefaultExecutionConfig, ): """This function defines the device transform program to be applied and an updated device configuration. @@ -192,12 +192,28 @@ def preprocess( parameters needed to fully describe the execution. Returns: - # TODO: decide + 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. + + This device: + + * Supports any qubit operations that provide a matrix. + * Currently does not support finite shots. """ config = self._setup_execution_config(execution_config) - return config + program = TransformProgram() + + # TODO: remove comments in next PR + # 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) + + return program, config def execute( self, @@ -213,7 +229,8 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - # TODO: call the function implemented in the appropriate interface + # TODO: remove comments in next PR + # return self._interface.execute(circuits, execution_config) def supports_derivatives( self, diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 6ac65a1e8d..250ae09ca2 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -43,6 +43,6 @@ def test_device_init(self, num_wires, c_dtype, backend, method): assert isinstance(dev._interface.state, qtn.MatrixProductState) assert isinstance(dev._interface.state_to_array(), np.ndarray) - config = dev.preprocess(circuits=None) + program, config = dev.preprocess() assert config.device_options["backend"] == backend assert config.device_options["method"] == method From 364ff804e5b69808b35b95b69f1ec1ef6f938ba8 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Tue, 16 Apr 2024 17:11:50 -0400 Subject: [PATCH 62/88] Updating `LightningTensor` class with new names from more advanced PR --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 8ab9231eb8..24eeb70e7a 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -43,20 +43,18 @@ def __init__(self, num_wires, dtype=np.complex128): self._num_wires = num_wires self._wires = Wires(range(num_wires)) self._dtype = dtype - - # TODO: allows users to specify initial state - self._circuit = qtn.CircuitMPS(psi0=self._set_initial_mps()) + self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) @property def state(self): """Current MPS handled by the device.""" - return self._circuit.psi + return self._circuitMPS.psi def state_to_array(self, digits: int = 5): """Contract the MPS into a dense array.""" - return self._circuit.to_dense().round(digits) + return self._circuitMPS.to_dense().round(digits) - def _set_initial_mps(self): + def _initial_mps(self): r""" Returns an initial state to :math:`\ket{0}`. From aebfd13ac64161c833752bd19244535cd47a64b2 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 17 Apr 2024 13:45:24 +0000 Subject: [PATCH 63/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 547a79e452..7fbbc4aee8 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev29" +__version__ = "0.36.0-dev30" From 6333163852ce9ebb3b4c025b3c051d9ce8c899c5 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 17 Apr 2024 18:15:57 +0000 Subject: [PATCH 64/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 7fbbc4aee8..22ae5e03f5 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev30" +__version__ = "0.36.0-dev31" From dd60aa97132dcd8a253af85e6ecc6df8117ab380 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Wed, 17 Apr 2024 14:24:21 -0400 Subject: [PATCH 65/88] Triggering CI From b419e86087e3fd468b47f8cfabad89ce72985c72 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Wed, 17 Apr 2024 21:30:57 +0000 Subject: [PATCH 66/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 22ae5e03f5..60d8f6e108 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev31" +__version__ = "0.36.0-dev32" From 74c656269a5b5117dab89ca0f6aa4cd8de07c06a Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 18 Apr 2024 10:46:58 -0400 Subject: [PATCH 67/88] Trying to remove pin from `quimb` in `requirements.dev` --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 3 +++ requirements-dev.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 91395aaa66..bbc5a41c1a 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -166,6 +166,9 @@ 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 = {} + # TODO: remove comments when gradients can be computed + # 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", diff --git a/requirements-dev.txt b/requirements-dev.txt index 711b5bd5bb..a848414d47 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,4 +18,4 @@ cmake custatevec-cu12 pylint scipy==1.12.0 -quimb==1.7.3 +quimb From d13d373ed2d5e0de52c26f65632aebee946f6264 Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 19 Apr 2024 01:33:52 +0000 Subject: [PATCH 68/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 60d8f6e108..9c6e90e856 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev32" +__version__ = "0.36.0-dev33" From 5487b574fd4f6c3cf261d735f653c853f87bfd6a Mon Sep 17 00:00:00 2001 From: Dev version update bot Date: Fri, 19 Apr 2024 14:34:29 +0000 Subject: [PATCH 69/88] Auto update version --- pennylane_lightning/core/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 9c6e90e856..01c5ebb276 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev33" +__version__ = "0.36.0-dev34" From 5e478744c4b4c788d852306396c60c6161deb403 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 19 Apr 2024 10:55:20 -0400 Subject: [PATCH 70/88] Removing infos on derivatives and using config options to pass parameters to interface --- .../lightning_tensor/lightning_tensor.py | 66 +++++++++++-------- .../lightning_tensor/quimb/_mps.py | 36 +++++++--- .../lightning_tensor/test_lightning_tensor.py | 16 ++++- tests/lightning_tensor/test_quimb_mps.py | 7 +- 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index bbc5a41c1a..d6de6fffc8 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -71,7 +71,8 @@ class LightningTensor(Device): **kwargs: keyword arguments. """ - # TODO: decide whether to move some of the attributes in interfaces classes + # TODO: add kwargs description during implementation phase (next PR) + # pylint: disable=too-many-instance-attributes # So far we just insert the options for MPS simulator @@ -89,7 +90,6 @@ class LightningTensor(Device): _new_API = True - # TODO: decide if `backend` and `method` should be keyword args as well # pylint: disable=too-many-arguments def __init__( self, @@ -129,10 +129,21 @@ def __init__( self._rehearse = kwargs.get("rehearse", None) self._interface = None + interface_opts = self._setup_execution_config().device_options - # TODO: implement the remaining combs of `backend` and `interface` + # TODO: implement the remaining interfaces when they will be available if self.backend == "quimb" and self.method == "mps": - self._interface = QuimbMPS(self._num_wires, self._c_dtype) + self._interface = QuimbMPS( + self._num_wires, + interface_opts, + self._c_dtype, + ) + + for arg in kwargs: + if arg not in self._device_options: + raise TypeError( + f"Unexpected argument: {arg} during initialization of lightning.tensor." + ) @property def name(self): @@ -161,21 +172,15 @@ def c_dtype(self): dtype = c_dtype - def _setup_execution_config(self, config): + def _setup_execution_config( + self, config: Optional[ExecutionConfig] = DefaultExecutionConfig + ): """ Update the execution config with choices for how the device should be used and the device options. """ + # TODO: add options for gradients next quarter + updated_values = {} - # TODO: remove comments when gradients can be computed - # 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: @@ -209,12 +214,7 @@ def preprocess( program = TransformProgram() - # TODO: remove comments in next PR - # 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) + # more in the next PR return program, config @@ -232,7 +232,7 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - # TODO: remove comments in next PR + # more in the next PR # return self._interface.execute(circuits, execution_config) def supports_derivatives( @@ -250,7 +250,8 @@ def supports_derivatives( Bool: Whether or not a derivative can be calculated provided the given information. """ - # TODO: call the function implemented in the appropriate interface + # TODO: implement during next quarter + return False # pragma: no cover def compute_derivatives( self, @@ -266,7 +267,9 @@ def compute_derivatives( Returns: Tuple: The jacobian for each trainable parameter. """ - # TODO: call the function implemented in the appropriate interface + raise NotImplementedError( + "The computation of derivatives has yet to be implemented for the lightning.tensor device." + ) def execute_and_compute_derivatives( self, @@ -282,7 +285,9 @@ def execute_and_compute_derivatives( Returns: tuple: A numeric result of the computation and the gradient. """ - # TODO: call the function implemented in the appropriate interface + raise NotImplementedError( + "The computation of derivatives has yet to be implemented for the lightning.tensor device." + ) # pragma: no cover def supports_vjp( self, @@ -298,7 +303,8 @@ def supports_vjp( Returns: Bool: Whether or not a derivative can be calculated provided the given information. """ - # TODO: call the function implemented in the appropriate interface + # TODO: implement during next quarter + return False # pragma: no cover def compute_vjp( self, @@ -318,7 +324,9 @@ def compute_vjp( Returns: tensor-like: A numeric result of computing the vector jacobian product. """ - # TODO: call the function implemented in the appropriate interface + raise NotImplementedError( + "The computation of vector jacobian product has yet to be implemented for the lightning.tensor device." + ) # pragma: no cover def execute_and_compute_vjp( self, @@ -337,4 +345,6 @@ def execute_and_compute_vjp( Returns: Tuple, Tuple: the result of executing the scripts and the numeric result of computing the vector jacobian product """ - # TODO: call the function implemented in the appropriate interface + raise NotImplementedError( + "The computation of vector jacobian product has yet to be implemented for the lightning.tensor device." + ) # pragma: no cover diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 24eeb70e7a..5c60df6344 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -35,19 +35,43 @@ class QuimbMPS: Interfaces with `quimb` for MPS manipulation. """ - def __init__(self, num_wires, dtype=np.complex128): + # TODO: add more details in description during implementation phase (next PR) + + def __init__(self, num_wires, interf_opts, dtype=np.complex128): if dtype not in [np.complex64, np.complex128]: # pragma: no cover raise TypeError(f"Unsupported complex type: {dtype}") - self._num_wires = num_wires self._wires = Wires(range(num_wires)) self._dtype = dtype + + self._init_state_ops = { + "binary": "0" * max(1, len(self._wires)), + "dtype": self._dtype.__name__, + "tags": [str(l) for l in self._wires.labels], + } + + self._gate_opts = { + "contract": "swap+split", + "parametrize": None, + "cutoff": interf_opts["cutoff"], + "max_bond": interf_opts["max_bond_dim"], + } + + self._expval_opts = { + "dtype": self._dtype.__name__, + "simplify_sequence": "ADCRS", + "simplify_atol": 0.0, + "rehearse": interf_opts["rehearse"], + } + + self._return_tn = interf_opts["return_tn"] + self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) @property def state(self): - """Current MPS handled by the device.""" + """Current MPS handled by the interface.""" return self._circuitMPS.psi def state_to_array(self, digits: int = 5): @@ -62,8 +86,4 @@ def _initial_mps(self): array: The initial state of a circuit. """ - return qtn.MPS_computational_state( - "0" * max(1, self._num_wires), - dtype=self._dtype.__name__, - tags=[str(l) for l in self._wires.labels], - ) + return qtn.MPS_computational_state(**self._init_state_ops) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index adb1671f94..ee07437077 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -24,6 +24,9 @@ from pennylane_lightning.lightning_tensor import LightningTensor +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + if LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @@ -57,7 +60,18 @@ def test_invalid_method(method): LightningTensor(method=method) +def test_invalid_keyword_arg(): + """Test an invalid method.""" + with pytest.raises( + TypeError, + match=f"Unexpected argument: fake_arg during initialization of lightning.tensor.", + ): + LightningTensor(fake_arg=None) + + def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): + with pytest.raises( + ValueError, match="LightningTensor does not support the `shots` parameter." + ): LightningTensor(shots=5) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 250ae09ca2..9651747434 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -24,6 +24,9 @@ from pennylane_lightning.lightning_tensor import LightningTensor +if not LightningDevice._new_API: + pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) + if LightningDevice._CPP_BINARY_AVAILABLE: pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @@ -39,7 +42,9 @@ def test_device_init(self, num_wires, c_dtype, backend, method): """Test the class initialization and returned properties.""" wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) + dev = LightningTensor( + wires=wires, backend=backend, method=method, c_dtype=c_dtype + ) assert isinstance(dev._interface.state, qtn.MatrixProductState) assert isinstance(dev._interface.state_to_array(), np.ndarray) From 804df342a3bbd41a96da0e3809d8d405c5bc7017 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 19 Apr 2024 11:09:59 -0400 Subject: [PATCH 71/88] Usual `pylint` failures --- .github/CHANGELOG.md | 2 +- .../lightning_tensor/lightning_tensor.py | 16 +++++++--------- tests/lightning_tensor/test_lightning_tensor.py | 4 +--- tests/lightning_tensor/test_quimb_mps.py | 4 +--- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 211f81faf1..a9eab09ece 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,7 +2,7 @@ ### New features since last release -* Add Python class for the `lightning.tensor` device which uses the new device API. +* Add Python class for the `lightning.tensor` device which uses the new device API and the interface for `quimb` based on the MPS method. [(#671)](https://github.com/PennyLaneAI/pennylane-lightning/pull/671) * `lightning.kokkos` supports mid-circuit measurements. diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index d6de6fffc8..07f4209a12 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -172,9 +172,7 @@ def c_dtype(self): dtype = c_dtype - def _setup_execution_config( - self, config: Optional[ExecutionConfig] = DefaultExecutionConfig - ): + def _setup_execution_config(self, config: Optional[ExecutionConfig] = DefaultExecutionConfig): """ Update the execution config with choices for how the device should be used and the device options. """ @@ -232,13 +230,13 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - # more in the next PR + # comment is removed in the next PR # return self._interface.execute(circuits, execution_config) def supports_derivatives( self, - execution_config: Optional[ExecutionConfig] = None, - circuit: Optional[qml.tape.QuantumTape] = None, + execution_config: Optional[ExecutionConfig] = None, # pylint: disable=unused-argument + circuit: Optional[qml.tape.QuantumTape] = None, # pylint: disable=unused-argument ) -> bool: """Check whether or not derivatives are available for a given configuration and circuit. @@ -269,7 +267,7 @@ def compute_derivatives( """ raise NotImplementedError( "The computation of derivatives has yet to be implemented for the lightning.tensor device." - ) + ) # pragma: no cover def execute_and_compute_derivatives( self, @@ -291,8 +289,8 @@ def execute_and_compute_derivatives( def supports_vjp( self, - execution_config: Optional[ExecutionConfig] = None, - circuit: Optional[QuantumTape] = None, + execution_config: Optional[ExecutionConfig] = None, # pylint: disable=unused-argument + circuit: Optional[QuantumTape] = None, # pylint: disable=unused-argument ) -> bool: """Whether or not this device defines a custom vector jacobian product. diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index ee07437077..ebe5ec7e09 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -71,7 +71,5 @@ def test_invalid_keyword_arg(): def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises( - ValueError, match="LightningTensor does not support the `shots` parameter." - ): + with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): LightningTensor(shots=5) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 9651747434..987d1f69a5 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -42,9 +42,7 @@ def test_device_init(self, num_wires, c_dtype, backend, method): """Test the class initialization and returned properties.""" wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor( - wires=wires, backend=backend, method=method, c_dtype=c_dtype - ) + dev = LightningTensor(wires=wires, backend=backend, method=method, c_dtype=c_dtype) assert isinstance(dev._interface.state, qtn.MatrixProductState) assert isinstance(dev._interface.state_to_array(), np.ndarray) From b1fbe3e09d948a6798323230bc3042130e1808cb Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 19 Apr 2024 11:55:25 -0400 Subject: [PATCH 72/88] Trying to solve formatting errors --- .../lightning_tensor/lightning_tensor.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 07f4209a12..82f7d619a5 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -233,10 +233,11 @@ def execute( # comment is removed in the next PR # return self._interface.execute(circuits, execution_config) + # pylint: disable=unused-argument def supports_derivatives( self, - execution_config: Optional[ExecutionConfig] = None, # pylint: disable=unused-argument - circuit: Optional[qml.tape.QuantumTape] = None, # pylint: disable=unused-argument + 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. @@ -267,7 +268,7 @@ def compute_derivatives( """ raise NotImplementedError( "The computation of derivatives has yet to be implemented for the lightning.tensor device." - ) # pragma: no cover + ) # pragma: no cover def execute_and_compute_derivatives( self, @@ -287,10 +288,11 @@ def execute_and_compute_derivatives( "The computation of derivatives has yet to be implemented for the lightning.tensor device." ) # pragma: no cover + # pylint: disable=unused-argument def supports_vjp( self, - execution_config: Optional[ExecutionConfig] = None, # pylint: disable=unused-argument - circuit: Optional[QuantumTape] = None, # pylint: disable=unused-argument + execution_config: Optional[ExecutionConfig] = None, + circuit: Optional[QuantumTape] = None, ) -> bool: """Whether or not this device defines a custom vector jacobian product. From 9dcff51ac19a9c2bfaf3eb25087f032ced379405 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 19 Apr 2024 12:37:17 -0400 Subject: [PATCH 73/88] typo in docstring --- tests/lightning_tensor/test_lightning_tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index ebe5ec7e09..ea58d6e5f8 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -61,7 +61,7 @@ def test_invalid_method(method): def test_invalid_keyword_arg(): - """Test an invalid method.""" + """Test an invalid keyword argument.""" with pytest.raises( TypeError, match=f"Unexpected argument: fake_arg during initialization of lightning.tensor.", From 50fa0e1fd71a6aae1e043f80b07a171e1d3d89e5 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Sun, 21 Apr 2024 13:04:34 -0400 Subject: [PATCH 74/88] Sunday update: improved docstrings and structure --- .../lightning_tensor/lightning_tensor.py | 27 ++++++++---- .../lightning_tensor/quimb/_mps.py | 43 +++++++++++++------ .../lightning_tensor/test_lightning_tensor.py | 2 +- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 82f7d619a5..60cae7a858 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -13,6 +13,7 @@ # limitations under the License. """ This module contains the LightningTensor class that inherits from the new device interface. +It is a device to perform tensor network operations on a quantum circuit. """ from dataclasses import replace from numbers import Number @@ -61,14 +62,21 @@ class LightningTensor(Device): Args: wires (int): The number of wires to initialize the device with. Defaults to ``None`` if not specified. - backend (str): Supported backend. Must be one of ``quimb`` or ``cutensornet``. - method (str): Supported method. Must be one of ``mps`` or ``tn``. + backend (str): Supported backend. Currently, only ``quimb`` is supported. + method (str): Supported method. Currently, only ``mps`` is supported. shots (int): How many times the circuit should be evaluated (or sampled) to estimate the expectation values. Currently, it can only be ``None``, so that computation of statistics like expectation values and variances is performed analytically. c_dtype: Datatypes for statevector representation. Must be one of ``np.complex64`` or ``np.complex128``. - **kwargs: keyword arguments. + **kwargs: keyword arguments. The following options are currently supported: + + ``max_bond_dim`` (int): Maximum bond dimension for the MPS simulator. + It corresponds to the number of Schmidt coefficients retained at the end of the SVD algorithm when applying gates. Default is `None`. + ``cutoff`` (float): Truncation threshold for the Schmidt coefficients in a MPS simulator. Default is `1e-16`. + ``return_tn`` (bool): Whether to return the tensor network object along with the results of circuit execution. Default is `False`. + ``rehearse`` (bool): Whether to rehearse the circuit. If `True`, generate and cache the simplified tensor network and contraction path + without performing the contraction. Default is `False`. """ # TODO: add kwargs description during implementation phase (next PR) @@ -124,14 +132,13 @@ def __init__( self._measure_algorithm = kwargs.get("measure_algorithm", None) # common options (MPS and TN) + self._return_tn = kwargs.get("return_tn", False) + self._rehearse = kwargs.get("rehearse", False) self._apply_reverse_lightcone = kwargs.get("apply_reverse_lightcone", None) - self._return_tn = kwargs.get("return_tn", None) - self._rehearse = kwargs.get("rehearse", None) self._interface = None interface_opts = self._setup_execution_config().device_options - # TODO: implement the remaining interfaces when they will be available if self.backend == "quimb" and self.method == "mps": self._interface = QuimbMPS( self._num_wires, @@ -142,7 +149,7 @@ def __init__( for arg in kwargs: if arg not in self._device_options: raise TypeError( - f"Unexpected argument: {arg} during initialization of lightning.tensor." + f"Unexpected argument: {arg} during initialization of the LightningTensor device." ) @property @@ -167,12 +174,14 @@ def method(self): @property def c_dtype(self): - """State vector complex data type.""" + """Tensor complex data type.""" return self._c_dtype dtype = c_dtype - def _setup_execution_config(self, config: Optional[ExecutionConfig] = DefaultExecutionConfig): + def _setup_execution_config( + self, config: Optional[ExecutionConfig] = DefaultExecutionConfig + ) -> ExecutionConfig: """ Update the execution config with choices for how the device should be used and the device options. """ diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 5c60df6344..f988880649 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -12,30 +12,42 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Class implementation for MPS manipulation based on the `quimb` Python package. +Class implementation for the Quimb MPS interface for simulating quantum circuits while keeping the state always in MPS form. """ import numpy as np +import pennylane as qml import quimb.tensor as qtn from pennylane.wires import Wires -# TODO: understand if supporting all operations and observables is feasible for the first release -# I comment the following lines since otherwise Codecov complaints - -# _operations = frozenset({}) +_operations = frozenset({}) # pragma: no cover # The set of supported operations. -# _observables = frozenset({}) +_observables = frozenset({}) # pragma: no cover # The set of supported observables. +def stopping_condition(op: qml.operation.Operator) -> bool: + """A function that determines if an operation is supported by ``lightning.tensor`` for this interface.""" + return op.name in _operations # pragma: no cover + + +def accepted_observables(obs: qml.operation.Operator) -> bool: + """A function that determines if an observable is supported by ``lightning.tensor`` for this interface.""" + return obs.name in _observables # pragma: no cover + + class QuimbMPS: """Quimb MPS class. - Interfaces with `quimb` for MPS manipulation. - """ + Used internally by the `LightningTensor` device. + Interfaces with `quimb` for MPS manipulation, and provides methods to execute quantum circuits. - # TODO: add more details in description during implementation phase (next PR) + Args: + num_wires (int): the number of wires in the circuit. + interf_opts (dict): dictionary containing the interface options. + dtype (np.dtype): the complex type used for the MPS. + """ def __init__(self, num_wires, interf_opts, dtype=np.complex128): @@ -44,6 +56,7 @@ def __init__(self, num_wires, interf_opts, dtype=np.complex128): self._wires = Wires(range(num_wires)) self._dtype = dtype + self._return_tn = interf_opts["return_tn"] self._init_state_ops = { "binary": "0" * max(1, len(self._wires)), @@ -65,8 +78,6 @@ def __init__(self, num_wires, interf_opts, dtype=np.complex128): "rehearse": interf_opts["rehearse"], } - self._return_tn = interf_opts["return_tn"] - self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) @property @@ -74,16 +85,22 @@ def state(self): """Current MPS handled by the interface.""" return self._circuitMPS.psi + def _reset_state(self) -> None: + """Reset the MPS.""" + self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) + def state_to_array(self, digits: int = 5): """Contract the MPS into a dense array.""" return self._circuitMPS.to_dense().round(digits) - def _initial_mps(self): + def _initial_mps(self) -> qtn.MatrixProductState: r""" Returns an initial state to :math:`\ket{0}`. + Internally, it uses `quimb`'s `MPS_computational_state` method. + Returns: - array: The initial state of a circuit. + MatrixProductState: The initial MPS of a circuit. """ return qtn.MPS_computational_state(**self._init_state_ops) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index ea58d6e5f8..c1bccd694a 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -64,7 +64,7 @@ def test_invalid_keyword_arg(): """Test an invalid keyword argument.""" with pytest.raises( TypeError, - match=f"Unexpected argument: fake_arg during initialization of lightning.tensor.", + match=f"Unexpected argument: fake_arg during initialization of the LightningTensor device.", ): LightningTensor(fake_arg=None) From 0d4c8708c6b7b47f22418d8beec9d691754f2bed Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Sun, 21 Apr 2024 13:33:58 -0400 Subject: [PATCH 75/88] Removing method that was supposed to be in next PR --- pennylane_lightning/lightning_tensor/quimb/_mps.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index f988880649..28dd779dbe 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -85,10 +85,6 @@ def state(self): """Current MPS handled by the interface.""" return self._circuitMPS.psi - def _reset_state(self) -> None: - """Reset the MPS.""" - self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) - def state_to_array(self, digits: int = 5): """Contract the MPS into a dense array.""" return self._circuitMPS.to_dense().round(digits) From e08afb0dee995751a37f1ae19095cfc1396935ce Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Sun, 21 Apr 2024 13:55:37 -0400 Subject: [PATCH 76/88] removing old TODO comment --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 60cae7a858..d7d541093c 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -79,8 +79,6 @@ class LightningTensor(Device): without performing the contraction. Default is `False`. """ - # TODO: add kwargs description during implementation phase (next PR) - # pylint: disable=too-many-instance-attributes # So far we just insert the options for MPS simulator From e3ed59f5241d4fbfec0a5bea78f2ec18928c4bd6 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 22 Apr 2024 13:03:15 -0400 Subject: [PATCH 77/88] Removing changes from the `setup.py` file --- .../lightning_tensor/lightning_tensor.py | 4 +-- setup.py | 29 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index d7d541093c..f150e5b33a 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -67,7 +67,7 @@ class LightningTensor(Device): shots (int): How many times the circuit should be evaluated (or sampled) to estimate the expectation values. Currently, it can only be ``None``, so that computation of statistics like expectation values and variances is performed analytically. - c_dtype: Datatypes for statevector representation. Must be one of + c_dtype: Datatypes for the tensor representation. Must be one of ``np.complex64`` or ``np.complex128``. **kwargs: keyword arguments. The following options are currently supported: @@ -81,7 +81,7 @@ class LightningTensor(Device): # pylint: disable=too-many-instance-attributes - # So far we just insert the options for MPS simulator + # So far we just consider the options for MPS simulator _device_options = ( "apply_reverse_lightcone", "backend", diff --git a/setup.py b/setup.py index 072e7e3f25..4c2779d664 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ from setuptools.command.build_ext import build_ext default_backend = "lightning_qubit" -supported_backends = {"lightning_kokkos", "lightning_qubit", "lightning_gpu", "lightning_tensor"} +supported_backends = {"lightning_kokkos", "lightning_qubit", "lightning_gpu"} supported_backends.update({sb.replace("_", ".") for sb in supported_backends}) @@ -29,7 +29,7 @@ def get_backend(): """Return backend. The backend is ``lightning_qubit`` by default. - Allowed values are: "lightning_kokkos", "lightning_qubit", "lightning_gpu" and "lightning_tensor". + Allowed values are: "lightning_kokkos", "lightning_qubit" and "lightning_gpu". A dot can also be used instead of an underscore. If the environment variable ``PL_BACKEND`` is defined, its value is used. Otherwise, if the environment variable ``CMAKE_ARGS`` is defined and it @@ -46,7 +46,9 @@ def get_backend(): if not arg and backend is not None: cmake_backend = backend else: - cmake_backend = arg[0].split("=")[1].replace(".", "_") if arg else default_backend + cmake_backend = ( + arg[0].split("=")[1].replace(".", "_") if arg else default_backend + ) if backend is not None and backend != cmake_backend: raise ValueError( f"Backends {backend} and {cmake_backend} specified by PL_BACKEND and CMAKE_ARGS respectively do not match." @@ -74,7 +76,9 @@ class CMakeBuild(build_ext): This class is built upon https://github.com/diegoferigo/cmake-build-extension/blob/master/src/cmake_build_extension/build_extension.py and https://github.com/pybind/cmake_example/blob/master/setup.py """ - user_options = build_ext.user_options + [("define=", "D", "Define variables for CMake")] + user_options = build_ext.user_options + [ + ("define=", "D", "Define variables for CMake") + ] def initialize_options(self): super().initialize_options() @@ -141,7 +145,9 @@ def build_extension(self, ext: CMakeExtension): if not Path(libomp_path).exists(): libomp_path = "" configure_args += ( - [f"-DOpenMP_ROOT={libomp_path}/"] if libomp_path else ["-DENABLE_OPENMP=OFF"] + [f"-DOpenMP_ROOT={libomp_path}/"] + if libomp_path + else ["-DENABLE_OPENMP=OFF"] ) elif platform.system() == "Windows": configure_args += ["-DENABLE_OPENMP=OFF", "-DENABLE_BLAS=OFF"] @@ -166,7 +172,9 @@ def build_extension(self, ext: CMakeExtension): ) -with open(os.path.join("pennylane_lightning", "core", "_version.py"), encoding="utf-8") as f: +with open( + os.path.join("pennylane_lightning", "core", "_version.py"), encoding="utf-8" +) as f: version = f.readlines()[-1].split()[-1].strip("\"'") requirements = [ @@ -185,7 +193,9 @@ def build_extension(self, ext: CMakeExtension): suffix = suffix[0:].upper() suffix = suffix[0].upper() + suffix[1:] -pennylane_plugins = [device_name + " = pennylane_lightning." + backend + ":Lightning" + suffix] +pennylane_plugins = [ + device_name + " = pennylane_lightning." + backend + ":Lightning" + suffix +] pkg_suffix = "" if suffix == "Qubit" else "_" + suffix @@ -204,14 +214,15 @@ def build_extension(self, ext: CMakeExtension): "long_description_content_type": "text/x-rst", "install_requires": requirements, "ext_modules": ( - [] if os.environ.get("SKIP_COMPILATION", False) else [CMakeExtension(f"{backend}_ops")] + [] + if os.environ.get("SKIP_COMPILATION", False) + else [CMakeExtension(f"{backend}_ops")] ), "cmdclass": {"build_ext": CMakeBuild}, "ext_package": "pennylane_lightning", "extras_require": { "gpu": ["pennylane-lightning-gpu"], "kokkos": ["pennylane-lightning-kokkos"], - "tensor": ["pennylane-lightning-tensor"], }, } From 11fae6d9f95adb5dd87af4b85f5367d5a6904d54 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 22 Apr 2024 13:16:17 -0400 Subject: [PATCH 78/88] restoring previous format to `setup.py` --- setup.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/setup.py b/setup.py index 4c2779d664..977f753559 100644 --- a/setup.py +++ b/setup.py @@ -46,9 +46,7 @@ def get_backend(): if not arg and backend is not None: cmake_backend = backend else: - cmake_backend = ( - arg[0].split("=")[1].replace(".", "_") if arg else default_backend - ) + cmake_backend = arg[0].split("=")[1].replace(".", "_") if arg else default_backend if backend is not None and backend != cmake_backend: raise ValueError( f"Backends {backend} and {cmake_backend} specified by PL_BACKEND and CMAKE_ARGS respectively do not match." @@ -76,9 +74,7 @@ class CMakeBuild(build_ext): This class is built upon https://github.com/diegoferigo/cmake-build-extension/blob/master/src/cmake_build_extension/build_extension.py and https://github.com/pybind/cmake_example/blob/master/setup.py """ - user_options = build_ext.user_options + [ - ("define=", "D", "Define variables for CMake") - ] + user_options = build_ext.user_options + [("define=", "D", "Define variables for CMake")] def initialize_options(self): super().initialize_options() @@ -145,9 +141,7 @@ def build_extension(self, ext: CMakeExtension): if not Path(libomp_path).exists(): libomp_path = "" configure_args += ( - [f"-DOpenMP_ROOT={libomp_path}/"] - if libomp_path - else ["-DENABLE_OPENMP=OFF"] + [f"-DOpenMP_ROOT={libomp_path}/"] if libomp_path else ["-DENABLE_OPENMP=OFF"] ) elif platform.system() == "Windows": configure_args += ["-DENABLE_OPENMP=OFF", "-DENABLE_BLAS=OFF"] @@ -172,9 +166,7 @@ def build_extension(self, ext: CMakeExtension): ) -with open( - os.path.join("pennylane_lightning", "core", "_version.py"), encoding="utf-8" -) as f: +with open(os.path.join("pennylane_lightning", "core", "_version.py"), encoding="utf-8") as f: version = f.readlines()[-1].split()[-1].strip("\"'") requirements = [ @@ -193,9 +185,7 @@ def build_extension(self, ext: CMakeExtension): suffix = suffix[0:].upper() suffix = suffix[0].upper() + suffix[1:] -pennylane_plugins = [ - device_name + " = pennylane_lightning." + backend + ":Lightning" + suffix -] +pennylane_plugins = [device_name + " = pennylane_lightning." + backend + ":Lightning" + suffix] pkg_suffix = "" if suffix == "Qubit" else "_" + suffix @@ -214,9 +204,7 @@ def build_extension(self, ext: CMakeExtension): "long_description_content_type": "text/x-rst", "install_requires": requirements, "ext_modules": ( - [] - if os.environ.get("SKIP_COMPILATION", False) - else [CMakeExtension(f"{backend}_ops")] + [] if os.environ.get("SKIP_COMPILATION", False) else [CMakeExtension(f"{backend}_ops")] ), "cmdclass": {"build_ext": CMakeBuild}, "ext_package": "pennylane_lightning", From 9c3d6303d004a3ae377b1db9e8a4c8b55432c745 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 25 Apr 2024 14:16:49 +0000 Subject: [PATCH 79/88] Auto update version from '0.36.0-dev34' to '0.36.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 01c5ebb276..fd9437debb 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev34" +__version__ = "0.36.0-dev41" From 2e91195fc640db49eb7ffd6c21e22c77c2df8f6c Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 25 Apr 2024 14:18:48 +0000 Subject: [PATCH 80/88] Auto update version from '0.36.0-dev40' to '0.36.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 06b4d144a5..fd9437debb 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev40" +__version__ = "0.36.0-dev41" From 149d126240fd65732a9f408a630bbfb30767bd1b Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 25 Apr 2024 10:20:56 -0400 Subject: [PATCH 81/88] Removing kwargs as suggested from code review --- pennylane_lightning/lightning_tensor/lightning_tensor.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index f150e5b33a..084f0ed77f 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -13,7 +13,7 @@ # limitations under the License. """ This module contains the LightningTensor class that inherits from the new device interface. -It is a device to perform tensor network operations on a quantum circuit. +It is a device to perform tensor network simulation of a quantum circuit. """ from dataclasses import replace from numbers import Number @@ -72,11 +72,8 @@ class LightningTensor(Device): **kwargs: keyword arguments. The following options are currently supported: ``max_bond_dim`` (int): Maximum bond dimension for the MPS simulator. - It corresponds to the number of Schmidt coefficients retained at the end of the SVD algorithm when applying gates. Default is `None`. - ``cutoff`` (float): Truncation threshold for the Schmidt coefficients in a MPS simulator. Default is `1e-16`. - ``return_tn`` (bool): Whether to return the tensor network object along with the results of circuit execution. Default is `False`. - ``rehearse`` (bool): Whether to rehearse the circuit. If `True`, generate and cache the simplified tensor network and contraction path - without performing the contraction. Default is `False`. + It corresponds to the number of Schmidt coefficients retained at the end of the SVD algorithm when applying gates. Default is ``None``. + ``cutoff`` (float): Truncation threshold for the Schmidt coefficients in a MPS simulator. Default is ``1e-16``. """ # pylint: disable=too-many-instance-attributes From 9ef5625afd4d0baaa48ef4e7a7e6727f1b9bdb7e Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 25 Apr 2024 12:16:07 -0400 Subject: [PATCH 82/88] Addressing comments from CR --- .../lightning_tensor/lightning_tensor.py | 25 +++------- .../lightning_tensor/quimb/_mps.py | 2 - .../lightning_tensor/test_lightning_tensor.py | 46 +++++++++++++++++-- tests/lightning_tensor/test_quimb_mps.py | 4 +- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 084f0ed77f..c13dd1f386 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -80,15 +80,11 @@ class LightningTensor(Device): # So far we just consider the options for MPS simulator _device_options = ( - "apply_reverse_lightcone", "backend", "c_dtype", "cutoff", "method", "max_bond_dim", - "measure_algorithm", - "return_tn", - "rehearse", ) _new_API = True @@ -112,7 +108,7 @@ def __init__( raise ValueError(f"Unsupported method: {method}") if shots is not None: - raise ValueError("LightningTensor does not support the `shots` parameter.") + raise ValueError("LightningTensor does not support finite shots.") super().__init__(wires=wires, shots=shots) @@ -124,12 +120,6 @@ def __init__( # options for MPS self._max_bond_dim = kwargs.get("max_bond_dim", None) self._cutoff = kwargs.get("cutoff", 1e-16) - self._measure_algorithm = kwargs.get("measure_algorithm", None) - - # common options (MPS and TN) - self._return_tn = kwargs.get("return_tn", False) - self._rehearse = kwargs.get("rehearse", False) - self._apply_reverse_lightcone = kwargs.get("apply_reverse_lightcone", None) self._interface = None interface_opts = self._setup_execution_config().device_options @@ -253,8 +243,7 @@ def supports_derivatives( Bool: Whether or not a derivative can be calculated provided the given information. """ - # TODO: implement during next quarter - return False # pragma: no cover + return False def compute_derivatives( self, @@ -272,7 +261,7 @@ def compute_derivatives( """ raise NotImplementedError( "The computation of derivatives has yet to be implemented for the lightning.tensor device." - ) # pragma: no cover + ) def execute_and_compute_derivatives( self, @@ -290,7 +279,7 @@ def execute_and_compute_derivatives( """ raise NotImplementedError( "The computation of derivatives has yet to be implemented for the lightning.tensor device." - ) # pragma: no cover + ) # pylint: disable=unused-argument def supports_vjp( @@ -308,7 +297,7 @@ def supports_vjp( Bool: Whether or not a derivative can be calculated provided the given information. """ # TODO: implement during next quarter - return False # pragma: no cover + return False def compute_vjp( self, @@ -330,7 +319,7 @@ def compute_vjp( """ raise NotImplementedError( "The computation of vector jacobian product has yet to be implemented for the lightning.tensor device." - ) # pragma: no cover + ) def execute_and_compute_vjp( self, @@ -351,4 +340,4 @@ def execute_and_compute_vjp( """ raise NotImplementedError( "The computation of vector jacobian product has yet to be implemented for the lightning.tensor device." - ) # pragma: no cover + ) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/quimb/_mps.py index 28dd779dbe..feb7bac487 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/quimb/_mps.py @@ -56,7 +56,6 @@ def __init__(self, num_wires, interf_opts, dtype=np.complex128): self._wires = Wires(range(num_wires)) self._dtype = dtype - self._return_tn = interf_opts["return_tn"] self._init_state_ops = { "binary": "0" * max(1, len(self._wires)), @@ -75,7 +74,6 @@ def __init__(self, num_wires, interf_opts, dtype=np.complex128): "dtype": self._dtype.__name__, "simplify_sequence": "ADCRS", "simplify_atol": 0.0, - "rehearse": interf_opts["rehearse"], } self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index c1bccd694a..b803fd92c4 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -27,8 +27,8 @@ if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +# if LightningDevice._CPP_BINARY_AVAILABLE: +# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("num_wires", [None, 4]) @@ -71,5 +71,45 @@ def test_invalid_keyword_arg(): def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises(ValueError, match="LightningTensor does not support the `shots` parameter."): + with pytest.raises(ValueError, match="LightningTensor does not support finite shots."): LightningTensor(shots=5) + + +def test_support_derivatives(): + """Test that the device does not support derivatives yet.""" + dev = LightningTensor() + assert not dev.supports_derivatives() + + +def test_compute_derivatives(): + """Test that an error is raised if the `compute_derivatives` method is called.""" + dev = LightningTensor() + with pytest.raises(NotImplementedError): + dev.compute_derivatives(circuits=None) + + +def test_execute_and_compute_derivatives(): + """Test that an error is raised if `execute_and_compute_derivative` method is called.""" + dev = LightningTensor() + with pytest.raises(NotImplementedError): + dev.execute_and_compute_derivatives(circuits=None) + + +def test_supports_vjp(): + """Test that the device does not support VJP yet.""" + dev = LightningTensor() + assert not dev.supports_vjp() + + +def test_compute_vjp(): + """Test that an error is raised if `compute_vjp` method is called.""" + dev = LightningTensor() + with pytest.raises(NotImplementedError): + dev.compute_vjp(circuits=None, cotangents=None) + + +def test_execute_and_compute_vjp(): + """Test that an error is raised if `execute_and_compute_vjp` method is called.""" + dev = LightningTensor() + with pytest.raises(NotImplementedError): + dev.execute_and_compute_vjp(circuits=None, cotangents=None) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 987d1f69a5..c3bc945b0c 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -27,8 +27,8 @@ if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +# if LightningDevice._CPP_BINARY_AVAILABLE: +# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("backend", ["quimb"]) From 6a894ca3d29f193e640851e78fc92585c39a94a1 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Thu, 25 Apr 2024 12:22:48 -0400 Subject: [PATCH 83/88] Skipping tests if CPP binary is available --- tests/lightning_tensor/test_lightning_tensor.py | 4 ++-- tests/lightning_tensor/test_quimb_mps.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index b803fd92c4..3d7d1033b8 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -27,8 +27,8 @@ if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) -# if LightningDevice._CPP_BINARY_AVAILABLE: -# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("num_wires", [None, 4]) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index c3bc945b0c..987d1f69a5 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -27,8 +27,8 @@ if not LightningDevice._new_API: pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) -# if LightningDevice._CPP_BINARY_AVAILABLE: -# pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) +if LightningDevice._CPP_BINARY_AVAILABLE: + pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) @pytest.mark.parametrize("backend", ["quimb"]) From 5981e98066996a4de001a9891ea4c4643d5cb4fb Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Thu, 25 Apr 2024 23:03:59 +0000 Subject: [PATCH 84/88] Auto update version from '0.36.0-dev42' to '0.36.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 1b3bcb62fc..0dd7399263 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev42" +__version__ = "0.36.0-dev43" From aa1f658c7c759a91dee6ce89534b0c76e2b490b4 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 26 Apr 2024 14:29:53 +0000 Subject: [PATCH 85/88] Auto update version from '0.36.0-dev43' to '0.36.0-dev44' --- 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 0dd7399263..b324311e95 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev43" +__version__ = "0.36.0-dev44" From 54ebe9174e693bf9215a4170af42832a33851dcc Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Fri, 26 Apr 2024 10:40:40 -0400 Subject: [PATCH 86/88] Restoring name in changelog (?) --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index cce44affa6..fcba09ca10 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -136,7 +136,7 @@ This release contains contributions from (in alphabetical order): -Ali Asadi, Amintor Dusko, Pietropaolo Frisoni, Christina Lee, Vincent Michaud-Rioux, Mudit Pandey, Shuli Shu +Ali Asadi, Amintor Dusko, Pietropaolo Frisoni, Christina Lee, Vincent Michaud-Rioux, Lee James O'Riordan, Mudit Pandey, Shuli Shu --- From fd82258f4c42f418ff3aa790f1a53be2feaa9c93 Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 29 Apr 2024 12:11:01 -0400 Subject: [PATCH 87/88] Increasing time limit for Python tests --- .github/workflows/tests_linux_python.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests_linux_python.yml b/.github/workflows/tests_linux_python.yml index 5e6d9f0af9..4a61f7a70d 100644 --- a/.github/workflows/tests_linux_python.yml +++ b/.github/workflows/tests_linux_python.yml @@ -37,7 +37,7 @@ jobs: matrix: os: [ubuntu-22.04] pl_backend: ["lightning_qubit"] - timeout-minutes: 60 + timeout-minutes: 75 name: Python tests runs-on: ${{ matrix.os }} @@ -144,7 +144,7 @@ jobs: matrix: os: [ubuntu-22.04] pl_backend: ["lightning_qubit"] - timeout-minutes: 60 + timeout-minutes: 75 name: Python tests with OpenBLAS runs-on: ${{ matrix.os }} @@ -257,7 +257,7 @@ jobs: exclude: - pl_backend: ["all"] exec_model: OPENMP - timeout-minutes: 60 + timeout-minutes: 75 name: Python tests with Kokkos runs-on: ${{ matrix.os }} From 13d1c117bd38305272d09f38228eef0d9e87063d Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 29 Apr 2024 14:05:12 -0400 Subject: [PATCH 88/88] Applying suggestions from code review --- .../lightning_tensor/{ => backends}/quimb/_mps.py | 4 ++-- pennylane_lightning/lightning_tensor/lightning_tensor.py | 9 +++++++-- requirements-dev.txt | 2 +- tests/lightning_tensor/test_quimb_mps.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) rename pennylane_lightning/lightning_tensor/{ => backends}/quimb/_mps.py (96%) diff --git a/pennylane_lightning/lightning_tensor/quimb/_mps.py b/pennylane_lightning/lightning_tensor/backends/quimb/_mps.py similarity index 96% rename from pennylane_lightning/lightning_tensor/quimb/_mps.py rename to pennylane_lightning/lightning_tensor/backends/quimb/_mps.py index feb7bac487..c5f53b15ec 100644 --- a/pennylane_lightning/lightning_tensor/quimb/_mps.py +++ b/pennylane_lightning/lightning_tensor/backends/quimb/_mps.py @@ -83,9 +83,9 @@ def state(self): """Current MPS handled by the interface.""" return self._circuitMPS.psi - def state_to_array(self, digits: int = 5): + def state_to_array(self) -> np.ndarray: """Contract the MPS into a dense array.""" - return self._circuitMPS.to_dense().round(digits) + return self._circuitMPS.to_dense() def _initial_mps(self) -> qtn.MatrixProductState: r""" diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index c13dd1f386..fc8974cda2 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -27,7 +27,7 @@ from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -from .quimb._mps import QuimbMPS +from .backends.quimb._mps import QuimbMPS Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] @@ -119,7 +119,7 @@ def __init__( # options for MPS self._max_bond_dim = kwargs.get("max_bond_dim", None) - self._cutoff = kwargs.get("cutoff", 1e-16) + self._cutoff = kwargs.get("cutoff", np.finfo(self._c_dtype).eps) self._interface = None interface_opts = self._setup_execution_config().device_options @@ -131,6 +131,11 @@ def __init__( self._c_dtype, ) + else: + raise ValueError( + f"Unsupported backend: {self.backend} or method: {self.method}" + ) # pragma: no cover + for arg in kwargs: if arg not in self._device_options: raise TypeError( diff --git a/requirements-dev.txt b/requirements-dev.txt index a848414d47..9d0e09b02c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,5 +17,5 @@ click==8.0.4 cmake custatevec-cu12 pylint -scipy==1.12.0 +scipy~=1.12.0 quimb diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py index 987d1f69a5..8f3e376e56 100644 --- a/tests/lightning_tensor/test_quimb_mps.py +++ b/tests/lightning_tensor/test_quimb_mps.py @@ -46,6 +46,6 @@ def test_device_init(self, num_wires, c_dtype, backend, method): assert isinstance(dev._interface.state, qtn.MatrixProductState) assert isinstance(dev._interface.state_to_array(), np.ndarray) - program, config = dev.preprocess() + _, config = dev.preprocess() assert config.device_options["backend"] == backend assert config.device_options["method"] == method