diff --git a/doc/code/qml_tape.rst b/doc/code/qml_tape.rst index 7cba523dd51..43550846a48 100644 --- a/doc/code/qml_tape.rst +++ b/doc/code/qml_tape.rst @@ -173,4 +173,4 @@ For more details and examples, please see the tape documentation. .. automodapi:: pennylane.tape :no-main-docstr: :include-all-objects: - :skip: enable_tape, disable_tape + :skip: QNode, qnode, enable_tape, disable_tape diff --git a/pennylane/__init__.py b/pennylane/__init__.py index 06613270905..0911e56f787 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -230,3 +230,6 @@ def circuit(): def version(): """Returns the PennyLane version number.""" return __version__ + + +enable_tape() diff --git a/pennylane/_device.py b/pennylane/_device.py index 431ce42554b..30936169b5b 100644 --- a/pennylane/_device.py +++ b/pennylane/_device.py @@ -21,6 +21,7 @@ import numpy as np +import pennylane as qml from pennylane.operation import ( Operation, Observable, @@ -550,6 +551,8 @@ def check_validity(self, queue, observables): ) for o in observables: + if isinstance(o, qml.tape.MeasurementProcess) and o.obs is not None: + o = o.obs if isinstance(o, Tensor): # TODO: update when all capabilities keys changed to "supports_tensor_observables" @@ -569,7 +572,6 @@ def check_validity(self, queue, observables): ) ) else: - observable_name = o.name if issubclass(o.__class__, Operation) and o.inverse: diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py index a36ef649de8..c21d0a14959 100644 --- a/pennylane/_qubit_device.py +++ b/pennylane/_qubit_device.py @@ -183,6 +183,13 @@ def execute(self, circuit, **kwargs): Returns: array[float]: measured value(s) """ + # TODO: Remove try/except when circuit is always QuantumTape and + # consider merging with caching case + try: + self._circuit_hash = circuit.graph.hash + except AttributeError as e: + self._circuit_hash = circuit.hash + if self._cache: try: # TODO: Remove try/except when circuit is always QuantumTape circuit_hash = circuit.graph.hash @@ -193,8 +200,6 @@ def execute(self, circuit, **kwargs): self.check_validity(circuit.operations, circuit.observables) - self._circuit_hash = circuit.hash - # apply all circuit operations self.apply(circuit.operations, rotations=circuit.diagonalizing_gates, **kwargs) @@ -205,13 +210,10 @@ def execute(self, circuit, **kwargs): # compute the required statistics results = self.statistics(circuit.observables) - # Ensures that a combination with sample does not put - # expvals and vars in superfluous arrays - all_sampled = all(obs.return_type is Sample for obs in circuit.observables) - if circuit.is_sampled and not all_sampled: - results = self._asarray(results, dtype="object") - else: + if circuit.all_sampled or not circuit.is_sampled: results = self._asarray(results) + else: + results = tuple(self._asarray(r) for r in results) if self._cache and circuit_hash not in self._cache_execute: self._cache_execute[circuit_hash] = results diff --git a/pennylane/beta/devices/default_tensor_tf.py b/pennylane/beta/devices/default_tensor_tf.py index 4ca71321eaf..d20185ebf01 100644 --- a/pennylane/beta/devices/default_tensor_tf.py +++ b/pennylane/beta/devices/default_tensor_tf.py @@ -25,9 +25,8 @@ raise ImportError("default.tensor.tf device requires TensorFlow>=2.0") except ImportError as e: - raise ImportError("default.tensor.tf device requires TensorFlow>=2.0") + raise ImportError("default.tensor.tf device requires TensorFlow>=2.0") from e -from pennylane.variable import Variable from pennylane.beta.devices.default_tensor import DefaultTensor from pennylane.devices import tf_ops as ops @@ -54,50 +53,25 @@ class DefaultTensorTF(DefaultTensor): **Example** - The ``default.tensor.tf`` device supports various differentiation modes. + The ``default.tensor.tf`` device supports end-to-end classical backpropagation with the TensorFlow interface. - * *End-to-end classical backpropagation with the TensorFlow interface*. - Using this method, the created QNode is a 'white-box', and is - tightly integrated with your TensorFlow computation: + Using this method, the created QNode is a 'white-box', and is + tightly integrated with your TensorFlow computation: - >>> dev = qml.device("default.tensor.tf", wires=1) - >>> @qml.qnode(dev, interface="tf", diff_method="backprop") - >>> def circuit(x): - ... qml.RX(x[1], wires=0) - ... qml.Rot(x[0], x[1], x[2], wires=0) - ... return qml.expval(qml.PauliZ(0)) - >>> vars = tf.Variable([0.2, 0.5, 0.1]) - >>> with tf.GradientTape() as tape: - ... res = circuit(vars) - >>> tape.gradient(res, vars) - + >>> dev = qml.device("default.tensor.tf", wires=1) + >>> @qml.qnode(dev, interface="tf", diff_method="backprop") + >>> def circuit(x): + ... qml.RX(x[1], wires=0) + ... qml.Rot(x[0], x[1], x[2], wires=0) + ... return qml.expval(qml.PauliZ(0)) + >>> vars = tf.Variable([0.2, 0.5, 0.1]) + >>> with tf.GradientTape() as tape: + ... res = circuit(vars) + >>> tape.gradient(res, vars) + - In this mode, you must use the ``"tf"`` interface, as TensorFlow - is used as the device backend. - - * *Device differentiation*. Using this method, the created QNode - is a 'black-box' to your classical computation. PennyLane will automatically - accept classical tensors from any supported interface, and query the - device directly for the quantum gradient when required. - - >>> dev = qml.device("default.tensor.tf", wires=1) - >>> @qml.qnode(dev, interface="autograd", diff_method="device") - >>> def circuit(x): - ... qml.RX(x[1], wires=0) - ... qml.Rot(x[0], x[1], x[2], wires=0) - ... return qml.expval(qml.PauliZ(0)) - >>> grad_fn = qml.grad(circuit, argnum=[0]) - >>> print(grad_fn([0.2, 0.5, 0.1])) - ([array(-0.22526717), array(-1.00864546), array(6.9388939e-18)],) - - In this mode, even though TensorFlow is used as the device backend, it - is independent of the chosen QNode interface. In the example above, we combine - ``default.tensor.tf`` with the ``autograd`` interface. - It can also be used with the ``torch`` and the ``tf`` interface. - - In addition to end-to-end classical backpropagation and device differentiation, - the ``default.tensor.tf`` device also supports ``parameter-shift`` and - ``finite-diff`` differentiation methods. + In this mode, you must use the ``"tf"`` interface, as TensorFlow + is used as the device backend. Args: wires (int): number of subsystems in the quantum state represented by the device @@ -144,141 +118,10 @@ class DefaultTensorTF(DefaultTensor): C_DTYPE = ops.C_DTYPE R_DTYPE = ops.R_DTYPE - def __init__(self, wires, shots=1000, representation="exact", contraction_method="auto"): - self.variables = [] - """List[tf.Variable]: Free parameters, cast to TensorFlow variables, - for this circuit.""" - - self.res = None - """tf.tensor[tf.float64]: result from the last circuit execution""" - - self.op_params = {} - """dict[Operation, List[Any, tf.Variable]]: A mapping from each operation - in the queue, to the corresponding list of parameter values. These - values can be Python numeric types, NumPy arrays, or TensorFlow variables.""" - - self.tape = None - """tf.GradientTape: the gradient tape under which all tensor network - modifications must be made""" - - super().__init__( - wires, - shots=shots, - representation=representation, - contraction_method=contraction_method, - ) - @classmethod def capabilities(cls): capabilities = super().capabilities().copy() capabilities.update( - provides_jacobian=True, passthru_interface="tf", ) return capabilities - - def reset(self): - self.res = None - self.variables = [] - super().reset() - - def execution_context(self): - self.tape = tf.GradientTape(persistent=True) - return self.tape - - def pre_apply(self): - super().pre_apply() - - self.op_params = {} - - for operation in self.op_queue: - # Copy the operation parameters to the op_params dictionary. - # Note that these are the unwrapped parameters, so PennyLane - # free parameters will be represented as Variable instances. - self.op_params[operation] = operation.data[:] - - # Loop through the free parameter reference dictionary - for _, par_dep_list in self.parameters.items(): - if not par_dep_list: - # parameter is not used within circuit - v = tf.Variable(0, dtype=self.R_DTYPE) - self.variables.append(v) - continue - - # get the first parameter dependency for each free parameter - first = par_dep_list[0] - - # For the above parameter dependency, get the corresponding - # operation parameter variable, and get the numeric value. - # Convert the resulting value to a TensorFlow tensor. - val = first.op.data[first.par_idx].val - mult = first.op.data[first.par_idx].mult - v = tf.Variable(val / mult, dtype=self.R_DTYPE) - - # Mark the variable to be watched by the gradient tape, - # and append it to the variable list. - self.variables.append(v) - - for p in par_dep_list: - # Replace the existing Variable free parameter in the op_params dictionary - # with the corresponding tf.Variable parameter. - # Note that the free parameter might be scaled by the - # variable.mult scaling factor. - mult = p.op.data[p.par_idx].mult - self.op_params[p.op][p.par_idx] = v * mult - - # check that no Variables remain in the op_params dictionary - values = [item for sublist in self.op_params.values() for item in sublist] - assert not any( - isinstance(v, Variable) for v in values - ), "A pennylane.Variable instance was not correctly converted to a tf.Variable" - - # flatten the variables list in case of nesting - self.variables = tf.nest.flatten(self.variables) - self.tape.watch(self.variables) - - for operation in self.op_queue: - # Apply each operation, but instead of passing operation.parameters - # (which contains the evaluated numeric parameter values), - # pass op_params[operation], which contains numeric values - # for fixed parameters, and tf.Variable objects for free parameters. - super().apply(operation.name, operation.wires, self.op_params[operation]) - - def apply(self, operation, wires, par): - # individual operations are already applied inside self.pre_apply() - pass - - def execute(self, queue, observables, parameters=None, **kwargs): - # pylint: disable=bad-super-call - results = super(DefaultTensor, self).execute(queue, observables, parameters=parameters) - - with self.tape: - # convert the results list into a single tensor - self.res = tf.stack(results) - - if kwargs.get("return_native_type", False): - return self.res - # return the results as a NumPy array - return self.res.numpy() - - def jacobian(self, queue, observables, parameters): - """Calculates the Jacobian of the device circuit using TensorFlow - backpropagation. - - Args: - queue (list[Operation]): operations to be applied to the device - observables (list[Observable]): observables to be measured - parameters (dict[int, ParameterDependency]): reference dictionary - mapping free parameter values to the operations that - depend on them - - Returns: - array[float]: Jacobian matrix of size (``num_params``, ``num_wires``) - """ - self.reset() - self.execute(queue, observables, parameters=parameters) - jac = self.tape.jacobian(self.res, self.variables, experimental_use_pfor=False) - # TODO use unconnected_gradients=tf.UnconnectedGradients.ZERO instead of the following? - jac = [i if i is not None else tf.zeros(self.res.shape, dtype=tf.float64) for i in jac] - jac = tf.stack(jac) - return jac.numpy().T diff --git a/pennylane/circuit_drawer/representation_resolver.py b/pennylane/circuit_drawer/representation_resolver.py index c188f29f65d..02262e8223f 100644 --- a/pennylane/circuit_drawer/representation_resolver.py +++ b/pennylane/circuit_drawer/representation_resolver.py @@ -316,6 +316,9 @@ def operator_representation(self, op, wire): Returns: str: String representation of the Operator """ + if isinstance(op, qml.tape.MeasurementProcess) and op.obs is not None: + op = op.obs + if isinstance(op, qml.operation.Tensor): constituent_representations = [ self.operator_representation(tensor_obs, wire) for tensor_obs in op.obs diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py index 0497ca7b788..1bf7dcd3d0b 100644 --- a/pennylane/circuit_graph.py +++ b/pennylane/circuit_graph.py @@ -160,6 +160,14 @@ def __init__(self, ops, variable_deps, wires): """int: number of wires the circuit contains""" for k, op in enumerate(ops): op.queue_idx = k # store the queue index in the Operator + + if hasattr(op, "return_type") and op.return_type is qml.operation.State: + # State measurements contain no wires by default, but wires are + # required for the circuit drawer, so we recreate the state + # measurement with all wires + op = qml.tape.MeasurementProcess(qml.operation.State, wires=wires) + op.queue_idx = k + for w in op.wires: # get the index of the wire on the device wire = wires.index(w) @@ -253,7 +261,7 @@ def hash(self): """ return hash(self.serialize()) - def to_openqasm(self, rotations=True): + def to_openqasm(self, wires=None, rotations=True): """Serialize the circuit as an OpenQASM 2.0 program. Only operations are serialized; all measurements @@ -265,6 +273,7 @@ def to_openqasm(self, rotations=True): in ``qelib1.inc`` are available. Args: + wires (Wires or None): the wires to use when serializing the circuit rotations (bool): in addition to serializing user-specified operations, also include the gates that diagonalize the measured wires such that they are in the eigenbasis of the circuit observables. @@ -273,16 +282,7 @@ def to_openqasm(self, rotations=True): str: OpenQASM serialization of the circuit """ # We import decompose_queue here to avoid a circular import - from pennylane.qnodes.base import decompose_queue # pylint: disable=import-outside-toplevel - - class QASMSerializerDevice: - """A mock device, to be used when performing the decomposition. - The short_name is used in error messages if the decomposition fails. - """ - - # pylint: disable=too-few-public-methods - short_name = "QASM serializer" - supports_operation = staticmethod(lambda x: x in OPENQASM_GATES) + wires = wires or self.wires # add the QASM headers qasm_str = "OPENQASM 2.0;\n" @@ -293,8 +293,8 @@ class QASMSerializerDevice: return qasm_str # create the quantum and classical registers - qasm_str += "qreg q[{}];\n".format(self.num_wires) - qasm_str += "creg c[{}];\n".format(self.num_wires) + qasm_str += "qreg q[{}];\n".format(len(wires)) + qasm_str += "creg c[{}];\n".format(len(wires)) # get the user applied circuit operations operations = self.operations @@ -304,13 +304,24 @@ class QASMSerializerDevice: # to circuit observables operations += self.diagonalizing_gates + with qml.tape.QuantumTape() as tape: + for op in operations: + op.queue() + + if op.inverse: + op.inv() + # decompose the queue - decomposed_ops = decompose_queue(operations, QASMSerializerDevice) + operations = tape.expand(stop_at=lambda obj: obj.name in OPENQASM_GATES).operations # create the QASM code representing the operations - for op in decomposed_ops: - gate = OPENQASM_GATES[op.name] - wires = ",".join(["q[{}]".format(self.wires.index(w)) for w in op.wires.tolist()]) + for op in operations: + try: + gate = OPENQASM_GATES[op.name] + except KeyError as e: + raise ValueError(f"Operation {op.name} not supported by the QASM serializer") from e + + wire_labels = ",".join([f"q[{wires.index(w)}]" for w in op.wires.tolist()]) params = "" if op.num_params > 0: @@ -318,14 +329,16 @@ class QASMSerializerDevice: # with parameter values. params = "(" + ",".join([str(p) for p in op.parameters]) + ")" - qasm_str += "{name}{params} {wires};\n".format(name=gate, params=params, wires=wires) + qasm_str += "{name}{params} {wires};\n".format( + name=gate, params=params, wires=wire_labels + ) # apply computational basis measurements to each quantum register # NOTE: This is not strictly necessary, we could inspect self.observables, # and then only measure wires which are requested by the user. However, # some devices which consume QASM require all registers be measured, so # measure all wires to be safe. - for wire in range(self.num_wires): + for wire in range(len(wires)): qasm_str += "measure q[{wire}] -> c[{wire}];\n".format(wire=wire) return qasm_str @@ -701,4 +714,12 @@ def diagonalizing_gates(self): def is_sampled(self): """Returns ``True`` if the circuit graph contains observables which are sampled.""" + # TODO: remove when tape is core return any(obs.return_type == Sample for obs in self.observables_in_order) + + @property + def all_sampled(self): + """Returns ``True`` if the circuit graph contains observables + which are sampled.""" + # TODO: remove when tape is core + return all(obs.return_type == Sample for obs in self.observables_in_order) diff --git a/pennylane/devices/default_qubit_jax.py b/pennylane/devices/default_qubit_jax.py index a88aa83bde4..439197f50d1 100644 --- a/pennylane/devices/default_qubit_jax.py +++ b/pennylane/devices/default_qubit_jax.py @@ -54,7 +54,6 @@ class DefaultQubitJax(DefaultQubit): Using this method, the created QNode is a 'white-box', and is tightly integrated with your JAX computation: - >>> qml.enable_tape() >>> dev = qml.device("default.qubit.jax", wires=1) >>> @qml.qnode(dev, interface="jax", diff_method="backprop") ... def circuit(x): diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 3c493745f85..28e47f4a1b2 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -25,7 +25,6 @@ from pennylane.operation import AnyWires, Observable, Operation, DiagonalOperation from pennylane.templates.state_preparations import BasisStatePreparation, MottonenStatePreparation from pennylane.utils import pauli_eigs, expand -from pennylane._queuing import OperationRecorder import pennylane as qml INV_SQRT2 = 1 / math.sqrt(2) @@ -1615,10 +1614,7 @@ class BasisState(Operation): @staticmethod def decomposition(n, wires): - with OperationRecorder() as rec: - BasisStatePreparation(n, wires) - - return rec.queue + return BasisStatePreparation(n, wires) class QubitStateVector(Operation): @@ -1649,10 +1645,7 @@ class QubitStateVector(Operation): @staticmethod def decomposition(state, wires): - with OperationRecorder() as rec: - MottonenStatePreparation(state, wires) - - return rec.queue + return MottonenStatePreparation(state, wires) # ============================================================================= diff --git a/pennylane/tape/__init__.py b/pennylane/tape/__init__.py index e46a866f1ab..de23d8fb0b3 100644 --- a/pennylane/tape/__init__.py +++ b/pennylane/tape/__init__.py @@ -17,6 +17,7 @@ """ import contextlib import inspect +import functools from unittest import mock import warnings @@ -40,6 +41,128 @@ _mock_stack = [] +class TapeOperationRecorder(QuantumTape): + """A template and quantum function inspector, + allowing easy introspection of operators that have been + applied without requiring a QNode. + + **Example**: + + The OperationRecorder is a context manager. Executing templates + or quantum functions stores applied operators in the + recorder, which can then be printed. + + >>> weights = qml.init.strong_ent_layers_normal(n_layers=1, n_wires=2) + >>> + >>> with OperationRecorder() as rec: + >>> qml.templates.layers.StronglyEntanglingLayers(*weights, wires=[0, 1]) + >>> + >>> print(rec) + Operations + ========== + Rot(-0.10832656163640327, 0.14429091013664083, -0.010835826725765343, wires=[0]) + Rot(-0.11254523669444501, 0.0947222564914006, -0.09139600968423377, wires=[1]) + CNOT(wires=[0, 1]) + CNOT(wires=[1, 0]) + + Alternatively, the :attr:`~.OperationRecorder.queue` attribute can be used + to directly access the applied :class:`~.Operation` and :class:`~.Observable` + objects. + + Attributes: + queue (List[Operator]): list of operators applied within + the OperatorRecorder context, includes operations and observables + operations (List[Operation]): list of operations applied within + the OperatorRecorder context + observables (List[Observable]): list of observables applied within + the OperatorRecorder context + """ + + def __init__(self): + super().__init__() + self.ops = None + self.obs = None + + def _process_queue(self): + super()._process_queue() + + for obj, info in self._queue.items(): + QueuingContext.append(obj, **info) + + # remove the operation recorder from the queuing + # context + QueuingContext.remove(self) + + new_tape = self.expand(depth=5, stop_at=lambda obj: not isinstance(obj, QuantumTape)) + self.ops = new_tape.operations + self.obs = new_tape.observables + + def __str__(self): + output = "" + output += "Operations\n" + output += "==========\n" + for op in self.ops: + output += repr(op) + "\n" + + output += "\n" + output += "Observables\n" + output += "==========\n" + for op in self.obs: + output += repr(op) + "\n" + + return output + + @property + def queue(self): + return self.ops + self.obs + + +def TapeTemplateDecorator(func): + """Register a quantum template with PennyLane. + + This decorator wraps the given function and makes it return a list of all queued Operations. + + **Example:** + + When defining a :doc:`template `, simply decorate + the template function with this decorator. + + .. code-block:: python3 + + @qml.template + def bell_state_preparation(wires): + qml.Hadamard(wires=wires[0]) + qml.CNOT(wires=wires) + + This registers the template with PennyLane, making it compatible with + functions that act on templates, such as :func:`pennylane.inv`: + + .. code-block:: python3 + + dev = qml.device('default.qubit', wires=2) + + @qml.qnode(dev) + def circuit(): + qml.inv(bell_state_preparation(wires=[0, 1])) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + Args: + func (callable): A template function + + Returns: + callable: The wrapper function + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with TapeOperationRecorder() as rec: + func(*args, **kwargs) + + return rec.queue + + return wrapper + + def enable_tape(): """Enable tape mode. @@ -82,6 +205,8 @@ def enable_tape(): mock.patch("pennylane.var", measure.var), mock.patch("pennylane.probs", measure.probs), mock.patch("pennylane.sample", measure.sample), + mock.patch("pennylane._queuing.OperationRecorder", TapeOperationRecorder), + mock.patch("pennylane.template", TapeTemplateDecorator), ] with contextlib.ExitStack() as stack: diff --git a/pennylane/tape/circuit_graph.py b/pennylane/tape/circuit_graph.py index 9775ca4cc09..b1c90ab9833 100644 --- a/pennylane/tape/circuit_graph.py +++ b/pennylane/tape/circuit_graph.py @@ -18,7 +18,6 @@ # pylint: disable=too-many-arguments import networkx as nx -import pennylane as qml from pennylane.circuit_graph import CircuitGraph, Layer @@ -54,11 +53,6 @@ def __init__(self, ops, obs, wires, par_info=None, trainable_params=None): self._depth = None - for m in self._observables: - if m.return_type is qml.operation.State: - # state measurements are applied to all device wires - m._wires = wires # pylint: disable=protected-access - super().__init__(ops + obs, variable_deps={}, wires=wires) # For computing depth; want only a graph with the operations, not diff --git a/pennylane/tape/interfaces/autograd.py b/pennylane/tape/interfaces/autograd.py index fdcbaf2bbe8..b82673b5270 100644 --- a/pennylane/tape/interfaces/autograd.py +++ b/pennylane/tape/interfaces/autograd.py @@ -21,7 +21,6 @@ from autograd.numpy.numpy_boxes import ArrayBox from pennylane import numpy as np - from pennylane.tape.queuing import AnnotatedQueue @@ -166,6 +165,9 @@ def _execute(self, params, device): res = self.execute_device(params, device=device) self.set_parameters(self._all_parameter_values, trainable_only=False) + if self.is_sampled: + return res + if res.dtype == np.dtype("object"): return np.hstack(res) diff --git a/pennylane/tape/interfaces/tf.py b/pennylane/tape/interfaces/tf.py index 1a39f746e55..e121f65268a 100644 --- a/pennylane/tape/interfaces/tf.py +++ b/pennylane/tape/interfaces/tf.py @@ -159,6 +159,9 @@ def grad(grad_output, **tfkwargs): return grad_input + if self.is_sampled: + return res, grad + if res.dtype == np.dtype("object"): res = np.hstack(res) diff --git a/pennylane/tape/interfaces/torch.py b/pennylane/tape/interfaces/torch.py index 3b4c35da71d..ffced188b6b 100644 --- a/pennylane/tape/interfaces/torch.py +++ b/pennylane/tape/interfaces/torch.py @@ -49,6 +49,9 @@ def forward(ctx, input_kwargs, *input_): res = tape.execute_device(ctx.args, device) tape.set_parameters(ctx.all_params, trainable_only=False) + if hasattr(res, "numpy"): + res = res.numpy() + # if any input tensor uses the GPU, the output should as well for i in input_: if isinstance(i, torch.Tensor): @@ -58,6 +61,9 @@ def forward(ctx, input_kwargs, *input_): torch.from_numpy(res), device=cuda_device, dtype=tape.dtype ) + if tape.is_sampled and not tape.all_sampled: + return tuple([torch.as_tensor(t, dtype=tape.dtype) for t in res]) + if res.dtype == np.dtype("object"): res = np.hstack(res) diff --git a/pennylane/tape/measure.py b/pennylane/tape/measure.py index 6224c1b416a..e79c20bd43a 100644 --- a/pennylane/tape/measure.py +++ b/pennylane/tape/measure.py @@ -72,16 +72,27 @@ def __init__(self, return_type, obs=None, wires=None, eigvals=None): # Below, we imitate an identity observable, so that the # device undertakes no action upon recieving this observable. self.name = "Identity" - self.diagonalizing_gates = lambda: [] self.data = [] # Queue the measurement process self.queue() + def diagonalizing_gates(self): + """Returns the gates that diagonalize the measured wires such that they + are in the eigenbasis of the circuit observables. + + Returns: + List[.Operation]: the operations that diagonalize the observables + """ + try: + return self.expand().operations + except NotImplementedError: + return [] + def __repr__(self): """Representation of this class.""" if self.obs is None: - return "{}(wires={})".format(self.return_type.value, self.wires) + return "{}(wires={})".format(self.return_type.value, self.wires.tolist()) # Todo: when tape is core the return type will always be taken from the MeasurementProcess if self.obs.return_type is None: @@ -345,14 +356,6 @@ def circuit(): def state(): r"""Quantum state in the computational basis. - .. note:: - - The quantum state can only be returned in tape mode: - - >>> qml.enable_tape() - - For more details on tape mode, see :mod:`pennylane.tape`. - This function accepts no observables and instead instructs the QNode to return its state. A ``wires`` argument should *not* be provided since ``state()`` always returns a pure state describing all wires in the device. @@ -361,8 +364,6 @@ def state(): .. code-block:: python3 - qml.enable_tape() - dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) @@ -391,14 +392,6 @@ def circuit(): def density_matrix(wires): r"""Quantum density matrix in the computational basis. - .. note:: - - The density matrix can only be returned in tape mode: - - >>> qml.enable_tape() - - For more details on tape mode, see :mod:`pennylane.tape`. - This function accepts no observables and instead instructs the QNode to return its density matrix or reduced density matrix. The ``wires`` argument gives the possibility to trace out a part of the system. It can result in obtaining a mixed state, which can be @@ -408,8 +401,6 @@ def density_matrix(wires): .. code-block:: python3 - qml.enable_tape() - dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) diff --git a/pennylane/tape/qnode.py b/pennylane/tape/qnode.py index 82f9c19d485..56277da76c2 100644 --- a/pennylane/tape/qnode.py +++ b/pennylane/tape/qnode.py @@ -37,26 +37,9 @@ class QNode: (corresponding to a :ref:`variational circuit `) and the computational device it is executed on. - The QNode calls the quantum function to construct a :class:`~.JacobianTape` instance representing + The QNode calls the quantum function to construct a :class:`~.QuantumTape` instance representing the quantum circuit. - .. note:: - - The quantum tape is an *experimental* feature. QNodes that use the quantum - tape have access to advanced features, such as in-QNode classical processing, - but do not yet have feature parity with the standard PennyLane QNode. - - This quantum tape-comaptible QNode can either be created directly, - - >>> import pennylane as qml - >>> qml.tape.QNode(qfunc, dev) - - or enabled globally via :func:`~.enable_tape` without changing your PennyLane code: - - >>> qml.enable_tape() - - For more details, see :mod:`pennylane.tape`. - Args: func (callable): a quantum function device (~.Device): a PennyLane-compatible device @@ -124,7 +107,6 @@ class QNode: **Example** - >>> qml.enable_tape() >>> def circuit(x): ... qml.RX(x, wires=0) ... return expval(qml.PauliZ(0)) @@ -491,21 +473,32 @@ def construct(self, args, kwargs): "All measurements must be returned in the order they are measured." ) + for obj in self.qtape.operations + self.qtape.observables: + if getattr(obj, "num_wires", None) is qml.operation.WiresEnum.AllWires: + # check here only if enough wires + if len(obj.wires) != self.device.num_wires: + raise qml.QuantumFunctionError( + "Operator {} must act on all wires".format(obj.name) + ) + # provide the jacobian options self.qtape.jacobian_options = self.diff_options # pylint: disable=protected-access obs_on_same_wire = len(self.qtape._obs_sharing_wires) > 0 ops_not_supported = any( - not self.device.supports_operation(op.name) for op in self.qtape.operations + isinstance(op, qml.tape.QuantumTape) # nested tapes must be expanded + or not self.device.supports_operation(op.name) # unsupported ops must be expanded + for op in self.qtape.operations ) - # expand out the tape, if any operations are not supported on the device or multiple - # observables are measured on the same wire + # expand out the tape, if nested tapes are present, any operations are not supported on the + # device, or multiple observables are measured on the same wire if ops_not_supported or obs_on_same_wire: self.qtape = self.qtape.expand( depth=self.max_expansion, - stop_at=lambda obj: self.device.supports_operation(obj.name), + stop_at=lambda obj: not isinstance(obj, qml.tape.QuantumTape) + and self.device.supports_operation(obj.name), ) def __call__(self, *args, **kwargs): @@ -525,20 +518,7 @@ def __call__(self, *args, **kwargs): if isinstance(self.qfunc_output, Sequence): return res - # HOTFIX: Output is a single measurement function. To maintain compatibility - # with core, we squeeze all outputs. - - # Get the namespace associated with the return type - res_type_namespace = res.__class__.__module__.split(".")[0] - - if res_type_namespace in ("pennylane", "autograd"): - # For PennyLane and autograd we must branch, since - # 'squeeze' does not exist in the top-level of the namespace - return anp.squeeze(res) - # Same for JAX - if res_type_namespace == "jax": - return __import__(res_type_namespace).numpy.squeeze(res) - return __import__(res_type_namespace).squeeze(res) + return qml.math.squeeze(res) def metric_tensor(self, *args, diag_approx=False, only_construct=False, **kwargs): """Evaluate the value of the metric tensor. @@ -772,26 +752,9 @@ def qnode(device, interface="autograd", diff_method="best", **diff_options): :ref:`quantum variational circuit ` that should be bound to a compatible device. - The QNode calls the quantum function to construct a :class:`~.JacobianTape` instance representing + The QNode calls the quantum function to construct a :class:`~.QuantumTape` instance representing the quantum circuit. - .. note:: - - The quantum tape is an *experimental* feature. QNodes that use the quantum - tape have access to advanced features, such as in-QNode classical processing, - but do not yet have feature parity with the standard PennyLane QNode. - - This quantum tape-comaptible QNode can either be created directly, - - >>> import pennylane as qml - >>> @qml.tape.qnode(dev) - - or enabled globally via :func:`~.enable_tape` without changing your PennyLane code: - - >>> qml.enable_tape() - - For more details, see :mod:`pennylane.tape`. - Args: func (callable): a quantum function device (~.Device): a PennyLane-compatible device @@ -858,7 +821,6 @@ def qnode(device, interface="autograd", diff_method="best", **diff_options): **Example** - >>> qml.enable_tape() >>> dev = qml.device("default.qubit", wires=1) >>> @qml.qnode(dev) >>> def circuit(x): diff --git a/pennylane/tape/tapes/jacobian_tape.py b/pennylane/tape/tapes/jacobian_tape.py index 7361645d4dc..c2611f551e0 100644 --- a/pennylane/tape/tapes/jacobian_tape.py +++ b/pennylane/tape/tapes/jacobian_tape.py @@ -430,7 +430,7 @@ def jacobian(self, device, params=None, **options): h=1e-7 (float): finite difference method step size order=1 (int): The order of the finite difference method to use. ``1`` corresponds to forward finite differences, ``2`` to centered finite differences. - shift=pi/2 (float): the size of the shift for two-term parameter-shift gradient computations + shift=pi/2 (float): the size of the shift for two-term parameter-shift gradient computations Returns: array[float]: 2-dimensional array of shape ``(tape.output_dim, tape.num_params)`` @@ -444,7 +444,7 @@ def jacobian(self, device, params=None, **options): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - probs(wires=[0, 'a']) + qml.probs(wires=[0, 'a']) If parameters are not provided, the existing tape parameters are used: @@ -489,6 +489,11 @@ def jacobian(self, device, params=None, **options): if any([m.return_type is State for m in self.measurements]): raise ValueError("The jacobian method does not support circuits that return the state") + if self.is_sampled: + raise qml.QuantumFunctionError( + "Circuits that include sampling can not be differentiated." + ) + method = options.get("method", "best") if method not in ("best", "numeric", "analytic", "device"): diff --git a/pennylane/tape/tapes/tape.py b/pennylane/tape/tapes/tape.py index e56dac97894..1b13f611af7 100644 --- a/pennylane/tape/tapes/tape.py +++ b/pennylane/tape/tapes/tape.py @@ -68,7 +68,7 @@ def expand_tape(tape, depth=1, stop_at=None, expand_measurements=False): qml.CNOT(wires=[0, 'a']) qml.RY(0.2, wires='a') - probs(wires=0), probs(wires='a') + qml.probs(wires=0), qml.probs(wires='a') The nested structure is preserved: @@ -150,8 +150,8 @@ def expand_tape(tape, depth=1, stop_at=None, expand_measurements=False): new_tape._ops += expanded_tape._ops new_tape._measurements += expanded_tape._measurements - # Check the observables without needing to create the circuit graph - new_tape.is_sampled = any(obs.return_type == Sample for obs in new_tape.observables) + # Update circuit info + new_tape._update_circuit_info() return new_tape @@ -178,7 +178,7 @@ class QuantumTape(AnnotatedQueue): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) Once constructed, information about the quantum circuit can be queried: @@ -254,8 +254,8 @@ def __init__(self, name=None): self.wires = qml.wires.Wires([]) self.num_wires = 0 - self.hash = 0 self.is_sampled = False + self.all_sampled = False self.inverse = False self._stack = None @@ -377,6 +377,7 @@ def _update_circuit_info(self): [op.wires for op in self.operations + self.observables] ) self.num_wires = len(self.wires) + self.all_sampled = all(m.return_type is Sample for m in self.measurements) def _update_observables(self): """Update information about observables, including the wires that are acted upon and @@ -447,7 +448,7 @@ def expand(self, depth=1, stop_at=None, expand_measurements=False): qml.CNOT(wires=[0, 'a']) qml.RY(0.2, wires='a') - probs(wires=0), probs(wires='a') + qml.probs(wires=0), qml.probs(wires='a') The nested structure is preserved: @@ -493,7 +494,7 @@ def inv(self): qml.RX(0.432, wires=0) qml.Rot(0.543, 0.1, 0.4, wires=0).inv() qml.CNOT(wires=[0, 'a']) - probs(wires=0), probs(wires='a') + qml.probs(wires=0), qml.probs(wires='a') This tape has the following properties: @@ -600,7 +601,7 @@ def trainable_params(self): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) >>> tape.trainable_params {0, 1, 2} @@ -646,7 +647,7 @@ def get_parameters(self, trainable_only=True, **kwargs): # pylint:disable=unuse qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) By default, all parameters are trainable and will be returned: @@ -694,7 +695,7 @@ def set_parameters(self, params, trainable_only=True): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) By default, all parameters are trainable and can be modified: @@ -752,7 +753,7 @@ def operations(self): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) >>> tape.operations [RX(0.432, wires=[0]), RY(0.543, wires=[0]), CNOT(wires=[0, 'a']), RX(0.133, wires=['a'])] @@ -775,7 +776,7 @@ def observables(self): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) >>> tape.observables [expval(PauliZ(wires=[0]))] @@ -810,7 +811,7 @@ def measurements(self): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) >>> tape.measurements [] @@ -1042,7 +1043,7 @@ def execute(self, device, params=None): qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') - probs(wires=[0, 'a']) + qml.probs(wires=[0, 'a']) If parameters are not provided, the existing tape parameters are used: diff --git a/pennylane/templates/decorator.py b/pennylane/templates/decorator.py index 55181e4edea..4f2b1c31a3b 100644 --- a/pennylane/templates/decorator.py +++ b/pennylane/templates/decorator.py @@ -54,10 +54,18 @@ def circuit(): Returns: callable: The wrapper function """ + # pylint: disable=import-outside-toplevel @wraps(func) def wrapper(*args, **kwargs): - with OperationRecorder() as rec: + import pennylane as qml + + recorder_class = OperationRecorder + + if qml.tape_mode_active(): + recorder_class = qml.tape.TapeOperationRecorder + + with recorder_class() as rec: func(*args, **kwargs) return rec.queue diff --git a/pennylane/templates/layer.py b/pennylane/templates/layer.py index b739fcc0205..46cc8330b4c 100644 --- a/pennylane/templates/layer.py +++ b/pennylane/templates/layer.py @@ -15,7 +15,6 @@ Contains the ``layer`` template constructor. """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access -import pennylane as qml from pennylane.templates.decorator import template as temp @@ -31,9 +30,6 @@ def _preprocess(args, depth): for arg in args: - if qml.tape_mode_active(): - arg = qml.math.angle(arg) - if len(arg) != depth: raise ValueError( "Each positional argument must have length matching 'depth'; expected {} got {}".format( diff --git a/pennylane/utils.py b/pennylane/utils.py index a08f4f3d471..a8d8d5be372 100644 --- a/pennylane/utils.py +++ b/pennylane/utils.py @@ -15,7 +15,7 @@ This module contains utilities and auxiliary functions which are shared across the PennyLane submodules. """ -# pylint: disable=protected-access +# pylint: disable=protected-access,too-many-branches from collections.abc import Iterable import copy import functools @@ -306,6 +306,9 @@ def circuit2(): "This could happen if inversion of a template function is attempted. " "Please use inv on the function including its arguments, as in inv(template(args))." ) + elif isinstance(operation_list, qml.tape.QuantumTape): + operation_list.inv() + return operation_list elif not isinstance(operation_list, Iterable): raise ValueError("The provided operation_list is not iterable.") @@ -323,6 +326,26 @@ def circuit2(): + ",".join(string_reps) ) + if qml.tape_mode_active(): + for op in operation_list: + try: + # remove the queued operation to be inverted + # from the existing queuing context + qml.tape.QueuingContext.remove(op) + except KeyError: + # operation to be inverted does not + # exist on the queuing context + pass + + with qml.tape.QuantumTape() as tape: + for o in operation_list: + o.queue() + if o.inverse: + o.inv() + + tape.inv() + return tape + inv_ops = [op.inv() for op in reversed(copy.deepcopy(operation_list))] for op in operation_list: diff --git a/pennylane/vqe/vqe.py b/pennylane/vqe/vqe.py index fd0e563aa32..2df03ea913a 100644 --- a/pennylane/vqe/vqe.py +++ b/pennylane/vqe/vqe.py @@ -441,7 +441,6 @@ class ExpvalCost: .. code-block:: python - qml.enable_tape() commuting_obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1)] H = qml.vqe.Hamiltonian([1, 1], commuting_obs) @@ -463,8 +462,6 @@ class ExpvalCost: Number of executions: 2 >>> print("Number of executions (optimized):", ex_opt) Number of executions (optimized): 1 - - Note that this feature is only available in :doc:`tape mode <../../code/qml_tape>`. """ def __init__( diff --git a/tests/beta/test_default_tensor.py b/tests/beta/test_default_tensor.py index b48ec25d0c0..30770361e01 100644 --- a/tests/beta/test_default_tensor.py +++ b/tests/beta/test_default_tensor.py @@ -868,8 +868,8 @@ def circuit(*x): return qml.expval(qml.X(0)) with pytest.raises( - QuantumFunctionError, - match="Device default.tensor is a qubit device; CV operations are not allowed.", + qml._device.DeviceError, + match="not supported on device default.tensor", ): x = np.random.random([op.num_params]) circuit(*x) @@ -894,8 +894,8 @@ def circuit(*x): return qml.expval(op(*x, wires=wires)) with pytest.raises( - QuantumFunctionError, - match="Device default.tensor is a qubit device; CV operations are not allowed.", + qml._device.DeviceError, + match="not supported on device default.tensor", ): x = np.random.random([op.num_params]) circuit(*x) diff --git a/tests/beta/test_default_tensor_tf.py b/tests/beta/test_default_tensor_tf.py index 92b1b6b9221..556d65f33f9 100644 --- a/tests/beta/test_default_tensor_tf.py +++ b/tests/beta/test_default_tensor_tf.py @@ -25,8 +25,6 @@ import pennylane as qml from pennylane.beta.devices.default_tensor_tf import DefaultTensorTF from gate_data import I, X, Y, Z, H, CNOT, SWAP, CNOT, Toffoli, CSWAP -from pennylane.qnodes import qnode, QNode -from pennylane.qnodes.decorator import ALLOWED_INTERFACES, ALLOWED_DIFF_METHODS np.random.seed(42) @@ -401,19 +399,16 @@ def test_load_tensornet_tf_device(self, rep): assert dev.num_wires == 2 assert dev.shots == 1000 assert dev.short_name == "default.tensor.tf" - assert dev.capabilities()["provides_jacobian"] assert dev.capabilities()["passthru_interface"] == "tf" - @pytest.mark.parametrize("decorator", [qml.qnode, qnode]) - def test_qubit_circuit(self, decorator, rep, tol): + def test_qubit_circuit(self, rep, tol): """Test that the tensor network plugin provides correct - result for a simple circuit using the old QNode. - This test is parametrized for both the new and old QNode decorator.""" + result for a simple circuit.""" p = 0.543 dev = qml.device("default.tensor.tf", wires=1, representation=rep) - @decorator(dev) + @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliY(0)) @@ -458,7 +453,7 @@ def test_jacobian_variable_multiply(self, torch_support, rep, tol): dev = qml.device("default.tensor.tf", wires=1, representation=rep) - @qnode(dev) + @qml.qnode(dev) def circuit(p): qml.RX(3 * p[0], wires=0) qml.RY(p[1], wires=0) @@ -469,7 +464,7 @@ def circuit(p): expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2) assert np.allclose(res, expected, atol=tol, rtol=0) - res = circuit.jacobian([[x, y, z]]) + res = qml.jacobian(circuit)(np.array([x, y, z])) expected = np.array( [ -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)), @@ -489,7 +484,7 @@ def test_jacobian_repeated(self, torch_support, rep, tol): p = np.array([x, y, z]) dev = qml.device("default.tensor.tf", wires=1, representation=rep) - @qnode(dev) + @qml.qnode(dev) def circuit(x): qml.RX(x[1], wires=0) qml.Rot(x[0], x[1], x[2], wires=0) @@ -499,34 +494,47 @@ def circuit(x): expected = np.cos(y) ** 2 - np.sin(x) * np.sin(y) ** 2 assert np.allclose(res, expected, atol=tol, rtol=0) - res = circuit.jacobian([p]) + res = qml.jacobian(circuit)(p) expected = np.array( [-np.cos(x) * np.sin(y) ** 2, -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y), 0,] ) assert np.allclose(res, expected, atol=tol, rtol=0) - @pytest.mark.parametrize("diff_method", ["parameter-shift", "finite-diff", "device"]) - def test_jacobian_agrees(self, diff_method, torch_support, rep, tol): - """Test that qnode.jacobian applied to the tensornet.tf device + @pytest.mark.parametrize("diff_method", ["parameter-shift", "finite-diff", "backprop"]) + def test_gradient_agrees(self, diff_method, torch_support, rep, tol): + """Test that gradient applied to the tensornet.tf device returns the same result as default.qubit.""" - p = np.array([0.43316321, 0.2162158, 0.75110998, 0.94714242]) + import tensorflow as tf + + p = tf.Variable([0.43316321, 0.2162158, 0.75110998, 0.94714242]) def circuit(x): - for i in range(0, len(p), 2): + # multiply the Variable by 1.0 to make it iterable + for i in range(0, len(1.0 * p), 2): qml.RX(x[i], wires=0) qml.RY(x[i + 1], wires=1) for i in range(2): qml.CNOT(wires=[i, i + 1]) - return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + return qml.var(qml.PauliZ(1)) dev1 = qml.device("default.tensor.tf", wires=3, representation=rep) dev2 = qml.device("default.qubit", wires=3) - circuit1 = QNode(circuit, dev1, diff_method=diff_method, h=1e-7) - circuit2 = QNode(circuit, dev2, diff_method="parameter-shift", h=1e-7) + circuit1 = qml.QNode(circuit, dev1, diff_method=diff_method, h=1e-7, interface="tf") + circuit2 = qml.QNode(circuit, dev2, diff_method="parameter-shift", h=1e-7, interface="tf") + + with tf.GradientTape() as tape1: + res1 = circuit1(p) + + with tf.GradientTape() as tape2: + res2 = circuit2(p) + + assert np.allclose(res1, res2, atol=tol, rtol=0) + + grad1 = tape1.gradient(res1, p) + grad2 = tape2.gradient(res2, p) - assert np.allclose(circuit1(p), circuit2(p), atol=tol, rtol=0) - assert np.allclose(circuit1.jacobian([p]), circuit2.jacobian([p]), atol=tol, rtol=0) + assert np.allclose(grad1, grad2, atol=tol, rtol=0) @pytest.mark.parametrize("rep", ("exact", "mps")) @@ -555,7 +563,7 @@ def circuit(self, interface, torch_support, rep): dev = qml.device("default.tensor.tf", wires=2, representation=rep) - @qnode(dev, diff_method="device", interface=interface) + @qml.qnode(dev, interface=interface) def circuit_fn(a, b): qml.RX(a, wires=0) qml.CRX(b, wires=[0, 1]) @@ -575,7 +583,6 @@ def test_defines_correct_capabilities(self, rep): "returns_state": False, "supports_analytic_computation": True, "passthru_interface": 'tf', - "provides_jacobian": True, } assert cap == capabilities @@ -660,7 +667,7 @@ def cost(self, diff_method, interface, torch_support, rep): if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") - @qnode(dev, diff_method=diff_method, interface=interface) + @qml.qnode(dev, diff_method=diff_method, interface=interface) def circuit(x, weights, w=None): """In this example, a mixture of scalar arguments, array arguments, and keyword arguments are used.""" @@ -677,7 +684,7 @@ def cost_fn(params): return cost_fn @pytest.mark.parametrize("interface", ["autograd"]) - @pytest.mark.parametrize("diff_method", ["device"]) + @pytest.mark.parametrize("diff_method", ["parameter-shift"]) def test_autograd_interface(self, cost, interface, diff_method, tol): """Tests that the gradient of an arbitrary U3 gate is correct using the autograd interface""" @@ -689,7 +696,7 @@ def test_autograd_interface(self, cost, interface, diff_method, tol): assert np.allclose(res, self.expected_grad, atol=tol, rtol=0) @pytest.mark.parametrize("interface", ["torch"]) - @pytest.mark.parametrize("diff_method", ["device"]) + @pytest.mark.parametrize("diff_method", ["parameter-shift"]) def test_torch_interface(self, cost, interface, diff_method, tol): """Tests that the gradient of an arbitrary U3 gate is correct using the Torch interface""" @@ -705,7 +712,7 @@ def test_torch_interface(self, cost, interface, diff_method, tol): assert np.allclose(res.detach().numpy(), self.expected_grad, atol=tol, rtol=0) @pytest.mark.parametrize("interface", ["tf"]) - @pytest.mark.parametrize("diff_method", ["backprop", "device"]) + @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift"]) def test_tf_interface(self, cost, interface, diff_method, tol): """Tests that the gradient of an arbitrary U3 gate is correct using the TensorFlow interface with the allowed differentiation methods""" @@ -743,14 +750,14 @@ def cost_raising_error(params): if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") - @qnode(dev, diff_method=diff_method, interface=interface) + @qml.qnode(dev, diff_method=diff_method, interface=interface) def circuit(x, w=None): qml.RZ(x, wires=w) return qml.expval(qml.PauliX(w)) return circuit(params[0], w=0) - with pytest.raises(ValueError, match="Device default.tensor.tf only supports diff_method='backprop' when using the tf interface"): + with pytest.raises(qml.QuantumFunctionError, match="Device default.tensor.tf only supports diff_method='backprop' when using the tf interface"): res = cost_raising_error(params) def test_error_backprop_diff_autograd(self, tol, rep): @@ -765,12 +772,12 @@ def cost_raising_error(params): # Cost within the test case such that the error can be caught dev = qml.device("default.tensor.tf", wires=1) - @qnode(dev, diff_method=diff_method, interface=interface) + @qml.qnode(dev, diff_method=diff_method, interface=interface) def circuit(x, w=None): qml.RZ(x, wires=w) return qml.expval(qml.PauliX(w)) return circuit(params[0], w=0) - with pytest.raises(ValueError, match="Device default.tensor.tf only supports diff_method='backprop' when using the tf interface"): + with pytest.raises(qml.QuantumFunctionError, match="Device default.tensor.tf only supports diff_method='backprop' when using the tf interface"): res = cost_raising_error(params) diff --git a/tests/circuit_graph/test_circuit_graph.py b/tests/circuit_graph/test_circuit_graph.py index 76f0a695901..5782f26fd62 100644 --- a/tests/circuit_graph/test_circuit_graph.py +++ b/tests/circuit_graph/test_circuit_graph.py @@ -163,8 +163,8 @@ def test_layers(self, parameterized_circuit, wires): dev = qml.device("default.gaussian", wires=wires) qnode = qml.QNode(parameterized_circuit, dev) - qnode._construct((0.1, 0.2, 0.3, 0.4, 0.5, 0.6), {}) - circuit = qnode.circuit + qnode.construct((0.1, 0.2, 0.3, 0.4, 0.5, 0.6), {}) + circuit = qnode.qtape.graph layers = circuit.parametrized_layers ops = circuit.operations @@ -174,7 +174,7 @@ def test_layers(self, parameterized_circuit, wires): assert layers[1].ops == [ops[3]] assert layers[1].param_inds == [3] assert layers[2].ops == [ops[x] for x in [5, 6]] - assert layers[2].param_inds == [4, 5] + assert layers[2].param_inds == [6, 7] @pytest.mark.parametrize("wires", [['a', 'q1', 3]]) def test_iterate_layers(self, parameterized_circuit, wires): @@ -182,8 +182,8 @@ def test_iterate_layers(self, parameterized_circuit, wires): dev = qml.device("default.gaussian", wires=wires) qnode = qml.QNode(parameterized_circuit, dev) - qnode._construct((0.1, 0.2, 0.3, 0.4, 0.5, 0.6), {}) - circuit = qnode.circuit + qnode.construct((0.1, 0.2, 0.3, 0.4, 0.5, 0.6), {}) + circuit = qnode.qtape.graph result = list(circuit.iterate_parametrized_layers()) assert len(result) == 3 @@ -199,7 +199,7 @@ def test_iterate_layers(self, parameterized_circuit, wires): assert set(result[2][0]) == set(circuit.operations[:4]) assert set(result[2][1]) == set(circuit.operations[5:]) - assert result[2][2] == (4, 5) + assert result[2][2] == (6, 7) assert set(result[2][3]) == set(circuit.observables[1:]) def test_diagonalizing_gates(self): diff --git a/tests/circuit_graph/test_qasm.py b/tests/circuit_graph/test_qasm.py index 16fc4c90ab8..96ae0f36df9 100644 --- a/tests/circuit_graph/test_qasm.py +++ b/tests/circuit_graph/test_qasm.py @@ -234,7 +234,7 @@ def test_unsupported_gate(self): circuit = CircuitGraph(ops, {}, Wires([0, 1])) with pytest.raises( - qml.DeviceError, match="Gate QubitUnitary not supported on device QASM serializer" + ValueError, match="QubitUnitary not supported by the QASM serializer" ): res = circuit.to_openqasm() @@ -292,7 +292,7 @@ def qnode(): # construct the qnode circuit qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ OPENQASM 2.0; @@ -322,7 +322,7 @@ def qnode(): # construct the qnode circuit qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -386,7 +386,7 @@ def qnode(x, y): """ ) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() assert res == expected # execute the QNode with new parameters, and serialize again @@ -412,7 +412,7 @@ def qnode(x, y): """ ) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() assert res == expected def test_native_inverse_gates(self): @@ -430,7 +430,7 @@ def qnode(): # construct the qnode circuit qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -460,7 +460,7 @@ def qnode(): # construct the qnode circuit qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm(wires=dev.wires) expected = dedent( """\ @@ -492,7 +492,7 @@ def qnode(): # construct the qnode circuit qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm(wires=dev.wires) expected = dedent( """\ @@ -522,7 +522,7 @@ def qnode(state=None): # construct the qnode circuit qnode(state=np.array([1, -1, -1, 1]) / np.sqrt(4)) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -556,7 +556,7 @@ def qnode(state=None): # construct the qnode circuit qnode(state=np.array([1, 0, 1, 1])) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -591,9 +591,9 @@ def qnode(): qnode() with pytest.raises( - qml.DeviceError, match="Gate QubitUnitary not supported on device QASM serializer" + ValueError, match="QubitUnitary not supported by the QASM serializer" ): - qnode.circuit.to_openqasm() + qnode.qtape.graph.to_openqasm() def test_rotations(self): """Test that observable rotations are correctly applied.""" @@ -610,7 +610,7 @@ def qnode(): ] qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -645,7 +645,7 @@ def qnode(): ] qnode() - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() expected = dedent( """\ @@ -696,7 +696,7 @@ def qnode(x): ] qnode([0.1, 0.2]) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm() # Note: Qiskit hardcodes in pi as a QASM constant. # Here, we replace it with its numerical value. @@ -718,7 +718,7 @@ def qnode(state=None): # construct the qnode circuit qnode(state=np.array([1, 0, 1, 1])) - res = qnode.circuit.to_openqasm() + res = qnode.qtape.graph.to_openqasm(wires=dev.wires) expected = dev._circuit.qasm() assert res == expected @@ -742,7 +742,7 @@ def qnode(x): params = [0.1, 0.2] qnode(params) - qasm = qnode.circuit.to_openqasm() + qasm = qnode.qtape.graph.to_openqasm() qc = self.qiskit.QuantumCircuit.from_qasm_str(qasm) gates = [g for g, _, _ in qc.data] diff --git a/tests/conftest.py b/tests/conftest.py index 9dc1afc78d4..b36df94e564 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,7 @@ import pennylane as qml from pennylane.devices import DefaultGaussian + # defaults TOL = 1e-3 TF_TOL = 2e-2 @@ -155,6 +156,7 @@ def mock_device(monkeypatch): m.setattr(dev, '__abstractmethods__', frozenset()) m.setattr(dev, 'short_name', 'mock_device') m.setattr(dev, 'capabilities', lambda cls: {"model": "qubit"}) + m.setattr(dev, "operations", {"RX", "RY", "RZ", "CNOT", "SWAP"}) yield qml.Device(wires=2) @@ -165,11 +167,16 @@ def tear_down_hermitian(): @pytest.fixture -def in_tape_mode(): +def non_tape_mode_only(): """Run the test in tape mode""" - qml.enable_tape() - yield qml.disable_tape() + yield + qml.enable_tape() + + +@pytest.fixture +def in_tape_mode(): + return @pytest.fixture(params=[False, True]) @@ -202,9 +209,9 @@ def patched_jacobian(self, args, **kwargs): mocker.patch("pennylane.tape.QNode.jacobian", patched_jacobian, create=True) - qml.enable_tape() + else: + qml.disable_tape() yield - if request.param: - qml.disable_tape() + qml.enable_tape() diff --git a/tests/devices/test_caching.py b/tests/devices/test_caching.py index aaa42fe94e3..93c839d99cd 100644 --- a/tests/devices/test_caching.py +++ b/tests/devices/test_caching.py @@ -265,14 +265,20 @@ def test_non_tape_mode(self): """Tests that an exception is raised when attempting to use caching outside of tape mode""" dev = qml.device("default.qubit", wires=3, cache=10) - def qfunc(x, y): - """Simple quantum function""" - qml.RX(x, wires=0) - qml.RX(y, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(wires=1)) + try: + qml.disable_tape() + + def qfunc(x, y): + """Simple quantum function""" + qml.RX(x, wires=0) + qml.RX(y, wires=1) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(wires=1)) + + qn = qml.QNode(qfunc, dev) - qn = qml.QNode(qfunc, dev) + with pytest.raises(ValueError, match="Caching is only available when using tape mode"): + qn(0.1, 0.2) - with pytest.raises(ValueError, match="Caching is only available when using tape mode"): - qn(0.1, 0.2) + finally: + qml.enable_tape() diff --git a/tests/devices/test_default_gaussian.py b/tests/devices/test_default_gaussian.py index dae6a6e8fe7..c571a8720b2 100644 --- a/tests/devices/test_default_gaussian.py +++ b/tests/devices/test_default_gaussian.py @@ -696,7 +696,8 @@ def test_supported_gates(self, g, qop, gaussian_dev): @qml.qnode(gaussian_dev) def circuit(*x): """Reference quantum function""" - qml.Displacement(a, 0, wires=[0]) + if "State" not in g: + qml.Displacement(a, 0, wires=[0]) op(*x, wires=wires) return qml.expval(qml.X(0)) diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py index 3e67ebe160d..e8841213e57 100644 --- a/tests/devices/test_default_qubit_autograd.py +++ b/tests/devices/test_default_qubit_autograd.py @@ -71,7 +71,7 @@ def circuit(x): expected = -np.sin(p) - assert isinstance(circuit, qml.qnodes.PassthruQNode) + assert circuit.diff_options["method"] == "backprop" assert np.isclose(circuit(p), expected, atol=tol, rtol=0) def test_correct_state(self, tol): @@ -119,7 +119,7 @@ def circuit(p): qml.RX(p[2] / 2, wires=0) return qml.expval(qml.PauliZ(0)) - assert isinstance(circuit, qml.qnodes.PassthruQNode) + assert circuit.diff_options["method"] == "backprop" res = circuit(weights) expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2) @@ -185,8 +185,8 @@ def circuit(x): circuit1 = qml.QNode(circuit, dev1, diff_method="backprop", interface="autograd") circuit2 = qml.QNode(circuit, dev2, diff_method="parameter-shift") - assert isinstance(circuit1, qml.qnodes.PassthruQNode) - assert isinstance(circuit2, qml.qnodes.QubitQNode) + assert circuit1.diff_options["method"] == "backprop" + assert circuit2.diff_options["method"] == "analytic" res = circuit1(p) @@ -194,7 +194,7 @@ def circuit(x): grad_fn = qml.jacobian(circuit1, 0) res = grad_fn(p) - assert np.allclose(res, circuit2.jacobian([p]), atol=tol, rtol=0) + assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0) def test_state_differentiability(self, tol): """Test that the device state can be differentiated""" @@ -233,7 +233,7 @@ def circuit(a, b): b = np.array(0.12, requires_grad=True) def cost(a, b): - prob_wire_1 = circuit(a, b)[0] + prob_wire_1 = circuit(a, b) return prob_wire_1[1] - prob_wire_1[0] res = cost(a, b) @@ -273,23 +273,16 @@ def test_autograd_interface_gradient(self, operation, diff_method, tol): """Tests that the gradient of an arbitrary U3 gate is correct using the Autograd interface, using a variety of differentiation methods.""" dev = qml.device("default.qubit.autograd", wires=1) + state = np.array(1j * np.array([1, -1]) / np.sqrt(2), requires_grad=False) @qml.qnode(dev, diff_method=diff_method, interface="autograd") - def circuit(x, weights, w=None): + def circuit(x, weights, w): """In this example, a mixture of scalar arguments, array arguments, and keyword arguments are used.""" - qml.QubitStateVector(1j * np.array([1, -1]) / np.sqrt(2), wires=w) + qml.QubitStateVector(state, wires=w) operation(x, weights[0], weights[1], wires=w) return qml.expval(qml.PauliX(w)) - # Check that the correct QNode type is being used. - if diff_method == "backprop": - assert isinstance(circuit, qml.qnodes.PassthruQNode) - assert not hasattr(circuit, "jacobian") - else: - assert not isinstance(circuit, qml.qnodes.PassthruQNode) - assert hasattr(circuit, "jacobian") - def cost(params): """Perform some classical processing""" return circuit(params[0], params[1:], w=0) ** 2 @@ -304,6 +297,14 @@ def cost(params): expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2 assert np.allclose(res, expected_cost, atol=tol, rtol=0) + # Check that the correct differentiation method is being used. + if diff_method == "backprop": + assert circuit.diff_options["method"] == "backprop" + elif diff_method == "parameter-shift": + assert circuit.diff_options["method"] == "analytic" + else: + assert circuit.diff_options["method"] == "numeric" + res = qml.grad(cost)(params) expected_grad = ( np.array( @@ -329,7 +330,7 @@ def circuit(x, w=None): return qml.expval(qml.PauliX(w)) with pytest.raises( - ValueError, + qml.QuantumFunctionError, match="default.qubit.autograd only supports diff_method='backprop' when using the autograd interface", ): qml.qnode(dev, diff_method="backprop", interface=interface)(circuit) diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index 070a2b0996d..3cb86794485 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -907,7 +907,7 @@ def circuit(x): expected = -tf.math.sin(p) - assert isinstance(circuit, qml.qnodes.PassthruQNode) + assert circuit.diff_options["method"] == "backprop" assert np.isclose(circuit(p), expected, atol=tol, rtol=0) def test_correct_state(self, tol): @@ -1099,7 +1099,7 @@ def circuit(x): assert np.allclose(res, circuit2(p), atol=tol, rtol=0) res = tape.jacobian(res, p_tf) - assert np.allclose(res, circuit2.jacobian([p]), atol=tol, rtol=0) + assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0) def test_state_differentiability(self, tol): """Test that the device state can be differentiated""" @@ -1137,7 +1137,7 @@ def circuit(a, b): with tf.GradientTape() as tape: # get the probability of wire 1 - prob_wire_1 = circuit(a, b)[0] + prob_wire_1 = circuit(a, b) # compute Prob(|1>_1) - Prob(|0>_1) res = prob_wire_1[1] - prob_wire_1[0] @@ -1189,7 +1189,7 @@ def test_tf_interface_gradient(self, operation, diff_method, tol): dev = qml.device("default.qubit.tf", wires=1) @qml.qnode(dev, diff_method=diff_method, interface="tf") - def circuit(x, weights, w=None): + def circuit(x, weights, w): """In this example, a mixture of scalar arguments, array arguments, and keyword arguments are used.""" qml.QubitStateVector(1j * np.array([1, -1]) / np.sqrt(2), wires=w) @@ -1198,11 +1198,11 @@ def circuit(x, weights, w=None): # Check that the correct QNode type is being used. if diff_method == "backprop": - assert isinstance(circuit, qml.qnodes.PassthruQNode) - assert not hasattr(circuit, "jacobian") - else: - assert not isinstance(circuit, qml.qnodes.PassthruQNode) - assert hasattr(circuit, "jacobian") + assert circuit.diff_options["method"] == "backprop" + elif diff_method == "parameter-shift": + assert circuit.diff_options["method"] == "analytic" + elif diff_method == "finite-diff": + assert circuit.diff_options["method"] == "numeric" def cost(params): """Perform some classical processing""" @@ -1249,7 +1249,7 @@ def circuit(x, w=None): return qml.expval(qml.PauliX(w)) with pytest.raises( - ValueError, + qml.QuantumFunctionError, match="default.qubit.tf only supports diff_method='backprop' when using the tf interface", ): qml.qnode(dev, diff_method="backprop", interface=interface)(circuit) @@ -1272,8 +1272,8 @@ def circuit(a): res = circuit(a) assert isinstance(res, tf.Tensor) - assert res.shape == (1, shots) - assert set(res[0].numpy()) == {-1, 1} + assert res.shape == (shots,) + assert set(res.numpy()) == {-1, 1} def test_sample_observables_non_differentiable(self): """Test that sampled observables cannot be differentiated.""" diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 5e90ac90fc9..25111b31390 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -35,6 +35,9 @@ sqz_vals = np.linspace(0., 1., 5) +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") + + class TestAutogradDetails: """Test configuration details of the autograd interface""" diff --git a/tests/interfaces/test_tf.py b/tests/interfaces/test_tf.py index 0c8fc3cff80..7c365e863a5 100644 --- a/tests/interfaces/test_tf.py +++ b/tests/interfaces/test_tf.py @@ -32,6 +32,7 @@ from gate_data import CNOT, Rotx, Roty, Rotz, I, Y, Z +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") def expZ(state): return np.abs(state[0]) ** 2 - np.abs(state[1]) ** 2 diff --git a/tests/interfaces/test_torch.py b/tests/interfaces/test_torch.py index 38edf6abfa9..788e6cfc323 100644 --- a/tests/interfaces/test_torch.py +++ b/tests/interfaces/test_torch.py @@ -31,6 +31,7 @@ from gate_data import CNOT, Rotx, Roty, Rotz, I, Y, Z +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") def expZ(state): return np.abs(state[0]) ** 2 - np.abs(state[1]) ** 2 diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index 6b47d294ad2..49cd9f3c47f 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -977,7 +977,7 @@ def test_PauliRot_decomposition_XIYZ(self): assert decomp_ops[4].data[0] == -np.pi / 2 @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) - def test_differentiability(self, angle): + def test_differentiability(self, angle, tol): """Test that differentiation of PauliRot works.""" dev = qml.device("default.qubit", wires=2) @@ -989,9 +989,9 @@ def circuit(theta): return qml.expval(qml.PauliZ(0)) res = circuit(angle) - gradient = np.squeeze(circuit.jacobian(angle)) + gradient = np.squeeze(qml.grad(circuit)(angle)) - assert gradient == 0.5 * (circuit(angle + np.pi / 2) - circuit(angle - np.pi / 2)) + assert gradient == pytest.approx(0.5 * (circuit(angle + np.pi / 2) - circuit(angle - np.pi / 2)), abs=tol) @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) def test_decomposition_integration(self, angle, tol): @@ -1012,8 +1012,8 @@ def decomp_circuit(theta): return qml.expval(qml.PauliZ(0)) assert circuit(angle) == pytest.approx(decomp_circuit(angle), abs=tol) - assert np.squeeze(circuit.jacobian(angle)) == pytest.approx( - np.squeeze(decomp_circuit.jacobian(angle)), abs=tol + assert np.squeeze(qml.grad(circuit)(angle)) == pytest.approx( + np.squeeze(qml.grad(decomp_circuit)(angle)), abs=tol ) def test_matrix_incorrect_pauli_word_error(self): @@ -1152,7 +1152,7 @@ def test_MultiRZ_decomposition_ZZZ(self): assert decomp_ops[4].wires == Wires([3, 2]) @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) - def test_differentiability(self, angle): + def test_differentiability(self, angle, tol): """Test that differentiation of MultiRZ works.""" dev = qml.device("default.qubit", wires=2) @@ -1165,9 +1165,9 @@ def circuit(theta): return qml.expval(qml.PauliX(0)) res = circuit(angle) - gradient = np.squeeze(circuit.jacobian(angle)) + gradient = np.squeeze(qml.grad(circuit)(angle)) - assert gradient == 0.5 * (circuit(angle + np.pi / 2) - circuit(angle - np.pi / 2)) + assert gradient == pytest.approx(0.5 * (circuit(angle + np.pi / 2) - circuit(angle - np.pi / 2)), abs=tol) @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) def test_decomposition_integration(self, angle, tol): @@ -1190,8 +1190,8 @@ def decomp_circuit(theta): return qml.expval(qml.PauliX(0)) assert circuit(angle) == pytest.approx(decomp_circuit(angle), abs=tol) - assert np.squeeze(circuit.jacobian(angle)) == pytest.approx( - np.squeeze(decomp_circuit.jacobian(angle)), abs=tol + assert np.squeeze(qml.jacobian(circuit)(angle)) == pytest.approx( + np.squeeze(qml.jacobian(decomp_circuit)(angle)), abs=tol ) @pytest.mark.parametrize("qubits", range(3, 6)) diff --git a/tests/qnn/test_cost.py b/tests/qnn/test_cost.py index 62d5fa451d7..57113403f5d 100644 --- a/tests/qnn/test_cost.py +++ b/tests/qnn/test_cost.py @@ -21,6 +21,9 @@ from pennylane.qnn.cost import SquaredErrorLoss +ALLOWED_INTERFACES = ["tf", "jax", "autograd", "torch"] + + def rx_ansatz(phis, **kwargs): for w, phi in enumerate(phis): qml.RX(phi, wires=w) @@ -38,7 +41,7 @@ def skip_if_no_torch_support(): pytest.importorskip("torch", minversion="1.4") -@pytest.mark.parametrize("interface", qml.qnodes.decorator.ALLOWED_INTERFACES) +@pytest.mark.parametrize("interface", ALLOWED_INTERFACES) @pytest.mark.usefixtures("skip_if_no_torch_support", "skip_if_no_tf_support") class TestSquaredErrorLoss: def test_no_target(self, interface): diff --git a/tests/qnodes/test_qnode_base.py b/tests/qnodes/test_qnode_base.py index e06eed308d3..d02c5e04cf0 100644 --- a/tests/qnodes/test_qnode_base.py +++ b/tests/qnodes/test_qnode_base.py @@ -26,7 +26,7 @@ from pennylane.qnodes.base import BaseQNode, QuantumFunctionError, decompose_queue from pennylane.variable import Variable from pennylane.wires import Wires, WireError - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") @pytest.fixture(scope="function") def mock_qnode(mock_device): @@ -1516,5 +1516,5 @@ def f(x): # check that tape mode is turned on again after evaluating the old QNode assert qml.tape_mode_active() - finally: # always make sure we turn off tape mode to prevent disrupting the other tests - qml.disable_tape() + finally: # always make sure we turn on tape mode to prevent disrupting the other tests + qml.enable_tape() diff --git a/tests/qnodes/test_qnode_cv.py b/tests/qnodes/test_qnode_cv.py index 3fc8d265af4..e8eeca3a005 100644 --- a/tests/qnodes/test_qnode_cv.py +++ b/tests/qnodes/test_qnode_cv.py @@ -22,7 +22,7 @@ from pennylane.operation import CVObservable from pennylane.qnodes.base import QuantumFunctionError from pennylane.qnodes.cv import CVQNode - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") class PolyN(qml.ops.PolyXP): """Mimics NumberOperator using the arbitrary 2nd order observable interface. diff --git a/tests/qnodes/test_qnode_decorator.py b/tests/qnodes/test_qnode_decorator.py index cfe3a5cd5a5..2d07bcee58f 100644 --- a/tests/qnodes/test_qnode_decorator.py +++ b/tests/qnodes/test_qnode_decorator.py @@ -21,7 +21,7 @@ import pennylane as qml from pennylane.qnodes import qnode, CVQNode, JacobianQNode, BaseQNode, QubitQNode, ReversibleQNode from pennylane.qnodes.jacobian import DEFAULT_STEP_SIZE_ANALYTIC, DEFAULT_STEP_SIZE - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") def test_create_qubit_qnode(): """Test the decorator correctly creates Qubit QNodes""" diff --git a/tests/qnodes/test_qnode_jacobian.py b/tests/qnodes/test_qnode_jacobian.py index 4aee7f57b7d..40dcef676e0 100644 --- a/tests/qnodes/test_qnode_jacobian.py +++ b/tests/qnodes/test_qnode_jacobian.py @@ -24,7 +24,7 @@ from pennylane.operation import CVObservable from pennylane.qnodes.base import QuantumFunctionError from pennylane.qnodes.jacobian import JacobianQNode - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") @pytest.fixture(scope="function") def operable_mock_device_2_wires(monkeypatch): diff --git a/tests/qnodes/test_qnode_metric_tensor.py b/tests/qnodes/test_qnode_metric_tensor.py index aa0a80eaf95..59c75d87d4e 100644 --- a/tests/qnodes/test_qnode_metric_tensor.py +++ b/tests/qnodes/test_qnode_metric_tensor.py @@ -22,7 +22,7 @@ from pennylane.qnodes.qubit import QubitQNode from pennylane.qnodes.base import QuantumFunctionError from gate_data import Y, Z - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") class TestMetricTensor: """Tests for metric tensor subcircuit construction and evaluation""" diff --git a/tests/qnodes/test_qnode_passthru.py b/tests/qnodes/test_qnode_passthru.py index b1a2e8f461e..f8676a87603 100644 --- a/tests/qnodes/test_qnode_passthru.py +++ b/tests/qnodes/test_qnode_passthru.py @@ -29,7 +29,7 @@ import pennylane as qml from pennylane.qnodes import PassthruQNode, BaseQNode, JacobianQNode - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") @pytest.fixture(scope="function") diff --git a/tests/qnodes/test_qnode_qubit.py b/tests/qnodes/test_qnode_qubit.py index 3f9d87edaae..637ccdf6330 100644 --- a/tests/qnodes/test_qnode_qubit.py +++ b/tests/qnodes/test_qnode_qubit.py @@ -22,7 +22,7 @@ from pennylane.operation import CVObservable from pennylane.qnodes.base import QuantumFunctionError from pennylane.qnodes.qubit import QubitQNode - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") thetas = np.linspace(-2*np.pi, 2*np.pi, 8) diff --git a/tests/qnodes/test_qnode_reversible.py b/tests/qnodes/test_qnode_reversible.py index d7528511e51..c0dcb1dc7cb 100644 --- a/tests/qnodes/test_qnode_reversible.py +++ b/tests/qnodes/test_qnode_reversible.py @@ -19,7 +19,7 @@ import pennylane as qml from pennylane.qnodes.rev import ReversibleQNode - +pytestmark = pytest.mark.usefixtures("non_tape_mode_only") thetas = np.linspace(-2 * np.pi, 2 * np.pi, 8) diff --git a/tests/tape/interfaces/test_qnode_torch.py b/tests/tape/interfaces/test_qnode_torch.py index d6b4d894e15..ec2bd33953d 100644 --- a/tests/tape/interfaces/test_qnode_torch.py +++ b/tests/tape/interfaces/test_qnode_torch.py @@ -509,6 +509,24 @@ def circuit(): assert res.shape == (2, 10) assert isinstance(res, torch.Tensor) + def test_sampling_expval(self, dev_name, diff_method): + """Test sampling works as expected if combined with expectation values""" + dev = qml.device(dev_name, wires=2, shots=10) + + @qnode(dev, diff_method=diff_method, interface="torch") + def circuit(): + qml.Hadamard(wires=[0]) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)), qml.expval(qml.PauliX(1)) + + res = circuit() + + assert len(res) == 2 + assert isinstance(res, tuple) + assert res[0].shape == (10,) + assert isinstance(res[0], torch.Tensor) + assert isinstance(res[1], torch.Tensor) + def qtransform(qnode, a, framework=torch): """Transforms every RY(y) gate in a circuit to RX(-a*cos(y))""" diff --git a/tests/tape/tapes/test_qnode.py b/tests/tape/tapes/test_qnode.py index 74b318b909b..185426595c3 100644 --- a/tests/tape/tapes/test_qnode.py +++ b/tests/tape/tapes/test_qnode.py @@ -440,6 +440,8 @@ def circuit(p1, p2, **kwargs): with pytest.raises(ValueError, match="only works when tape mode is enabled"): result = draw(circuit, charset="ascii") + qml.enable_tape() + def test_drawing(self): """Test circuit drawing""" from pennylane import numpy as anp @@ -722,13 +724,7 @@ def func(x, y): class TestQNodeCollection: """Unittests for the QNodeCollection""" - @pytest.fixture - def enable_tape_mode(self): - qml.enable_tape() - yield - qml.disable_tape() - - def test_multi_thread(self, enable_tape_mode): + def test_multi_thread(self): """Test that multi-threaded queuing in tape mode works correctly""" n_qubits = 4 n_batches = 5 diff --git a/tests/tape/tapes/test_tape.py b/tests/tape/tapes/test_tape.py index 3f4a70efa1b..94cf27987a4 100644 --- a/tests/tape/tapes/test_tape.py +++ b/tests/tape/tapes/test_tape.py @@ -1090,7 +1090,7 @@ def test_samples_expval(self): res = tape.execute(dev) assert res[0].shape == (10,) - assert isinstance(res[1], float) + assert isinstance(res[1], np.ndarray) def test_decomposition(self, tol): """Test decomposition onto a device's supported gate set""" diff --git a/tests/tape/test_tape_mode.py b/tests/tape/test_tape_mode.py index b1b0bacce48..3c4c29a3114 100644 --- a/tests/tape/test_tape_mode.py +++ b/tests/tape/test_tape_mode.py @@ -22,6 +22,8 @@ def test_enable_tape_mode_decorator(): enables tape mode when creating QNodes using the decorator.""" dev = qml.device("default.qubit", wires=1) + qml.disable_tape() + @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) @@ -48,14 +50,14 @@ def circuit(x, y): assert not isinstance(circuit, qml.qnodes.BaseQNode) assert hasattr(circuit, "qtape") - qml.disable_tape() - def test_enable_tape_mode_class(): """Test that the enable_tape function properly enables tape mode when creating QNodes using the class.""" dev = qml.device("default.qubit", wires=1) + qml.disable_tape() + def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @@ -80,7 +82,6 @@ def circuit(x, y): assert not isinstance(qnode, qml.qnodes.BaseQNode) assert hasattr(qnode, "qtape") - qml.disable_tape() def test_disable_tape(): @@ -114,19 +115,22 @@ def circuit(x, y): assert isinstance(qnode, qml.qnodes.BaseQNode) assert not hasattr(qnode, "qtape") + qml.enable_tape() + def test_disable_tape_exception(): """Test that disabling tape mode raises a warning if not currently in tape mode""" + qml.disable_tape() with pytest.warns(UserWarning, match="Tape mode is not currently enabled"): qml.disable_tape() + qml.enable_tape() def test_tape_mode_detection(): """Test that the function `tape_mode_active` returns True only if tape mode is activated.""" + qml.disable_tape() assert not qml.tape_mode_active() qml.enable_tape() assert qml.tape_mode_active() - qml.disable_tape() - assert not qml.tape_mode_active() diff --git a/tests/templates/test_layers.py b/tests/templates/test_layers.py index 3e3a6e3b6a4..129375a3e1c 100644 --- a/tests/templates/test_layers.py +++ b/tests/templates/test_layers.py @@ -750,44 +750,44 @@ def test_u2_operations(self, layers, qubits, init_state): with pennylane._queuing.OperationRecorder() as rec: ParticleConservingU2(weights, wires=range(qubits), init_state=init_state) - # number of gates - assert len(rec.queue) == n_gates + # number of gates + assert len(rec.queue) == n_gates - # initialization - assert isinstance(rec.queue[0], qml.BasisState) + # initialization + assert isinstance(rec.queue[0], qml.BasisState) - # order of gates - for op1, op2 in zip(rec.queue[1:], exp_gates): - assert isinstance(op1, op2) + # order of gates + for op1, op2 in zip(rec.queue[1:], exp_gates): + assert isinstance(op1, op2) - # gate parameter - params = np.array( - [ - rec.queue[i].parameters - for i in range(1, n_gates) - if rec.queue[i].parameters != [] - ] - ) - weights[:, qubits:] = weights[:, qubits:] * 2 - assert np.allclose(params.flatten(), weights.flatten()) - - # gate wires - wires = Wires(range(qubits)) - nm_wires = [wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2)] - nm_wires += [wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2)] - - exp_wires = [] - for _ in range(layers): - for i in range(qubits): - exp_wires.append(wires.subset([i])) - for j in nm_wires: - exp_wires.append(j) - exp_wires.append(j[::-1]) - exp_wires.append(j) - - res_wires = [rec.queue[i]._wires for i in range(1, n_gates)] - - assert res_wires == exp_wires + # gate parameter + params = np.array( + [ + rec.queue[i].parameters + for i in range(1, n_gates) + if rec.queue[i].parameters != [] + ] + ) + weights[:, qubits:] = weights[:, qubits:] * 2 + assert np.allclose(params.flatten(), weights.flatten()) + + # gate wires + wires = Wires(range(qubits)) + nm_wires = [wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2)] + nm_wires += [wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2)] + + exp_wires = [] + for _ in range(layers): + for i in range(qubits): + exp_wires.append(wires.subset([i])) + for j in nm_wires: + exp_wires.append(j) + exp_wires.append(j[::-1]) + exp_wires.append(j) + + res_wires = [rec.queue[i]._wires for i in range(1, n_gates)] + + assert res_wires == exp_wires @pytest.mark.parametrize( ("weights", "wires", "msg_match"), diff --git a/tests/test_about.py b/tests/test_about.py index 1187744325b..a1314362945 100644 --- a/tests/test_about.py +++ b/tests/test_about.py @@ -45,6 +45,7 @@ def test_about(): assert "default.gaussian" in out +@pytest.mark.skip(reason="This test causes tape mode to be reset") def test_qchem_not_installed_error(monkeypatch): """Test QChem causes an import error on access if not installed""" diff --git a/tests/test_device.py b/tests/test_device.py index f57041e94b3..3c91c916b63 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -511,6 +511,7 @@ def test_op_queue_is_filled_at_pre_measure(self, mock_device_with_paulis_and_met qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] queue_at_pre_measure = [] @@ -536,6 +537,8 @@ def test_op_queue_is_filled_during_execution(self, mock_device_with_paulis_and_m qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] + call_history = [] with monkeypatch.context() as m: m.setattr(Device, 'apply', lambda self, op, wires, params: call_history.append([op, wires, params])) @@ -561,6 +564,8 @@ def test_unsupported_operations_raise_error(self, mock_device_with_paulis_and_me qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] + with pytest.raises(DeviceError, match="Gate Hadamard not supported on device"): dev.execute(queue, observables) @@ -596,6 +601,7 @@ def test_obs_queue_is_filled_at_pre_measure(self, mock_device_with_paulis_and_me qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] queue_at_pre_measure = [] @@ -609,11 +615,10 @@ def test_obs_queue_is_filled_during_execution(self, monkeypatch, mock_device_wit """Tests that the operations are properly applied and queued""" dev = mock_device_with_paulis_and_methods(wires=3) - observables = [ - qml.expval(qml.PauliX(0)), - qml.var(qml.PauliY(1)), - qml.sample(qml.PauliZ(2)), - ] + observables = [] + for m in [qml.expval(qml.PauliX(0)), qml.var(qml.PauliY(1)), qml.sample(qml.PauliZ(2))]: + m.obs.return_type = m.return_type + observables.append(m.obs) # capture the arguments passed to dev methods expval_args = [] @@ -644,6 +649,7 @@ def test_unsupported_observables_raise_error(self, mock_device_with_paulis_and_m qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] with pytest.raises(DeviceError, match="Observable Hadamard not supported on device"): dev.execute(queue, observables) @@ -697,6 +703,7 @@ def test_parameters_available_at_pre_measure(self, mock_device, monkeypatch): qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)), ] + observables = [o.obs for o in observables] p_mapping = {} @@ -724,6 +731,7 @@ def test_outdated_API(self, monkeypatch): with pytest.raises(DeviceError, match="plugin requires PennyLane versions"): qml.device("default.qubit", wires=0) + @pytest.mark.skip(reason="Reloading PennyLane messes with tape mode") def test_refresh_entrypoints(self, monkeypatch): """Test that new entrypoints are found by the refresh_devices function""" assert qml.plugin_devices @@ -747,6 +755,7 @@ def test_refresh_entrypoints(self, monkeypatch): # restore the plugin_device dictionary importlib.reload(qml) + @pytest.mark.skip(reason="Reloading PennyLane messes with tape mode") def test_hot_refresh_entrypoints(self, monkeypatch): """Test that new entrypoints are found by the device loader if not currently present""" assert qml.plugin_devices diff --git a/tests/test_measure.py b/tests/test_measure.py index ac722c65fa7..4b2945cd2d2 100644 --- a/tests/test_measure.py +++ b/tests/test_measure.py @@ -30,7 +30,7 @@ def circuit(x): qml.RX(x, wires=0) return qml.PauliY(0) - with pytest.raises(QuantumFunctionError, match="does not have the measurement"): + with pytest.raises(QuantumFunctionError, match="must return either a single measurement"): res = circuit(0.65) @@ -146,7 +146,7 @@ def test_sample_combination(self, tol): dev = qml.device("default.qubit", wires=3, shots=n_sample) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(): qml.RX(0.54, wires=0) @@ -154,10 +154,10 @@ def circuit(): result = circuit() - assert np.array_equal(result.shape, (3,)) + assert len(result) == 3 assert np.array_equal(result[0].shape, (n_sample,)) - assert isinstance(result[1], float) - assert isinstance(result[2], float) + assert isinstance(result[1], np.ndarray) + assert isinstance(result[2], np.ndarray) def test_single_wire_sample(self, tol): """Test the return type and shape of sampling a single wire""" @@ -201,18 +201,16 @@ def test_sample_output_type_in_combination(self, tol): dev = qml.device("default.qubit", wires=3, shots=n_sample) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(): return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)) result = circuit() # If all the dimensions are equal the result will end up to be a proper rectangular array - assert isinstance(result, np.ndarray) - assert result.dtype == np.dtype("object") - assert np.array_equal(result.shape, (3,)) - assert isinstance(result[0], float) - assert isinstance(result[1], float) + assert len(result) == 3 + assert isinstance(result[0], np.ndarray) + assert isinstance(result[1], np.ndarray) assert result[2].dtype == np.dtype("int") assert np.array_equal(result[2].shape, (n_sample,)) diff --git a/tests/test_numpy_wrapper.py b/tests/test_numpy_wrapper.py index c877c665fa5..f89b1025d95 100644 --- a/tests/test_numpy_wrapper.py +++ b/tests/test_numpy_wrapper.py @@ -503,7 +503,7 @@ def test_single_gate_parameter(self, monkeypatch): unwrapped tensor as the argument to a gate taking a single parameter""" dev = qml.device("default.qubit", wires=4) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(phi=None): for y in phi: for idx, x in enumerate(y): @@ -520,17 +520,12 @@ def circuit(phi=None): assert rec.queue[0].name == "RX" assert len(rec.queue[0].parameters) == 1 - # Test that the gate parameter is not a PennyLane tensor, but a - # float - assert not isinstance(rec.queue[0].parameters[0], np.tensor) - assert isinstance(rec.queue[0].parameters[0], float) - def test_multiple_gate_parameter(self): """Test that when supplied a PennyLane tensor, a QNode passes arguments as unwrapped tensors to a gate taking multiple parameters""" dev = qml.device("default.qubit", wires=1) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(phi=None): for idx, x in enumerate(phi): qml.Rot(*x, wires=idx) @@ -545,14 +540,3 @@ def circuit(phi=None): # Test the rotation applied assert rec.queue[0].name == "Rot" assert len(rec.queue[0].parameters) == 3 - - # Test that the gate parameters are not PennyLane tensors, but a - # floats - assert not isinstance(rec.queue[0].parameters[0], np.tensor) - assert isinstance(rec.queue[0].parameters[0], float) - - assert not isinstance(rec.queue[0].parameters[1], np.tensor) - assert isinstance(rec.queue[0].parameters[1], float) - - assert not isinstance(rec.queue[0].parameters[2], np.tensor) - assert isinstance(rec.queue[0].parameters[2], float) diff --git a/tests/test_observable.py b/tests/test_observable.py index 66197c315b3..be379ea06bd 100644 --- a/tests/test_observable.py +++ b/tests/test_observable.py @@ -30,4 +30,4 @@ def circuit(): return qml.expval(obs) circuit() - assert obs in circuit.ops + assert obs in circuit.qtape.observables diff --git a/tests/test_operation.py b/tests/test_operation.py index 89cdde633af..66d7cbf1ff6 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -199,30 +199,6 @@ def test_operation_init(self, test_class, monkeypatch): with pytest.raises(ValueError, match="Unknown parameter domain"): test_class(*pars, wires=ww) - @pytest.fixture(scope="function") - def qnode(self, mock_device): - """Provides a QNode for the subsequent tests of do_queue""" - - def circuit(x): - qml.RX(x, wires=[0]) - qml.CNOT(wires=[0, 1], do_queue=False) - qml.RY(0.4, wires=[0]) - qml.RZ(-0.2, wires=[1], do_queue=False) - return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(1)) - - node = qml.QNode(circuit, mock_device) - node._construct([1.0], {}) - - return node - - def test_operation_inside_context_do_queue_false(self, qnode): - """Test that an operation does not get added to the QNode queue when do_queue=False""" - assert len(qnode.ops) == 4 - assert qnode.ops[0].name == "RX" - assert qnode.ops[1].name == "RY" - assert qnode.ops[2].name == "PauliX" - assert qnode.ops[3].name == "PauliZ" - @pytest.fixture(scope="function") def qnode_for_inverse(self, mock_device): """Provides a QNode for the subsequent tests of inv""" @@ -233,19 +209,19 @@ def circuit(x): return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(1)) node = qml.QNode(circuit, mock_device) - node._construct([1.0], {}) + node.construct([1.0], {}) return node def test_operation_inverse_defined(self, qnode_for_inverse): """Test that the inverse of an operation is added to the QNode queue and the operation is an instance of the original class""" - assert qnode_for_inverse.ops[0].name == "RZ.inv" - assert qnode_for_inverse.ops[0].inverse - assert issubclass(qnode_for_inverse.ops[0].__class__, qml.operation.Operation) - assert qnode_for_inverse.ops[1].name == "RZ" - assert not qnode_for_inverse.ops[1].inverse - assert issubclass(qnode_for_inverse.ops[1].__class__, qml.operation.Operation) + assert qnode_for_inverse.qtape.operations[0].name == "RZ.inv" + assert qnode_for_inverse.qtape.operations[0].inverse + assert issubclass(qnode_for_inverse.qtape.operations[0].__class__, qml.operation.Operation) + assert qnode_for_inverse.qtape.operations[1].name == "RZ" + assert not qnode_for_inverse.qtape.operations[1].inverse + assert issubclass(qnode_for_inverse.qtape.operations[1].__class__, qml.operation.Operation) def test_operation_inverse_using_dummy_operation(self): @@ -605,7 +581,7 @@ def test_all_wires_defined_but_init_with_one(self): dev1 = qml.device("default.qubit", wires=2) - class DummyOp(qml.operation.Operator): + class DummyOp(qml.operation.Operation): r"""Dummy custom operator""" num_wires = qml.operation.WiresEnum.AllWires num_params = 0 @@ -653,7 +629,7 @@ def mean_photon_gaussian(mag_alpha, phase_alpha, phi): with pytest.raises( qml.DeviceError, - match="Gate Rotation.inv not supported on device {}".format(dev1.short_name), + match=r"inverse of gates are not supported on device default\.gaussian", ): mean_photon_gaussian(0.015, 0.02, 0.005) @@ -1005,16 +981,14 @@ def test_non_identity_obs(self, tensor_observable, expected): ] @pytest.mark.parametrize("tensor_observable, expected", tensor_obs_pruning) - @pytest.mark.parametrize("statistics", [qml.expval, qml.var, qml.sample]) - def test_prune(self, tensor_observable, expected, statistics): + def test_prune(self, tensor_observable, expected): """Tests that the prune method returns the expected Tensor or single non-Tensor Observable.""" - O = statistics(tensor_observable) - O_expected = statistics(expected) + O = tensor_observable + O_expected = expected O_pruned = O.prune() assert type(O_pruned) == type(expected) assert O_pruned.wires == expected.wires - assert O_pruned.return_type == O_expected.return_type equal_obs = [ diff --git a/tests/test_optimize.py b/tests/test_optimize.py index 364364e1c68..46ed81b8b7d 100644 --- a/tests/test_optimize.py +++ b/tests/test_optimize.py @@ -47,7 +47,6 @@ # function arguments in various formats mixed_list = [(0.2, 0.3), np.array([0.4, 0.2, 0.4]), 0.1] mixed_tuple = (np.array([0.2, 0.3]), [0.4, 0.2, 0.4], 0.1) -nested_list = [[[0.2], 0.3], [0.1, [0.4]], -0.1] flat_list = [0.2, 0.3, 0.1, 0.4, -0.1] multid_array = np.array([[0.1, 0.2], [-0.1, -0.4]]) multid_list = [[0.1, 0.2], [-0.1, -0.4]] @@ -89,22 +88,13 @@ @qml.qnode(qml.device("default.qubit", wires=1)) -def quant_fun(variables): +def quant_fun(*variables): qml.RX(variables[0][1], wires=[0]) qml.RY(variables[1][2], wires=[0]) qml.RY(variables[2], wires=[0]) return qml.expval(qml.PauliZ(0)) -@qml.qnode(qml.device('default.qubit', wires=1)) -def quant_fun_nested(var): - qml.RX(var[0][0][0], wires=[0]) - qml.RY(var[0][1], wires=[0]) - qml.RY(var[1][0], wires=[0]) - qml.RX(var[1][1][0], wires=[0]) - return qml.expval(qml.PauliZ(0)) - - @qml.qnode(qml.device('default.qubit', wires=1)) def quant_fun_flat(var): qml.RX(var[0], wires=[0]) @@ -153,7 +143,7 @@ def test_mixed_inputs_for_hybrid_optimization(self, bunch, tol): for hybrid optimization tasks.""" def hybrid_fun(variables): - return quant_fun(variables) + variables[0][1] + return quant_fun(*variables) + variables[0][1] hybrid_list = bunch.sgd_opt.step(hybrid_fun, mixed_list) hybrid_tuple = bunch.sgd_opt.step(hybrid_fun, mixed_tuple) @@ -180,28 +170,13 @@ def test_mixed_inputs_for_quantum_optimization(self, bunch, tol): """Tests that gradient descent optimizer treats parameters of mixed types the same for purely quantum optimization tasks.""" - quant_list = bunch.sgd_opt.step(quant_fun, mixed_list) - quant_tuple = bunch.sgd_opt.step(quant_fun, mixed_tuple) + quant_list = bunch.sgd_opt.step(quant_fun, *mixed_list) + quant_tuple = bunch.sgd_opt.step(quant_fun, *mixed_tuple) assert quant_list[0] == pytest.approx(quant_tuple[0], abs=tol) assert quant_list[1] == pytest.approx(quant_tuple[1], abs=tol) assert quant_list[2] == pytest.approx(quant_tuple[2], abs=tol) - def test_nested_and_flat_returns_same_update(self, bunch, tol): - """Tests that gradient descent optimizer has the same output for - nested and flat lists.""" - - def hybrid_fun_flat(var): - return quant_fun_flat(var) + var[4] - - def hybrid_fun_nested(var): - return quant_fun_nested(var) + var[2] - - nested = bunch.sgd_opt.step(hybrid_fun_nested, nested_list) - flat = bunch.sgd_opt.step(hybrid_fun_flat, flat_list) - - assert flat == pytest.approx(list(_flatten(nested)), abs=tol) - def test_array_and_list_return_same_update(self, bunch, tol): """Tests that gradient descent optimizer has the same output for lists and arrays.""" @@ -217,36 +192,51 @@ def hybrid_fun_mdlist(var): assert array == pytest.approx(np.asarray(ls), abs=tol) - @pytest.mark.parametrize( - "circuit, var", [(quant_fun, mixed_list), (quant_fun_mdarr, multid_array)] - ) - def test_step_and_cost_autograd_sgd(self, bunch, circuit, var): + def test_step_and_cost_autograd_sgd_mixed_list(self, bunch): + """Test that the correct cost is returned via the step_and_cost method for the + gradient-descent optimizer""" + _, res = bunch.sgd_opt.step_and_cost(quant_fun, *mixed_list) + expected = quant_fun(*mixed_list) + + assert np.all(res == expected) + + def test_step_and_cost_autograd_sgd_multid_array(self, bunch): """Test that the correct cost is returned via the step_and_cost method for the gradient-descent optimizer""" - _, res = bunch.sgd_opt.step_and_cost(circuit, var) - expected = circuit(var) + _, res = bunch.sgd_opt.step_and_cost(quant_fun_mdarr, multid_array) + expected = quant_fun_mdarr(multid_array) assert np.all(res == expected) - @pytest.mark.parametrize( - "circuit, var", [(quant_fun, mixed_list), (quant_fun_mdarr, multid_array)] - ) - def test_step_and_cost_autograd_nesterov(self, bunch, circuit, var): + def test_step_and_cost_autograd_nesterov_mixed_list(self, bunch): """Test that the correct cost is returned via the step_and_cost method for the Nesterov momentum optimizer""" - _, res = bunch.nesmom_opt.step_and_cost(circuit, var) - expected = circuit(var) + _, res = bunch.nesmom_opt.step_and_cost(quant_fun, *mixed_list) + expected = quant_fun(*mixed_list) + + assert np.all(res == expected) + + def test_step_and_cost_autograd_nesterov_multid_array(self, bunch): + """Test that the correct cost is returned via the step_and_cost method for the + Nesterov momentum optimizer""" + _, res = bunch.nesmom_opt.step_and_cost(quant_fun_mdarr, multid_array) + expected = quant_fun_mdarr(multid_array) + + assert np.all(res == expected) + + def test_step_and_cost_autograd_rotosolve_mixed_list(self, bunch): + """Test that the correct cost is returned via the step_and_cost method for the + Rotosolve optimizer""" + _, res = bunch.rotosolve_opt.step_and_cost(quant_fun, *mixed_list) + expected = quant_fun(*mixed_list) assert np.all(res == expected) - @pytest.mark.parametrize( - "circuit, var", [(quant_fun, mixed_list), (quant_fun_mdarr, multid_array)] - ) - def test_step_and_cost_autograd_rotosolve(self, bunch, circuit, var): + def test_step_and_cost_autograd_rotosolve_multid_array(self, bunch): """Test that the correct cost is returned via the step_and_cost method for the Rotosolve optimizer""" - _, res = bunch.rotosolve_opt.step_and_cost(circuit, var) - expected = circuit(var) + _, res = bunch.rotosolve_opt.step_and_cost(quant_fun_mdarr, multid_array) + expected = quant_fun_mdarr(multid_array) assert np.all(res == expected) diff --git a/tests/test_prob.py b/tests/test_prob.py index 4ed1caa277d..cd737cda354 100644 --- a/tests/test_prob.py +++ b/tests/test_prob.py @@ -137,11 +137,13 @@ def circuit(x, y, z): return qml.probs(wires=[1, 3]) - circuit = qml.QNode(circuit, dev) params = [0.543, -0.765, -0.3] - res_F = circuit.jacobian(params, method="F") - res_A = circuit.jacobian(params, method="A") + + circuit_F = qml.QNode(circuit, dev, diff_method="finite-diff") + circuit_A = qml.QNode(circuit, dev, diff_method="parameter-shift") + res_F = qml.jacobian(circuit_F)(*params) + res_A = qml.jacobian(circuit_A)(*params) # Both jacobians should be of shape (2**prob.wires, num_params) assert res_F.shape == (2**2, 3) diff --git a/tests/test_quantum_gradients.py b/tests/test_quantum_gradients.py index 3763254c89e..1ab445380a6 100644 --- a/tests/test_quantum_gradients.py +++ b/tests/test_quantum_gradients.py @@ -165,17 +165,17 @@ def circuit(x): return qml.expval(O(wires=0)) q = qml.QNode(circuit, gaussian_dev) - val = q.evaluate(par, {}) + val = q(par) - grad_F = q.jacobian(par, method='F') - grad_A2 = q.jacobian(par, method='A', options={"force_order2": True}) + grad_F = q.qtape.jacobian(gaussian_dev, method='numeric') + grad_A2 = q.qtape.jacobian(gaussian_dev, method='analytic', force_order2=True) if O.ev_order == 1: - grad_A = q.jacobian(par, method='A') + grad_A = q.qtape.jacobian(gaussian_dev, method='analytic') # the different methods agree assert grad_A == pytest.approx(grad_F, abs=tol) # analytic method works for every parameter - assert q.par_to_grad_method == {0:'A'} + assert {q.qtape._grad_method(i) for i in range(q.qtape.num_params)}.issubset({"A", "A2"}) # the different methods agree assert grad_A2 == pytest.approx(grad_F, abs=tol) @@ -190,12 +190,13 @@ def qf(r0, phi0, r1, phi1): return qml.expval(qml.NumberOperator(0)) q = qml.QNode(qf, gaussian_dev) - grad_F = q.jacobian(par, method='F') - grad_A = q.jacobian(par, method='A') - grad_A2 = q.jacobian(par, method='A', options={"force_order2": True}) + q(*par) + grad_F = q.qtape.jacobian(gaussian_dev, method='numeric') + grad_A = q.qtape.jacobian(gaussian_dev, method='analytic') + grad_A2 = q.qtape.jacobian(gaussian_dev, method='analytic', force_order2=True) # analytic method works for every parameter - assert q.par_to_grad_method == {i:'A' for i in range(4)} + assert {q.qtape._grad_method(i) for i in range(q.qtape.num_params)} == {"A2"} # the different methods agree assert grad_A == pytest.approx(grad_F, abs=tol) assert grad_A2 == pytest.approx(grad_F, abs=tol) @@ -220,12 +221,13 @@ def qf(x, y): return qml.expval(qml.X(0)) q = qml.QNode(qf, gaussian_dev) - grad_F = q.jacobian(par, method='F') - grad_A = q.jacobian(par, method='A') - grad_A2 = q.jacobian(par, method='A', options={"force_order2": True}) + q(*par) + grad_F = q.qtape.jacobian(gaussian_dev, method='numeric') + grad_A = q.qtape.jacobian(gaussian_dev, method='analytic') + grad_A2 = q.qtape.jacobian(gaussian_dev, method='analytic', force_order2=True) # analytic method works for every parameter - assert q.par_to_grad_method == {0:'A', 1:'A'} + assert {q.qtape._grad_method(i) for i in range(q.qtape.num_params)} == {"A"} # the different methods agree assert grad_A == pytest.approx(grad_F, abs=tol) assert grad_A2 == pytest.approx(grad_F, abs=tol) @@ -245,15 +247,15 @@ def qf(x, y): return qml.expval(qml.PolyXP(M, [0, 1])) q = qml.QNode(qf, gaussian_dev) - grad = q.jacobian(par) - grad_F = q.jacobian(par, method='F') - grad_A = q.jacobian(par, method="best") - grad_A2 = q.jacobian(par, method="best", options={"force_order2": True}) + q(*par) + grad_F = q.qtape.jacobian(gaussian_dev, method='numeric') + grad_A = q.qtape.jacobian(gaussian_dev, method="best") + grad_A2 = q.qtape.jacobian(gaussian_dev, method="best", force_order2=True) # par[0] can use the 'A' method, par[1] cannot - assert q.par_to_grad_method == {0:'A', 1:'F'} + assert {q.qtape._grad_method(i) for i in range(q.qtape.num_params)} == {"A2"} # the different methods agree - assert grad == pytest.approx(grad_F, abs=tol) + assert grad_A2 == pytest.approx(grad_F, abs=tol) def test_cv_gradient_fanout(self, gaussian_dev, tol): @@ -267,12 +269,13 @@ def circuit(x, y): return qml.expval(qml.X(0)) q = qml.QNode(circuit, gaussian_dev) - grad_F = q.jacobian(par, method='F') - grad_A = q.jacobian(par, method='A') - grad_A2 = q.jacobian(par, method='A', options={"force_order2": True}) + q(*par) + grad_F = q.qtape.jacobian(gaussian_dev, method='numeric') + grad_A = q.qtape.jacobian(gaussian_dev, method='analytic') + grad_A2 = q.qtape.jacobian(gaussian_dev, method='analytic', force_order2=True) # analytic method works for every parameter - assert q.par_to_grad_method == {0:'A', 1:'A'} + assert {q.qtape._grad_method(i) for i in range(q.qtape.num_params)} == {"A"} # the different methods agree assert grad_A == pytest.approx(grad_F, abs=tol) assert grad_A2 == pytest.approx(grad_F, abs=tol) @@ -308,12 +311,13 @@ def circuit(x): return qml.expval(qml.X(0)) qnode = qml.QNode(circuit, gaussian_dev) - grad_F = qnode.jacobian(0.5, method='F') - grad_A = qnode.jacobian(0.5, method='A') - grad_A2 = qnode.jacobian(0.5, method='A', options={"force_order2": True}) + qnode(0.5) + grad_F = qnode.qtape.jacobian(gaussian_dev, method='numeric') + grad_A = qnode.qtape.jacobian(gaussian_dev, method='analytic') + grad_A2 = qnode.qtape.jacobian(gaussian_dev, method='analytic', force_order2=True) # par[0] can use the 'A' method - assert qnode.par_to_grad_method == {0: 'A'} + assert {i: qnode.qtape._grad_method(i) for i in range(qnode.qtape.num_params)} == {0: 'A'} # the different methods agree assert grad_A == pytest.approx(grad_F, abs=tol) @@ -454,24 +458,23 @@ def circuit(x, y, z): qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) - qnode = qml.QNode(circuit, qubit_device_2_wires) + qnode = qml.QNode(circuit, qubit_device_2_wires, diff_method="parameter-shift") params = np.array([0.1, -1.6, np.pi / 5]) # manual gradients - grad_fd1 = qnode.jacobian(params, method='F', options={"order": 1}) - grad_fd2 = qnode.jacobian(params, method='F', options={"order": 2}) - grad_angle = qnode.jacobian(params, method='A') + qnode(*params) + grad_fd1 = qnode.qtape.jacobian(qubit_device_2_wires, method='numeric', order=1) + grad_fd2 = qnode.qtape.jacobian(qubit_device_2_wires, method='numeric', order=2) + grad_angle = qnode.qtape.jacobian(qubit_device_2_wires, method='analytic') # automatic gradient - # Note: the lambda function is required as evaluate now receives a required `kwargs` argument - # that cannot be differentiated by autograd. - grad_fn = autograd.grad(lambda x: qnode.evaluate(x, {})) - grad_auto = grad_fn(params)[np.newaxis, :] # so shapes will match + grad_fn = qml.grad(qnode) + grad_auto = grad_fn(*params) # gradients computed with different methods must agree assert grad_fd1 == pytest.approx(grad_fd2, abs=tol) assert grad_fd1 == pytest.approx(grad_angle, abs=tol) - assert grad_fd1 == pytest.approx(grad_auto, abs=tol) + assert np.allclose(grad_fd1, grad_auto, atol=tol, rtol=0) def test_hybrid_gradients(self, qubit_device_2_wires, tol): "Tests that the various ways of computing the gradient of a hybrid computation all agree." @@ -487,10 +490,10 @@ def classifier_circuit(in_data, x): qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) - classifier = qml.QNode(classifier_circuit, qubit_device_2_wires) + classifier = qml.QNode(classifier_circuit, qubit_device_2_wires, diff_method="parameter-shift") param = -0.1259 - in_data = np.array([-0.1, -0.88, np.exp(0.5)]) + in_data = qml.numpy.array([-0.1, -0.88, np.exp(0.5)], requires_grad=False) out_data = np.array([1.5, np.pi / 3, 0.0]) def error(p): @@ -507,15 +510,16 @@ def d_error(p, grad_method): for d_in, d_out in zip(in_data, out_data): args = (d_in, p) diff = (classifier(*args) - d_out) - ret = ret + 2 * diff * classifier.jacobian(args, wrt=[1], method=grad_method) + classifier.qtape.set_parameters((d_in, -1.6, d_in, p), trainable_only=False) + ret = ret + 2 * diff * classifier.qtape.jacobian(qubit_device_2_wires, method=grad_method) return ret y0 = error(param) grad = autograd.grad(error) grad_auto = grad(param) - grad_fd1 = d_error(param, 'F') - grad_angle = d_error(param, 'A') + grad_fd1 = d_error(param, 'numeric') + grad_angle = d_error(param, 'analytic') # gradients computed with different methods must agree assert grad_fd1 == pytest.approx(grad_angle, abs=tol) @@ -534,7 +538,7 @@ def circuit(x, y): qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - quantum = qml.QNode(circuit, qubit_device_2_wires) + quantum = qml.QNode(circuit, qubit_device_2_wires, diff_method="parameter-shift") def classical(p): "Classical node, requires autograd.numpy functions." @@ -543,7 +547,7 @@ def classical(p): def d_classical(a, b, method): "Gradient of classical computed symbolically, can use normal numpy functions." val = classical((a, b)) - J = quantum.jacobian((a, np.log(b)), method=method) + J = quantum.qtape.jacobian(qubit_device_2_wires, params=(a, np.log(b)), method=method) return val * np.array([J[0, 0] + J[1, 0], (J[0, 1] + J[1, 1]) / b]) param = np.array([-0.1259, 1.53]) @@ -551,8 +555,8 @@ def d_classical(a, b, method): grad_classical = autograd.jacobian(classical) grad_auto = grad_classical(param) - grad_fd1 = d_classical(*param, 'F') - grad_angle = d_classical(*param, 'A') + grad_fd1 = d_classical(*param, 'numeric') + grad_angle = d_classical(*param, 'analytic') # gradients computed with different methods must agree assert grad_fd1 == pytest.approx(grad_angle, abs=tol) @@ -595,10 +599,11 @@ def circuit(reused_param, other_param): assert grad_eval == pytest.approx(grad_true, abs=tol) - def test_gradient_exception_on_sample(self, qubit_device_2_wires): + def test_gradient_exception_on_sample(self): """Tests that the proper exception is raised if differentiation of sampling is attempted.""" + dev = qml.device("default.qubit", wires=2) - @qml.qnode(qubit_device_2_wires) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=[0]) return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliX(1)) diff --git a/tests/test_qubit_device.py b/tests/test_qubit_device.py index a86b401b8e0..03c0e4dd07d 100644 --- a/tests/test_qubit_device.py +++ b/tests/test_qubit_device.py @@ -26,6 +26,7 @@ from pennylane.circuit_graph import CircuitGraph from pennylane.variable import Variable from pennylane.wires import Wires +from pennylane.tape import QuantumTape from pennylane.tape.measure import state mock_qubit_device_paulis = ["PauliX", "PauliY", "PauliZ"] @@ -764,6 +765,42 @@ def circuit_3(x, y): node_3(0.432, 0.12) assert dev_1.num_executions == num_evals_1 + num_evals_3 + def test_same_hash(self): + """Test that executing the same tape twice produces the same circuit + hash.""" + dev = qml.device("default.qubit", wires=2) + + with QuantumTape() as tape0: + qml.RZ(0.3, wires=[0]) + qml.expval(qml.PauliX(0)) + + tape0.execute(dev) + orig_hash = dev.circuit_hash + + tape0.execute(dev) + new_hash = dev.circuit_hash + assert orig_hash == new_hash + + def test_different_hash(self): + """Test that executing different tapes affects the circuit hash.""" + dev = qml.device("default.qubit", wires=2) + + with QuantumTape() as tape0: + qml.RZ(0.3, wires=[0]) + qml.expval(qml.PauliX(0)) + + tape0.execute(dev) + orig_hash = dev.circuit_hash + + with QuantumTape() as tape1: + qml.RY(1.3, wires=[0]) + qml.RX(0.9, wires=[0]) + qml.expval(qml.PauliX(0)) + + tape1.execute(dev) + new_hash = dev.circuit_hash + assert orig_hash != new_hash + class TestBatchExecution: """Tests for the batch_execute method.""" diff --git a/tests/test_queuing.py b/tests/test_queuing.py index 991889465a4..d0cf39d4f22 100644 --- a/tests/test_queuing.py +++ b/tests/test_queuing.py @@ -188,13 +188,6 @@ def test_append_tensor_ops_overloaded(self): class TestOperationRecorder: """Test the OperationRecorder class.""" - def test_context_adding(self, monkeypatch): - """Test that the OperationRecorder is added to the list of contexts.""" - with qml._queuing.OperationRecorder() as recorder: - assert recorder in qml.QueuingContext._active_contexts - - assert recorder not in qml.QueuingContext._active_contexts - def test_circuit_integration(self): """Tests that the OperationRecorder integrates well with the core behaviour of PennyLane.""" @@ -203,8 +196,8 @@ def test_circuit_integration(self): + "==========\n" + "PauliY(wires=[0])\n" + "PauliY(wires=[1])\n" - + "RZ(0.4, wires=[0])\n" - + "RZ(0.4, wires=[1])\n" + + "RZ(tensor(0.4, requires_grad=True), wires=[0])\n" + + "RZ(tensor(0.4, requires_grad=True), wires=[1])\n" + "CNOT(wires=[0, 1])\n" + "\n" + "Observables\n" diff --git a/tests/test_tensor_measurements.py b/tests/test_tensor_measurements.py index 478c68e09eb..2a4a321548c 100644 --- a/tests/test_tensor_measurements.py +++ b/tests/test_tensor_measurements.py @@ -363,7 +363,7 @@ def test_paulix_tensor_pauliy(self, theta, phi, varphi, tol): """Test that a tensor product involving PauliX and PauliY works correctly""" dev = qml.device("default.qubit", wires=3) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(a, b, c): ansatz(a, b, c) return sample(qml.PauliX(0) @ qml.PauliY(2)) @@ -396,7 +396,7 @@ def test_pauliz_tensor_hadamard(self, theta, phi, varphi, tol): """Test that a tensor product involving PauliZ and hadamard works correctly""" dev = qml.device("default.qubit", wires=3) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(a, b, c): ansatz(a, b, c) return sample(qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)) @@ -438,7 +438,7 @@ def test_tensor_hermitian(self, theta, phi, varphi, tol): ] ) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(a, b, c): ansatz(a, b, c) return sample(qml.PauliZ(0) @ qml.Hermitian(A, [1, 2])) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5922acd2eb8..01dadab29f9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -473,10 +473,10 @@ class TestInv: def test_inversion_without_context(self): """Test that a sequence of operations is properly inverted.""" - op_queue = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] - inv_queue = [qml.PauliZ(0).inv(), qml.PauliY(0).inv(), qml.PauliX(0).inv()] + op_queue = [qml.PauliX(0), qml.PauliY(0).inv(), qml.PauliZ(0)] + inv_queue = [qml.PauliZ(0).inv(), qml.PauliY(0), qml.PauliX(0).inv()] - inv_ops = pu.inv(op_queue) + inv_ops = pu.inv(op_queue).operations for inv_op, exp_op in zip(inv_ops, inv_queue): assert inv_op.name == exp_op.name @@ -487,7 +487,7 @@ def test_template_inversion_without_context(self): """Test that a template is properly inverted.""" inv_queue = inverted_dummy_template_operations([0, 1, 2]) - inv_ops = pu.inv(dummy_template([0, 1, 2])) + inv_ops = pu.inv(dummy_template([0, 1, 2])).operations for inv_op, exp_op in zip(inv_ops, inv_queue): assert inv_op.name == exp_op.name @@ -498,7 +498,7 @@ def test_double_inversion(self): """Test that inverting twice changes nothing.""" op_queue = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] - inv_inv_ops = pu.inv(pu.inv(op_queue)) + inv_inv_ops = pu.inv(pu.inv(op_queue)).operations for inv_inv_op, exp_op in zip(inv_inv_ops, op_queue): assert inv_inv_op.name == exp_op.name @@ -507,7 +507,7 @@ def test_double_inversion(self): def test_template_double_inversion(self): """Test that inverting twice changes nothing for a template.""" - inv_inv_ops = pu.inv(pu.inv(dummy_template([0, 1, 2]))) + inv_inv_ops = pu.inv(pu.inv(dummy_template([0, 1, 2]))).operations for inv_inv_op, exp_op in zip(inv_inv_ops, dummy_template([0, 1, 2])): assert inv_inv_op.name == exp_op.name @@ -677,7 +677,7 @@ def qfunc(): qfunc() - for inv_op, exp_op in zip(qfunc.ops, inv_queue): + for inv_op, exp_op in zip(qfunc.qtape.operations, inv_queue): assert inv_op.name == exp_op.name assert inv_op.wires == exp_op.wires assert inv_op.data == exp_op.data @@ -711,7 +711,7 @@ def qfunc(): qfunc() - for inv_op, exp_op in zip(qfunc.ops, inv_queue): + for inv_op, exp_op in zip(qfunc.qtape.operations, inv_queue): assert inv_op.name == exp_op.name assert inv_op.wires == exp_op.wires assert inv_op.data == exp_op.data @@ -746,7 +746,7 @@ def qfunc(): qfunc() - for inv_op, exp_op in zip(qfunc.ops, inv_queue): + for inv_op, exp_op in zip(qfunc.qtape.operations, inv_queue): assert inv_op.name == exp_op.name assert inv_op.wires == exp_op.wires assert inv_op.data == exp_op.data @@ -775,7 +775,7 @@ def qfunc(): qfunc() - for inv_op, exp_op in zip(qfunc.ops, inv_queue): + for inv_op, exp_op in zip(qfunc.qtape.operations, inv_queue): assert inv_op.name == exp_op.name assert inv_op.wires == exp_op.wires assert inv_op.data == exp_op.data @@ -785,7 +785,7 @@ def test_argument_wrapping(self): op = qml.PauliX(0) exp_op = qml.PauliX(0).inv() - inv_ops = pu.inv(op) + inv_ops = pu.inv(op).operations assert inv_ops[0].name == exp_op.name assert inv_ops[0].wires == exp_op.wires diff --git a/tests/test_vqe.py b/tests/test_vqe.py index 9fa79a80c2a..373c46cee12 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -895,17 +895,21 @@ def ansatz(params, **kwargs): mt = qml.metric_tensor(qnodes)(p) assert qml.tape_mode_active() # Check that tape mode is still active - qml.disable_tape() + try: + qml.disable_tape() - @qml.qnode(dev) - def circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - qml.CNOT(wires=[0, 1]) - qml.PhaseShift(params[2], wires=1) - return qml.expval(qml.PauliZ(0)) + @qml.qnode(dev) + def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(params[2], wires=1) + return qml.expval(qml.PauliZ(0)) + + mt2 = circuit.metric_tensor([p]) + finally: + qml.enable_tape() - mt2 = circuit.metric_tensor([p]) assert np.allclose(mt, mt2) def test_multiple_devices(self, mocker):