From 2c0fdf0ffd87ea05633f24c0e09714b6e681a54c Mon Sep 17 00:00:00 2001 From: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:44:40 -0400 Subject: [PATCH] `qinfo` module deprecations and removals (#5911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Context:** As part of our v0.39 deprecation cycle, `qinfo` will be deprecated. All PRs for deprecating each function can be merged to here, **Description of the Change:** Deprecates `qinfo`. Specifically: - [x] deprecate `qinfo.mutual_info`: https://github.com/PennyLaneAI/pennylane/pull/5917 - [x] deprecate `qinfo.reduced_dm`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.purity`: https://github.com/PennyLaneAI/pennylane/pull/5916 - [x] deprecate `qinfo.vn_entropy`: https://github.com/PennyLaneAI/pennylane/pull/5912 - [x] deprecate `qinfo.vn_entanglement_entropy` https://github.com/PennyLaneAI/pennylane/pull/5914 - [x] deprecate `qinfo.fidelity`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.relative_entropy`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.trace_distance` - [x] remove `qinfo.classical_fisher` - [x] remove `qinfo.quantum_fisher` **Benefits:** Desired UI and less redundancies. **Possible Drawbacks:** None **Related GitHub Issues:** [sc-67217] [sc-67216] [sc-66716] [sc-66715] [sc-66714] [sc-66713] [sc-67664] [sc-67665] [sc-67663] [sc-67446] --------- Co-authored-by: Mudit Pandey Co-authored-by: Astral Cai Co-authored-by: Ahmed Darwish Co-authored-by: Cristian Emiliano Godinez Ramirez <57567043+EmilianoG-byte@users.noreply.github.com> Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> Co-authored-by: albi3ro Co-authored-by: Christina Lee Co-authored-by: ringo-but-quantum Co-authored-by: David Wierichs Co-authored-by: Thomas R. Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Tonmoy Bhattacharya Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Matthew Silverman Co-authored-by: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Co-authored-by: Utkarsh Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: Romain Moyard Co-authored-by: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Co-authored-by: soranjh Co-authored-by: Jorge J. Martínez de Lejarza <61199780+gmlejarza@users.noreply.github.com> Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com> Co-authored-by: Alex Preciado Co-authored-by: Mikhail Andrenkov Co-authored-by: Jack Brown Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> Co-authored-by: Pietropaolo Frisoni Co-authored-by: Justin Pickering <79890410+justinpickering@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Will Co-authored-by: Josh Izaac Co-authored-by: Lee James O'Riordan Co-authored-by: Pietropaolo Frisoni --- doc/code/qml_qinfo.rst | 11 +- doc/development/deprecations.rst | 20 +- doc/releases/changelog-dev.md | 10 + pennylane/devices/default_mixed.py | 5 +- pennylane/measurements/mutual_info.py | 2 +- pennylane/measurements/purity.py | 2 +- pennylane/measurements/vn_entropy.py | 6 +- pennylane/qinfo/__init__.py | 2 - pennylane/qinfo/transforms.py | 399 +++++------------- tests/devices/test_default_mixed.py | 7 +- tests/measurements/test_mutual_info.py | 47 ++- tests/qinfo/test_entropies.py | 67 ++- tests/qinfo/test_fidelity.py | 39 +- tests/qinfo/test_fisher_deprecation.py | 47 --- tests/qinfo/test_purity.py | 24 +- tests/qinfo/test_reduced_dm.py | 23 +- tests/qinfo/test_trace_distance.py | 22 +- ... => test_vn_entanglement_entropy_qinfo.py} | 20 + tests/shadow/test_shadow_entropies.py | 10 +- 19 files changed, 367 insertions(+), 396 deletions(-) delete mode 100644 tests/qinfo/test_fisher_deprecation.py rename tests/qinfo/{test_vn_entanglement_entropy.py => test_vn_entanglement_entropy_qinfo.py} (92%) diff --git a/doc/code/qml_qinfo.rst b/doc/code/qml_qinfo.rst index 04563167e2c..02674bbce41 100644 --- a/doc/code/qml_qinfo.rst +++ b/doc/code/qml_qinfo.rst @@ -4,6 +4,13 @@ qml.qinfo Overview -------- +.. warning:: + + The ``qinfo`` module is deprecated and scheduled to be removed in v0.40. Most quantum information transforms + are available as measurement processes (see the :mod:`~pennylane.measurements` module for more details). + Additionally, the transforms are also available as standalone functions in the :mod:`~pennylane.math` and + :mod:`~pennylane.gradients` modules. + This module provides a collection of methods to return quantum information quantities from :class:`~.QNode` returning :func:`~pennylane.state`. @@ -15,8 +22,4 @@ Transforms .. automodapi:: pennylane.qinfo.transforms :no-heading: :no-inherited-members: - :skip: metric_tensor - :skip: adjoint_metric_tensor :skip: transform - :skip: classical_fisher - :skip: quantum_fisher diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 22b478ab3a5..f44598fc765 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,12 +9,18 @@ deprecations are listed below. Pending deprecations -------------------- +* The ``qml.qinfo`` module has been deprecated. Please see the respective functions in the ``qml.math`` and ``qml.measurements`` + modules instead. + + - Deprecated in v0.39 + - Will be removed in v0.40 + * ``Device``, ``QubitDevice``, and ``QutritDevice`` will no longer be imported top level in v0.40. They instead we be available as ``qml.devices.LegacyDevice``, ``qml.devices.QubitDevice``, and ``qml.devices.QutritDevice`` respectively. - Deprecated top level access in v0.39 - - Top level access removed in v0.40 + - Top level access will be removed in v0.40 * `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to process the diff method. @@ -27,12 +33,6 @@ Pending deprecations - Deprecated in v0.38 - Will be removed in v0.39 -* The functions ``qml.qinfo.classical_fisher`` and ``qml.qinfo.quantum_fisher`` are deprecated since they are being migrated - to the ``qml.gradients`` module. Therefore, ``qml.gradients.classical_fisher`` and ``qml.gradients.quantum_fisher`` should be used instead. - - - Deprecated and Duplicated in v0.38 - - Will be removed in v0.39 - * The ``simplify`` argument in ``qml.Hamiltonian`` and ``qml.ops.LinearCombination`` is deprecated. Instead, ``qml.simplify()`` can be called on the constructed operator. @@ -89,6 +89,12 @@ Other deprecations Completed deprecation cycles ---------------------------- +* The functions ``qml.qinfo.classical_fisher`` and ``qml.qinfo.quantum_fisher`` have been removed and migrated to the ``qml.gradients`` + module. Therefore, ``qml.gradients.classical_fisher`` and ``qml.gradients.quantum_fisher`` should be used instead. + + - Deprecated in v0.38 + - Removed in v0.39 + * All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are removed. Use ``default.qubit`` instead, as it supports backpropagation for the many backends the legacy devices support. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index b03ff7b4f72..c75f86c9e55 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -63,6 +63,10 @@

Breaking changes 💔

+* The functions `qml.qinfo.classical_fisher` and `qml.qinfo.quantum_fisher` have been removed and migrated to the `qml.gradients` + module. Therefore, `qml.gradients.classical_fisher` and `qml.gradients.quantum_fisher` should be used instead. + [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) + * Remove support for Python 3.9. [(#6223)](https://github.com/PennyLaneAI/pennylane/pull/6223) @@ -93,6 +97,10 @@

Deprecations 👋

+* The `qml.qinfo` module has been deprecated. Please see the respective functions in the `qml.math` and + `qml.measurements` modules instead. + [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) + * The ``QubitStateVector`` template is deprecated. Instead, use ``StatePrep``. [(#6172)](https://github.com/PennyLaneAI/pennylane/pull/6172) @@ -140,10 +148,12 @@ This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, Astral Cai, +Isaac De Vlugt, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Christina Lee, William Maxwell, Lee J. O'Riordan, +Mudit Pandey, David Wierichs, diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py index d7a3a55e0b5..d0284e4b3e8 100644 --- a/pennylane/devices/default_mixed.py +++ b/pennylane/devices/default_mixed.py @@ -763,8 +763,9 @@ def execute(self, circuit, **kwargs): self.measured_wires = self.wires return super().execute(circuit, **kwargs) if isinstance(m, (VnEntropyMP, MutualInfoMP)): - # VnEntropy, MutualInfo: Computed for the state prior to measurement. So, readout - # error need not be applied on the corresponding device wires. + # VnEntropy, MutualInfo: Computed for the state + # prior to measurement. So, readout error need not be applied to the + # corresponding device wires. continue wires_list.append(m.wires) self.measured_wires = qml.wires.Wires.all_wires(wires_list) diff --git a/pennylane/measurements/mutual_info.py b/pennylane/measurements/mutual_info.py index 18335fe4306..81e39864896 100644 --- a/pennylane/measurements/mutual_info.py +++ b/pennylane/measurements/mutual_info.py @@ -74,7 +74,7 @@ def circuit_mutual(x): using the classical backpropagation differentiation method (``diff_method="backprop"``) with a compatible device and finite differences (``diff_method="finite-diff"``). - .. seealso:: :func:`~.vn_entropy`, :func:`pennylane.qinfo.transforms.mutual_info` and :func:`pennylane.math.mutual_info` + .. seealso:: :func:`~pennylane.vn_entropy`, :func:`pennylane.math.mutual_info` """ wires0 = qml.wires.Wires(wires0) wires1 = qml.wires.Wires(wires1) diff --git a/pennylane/measurements/purity.py b/pennylane/measurements/purity.py index 83e4166cbc9..4427f16231d 100644 --- a/pennylane/measurements/purity.py +++ b/pennylane/measurements/purity.py @@ -57,7 +57,7 @@ def circuit_purity(p): >>> circuit_purity(0.1) array(0.7048) - .. seealso:: :func:`pennylane.qinfo.transforms.purity` and :func:`pennylane.math.purity` + .. seealso:: :func:`pennylane.math.purity` """ wires = Wires(wires) return PurityMP(wires=wires) diff --git a/pennylane/measurements/vn_entropy.py b/pennylane/measurements/vn_entropy.py index 79c1a875eae..e07789add66 100644 --- a/pennylane/measurements/vn_entropy.py +++ b/pennylane/measurements/vn_entropy.py @@ -61,11 +61,11 @@ def circuit_entropy(x): .. note:: - Calculating the derivative of :func:`~.vn_entropy` is currently supported when + Calculating the derivative of :func:`~pennylane.vn_entropy` is currently supported when using the classical backpropagation differentiation method (``diff_method="backprop"``) with a compatible device and finite differences (``diff_method="finite-diff"``). - .. seealso:: :func:`pennylane.qinfo.transforms.vn_entropy` and :func:`pennylane.math.vn_entropy` + .. seealso:: :func:`pennylane.math.vn_entropy` """ wires = Wires(wires) return VnEntropyMP(wires=wires, log_base=log_base) @@ -74,7 +74,7 @@ def circuit_entropy(x): class VnEntropyMP(StateMeasurement): """Measurement process that computes the Von Neumann entropy of the system prior to measurement. - Please refer to :func:`vn_entropy` for detailed documentation. + Please refer to :func:`~pennylane.vn_entropy` for detailed documentation. Args: wires (.Wires): The wires the measurement process applies to. diff --git a/pennylane/qinfo/__init__.py b/pennylane/qinfo/__init__.py index 819c52d989f..691e357c0d0 100644 --- a/pennylane/qinfo/__init__.py +++ b/pennylane/qinfo/__init__.py @@ -18,8 +18,6 @@ vn_entropy, purity, mutual_info, - classical_fisher, - quantum_fisher, fidelity, relative_entropy, trace_distance, diff --git a/pennylane/qinfo/transforms.py b/pennylane/qinfo/transforms.py index 07e58cfe33f..d9fceccda04 100644 --- a/pennylane/qinfo/transforms.py +++ b/pennylane/qinfo/transforms.py @@ -19,8 +19,7 @@ import pennylane as qml from pennylane import transform -from pennylane.devices import DefaultMixed, DefaultQubit, DefaultQubitLegacy -from pennylane.gradients import adjoint_metric_tensor, metric_tensor +from pennylane.devices import DefaultMixed from pennylane.measurements import DensityMatrixMP, StateMP from pennylane.tape import QuantumScript, QuantumScriptBatch from pennylane.typing import PostprocessingFn @@ -31,6 +30,11 @@ def reduced_dm(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch """Compute the reduced density matrix from a :class:`~.QNode` returning :func:`~pennylane.state`. + .. warning:: + + The ``qml.qinfo.reduced_dm`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.density_matrix` measurement process in the return line of your QNode. + Args: tape (QuantumTape or QNode or Callable)): A quantum circuit returning :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -78,6 +82,14 @@ def measured_circuit(x): .. seealso:: :func:`pennylane.density_matrix` and :func:`pennylane.math.reduce_dm` """ + + warnings.warn( + "The qml.qinfo.reduced_dm transform is deprecated and will be removed " + "in v0.40. Instead include the qml.density_matrix measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -137,6 +149,11 @@ def purity(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch, Po It is possible to compute the purity of a sub-system from a given state. To find the purity of the overall state, include all wires in the ``wires`` argument. + .. warning:: + + The ``qml.qinfo.purity transform`` is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.purity` measurement process in the return line of your QNode. + Args: tape (QNode or QuantumTape or Callable): A quantum circuit object returning a :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -175,6 +192,14 @@ def circuit(x): .. seealso:: :func:`pennylane.math.purity` """ + + warnings.warn( + "The qml.qinfo.purity transform is deprecated and will be removed " + "in v0.40. Instead include the qml.purity measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -229,6 +254,11 @@ def vn_entropy( .. math:: S( \rho ) = -\text{Tr}( \rho \log ( \rho )) + .. warning:: + + The ``qml.qinfo.vn_entropy`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.vn_entropy` measurement process in the return line of your QNode. + Args: tape (QNode or QuantumTape or Callable): A quantum circuit returning a :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -263,6 +293,14 @@ def circuit(x): .. seealso:: :func:`pennylane.math.vn_entropy` and :func:`pennylane.vn_entropy` """ + + warnings.warn( + "The qml.qinfo.vn_entropy transform is deprecated and will be removed " + "in v0.40. Instead include the qml.vn_entropy measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -366,6 +404,11 @@ def mutual_info( More specifically, it quantifies the amount of information obtained about one system by measuring the other system. + .. warning:: + + The ``qml.qinfo.mutual_info`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.mutual_info` measurement process in the return line of your QNode. + Args: qnode (QNode or QuantumTape or Callable): A quantum circuit returning a :func:`~pennylane.state`. wires0 (Sequence(int)): List of wires in the first subsystem. @@ -403,6 +446,14 @@ def circuit(x): .. seealso:: :func:`~.qinfo.vn_entropy`, :func:`pennylane.math.mutual_info` and :func:`pennylane.mutual_info` """ + + warnings.warn( + "The qml.qinfo.mutual_info transform is deprecated and will be removed " + "in v0.40. Instead include the qml.mutual_info measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + return _bipartite_qinfo_transform(qml.math.mutual_info, tape, wires0, wires1, base, **kwargs) @@ -437,6 +488,10 @@ def vn_entanglement_entropy( where :math:`S` is the von Neumann entropy; :math:`\rho_A = \text{Tr}_B [\rho_{AB}]` and :math:`\rho_B = \text{Tr}_A [\rho_{AB}]` are the reduced density matrices for each partition. + .. warning:: + The ``qml.qinfo.vn_entanglement_entropy`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.vn_entanglement_entropy` measurement process in the return line of your QNode. + The Von Neumann entanglement entropy is a measure of the degree of quantum entanglement between two subsystems constituting a pure bipartite quantum state. The entropy of entanglement is the Von Neumann entropy of the reduced density matrix for any of the subsystems. If it is non-zero, @@ -455,297 +510,18 @@ def vn_entanglement_entropy( will provide the entanglement entropy in the form of a tensor. """ - return _bipartite_qinfo_transform( - qml.math.vn_entanglement_entropy, tape, wires0, wires1, base, **kwargs - ) - - -def classical_fisher(qnode, argnums=0): - r"""Returns a function that computes the classical fisher information matrix (CFIM) of a given :class:`.QNode` or - quantum tape. - - Given a parametrized (classical) probability distribution :math:`p(\bm{\theta})`, the classical fisher information - matrix quantifies how changes to the parameters :math:`\bm{\theta}` are reflected in the probability distribution. - For a parametrized quantum state, we apply the concept of classical fisher information to the computational - basis measurement. - More explicitly, this function implements eq. (15) in `arxiv:2103.15191 `_: - - .. math:: - - \text{CFIM}_{i, j} = \sum_{\ell=0}^{2^N-1} \frac{1}{p_\ell(\bm{\theta})} \frac{\partial p_\ell(\bm{\theta})}{ - \partial \theta_i} \frac{\partial p_\ell(\bm{\theta})}{\partial \theta_j} - - for :math:`N` qubits. - - Args: - tape (:class:`.QNode` or qml.QuantumTape): A :class:`.QNode` or quantum tape that may have arbitrary return types. - argnums (Optional[int or List[int]]): Arguments to be differentiated in case interface ``jax`` is used. - - Returns: - func: The function that computes the classical fisher information matrix. This function accepts the same - signature as the :class:`.QNode`. If the signature contains one differentiable variable ``params``, the function - returns a matrix of size ``(len(params), len(params))``. For multiple differentiable arguments ``x, y, z``, - it returns a list of sizes ``[(len(x), len(x)), (len(y), len(y)), (len(z), len(z))]``. - - .. warning:: - ``pennylane.qinfo.classical_fisher`` is being migrated to a different module and will - removed in version 0.39. Instead, use :func:`pennylane.gradients.classical_fisher`. - - .. seealso:: :func:`~.pennylane.metric_tensor`, :func:`~.pennylane.qinfo.transforms.quantum_fisher` - - **Example** - - First, let us define a parametrized quantum state and return its (classical) probability distribution for all - computational basis elements: - - .. code-block:: python - - import pennylane.numpy as pnp - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def circ(params): - qml.RX(params[0], wires=0) - qml.CNOT([0, 1]) - qml.CRY(params[1], wires=[1, 0]) - qml.Hadamard(1) - return qml.probs(wires=[0, 1]) - - Executing this circuit yields the ``2**2=4`` elements of :math:`p_\ell(\bm{\theta})` - - >>> pnp.random.seed(25) - >>> params = pnp.random.random(2) - >>> circ(params) - [0.41850088 0.41850088 0.08149912 0.08149912] - - We can obtain its ``(2, 2)`` classical fisher information matrix (CFIM) by simply calling the function returned - by ``classical_fisher()``: - - >>> cfim_func = qml.qinfo.classical_fisher(circ) - >>> cfim_func(params) - [[ 0.901561 -0.125558] - [-0.125558 0.017486]] - - This function has the same signature as the :class:`.QNode`. Here is a small example with multiple arguments: - - .. code-block:: python - - @qml.qnode(dev) - def circ(x, y): - qml.RX(x, wires=0) - qml.RY(y, wires=0) - return qml.probs(wires=range(n_wires)) - - >>> x, y = pnp.array([0.5, 0.6], requires_grad=True) - >>> circ(x, y) - [0.86215007 0. 0.13784993 0. ] - >>> qml.qinfo.classical_fisher(circ)(x, y) - [array([[0.32934729]]), array([[0.51650396]])] - - Note how in the case of multiple variables we get a list of matrices with sizes - ``[(n_params0, n_params0), (n_params1, n_params1)]``, which in this case is simply two ``(1, 1)`` matrices. - - - A typical setting where the classical fisher information matrix is used is in variational quantum algorithms. - Closely related to the `quantum natural gradient `_, which employs the - `quantum` fisher information matrix, we can compute a rescaled gradient using the CFIM. In this scenario, - typically a Hamiltonian objective function :math:`\langle H \rangle` is minimized: - - .. code-block:: python - - H = qml.Hamiltonian(coeffs=[0.5, 0.5], observables=[qml.Z(0), qml.Z(1)]) - - @qml.qnode(dev) - def circ(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - qml.RX(params[2], wires=1) - qml.RY(params[3], wires=1) - qml.CNOT(wires=(0,1)) - return qml.expval(H) - - params = pnp.random.random(4) - - We can compute both the gradient of :math:`\langle H \rangle` and the CFIM with the same :class:`.QNode` ``circ`` - in this example since ``classical_fisher()`` ignores the return types and assumes ``qml.probs()`` for all wires. - >>> grad = qml.grad(circ)(params) - >>> cfim = qml.qinfo.classical_fisher(circ)(params) - >>> print(grad.shape, cfim.shape) - (4,) (4, 4) - - Combined together, we can get a rescaled gradient to be employed for optimization schemes like natural gradient - descent. - - >>> rescaled_grad = cfim @ grad - >>> print(rescaled_grad) - [-0.66772533 -0.16618756 -0.05865127 -0.06696078] - - The ``classical_fisher`` matrix itself is again differentiable: - - .. code-block:: python - - @qml.qnode(dev) - def circ(params): - qml.RX(qml.math.cos(params[0]), wires=0) - qml.RX(qml.math.cos(params[0]), wires=1) - qml.RX(qml.math.cos(params[1]), wires=0) - qml.RX(qml.math.cos(params[1]), wires=1) - return qml.probs(wires=range(2)) - - params = pnp.random.random(2) - - >>> qml.qinfo.classical_fisher(circ)(params) - [[4.18575068e-06 2.34443943e-03] - [2.34443943e-03 1.31312079e+00]] - >>> qml.jacobian(qml.qinfo.classical_fisher(circ))(params) - array([[[9.98030491e-01, 3.46944695e-18], - [1.36541817e-01, 5.15248592e-01]], - [[1.36541817e-01, 5.15248592e-01], - [2.16840434e-18, 2.81967252e-01]]])) - - """ warnings.warn( - "pennylane.qinfo.classical_fisher is being migrated to a different module and will " - "removed in version 0.39. Instead, use pennylane.gradients.classical_fisher.", + "The qml.qinfo.vn_entanglement_entropy transform is deprecated and will be removed " + "in v0.40. Instead include the qml.vn_entanglement_entropy measurement process in the " + "return line of your QNode.", qml.PennyLaneDeprecationWarning, ) - return qml.gradients.classical_fisher(qnode, argnums=argnums) - - -@partial(transform, is_informative=True) -def quantum_fisher( - tape: QuantumScript, device, *args, **kwargs -) -> tuple[QuantumScriptBatch, PostprocessingFn]: - r"""Returns a function that computes the quantum fisher information matrix (QFIM) of a given :class:`.QNode`. - - Given a parametrized quantum state :math:`|\psi(\bm{\theta})\rangle`, the quantum fisher information matrix (QFIM) quantifies how changes to the parameters :math:`\bm{\theta}` - are reflected in the quantum state. The metric used to induce the QFIM is the fidelity :math:`f = |\langle \psi | \psi' \rangle|^2` between two (pure) quantum states. - This leads to the following definition of the QFIM (see eq. (27) in `arxiv:2103.15191 `_): - - .. math:: - - \text{QFIM}_{i, j} = 4 \text{Re}\left[ \langle \partial_i \psi(\bm{\theta}) | \partial_j \psi(\bm{\theta}) \rangle - - \langle \partial_i \psi(\bm{\theta}) | \psi(\bm{\theta}) \rangle \langle \psi(\bm{\theta}) | \partial_j \psi(\bm{\theta}) \rangle \right] - - with short notation :math:`| \partial_j \psi(\bm{\theta}) \rangle := \frac{\partial}{\partial \theta_j}| \psi(\bm{\theta}) \rangle`. - - .. seealso:: - :func:`~.pennylane.metric_tensor`, :func:`~.pennylane.adjoint_metric_tensor`, :func:`~.pennylane.qinfo.transforms.classical_fisher` - - Args: - tape (QNode or QuantumTape or Callable): A quantum circuit that may have arbitrary return types. - *args: In case finite shots are used, further arguments according to :func:`~.pennylane.metric_tensor` may be passed. - - Returns: - qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: - - The transformed circuit as described in :func:`qml.transform `. Executing this circuit - will provide the quantum Fisher information in the form of a tensor. - - .. warning:: - ``pennylane.qinfo.quantum_fisher`` is being migrated to a different module and will - removed in version 0.39. Instead, use :func:`pennylane.gradients.quantum_fisher`. - - .. note:: - - ``quantum_fisher`` coincides with the ``metric_tensor`` with a prefactor of :math:`4`. - Internally, :func:`~.pennylane.adjoint_metric_tensor` is used when executing on ``"default.qubit"`` - with exact expectations (``shots=None``). In all other cases, e.g. if a device with finite shots - is used, the hardware-compatible transform :func:`~.pennylane.metric_tensor` is used, which - may require an additional wire on the device. - Please refer to the respective documentations for details. - - **Example** - - The quantum Fisher information matrix (QIFM) can be used to compute the `natural` gradient for `Quantum Natural Gradient Descent `_. - A typical scenario is optimizing the expectation value of a Hamiltonian: - - .. code-block:: python - - n_wires = 2 - - dev = qml.device("default.qubit", wires=n_wires) - - H = 1.*qml.X(0) @ qml.X(1) - 0.5 * qml.Z(1) - - @qml.qnode(dev) - def circ(params): - qml.RY(params[0], wires=1) - qml.CNOT(wires=(1,0)) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=1) - return qml.expval(H) - - params = pnp.array([0.5, 1., 0.2], requires_grad=True) - - The natural gradient is then simply the QFIM multiplied by the gradient: - - >>> grad = qml.grad(circ)(params) - >>> grad - [ 0.59422561 -0.02615095 -0.05146226] - >>> qfim = qml.qinfo.quantum_fisher(circ)(params) - >>> qfim - [[1. 0. 0. ] - [0. 1. 0. ] - [0. 0. 0.77517241]] - >>> qfim @ grad - tensor([ 0.59422561, -0.02615095, -0.03989212], requires_grad=True) - - When using real hardware or finite shots, ``quantum_fisher`` is internally calling :func:`~.pennylane.metric_tensor`. - To obtain the full QFIM, we need an auxilary wire to perform the Hadamard test. - - >>> dev = qml.device("default.qubit", wires=n_wires+1, shots=1000) - >>> @qml.qnode(dev) - ... def circ(params): - ... qml.RY(params[0], wires=1) - ... qml.CNOT(wires=(1,0)) - ... qml.RY(params[1], wires=1) - ... qml.RZ(params[2], wires=1) - ... return qml.expval(H) - >>> qfim = qml.qinfo.quantum_fisher(circ)(params) - - Alternatively, we can fall back on the block-diagonal QFIM without the additional wire. - - >>> qfim = qml.qinfo.quantum_fisher(circ, approx="block-diag")(params) - - """ - warnings.warn( - "pennylane.qinfo.quantum_fisher is being migrated to a different module and will " - "removed in version 0.39. Instead, use pennylane.gradients.quantum_fisher.", - qml.PennyLaneDeprecationWarning, + return _bipartite_qinfo_transform( + qml.math.vn_entanglement_entropy, tape, wires0, wires1, base, **kwargs ) - if device.shots or not isinstance(device, (DefaultQubitLegacy, DefaultQubit)): - tapes, processing_fn = metric_tensor(tape, *args, **kwargs) - - def processing_fn_multiply(res): - res = qml.execute(res, device=device) - return 4 * processing_fn(res) - - return tapes, processing_fn_multiply - - res = adjoint_metric_tensor(tape, *args, **kwargs) - - def processing_fn_multiply(r): # pylint: disable=function-redefined - r = qml.math.stack(r) - return 4 * r - - return res, processing_fn_multiply - - -@quantum_fisher.custom_qnode_transform -def qnode_execution_wrapper(self, qnode, targs, tkwargs): - """Here, we overwrite the QNode execution wrapper in order - to take into account that classical processing may be present - inside the QNode.""" - - tkwargs["device"] = qnode.device - - return self.default_qnode_transform(qnode, targs, tkwargs) - def fidelity(qnode0, qnode1, wires0, wires1): r"""Compute the fidelity for two :class:`.QNode` returning a :func:`~pennylane.state` (a state can be a state vector @@ -773,6 +549,11 @@ def fidelity(qnode0, qnode1, wires0, wires1): The second state is coerced to the type and dtype of the first state. The fidelity is returned in the type of the interface of the first state. + .. warning:: + + ``qml.qinfo.fidelity`` is deprecated and will be removed in v0.40. Instead, use + :func:`pennylane.math.fidelity`. + Args: state0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. state1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -877,9 +658,21 @@ def circuit_ry(y, use_ry): .. seealso:: :func:`pennylane.math.fidelity` """ + warnings.warn( + "qml.qinfo.fidelity is deprecated and will be removed in v0.40. Instead, use " + "qml.math.fidelity.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) @@ -947,6 +740,11 @@ def relative_entropy(qnode0, qnode1, wires0, wires1): Roughly speaking, quantum relative entropy is a measure of distinguishability between two quantum states. It is the quantum mechanical analog of relative entropy. + .. warning:: + + ``qml.qinfo.relative_entropy`` is deprecated and will be removed in v0.40. Instead, use + :func:`~pennylane.math.relative_entropy`. + Args: qnode0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. qnode1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -997,9 +795,21 @@ def wrapper(x, y): tensor(0.16953273, requires_grad=True)) """ + warnings.warn( + "qml.qinfo.relative_entropy is deprecated and will be removed in v0.40. Instead, use " + "qml.math.relative_entropy.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) @@ -1068,6 +878,11 @@ def trace_distance(qnode0, qnode1, wires0, wires1): The trace distance measures how close two quantum states are. In particular, it upper-bounds the probability of distinguishing two quantum states. + .. warning:: + + ``qml.qinfo.trace_distance`` is deprecated and will be removed in v0.40. Instead, use + :func:`~pennylane.math.trace_distance`. + Args: qnode0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. qnode1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -1118,9 +933,21 @@ def wrapper(x, y): tensor(0.28232124, requires_grad=True)) """ + warnings.warn( + "qml.qinfo.trace_distance is deprecated and will be removed in v0.40. Instead, use " + "qml.math.trace_distance.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) diff --git a/tests/devices/test_default_mixed.py b/tests/devices/test_default_mixed.py index a6bf45665a4..86cdbf02430 100644 --- a/tests/devices/test_default_mixed.py +++ b/tests/devices/test_default_mixed.py @@ -1203,13 +1203,14 @@ def circuit(): @pytest.mark.parametrize("prob", [0, 0.5, 1]) @pytest.mark.parametrize("nr_wires", [2, 3]) def test_readout_vnentropy_and_mutualinfo(self, nr_wires, prob): - """Tests the output of qml.vn_entropy and qml.mutual_info is not affected by readout error""" + """Tests the output of qml.vn_entropy and qml.mutual_info are not affected by readout error""" dev = qml.device("default.mixed", wires=nr_wires, readout_prob=prob) @qml.qnode(dev) def circuit(): - return qml.vn_entropy(wires=0, log_base=2), qml.mutual_info( - wires0=[0], wires1=[1], log_base=2 + return ( + qml.vn_entropy(wires=0, log_base=2), + qml.mutual_info(wires0=[0], wires1=[1], log_base=2), ) res = circuit() diff --git a/tests/measurements/test_mutual_info.py b/tests/measurements/test_mutual_info.py index a47284f744b..fa0ab30b83d 100644 --- a/tests/measurements/test_mutual_info.py +++ b/tests/measurements/test_mutual_info.py @@ -22,6 +22,12 @@ from pennylane.measurements.mutual_info import MutualInfoMP from pennylane.wires import Wires +DEP_WARNING_MESSAGE_MUTUAL_INFO = ( + "The qml.qinfo.mutual_info transform is deprecated and will be removed " + "in v0.40. Instead include the qml.mutual_info measurement process in the " + "return line of your QNode." +) + class TestMutualInfoUnitTests: """Tests for the mutual_info function""" @@ -143,7 +149,11 @@ def circuit(params): qml.CNOT(wires=[0, 1]) return qml.state() - actual = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1])(params) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1])(params) # compare transform results with analytic values expected = -2 * np.cos(params / 2) ** 2 * np.log( @@ -175,12 +185,13 @@ def circuit_state(params): qml.RY(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) - return qml.state() + return qml.density_matrix(wires=[0, 1]) actual = circuit_mutual_info(params) # compare measurement results with transform results - expected = qml.qinfo.mutual_info(circuit_state, wires0=[0], wires1=[1])(params) + dm = circuit_state(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) assert np.allclose(actual, expected) @@ -197,7 +208,11 @@ def circuit(param): qml.CNOT(wires=wires) return qml.state() - actual = qml.qinfo.mutual_info(circuit, wires0=[wires[0]], wires1=[wires[1]])(param) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = qml.qinfo.mutual_info(circuit, wires0=[wires[0]], wires1=[wires[1]])(param) # compare transform results with analytic values expected = -2 * np.cos(param / 2) ** 2 * np.log(np.cos(param / 2) ** 2) - 2 * np.sin( @@ -236,7 +251,11 @@ def circuit(params): transformed_circuit = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]) with pytest.raises(ValueError, match="The qfunc return type needs to be a state"): - _ = transformed_circuit(0.1) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + _ = transformed_circuit(0.1) @pytest.mark.jax @pytest.mark.parametrize("params", np.linspace(0, 2 * np.pi, 8)) @@ -256,7 +275,11 @@ def circuit(params): qml.CNOT(wires=[0, 1]) return qml.state() - actual = jax.jit(qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]))(params) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = jax.jit(qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]))(params) # compare transform results with analytic values expected = -2 * jnp.cos(params / 2) ** 2 * jnp.log( @@ -290,12 +313,13 @@ def circuit_state(params): qml.RY(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) - return qml.state() + return qml.density_matrix(wires=[0, 1]) actual = jax.jit(circuit_mutual_info)(params) # compare measurement results with transform results - expected = jax.jit(qml.qinfo.mutual_info(circuit_state, wires0=[0], wires1=[1]))(params) + dm = circuit_state(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) assert np.allclose(actual, expected) @@ -513,8 +537,11 @@ def circuit_expected(params): qml.RY(params[0], wires="a") qml.RY(params[1], wires="b") qml.CNOT(wires=["a", "b"]) - return qml.state() + return qml.density_matrix(wires=["a", "b"]) actual = circuit(params) - expected = qml.qinfo.mutual_info(circuit_expected, wires0=["a"], wires1=["b"])(params) + + dm = circuit_expected(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) + assert np.allclose(actual, expected) diff --git a/tests/qinfo/test_entropies.py b/tests/qinfo/test_entropies.py index 92b8e48971b..c275036a58e 100644 --- a/tests/qinfo/test_entropies.py +++ b/tests/qinfo/test_entropies.py @@ -19,6 +19,15 @@ import pennylane as qml from pennylane import numpy as np +pytestmark = [ + pytest.mark.filterwarnings( + r"ignore:The qml\.qinfo\.(vn_entropy|mutual_info|reduced_dm) transform:pennylane.PennyLaneDeprecationWarning" + ), + pytest.mark.filterwarnings( + "ignore:qml.qinfo.relative_entropy is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] + def expected_entropy_ising_xx(param): """ @@ -70,6 +79,21 @@ class TestVonNeumannEntropy: parameters = np.linspace(0, 2 * np.pi, 10) devices = ["default.qubit", "default.mixed", "lightning.qubit"] + def test_qinfo_vn_entropy_deprecated(self): + """Test that qinfo.vn_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.vn_entropy transform is deprecated", + ): + _ = qml.qinfo.vn_entropy(circuit, [0])() + def test_vn_entropy_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the vn_entropy transform manually.""" @@ -102,7 +126,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -124,7 +147,6 @@ def circuit_state(x): return qml.state() grad_entropy = qml.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))(param) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy) @@ -148,7 +170,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(torch.tensor(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -176,8 +197,8 @@ def circuit_state(x): grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) - entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) + entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) entropy.backward() grad_entropy = param.grad @@ -203,7 +224,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(tf.Variable(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -229,7 +249,6 @@ def circuit_state(x): entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) grad_entropy = tape.gradient(entropy, param) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy) @@ -254,7 +273,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(jnp.array(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -278,7 +296,6 @@ def circuit_state(x): grad_entropy = jax.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))( jax.numpy.array(param) ) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) @@ -305,7 +322,6 @@ def circuit_state(x): entropy = jax.jit(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))( jnp.array(param) ) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -329,7 +345,6 @@ def circuit_state(x): grad_entropy = jax.jit( jax.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)) )(jax.numpy.array(param)) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) @@ -361,7 +376,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=[0, 1])(param) - expected_entropy = 0.0 assert qml.math.allclose(entropy, expected_entropy) @@ -377,7 +391,6 @@ def circuit_state(x): entropy = qml.qinfo.vn_entropy(circuit_state, wires=[0, 1])(param) expected_entropy = 0.0 - assert qml.math.allclose(entropy, expected_entropy) @pytest.mark.parametrize("device", devices) @@ -394,10 +407,12 @@ def circuit(x): return qml.state() entropy0 = qml.qinfo.vn_entropy(circuit, wires=[wires[0]])(param) + eigs0 = [np.sin(param / 2) ** 2, np.cos(param / 2) ** 2] exp0 = -np.sum(eigs0 * np.log(eigs0)) entropy1 = qml.qinfo.vn_entropy(circuit, wires=[wires[1]])(param) + eigs1 = [np.cos(param / 2) ** 2, np.sin(param / 2) ** 2] exp1 = -np.sum(eigs1 * np.log(eigs1)) @@ -406,7 +421,7 @@ def circuit(x): class TestRelativeEntropy: - """Tests for the mutual information functions""" + """Tests for the relative entropy information functions""" diff_methods = ["backprop", "finite-diff"] @@ -415,6 +430,25 @@ class TestRelativeEntropy: # to avoid nan values in the gradient for relative entropy grad_params = [[0.123, 0.456], [0.789, 1.618]] + def test_qinfo_relative_entropy_deprecated(self): + """Test that qinfo.relative_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(param): + qml.RY(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.state() + + x, y = 0.4, 0.6 + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.relative_entropy is deprecated", + ): + _ = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0])((x,), (y,)) + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @@ -464,7 +498,7 @@ def circuit2(param): @pytest.mark.parametrize("param", params) @pytest.mark.parametrize("interface", interfaces) def test_qnode_relative_entropy_jax_jit(self, param, interface): - """Test that the mutual information transform works for QNodes by comparing + """Test that the relative entropy transform works for QNodes by comparing against analytic values, for the JAX-jit interface""" import jax import jax.numpy as jnp @@ -660,6 +694,7 @@ def circuit2(param): ] param0, param1 = tf.Variable(param[0]), tf.Variable(param[1]) + with tf.GradientTape() as tape: out = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [1])((param0,), (param1,)) @@ -701,9 +736,9 @@ def circuit2(param): param0 = torch.tensor(param[0], requires_grad=True) param1 = torch.tensor(param[1], requires_grad=True) + out = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [1])((param0,), (param1,)) out.backward() - actual = [param0.grad, param1.grad] assert np.allclose(actual, expected, atol=1e-8) @@ -748,7 +783,6 @@ def circuit2(param): return qml.state() rel_ent_circuit = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [0]) - x, y = np.array(0.3), np.array(0.7) # test that the circuit executes @@ -878,6 +912,7 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) y = np.array([0.6, 0.8, 1.0]) + entropy = qml.qinfo.relative_entropy(circuit_state, circuit_state, wires0=[0], wires1=[1])( x, y ) diff --git a/tests/qinfo/test_fidelity.py b/tests/qinfo/test_fidelity.py index 90c59386d00..5ace7162908 100644 --- a/tests/qinfo/test_fidelity.py +++ b/tests/qinfo/test_fidelity.py @@ -18,6 +18,10 @@ import pennylane as qml from pennylane import numpy as np +pytestmark = pytest.mark.filterwarnings( + "ignore:qml.qinfo.fidelity is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def expected_fidelity_rx_pauliz(param): """Return the analytical fidelity for the RX and PauliZ.""" @@ -34,6 +38,21 @@ class TestFidelityQnode: devices = ["default.qubit", "lightning.qubit", "default.mixed"] + def test_qinfo_transform_deprecated(self): + """Test that qinfo.fidelity is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.fidelity is deprecated", + ): + _ = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[1])() + @pytest.mark.parametrize("device", devices) def test_not_same_number_wires(self, device): """Test that wires must have the same length.""" @@ -120,7 +139,7 @@ def circuit1(x): qml.RX(x, wires=0) return qml.state() - fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(all_args1=(np.pi)) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(all_args1=np.pi) assert qml.math.allclose(fid, 0.0) @pytest.mark.parametrize("device", devices) @@ -266,6 +285,7 @@ def circuit(x): (qml.numpy.array(param, requires_grad=True)), (qml.numpy.array(2 * param, requires_grad=True)), ) + expected = expected_grad_fidelity_rx_pauliz(param) expected_fid = [-expected, expected] assert qml.math.allclose(fid_grad, expected_fid) @@ -319,6 +339,7 @@ def circuit1(): expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param)) fid.backward() fid_grad = param.grad @@ -347,6 +368,7 @@ def circuit1(x): expected_fid = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(None, (param)) fid.backward() fid_grad = param.grad @@ -374,6 +396,7 @@ def circuit(x): torch.tensor(param, dtype=torch.float64, requires_grad=True), torch.tensor(2 * param, dtype=torch.float64, requires_grad=True), ) + fid = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[0])(*params) fid.backward() fid_grad = [p.grad for p in params] @@ -428,9 +451,9 @@ def circuit1(): expected_grad_fid = expected_grad_fidelity_rx_pauliz(param) param = tf.Variable(param) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param)) - fid_grad = tape.gradient(entropy, param) assert qml.math.allclose(fid_grad, expected_grad_fid) @@ -456,9 +479,9 @@ def circuit1(x): expected_fid = expected_grad_fidelity_rx_pauliz(param) param = tf.Variable(param) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(None, (param)) - fid_grad = tape.gradient(entropy, param) assert qml.math.allclose(fid_grad, expected_fid) @@ -480,9 +503,9 @@ def circuit(x): expected = expected_grad_fidelity_rx_pauliz(param) expected_fid = [-expected, expected] params = (tf.Variable(param), tf.Variable(2 * param)) + with tf.GradientTape() as tape: fid = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[0])(*params) - fid_grad = tape.gradient(fid, params) assert qml.math.allclose(fid_grad, expected_fid) @@ -712,7 +735,8 @@ def circuit1(x): return qml.state() fid_grad = qml.grad(qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0]))( - (qml.numpy.array(param, requires_grad=True)), (qml.numpy.array(2.0, requires_grad=True)) + (qml.numpy.array(param, requires_grad=True)), + (qml.numpy.array(2.0, requires_grad=True)), ) expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) assert qml.math.allclose(fid_grad, (expected_fid_grad, 0.0)) @@ -744,6 +768,7 @@ def circuit1(x): expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) param2 = torch.tensor(0, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param), (param2)) fid.backward() fid_grad = (param.grad, param2.grad) @@ -777,11 +802,11 @@ def circuit1(x): param1 = tf.Variable(param) params2 = tf.Variable(0.0) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( (param1), (params2) ) - fid_grad = tape.gradient(entropy, [param1, params2]) assert qml.math.allclose(fid_grad, (expected_fid_grad, 0.0)) @@ -858,7 +883,7 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) y = np.array([0.6, 0.8, 1.0]) - fid = qml.qinfo.fidelity(circuit_state, circuit_state, wires0=[0], wires1=[1])(x, y) + fid = qml.qinfo.fidelity(circuit_state, circuit_state, wires0=[0], wires1=[1])(x, y) expected = 0.5 * (np.sin(x) * np.sin(y) + np.cos(x) * np.cos(y) + 1) assert qml.math.allclose(fid, expected) diff --git a/tests/qinfo/test_fisher_deprecation.py b/tests/qinfo/test_fisher_deprecation.py deleted file mode 100644 index b5412f56a82..00000000000 --- a/tests/qinfo/test_fisher_deprecation.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for the deprecation of the classical and quantum fisher information matrix in the pennylane.qinfo -""" -import pytest - -import pennylane as qml -import pennylane.numpy as pnp -from pennylane.qinfo import classical_fisher, quantum_fisher - - -@pytest.mark.parametrize("fn", (classical_fisher, quantum_fisher)) -def test_qinfo_fisher_fns_raises_warning(fn): - n_wires = 3 - n_params = 3 - - dev = qml.device("default.qubit", shots=10000) - - @qml.qnode(dev) - def circ(params): - for i in range(n_wires): - qml.Hadamard(wires=i) - - for x in params: - for j in range(n_wires): - qml.RX(x, wires=j) - qml.RY(x, wires=j) - qml.RZ(x, wires=j) - - return qml.probs(wires=range(n_wires)) - - params = pnp.zeros(n_params, requires_grad=True) - - with pytest.warns(qml.PennyLaneDeprecationWarning, match=f"{fn.__name__} is being migrated"): - fn(circ)(params) diff --git a/tests/qinfo/test_purity.py b/tests/qinfo/test_purity.py index d59f140a89d..8a96815fddf 100644 --- a/tests/qinfo/test_purity.py +++ b/tests/qinfo/test_purity.py @@ -12,12 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for purities.""" + # pylint: disable=too-many-arguments import pytest import pennylane as qml from pennylane import numpy as np +pytestmark = pytest.mark.filterwarnings( + "ignore:The qml.qinfo.purity transform is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def expected_purity_ising_xx(param): """Returns the analytical purity for subsystems of the IsingXX""" @@ -60,6 +65,21 @@ class TestPurity: wires_list = [([0], True), ([1], True), ([0, 1], False)] + def test_qinfo_purity_deprecated(self): + """Test that qinfo.purity is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.purity transform is deprecated", + ): + _ = qml.qinfo.purity(circuit, [0])() + def test_purity_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the purity transform manually.""" @@ -333,6 +353,7 @@ def circuit_state(x): expected_grad = expected_purity_grad_ising_xx(param) if is_partial else 0 param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + purity = qml.qinfo.purity(circuit_state, wires=wires)(param) purity.backward() grad_purity = param.grad @@ -385,6 +406,7 @@ def circuit_state(x): grad_expected_purity = expected_purity_grad_ising_xx(param) if is_partial else 0 param = tf.Variable(param) + with tf.GradientTape() as tape: purity = qml.qinfo.purity(circuit_state, wires=wires)(param) @@ -407,6 +429,7 @@ def circuit_state(x): purity0 = qml.qinfo.purity(circuit_state, wires=[wires[0]])(param) purity1 = qml.qinfo.purity(circuit_state, wires=[wires[1]])(param) + expected = expected_purity_ising_xx(param) assert qml.math.allclose(purity0, expected, atol=tol) @@ -425,6 +448,5 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) purity = qml.qinfo.purity(circuit_state, wires=[0])(x) - expected = expected_purity_ising_xx(x) assert qml.math.allclose(purity, expected) diff --git a/tests/qinfo/test_reduced_dm.py b/tests/qinfo/test_reduced_dm.py index 90f2fa271b8..0efc59cf1a1 100644 --- a/tests/qinfo/test_reduced_dm.py +++ b/tests/qinfo/test_reduced_dm.py @@ -18,7 +18,12 @@ import pennylane as qml from pennylane import numpy as np -pytestmark = pytest.mark.all_interfaces +pytestmark = [ + pytest.mark.all_interfaces, + pytest.mark.filterwarnings( + "ignore:The qml.qinfo.reduced_dm transform is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] tf = pytest.importorskip("tensorflow", minversion="2.1") torch = pytest.importorskip("torch") @@ -42,6 +47,21 @@ class TestDensityMatrixQNode: """Tests for the (reduced) density matrix for QNodes returning states.""" + def test_qinfo_transform_deprecated(self): + """Test that qinfo.reduced_dm is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.reduced_dm transform is deprecated", + ): + _ = qml.qinfo.reduced_dm(circuit, [0])() + def test_reduced_dm_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the reduced_dm transform manually.""" @@ -165,6 +185,7 @@ def circuit(x): jit_compile=True, input_signature=(tf.TensorSpec(shape=(), dtype=tf.float32),), ) + density_matrix = density_matrix(tf.Variable(0.0, dtype=tf.float32)) assert np.allclose(density_matrix, [[1, 0], [0, 0]]) diff --git a/tests/qinfo/test_trace_distance.py b/tests/qinfo/test_trace_distance.py index 892000a9737..f90411c51fa 100644 --- a/tests/qinfo/test_trace_distance.py +++ b/tests/qinfo/test_trace_distance.py @@ -18,7 +18,12 @@ import pennylane as qml from pennylane import numpy as np -pytestmark = pytest.mark.all_interfaces +pytestmark = [ + pytest.mark.all_interfaces, + pytest.mark.filterwarnings( + "ignore:qml.qinfo.trace_distance is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] tf = pytest.importorskip("tensorflow", minversion="2.1") torch = pytest.importorskip("torch") @@ -41,6 +46,21 @@ class TestTraceDistanceQnode: devices = ["default.qubit", "lightning.qubit", "default.mixed"] + def test_qinfo_transform_deprecated(self): + """Test that qinfo.trace_distance is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.trace_distance is deprecated", + ): + _ = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[1])() + @pytest.mark.parametrize("device", devices) def test_not_same_number_wires(self, device): """Test that wires must have the same length.""" diff --git a/tests/qinfo/test_vn_entanglement_entropy.py b/tests/qinfo/test_vn_entanglement_entropy_qinfo.py similarity index 92% rename from tests/qinfo/test_vn_entanglement_entropy.py rename to tests/qinfo/test_vn_entanglement_entropy_qinfo.py index b160c228a8b..ca4927ff4ea 100644 --- a/tests/qinfo/test_vn_entanglement_entropy.py +++ b/tests/qinfo/test_vn_entanglement_entropy_qinfo.py @@ -20,10 +20,29 @@ import pennylane as qml +pytestmark = pytest.mark.filterwarnings( + "ignore:The qml.qinfo.vn_entanglement_entropy transform is deprecated:pennylane.PennyLaneDeprecationWarning" +) + class TestVnEntanglementEntropy: """Tests for the vn entanglement entropy transform""" + def test_qinfo_transform_deprecated(self): + """Test that vn_entanglement_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.vn_entanglement_entropy transform is deprecated", + ): + _ = qml.qinfo.vn_entanglement_entropy(circuit, [0], [1])() + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @@ -175,6 +194,7 @@ def circuit(theta): ) params = torch.tensor(params, dtype=torch.float64, requires_grad=True) + entropy = qml.qinfo.vn_entanglement_entropy(circuit, wires0=[0], wires1=[1])(params) entropy.backward() actual = params.grad diff --git a/tests/shadow/test_shadow_entropies.py b/tests/shadow/test_shadow_entropies.py index 009607bdf0b..6549cd5e984 100644 --- a/tests/shadow/test_shadow_entropies.py +++ b/tests/shadow/test_shadow_entropies.py @@ -68,9 +68,7 @@ def test_constant_distribution(self, n_wires, base): expected = np.log(2) / np.log(base) assert np.allclose(entropies, expected, atol=2e-2) - def test_non_constant_distribution( - self, - ): + def test_non_constant_distribution(self): """Test entropies match roughly with exact solution for a non-constant distribution using other PennyLane functionalities""" n_wires = 4 # exact solution @@ -108,7 +106,11 @@ def qnode(x): # this is intentionally not done in a parametrize loop because this would re-execute the quantum function # exact solution - rdm = qml.qinfo.reduced_dm(qnode_exact, wires=rdm_wires)(x) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=("The qml.qinfo.reduced_dm transform is deprecated"), + ): + rdm = qml.qinfo.reduced_dm(qnode_exact, wires=rdm_wires)(x) evs = qml.math.eigvalsh(rdm) evs = evs[np.where(evs > 0)]