Source code for pennylane.qinfo.transforms
# Copyright 2018-2020 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.
"""QNode transforms for the quantum information quantities."""
# pylint: disable=import-outside-toplevel, not-callable
import warnings
from collections.abc import Callable, Sequence
from functools import partial
import pennylane as qml
from pennylane import transform
from pennylane.devices import DefaultMixed
from pennylane.measurements import DensityMatrixMP, StateMP
from pennylane.tape import QuantumScript, QuantumScriptBatch
from pennylane.typing import PostprocessingFn
[docs]@partial(transform, final_transform=True)
def reduced_dm(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch, PostprocessingFn]:
"""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.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will provide the reduced density matrix in the form of a tensor.
**Example**
.. code-block:: python
import numpy as np
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit(x):
qml.IsingXX(x, wires=[0,1])
return qml.state()
>>> transformed_circuit = reduced_dm(circuit, wires=[0])
>>> transformed_circuit(np.pi/2)
tensor([[0.5+0.j, 0. +0.j],
[0. +0.j, 0.5+0.j]], requires_grad=True)
This is equivalent to the state of the wire ``0`` after measuring the wire ``1``:
.. code-block:: python
@qml.qnode(dev)
def measured_circuit(x):
qml.IsingXX(x, wires=[0,1])
m = qml.measure(1)
return qml.density_matrix(wires=[0]), qml.probs(op=m)
>>> dm, probs = measured_circuit(np.pi/2)
>>> dm
tensor([[0.5+0.j, 0. +0.j],
[0. +0.j, 0.5+0.j]], requires_grad=True)
>>> probs
tensor([0.5, 0.5], requires_grad=True)
.. 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)}
indices = [wire_map[w] for w in wires]
measurements = tape.measurements
if len(measurements) != 1 or not isinstance(measurements[0], StateMP):
raise ValueError("The qfunc measurement needs to be State.")
def processing_fn(res):
# device is provided by the custom QNode transform
device = kwargs.get("device", None)
c_dtype = getattr(device, "C_DTYPE", "complex128")
# determine the density matrix
dm_func = (
qml.math.reduce_dm
if isinstance(measurements[0], DensityMatrixMP) or isinstance(device, DefaultMixed)
else qml.math.reduce_statevector
)
density_matrix = dm_func(res[0], indices=indices, c_dtype=c_dtype)
return density_matrix
return [tape], processing_fn
@reduced_dm.custom_qnode_transform
def _reduced_dm_qnode(self, qnode, targs, tkwargs):
if tkwargs.get("device", False):
raise ValueError(
"Cannot provide a 'device' value directly to the reduced_dm decorator when "
"transforming a QNode."
)
if tkwargs.get("device_wires", None):
raise ValueError(
"Cannot provide a 'device_wires' value directly to the reduced_dm decorator when "
"transforming a QNode."
)
tkwargs.setdefault("device", qnode.device)
tkwargs.setdefault("device_wires", qnode.device.wires)
return self.default_qnode_transform(qnode, targs, tkwargs)
[docs]@partial(transform, final_transform=True)
def purity(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch, PostprocessingFn]:
r"""Compute the purity of a :class:`~.QuantumTape` returning :func:`~pennylane.state`.
.. math::
\gamma = \text{Tr}(\rho^2)
where :math:`\rho` is the density matrix. The purity of a normalized quantum state satisfies
:math:`\frac{1}{d} \leq \gamma \leq 1`, where :math:`d` is the dimension of the Hilbert space.
A pure state has a purity of 1.
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.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[.QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will provide the purity in the form of a tensor.
**Example**
.. code-block:: python
dev = qml.device("default.mixed", wires=2)
@qml.qnode(dev)
def noisy_circuit(p):
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
qml.BitFlip(p, wires=0)
qml.BitFlip(p, wires=1)
return qml.state()
@qml.qnode(dev)
def circuit(x):
qml.IsingXX(x, wires=[0, 1])
return qml.state()
>>> purity(noisy_circuit, wires=[0, 1])(0.2)
0.5648000000000398
>>> purity(circuit, wires=[0])(np.pi / 2)
0.5
>>> purity(circuit, wires=[0, 1])(np.pi / 2)
1.0
.. 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)}
indices = [wire_map[w] for w in wires]
# Check measurement
measurements = tape.measurements
if len(measurements) != 1 or not isinstance(measurements[0], StateMP):
raise ValueError("The qfunc return type needs to be a state.")
def processing_fn(res):
# device is provided by the custom QNode transform
device = kwargs.get("device", None)
c_dtype = getattr(device, "C_DTYPE", "complex128")
# determine the density matrix
density_matrix = (
res[0]
if isinstance(measurements[0], DensityMatrixMP) or isinstance(device, DefaultMixed)
else qml.math.dm_from_state_vector(res[0], c_dtype=c_dtype)
)
return qml.math.purity(density_matrix, indices, c_dtype=c_dtype)
return [tape], processing_fn
@purity.custom_qnode_transform
def _purity_qnode(self, qnode, targs, tkwargs):
if tkwargs.get("device", False):
raise ValueError(
"Cannot provide a 'device' value directly to the purity decorator when "
"transforming a QNode."
)
if tkwargs.get("device_wires", None):
raise ValueError(
"Cannot provide a 'device_wires' value directly to the purity decorator when "
"transforming a QNode."
)
tkwargs.setdefault("device", qnode.device)
tkwargs.setdefault("device_wires", qnode.device.wires)
return self.default_qnode_transform(qnode, targs, tkwargs)
[docs]@partial(transform, final_transform=True)
def vn_entropy(
tape: QuantumScript, wires: Sequence[int], base: float = None, **kwargs
) -> tuple[QuantumScriptBatch, PostprocessingFn]:
r"""Compute the Von Neumann entropy from a :class:`.QuantumTape` returning a :func:`~pennylane.state`.
.. 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.
base (float): Base for the logarithm, default is None the natural logarithm is used in this case.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will provide the Von Neumann entropy in the form of a tensor.
**Example**
It is possible to obtain the entropy of a subsystem from a :class:`.QNode` returning a :func:`~pennylane.state`.
.. code-block:: python
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit(x):
qml.IsingXX(x, wires=[0, 1])
return qml.state()
>>> vn_entropy(circuit, wires=[0])(np.pi/2)
0.6931471805599453
The function is differentiable with backpropagation for all interfaces, e.g.:
>>> param = np.array(np.pi/4, requires_grad=True)
>>> qml.grad(vn_entropy(circuit, wires=[0]))(param)
tensor(0.62322524, requires_grad=True)
.. 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)}
indices = [wire_map[w] for w in wires]
measurements = tape.measurements
if len(measurements) != 1 or not isinstance(measurements[0], StateMP):
raise ValueError("The qfunc return type needs to be a state.")
def processing_fn(res):
# device is provided by the custom QNode transform
device = kwargs.get("device", None)
c_dtype = getattr(device, "C_DTYPE", "complex128")
# determine if the measurement is a state vector or a density matrix
if not isinstance(measurements[0], DensityMatrixMP) and not isinstance(
device, DefaultMixed
): # Compute entropy from state vector
if len(wires) == len(all_wires):
# The subsystem has all wires, so the entropy is 0
return 0.0
density_matrix = qml.math.dm_from_state_vector(res[0], c_dtype=c_dtype)
entropy = qml.math.vn_entropy(density_matrix, indices, base, c_dtype=c_dtype)
return entropy
# Compute entropy from density matrix
entropy = qml.math.vn_entropy(res[0], indices, base, c_dtype)
return entropy
return [tape], processing_fn
@vn_entropy.custom_qnode_transform
def _vn_entropy_qnode(self, qnode, targs, tkwargs):
if tkwargs.get("device", False):
raise ValueError(
"Cannot provide a 'device' value directly to the vn_entropy decorator when "
"transforming a QNode."
)
if tkwargs.get("device_wires", None):
raise ValueError(
"Cannot provide a 'device_wires' value directly to the vn_entropy decorator when "
"transforming a QNode."
)
tkwargs.setdefault("device", qnode.device)
tkwargs.setdefault("device_wires", qnode.device.wires)
return self.default_qnode_transform(qnode, targs, tkwargs)
def _bipartite_qinfo_transform(
transform_func: Callable,
tape: QuantumScript,
wires0: Sequence[int],
wires1: Sequence[int],
base: float = None,
**kwargs,
):
# 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)}
indices0 = [wire_map[w] for w in wires0]
indices1 = [wire_map[w] for w in wires1]
# Check measurement
measurements = tape.measurements
if len(measurements) != 1 or not isinstance(measurements[0], StateMP):
raise ValueError("The qfunc return type needs to be a state.")
def processing_fn(res):
# device is provided by the custom QNode transform
device = kwargs.get("device", None)
c_dtype = getattr(device, "C_DTYPE", "complex128")
density_matrix = (
res[0]
if isinstance(measurements[0], DensityMatrixMP) or isinstance(device, DefaultMixed)
else qml.math.dm_from_state_vector(res[0], c_dtype=c_dtype)
)
entropy = transform_func(density_matrix, indices0, indices1, base=base, c_dtype=c_dtype)
return entropy
return [tape], processing_fn
[docs]@partial(transform, final_transform=True)
def mutual_info(
tape: QuantumScript, wires0: Sequence[int], wires1: Sequence[int], base: float = None, **kwargs
) -> tuple[QuantumScriptBatch, PostprocessingFn]:
r"""Compute the mutual information from a :class:`.QuantumTape` returning a :func:`~pennylane.state`:
.. math::
I(A, B) = S(\rho^A) + S(\rho^B) - S(\rho^{AB})
where :math:`S` is the von Neumann entropy.
The mutual information is a measure of correlation between two subsystems.
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.
wires1 (Sequence(int)): List of wires in the second subsystem.
base (float): Base for the logarithm. If None, the natural logarithm is used.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will provide the mutual information in the form of a tensor.
**Example**
It is possible to obtain the mutual information of two subsystems from a
:class:`.QNode` returning a :func:`~pennylane.state`.
.. code-block:: python
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit(x):
qml.IsingXX(x, wires=[0, 1])
return qml.state()
>>> mutual_info_circuit = qinfo.mutual_info(circuit, wires0=[0], wires1=[1])
>>> mutual_info_circuit(np.pi/2)
1.3862943611198906
>>> x = np.array(0.4, requires_grad=True)
>>> mutual_info_circuit(x)
0.3325090393262875
>>> qml.grad(mutual_info_circuit)(np.array(0.4, requires_grad=True))
tensor(1.24300677, requires_grad=True)
.. 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)
@mutual_info.custom_qnode_transform
def _mutual_info_qnode(self, qnode, targs, tkwargs):
if tkwargs.get("device", False):
raise ValueError(
"Cannot provide a 'device' value directly to the mutual_info decorator when "
"transforming a QNode."
)
if tkwargs.get("device_wires", None):
raise ValueError(
"Cannot provide a 'device_wires' value directly to the mutual_info decorator when "
"transforming a QNode."
)
tkwargs.setdefault("device", qnode.device)
tkwargs.setdefault("device_wires", qnode.device.wires)
return self.default_qnode_transform(qnode, targs, tkwargs)
[docs]@partial(transform, final_transform=True)
def vn_entanglement_entropy(
tape, wires0: Sequence[int], wires1: Sequence[int], base: float = None, **kwargs
):
r"""Compute the Von Neumann entanglement entropy from a circuit returning a :func:`~pennylane.state`:
.. math::
S(\rho_A) = -\text{Tr}[\rho_A \log \rho_A] = -\text{Tr}[\rho_B \log \rho_B] = S(\rho_B)
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.
See the :func:`pennylane.vn_entropy` measurement instead.
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,
it indicates the two subsystems are entangled.
Args:
tape (QNode or QuantumTape or Callable): A quantum circuit returning a :func:`~pennylane.state`.
wires0 (Sequence(int)): List of wires in the first subsystem.
wires1 (Sequence(int)): List of wires in the second subsystem.
base (float): Base for the logarithm. If None, the natural logarithm is used.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
will provide the entanglement entropy in the form of a tensor.
"""
warnings.warn(
"The qml.qinfo.vn_entanglement_entropy transform is deprecated and will "
"be removed in v0.40. Instead, include the qml.vn_entropy measurement process "
"on one of the subsystems.",
qml.PennyLaneDeprecationWarning,
)
return _bipartite_qinfo_transform(
qml.math.vn_entanglement_entropy, tape, wires0, wires1, base, **kwargs
)
[docs]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
or a density matrix, depending on the device) acting on quantum systems with the same size.
The fidelity for two mixed states given by density matrices :math:`\rho` and :math:`\sigma`
is defined as
.. math::
F( \rho , \sigma ) = \text{Tr}( \sqrt{\sqrt{\rho} \sigma \sqrt{\rho}})^2
If one of the states is pure, say :math:`\rho=\ket{\psi}\bra{\psi}`, then the expression
for fidelity simplifies to
.. math::
F( \ket{\psi} , \sigma ) = \bra{\psi} \sigma \ket{\psi}
Finally, if both states are pure, :math:`\sigma=\ket{\phi}\bra{\phi}`, then the
fidelity is simply
.. math::
F( \ket{\psi} , \ket{\phi}) = \left|\braket{\psi| \phi}\right|^2
.. note::
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`.
wires0 (Sequence[int]): the wires of the first subsystem
wires1 (Sequence[int]): the wires of the second subsystem
Returns:
func: A function that returns the fidelity between the states outputted by the QNodes.
**Example**
First, let's consider two QNodes with potentially different signatures: a circuit with two parameters
and another circuit with a single parameter. The output of the :func:`~.qinfo.fidelity` transform then requires
two tuples to be passed as arguments, each containing the args and kwargs of their respective circuit, e.g.
``all_args0 = (0.1, 0.3)`` and ``all_args1 = (0.2)`` in the following case:
.. code-block:: python
dev = qml.device('default.qubit', wires=1)
@qml.qnode(dev)
def circuit_rx(x, y):
qml.RX(x, wires=0)
qml.RZ(y, wires=0)
return qml.state()
@qml.qnode(dev)
def circuit_ry(y):
qml.RY(y, wires=0)
return qml.state()
>>> qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])((0.1, 0.3), (0.2))
0.9905158135644924
It is also possible to use QNodes that do not depend on any parameters. When it is the case for the first QNode, it
is required to pass an empty tuple as an argument for the first QNode.
.. code-block:: python
dev = qml.device('default.qubit', wires=1)
@qml.qnode(dev)
def circuit_rx():
return qml.state()
@qml.qnode(dev)
def circuit_ry(x):
qml.RY(x, wires=0)
return qml.state()
>>> qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])(None, (0.2))
0.9900332889206207
On the other hand, if the second QNode is the one that does not depend on parameters then a single tuple can also be
passed:
>>> qml.qinfo.fidelity(circuit_ry, circuit_rx, wires0=[0], wires1=[0])((0.2))
0.9900332889206207
The :func:`~.qinfo.fidelity` transform is also differentiable and the gradient can be obtained in the different frameworks
with backpropagation, the following example uses ``jax`` and ``backprop``.
.. code-block:: python
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev, interface="jax")
def circuit0(x):
qml.RX(x, wires=0)
return qml.state()
@qml.qnode(dev, interface="jax")
def circuit1():
qml.Z(0)
return qml.state()
>>> jax.grad(qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0]))((jax.numpy.array(0.3)))
Array(-0.14776011, dtype=float64, weak_type=True)
There is also the possibility to pass a single dictionary at the end of the tuple for fixing args,
you can follow this example:
.. code-block:: python
dev = qml.device('default.qubit', wires=1)
@qml.qnode(dev)
def circuit_rx(x, y):
qml.RX(x, wires=0)
qml.RZ(y, wires=0)
return qml.state()
@qml.qnode(dev)
def circuit_ry(y, use_ry):
if use_ry:
qml.RY(y, wires=0)
return qml.state()
>>> fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])((0.1, 0.3), (0.9, {'use_ry': True}))
0.8208074192135424
.. 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)
def evaluate_fidelity(all_args0=None, all_args1=None):
"""Wrapper used for evaluation of the fidelity between two states computed from QNodes. It allows giving
the args and kwargs to each :class:`.QNode`.
Args:
all_args0 (tuple): Tuple containing the arguments (*args, kwargs) of the first :class:`.QNode`.
all_args1 (tuple): Tuple containing the arguments (*args, kwargs) of the second :class:`.QNode`.
Returns:
float: Fidelity between two quantum states
"""
if not isinstance(all_args0, tuple) and all_args0 is not None:
all_args0 = (all_args0,)
if not isinstance(all_args1, tuple) and all_args1 is not None:
all_args1 = (all_args1,)
# If no all_args is given, evaluate the QNode without args
if all_args0 is not None:
# Handle a dictionary as last argument
if isinstance(all_args0[-1], dict):
args0 = all_args0[:-1]
kwargs0 = all_args0[-1]
else:
args0 = all_args0
kwargs0 = {}
state0 = state_qnode0(*args0, **kwargs0)
else:
# No args
state0 = state_qnode0()
# If no all_args is given, evaluate the QNode without args
if all_args1 is not None:
# Handle a dictionary as last argument
if isinstance(all_args1[-1], dict):
args1 = all_args1[:-1]
kwargs1 = all_args1[-1]
else:
args1 = all_args1
kwargs1 = {}
state1 = state_qnode1(*args1, **kwargs1)
else:
# No args
state1 = state_qnode1()
# From the two generated states, compute the fidelity.
fid = qml.math.fidelity(state0, state1)
return fid
return evaluate_fidelity
[docs]def relative_entropy(qnode0, qnode1, wires0, wires1):
r"""
Compute the relative entropy for two :class:`.QNode` returning a :func:`~pennylane.state` (a state can be a state vector
or a density matrix, depending on the device) acting on quantum systems with the same size.
.. math::
S(\rho\,\|\,\sigma)=-\text{Tr}(\rho\log\sigma)-S(\rho)=\text{Tr}(\rho\log\rho)-\text{Tr}(\rho\log\sigma)
=\text{Tr}(\rho(\log\rho-\log\sigma))
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`.
wires0 (Sequence[int]): the subsystem of the first QNode
wires1 (Sequence[int]): the subsystem of the second QNode
Returns:
func: A function that takes as input the joint arguments of the two QNodes,
and returns the relative entropy from their output states.
**Example**
Consider the following QNode:
.. code-block:: python
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()
The ``qml.qinfo.relative_entropy`` transform can be used to compute the relative
entropy between the output states of the QNode:
>>> relative_entropy_circuit = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0])
The returned function takes two tuples as input, the first being the arguments to the
first QNode and the second being the arguments to the second QNode:
>>> x, y = np.array(0.4), np.array(0.6)
>>> relative_entropy_circuit((x,), (y,))
tensor(0.01775001, requires_grad=True)
This transform is fully differentiable:
.. code-block:: python
def wrapper(x, y):
return relative_entropy_circuit((x,), (y,))
>>> wrapper(x, y)
tensor(0.01775001, requires_grad=True)
>>> qml.grad(wrapper)(x, y)
(tensor(-0.16458856, requires_grad=True),
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)
def evaluate_relative_entropy(all_args0=None, all_args1=None):
"""Wrapper used for evaluation of the relative entropy between two states computed from
QNodes. It allows giving the args and kwargs to each :class:`.QNode`.
Args:
all_args0 (tuple): Tuple containing the arguments (*args, kwargs) of the first :class:`.QNode`.
all_args1 (tuple): Tuple containing the arguments (*args, kwargs) of the second :class:`.QNode`.
Returns:
float: Relative entropy between two quantum states
"""
if not isinstance(all_args0, tuple) and all_args0 is not None:
all_args0 = (all_args0,)
if not isinstance(all_args1, tuple) and all_args1 is not None:
all_args1 = (all_args1,)
# If no all_args is given, evaluate the QNode without args
if all_args0 is not None:
# Handle a dictionary as last argument
if isinstance(all_args0[-1], dict):
args0 = all_args0[:-1]
kwargs0 = all_args0[-1]
else:
args0 = all_args0
kwargs0 = {}
state0 = state_qnode0(*args0, **kwargs0)
else:
# No args
state0 = state_qnode0()
# If no all_args is given, evaluate the QNode without args
if all_args1 is not None:
# Handle a dictionary as last argument
if isinstance(all_args1[-1], dict):
args1 = all_args1[:-1]
kwargs1 = all_args1[-1]
else:
args1 = all_args1
kwargs1 = {}
state1 = state_qnode1(*args1, **kwargs1)
else:
# No args
state1 = state_qnode1()
# From the two generated states, compute the relative entropy
return qml.math.relative_entropy(state0, state1)
return evaluate_relative_entropy
[docs]def trace_distance(qnode0, qnode1, wires0, wires1):
r"""
Compute the trace distance for two :class:`.QNode` returning a :func:`~pennylane.state` (a state can be a state vector
or a density matrix, depending on the device) acting on quantum systems with the same size.
.. math::
T(\rho, \sigma)=\frac12\|\rho-\sigma\|_1
=\frac12\text{Tr}\left(\sqrt{(\rho-\sigma)^{\dagger}(\rho-\sigma)}\right)
where :math:`\|\cdot\|_1` is the Schatten :math:`1`-norm.
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`.
wires0 (Sequence[int]): the subsystem of the first QNode.
wires1 (Sequence[int]): the subsystem of the second QNode.
Returns:
func: A function that takes as input the joint arguments of the two QNodes,
and returns the trace distance between their output states.
**Example**
Consider the following QNode:
.. code-block:: python
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()
The ``qml.qinfo.trace_distance`` transform can be used to compute the trace distance
between the output states of the QNode:
>>> trace_distance_circuit = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[0])
The returned function takes two tuples as input, the first being the arguments to the
first QNode and the second being the arguments to the second QNode:
>>> x, y = np.array(0.4), np.array(0.6)
>>> trace_distance_circuit((x,), (y,))
0.047862689546603415
This transform is fully differentiable:
.. code-block:: python
def wrapper(x, y):
return trace_distance_circuit((x,), (y,))
>>> wrapper(x, y)
0.047862689546603415
>>> qml.grad(wrapper)(x, y)
(tensor(-0.19470917, requires_grad=True),
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)
def evaluate_trace_distance(all_args0=None, all_args1=None):
"""Wrapper used for evaluation of the trace distance between two states computed from
QNodes. It allows giving the args and kwargs to each :class:`.QNode`.
Args:
all_args0 (tuple): Tuple containing the arguments (*args, kwargs) of the first :class:`.QNode`.
all_args1 (tuple): Tuple containing the arguments (*args, kwargs) of the second :class:`.QNode`.
Returns:
float: Trace distance between two quantum states
"""
if not isinstance(all_args0, tuple) and all_args0 is not None:
all_args0 = (all_args0,)
if not isinstance(all_args1, tuple) and all_args1 is not None:
all_args1 = (all_args1,)
# If no all_args is given, evaluate the QNode without args
if all_args0 is not None:
# Handle a dictionary as last argument
if isinstance(all_args0[-1], dict):
args0 = all_args0[:-1]
kwargs0 = all_args0[-1]
else:
args0 = all_args0
kwargs0 = {}
state0 = state_qnode0(*args0, **kwargs0)
else:
# No args
state0 = state_qnode0()
# If no all_args is given, evaluate the QNode without args
if all_args1 is not None:
# Handle a dictionary as last argument
if isinstance(all_args1[-1], dict):
args1 = all_args1[:-1]
kwargs1 = all_args1[-1]
else:
args1 = all_args1
kwargs1 = {}
state1 = state_qnode1(*args1, **kwargs1)
else:
# No args
state1 = state_qnode1()
# From the two generated states, compute the trace distance
return qml.math.trace_distance(state0, state1)
return evaluate_trace_distance
_modules/pennylane/qinfo/transforms
Download Python script
Download Notebook
View on GitHub