diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 0eb8ba08d43..288955460f5 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -505,7 +505,7 @@ random_mat2 = rng.standard_normal(3, requires_grad=False) ``` ```pycon - >>> tape = tape.expand(depth=2) + >>> tape = tape.expand(depth=1) >>> print(tape.draw(wire_order=Wires(all_wires))) c0: ──────────────╭C──────────────────────╭C──────────┤ c1: ──────────────├C──────────────────────├C──────────┤ @@ -577,6 +577,19 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)

Bug fixes

+* Fixes a bug with `qml.math.cast` where the `MottonenStatePreparation` operation expected + a float type instead of double. + [(#1400)](https://github.com/XanaduAI/pennylane/pull/1400) + +* Fixes a bug where a copy of `qml.ControlledQubitUnitary` was non-functional as it did not have all the necessary information. +[(#1411)](https://github.com/PennyLaneAI/pennylane/pull/1411) + +* Warns when adjoint or reversible differentiation specified or called on a device with finite shots. + [(#1406)](https://github.com/PennyLaneAI/pennylane/pull/1406) + +* Fixes the differentiability of the operations `IsingXX` and `IsingZZ` for Autograd, Jax and Tensorflow. + [(#1390)](https://github.com/PennyLaneAI/pennylane/pull/1390) + * Fixes a bug where multiple identical Hamiltonian terms will produce a different result with ``optimize=True`` using ``ExpvalCost``. [(#1405)](https://github.com/XanaduAI/pennylane/pull/1405) @@ -586,7 +599,7 @@ random_mat2 = rng.standard_normal(3, requires_grad=False) [(#1392)](https://github.com/XanaduAI/pennylane/pull/1392) * Fixes floating point errors with `diff_method="finite-diff"` and `order=1` when parameters are `float32`. -[(#1381)](https://github.com/PennyLaneAI/pennylane/pull/1381) + [(#1381)](https://github.com/PennyLaneAI/pennylane/pull/1381) * Fixes a bug where `qml.ctrl` would fail to transform gates that had no control defined and no decomposition defined. @@ -647,9 +660,10 @@ random_mat2 = rng.standard_normal(3, requires_grad=False) This release contains contributions from (in alphabetical order): Marius Aglitoiu, Vishnu Ajith, Thomas Bromley, Jack Ceroni, Alaric Cheng, Miruna Daian, Olivia Di Matteo, + Tanya Garg, Christian Gogolin, Diego Guala, Anthony Hayes, Ryan Hill, Josh Izaac, Pavan Jayasinha, Nathan Killoran, -Christina Lee, Ryan Levy, Nahum Sá, Maria Schuld, Johannes Jakob Meyer, Brian Shi, Antal Száva, David Wierichs, -Vincent Wong, Alberto Maldonado, Ashish Panigrahi. +Christina Lee, Ryan Levy, Johannes Jakob Meyer, Romain Moyard, Nahum Sá, Maria Schuld, Brian Shi, Antal Száva, +David Wierichs, Vincent Wong, Alberto Maldonado, Ashish Panigrahi. # Release 0.15.1 (current release) diff --git a/pennylane/__init__.py b/pennylane/__init__.py index f7371f829f8..1524d56e6b7 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -53,6 +53,7 @@ qfunc_transform, single_tape_transform, quantum_monte_carlo, + apply_controlled_Q, ) from pennylane.utils import inv from pennylane.vqe import ExpvalCost, Hamiltonian, VQECost diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index b75b83ac0b5..5300cd088e5 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -854,6 +854,13 @@ def adjoint_jacobian(self, tape, starting_state=None, use_device_state=False): if not hasattr(m.obs, "base_name"): m.obs.base_name = None # This is needed for when the observable is a tensor product + if self.shots is not None: + warnings.warn( + "Requested adjoint differentiation to be computed with finite shots." + " The derivative is always exact when using the adjoint differentiation method.", + UserWarning, + ) + # Initialization of state if starting_state is not None: ket = self._reshape(starting_state, [2] * self.num_wires) diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py index f57da751fd1..38679a3524c 100644 --- a/pennylane/circuit_graph.py +++ b/pennylane/circuit_graph.py @@ -68,7 +68,7 @@ def _list_at_index_or_none(ls, idx): return None -def is_returned_observable(op): +def _is_returned_observable(op): """Helper for the condition of having an observable or measurement process in the return statement. @@ -523,7 +523,7 @@ def greedy_layers(self, wire_order=None, show_all_wires=False): observables[wire] = [None] * self.max_simultaneous_measurements for op in self._grid[wire]: - if is_returned_observable(op): + if _is_returned_observable(op): obs_idx = mp_map[op] observables[wire][obs_idx] = op diff --git a/pennylane/devices/autograd_ops.py b/pennylane/devices/autograd_ops.py index cc1a611e01e..ee4b1e0f82d 100644 --- a/pennylane/devices/autograd_ops.py +++ b/pennylane/devices/autograd_ops.py @@ -28,6 +28,7 @@ II = np.eye(4, dtype=C_DTYPE) ZZ = np.array(kron(Z, Z), dtype=C_DTYPE) +XX = np.array(kron(X, X), dtype=C_DTYPE) IX = np.array(kron(I, X), dtype=C_DTYPE) IY = np.array(kron(I, Y), dtype=C_DTYPE) @@ -182,6 +183,44 @@ def MultiRZ(theta, n): return np.exp(-1j * theta / 2 * pauli_eigs(n)) +def IsingXX(phi): + r"""Ising XX coupling gate + + .. math:: XX(\phi) = \begin{bmatrix} + \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ + 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ + 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ + -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) + \end{bmatrix}. + + Args: + phi (float): rotation angle :math:`\phi` + Returns: + array[complex]: unitary 4x4 rotation matrix + """ + return np.cos(phi / 2) * II - 1j * np.sin(phi / 2) * XX + + +def IsingZZ(phi): + r"""Ising ZZ coupling gate + + .. math:: ZZ(\phi) = \begin{bmatrix} + e^{-i \phi / 2} & 0 & 0 & 0 \\ + 0 & e^{i \phi / 2} & 0 & 0 \\ + 0 & 0 & e^{i \phi / 2} & 0 \\ + 0 & 0 & 0 & e^{-i \phi / 2} + \end{bmatrix}. + + Args: + phi (float): rotation angle :math:`\phi` + Returns: + array[complex]: unitary 4x4 rotation matrix + """ + e_m = np.exp(-1j * phi / 2) + e = np.exp(1j * phi / 2) + return np.array([[e_m, 0, 0, 0], [0, e, 0, 0], [0, 0, e, 0], [0, 0, 0, e_m]]) + + def SingleExcitation(phi): r"""Single excitation rotation. diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index 8571b863467..d06d383f4b7 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -94,6 +94,8 @@ class DefaultQubitAutograd(DefaultQubit): "CRZ": autograd_ops.CRZ, "CRot": autograd_ops.CRot, "MultiRZ": autograd_ops.MultiRZ, + "IsingXX": autograd_ops.IsingXX, + "IsingZZ": autograd_ops.IsingZZ, "SingleExcitation": autograd_ops.SingleExcitation, "SingleExcitationPlus": autograd_ops.SingleExcitationPlus, "SingleExcitationMinus": autograd_ops.SingleExcitationMinus, diff --git a/pennylane/devices/default_qubit_jax.py b/pennylane/devices/default_qubit_jax.py index 3d99a19280b..67913669af7 100644 --- a/pennylane/devices/default_qubit_jax.py +++ b/pennylane/devices/default_qubit_jax.py @@ -147,6 +147,8 @@ def circuit(): "CRZ": jax_ops.CRZ, "CRot": jax_ops.CRot, "MultiRZ": jax_ops.MultiRZ, + "IsingXX": jax_ops.IsingXX, + "IsingZZ": jax_ops.IsingZZ, "SingleExcitation": jax_ops.SingleExcitation, "SingleExcitationPlus": jax_ops.SingleExcitationPlus, "SingleExcitationMinus": jax_ops.SingleExcitationMinus, diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index b60281d7f5f..df0dba8dd08 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -141,6 +141,8 @@ class DefaultQubitTF(DefaultQubit): "CRY": tf_ops.CRY, "CRZ": tf_ops.CRZ, "CRot": tf_ops.CRot, + "IsingXX": tf_ops.IsingXX, + "IsingZZ": tf_ops.IsingZZ, "SingleExcitation": tf_ops.SingleExcitation, "SingleExcitationPlus": tf_ops.SingleExcitationPlus, "SingleExcitationMinus": tf_ops.SingleExcitationMinus, diff --git a/pennylane/devices/jax_ops.py b/pennylane/devices/jax_ops.py index dae1c675b01..9d9555ae719 100644 --- a/pennylane/devices/jax_ops.py +++ b/pennylane/devices/jax_ops.py @@ -28,6 +28,7 @@ II = jnp.eye(4, dtype=C_DTYPE) ZZ = jnp.array(jnp.kron(Z, Z), dtype=C_DTYPE) +XX = jnp.array(jnp.kron(X, X), dtype=C_DTYPE) IX = jnp.array(jnp.kron(I, X), dtype=C_DTYPE) IY = jnp.array(jnp.kron(I, Y), dtype=C_DTYPE) @@ -182,6 +183,44 @@ def MultiRZ(theta, n): return jnp.exp(-1j * theta / 2 * pauli_eigs(n)) +def IsingXX(phi): + r"""Ising XX coupling gate. + + .. math:: XX(\phi) = \begin{bmatrix} + \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ + 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ + 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ + -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) + \end{bmatrix}. + + Args: + phi (float): rotation angle :math:`\phi` + Returns: + array[complex]: unitary 4x4 rotation matrix + """ + return jnp.cos(phi / 2) * II - 1j * jnp.sin(phi / 2) * XX + + +def IsingZZ(phi): + r"""Ising ZZ coupling gate + + .. math:: ZZ(\phi) = \begin{bmatrix} + e^{-i \phi / 2} & 0 & 0 & 0 \\ + 0 & e^{i \phi / 2} & 0 & 0 \\ + 0 & 0 & e^{i \phi / 2} & 0 \\ + 0 & 0 & 0 & e^{-i \phi / 2} + \end{bmatrix}. + + Args: + phi (float): rotation angle :math:`\phi` + Returns: + array[complex]: unitary 4x4 rotation matrix + """ + e_m = jnp.exp(-1j * phi / 2) + e = jnp.exp(1j * phi / 2) + return jnp.array([[e_m, 0, 0, 0], [0, e, 0, 0], [0, 0, e, 0], [0, 0, 0, e_m]]) + + def SingleExcitation(phi): r"""Single excitation rotation. diff --git a/pennylane/devices/tests/test_measurements.py b/pennylane/devices/tests/test_measurements.py index b72813d08b0..fe91ae7439a 100755 --- a/pennylane/devices/tests/test_measurements.py +++ b/pennylane/devices/tests/test_measurements.py @@ -816,8 +816,8 @@ def test_projector(self, device, tol, skip_if): skip_if(dev, {"supports_tensor_observables": False}) - theta = 0.432 - phi = 0.123 + theta = 1.432 + phi = 1.123 varphi = -0.543 @qml.qnode(dev) diff --git a/pennylane/devices/tf_ops.py b/pennylane/devices/tf_ops.py index ff3c8a700e5..febaa6c8d7c 100644 --- a/pennylane/devices/tf_ops.py +++ b/pennylane/devices/tf_ops.py @@ -28,6 +28,7 @@ II = tf.eye(4, dtype=C_DTYPE) ZZ = tf.constant(kron(Z, Z), dtype=C_DTYPE) +XX = tf.constant(kron(X, X), dtype=C_DTYPE) IX = tf.constant(kron(I, X), dtype=C_DTYPE) IY = tf.constant(kron(I, Y), dtype=C_DTYPE) @@ -193,6 +194,46 @@ def CRot(a, b, c): return tf.linalg.diag(CRZ(c)) @ (CRY(b) @ tf.linalg.diag(CRZ(a))) +def IsingXX(phi): + r"""Ising XX coupling gate + + .. math:: XX(\phi) = \begin{bmatrix} + \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ + 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ + 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ + -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) + \end{bmatrix}. + + Args: + phi (float): rotation angle :math:`\phi` + Returns: + tf.Tensor[complex]: unitary 4x4 rotation matrix + """ + phi = tf.cast(phi, dtype=C_DTYPE) + return tf.cos(phi / 2) * II - 1j * tf.sin(phi / 2) * XX + + +def IsingZZ(phi): + r"""Ising ZZ coupling gate + + .. math:: ZZ(\phi) = \begin{bmatrix} + e^{-i \phi / 2} & 0 & 0 & 0 \\ + 0 & e^{i \phi / 2} & 0 & 0 \\ + 0 & 0 & e^{i \phi / 2} & 0 \\ + 0 & 0 & 0 & e^{-i \phi / 2} + \end{bmatrix}. + + Args: + phi (float): rotation :math:`\phi` + Returns: + tf.Tensor[complex]: unitary 4x4 rotation matrix + """ + phi = tf.cast(phi, dtype=C_DTYPE) + e_m = tf.exp(-1j * phi / 2) + e = tf.exp(1j * phi / 2) + return tf.convert_to_tensor([[e_m, 0, 0, 0], [0, e, 0, 0], [0, 0, e, 0], [0, 0, 0, e_m]]) + + def SingleExcitation(phi): r"""Single excitation rotation. diff --git a/pennylane/fourier/coefficients.py b/pennylane/fourier/coefficients.py index a3f3eb78334..2e71c0db16c 100644 --- a/pennylane/fourier/coefficients.py +++ b/pennylane/fourier/coefficients.py @@ -80,10 +80,10 @@ def coefficients(f, n_inputs, degree, lowpass_filter=False, filter_threshold=Non @qml.qnode(dev) def circuit(weights, inpt): qml.RX(inpt[0], wires='a') - qml.Rot(0.1, 0.2, 0.3, wires='a') + qml.Rot(*weights[0], wires='a') qml.RY(inpt[1], wires='a') - qml.Rot(-4.1, 3.2, 1.3, wires='a') + qml.Rot(*weights[1], wires='a') return qml.expval(qml.PauliZ(wires='a')) @@ -94,7 +94,7 @@ def circuit(weights, inpt): ``weights``: >>> from functools import partial - >>> weights = np.array([0.5, 0.2]) + >>> weights = np.array([[0.1, 0.2, 0.3], [-4.1, 3.2, 1.3]]) >>> partial_circuit = partial(circuit, weights) Now we must specify the number of inputs, and the maximum desired diff --git a/pennylane/kernels/postprocessing.py b/pennylane/kernels/postprocessing.py index 70b5aec01a0..c3c3a3d714d 100644 --- a/pennylane/kernels/postprocessing.py +++ b/pennylane/kernels/postprocessing.py @@ -267,27 +267,48 @@ def mitigate_depolarizing_noise(K, num_wires, method, use_entries=None): **Example:** For an example usage of ``mitigate_depolarizing_noise`` please refer to the - `PennyLane demo on the kernel module `_ or `the postprocessing demo for arXiv:2105.02276 `_. + `PennyLane demo on the kernel module `_ or `the postprocessing demo for arXiv:2105.02276 `_. """ dim = 2 ** num_wires if method == "single": if use_entries is None: use_entries = (0,) + + if K[use_entries[0], use_entries[0]] <= (1 / dim): + raise ValueError( + "The single noise mitigation method cannot be applied " + "as the single diagonal element specified is too small." + ) + diagonal_element = K[use_entries[0], use_entries[0]] noise_rate = (1 - diagonal_element) * dim / (dim - 1) mitigated_matrix = (K - noise_rate / dim) / (1 - noise_rate) elif method == "average": + if use_entries is None: diagonal_elements = np.diag(K) else: diagonal_elements = np.diag(K)[np.array(use_entries)] + + if np.mean(diagonal_elements) <= 1 / dim: + raise ValueError( + "The average noise mitigation method cannot be applied " + "as the average of the used diagonal terms is too small." + ) + noise_rates = (1 - diagonal_elements) * dim / (dim - 1) mean_noise_rate = np.mean(noise_rates) mitigated_matrix = (K - mean_noise_rate / dim) / (1 - mean_noise_rate) elif method == "split_channel": + if np.any(np.diag(K) <= 1 / dim): + raise ValueError( + "The split channel noise mitigation method cannot be applied " + "to the input matrix as its diagonal terms are too small." + ) + eff_noise_rates = np.clip((1 - np.diag(K)) * dim / (dim - 1), 0.0, 1.0) noise_rates = 1 - np.sqrt(1 - eff_noise_rates) inverse_noise = ( @@ -295,6 +316,12 @@ def mitigate_depolarizing_noise(K, num_wires, method, use_entries=None): + noise_rates.reshape((1, len(K))) + noise_rates.reshape((len(K), 1)) ) + mitigated_matrix = (K - inverse_noise / dim) / (1 - inverse_noise) + else: + raise ValueError( + "Incorrect noise depolarization mitigation method specified. " + "Accepted strategies are: 'single', 'average' and 'split_channel'." + ) return mitigated_matrix diff --git a/pennylane/math/multi_dispatch.py b/pennylane/math/multi_dispatch.py index 783a7e45acf..3193c7b49a1 100644 --- a/pennylane/math/multi_dispatch.py +++ b/pennylane/math/multi_dispatch.py @@ -173,13 +173,13 @@ def diag(values, k=0): **Example** >>> x = [1., 2., tf.Variable(3.)] - >>> diag(x) + >>> qml.math.diag(x) >>> y = tf.Variable([0.65, 0.2, 0.1]) - >>> diag(y, k=-1) + >>> qml.math.diag(y, k=-1) >> x = torch.tensor([1, 2]) >>> y = torch.tensor([3., 4.]) - >>> cast(x, y) + >>> cast_like(x, y) tensor([1., 2.]) """ dtype = ar.to_numpy(tensor2).dtype.type @@ -139,7 +139,7 @@ def convert_like(tensor1, tensor2): >>> x = np.array([1, 2]) >>> y = tf.Variable([3, 4]) - >>> cast(x, y) + >>> convert_like(x, y) """ return np.asarray(tensor1, like=get_interface(tensor2)) @@ -189,7 +189,6 @@ def requires_grad(tensor): For example, Torch tensors and PennyLane tensors track trainability as a property of the tensor itself. TensorFlow, on the other hand, - only tracks trainability if being watched by a gradient tape. Args: diff --git a/pennylane/operation.py b/pennylane/operation.py index 1142c28cbdb..fea8a597ee4 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -256,11 +256,9 @@ def __copy__(self): cls = self.__class__ copied_op = cls.__new__(cls) copied_op.data = self.data.copy() - copied_op._wires = self.wires - copied_op._name = self._name - - if hasattr(self, "_inverse"): - copied_op._inverse = self._inverse + for attr, value in vars(self).items(): + if attr != "data": + setattr(copied_op, attr, value) return copied_op diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 97fd3ce7581..de82446bd06 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1797,6 +1797,7 @@ class IsingXX(Operation): num_wires = 2 par_domain = "R" grad_method = "A" + generator = [np.array([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]]), -1 / 2] @classmethod def _matrix(cls, *params): @@ -1848,6 +1849,7 @@ class IsingZZ(Operation): num_wires = 2 par_domain = "R" grad_method = "A" + generator = [np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]), -1 / 2] @staticmethod def decomposition(phi, wires): diff --git a/pennylane/qaoa/cycle.py b/pennylane/qaoa/cycle.py index 67cd6832706..9ea355bec50 100644 --- a/pennylane/qaoa/cycle.py +++ b/pennylane/qaoa/cycle.py @@ -408,7 +408,7 @@ def _inner_out_flow_constraint_hamiltonian(graph: nx.DiGraph, node) -> Hamiltoni d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + - ( \sum_{j,(i,j)\in E}\hat{Z}_{ij}) )^{2} + ( \sum_{j,(i,j)\in E}\hat{Z}_{ij} )^{2} Args: graph (nx.DiGraph): the directed graph specifying possible edges diff --git a/pennylane/qnode.py b/pennylane/qnode.py index d0e9a0d7ecc..3381eaea837 100644 --- a/pennylane/qnode.py +++ b/pennylane/qnode.py @@ -263,8 +263,8 @@ def get_best_method(device, interface): This method attempts to determine support for differentiation methods using the following order: - * ``"backprop"`` * ``"device"`` + * ``"backprop"`` * ``"parameter-shift"`` * ``"finite-diff"`` @@ -386,6 +386,13 @@ def _validate_reversible_method(device, interface): f"The {device.short_name} device does not support reversible differentiation." ) + if device.shots is not None: + warnings.warn( + "Requested reversible differentiation to be computed with finite shots." + " Reversible differentiation always calculated exactly.", + UserWarning, + ) + return ReversibleTape, interface, device, {"method": "analytic"} @staticmethod @@ -419,6 +426,13 @@ def _validate_adjoint_method(device, interface): f"The {device.short_name} device does not support adjoint differentiation." ) + if device.shots is not None: + warnings.warn( + "Requested adjoint differentiation to be computed with finite shots." + " Adjoint differentiation always calculated exactly.", + UserWarning, + ) + jac_options = {"method": "device", "jacobian_method": "adjoint_jacobian"} # reuse the forward pass # torch and tensorflow can cache the state diff --git a/pennylane/tape/reversible.py b/pennylane/tape/reversible.py index 4396baacc24..d181f752575 100644 --- a/pennylane/tape/reversible.py +++ b/pennylane/tape/reversible.py @@ -18,6 +18,7 @@ import copy from functools import reduce from string import ascii_letters as ABC +import warnings import numpy as np @@ -35,7 +36,7 @@ class ReversibleTape(JacobianTape): .. note:: - The reversible analytic differentation method has the following restrictions: + The reversible analytic differentiation method has the following restrictions: * As it requires knowledge of the statevector, only statevector simulator devices can be used. @@ -246,6 +247,13 @@ def jacobian(self, device, params=None, **options): # before each Jacobian call, so that the statevector is calculated only once. self._final_state = None + if device.shots is not None: + warnings.warn( + "Requested reversible differentiation to be computed with finite shots." + " Reversible differentiation always calculated exactly.", + UserWarning, + ) + return super().jacobian(device, params, **options) def analytic_pd(self, idx, params, **options): diff --git a/pennylane/templates/state_preparations/mottonen.py b/pennylane/templates/state_preparations/mottonen.py index 860eeb80f50..fe3ca856a52 100644 --- a/pennylane/templates/state_preparations/mottonen.py +++ b/pennylane/templates/state_preparations/mottonen.py @@ -203,6 +203,10 @@ def _get_alpha_y(a, n, k): with np.errstate(divide="ignore", invalid="ignore"): division = numerator / denominator + # Cast the numerator and denominator to ensure compatibility with interfaces + division = qml.math.cast(division, np.float64) + denominator = qml.math.cast(denominator, np.float64) + division = qml.math.where(denominator != 0.0, division, 0.0) return 2 * qml.math.arcsin(qml.math.sqrt(division)) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 688893390b8..0460240dd09 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -46,6 +46,7 @@ ~adjoint ~ctrl ~transforms.invisible + ~apply_controlled_Q ~quantum_monte_carlo Transforms that act on tapes @@ -84,4 +85,4 @@ from .metric_tensor import metric_tensor, metric_tensor_tape from .specs import specs from .qfunc_transforms import make_tape, single_tape_transform, qfunc_transform -from .qmc import quantum_monte_carlo +from .qmc import apply_controlled_Q, quantum_monte_carlo diff --git a/tests/collections/conftest.py b/tests/collections/conftest.py index cb5142d475d..d8f5a3a370e 100644 --- a/tests/collections/conftest.py +++ b/tests/collections/conftest.py @@ -41,7 +41,7 @@ def qnodes(interface, tf_support, torch_support, jax_support): pytest.skip("Skipped, no tf support") if interface == "jax" and not jax_support: - pytest.skip("Skipped, no tf support") + pytest.skip("Skipped, no jax support") dev1 = qml.device("default.qubit", wires=2) dev2 = qml.device("default.qubit", wires=2) diff --git a/tests/kernels/test_kernels.py b/tests/kernels/test_kernels.py index 57ed308f62e..7724d83f29e 100644 --- a/tests/kernels/test_kernels.py +++ b/tests/kernels/test_kernels.py @@ -494,3 +494,91 @@ def test_mitigate_depolarizing_noise_split_channel(self, input, expected_output) noise rates per datapoint.""" output = kern.mitigate_depolarizing_noise(input, self.num_wires, "split_channel") assert np.allclose(output, expected_output) + + +class TestErrorForNonRealistic: + """Tests that the noise mitigation techniques raise an error whenever the + used quantities are too small.""" + + def test_mitigate_depolarizing_noise_wrong_method(self, recwarn): + """Test that an error is raised when specifying an incorrect method.""" + with pytest.raises( + ValueError, match="Incorrect noise depolarization mitigation method specified" + ): + qml.kernels.mitigate_depolarizing_noise(np.array([0]), 4, method="some_dummy_strat") + + def test_mitigate_depolarizing_noise_average_method_error(self, recwarn): + """Test that an error is raised when using the average method for the + mitigation of depolarizing noise with a matrix that has too small diagonal + entries.""" + num_wires = 6 + wires = range(num_wires) + + dev = qml.device("default.qubit", wires=num_wires) + + @qml.qnode(dev) + def kernel_circuit(x1, x2): + qml.templates.AngleEmbedding(x1, wires=wires) + qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=wires) + return qml.probs(wires) + + kernel = lambda x1, x2: kernel_circuit(x1, x2)[0] + + # "Training feature vectors" + X_train = qml.numpy.tensor( + [[0.73096199, 0.19012506, 0.57223395], [0.78126872, 0.53535039, 0.31160784]], + requires_grad=True, + ) + + # Create symmetric square kernel matrix (for training) + K = qml.kernels.square_kernel_matrix(X_train, kernel) + + # Add some (symmetric) Gaussian noise to the kernel matrix. + N = qml.numpy.tensor( + [[-2.33010045, -2.22195441], [-0.40680862, 0.21785961]], requires_grad=True + ) + K += (N + N.T) / 2 + with pytest.raises( + ValueError, match="The average noise mitigation method cannot be applied" + ): + qml.kernels.mitigate_depolarizing_noise(K, num_wires, method="average") + + @pytest.mark.parametrize( + "msg, method", [("single", "single"), ("split channel", "split_channel")] + ) + def test_mitigate_depolarizing_noise_error(self, msg, method): + """Test that an error is raised for the mitigation of depolarizing + noise with a matrix that has too small specified entries for the + single and the split channel strategies.""" + num_wires = 6 + wires = range(num_wires) + + dev = qml.device("default.qubit", wires=num_wires) + + @qml.qnode(dev) + def kernel_circuit(x1, x2): + qml.templates.AngleEmbedding(x1, wires=wires) + qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=wires) + return qml.probs(wires) + + kernel = lambda x1, x2: kernel_circuit(x1, x2)[0] + + # "Training feature vectors" + X_train = qml.numpy.tensor( + [[0.39375865, 0.50895605, 0.30720779], [0.34389837, 0.7043728, 0.40067889]], + requires_grad=True, + ) + + # Create symmetric square kernel matrix (for training) + K = qml.kernels.square_kernel_matrix(X_train, kernel) + + # Add some (symmetric) Gaussian noise to the kernel matrix. + N = qml.numpy.tensor( + [[-1.15035284, 0.36726945], [0.26436627, -0.59287149]], requires_grad=True + ) + K += (N + N.T) / 2 + + with pytest.raises( + ValueError, match=f"The {msg} noise mitigation method cannot be applied" + ): + qml.kernels.mitigate_depolarizing_noise(K, num_wires, method=method) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index ed4ca06b0fc..e6b7a5a80e0 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -18,10 +18,12 @@ import re import pytest import functools +import copy import numpy as np from numpy.linalg import multi_dot from scipy.stats import unitary_group from scipy.linalg import expm +from pennylane import numpy as npp import pennylane as qml from pennylane.wires import Wires @@ -406,10 +408,78 @@ def circuit(basis_state): (qml.Toffoli, Toffoli), ] +PARAMETRIZED_OPERATIONS = [ + qml.RX(0.123, wires=0), + qml.RY(1.434, wires=0), + qml.RZ(2.774, wires=0), + qml.S(wires=0), + qml.SX(wires=0), + qml.T(wires=0), + qml.CNOT(wires=[0, 1]), + qml.CZ(wires=[0, 1]), + qml.CY(wires=[0, 1]), + qml.SWAP(wires=[0, 1]), + qml.ISWAP(wires=[0, 1]), + qml.CSWAP(wires=[0, 1, 2]), + qml.PauliRot(0.123, "Y", wires=0), + qml.IsingXX(0.123, wires=[0, 1]), + qml.IsingZZ(0.123, wires=[0, 1]), + qml.Rot(0.123, 0.456, 0.789, wires=0), + qml.Toffoli(wires=[0, 1, 2]), + qml.PhaseShift(2.133, wires=0), + qml.ControlledPhaseShift(1.777, wires=[0, 2]), + qml.CPhase(1.777, wires=[0, 2]), + qml.MultiRZ(0.112, wires=[1, 2, 3]), + qml.CRX(0.836, wires=[2, 3]), + qml.CRY(0.721, wires=[2, 3]), + qml.CRZ(0.554, wires=[2, 3]), + qml.U1(0.123, wires=0), + qml.U2(3.556, 2.134, wires=0), + qml.U3(2.009, 1.894, 0.7789, wires=0), + qml.Hadamard(wires=0), + qml.PauliX(wires=0), + qml.PauliZ(wires=0), + qml.PauliY(wires=0), + qml.CRot(0.123, 0.456, 0.789, wires=[0, 1]), + qml.QubitUnitary(np.eye(2) * 1j, wires=0), + qml.DiagonalQubitUnitary(np.array([1.0, 1.0j]), wires=1), + qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[0], control_wires=[2]), + qml.MultiControlledX(control_wires=[0, 1], wires=2, control_values="01"), + qml.SingleExcitation(0.123, wires=[0, 3]), + qml.SingleExcitationPlus(0.123, wires=[0, 3]), + qml.SingleExcitationMinus(0.123, wires=[0, 3]), + qml.DoubleExcitation(0.123, wires=[0, 1, 2, 3]), + qml.DoubleExcitationPlus(0.123, wires=[0, 1, 2, 3]), + qml.DoubleExcitationMinus(0.123, wires=[0, 1, 2, 3]), + qml.QubitSum(wires=[0, 1, 2]), +] + class TestOperations: """Tests for the operations""" + @pytest.mark.parametrize("op_cls, mat", NON_PARAMETRIZED_OPERATIONS) + def test_nonparametrized_op_copy(self, op_cls, mat, tol): + """Tests that copied nonparametrized ops function as expected""" + op = op_cls(wires=range(op_cls.num_wires)) + copied_op = copy.copy(op) + np.testing.assert_allclose(op.matrix, copied_op.matrix, atol=tol) + + op._inverse = True + copied_op2 = copy.copy(op) + np.testing.assert_allclose(op.matrix, copied_op2.matrix, atol=tol) + + @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS) + def test_parametrized_op_copy(self, op, tol): + """Tests that copied parametrized ops function as expected""" + copied_op = copy.copy(op) + np.testing.assert_allclose(op.matrix, copied_op.matrix, atol=tol) + + op.inv() + copied_op2 = copy.copy(op) + np.testing.assert_allclose(op.matrix, copied_op2.matrix, atol=tol) + op.inv() + @pytest.mark.parametrize("ops, mat", NON_PARAMETRIZED_OPERATIONS) def test_matrices(self, ops, mat, tol): """Test matrices of non-parametrized operations are correct""" @@ -417,54 +487,7 @@ def test_matrices(self, ops, mat, tol): res = op.matrix assert np.allclose(res, mat, atol=tol, rtol=0) - @pytest.mark.parametrize( - "op", - [ - qml.RX(0.123, wires=0), - qml.RY(1.434, wires=0), - qml.RZ(2.774, wires=0), - qml.S(wires=0), - qml.SX(wires=0), - qml.T(wires=0), - qml.CNOT(wires=[0, 1]), - qml.CZ(wires=[0, 1]), - qml.CY(wires=[0, 1]), - qml.SWAP(wires=[0, 1]), - qml.ISWAP(wires=[0, 1]), - qml.CSWAP(wires=[0, 1, 2]), - qml.PauliRot(0.123, "Y", wires=0), - qml.IsingXX(0.123, wires=[0, 1]), - qml.IsingZZ(0.123, wires=[0, 1]), - qml.Rot(0.123, 0.456, 0.789, wires=0), - qml.Toffoli(wires=[0, 1, 2]), - qml.PhaseShift(2.133, wires=0), - qml.ControlledPhaseShift(1.777, wires=[0, 2]), - qml.CPhase(1.777, wires=[0, 2]), - qml.MultiRZ(0.112, wires=[1, 2, 3]), - qml.CRX(0.836, wires=[2, 3]), - qml.CRY(0.721, wires=[2, 3]), - qml.CRZ(0.554, wires=[2, 3]), - qml.U1(0.123, wires=0), - qml.U2(3.556, 2.134, wires=0), - qml.U3(2.009, 1.894, 0.7789, wires=0), - qml.Hadamard(wires=0), - qml.PauliX(wires=0), - qml.PauliZ(wires=0), - qml.PauliY(wires=0), - qml.CRot(0.123, 0.456, 0.789, wires=[0, 1]), - qml.QubitUnitary(np.eye(2) * 1j, wires=0), - qml.DiagonalQubitUnitary(np.array([1.0, 1.0j]), wires=1), - qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[0], control_wires=[2]), - qml.MultiControlledX(control_wires=[0, 1], wires=2, control_values="01"), - qml.SingleExcitation(0.123, wires=[0, 3]), - qml.SingleExcitationPlus(0.123, wires=[0, 3]), - qml.SingleExcitationMinus(0.123, wires=[0, 3]), - qml.DoubleExcitation(0.123, wires=[0, 1, 2, 3]), - qml.DoubleExcitationPlus(0.123, wires=[0, 1, 2, 3]), - qml.DoubleExcitationMinus(0.123, wires=[0, 1, 2, 3]), - qml.QubitSum(wires=[0, 1, 2]), - ], - ) + @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS) def test_adjoint_unitaries(self, op, tol): op_d = op.adjoint() res1 = np.dot(op.matrix, op_d.matrix) @@ -777,6 +800,241 @@ def test_isingxx_decomposition(self, tol): assert np.allclose(decomposed_matrix, op.matrix, atol=tol, rtol=0) + device_methods = [ + ["default.qubit", "finite-diff"], + ["default.qubit", "parameter-shift"], + ["default.qubit", "backprop"], + ["default.qubit", "adjoint"], + ] + + phis = [0.1, 0.2, 0.3] + + configuration = [] + + for phi in phis: + for device, method in device_methods: + configuration.append([device, method, phi]) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingxx_autograd_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingXX.""" + dev = qml.device(dev_name, wires=2) + + psi_0 = 0.1 + psi_1 = 0.2 + psi_2 = 0.3 + psi_3 = 0.4 + + init_state = npp.array([psi_0, psi_1, psi_2, psi_3], requires_grad=False) + norm = np.linalg.norm(init_state) + init_state /= norm + + @qml.qnode(dev, diff_method=diff_method, interface="autograd") + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingXX(phi, wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + phi = npp.array(0.1, requires_grad=True) + + expected = ( + 0.5 + * (1 / norm ** 2) + * ( + -np.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2) + + 2 + * np.sin(phi / 2) + * np.cos(phi / 2) + * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2) + ) + ) + + res = qml.grad(circuit)(phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingzz_autograd_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingZZ.""" + dev = qml.device(dev_name, wires=2) + + psi_0 = 0.1 + psi_1 = 0.2 + psi_2 = 0.3 + psi_3 = 0.4 + + init_state = npp.array([psi_0, psi_1, psi_2, psi_3], requires_grad=False) + norm = np.linalg.norm(init_state) + init_state /= norm + + @qml.qnode(dev, diff_method=diff_method, interface="autograd") + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingZZ(phi, wires=[0, 1]) + return qml.expval(qml.PauliX(0)) + + phi = npp.array(0.1, requires_grad=True) + + expected = (1 / norm ** 2) * (-2 * (psi_0 * psi_2 + psi_1 * psi_3) * np.sin(phi)) + + res = qml.grad(circuit)(phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingxx_jax_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingXX.""" + + if diff_method in {"finite-diff"}: + pytest.skip("Test does not support finite-diff") + + if diff_method in {"parameter-shift"}: + pytest.skip("Test does not support parameter-shift") + + jax = pytest.importorskip("jax") + jnp = pytest.importorskip("jax.numpy") + + dev = qml.device(dev_name, wires=2) + + psi_0 = 0.1 + psi_1 = 0.2 + psi_2 = 0.3 + psi_3 = 0.4 + + init_state = jnp.array([psi_0, psi_1, psi_2, psi_3]) + norm = jnp.linalg.norm(init_state) + init_state = init_state / norm + + @qml.qnode(dev, diff_method=diff_method, interface="jax") + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingXX(phi, wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + phi = jnp.array(0.1) + + expected = ( + 0.5 + * (1 / norm ** 2) + * ( + -np.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2) + + 2 + * np.sin(phi / 2) + * np.cos(phi / 2) + * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2) + ) + ) + + res = jax.grad(circuit, argnums=0)(phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingzz_jax_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingZZ.""" + + if diff_method in {"finite-diff"}: + pytest.skip("Test does not support finite-diff") + + if diff_method in {"parameter-shift"}: + pytest.skip("Test does not support parameter-shift") + + jax = pytest.importorskip("jax") + jnp = pytest.importorskip("jax.numpy") + + dev = qml.device(dev_name, wires=2) + + psi_0 = 0.1 + psi_1 = 0.2 + psi_2 = 0.3 + psi_3 = 0.4 + + init_state = jnp.array([psi_0, psi_1, psi_2, psi_3]) + norm = jnp.linalg.norm(init_state) + init_state = init_state / norm + + @qml.qnode(dev, diff_method=diff_method, interface="jax") + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingZZ(phi, wires=[0, 1]) + return qml.expval(qml.PauliX(0)) + + phi = jnp.array(0.1) + + expected = (1 / norm ** 2) * (-2 * (psi_0 * psi_2 + psi_1 * psi_3) * np.sin(phi)) + + res = jax.grad(circuit, argnums=0)(phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingxx_tf_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingXX.""" + tf = pytest.importorskip("tensorflow", minversion="2.1") + + dev = qml.device(dev_name, wires=2) + + psi_0 = tf.Variable(0.1, dtype=tf.complex128) + psi_1 = tf.Variable(0.2, dtype=tf.complex128) + psi_2 = tf.Variable(0.3, dtype=tf.complex128) + psi_3 = tf.Variable(0.4, dtype=tf.complex128) + + init_state = tf.Variable([psi_0, psi_1, psi_2, psi_3], dtype=tf.complex128) + norm = tf.norm(init_state) + init_state = init_state / norm + + @qml.qnode(dev, interface="tf", diff_method=diff_method) + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingXX(phi, wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + phi = tf.Variable(0.1, dtype=tf.complex128) + + expected = ( + 0.5 + * (1 / norm ** 2) + * ( + -tf.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2) + + 2 + * tf.sin(phi / 2) + * tf.cos(phi / 2) + * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2) + ) + ) + + with tf.GradientTape() as tape: + result = circuit(phi) + res = tape.gradient(result, phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("dev_name,diff_method,phi", configuration) + def test_isingzz_tf_grad(self, tol, dev_name, diff_method, phi): + """Test the gradient for the gate IsingZZ.""" + tf = pytest.importorskip("tensorflow", minversion="2.1") + + dev = qml.device(dev_name, wires=2) + + psi_0 = tf.Variable(0.1, dtype=tf.complex128) + psi_1 = tf.Variable(0.2, dtype=tf.complex128) + psi_2 = tf.Variable(0.3, dtype=tf.complex128) + psi_3 = tf.Variable(0.4, dtype=tf.complex128) + + init_state = tf.Variable([psi_0, psi_1, psi_2, psi_3], dtype=tf.complex128) + norm = tf.norm(init_state) + init_state = init_state / norm + + @qml.qnode(dev, interface="tf", diff_method=diff_method) + def circuit(phi): + qml.QubitStateVector(init_state, wires=[0, 1]) + qml.IsingZZ(phi, wires=[0, 1]) + return qml.expval(qml.PauliX(0)) + + phi = tf.Variable(0.1, dtype=tf.complex128) + + expected = (1 / norm ** 2) * (-2 * (psi_0 * psi_2 + psi_1 * psi_3) * np.sin(phi)) + + with tf.GradientTape() as tape: + result = circuit(phi) + res = tape.gradient(result, phi) + assert np.allclose(res, expected, atol=tol, rtol=0) + def test_isingzz_decomposition(self, tol): """Tests that the decomposition of the IsingZZ gate is correct""" param = 0.1234 diff --git a/tests/tape/test_qnode.py b/tests/tape/test_qnode.py index d0d10b7e5a8..4504236dff8 100644 --- a/tests/tape/test_qnode.py +++ b/tests/tape/test_qnode.py @@ -261,6 +261,60 @@ def test_validate_adjoint_invalid_device(self): with pytest.raises(ValueError, match="The default.gaussian device does not"): QNode._validate_adjoint_method(dev, "tf") + def test_validate_adjoint_finite_shots(self): + """Test that a UserWarning is raised when device has finite shots""" + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + QNode._validate_adjoint_method(dev, "autograd") + + def test_adjoint_finite_shots(self): + """Tests that UserWarning is raised with the adjoint differentiation method + on QNode construction when the device has finite shots + """ + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + + @qml.qnode(dev, diff_method="adjoint") + def circ(): + return qml.expval(qml.PauliZ(0)) + + def test_validate_reversible_finite_shots(self): + """Test that a UserWarning is raised when validating the reversible differentiation method + and using a device that has finite shots + """ + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, + match="Requested reversible differentiation to be computed with finite shots.", + ): + QNode._validate_reversible_method(dev, "autograd") + + def test_reversible_finite_shots(self): + """Tests that UserWarning is raised with the reversible differentiation method + on QNode construction when the device has finite shots + """ + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, + match="Requested reversible differentiation to be computed with finite shots.", + ): + + @qml.qnode(dev, diff_method="reversible") + def circ(): + return qml.expval(qml.PauliZ(0)) + def test_qnode_print(self): """Test that printing a QNode object yields the right information.""" dev = qml.device("default.qubit", wires=1) diff --git a/tests/tape/test_reversible.py b/tests/tape/test_reversible.py index a83633a7a3a..d391a7f68d0 100644 --- a/tests/tape/test_reversible.py +++ b/tests/tape/test_reversible.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Unit tests for the qubit parameter-shift QubitParamShiftTape""" +"""Unit tests for the ReversibleTape""" import pytest from pennylane import numpy as np @@ -160,6 +160,21 @@ def test_phaseshift_exception(self): class TestGradients: """Jacobian integration tests for qubit expectations.""" + def test_finite_shots_warning(self): + """Test that a warning is raised when calling the jacobian with a device with finite shots""" + + with ReversibleTape() as tape: + qml.RX(0.1, wires=0) + qml.expval(qml.PauliZ(0)) + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, + match="Requested reversible differentiation to be computed with finite shots.", + ): + tape.jacobian(dev) + @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, 2 * np.pi, 7)) @pytest.mark.parametrize("G", [qml.RX, qml.RY, qml.RZ]) def test_pauli_rotation_gradient(self, G, theta, tol): @@ -369,6 +384,27 @@ def test_gradient_gate_with_multiple_parameters(self, tol): class TestQNodeIntegration: """Test QNode integration with the reversible method""" + def test_finite_shots_warning(self): + """Test that a warning is raised when calling the jacobian with a device with finite shots""" + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, + match="Requested reversible differentiation to be computed with finite shots.", + ): + + @qml.qnode(dev, diff_method="reversible") + def circ(x): + qml.RX(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + with pytest.warns( + UserWarning, + match="Requested reversible differentiation to be computed with finite shots.", + ): + qml.grad(circ)(0.1) + def test_qnode(self, mocker, tol): """Test that specifying diff_method allows the reversible method to be selected""" diff --git a/tests/templates/test_state_preparations/test_mottonen_state_prep.py b/tests/templates/test_state_preparations/test_mottonen_state_prep.py index 44de7c4df8e..9958585e24c 100644 --- a/tests/templates/test_state_preparations/test_mottonen_state_prep.py +++ b/tests/templates/test_state_preparations/test_mottonen_state_prep.py @@ -18,6 +18,8 @@ import numpy as np import pennylane as qml from pennylane import numpy as pnp + +torch = pytest.importorskip("torch", minversion="1.3") from pennylane.templates.state_preparations.mottonen import gray_code, _get_alpha_y @@ -342,3 +344,31 @@ def circuit(state_vector): return qml.expval(qml.PauliZ(0)) qml.grad(circuit)(state_vector) + + +class TestCasting: + """Test that the Mottonen state preparation ensures the compatibility with + interfaces by using casting'""" + + @pytest.mark.parametrize( + "inputs, expected", + [ + ( + torch.tensor([0.0, 0.7, 0.7, 0.0], requires_grad=True), + [0.0, 0.5, 0.5, 0.0], + ), + (torch.tensor([0.1, 0.0, 0.0, 0.1], requires_grad=True), [0.5, 0.0, 0.0, 0.5]), + ], + ) + def test_scalar_torch(self, inputs, expected): + """Test that MottonenStatePreparation can be correctly used with the Torch interface.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface="torch") + def circuit(inputs): + qml.templates.MottonenStatePreparation(inputs, wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + inputs = inputs / torch.linalg.norm(inputs) + res = circuit(inputs) + assert np.allclose(res.detach().numpy(), expected, atol=1e-6, rtol=0) diff --git a/tests/test_qubit_device_adjoint_jacobian.py b/tests/test_qubit_device_adjoint_jacobian.py index b421090067d..910886a4dd9 100644 --- a/tests/test_qubit_device_adjoint_jacobian.py +++ b/tests/test_qubit_device_adjoint_jacobian.py @@ -39,6 +39,19 @@ def test_not_expval(self, dev): with pytest.raises(qml.QuantumFunctionError, match="Adjoint differentiation method does"): dev.adjoint_jacobian(tape) + def test_finite_shots_warns(self): + """Tests warning raised when finite shots specified""" + + dev = qml.device("default.qubit", wires=1, shots=1) + + with qml.tape.JacobianTape() as tape: + qml.expval(qml.PauliZ(0)) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + dev.adjoint_jacobian(tape) + def test_unsupported_op(self, dev): """Test if a QuantumFunctionError is raised for an unsupported operation, i.e., multi-parameter operations that are not qml.Rot""" @@ -239,6 +252,25 @@ class TestAdjointJacobianQNode: def dev(self): return qml.device("default.qubit", wires=2) + def test_finite_shots_warning(self): + """Tests that a warning is raised when computing the adjoint diff on a device with finite shots""" + + dev = qml.device("default.qubit", wires=1, shots=1) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + + @qml.qnode(dev, diff_method="adjoint") + def circ(x): + qml.RX(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + with pytest.warns( + UserWarning, match="Requested adjoint differentiation to be computed with finite shots." + ): + qml.grad(circ)(0.1) + def test_qnode(self, mocker, tol, dev): """Test that specifying diff_method allows the adjoint method to be selected""" args = np.array([0.54, 0.1, 0.5], requires_grad=True)