diff --git a/pennylane/math/__init__.py b/pennylane/math/__init__.py index f00687993e8..aebde6440bc 100644 --- a/pennylane/math/__init__.py +++ b/pennylane/math/__init__.py @@ -102,7 +102,6 @@ def __getattr__(name): "is_independent", "marginal_prob", "mutual_info", - "reduced_dm", "ones_like", "reduced_dm", "requires_grad", diff --git a/pennylane/math/quantum.py b/pennylane/math/quantum.py index f32320e8e74..f39d7a86a3f 100644 --- a/pennylane/math/quantum.py +++ b/pennylane/math/quantum.py @@ -174,6 +174,7 @@ def marginal_prob(prob, axis): def _density_matrix_from_matrix(density_matrix, indices, check_state=False): """Compute the density matrix from a state represented with a density matrix. + Args: density_matrix (tensor_like): 2D density matrix tensor. This tensor should be of size ``(2**N, 2**N)`` for some integer number of wires``N``. @@ -249,7 +250,7 @@ def _partial_trace(density_matrix, indices): [[1.+0.j 0.+0.j] [0.+0.j 0.+0.j]], shape=(2, 2), dtype=complex128) """ - # Does not support same indices sum in backprop + # Autograd does not support same indices sum in backprop if get_interface(density_matrix) == "autograd": density_matrix = _partial_trace_autograd(density_matrix, indices) return density_matrix @@ -395,7 +396,8 @@ def _density_matrix_from_state_vector(state, indices, check_state=False): def reduced_dm(state, indices, check_state=False, c_dtype="complex128"): - """Compute the reduced density matrix from a state vector or a density matrix. + """Compute the reduced density matrix from a state vector or a density matrix. It supports all interfaces (Numpy, + Autograd, Torch, Tensorflow and Jax). Args: state (tensor_like): ``(2**N)`` state vector or ``(2**N, 2**N)`` density matrix. @@ -406,8 +408,6 @@ def reduced_dm(state, indices, check_state=False, c_dtype="complex128"): Returns: tensor_like: Reduced density matrix of size ``(2**len(indices), 2**len(indices))`` - - **Example** >>> x = [1, 0, 1, 0] / np.sqrt(2) @@ -420,7 +420,7 @@ def reduced_dm(state, indices, check_state=False, c_dtype="complex128"): [0.+0.j 0.+0.j]] >>> y = tf.Variable([1, 0, 0, 0], dtype=tf.complex128) - >>> reduced_dm(z, indices=[1]) + >>> reduced_dm(y, indices=[1]) tf.Tensor( [[1.+0.j 0.+0.j] [0.+0.j 0.+0.j]], shape=(2, 2), dtype=complex128) @@ -454,7 +454,8 @@ def reduced_dm(state, indices, check_state=False, c_dtype="complex128"): def vn_entropy(state, indices, base=None, check_state=False, c_dtype="complex128"): - r"""Compute the Von Neumann entropy from a state vector or density matrix on a given subsystem. + r"""Compute the Von Neumann entropy from a state vector or density matrix on a given subsystem. It supports all + interfaces (Numpy, Autograd, Torch, Tensorflow and Jax). .. math:: S( \rho ) = -\text{Tr}( \rho \log ( \rho )) @@ -471,13 +472,21 @@ def vn_entropy(state, indices, base=None, check_state=False, c_dtype="complex128 **Example** + The entropy of a subsystem for any state vectors can be obtained. Here is an example for the + maximally entangled state, where the subsystem entropy is maximal (default base for log is exponential). + + >>> x = [1, 0, 0, 1] / np.sqrt(2) >>> vn_entropy(x, indices=[0]) 0.6931472 + The logarithm base can be switched to 2 for example. + >>> vn_entropy(x, indices=[0], base=2) 1.0 + The entropy can be obtained by providing a quantum state as a density matrix, for example: + >>> y = [[1/2, 0, 0, 1/2], [0, 0, 0, 0], [0, 0, 0, 0], [1/2, 0, 0, 1/2]] >>> vn_entropy(x, indices=[0]) 0.6931472 @@ -534,7 +543,8 @@ def mutual_info(state, indices0, indices1, base=None, check_state=False, c_dtype 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. + one system by measuring the other system. It supports all interfaces + (Numpy, Autograd, Torch, Tensorflow and Jax). Each state can be given as a state vector in the computational basis, or as a density matrix. @@ -552,13 +562,19 @@ def mutual_info(state, indices0, indices1, base=None, check_state=False, c_dtype **Examples** + The mutual information between subsystems for a state vector can be returned as follows: + >>> x = np.array([1, 0, 0, 1]) / np.sqrt(2) >>> qml.math.mutual_info(x, indices0=[0], indices1=[1]) 1.3862943611198906 + It is also possible to change the log basis. + >>> qml.math.mutual_info(x, indices0=[0], indices1=[1], base=2) 2.0 + Similarly the quantum state can be provided as a density matrix: + >>> y = np.array([[1/2, 1/2, 0, 1/2], [1/2, 0, 0, 0], [0, 0, 0, 0], [1/2, 0, 0, 1/2]]) >>> qml.math.mutual_info(y, indices0=[0], indices1=[1]) 0.4682351577408206 @@ -628,8 +644,9 @@ def fidelity(state0, state1, check_state=False, c_dtype="complex128"): 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. + It supports all interfaces (Numpy, Autograd, Torch, Tensorflow and Jax). 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. Args: state0 (tensor_like): 1D state vector or 2D density matrix @@ -643,20 +660,26 @@ def fidelity(state0, state1, check_state=False, c_dtype="complex128"): **Example** - >>> state0 = [0.98753537-0.14925137j, 0.00746879-0.04941796j] - >>> state1 = [0.99500417+0.j, 0.09983342+0.j] - >>> qml.math.fidelity(state0, state1) - 0.9905158135644924 + Two state vectors can be used as arguments and the fidelity (overlap) is returned, e.g.: + + >>> state0 = [0.98753537-0.14925137j, 0.00746879-0.04941796j] + >>> state1 = [0.99500417+0.j, 0.09983342+0.j] + >>> qml.math.fidelity(state0, state1) + 0.9905158135644924 + + Alternatively one can give a state vector and a density matrix as arguments, e.g.: + + >>> state0 = [0, 1] + >>> state1 = [[0, 0], [0, 1]] + >>> qml.math.fidelity(state0, state1) + 1.0 - >>> state0 = [0, 1] - >>> state1 = [[0, 0], [0, 1]] - >>> qml.math.fidelity(state0, state1) - 1.0 + It also works with two density matrices, e.g.: - >>> state0 = [[1, 0], [0, 0]] - >>> state1 = [[0, 0], [0, 1]] - >>> qml.math.fidelity(state0, state1) - 0.0 + >>> state0 = [[1, 0], [0, 0]] + >>> state1 = [[0, 0], [0, 1]] + >>> qml.math.fidelity(state0, state1) + 0.0 """ # Cast as a c_dtype array diff --git a/pennylane/measurements.py b/pennylane/measurements.py index 107aa1ae79d..fdc4a1ff73e 100644 --- a/pennylane/measurements.py +++ b/pennylane/measurements.py @@ -879,11 +879,17 @@ def circuit_entropy(x): >>> circuit_entropy(np.pi/2) 0.6931472 + It is also possible to get the gradient of the previous QNode: + + >>> param = np.array(np.pi/4, requires_grad=True) + >>> qml.grad(circuit_entropy)(param) + 0.6232252401402305 + .. note:: - Calculating the derivative of :func:`~.vn_entropy` is currently only supported when + Calculating the derivative of :func:`~.vn_entropy` is currently supported when using the classical backpropagation differentiation method (``diff_method="backprop"``) - with a compatible device. + with a compatible device and finite differences (``diff_method="finite-diff"``). """ wires = qml.wires.Wires(wires) return MeasurementProcess(VnEntropy, wires=wires, log_base=log_base) @@ -914,20 +920,26 @@ def mutual_info(wires0, wires1, log_base=None): dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) - def circuit(x): + def circuit_mutual(x): qml.IsingXX(x, wires=[0, 1]) return qml.mutual_info(wires0=[0], wires1=[1]) Executing this QNode: - >>> circuit(np.pi / 2) + >>> circuit_mutual(np.pi/2) 1.3862943611198906 + It is also possible to get the gradient of the previous QNode: + + >>> param = np.array(np.pi/4, requires_grad=True) + >>> qml.grad(circuit_mutual)(param) + 1.2464504802804612 + .. note:: - Calculating the derivative of :func:`~.mutual_info` is currently only supported when + Calculating the derivative of :func:`~.mutual_info` is currently supported when using the classical backpropagation differentiation method (``diff_method="backprop"``) - with a compatible device. + with a compatible device and finite differences (``diff_method="finite-diff"``). .. seealso:: diff --git a/pennylane/ops/qubit/parametric_ops.py b/pennylane/ops/qubit/parametric_ops.py index 7aae1c1aa7f..7307b8157f6 100644 --- a/pennylane/ops/qubit/parametric_ops.py +++ b/pennylane/ops/qubit/parametric_ops.py @@ -2306,7 +2306,7 @@ class IsingXX(Operation): r""" Ising XX coupling gate - .. math:: XX(\phi) = \exp(-i \frac{\theta}{2} (X \otimes X)) = + .. math:: XX(\phi) = \exp(-i \frac{\phi}{2} (X \otimes X)) = \begin{bmatrix} = \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ @@ -2437,7 +2437,7 @@ class IsingYY(Operation): r""" Ising YY coupling gate - .. math:: \mathtt{YY}(\phi) = \exp(-i \frac{\theta}{2} (Y \otimes Y)) = + .. math:: \mathtt{YY}(\phi) = \exp(-i \frac{\phi}{2} (Y \otimes Y)) = \begin{bmatrix} \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\ 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ @@ -2567,7 +2567,7 @@ class IsingZZ(Operation): r""" Ising ZZ coupling gate - .. math:: ZZ(\phi) = \exp(-i \frac{\theta}{2} (Z \otimes Z) = + .. math:: ZZ(\phi) = \exp(-i \frac{\phi}{2} (Z \otimes Z)) = \begin{bmatrix} e^{-i \phi / 2} & 0 & 0 & 0 \\ 0 & e^{i \phi / 2} & 0 & 0 \\ @@ -2729,7 +2729,7 @@ class IsingXY(Operation): r""" Ising (XX + YY) coupling gate - .. math:: \mathtt{XY}(\phi) = \exp(-i \frac{\theta}{4} (X \otimes X + Y \otimes Y) = + .. math:: \mathtt{XY}(\phi) = \exp(-i \frac{\theta}{4} (X \otimes X + Y \otimes Y)) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\phi / 2) & i \sin(\phi / 2) & 0 \\ diff --git a/pennylane/qinfo/transforms.py b/pennylane/qinfo/transforms.py index bc5c002bdd6..a83f47a8f1c 100644 --- a/pennylane/qinfo/transforms.py +++ b/pennylane/qinfo/transforms.py @@ -21,8 +21,7 @@ def reduced_dm(qnode, wires): - """Compute the reduced density matrix from a :class:`~.QNode` returning - :func:`~.state`. + """Compute the reduced density matrix from a :class:`~.QNode` returning :func:`~.state`. Args: qnode (QNode): A :class:`~.QNode` returning :func:`~.state`. @@ -83,17 +82,24 @@ def vn_entropy(qnode, wires, base=None): **Example** - .. code-block:: python + It is possible to obtain the entropy of a subsystem from a :class:`.QNode` returning a :func:`~.state`. - dev = qml.device("default.qubit", wires=2) - @qml.qnode(dev) - def circuit(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.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.6931472 + 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) + 0.6232252401402305 """ density_matrix_qnode = qml.qinfo.reduced_dm(qnode, qnode.device.wires) @@ -143,14 +149,17 @@ def mutual_info(qnode, wires0, wires1, base=None): **Example** - .. code-block:: python + It is possible to obtain the mutual information of two subsystems from a + :class:`.QNode` returning a :func:`~.state`. - dev = qml.device("default.qubit", wires=2) + .. code-block:: python - @qml.qnode(dev) - def circuit(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.state() + 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) @@ -560,8 +569,8 @@ def fidelity(qnode0, qnode1, wires0, wires1): 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 `qml.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: + 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 @@ -581,8 +590,8 @@ def circuit_ry(y): >>> 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, you - need to pass an empty tuple as an argument for the first QNode. + 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 @@ -606,8 +615,8 @@ def circuit_ry(x): >>> qml.qinfo.fidelity(circuit_ry, circuit_rx, wires0=[0], wires1=[0])((0.2)) 0.9900332889206207 - The `qml.qinfo.fidelity` transform is also differentiable and you can use the gradient in the different frameworks - with backpropagation, the following example uses `jax` and `backprop`. + The ``qml.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 @@ -626,6 +635,27 @@ def circuit1(): >>> jax.grad(qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0]))((jax.numpy.array(0.3))) -0.14776011 + 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 """ if len(wires0) != len(wires1): @@ -662,14 +692,28 @@ def evaluate_fidelity(all_args0=None, all_args1=None): # If no all_args is given, evaluate the QNode without args if all_args0 is not None: - state0 = state_qnode0(*all_args0) + # 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: - state1 = state_qnode1(*all_args1) + # 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() diff --git a/tests/math/test_entropies_math.py b/tests/math/test_entropies_math.py index 040f2eabc30..3c8f0b55f84 100644 --- a/tests/math/test_entropies_math.py +++ b/tests/math/test_entropies_math.py @@ -26,46 +26,6 @@ jax = pytest.importorskip("jax") -def expected_entropy_ising_xx(param): - """ - Return the analytical entropy for the IsingXX. - """ - eig_1 = (1 + np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eig_2 = (1 - np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eigs = [eig_1, eig_2] - eigs = [eig for eig in eigs if eig > 0] - - expected_entropy = eigs * np.log(eigs) - - expected_entropy = -np.sum(expected_entropy) - return expected_entropy - - -def expected_entropy_grad_ising_xx(param): - """ - Return the analytical gradient entropy for the IsingXX. - """ - eig_1 = (1 + np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eig_2 = (1 - np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2)) / 2 - eigs = [eig_1, eig_2] - eigs = np.maximum(eigs, 1e-08) - - grad_expected_entropy = -( - (np.log(eigs[0]) + 1) - * (np.sin(param / 2) ** 3 * np.cos(param / 2) - np.sin(param / 2) * np.cos(param / 2) ** 3) - / np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2) - ) - ( - (np.log(eigs[1]) + 1) - * ( - np.sin(param / 2) - * np.cos(param / 2) - * (np.cos(param / 2) ** 2 - np.sin(param / 2) ** 2) - ) - / np.sqrt(1 - 4 * np.cos(param / 2) ** 2 * np.sin(param / 2) ** 2) - ) - return grad_expected_entropy - - class TestVonNeumannEntropy: """Tests for creating a density matrix from state vectors.""" @@ -83,8 +43,14 @@ class TestVonNeumannEntropy: @pytest.mark.parametrize("wires", single_wires_list) @pytest.mark.parametrize("state_vector,pure", state_vector) @pytest.mark.parametrize("check_state", check_state) - def test_state_vector_entropy_without_base(self, state_vector, wires, check_state, pure): + @pytest.mark.parametrize("interface", [None, "autograd", "jax", "tensorflow", "torch"]) + def test_state_vector_entropy_without_base( + self, state_vector, wires, check_state, pure, interface + ): """Test entropy for different state vectors without base for log.""" + if interface: + state_vector = qml.math.asarray(state_vector, like=interface) + entropy = qml.math.vn_entropy(state_vector, wires, check_state=check_state) if pure: @@ -97,8 +63,11 @@ def test_state_vector_entropy_without_base(self, state_vector, wires, check_stat @pytest.mark.parametrize("state_vector,pure", state_vector) @pytest.mark.parametrize("base", base) @pytest.mark.parametrize("check_state", check_state) - def test_state_vector_entropy(self, state_vector, wires, base, check_state, pure): + @pytest.mark.parametrize("interface", [None, "autograd", "jax", "tensorflow", "torch"]) + def test_state_vector_entropy(self, state_vector, wires, base, check_state, pure, interface): """Test entropy for different state vectors.""" + if interface: + state_vector = qml.math.asarray(state_vector, like=interface) entropy = qml.math.vn_entropy(state_vector, wires, base, check_state) if pure: @@ -113,12 +82,17 @@ def test_state_vector_entropy(self, state_vector, wires, base, check_state, pure ([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], True), ] - @pytest.mark.parametrize("wires", single_wires_list) @pytest.mark.parametrize("density_matrix,pure", density_matrices) + @pytest.mark.parametrize("wires", single_wires_list) @pytest.mark.parametrize("base", base) @pytest.mark.parametrize("check_state", check_state) - def test_density_matrices_entropy(self, density_matrix, wires, base, check_state, pure): + @pytest.mark.parametrize("interface", [None, "autograd", "jax", "tensorflow", "torch"]) + def test_density_matrices_entropy( + self, density_matrix, pure, wires, base, check_state, interface + ): """Test entropy for different density matrices.""" + if interface: + density_matrix = qml.math.asarray(density_matrix, like=interface) entropy = qml.math.vn_entropy(density_matrix, wires, base, check_state) if pure: @@ -128,273 +102,6 @@ def test_density_matrices_entropy(self, density_matrix, wires, base, check_state assert qml.math.allclose(entropy, expected_entropy) - parameters = np.linspace(0, 2 * np.pi, 20) - - devices = ["default.qubit", "default.mixed"] - - single_wires_list = [ - [0], - [1], - ] - - base = [2, np.exp(1), 10] - - check_state = [True, False] - - devices = ["default.qubit", "default.mixed"] - diff_methods = ["backprop", "finite-diff"] - - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("device", devices) - @pytest.mark.parametrize("base", base) - def test_IsingXX_qnode_entropy(self, param, wires, device, base): - """Test entropy for a QNode numpy.""" - - dev = qml.device(device, wires=2) - - @qml.qnode(dev) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - entropy = circuit_entropy(param) - - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) - assert qml.math.allclose(entropy, expected_entropy) - - @pytest.mark.autograd - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("diff_method", diff_methods) - def test_IsingXX_qnode_entropy_grad(self, param, wires, base, diff_method): - """Test entropy for a QNode gradient with autograd.""" - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev, diff_method=diff_method) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - grad_entropy = qml.grad(circuit_entropy)(param) - - # higher tolerance for finite-diff method - tol = 1e-8 if diff_method == "backprop" else 1e-5 - - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) - assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) - - @pytest.mark.torch - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("device", devices) - @pytest.mark.parametrize("base", base) - def test_IsingXX_qnode_torch_entropy(self, param, wires, device, base): - """Test entropy for a QNode with torch interface.""" - import torch - - dev = qml.device(device, wires=2) - - @qml.qnode(dev, interface="torch") - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - entropy = circuit_entropy(torch.tensor(param)) - - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) - - assert qml.math.allclose(entropy, expected_entropy) - - @pytest.mark.torch - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("diff_method", diff_methods) - def test_IsingXX_qnode_entropy_grad_torch(self, param, wires, base, diff_method): - """Test entropy for a QNode gradient with torch.""" - import torch - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev, interface="torch", diff_method=diff_method) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) - - param = torch.tensor(param, dtype=torch.float64, requires_grad=True) - entropy = circuit_entropy(param) - entropy.backward() - grad_entropy = param.grad - - # higher tolerance for finite-diff method - tol = 1e-8 if diff_method == "backprop" else 1e-5 - - assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) - - @pytest.mark.tf - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("device", devices) - @pytest.mark.parametrize("base", base) - def test_IsingXX_qnode_tf_entropy(self, param, wires, device, base): - """Test entropy for a QNode with tf interface.""" - import tensorflow as tf - - dev = qml.device(device, wires=2) - - @qml.qnode(dev, interface="tf") - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - entropy = circuit_entropy(tf.Variable(param)) - - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) - - assert qml.math.allclose(entropy, expected_entropy) - - @pytest.mark.tf - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("diff_method", diff_methods) - def test_IsingXX_qnode_entropy_grad_tf(self, param, wires, base, diff_method): - """Test entropy for a QNode gradient with tf.""" - import tensorflow as tf - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev, interface="tf", diff_method=diff_method) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - param = tf.Variable(param) - with tf.GradientTape() as tape: - entropy = circuit_entropy(param) - - grad_entropy = tape.gradient(entropy, param) - - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) - - # higher tolerance for finite-diff method - tol = 1e-8 if diff_method == "backprop" else 1e-5 - - assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) - - @pytest.mark.jax - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("device", devices) - @pytest.mark.parametrize("base", base) - def test_IsingXX_qnode_jax_entropy(self, param, wires, device, base): - """Test entropy for a QNode with jax interface.""" - import jax.numpy as jnp - - dev = qml.device(device, wires=2) - - @qml.qnode(dev, interface="jax") - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - entropy = circuit_entropy(jnp.array(param)) - - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) - - assert qml.math.allclose(entropy, expected_entropy) - - @pytest.mark.jax - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("diff_method", diff_methods) - def test_IsingXX_qnode_entropy_grad_jax(self, param, wires, base, diff_method): - """Test entropy for a QNode gradient with Jax.""" - import jax - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev, interface="jax", diff_method=diff_method) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - grad_entropy = jax.grad(circuit_entropy)(jax.numpy.array(param)) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) - - # higher tolerance for finite-diff method - tol = 1e-8 if diff_method == "backprop" else 1e-5 - - assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) - - @pytest.mark.jax - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("device", devices) - def test_IsingXX_qnode_jax_jit_entropy(self, param, wires, base, device): - """Test entropy for a QNode with jax-jit interface.""" - import jax - import jax.numpy as jnp - - dev = qml.device(device, wires=2) - - @qml.qnode(dev, interface="jax-jit") - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - entropy = jax.jit(circuit_entropy)(jnp.array(param)) - - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) - - assert qml.math.allclose(entropy, expected_entropy) - - @pytest.mark.jax - @pytest.mark.parametrize("wires", single_wires_list) - @pytest.mark.parametrize("param", parameters) - @pytest.mark.parametrize("base", base) - @pytest.mark.parametrize("diff_method", diff_methods) - def test_IsingXX_qnode_entropy_grad_jax_jit(self, param, wires, base, diff_method): - """Test entropy for a QNode gradient with Jax-jit.""" - import jax - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev, interface="jax-jit", diff_method=diff_method) - def circuit_entropy(x): - qml.IsingXX(x, wires=[0, 1]) - return qml.vn_entropy(wires=wires, log_base=base) - - grad_entropy = jax.jit(jax.grad(circuit_entropy))(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) - - @pytest.mark.parametrize("device", devices) - def test_qnode_entropy_no_custom_wires(self, device): - """Test that entropy cannot be returned with custom wires.""" - - dev = qml.device(device, wires=["a", 1]) - - @qml.qnode(dev) - def circuit_entropy(x): - qml.IsingXX(x, wires=["a", 1]) - return qml.vn_entropy(wires=["a"]) - - with pytest.raises( - qml.QuantumFunctionError, - match="Returning the Von Neumann entropy is not supported when using custom wire labels", - ): - circuit_entropy(0.1) - class TestMutualInformation: """Tests for the mutual information functions""" diff --git a/tests/qinfo/test_entropies.py b/tests/qinfo/test_entropies.py index 41c0ec1794e..10774337f42 100644 --- a/tests/qinfo/test_entropies.py +++ b/tests/qinfo/test_entropies.py @@ -19,12 +19,6 @@ import pennylane as qml from pennylane import numpy as np -pytestmark = pytest.mark.all_interfaces - -tf = pytest.importorskip("tensorflow", minversion="2.1") -torch = pytest.importorskip("torch") -jax = pytest.importorskip("jax") - def expected_entropy_ising_xx(param): """ @@ -67,7 +61,7 @@ def expected_entropy_grad_ising_xx(param): class TestVonNeumannEntropy: - """Tests for creating a density matrix from state vectors.""" + """Tests Von Neumann entropy transform""" single_wires_list = [ [0], @@ -78,8 +72,8 @@ class TestVonNeumannEntropy: check_state = [True, False] - parameters = np.linspace(0, 2 * np.pi, 20) - devices = ["default.qubit", "default.mixed"] + parameters = np.linspace(0, 2 * np.pi, 10) + devices = ["default.qubit", "default.mixed", "lightning.qubit"] @pytest.mark.parametrize("wires", single_wires_list) @pytest.mark.parametrize("param", parameters) @@ -356,11 +350,282 @@ def circuit_state(x): assert qml.math.allclose(entropy, expected_entropy) +class TestVonNeumannEntropyQNode: + + parameters = np.linspace(0, 2 * np.pi, 10) + + devices = ["default.qubit", "default.mixed", "lightning.qubit"] + + single_wires_list = [ + [0], + [1], + ] + + base = [2, np.exp(1), 10] + + check_state = [True, False] + + devices = ["default.qubit", "default.mixed"] + diff_methods = ["backprop", "finite-diff"] + + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("device", devices) + @pytest.mark.parametrize("base", base) + def test_IsingXX_qnode_entropy(self, param, wires, device, base): + """Test entropy for a QNode numpy.""" + + dev = qml.device(device, wires=2) + + @qml.qnode(dev) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + entropy = circuit_entropy(param) + + expected_entropy = expected_entropy_ising_xx(param) / np.log(base) + assert qml.math.allclose(entropy, expected_entropy) + + @pytest.mark.autograd + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("diff_method", diff_methods) + def test_IsingXX_qnode_entropy_grad(self, param, wires, base, diff_method): + """Test entropy for a QNode gradient with autograd.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, diff_method=diff_method) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + grad_entropy = qml.grad(circuit_entropy)(param) + + # higher tolerance for finite-diff method + tol = 1e-8 if diff_method == "backprop" else 1e-5 + + grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) + assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) + + @pytest.mark.torch + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("device", devices) + @pytest.mark.parametrize("base", base) + def test_IsingXX_qnode_torch_entropy(self, param, wires, device, base): + """Test entropy for a QNode with torch interface.""" + import torch + + dev = qml.device(device, wires=2) + + @qml.qnode(dev, interface="torch") + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + entropy = circuit_entropy(torch.tensor(param)) + + expected_entropy = expected_entropy_ising_xx(param) / np.log(base) + + assert qml.math.allclose(entropy, expected_entropy) + + @pytest.mark.torch + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("diff_method", diff_methods) + def test_IsingXX_qnode_entropy_grad_torch(self, param, wires, base, diff_method): + """Test entropy for a QNode gradient with torch.""" + import torch + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface="torch", diff_method=diff_method) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) + + param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + entropy = circuit_entropy(param) + entropy.backward() + grad_entropy = param.grad + + # higher tolerance for finite-diff method + tol = 1e-8 if diff_method == "backprop" else 1e-5 + + assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) + + @pytest.mark.tf + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("device", devices) + @pytest.mark.parametrize("base", base) + def test_IsingXX_qnode_tf_entropy(self, param, wires, device, base): + """Test entropy for a QNode with tf interface.""" + import tensorflow as tf + + dev = qml.device(device, wires=2) + + @qml.qnode(dev, interface="tf") + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + entropy = circuit_entropy(tf.Variable(param)) + + expected_entropy = expected_entropy_ising_xx(param) / np.log(base) + + assert qml.math.allclose(entropy, expected_entropy) + + @pytest.mark.tf + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("diff_method", diff_methods) + def test_IsingXX_qnode_entropy_grad_tf(self, param, wires, base, diff_method): + """Test entropy for a QNode gradient with tf.""" + import tensorflow as tf + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface="tf", diff_method=diff_method) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + param = tf.Variable(param) + with tf.GradientTape() as tape: + entropy = circuit_entropy(param) + + grad_entropy = tape.gradient(entropy, param) + + grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) + + # higher tolerance for finite-diff method + tol = 1e-8 if diff_method == "backprop" else 1e-5 + + assert qml.math.allclose(grad_entropy, grad_expected_entropy, atol=tol) + + @pytest.mark.jax + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("device", devices) + @pytest.mark.parametrize("base", base) + def test_IsingXX_qnode_jax_entropy(self, param, wires, device, base): + """Test entropy for a QNode with jax interface.""" + import jax.numpy as jnp + + dev = qml.device(device, wires=2) + + @qml.qnode(dev, interface="jax") + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + entropy = circuit_entropy(jnp.array(param)) + + expected_entropy = expected_entropy_ising_xx(param) / np.log(base) + + assert qml.math.allclose(entropy, expected_entropy) + + @pytest.mark.jax + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("diff_method", diff_methods) + def test_IsingXX_qnode_entropy_grad_jax(self, param, wires, base, diff_method): + """Test entropy for a QNode gradient with Jax.""" + import jax + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface="jax", diff_method=diff_method) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + grad_entropy = jax.grad(circuit_entropy)(jax.numpy.array(param)) + grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) + + # higher tolerance for finite-diff method + tol = 1e-8 if diff_method == "backprop" else 1e-5 + + assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) + + @pytest.mark.jax + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("device", devices) + def test_IsingXX_qnode_jax_jit_entropy(self, param, wires, base, device): + """Test entropy for a QNode with jax-jit interface.""" + import jax + import jax.numpy as jnp + + dev = qml.device(device, wires=2) + + @qml.qnode(dev, interface="jax-jit") + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + entropy = jax.jit(circuit_entropy)(jnp.array(param)) + + expected_entropy = expected_entropy_ising_xx(param) / np.log(base) + + assert qml.math.allclose(entropy, expected_entropy) + + @pytest.mark.jax + @pytest.mark.parametrize("wires", single_wires_list) + @pytest.mark.parametrize("param", parameters) + @pytest.mark.parametrize("base", base) + @pytest.mark.parametrize("diff_method", diff_methods) + def test_IsingXX_qnode_entropy_grad_jax_jit(self, param, wires, base, diff_method): + """Test entropy for a QNode gradient with Jax-jit.""" + import jax + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface="jax-jit", diff_method=diff_method) + def circuit_entropy(x): + qml.IsingXX(x, wires=[0, 1]) + return qml.vn_entropy(wires=wires, log_base=base) + + grad_entropy = jax.jit(jax.grad(circuit_entropy))(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) + + @pytest.mark.parametrize("device", devices) + def test_qnode_entropy_no_custom_wires(self, device): + """Test that entropy cannot be returned with custom wires.""" + + dev = qml.device(device, wires=["a", 1]) + + @qml.qnode(dev) + def circuit_entropy(x): + qml.IsingXX(x, wires=["a", 1]) + return qml.vn_entropy(wires=["a"]) + + with pytest.raises( + qml.QuantumFunctionError, + match="Returning the Von Neumann entropy is not supported when using custom wire labels", + ): + circuit_entropy(0.1) + + class TestMutualInformation: """Tests for the mutual information functions""" diff_methods = ["backprop", "finite-diff"] + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @pytest.mark.parametrize("params", np.linspace(0, 2 * np.pi, 8)) @@ -386,6 +651,7 @@ def circuit(params): assert np.allclose(actual, expected) + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @pytest.mark.parametrize("params", zip(np.linspace(0, np.pi, 8), np.linspace(0, 2 * np.pi, 8))) @@ -422,6 +688,7 @@ def circuit_state(params): def test_qnode_state_jax_jit(self, params): """Test that the mutual information transform works for QNodes by comparing against analytic values, for the JAX-jit interface""" + import jax import jax.numpy as jnp dev = qml.device("default.qubit", wires=2) @@ -448,11 +715,12 @@ def circuit(params): def test_qnode_mutual_info_jax_jit(self, params): """Test that the measurement process for mutual information works for QNodes by comparing against the mutual information transform, for the JAX-jit interface""" + import jax import jax.numpy as jnp dev = qml.device("default.qubit", wires=2) - params = qml.math.asarray(np.array(params), like="jax") + params = jnp.array(params) @qml.qnode(dev, interface="jax-jit") def circuit_mutual_info(params): @@ -509,6 +777,7 @@ def circuit(param): def test_qnode_grad_jax(self, param, diff_method): """Test that the gradient of mutual information works for QNodes with the JAX interface""" + import jax import jax.numpy as jnp dev = qml.device("default.qubit", wires=2) @@ -541,6 +810,7 @@ def circuit(param): def test_qnode_grad_jax_jit(self, param, diff_method): """Test that the gradient of mutual information works for QNodes with the JAX-jit interface""" + import jax import jax.numpy as jnp dev = qml.device("default.qubit", wires=2) @@ -573,6 +843,8 @@ def circuit(param): def test_qnode_grad_tf(self, param, diff_method): """Test that the gradient of mutual information works for QNodes with the tensorflow interface""" + import tensorflow as tf + dev = qml.device("default.qubit", wires=2) param = tf.Variable(param) @@ -606,6 +878,8 @@ def circuit(param): def test_qnode_grad_torch(self, param, diff_method): """Test that the gradient of mutual information works for QNodes with the torch interface""" + import torch + dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="torch", diff_method=diff_method) @@ -632,6 +906,7 @@ def circuit(param): actual = param.grad assert np.allclose(actual, expected, atol=tol) + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @pytest.mark.parametrize( @@ -655,6 +930,7 @@ def circuit(params): with pytest.raises(qml.QuantumFunctionError, match=msg): circuit(params) + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @pytest.mark.parametrize( diff --git a/tests/qinfo/test_fidelity.py b/tests/qinfo/test_fidelity.py index 905a52a2678..913dc0758c4 100644 --- a/tests/qinfo/test_fidelity.py +++ b/tests/qinfo/test_fidelity.py @@ -140,11 +140,19 @@ def circuit1(x, y): qml.RY(y, wires=0) return qml.state() - fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( + fid_args = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( (0.0, np.pi), (0.0, 0.0) ) + fid_arg_kwarg = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( + (0.0, {"y": np.pi}), (0.0, {"y": 0}) + ) + fid_kwargs = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( + ({"x": 0, "y": np.pi}), ({"x": 0, "y": 0}) + ) - assert qml.math.allclose(fid, 1.0) + assert qml.math.allclose(fid_args, 1.0) + assert qml.math.allclose(fid_arg_kwarg, 1.0) + assert qml.math.allclose(fid_kwargs, 1.0) parameters = np.linspace(0, 2 * np.pi, 20) wires = [1, 2]