diff --git a/doc/development/adding_operators.rst b/doc/development/adding_operators.rst index 3d7b39aa08d..7a84666da66 100644 --- a/doc/development/adding_operators.rst +++ b/doc/development/adding_operators.rst @@ -70,25 +70,35 @@ The basic components of operators are the following: >>> op.terms() ((1.0, 2.0), [PauliX(wires=[0]), PauliZ(wires=[0])]) - * Representation via the **eigenvalue decomposition** specified by eigenvalues (for the diagonal matrix, :meth:`.Operator.eigvals`) + * Representation via the **eigenvalue decomposition** specified by eigenvalues (for the diagonal matrix, :meth:`.Operator.get_eigvals`) and diagonalizing gates (for the unitaries :meth:`.Operator.diagonalizing_gates`): >>> op = qml.PauliX(0) >>> op.diagonalizing_gates() [Hadamard(wires=[0])] - >>> op.eigvals() + >>> op.get_eigvals() [ 1 -1] - * Representation as a **matrix** (:meth:`.Operator.matrix`), as specified by a global wire order that tells us where the + .. note:: + + The :meth:`.Operator.get_eigvals` method is temporary and will be renamed to :meth:`.Operator.eigvals` in an + upcoming release. It is recommended to use the higher-level :func:`~.eigvals` function where possible. + + * Representation as a **matrix** (:meth:`.Operator.get_matrix`), as specified by a global wire order that tells us where the wires are found on a register: >>> op = qml.PauliRot(0.2, "X", wires=["b"]) - >>> op.matrix(wire_order=["a", "b"]) + >>> op.get_matrix(wire_order=["a", "b"]) [[9.95e-01-2.26e-18j 2.72e-17-9.98e-02j, 0+0j, 0+0j] [2.72e-17-9.98e-02j 9.95e-01-2.26e-18j, 0+0j, 0+0j] [0+0j, 0+0j, 9.95e-01-2.26e-18j 2.72e-17-9.98e-02j] [0+0j, 0+0j, 2.72e-17-9.98e-02j 9.95e-01-2.26e-18j]] + .. note:: + + The :meth:`.Operator.get_matrix` method is temporary and will be renamed to :meth:`.Operator.matrix` in an + upcoming release. It is recommended to use the higher-level :func:`~.matrix` function where possible. + * Representation as a **sparse matrix** (:meth:`.Operator.sparse_matrix`): >>> from scipy.sparse.coo import coo_matrix @@ -214,7 +224,7 @@ whether it supports operations using the operation name. - If the device registers support for an operation with the same name, PennyLane leaves the gate implementation up to the device. The device might have a hardcoded implementation, *or* it may refer to one of the - numerical representations of the operator (such as :meth:`.Operator.matrix`). + numerical representations of the operator (such as :meth:`.Operator.get_matrix`). - If the device does not register support for an operation with the same name, PennyLane will automatically decompose the gate using :meth:`.Operator.decomposition`. diff --git a/doc/development/guide/architecture.rst b/doc/development/guide/architecture.rst index f2ec97da413..a4f45f234b0 100644 --- a/doc/development/guide/architecture.rst +++ b/doc/development/guide/architecture.rst @@ -88,10 +88,16 @@ details in the documentation on :doc:`adding operations >> op = qml.PauliRot(0.2, "X", wires=["b"]) - >>> op.matrix() + >>> op.get_matrix() [[9.95004177e-01-2.25761781e-18j 2.72169462e-17-9.98334214e-02j] [2.72169462e-17-9.98334214e-02j 9.95004177e-01-2.25761781e-18j]] +.. note:: + + The ``op.get_matrix()`` method is temporary and will be renamed to ``op.matrix()`` in an + upcoming release. Where possible it is recommended to use higher-level functions such as + :func:`~.matrix`. + Devices query operators for their properties and representations to gain information on how to implement the operator. diff --git a/doc/introduction/measurements.rst b/doc/introduction/measurements.rst index 4ee40ae031e..9c07dda65ca 100644 --- a/doc/introduction/measurements.rst +++ b/doc/introduction/measurements.rst @@ -158,8 +158,8 @@ Mid-circuit measurements and conditional operations --------------------------------------------------- PennyLane allows specifying measurements in the middle of the circuit. -Operations can then be conditioned on the measurement outcome of such -mid-circuit measurements: +Quantum functions such as operations can then be conditioned on the measurement +outcome of such mid-circuit measurements: .. code-block:: python @@ -180,7 +180,7 @@ measurement on qubit 1 yielded ``1`` as an outcome, otherwise doing nothing for the ``0`` measurement outcome. PennyLane implements the deferred measurement principle to transform -conditional operations with the :func:`defer_measurements` quantum +conditional operations with the :func:`~.defer_measurements` quantum function transform. .. code-block:: python @@ -226,6 +226,10 @@ differentiable and device-independent way. Performing true mid-circuit measurements and conditional operations is dependent on the quantum hardware and PennyLane device capabilities. +For more examples on applying quantum functions conditionally, refer to the +:func:`~.pennylane.cond` transform. + + Changing the number of shots ---------------------------- diff --git a/doc/releases/changelog-0.21.0.md b/doc/releases/changelog-0.21.0.md index c26d1d58836..9defcc03e61 100644 --- a/doc/releases/changelog-0.21.0.md +++ b/doc/releases/changelog-0.21.0.md @@ -660,7 +660,7 @@ This release contains contributions from (in alphabetical order): Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, -Esther Cruz, Christian Gogolin, Nathan Killoran, Christina Lee, Olivia Di -Matteo, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, -Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Romain Moyard, Lee James +Esther Cruz, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, +David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, +Nathan Killoran, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O'Riordan, Maria Schuld, Jay Soni, Antal SzΓ‘va, David Wierichs, Shaoming Zhang. diff --git a/doc/releases/changelog-0.22.0.md b/doc/releases/changelog-0.22.0.md index 7d1f9e1b1fd..1d6df9aa68d 100644 --- a/doc/releases/changelog-0.22.0.md +++ b/doc/releases/changelog-0.22.0.md @@ -4,16 +4,208 @@

New features since last release

-* A new operation `qml.Snapshot` has been added to assist users in debugging quantum progams. - The instruction is used to save the internal state of simulator devices at arbitrary points of - execution, such as the quantum state vector and density matrix in the qubit case, or the - covariance matrix and vector of means in the continuous variable case. The saved states can be - retrieved in the form of a dictionary via the top-level `qml.snapshots` function. +

Quantum circuit cutting βœ‚οΈ

+ +* You can now run `N`-wire circuits on devices with fewer than `N` wires, by + strategically placing `WireCut` operations that allow their circuit to be + partitioned into smaller fragments, at a cost of needing to perform a greater + number of device executions. Circuit cutting is enabled by decorating a QNode + with the `@qml.cut_circuit` transform. + [(#2107)](https://github.com/PennyLaneAI/pennylane/pull/2107) + [(#2124)](https://github.com/PennyLaneAI/pennylane/pull/2124) + [(#2153)](https://github.com/PennyLaneAI/pennylane/pull/2153) + [(#2165)](https://github.com/PennyLaneAI/pennylane/pull/2165) + [(#2158)](https://github.com/PennyLaneAI/pennylane/pull/2158) + [(#2169)](https://github.com/PennyLaneAI/pennylane/pull/2169) + [(#2192)](https://github.com/PennyLaneAI/pennylane/pull/2192) + [(#2216)](https://github.com/PennyLaneAI/pennylane/pull/2216) + [(#2168)](https://github.com/PennyLaneAI/pennylane/pull/2168) + [(#2223)](https://github.com/PennyLaneAI/pennylane/pull/2223) + [(#2231)](https://github.com/PennyLaneAI/pennylane/pull/2231) + [(#2234)](https://github.com/PennyLaneAI/pennylane/pull/2234) + [(#2244)](https://github.com/PennyLaneAI/pennylane/pull/2244) + [(#2251)](https://github.com/PennyLaneAI/pennylane/pull/2251) + [(#2265)](https://github.com/PennyLaneAI/pennylane/pull/2265) + [(#2254)](https://github.com/PennyLaneAI/pennylane/pull/2254) + [(#2260)](https://github.com/PennyLaneAI/pennylane/pull/2260) + [(#2257)](https://github.com/PennyLaneAI/pennylane/pull/2257) + [(#2279)](https://github.com/PennyLaneAI/pennylane/pull/2279) + + The example below shows how a three-wire circuit can be run on a two-wire device: + + ```python + dev = qml.device("default.qubit", wires=2) + + @qml.cut_circuit + @qml.qnode(dev) + def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.9, wires=1) + qml.RX(0.3, wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(-0.4, wires=0) + + qml.WireCut(wires=1) + + qml.CZ(wires=[1, 2]) + + return qml.expval(qml.grouping.string_to_pauli_word("ZZZ")) + ``` + + Instead of executing the circuit directly, it will be partitioned into smaller fragments + according to the `WireCut` locations, and each fragment executed multiple times. Combining the + results of the fragment executions will recover the expected output of the original uncut circuit. + + ```pycon + >>> x = np.array(0.531, requires_grad=True) + >>> circuit(0.531) + 0.47165198882111165 + ``` + + Circuit cutting support is also differentiable: + + ```pycon + >>> qml.grad(circuit)(x) + -0.276982865449393 + ``` + + For more details on circuit cutting, check out the + [qml.cut_circuit](https://pennylane.readthedocs.io/en/latest/code/api/pennylane.cut_circuit.html) + documentation page or [Peng et. al](https://arxiv.org/abs/1904.00102). + +

Conditional operations: quantum teleportation unlocked πŸ”“πŸŒ€

+ +* Support for mid-circuit measurements and conditional operations + has been added, to enable use cases like quantum teleportation, quantum error + correction and quantum error mitigation. + [(#2211)](https://github.com/PennyLaneAI/pennylane/pull/2211) + [(#2236)](https://github.com/PennyLaneAI/pennylane/pull/2236) + [(#2275)](https://github.com/PennyLaneAI/pennylane/pull/2275) + + Two new functions have been added to support this capability: + + - `qml.measure()` places mid-circuit measurements in the middle of a quantum function. + + - `qml.cond()` allows operations and quantum functions to be conditioned on the result of a + previous measurement. + + For example, the code below shows how to teleport a qubit from wire 0 to wire 2: + + ```python + dev = qml.device("default.qubit", wires=3) + input_state = np.array([1, -1], requires_grad=False) / np.sqrt(2) + + @qml.qnode(dev) + def teleport(state): + # Prepare input state + qml.QubitStateVector(state, wires=0) + + # Prepare Bell state + qml.Hadamard(wires=1) + qml.CNOT(wires=[1, 2]) + + # Apply gates + qml.CNOT(wires=[0, 1]) + qml.Hadamard(wires=0) + + # Measure first two wires + m1 = qml.measure(0) + m2 = qml.measure(1) + + # Condition final wire on results + qml.cond(m2 == 1, qml.PauliX)(wires=2) + qml.cond(m1 == 1, qml.PauliZ)(wires=2) + + # Return state on final wire + return qml.density_matrix(wires=2) + ``` + + We can double-check that the qubit has been teleported by computing the + overlap between the input state and the resulting state on wire 2: + + ```pycon + >>> output_state = teleport(input_state) + >>> output_state + tensor([[ 0.5+0.j, -0.5+0.j], + [-0.5+0.j, 0.5+0.j]], requires_grad=True) + >>> input_state.conj() @ output_state @ input_state + tensor(1.+0.j, requires_grad=True) + ``` + + For a full description of new capabilities, refer to the [Mid-circuit + measurements and conditional + operations](https://pennylane.readthedocs.io/en/latest/introduction/measurements.html#mid-circuit-measurements-and-conditional-operations) + section in the documentation. + +* Train mid-circuit measurements by deferring them, via the new + `@qml.defer_measurements` transform. + [(#2211)](https://github.com/PennyLaneAI/pennylane/pull/2211) + [(#2236)](https://github.com/PennyLaneAI/pennylane/pull/2236) + [(#2275)](https://github.com/PennyLaneAI/pennylane/pull/2275) + + If a device doesn't natively support mid-circuit measurements, the `@qml.defer_measurements` + transform can be applied to the QNode to transform the QNode into one with _terminal_ measurements + and _controlled_ operations: + + ```python + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @qml.defer_measurements + def circuit(x): + qml.Hadamard(wires=0) + + m = qml.measure(0) + + def op_if_true(): + return qml.RX(x**2, wires=1) + + def op_if_false(): + return qml.RY(x, wires=1) + + qml.cond(m==1, op_if_true, op_if_false)() + + return qml.expval(qml.PauliZ(1)) + ``` + + ```pycon + >>> x = np.array(0.7, requires_grad=True) + >>> print(qml.draw(circuit, expansion_strategy="device")(x)) + 0: ──H─╭C─────────X─╭C─────────X── + 1: ────╰RX(0.49)────╰RY(0.70)───── + >>> circuit(x) + tensor(0.82358752, requires_grad=True) + ``` + + Deferring mid-circuit measurements also enables differentiation: + + ```pycon + >>> qml.grad(circuit)(x) + -0.651546965338656 + ``` + +

Debug with mid-circuit quantum snapshots πŸ“·

+ +* A new operation `qml.Snapshot` has been added to assist in debugging quantum functions. [(#2233)](https://github.com/PennyLaneAI/pennylane/pull/2233) + [(#2289)](https://github.com/PennyLaneAI/pennylane/pull/2289) + [(#2291)](https://github.com/PennyLaneAI/pennylane/pull/2291) + [(#2315)](https://github.com/PennyLaneAI/pennylane/pull/2315) + + `qml.Snapshot` saves the internal state of devices at arbitrary points of execution. - ```py + Currently supported devices include: + + - `default.qubit`: each snapshot saves the quantum state vector + - `default.mixed`: each snapshot saves the density matrix + - `default.gaussian`: each snapshot saves the covariance matrix and vector of means + + During normal execution, the snapshots are ignored: + + ```python dev = qml.device("default.qubit", wires=2) - + @qml.qnode(dev, interface=None) def circuit(): qml.Snapshot() @@ -24,6 +216,10 @@ return qml.expval(qml.PauliX(0)) ``` + However, when using the `qml.snapshots` + transform, intermediate device states will be stored and returned alongside the + results. + ```pycon >>> qml.snapshots(circuit)() {0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]), @@ -32,7 +228,41 @@ 'execution_results': array(0.)} ``` -* New functions and transforms of operators have been added. These include: +

Batch embedding and state preparation data πŸ“¦

+ +* Added the `@qml.batch_input` transform to enable batching non-trainable gate parameters. + In addition, the `qml.qnn.KerasLayer` class has been updated to natively support + batched training data. + [(#2069)](https://github.com/PennyLaneAI/pennylane/pull/2069) + + As with other transforms, `@qml.batch_input` can be used to decorate QNodes: + ```python + dev = qml.device("default.qubit", wires=2, shots=None) + + @qml.batch_input(argnum=0) + @qml.qnode(dev, diff_method="parameter-shift", interface="tf") + def circuit(inputs, weights): + # add a batch dimension to the embedding data + qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") + qml.RY(weights[0], wires=0) + qml.RY(weights[1], wires=1) + return qml.expval(qml.PauliZ(1)) + ``` + + Batched input parameters can then be passed during QNode evaluation: + + ```pycon + >>> x = tf.random.uniform((10, 2), 0, 1) + >>> w = tf.random.uniform((2,), 0, 1) + >>> circuit(x, w) + + ``` + +

Even more mighty quantum transforms πŸ›βž‘πŸ¦‹

+ +* New functions and transforms of operators have been added: - `qml.matrix()` for computing the matrix representation of one or more unitary operators. [(#2241)](https://github.com/PennyLaneAI/pennylane/pull/2241) @@ -57,7 +287,7 @@ ```pycon >>> x = torch.tensor(0.6, requires_grad=True) >>> matrix_fn = qml.matrix(qml.RX) - >>> matrix_fn(x) + >>> matrix_fn(x, wires=[0]) tensor([[0.9553+0.0000j, 0.0000-0.2955j], [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=) ``` @@ -68,7 +298,7 @@ >>> loss = torch.real(torch.trace(matrix_fn(x, wires=0))) >>> loss.backward() >>> x.grad - tensor(-0.5910) + tensor(-0.2955) ``` Some operator transform can also act on multiple operations, by passing @@ -85,62 +315,6 @@ [ 0.+0.j, 0.+0.38268343j, 0.+0.j, -0.92387953+0.j]]) ``` -* The user-interface for mid-circuit measurements and conditional operations - has been added to support use cases like quantum teleportation. - [(#2211)](https://github.com/PennyLaneAI/pennylane/pull/2211) - [(#2236)](https://github.com/PennyLaneAI/pennylane/pull/2236) - [(#2275)](https://github.com/PennyLaneAI/pennylane/pull/2275) - - The addition includes the `defer_measurements` device-independent transform - that can be applied on devices that have no native mid-circuit measurements - capabilities. This transform is applied by default when evaluating a QNode on a - device that doesn't support mid-circuit measurements. - - For example, the code below shows how to teleport a qubit: - - ```python - from scipy.stats import unitary_group - - random_state = unitary_group.rvs(2, random_state=1967)[0] - - dev = qml.device("default.mixed", wires=3) - - @qml.qnode(dev) - def teleport(state): - # Prepare input state - qml.QubitStateVector(state, wires=0) - - # Prepare Bell state - qml.Hadamard(wires=1) - qml.CNOT(wires=[1, 2]) - - # Apply gates - qml.CNOT(wires=[0, 1]) - qml.Hadamard(wires=0) - - # Measure first two wires - m1 = qml.measure(0) - m2 = qml.measure(1) - - # Condition final wire on results - qml.cond(m2 == 1, qml.PauliX)(wires=2) - qml.cond(m1 == 1, qml.PauliZ)(wires=2) - - # Return state on final wire - return qml.density_matrix(wires=2) - - output_projector = teleport(random_state) - overlap = random_state.conj() @ output_projector @ random_state - ``` - ```pycon - >>> overlap - tensor(1.+0.j, requires_grad=True) - ``` - - For further examples, refer to the [Mid-circuit measurements and conditional - operations](https://pennylane.readthedocs.io/en/latest/introduction/measurements.html#mid-circuit-measurements-and-conditional-operations) - section in the documentation. - * A new transform has been added to construct the pairwise-commutation directed acyclic graph (DAG) representation of a quantum circuit. [(#1712)](https://github.com/PennyLaneAI/pennylane/pull/1712) @@ -161,7 +335,7 @@ ... qml.CRZ(z, wires=[2, 0]) ... qml.RY(-y, wires=1) ... return qml.expval(qml.PauliZ(0)) - >>> dag_fn = commutation_dag(circuit) + >>> dag_fn = qml.commutation_dag(circuit) >>> dag = dag_fn(np.pi / 4, np.pi / 3, np.pi / 2) ``` @@ -169,8 +343,9 @@ the form `(ID, CommutationDAGNode)`: ```pycon - nodes = dag.get_nodes() - [(0, ), ...] + >>> nodes = dag.get_nodes() + >>> nodes + NodeDataView({0: , ...}, data='node') ``` Specific nodes in the commutation DAG can be accessed via the `get_node()` method: @@ -187,7 +362,9 @@ [] ``` -* The text based drawer accessed via `qml.draw` has been overhauled. +

Improvements

+ +* The text-based drawer accessed via `qml.draw()` has been optimized and improved. [(#2128)](https://github.com/PennyLaneAI/pennylane/pull/2128) [(#2198)](https://github.com/PennyLaneAI/pennylane/pull/2198) @@ -215,101 +392,41 @@ 1: ────╰RX(2.30)──Rot(1.20,3.20,0.70)─╰RX(-2.30)── β•° ``` -* Parametric operations now have the `parameter_frequencies` - method that returns the frequencies with which a parameter - enters a circuit. In addition, the general parameter-shift - rule is now automatically used by `qml.gradients.param_shift`. +* The frequencies of gate parameters are now accessible as an operation + property and can be used for circuit analysis, optimization via the + `RotosolveOptimizer` and differentiation with the parameter-shift rule + (including the general shift rule). [(#2180)](https://github.com/PennyLaneAI/pennylane/pull/2180) [(#2182)](https://github.com/PennyLaneAI/pennylane/pull/2182) [(#2227)](https://github.com/PennyLaneAI/pennylane/pull/2227) - The frequencies can be used for circuit analysis, optimization - via the `RotosolveOptimizer` and differentiation with the - parameter-shift rule. They assume that the circuit returns - expectation values or probabilities, for a variance - measurement the frequencies will differ. - - By default, the frequencies will be obtained from the - `generator` property (if it is defined). - - When using `qml.gradients.param_shift`, either a custom `grad_recipe` - or the parameter frequencies are used to obtain the shift rule - for the operation, in that order of preference. - - See [Vidal and Theis (2018)](https://arxiv.org/abs/1812.06323) - and [Wierichs et al. (2021)](https://arxiv.org/abs/2107.12390) - for theoretical background information on the general - parameter-shift rule. - -* Continued development of the circuit-cutting compiler: - - - A method for converting a quantum tape to a directed multigraph that is amenable - to graph partitioning algorithms for circuit cutting has been added. - [(#2107)](https://github.com/PennyLaneAI/pennylane/pull/2107) - - - A method to replace `WireCut` nodes in a directed multigraph with `MeasureNode` - and `PrepareNode` placeholders has been added. - [(#2124)](https://github.com/PennyLaneAI/pennylane/pull/2124) - - - A method has been added that takes a directed multigraph with `MeasureNode` and - `PrepareNode` placeholders and fragments into subgraphs and a communication graph. - [(#2153)](https://github.com/PennyLaneAI/pennylane/pull/2153) - - - A method has been added that takes a directed multigraph with `MeasureNode` - and `PrepareNode` placeholder nodes and converts it into a tape. - [(#2165)](https://github.com/PennyLaneAI/pennylane/pull/2165) - - - A differentiable tensor contraction function `contract_tensors` has been - added. - [(#2158)](https://github.com/PennyLaneAI/pennylane/pull/2158) - - - A method has been added that expands a quantum tape over `MeasureNode` and `PrepareNode` - configurations. - [(#2169)](https://github.com/PennyLaneAI/pennylane/pull/2169) - - - The postprocessing function for the `cut_circuit` transform has been added. - [(#2192)](https://github.com/PennyLaneAI/pennylane/pull/2192) - - - The `cut_circuit` transform has been added. - [(#2216)](https://github.com/PennyLaneAI/pennylane/pull/2216) - - - A class `CutStrategy` which acts as an interface and coordinates device/user - constraints with circuit execution requirements to come up with the best sets - of graph partitioning parameters. - [(#2168)](https://github.com/PennyLaneAI/pennylane/pull/2168) - - - A suite of integration tests has been added. - [(#2231)](https://github.com/PennyLaneAI/pennylane/pull/2231) - [(#2234)](https://github.com/PennyLaneAI/pennylane/pull/2234) - [(#2244)](https://github.com/PennyLaneAI/pennylane/pull/2244) - [(#2251)](https://github.com/PennyLaneAI/pennylane/pull/2251) - [(#2265)](https://github.com/PennyLaneAI/pennylane/pull/2265) - - - Circuit fragments that are disconnected from the terminal measurements are now removed. - [(#2254)](https://github.com/PennyLaneAI/pennylane/pull/2254) - - - `WireCut` operations that do not lead to a disconnection are now being removed. - [(#2260)](https://github.com/PennyLaneAI/pennylane/pull/2260) - - - Circuit cutting now remaps the wires of fragment circuits to match the available wires on the - device. - [(#2257)](https://github.com/PennyLaneAI/pennylane/pull/2257) + ```pycon + >>> op = qml.CRot(0.4, 0.1, 0.3, wires=[0, 1]) + >>> op.parameter_frequencies + [(0.5, 1.0), (0.5, 1.0), (0.5, 1.0)] + ``` -

Improvements

+ When using `qml.gradients.param_shift`, either a custom `grad_recipe` or the + parameter frequencies are used to obtain the shift rule for the operation, in + that order of preference. -* Most compilation transforms, and relevant subroutines, have been updated to - support just-in-time compilation with `jax.jit`. - [(#1894)](https://github.com/PennyLaneAI/pennylane/pull/1894/) + See [Vidal and Theis (2018)](https://arxiv.org/abs/1812.06323) and [Wierichs + et al. (2021)](https://arxiv.org/abs/2107.12390) for theoretical background + information on the general parameter-shift rule. * No two-term parameter-shift rule is assumed anymore by default. [(#2227)](https://github.com/PennyLaneAI/pennylane/pull/2227) Previously, operations marked for analytic differentiation that - do not provide a `generator`, `parameter_frequencies` or a + did not provide a `generator`, `parameter_frequencies` or a custom `grad_recipe` were assumed to satisfy the two-term shift rule. This now has to be made explicit for custom operations by adding any of the above attributes. +* Most compilation transforms, and relevant subroutines, have been updated to + support just-in-time compilation with `jax.jit`. + [(#1894)](https://github.com/PennyLaneAI/pennylane/pull/1894/) + * The `qml.draw_mpl` transform supports a `expansion_strategy` keyword argument. [(#2271)](https://github.com/PennyLaneAI/pennylane/pull/2271/) @@ -322,61 +439,180 @@ qubit-wise commuting terms. [(#2185)](https://github.com/PennyLaneAI/pennylane/pull/2185) - ```pycon - >>> qml.grouping.partition_pauli_group(2) - [['II', 'IZ', 'ZI', 'ZZ'], - ['IX', 'ZX'], - ['IY', 'ZY'], - ['XI', 'XZ'], - ['XX'], - ['XY'], - ['YI', 'YZ'], - ['YX'], - ['YY']] - ``` +* The Operator class has undergone a major refactor with the following changes: + + * **Matrices**: the static method `Operator.compute_matrices()` defines the matrix representation + of the operator, and the function `qml.matrix(op)` computes this for a given + instance. + [(#1996)](https://github.com/PennyLaneAI/pennylane/pull/1996) + + * **Eigvals**: the static method `Operator.compute_eigvals()` defines the matrix representation + of the operator, and the function `qml.eigvals(op)` computes this for a given + instance. + [(#2048)](https://github.com/PennyLaneAI/pennylane/pull/2048) + + * **Decompositions**: the static method `Operator.compute_decomposition()` defines the matrix representation + of the operator, and the method `op.decomposition()` computes this for a given + instance. + [(#2024)](https://github.com/PennyLaneAI/pennylane/pull/2024) + [(#2053)](https://github.com/PennyLaneAI/pennylane/pull/2053) + + * **Sparse matrices**: the static method `Operator.compute_sparse_matrix()` defines the sparse + matrix representation of the operator, and the method `op.sparse_matrix()` computes this for a + given instance. + [(#2050)](https://github.com/PennyLaneAI/pennylane/pull/2050) + + * **Linear combinations of operators**: The static method `compute_terms()`, used for representing + the linear combination of coefficients and operators representing the operator, has been added. + The method `op.terms()` computes this for a given instance. + Currently, only the `Hamiltonian` class overwrites `compute_terms()` to store + coefficients and operators. The `Hamiltonian.terms` property hence becomes + a proper method called by `Hamiltonian.terms()`. + [(#2036)](https://github.com/PennyLaneAI/pennylane/pull/2036) + + * **Diagonalization**: The `diagonalizing_gates()` representation has been moved to the + highest-level `Operator` class and is therefore available to all subclasses. A condition + `qml.operation.defines_diagonalizing_gates` has been added, which can be used in tape contexts + without queueing. In addition, a static `compute_diagonalizing_gates` method has been added, + which is called by default in `diagonalizing_gates()`. + [(#1985)](https://github.com/PennyLaneAI/pennylane/pull/1985) + [(#1993)](https://github.com/PennyLaneAI/pennylane/pull/1993) + + * Error handling has been improved for Operator representations. Custom errors subclassing + `OperatorPropertyUndefined` are raised if a representation has not been defined. This replaces + the `NotImplementedError` and allows finer control for developers. + [(#2064)](https://github.com/PennyLaneAI/pennylane/pull/2064) + [(#2287)](https://github.com/PennyLaneAI/pennylane/pull/2287/) + + * A `Operator.hyperparameters` attribute, used for storing operation parameters that are *never* + trainable, has been added to the operator class. + [(#2017)](https://github.com/PennyLaneAI/pennylane/pull/2017) + + * The `string_for_inverse` attribute is removed. + [(#2021)](https://github.com/PennyLaneAI/pennylane/pull/2021) + + * The `expand()` method was moved from the `Operation` class to the main `Operator` class. + [(#2053)](https://github.com/PennyLaneAI/pennylane/pull/2053) + [(#2239)](https://github.com/PennyLaneAI/pennylane/pull/2239) -

Breaking changes

+

Deprecations

-* The `MultiControlledX` operation now accepts a single `wires` keyword argument for both `control_wires` and `wires`. - The single `wires` keyword should be all the control wires followed by a single target wire. - [(#2121)](https://github.com/PennyLaneAI/pennylane/pull/2121) - [(#2278)](https://github.com/PennyLaneAI/pennylane/pull/2278) +* There are several important changes when creating custom operations: + [(#2214)](https://github.com/PennyLaneAI/pennylane/pull/2214) + [(#2227)](https://github.com/PennyLaneAI/pennylane/pull/2227) + [(#2030)](https://github.com/PennyLaneAI/pennylane/pull/2030) + [(#2061)](https://github.com/PennyLaneAI/pennylane/pull/2061) -

Deprecations

+ - The `Operator.matrix` method has been deprecated and `Operator.compute_matrix` + should be defined instead. Operator matrices should be accessed using `qml.matrix(op)`. + If you were previously defining the class method `Operator._matrix()`, this is a a **breaking + change** --- please update your operation to instead overwrite `Operator.compute_matrix`. -* The `qml.operation.Operation.get_parameter_shift` method has been deprecated - and will be removed in a future release. - [#2227](https://github.com/PennyLaneAI/pennylane/pull/2227) + - The `Operator.decomposition` method has been deprecated and `Operator.compute_decomposition` + should be defined instead. Operator decompositions should be accessed using `Operator.decomposition()`. - Instead, the functionalities for general parameter-shift rules in the - `qml.gradients` module should be used, together with the operation attributes - `parameter_frequencies` or `grad_recipe`. + - The `Operator.eigvals` method has been deprecated and `Operator.compute_eigvals` + should be defined instead. Operator eigenvalues should be accessed using `qml.eigvals(op)`. -* The `qml.finite_diff()` function has been deprecated and will be removed - in an upcoming release. Instead, - `qml.gradients.finite_diff()` can be used to compute purely quantum gradients - (that is, gradients of tapes or QNode). - [#2212](https://github.com/PennyLaneAI/pennylane/pull/2212) + - The `Operator.generator` property is now a method, and should return an *operator instance* + representing the generator. Note that unlike the other representations above, this is a + **breaking change**. Operator generators should be accessed using `qml.generator(op)`. + + - The `Operation.get_parameter_shift` method has been deprecated + and will be removed in a future release. + + Instead, the functionalities for general parameter-shift rules in the + `qml.gradients` module should be used, together with the operation attributes + `parameter_frequencies` or `grad_recipe`. + +* Executing tapes using `tape.execute(dev)` is deprecated. + Please use the `qml.execute([tape], dev)` function instead. + [(#2306)](https://github.com/PennyLaneAI/pennylane/pull/2306) + +* The subclasses of the quantum tape, including `JacobianTape`, `QubitParamShiftTape`, + `CVParamShiftTape`, and `ReversibleTape` are deprecated. Instead of calling + `JacobianTape.jacobian()` and `JacobianTape.hessian()`, + please use a standard `QuantumTape`, and apply gradient transforms using + the `qml.gradients` module. + [(#2306)](https://github.com/PennyLaneAI/pennylane/pull/2306) * `qml.transforms.get_unitary_matrix()` has been deprecated and will be removed in a future release. For extracting matrices of operations and quantum functions, please use `qml.matrix()`. [(#2248)](https://github.com/PennyLaneAI/pennylane/pull/2248) +* The `qml.finite_diff()` function has been deprecated and will be removed + in an upcoming release. Instead, + `qml.gradients.finite_diff()` can be used to compute purely quantum gradients + (that is, gradients of tapes or QNode). + [(#2212)](https://github.com/PennyLaneAI/pennylane/pull/2212) + +* The `MultiControlledX` operation now accepts a single `wires` keyword argument for both `control_wires` and `wires`. + The single `wires` keyword should be all the control wires followed by a single target wire. + [(#2121)](https://github.com/PennyLaneAI/pennylane/pull/2121) + [(#2278)](https://github.com/PennyLaneAI/pennylane/pull/2278) + +

Breaking changes

+ +* The representation of an operator as a matrix has been overhauled. + [(#1996)](https://github.com/PennyLaneAI/pennylane/pull/1996) + + The "canonical matrix", which is independent of wires, + is now defined in the static method `compute_matrix()` instead of `_matrix`. + By default, this method is assumed to take all parameters and non-trainable + hyperparameters that define the operation. + + ```pycon + >>> qml.RX.compute_matrix(0.5) + [[0.96891242+0.j 0. -0.24740396j] + [0. -0.24740396j 0.96891242+0.j ]] + ``` + + If no canonical matrix is specified for a gate, `compute_matrix()` + raises a `MatrixUndefinedError`. + +* The generator property has been updated to an instance method, + `Operator.generator()`. It now returns an instantiated operation, + representing the generator of the instantiated operator. + [(#2030)](https://github.com/PennyLaneAI/pennylane/pull/2030) + [(#2061)](https://github.com/PennyLaneAI/pennylane/pull/2061) + + Various operators have been updated to specify the generator as either + an `Observable`, `Tensor`, `Hamiltonian`, `SparseHamiltonian`, or `Hermitian` + operator. + + In addition, `qml.generator(operation)` has been added to aid in retrieving + generator representations of operators. + +* The argument `wires` in `heisenberg_obs`, `heisenberg_expand` and `heisenberg_tr` + was renamed to `wire_order` to be consistent with other matrix representations. + [(#2051)](https://github.com/PennyLaneAI/pennylane/pull/2051) + +* The property `kraus_matrices` has been changed to a method, and `_kraus_matrices` renamed to + `compute_kraus_matrices`, which is now a static method. + [(#2055)](https://github.com/PennyLaneAI/pennylane/pull/2055) + +* The `pennylane.measure` module has been renamed to `pennylane.measurements`. + [(#2236)](https://github.com/PennyLaneAI/pennylane/pull/2236) +

Bug fixes

+* The `basis` property of `qml.SWAP` was set to `"X"`, which is incorrect; it is + now set to `None`. + [(#2287)](https://github.com/PennyLaneAI/pennylane/pull/2287/) + * The `qml.RandomLayers` template now decomposes when the weights are a list of lists. [(#2266)](https://github.com/PennyLaneAI/pennylane/pull/2266/) -* The `qml.QubitUnitary` operation now supports jitting. +* The `qml.QubitUnitary` operation now supports just-in-time compilation using JAX. [(#2249)](https://github.com/PennyLaneAI/pennylane/pull/2249) -* Fixes a bug in the JAX interface where ``DeviceArray`` objects +* Fixes a bug in the JAX interface where `DeviceArray` objects were not being converted to NumPy arrays before executing an external device. [(#2255)](https://github.com/PennyLaneAI/pennylane/pull/2255) -* The ``qml.ctrl`` transform now works correctly with gradient transforms +* The `qml.ctrl` transform now works correctly with gradient transforms such as the parameter-shift rule. [(#2238)](https://github.com/PennyLaneAI/pennylane/pull/2238) @@ -390,9 +626,18 @@ using the parameter-shift rule. [(#2180)](https://github.com/PennyLaneAI/pennylane/pull/2180) +* Fixes a bug where `qml.gradients.param_shift_hessian` would produce an + error whenever all elements of the Hessian are known in advance to be 0. + [(#2299)](https://github.com/PennyLaneAI/pennylane/pull/2299) +

Documentation

-* Link to the strawberry fields docs for information on the CV model. +* The developer guide on adding templates and the architecture overview were rewritten + to reflect the past and planned changes of the operator refactor. + [(#2066)](https://github.com/PennyLaneAI/pennylane/pull/2066) + +* Links to the Strawberry Fields documentation for information on the CV + model. [(#2259)](https://github.com/PennyLaneAI/pennylane/pull/2259) * Fixes the documentation example for `qml.QFT`. @@ -413,135 +658,19 @@ display correctly in the docs. [(#2286)](https://github.com/PennyLaneAI/pennylane/pull/2286) -

Operator class refactor

- -The Operator class has undergone a major refactor with the following changes: - -* The static `compute_decomposition` method defines the decomposition - of an operator into a product of simpler operators, and the instance method - `decomposition()` computes this for a given instance. When a custom - decomposition does not exist, the code now raises a custom `NoDecompositionError` - instead of `NotImplementedError`. - [(#2024)](https://github.com/PennyLaneAI/pennylane/pull/2024) - -* The `diagonalizing_gates()` representation has been moved to the highest-level - `Operator` class and is therefore available to all subclasses. A condition - `qml.operation.defines_diagonalizing_gates` has been added, which can be used - in tape contexts without queueing. - [(#1985)](https://github.com/PennyLaneAI/pennylane/pull/1985) - -* A static `compute_diagonalizing_gates` method has been added, which is called - by default in `diagonalizing_gates()`. - [(#1993)](https://github.com/PennyLaneAI/pennylane/pull/1993) - -* A `hyperparameters` attribute was added to the operator class. - [(#2017)](https://github.com/PennyLaneAI/pennylane/pull/2017) - -* The representation of an operator as a matrix has been overhauled. - - The `matrix()` method now accepts a - `wire_order` argument and calculates the correct numerical representation - with respect to that ordering. - - ```pycon - >>> op = qml.RX(0.5, wires="b") - >>> op.matrix() - [[0.96891242+0.j 0. -0.24740396j] - [0. -0.24740396j 0.96891242+0.j ]] - >>> op.matrix(wire_order=["a", "b"]) - [[0.9689+0.j 0.-0.2474j 0.+0.j 0.+0.j] - [0.-0.2474j 0.9689+0.j 0.+0.j 0.+0.j] - [0.+0.j 0.+0.j 0.9689+0.j 0.-0.2474j] - [0.+0.j 0.+0.j 0.-0.2474j 0.9689+0.j]] - ``` - - The "canonical matrix", which is independent of wires, - is now defined in the static method `compute_matrix()` instead of `_matrix`. - By default, this method is assumed to take all parameters and non-trainable - hyperparameters that define the operation. - - ```pycon - >>> qml.RX.compute_matrix(0.5) - [[0.96891242+0.j 0. -0.24740396j] - [0. -0.24740396j 0.96891242+0.j ]] - ``` - - If no canonical matrix is specified for a gate, `compute_matrix()` - raises a `NotImplementedError`. - - The new `matrix()` method is now used in the - `pennylane.transforms.get_qubit_unitary()` transform. - [(#1996)](https://github.com/PennyLaneAI/pennylane/pull/1996) - -* The `string_for_inverse` attribute is removed. - [(#2021)](https://github.com/PennyLaneAI/pennylane/pull/2021) - -* A `terms()` method and a `compute_terms()` static method were added to `Operator`. - Currently, only the `Hamiltonian` class overwrites `compute_terms` to store - coefficients and operators. The `Hamiltonian.terms` property hence becomes - a proper method called by `Hamiltonian.terms()`. - -* The generator property has been updated to an instance method, - `Operator.generator()`. It now returns an instantiated operation, - representing the generator of the instantiated operator. - [(#2030)](https://github.com/PennyLaneAI/pennylane/pull/2030) - [(#2061)](https://github.com/PennyLaneAI/pennylane/pull/2061) - - Various operators have been updated to specify the generator as either - an `Observable`, `Tensor`, `Hamiltonian`, `SparseHamiltonian`, or `Hermitian` - operator. - - In addition, a temporary utility function get_generator has been added - to the utils module, to automate: - - - Extracting the matrix representation - - Extracting the 'coefficient' if possible (only occurs if the generator is a single Pauli word) - - Converting a Hamiltonian to a sparse matrix if there are more than 1 Pauli word present. - - Negating the coefficient/taking the adjoint of the matrix if the operation was inverted - - This utility logic is currently needed because: - - - Extracting the matrix representation is not supported natively on - Hamiltonians and SparseHamiltonians. - - By default, calling `op.generator()` does not take into account `op.inverse()`. - - If the generator is a single Pauli word, it is convenient to have access to - both the coefficient and the observable separately. - -* Decompositions are now defined in `compute_decomposition`, instead of `expand`. - [(#2053)](https://github.com/PennyLaneAI/pennylane/pull/2053) - -* The `expand` method was moved to the main `Operator` class. - [(#2053)](https://github.com/PennyLaneAI/pennylane/pull/2053) - -* A `sparse_matrix` method and a `compute_sparse_matrix` static method were added - to the `Operator` class. The sparse representation of `SparseHamiltonian` - is moved to this method, so that its `matrix` method now returns a dense matrix. - [(#2050)](https://github.com/PennyLaneAI/pennylane/pull/2050) - -* The argument `wires` in `heisenberg_obs`, `heisenberg_expand` and `heisenberg_tr` - was renamed to `wire_order` to be consistent with other matrix representations. - [(#2051)](https://github.com/PennyLaneAI/pennylane/pull/2051) - -* The property `kraus_matrices` has been changed to a method, and `_kraus_matrices` renamed to - `compute_kraus_matrices`, which is now a static method. - [(#2055)](https://github.com/PennyLaneAI/pennylane/pull/2055) - -* The developer guide on adding templates and the architecture overview were rewritten - to reflect the past and planned changes of the operator refactor. - [(#2066)](https://github.com/PennyLaneAI/pennylane/pull/2066) - -* Custom errors subclassing ``OperatorPropertyUndefined`` are raised if a representation - has not been defined. This replaces the ``NotImplementedError`` and allows finer control - for developers. - [(#2064)](https://github.com/PennyLaneAI/pennylane/pull/2064) +* Docstring examples now display using the updated text-based circuit drawer. + [(#2252)](https://github.com/PennyLaneAI/pennylane/pull/2252) -* Moved ``expand()`` from ``Operation`` to ``Operator``. - [(#2239)](https://github.com/PennyLaneAI/pennylane/pull/2239) +* Add docstring to `OrbitalRotation.grad_recipe`. + [(#2193)](https://github.com/PennyLaneAI/pennylane/pull/2193)

Contributors

This release contains contributions from (in alphabetical order): -Sam Banning, Thomas Bromley, Olivia Di Matteo, Anthony Hayes, David Ittah, Josh Izaac, Christina Lee, Angus Lowe, -Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Maria Schuld, Jay Soni, -Antal SzΓ‘va, David Wierichs +Catalina Albornoz, Jack Y. Araz, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, +Sam Banning, Thomas Bromley, Olivia Di Matteo, Christian Gogolin, Diego Guala, +Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, +Christina Lee, Angus Lowe, Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Lee +James O'Riordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal SzΓ‘va, David +Wierichs. diff --git a/pennylane/debugging.py b/pennylane/debugging.py index 11cfa5b0522..060479c237e 100644 --- a/pennylane/debugging.py +++ b/pennylane/debugging.py @@ -28,7 +28,7 @@ class _Debugger: """ def __init__(self, dev): - if dev.short_name == "lightning.qubit" or not hasattr(dev, "_debugger"): + if "Snapshot" not in dev.operations: raise DeviceError("Device does not support snapshots.") self.snapshots = {} diff --git a/pennylane/drawer/tape_text.py b/pennylane/drawer/tape_text.py index 9a01852b302..0354c2320bb 100644 --- a/pennylane/drawer/tape_text.py +++ b/pennylane/drawer/tape_text.py @@ -128,7 +128,7 @@ def tape_text( qml.var(qml.PauliZ(0) @ qml.PauliZ(1)) qml.probs(wires=(0, 1, 2, "aux")) - >>> print(tape_text(tape)) + >>> print(qml.drawer.tape_text(tape)) 0: ─╭QFT──RX─╭C── β•­Var[Z@Z] β•­Probs 1: β”€β”œQFT──RYβ”€β”œC── β•°Var[Z@Z] β”œProbs 2: ─╰QFT──RZ─│─── β”œProbs @@ -139,7 +139,7 @@ def tape_text( By default, parameters are omitted. By specifying the ``decimals`` keyword, parameters are displayed to the specified precision. Matrix-valued parameters are never displayed. - >>> print(tape_text(tape, decimals=2)) + >>> print(qml.drawer.tape_text(tape, decimals=2)) 0: ─╭QFT──RX(1.23)─╭C── β•­Var[Z@Z] β•­Probs 1: β”€β”œQFT──RY(1.23)β”€β”œC── β•°Var[Z@Z] β”œProbs 2: ─╰QFT──RZ(1.23)─│─── β”œProbs @@ -154,7 +154,7 @@ def tape_text( shape = qml.StronglyEntanglingLayers.shape(n_wires=5, n_layers=5) params = rng.random(shape) tape2 = qml.StronglyEntanglingLayers(params, wires=range(5)).expand() - print(tape_text(tape2, max_length=60)) + print(qml.drawer.tape_text(tape2, max_length=60)) .. code-block:: none @@ -175,7 +175,7 @@ def tape_text( The ``wire_order`` keyword specifies the order of the wires from top to bottom: - >>> print(tape_text(tape, wire_order=["aux", 2, 1, 0])) + >>> print(qml.drawer.tape_text(tape, wire_order=["aux", 2, 1, 0])) aux: ──────────╭X── β•­Probs 2: ─╭QFT──RZ─│─── β”œProbs 1: β”€β”œQFT──RYβ”€β”œC── β•­Var[Z@Z] β”œProbs @@ -183,7 +183,7 @@ def tape_text( If the wire order contains empty wires, they are only shown if the ``show_all_wires=True``. - >>> print(tape_text(tape, wire_order=["a", "b", "aux", 0, 1, 2], show_all_wires=True)) + >>> print(qml.drawer.tape_text(tape, wire_order=["a", "b", "aux", 0, 1, 2], show_all_wires=True)) a: ────────────── b: ────────────── aux: ──────────╭X── β•­Probs @@ -202,7 +202,7 @@ def tape_text( qml.QubitUnitary(np.eye(2), wires=1) qml.expval(qml.Hermitian(np.eye(4), wires=(0,1))) - >>> print(tape_text(tape, show_matrices=True)) + >>> print(qml.drawer.tape_text(tape, show_matrices=True)) 0: ──U(M0)── β•­<𝓗(M1)> 1: ──U(M0)── β•°<𝓗(M1)> M0 = @@ -219,7 +219,7 @@ def tape_text( tape offset. >>> cache = {'matrices': [-np.eye(3)]} - >>> print(tape_text(tape, show_matrices=True, cache=cache)) + >>> print(qml.drawer.tape_text(tape, show_matrices=True, cache=cache)) 0: ──U(M1)── β•­<𝓗(M2)> 1: ──U(M1)── β•°<𝓗(M2)> M0 = @@ -255,7 +255,7 @@ def tape_text( qml.PauliX(0) cache = {'tape_offset': 3} - print(tape_text(tape, cache=cache)) + print(qml.drawer.tape_text(tape, cache=cache)) print("New tape offset: ", cache['tape_offset']) diff --git a/pennylane/gradients/__init__.py b/pennylane/gradients/__init__.py index 38a0d458105..afecc469413 100644 --- a/pennylane/gradients/__init__.py +++ b/pennylane/gradients/__init__.py @@ -238,12 +238,12 @@ def circuit(weights): >>> gradient_tapes, fn = qml.gradients.param_shift(tape) >>> gradient_tapes -[, - , - , - , - , - ] +[, + , + , + , + , + ] This can be useful if the underlying circuits representing the gradient computation need to be analyzed. diff --git a/pennylane/gradients/finite_difference.py b/pennylane/gradients/finite_difference.py index 79ce33385df..1e36c043b53 100644 --- a/pennylane/gradients/finite_difference.py +++ b/pennylane/gradients/finite_difference.py @@ -28,6 +28,7 @@ gradient_transform, grad_method_validation, choose_grad_methods, + gradient_analysis, ) @@ -272,7 +273,7 @@ def finite_diff( device evaluation. Instead, the processed tapes, and post-processing function, which together define the gradient are directly returned: - >>> with qml.tape.JacobianTape() as tape: + >>> with qml.tape.QuantumTape() as tape: ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) @@ -280,10 +281,10 @@ def finite_diff( ... qml.var(qml.PauliZ(0)) >>> gradient_tapes, fn = qml.gradients.finite_diff(tape) >>> gradient_tapes - [, - , - , - ] + [, + , + , + ] This can be useful if the underlying circuits representing the gradient computation need to be analyzed. @@ -306,7 +307,7 @@ def finite_diff( if validate_params: if "grad_method" not in tape._par_info[0]: - tape._update_gradient_info() + gradient_analysis(tape, grad_fn=finite_diff) diff_methods = grad_method_validation("numeric", tape) else: diff_methods = ["F" for i in tape.trainable_params] diff --git a/pennylane/gradients/gradient_transform.py b/pennylane/gradients/gradient_transform.py index ff59483cb18..eec0501cf5d 100644 --- a/pennylane/gradients/gradient_transform.py +++ b/pennylane/gradients/gradient_transform.py @@ -20,6 +20,60 @@ from pennylane.transforms.tape_expand import expand_invalid_trainable +def gradient_analysis(tape, use_graph=True, grad_fn=None): + """Update the parameter information dictionary of the tape with + gradient information of each parameter. + + Parameter gradient methods include: + + * ``None``: the parameter does not support differentiation. + + * ``"0"``: the variational circuit output does not depend on this + parameter (the partial derivative is zero). + + In addition, the operator might define its own grad method + via :attr:`.Operator.grad_method`. + + Note that this function modifies the input tape in-place. + + Args: + tape (.QuantumTape): the quantum tape to analyze + use_graph (bool): whether to use a directed-acyclic graph to determine + if the parameter has a gradient of 0 + grad_fn (None or callable): The gradient transform performing the analysis. + This is an optional argument; if provided, and the tape has already + been analyzed for the gradient information by the same gradient transform, + the cached gradient analysis will be used. + """ + # pylint:disable=protected-access + if grad_fn is not None and getattr(tape, "_gradient_fn", None) is grad_fn: + # gradient analysis has already been performed on this tape + return + + if grad_fn is not None: + tape._gradient_fn = grad_fn + + for idx, info in tape._par_info.items(): + + if idx not in tape.trainable_params: + # non-trainable parameters do not require a grad_method + info["grad_method"] = None + else: + op = tape._par_info[idx]["op"] + + if not qml.operation.has_grad_method(op): + # no differentiation method is registered for this operation + info["grad_method"] = None + + elif (tape._graph is not None) or use_graph: + if not any(tape.graph.has_path(op, ob) for ob in tape.observables): + # there is no influence of this operation on any of the observables + info["grad_method"] = "0" + continue + + info["grad_method"] = op.grad_method + + def grad_method_validation(method, tape): """Validates if the gradient method requested is supported by the trainable parameters of a tape, and returns the allowed parameter gradient methods. @@ -39,7 +93,7 @@ def grad_method_validation(method, tape): Args: method (str): the overall Jacobian differentiation method - tape (.JacobianTape): the tape with associated parameter information + tape (.QuantumTape): the tape with associated parameter information Returns: tuple[str, None]: the allowed parameter gradient methods for each trainable parameter diff --git a/pennylane/gradients/param_shift_hessian.py b/pennylane/gradients/param_shift_hessian.py index 145485f2ab9..26bf6e257f0 100644 --- a/pennylane/gradients/param_shift_hessian.py +++ b/pennylane/gradients/param_shift_hessian.py @@ -20,8 +20,7 @@ import pennylane as qml -from .parameter_shift import _gradient_analysis -from .gradient_transform import grad_method_validation +from .gradient_transform import gradient_analysis, grad_method_validation from .hessian_transform import hessian_transform @@ -265,13 +264,13 @@ def param_shift_hessian(tape, f0=None): >>> tape = circuit.qtape >>> hessian_tapes, postproc_fn = qml.gradients.param_shift_hessian(tape) >>> hessian_tapes - [, - , - , - , - , - , - ] + [, + , + , + , + , + , + ] >>> postproc_fn(qml.execute(hessian_tapes, dev, None)) array([[-0.97517033, 0.01983384], [ 0.01983384, -0.97517033]]) @@ -340,7 +339,7 @@ def param_shift_hessian(tape, f0=None): f"Hessian. Only two-term parameter shift rules are currently supported." ) - _gradient_analysis(tape) + gradient_analysis(tape, grad_fn=qml.gradients.param_shift) diff_methods = grad_method_validation("analytic", tape) if all(g == "0" for g in diff_methods): diff --git a/pennylane/gradients/parameter_shift.py b/pennylane/gradients/parameter_shift.py index 71846b438b9..b08988a20b1 100644 --- a/pennylane/gradients/parameter_shift.py +++ b/pennylane/gradients/parameter_shift.py @@ -25,6 +25,7 @@ gradient_transform, grad_method_validation, choose_grad_methods, + gradient_analysis, ) from .finite_difference import finite_diff, generate_shifted_tapes from .general_shift_rules import process_shifts @@ -109,37 +110,6 @@ def _get_operation_recipe(tape, t_idx, shifts): return coeffs, mults, shifts -def _gradient_analysis(tape, use_graph=True): - """Update the parameter information dictionary of the tape with - gradient information of each parameter.""" - - if getattr(tape, "_gradient_fn", None) is param_shift: - # gradient analysis has already been performed on this tape - return - - tape._gradient_fn = param_shift - - for idx, info in tape._par_info.items(): - - if idx not in tape.trainable_params: - # non-trainable parameters do not require a grad_method - info["grad_method"] = None - else: - op = tape._par_info[idx]["op"] - - if not qml.operation.has_grad_method(op): - # no differentiation method is registered for this operation - info["grad_method"] = None - - elif (tape._graph is not None) or use_graph: - if not any(tape.graph.has_path(op, ob) for ob in tape.observables): - # there is no influence of this operation on any of the observables - info["grad_method"] = "0" - continue - - info["grad_method"] = op.grad_method - - def expval_param_shift(tape, argnum=None, shifts=None, gradient_recipes=None, f0=None): r"""Generate the parameter-shift tapes and postprocessing methods required to compute the gradient of a gate parameter with respect to an @@ -573,7 +543,7 @@ def param_shift( device evaluation. Instead, the processed tapes, and post-processing function, which together define the gradient are directly returned: - >>> with qml.tape.JacobianTape() as tape: + >>> with qml.tape.QuantumTape() as tape: ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) @@ -581,12 +551,12 @@ def param_shift( ... qml.var(qml.PauliZ(0)) >>> gradient_tapes, fn = qml.gradients.param_shift(tape) >>> gradient_tapes - [, - , - , - , - , - ] + [, + , + , + , + , + ] This can be useful if the underlying circuits representing the gradient computation need to be analyzed. @@ -613,7 +583,7 @@ def param_shift( ) return [], lambda _: () - _gradient_analysis(tape) + gradient_analysis(tape, grad_fn=param_shift) method = "analytic" if fallback_fn is None else "best" diff_methods = grad_method_validation(method, tape) diff --git a/pennylane/gradients/parameter_shift_cv.py b/pennylane/gradients/parameter_shift_cv.py index bee9fb41401..b8808da771d 100644 --- a/pennylane/gradients/parameter_shift_cv.py +++ b/pennylane/gradients/parameter_shift_cv.py @@ -593,7 +593,7 @@ def circuit(weights): to use during autodifferentiation: >>> dev = qml.device("default.gaussian", wires=2) - >>> @qml.qnode(dev, gradient_fn=qml.gradients.param_shift_cv) + >>> @qml.qnode(dev, diff_method="parameter-shift") ... def circuit(params): ... qml.Squeezing(params[0], params[1], wires=[0]) ... qml.Squeezing(params[2], params[3], wires=[0]) @@ -621,16 +621,16 @@ def circuit(weights): function, which together define the gradient are directly returned: >>> r0, phi0, r1, phi1 = [0.4, -0.3, -0.7, 0.2] - >>> with qml.tape.JacobianTape() as tape: + >>> with qml.tape.QuantumTape() as tape: ... qml.Squeezing(r0, phi0, wires=[0]) ... qml.Squeezing(r1, phi1, wires=[0]) ... qml.expval(qml.NumberOperator(0)) # second-order >>> gradient_tapes, fn = qml.gradients.param_shift_cv(tape, dev) >>> gradient_tapes - [, - , - , - ] + [, + , + , + ] This can be useful if the underlying circuits representing the gradient computation need to be analyzed. diff --git a/pennylane/gradients/vjp.py b/pennylane/gradients/vjp.py index 8164c13283f..9fee8fd5233 100644 --- a/pennylane/gradients/vjp.py +++ b/pennylane/gradients/vjp.py @@ -112,17 +112,16 @@ def vjp(tape, dy, gradient_fn, gradient_kwargs=None): **Example** - Consider the following Torch-compatible quantum tape: + Consider the following quantum tape with PyTorch parameters: .. code-block:: python import torch - from pennylane.interfaces.torch import TorchInterface x = torch.tensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], requires_grad=True, dtype=torch.float64) - with TorchInterface.apply(qml.tape.JacobianTape()) as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0, 0], wires=0) qml.RY(x[0, 1], wires=1) qml.RZ(x[0, 2], wires=0) @@ -145,10 +144,10 @@ def vjp(tape, dy, gradient_fn, gradient_kwargs=None): Executing the VJP tapes, and applying the processing function: >>> dev = qml.device("default.qubit", wires=2) - >>> vjp = fn([t.execute(dev) for t in vjp_tapes]) + >>> vjp = fn(qml.execute(vjp_tapes, dev, gradient_fn=qml.gradients.param_shift, interface="torch")) >>> vjp - tensor([-0.6069, -0.0451, 0.0451, -0.0139, -0.2809, 0.2809], - dtype=torch.float64, grad_fn=) + tensor([-1.1562e-01, -1.3862e-02, -9.0841e-03, -1.3878e-16, -4.8217e-01, + 2.1329e-17], dtype=torch.float64, grad_fn=) The output VJP is also differentiable with respect to the tape parameters: @@ -156,7 +155,7 @@ def vjp(tape, dy, gradient_fn, gradient_kwargs=None): >>> cost.backward() >>> x.grad tensor([[-1.1025e+00, -2.0554e-01, -1.4917e-01], - [-1.9429e-09, -9.1580e-01, 1.3878e-09]], dtype=torch.float64) + [-1.2490e-16, -9.1580e-01, 0.0000e+00]], dtype=torch.float64) """ gradient_kwargs = gradient_kwargs or {} num_params = len(tape.trainable_params) @@ -259,12 +258,12 @@ def ansatz(x): qml.RY(x[1, 1], wires=0) qml.RZ(x[1, 2], wires=1) - with TorchInterface.apply(qml.tape.JacobianTape()) as tape1: + with qml.tape.QuantumTape() as tape1: ansatz(x) qml.expval(qml.PauliZ(0)) qml.probs(wires=1) - with TorchInterface.apply(qml.tape.JacobianTape()) as tape2: + with qml.tape.QuantumTape() as tape2: ansatz(x) qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) @@ -286,12 +285,12 @@ def ansatz(x): Executing the VJP tapes, and applying the processing function: >>> dev = qml.device("default.qubit", wires=2) - >>> vjps = fn([t.execute(dev) for t in vjp_tapes]) + >>> vjps = fn(qml.execute(vjp_tapes, dev, gradient_fn=qml.gradients.param_shift, interface="torch")) >>> vjps - [tensor([-0.6069, -0.0451, 0.0451, -0.0139, -0.2809, 0.2809], - dtype=torch.float64, grad_fn=), - tensor([ 0.1739, -0.1641, -0.0054, -0.2937, -0.4008, 0.0000], - dtype=torch.float64, grad_fn=)] + [tensor([-1.1562e-01, -1.3862e-02, -9.0841e-03, -1.3878e-16, -4.8217e-01, + 2.1329e-17], dtype=torch.float64, grad_fn=), + tensor([ 1.7393e-01, -1.6412e-01, -5.3983e-03, -2.9366e-01, -4.0083e-01, + 2.1134e-17], dtype=torch.float64, grad_fn=)] We have two VJPs; one per tape. Each one corresponds to the number of parameters on the tapes (6). @@ -301,8 +300,8 @@ def ansatz(x): >>> cost = torch.sum(vjps[0] + vjps[1]) >>> cost.backward() >>> x.grad - tensor([[-4.7924e-01, -9.0857e-01, -2.4198e-01], - [-9.2973e-02, -1.0772e+00, 4.7184e-09]], dtype=torch.float64) + tensor([[-0.4792, -0.9086, -0.2420], + [-0.0930, -1.0772, 0.0000]], dtype=torch.float64) """ gradient_kwargs = gradient_kwargs or {} reshape_info = [] diff --git a/pennylane/interfaces/batch/__init__.py b/pennylane/interfaces/batch/__init__.py index e9ee5bc1042..6e09b200f40 100644 --- a/pennylane/interfaces/batch/__init__.py +++ b/pennylane/interfaces/batch/__init__.py @@ -263,12 +263,12 @@ def execute( dev = qml.device("lightning.qubit", wires=2) def cost_fn(params, x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(params[2], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -277,7 +277,7 @@ def cost_fn(params, x): tapes = [tape1, tape2] # execute both tapes in a batch on the given device - res = execute(tapes, dev, qml.gradients.param_shift) + res = qml.execute(tapes, dev, qml.gradients.param_shift, max_diff=2) return res[0][0] + res[1][0, 0] - res[1][0, 1] @@ -291,7 +291,7 @@ def cost_fn(params, x): >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) >>> x = np.array([0.5], requires_grad=True) >>> cost_fn(params, x) - 1.9305068163274222 + tensor(1.93050682, requires_grad=True) Since the ``execute`` function is differentiable, we can also compute the gradient: diff --git a/pennylane/measurements.py b/pennylane/measurements.py index c4358b0a095..35d78addd37 100644 --- a/pennylane/measurements.py +++ b/pennylane/measurements.py @@ -193,7 +193,7 @@ def expand(self): rotation and a measurement in the computational basis. Returns: - .JacobianTape: a quantum tape containing the operations + .QuantumTape: a quantum tape containing the operations required to diagonalize the observable **Example** @@ -224,9 +224,7 @@ def expand(self): if self.obs is None: raise qml.operation.DecompositionUndefinedError - from pennylane.tape import JacobianTape # pylint: disable=import-outside-toplevel - - with JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: self.obs.diagonalizing_gates() MeasurementProcess( self.return_type, wires=self.obs.wires, eigvals=self.obs.get_eigvals() @@ -636,17 +634,25 @@ def branches(self): return branch_dict def __invert__(self): - """Inverts the control value of the measurement.""" + """Return a copy of the measurement value with an inverted control + value.""" + inverted_self = copy.copy(self) zero = self._zero_case one = self._one_case - self._control_value = one if self._control_value == zero else zero + inverted_self._control_value = one if self._control_value == zero else zero - return self + return inverted_self def __eq__(self, control_value): """Allow asserting measurement values.""" measurement_outcomes = {self._zero_case, self._one_case} + + if not isinstance(control_value, tuple(type(val) for val in measurement_outcomes)): + raise MeasurementValueError( + f"The equality operator is used to assert measurement outcomes, but got a value with type {type(control_value)}." + ) + if control_value not in measurement_outcomes: raise MeasurementValueError( f"Unknown measurement value asserted; the set of possible measurement outcomes is: {measurement_outcomes}." @@ -698,7 +704,7 @@ def func(x, y): tensor([0.90165331, 0.09834669], requires_grad=True) Args: - wires (Wires): The wires the measurement process applies to. + wires (Wires): The wire of the qubit the measurement process applies to. Raises: QuantumFunctionError: if multiple wires were specified diff --git a/pennylane/operation.py b/pennylane/operation.py index 05702a47bdf..c56c563db20 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -137,7 +137,7 @@ def expand_matrix(base_matrix, wires, wire_order): ... [5, 6, 7, 8], ... [9, 10, 11, 12], ... [13, 14, 15, 16]]) - >>> expand_matrix(base_matrix, wires=[0, 2], wire_order=[0, 2]) + >>> print(expand_matrix(base_matrix, wires=[0, 2], wire_order=[0, 2])) [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12] @@ -145,7 +145,7 @@ def expand_matrix(base_matrix, wires, wire_order): If the wire order is a permutation of ``wires``, the entries of the base matrix get permuted: - >>> expand_matrix(base_matrix, wires=[0, 2], wire_order=[2, 0]) + >>> print(expand_matrix(base_matrix, wires=[0, 2], wire_order=[2, 0])) [[ 1 3 2 4] [ 9 11 10 12] [ 5 7 6 8] @@ -153,7 +153,7 @@ def expand_matrix(base_matrix, wires, wire_order): If the wire order contains wire labels not found in ``wires``, the matrix gets expanded: - >>> expand_matrix(base_matrix, wires=[0, 2], wire_order=[0, 1, 2]) + >>> print(expand_matrix(base_matrix, wires=[0, 2], wire_order=[0, 1, 2])) [[ 1 2 0 0 3 4 0 0] [ 5 6 0 0 7 8 0 0] [ 0 0 1 2 0 0 3 4] @@ -169,7 +169,7 @@ def expand_matrix(base_matrix, wires, wire_order): ... [3., 4.]], requires_grad=True) >>> res = expand_matrix(base_matrix_torch, wires=["b"], wire_order=["a", "b"]) >>> type(res) - + torch.Tensor >>> res.requires_grad True """ @@ -561,7 +561,7 @@ def compute_matrix(*params, **hyperparams): # pylint:disable=unused-argument The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. - .. seealso:: :meth:`~.CNOT.matrix` + .. seealso:: :meth:`~.Operator.get_matrix` and :func:`~.matrix` Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -633,7 +633,7 @@ def compute_sparse_matrix(*params, **hyperparams): # pylint:disable=unused-argu The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. - .. seealso:: :meth:`~.SparseHamiltonian.sparse_matrix` + .. seealso:: :meth:`~.Operator.sparse_matrix` Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -657,7 +657,7 @@ def sparse_matrix(self, wire_order=None): A ``SparseMatrixUndefinedError`` is raised if the sparse matrix representation has not been defined. - .. seealso:: :meth:`~.SparseHamiltonian.compute_sparse_matrix` + .. seealso:: :meth:`~.Operator.compute_sparse_matrix` Args: wire_order (Iterable): global wire order, must contain all wire labels from the operator's wires @@ -686,7 +686,7 @@ def compute_eigvals(*params, **hyperparams): Otherwise, no particular order for the eigenvalues is guaranteed. - .. seealso:: :meth:`~.RZ.eigvals` + .. seealso:: :meth:`~.Operator.get_eigvals` and :func:`~.eigvals` Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -765,7 +765,7 @@ def compute_terms(*params, **hyperparams): # pylint: disable=unused-argument .. math:: O = \sum_i c_i O_i - .. seealso:: :meth:`~.Hamiltonian.terms` + .. seealso:: :meth:`~.Operator.terms` Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -784,7 +784,7 @@ def terms(self): A ``TermsUndefinedError`` is raised if no representation by terms is defined. - .. seealso:: :meth:`~.Hamiltonian.compute_terms` + .. seealso:: :meth:`~.Operator.compute_terms` Returns: tuple[list[tensor_like or float], list[.Operation]]: list of coefficients :math:`c_i` @@ -993,7 +993,7 @@ def decomposition(self): A ``DecompositionUndefinedError`` is raised if no representation by decomposition is defined. - .. seealso:: :meth:`~.operation.Operator.compute_decomposition`. + .. seealso:: :meth:`~.Operator.compute_decomposition`. Returns: list[Operator]: decomposition of the operator @@ -1008,7 +1008,7 @@ def compute_decomposition(*params, wires=None, **hyperparameters): .. math:: O = O_1 O_2 \dots O_n. - .. seealso:: :meth:`~.operation.Operator.decomposition`. + .. seealso:: :meth:`~.Operator.decomposition`. Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -1033,7 +1033,7 @@ def compute_diagonalizing_gates( The diagonalizing gates rotate the state into the eigenbasis of the operator. - .. seealso:: :meth:`~.PauliX.diagonalizing_gates`. + .. seealso:: :meth:`~.Operator.diagonalizing_gates`. Args: params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute @@ -1098,7 +1098,7 @@ def expand(self): """Returns a tape that has recorded the decomposition of the operator. Returns: - .JacobianTape: quantum tape + .QuantumTape: quantum tape """ tape = qml.tape.QuantumTape(do_queue=False) @@ -1291,9 +1291,10 @@ def parameter_frequencies(self): >>> op = qml.ControlledPhaseShift(0.1, wires=[0, 1]) >>> op.parameter_frequencies [(1,)] - >>> gen_eigvals = tuple(np.linalg.eigvals(op.generator[0] * op.generator[1])) - >>> qml.gradients.eigvals_to_frequencies(gen_eigvals) - (tensor(1., requires_grad=True),) + >>> gen = qml.generator(op, format="observable") + >>> gen_eigvals = qml.eigvals(gen) + >>> qml.gradients.eigvals_to_frequencies(tuple(gen_eigvals)) + (1.0,) For more details on this relationship, see :func:`.eigvals_to_frequencies`. """ diff --git a/pennylane/ops/functions/generator.py b/pennylane/ops/functions/generator.py index 9be458db502..431c8a4f556 100644 --- a/pennylane/ops/functions/generator.py +++ b/pennylane/ops/functions/generator.py @@ -15,6 +15,11 @@ This module contains the qml.generator function. """ # pylint: disable=protected-access +import inspect +import warnings + +import numpy as np + import pennylane as qml @@ -96,6 +101,28 @@ def _generator_prefactor(gen, op): return obs, prefactor +def _generator_backcompatibility(op): + r"""Preserve backwards compatibility behaviour for PennyLane + versions <=0.22, where generators returned List[type or ndarray, float]. + This function raises a deprecation warning, and converts to the new + format where an instantiated Operator is returned.""" + warnings.warn( + "The Operator.generator property is deprecated. Please update the operator so that " + "\n\t1. Operator.generator() is a method, and" + "\n\t2. Operator.generator() returns an Operator instance representing the operator.", + UserWarning, + ) + gen = op.generator + + if inspect.isclass(gen[0]): + return gen[1] * gen[0](wires=op.wires) + + if isinstance(gen[0], np.ndarray) and len(gen[0].shape) == 2: + return gen[1] * qml.Hermitian(gen[0], wires=op.wires) + + raise qml.operation.GeneratorUndefinedError + + @qml.op_transform def generator(op, format="prefactor"): r"""Returns the generator of an operation. @@ -107,19 +134,16 @@ def generator(op, format="prefactor"): ``'observable'``, or ``'hamiltonian'``. See below for more details. Returns: - .Observable or tuple[float, .Observable]: The returned generator, with format/type + .Observable or tuple[.Observable, float]: The returned generator, with format/type dependent on the ``format`` argument. - * ``"prefactor"``: Return the generator as ```(obs, prefactor)`` (representing - :math:`G=p \hat{O}`), where + * ``"prefactor"``: Return the generator as ``(obs, prefactor)`` (representing + :math:`G=p \hat{O}`), where: - - observable `\hat{O}` is one of :class:`~.Hermitian`, + - observable :math:`\hat{O}` is one of :class:`~.Hermitian`, :class:`~.SparseHamiltonian`, or a tensor product of Pauli words. - - prefactor :math:`p` is a float - - The prefactor will in most cases be :math:`\pm 1.0`, unless the generator is a single Pauli - word, in which case the prefactor is the coefficient of the Pauli word. + - prefactor :math:`p` is a float. * ``"observable"``: Return the generator as a single observable as directly defined by ``op``. Returned generators may be any type of observable, including @@ -152,16 +176,22 @@ def generator(op, format="prefactor"): >>> op = qml.RX(0.2, wires=0) >>> qml.generator(op, format="prefactor") # output will always be (prefactor, obs) - (Projector([1], wires=[0]), 1.0) + (PauliX(wires=[0]), -0.5) >>> qml.generator(op, format="hamiltonian") # output will always be a Hamiltonian - >>> qml.generator(op, format="observable") # ouput will be a simplified obs where possible + >>> qml.generator(qml.PhaseShift(0.1, wires=0), format="observable") # ouput will be a simplified obs where possible Projector([1], wires=[0]) + """ if op.num_params != 1: raise ValueError(f"Operation {op.name} is not written in terms of a single parameter") - gen = op.generator() + try: + gen = op.generator() + except TypeError: + # For backwards compatibility with PennyLane + # versions <=0.22, assume op.generator is a property + gen = _generator_backcompatibility(op) if not isinstance(gen, qml.operation.Observable): raise qml.QuantumFunctionError( diff --git a/pennylane/ops/functions/matrix.py b/pennylane/ops/functions/matrix.py index 4a623032ecf..58ace17bab2 100644 --- a/pennylane/ops/functions/matrix.py +++ b/pennylane/ops/functions/matrix.py @@ -46,7 +46,7 @@ def matrix(op, *, wire_order=None): >>> x = torch.tensor(0.6, requires_grad=True) >>> matrix_fn = qml.matrix(qml.RX) - >>> matrix_fn(x) + >>> matrix_fn(x, wires=0) tensor([[0.9553+0.0000j, 0.0000-0.2955j], [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=) diff --git a/pennylane/ops/identity.py b/pennylane/ops/identity.py index 9b5d8b4366a..a5483b70e2c 100644 --- a/pennylane/ops/identity.py +++ b/pennylane/ops/identity.py @@ -64,7 +64,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.Identity.compute_eigvals() + >>> print(qml.Identity.compute_eigvals()) [ 1 1] """ return np.array([1, 1]) @@ -83,7 +83,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.Identity.compute_matrix() + >>> print(qml.Identity.compute_matrix()) [[1. 0.] [0. 1.]] """ diff --git a/pennylane/ops/qubit/arithmetic_ops.py b/pennylane/ops/qubit/arithmetic_ops.py index b8284241578..a24d5991bd0 100644 --- a/pennylane/ops/qubit/arithmetic_ops.py +++ b/pennylane/ops/qubit/arithmetic_ops.py @@ -107,7 +107,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.QubitCarry.compute_matrix() + >>> print(qml.QubitCarry.compute_matrix()) [[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0] @@ -254,7 +254,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.QubitSum.compute_matrix() + >>> print(qml.QubitSum.compute_matrix()) [[1 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0] [0 0 0 1 0 0 0 0] diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py index 19f04cd99bc..53c0b6fc269 100644 --- a/pennylane/ops/qubit/matrix_ops.py +++ b/pennylane/ops/qubit/matrix_ops.py @@ -20,7 +20,7 @@ import numpy as np import pennylane as qml -from pennylane.operation import AnyWires, Operation +from pennylane.operation import AnyWires, Operation, DecompositionUndefinedError from pennylane.wires import Wires @@ -241,6 +241,10 @@ def __init__( total_wires = control_wires + wires super().__init__(*params, wires=total_wires, do_queue=do_queue) + @staticmethod + def compute_decomposition(*params, wires=None, **hyperparameters): + raise DecompositionUndefinedError + @staticmethod def compute_matrix( U, control_wires, u_wires, control_values=None diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py index 56c16f48612..6e25e9c7f74 100644 --- a/pennylane/ops/qubit/non_parametric_ops.py +++ b/pennylane/ops/qubit/non_parametric_ops.py @@ -66,7 +66,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.Hadamard.compute_matrix() + >>> print(qml.Hadamard.compute_matrix()) [[ 0.70710678 0.70710678] [ 0.70710678 -0.70710678]] """ @@ -92,7 +92,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.Hadamard.compute_eigvals() + >>> print(qml.Hadamard.compute_eigvals()) [ 1 -1] """ return pauli_eigs(1) @@ -117,7 +117,7 @@ def compute_diagonalizing_gates(wires): **Example** - >>> qml.Hadamard.compute_diagonalizing_gates(wires=[0]) + >>> print(qml.Hadamard.compute_diagonalizing_gates(wires=[0])) [RY(-0.7853981633974483, wires=[0])] """ return [qml.RY(-np.pi / 4, wires=wires)] @@ -138,7 +138,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.Hadamard.compute_decomposition(0) + >>> print(qml.Hadamard.compute_decomposition(0)) [PhaseShift(1.5707963267948966, wires=[0]), RX(1.5707963267948966, wires=[0]), PhaseShift(1.5707963267948966, wires=[0])] @@ -199,7 +199,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliX.compute_matrix() + >>> print(qml.PauliX.compute_matrix()) [[0 1] [1 0]] """ @@ -225,7 +225,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliX.compute_eigvals() + >>> print(qml.PauliX.compute_eigvals()) [ 1 -1] """ return pauli_eigs(1) @@ -250,7 +250,7 @@ def compute_diagonalizing_gates(wires): **Example** - >>> qml.PauliX.compute_diagonalizing_gates(wires=[0]) + >>> print(qml.PauliX.compute_diagonalizing_gates(wires=[0])) [Hadamard(wires=[0])] """ return [Hadamard(wires=wires)] @@ -272,7 +272,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.PauliX.compute_decomposition(0) + >>> print(qml.PauliX.compute_decomposition(0)) [PhaseShift(1.5707963267948966, wires=[0]), RX(3.141592653589793, wires=[0]), PhaseShift(1.5707963267948966, wires=[0])] @@ -335,7 +335,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliY.compute_matrix() + >>> print(qml.PauliY.compute_matrix()) [[ 0.+0.j -0.-1.j] [ 0.+1.j 0.+0.j]] """ @@ -361,7 +361,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliY.compute_eigvals() + >>> print(qml.PauliY.compute_eigvals()) [ 1 -1] """ return pauli_eigs(1) @@ -386,7 +386,7 @@ def compute_diagonalizing_gates(wires): **Example** - >>> qml.PauliY.compute_diagonalizing_gates(wires=[0]) + >>> print(qml.PauliY.compute_diagonalizing_gates(wires=[0])) [PauliZ(wires=[0]), S(wires=[0]), Hadamard(wires=[0])] """ return [ @@ -411,7 +411,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.PauliY.compute_decomposition(0) + >>> print(qml.PauliY.compute_decomposition(0)) [PhaseShift(1.5707963267948966, wires=[0]), RY(3.141592653589793, wires=[0]), PhaseShift(1.5707963267948966, wires=[0])] @@ -472,7 +472,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliZ.compute_matrix() + >>> print(qml.PauliZ.compute_matrix()) [[ 1 0] [ 0 -1]] """ @@ -498,7 +498,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.PauliZ.compute_eigvals() + >>> print(qml.PauliZ.compute_eigvals()) [ 1 -1] """ return pauli_eigs(1) @@ -524,7 +524,7 @@ def compute_diagonalizing_gates(wires): # pylint: disable=unused-argument **Example** - >>> qml.PauliZ.compute_diagonalizing_gates(wires=[0]) + >>> print(qml.PauliZ.compute_diagonalizing_gates(wires=[0])) [] """ return [] @@ -545,7 +545,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.PauliZ.compute_decomposition(0) + >>> print(qml.PauliZ.compute_decomposition(0)) [PhaseShift(3.141592653589793, wires=[0])] """ @@ -599,7 +599,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.S.compute_matrix() + >>> print(qml.S.compute_matrix()) [[1.+0.j 0.+0.j] [0.+0.j 0.+1.j]] """ @@ -625,7 +625,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.S.compute_eigvals() + >>> print(qml.S.compute_eigvals()) [1.+0.j 0.+1.j] """ return np.array([1, 1j]) @@ -647,7 +647,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.S.compute_decomposition(0) + >>> print(qml.S.compute_decomposition(0)) [PhaseShift(1.5707963267948966, wires=[0])] """ @@ -700,7 +700,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.T.compute_matrix() + >>> print(qml.T.compute_matrix()) [[1.+0.j 0. +0.j ] [0.+0.j 0.70710678+0.70710678j]] """ @@ -726,7 +726,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.T.compute_eigvals() + >>> print(qml.T.compute_eigvals()) [1.+0.j 0.70710678+0.70710678j] """ return np.array([1, cmath.exp(1j * np.pi / 4)]) @@ -748,7 +748,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.T.compute_decomposition(0) + >>> print(qml.T.compute_decomposition(0)) [PhaseShift(0.7853981633974483, wires=[0])] """ @@ -801,7 +801,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.SX.compute_matrix() + >>> print(qml.SX.compute_matrix()) [[0.5+0.5j 0.5-0.5j] [0.5-0.5j 0.5+0.5j]] """ @@ -828,7 +828,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.SX.compute_eigvals() + >>> print(qml.SX.compute_eigvals()) [1.+0.j 0.+1.j] """ return np.array([1, 1j]) @@ -850,7 +850,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.SX.compute_decomposition(0) + >>> print(qml.SX.compute_decomposition(0)) [RZ(1.5707963267948966, wires=[0]), RY(1.5707963267948966, wires=[0]), RZ(-3.141592653589793, wires=[0]), @@ -920,7 +920,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.CNOT.compute_matrix() + >>> print(qml.CNOT.compute_matrix()) [[1 0 0 0] [0 1 0 0] [0 0 0 1] @@ -983,7 +983,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.CZ.compute_matrix() + >>> print(qml.CZ.compute_matrix()) [[ 1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] @@ -1012,7 +1012,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.CZ.compute_eigvals() + >>> print(qml.CZ.compute_eigvals()) [1, 1, 1, -1] """ return np.array([1, 1, 1, -1]) @@ -1070,7 +1070,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.CY.compute_matrix() + >>> print(qml.CY.compute_matrix()) [[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j] [ 0.+0.j 1.+0.j 0.+0.j 0.+0.j] [ 0.+0.j 0.+0.j 0.+0.j -0.-1.j] @@ -1103,7 +1103,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.CY.compute_decomposition(0) + >>> print(qml.CY.compute_decomposition(0)) [CRY(3.141592653589793, wires=[0, 1]), S(wires=[0])] """ @@ -1154,7 +1154,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.SWAP.compute_matrix() + >>> print(qml.SWAP.compute_matrix()) [[1 0 0 0] [0 0 1 0] [0 1 0 0] @@ -1179,7 +1179,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.SWAP.compute_decomposition((0,1)) + >>> print(qml.SWAP.compute_decomposition((0,1))) [CNOT(wires=[0, 1]), CNOT(wires=[1, 0]), CNOT(wires=[0, 1])] """ @@ -1234,7 +1234,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.ISWAP.compute_matrix() + >>> print(qml.ISWAP.compute_matrix()) [[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+1.j 0.+0.j] [0.+0.j 0.+1.j 0.+0.j 0.+0.j] @@ -1263,7 +1263,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.ISWAP.compute_eigvals() + >>> print(qml.ISWAP.compute_eigvals()) [1j, -1j, 1, 1] """ return np.array([1j, -1j, 1, 1]) @@ -1285,7 +1285,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.ISWAP.compute_decomposition((0,1)) + >>> print(qml.ISWAP.compute_decomposition((0,1))) [S(wires=[0]), S(wires=[1]), Hadamard(wires=[0]), @@ -1348,7 +1348,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.SISWAP.compute_matrix() + >>> print(qml.SISWAP.compute_matrix()) [[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.70710678+0.j 0.+0.70710678j 0.+0.j] [0.+0.j 0.+0.70710678j 0.70710678+0.j 0.+0.j] @@ -1384,7 +1384,7 @@ def compute_eigvals(): # pylint: disable=arguments-differ **Example** - >>> qml.SISWAP.compute_eigvals() + >>> print(qml.SISWAP.compute_eigvals()) [0.70710678+0.70710678j 0.70710678-0.70710678j 1.+0.j 1.+0.j] """ return np.array([INV_SQRT2 * (1 + 1j), INV_SQRT2 * (1 - 1j), 1, 1]) @@ -1406,7 +1406,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.SISWAP.compute_decomposition((0,1)) + >>> print(qml.SISWAP.compute_decomposition((0,1))) [SX(wires=[0]), RZ(1.5707963267948966, wires=[0]), CNOT(wires=[0, 1]), @@ -1493,7 +1493,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.CSWAP.compute_matrix() + >>> print(qml.CSWAP.compute_matrix()) [[1 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0] [0 0 1 0 0 0 0 0] @@ -1533,7 +1533,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.CSWAP.compute_decomposition((0,1,2)) + >>> print(qml.CSWAP.compute_decomposition((0,1,2))) [Toffoli(wires=[0, 2, 1]), Toffoli(wires=[0, 1, 2]), Toffoli(wires=[0, 2, 1])] """ @@ -1602,7 +1602,7 @@ def compute_matrix(): # pylint: disable=arguments-differ **Example** - >>> qml.Toffoli.compute_matrix() + >>> print(qml.Toffoli.compute_matrix()) [[1 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0] [0 0 1 0 0 0 0 0] @@ -1642,7 +1642,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.Toffoli.compute_decomposition((0,1,2)) + >>> print(qml.Toffoli.compute_decomposition((0,1,2))) [Hadamard(wires=[2]), CNOT(wires=[1, 2]), T.inv(wires=[2]), @@ -1812,12 +1812,12 @@ def compute_matrix( **Example** - >>> qml.MultiControlledX.compute_matrix([0], '1') + >>> print(qml.MultiControlledX.compute_matrix([0], '1')) [[1. 0. 0. 0.] [0. 1. 0. 0.] [0. 0. 0. 1.] [0. 0. 1. 0.]] - >>> qml.MultiControlledX.compute_matrix([1], '0') + >>> print(qml.MultiControlledX.compute_matrix([1], '0')) [[0. 1. 0. 0.] [1. 0. 0. 0.] [0. 0. 1. 0.] @@ -1875,7 +1875,7 @@ def compute_decomposition(wires=None, work_wires=None, control_values=None, **kw **Example:** - >>> qml.MultiControlledX.compute_decomposition(wires=[0,1,2,3],control_values="111", work_wires=qml.wires.Wires("aux")) + >>> print(qml.MultiControlledX.compute_decomposition(wires=[0,1,2,3],control_values="111", work_wires=qml.wires.Wires("aux"))) [Toffoli(wires=[2, 'aux', 3]), Toffoli(wires=[0, 1, 'aux']), Toffoli(wires=[2, 'aux', 3]), @@ -2041,7 +2041,7 @@ def compute_decomposition(wires, only_visual=False): # pylint: disable=unused-a **Example:** - >>> qml.Barrier.compute_decomposition(0) + >>> print(qml.Barrier.compute_decomposition(0)) [] """ @@ -2050,7 +2050,10 @@ def compute_decomposition(wires, only_visual=False): # pylint: disable=unused-a def label(self, decimals=None, base_label=None, cache=None): return "||" - def adjoint(self): + def _controlled(self, _): + return Barrier(wires=self.wires) + + def adjoint(self, do_queue=False): return Barrier(wires=self.wires) @@ -2089,7 +2092,7 @@ def compute_decomposition(wires): **Example:** - >>> qml.WireCut.compute_decomposition(0) + >>> print(qml.WireCut.compute_decomposition(0)) [] """ diff --git a/pennylane/ops/snapshot.py b/pennylane/ops/snapshot.py index c99840b3b99..bc838d058ef 100644 --- a/pennylane/ops/snapshot.py +++ b/pennylane/ops/snapshot.py @@ -15,8 +15,6 @@ This module contains the Snapshot (pseudo) operation that is common to both cv and qubit computing paradigms in PennyLane. """ -import numpy as np - from pennylane.operation import AnyWires, Operation @@ -35,6 +33,29 @@ class Snapshot(Operation): Args: tag (str or None): An optional custom tag for the snapshot, used to index it in the snapshots dictionary. + + **Example** + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev, interface=None) + def circuit(): + qml.Snapshot() + qml.Hadamard(wires=0) + qml.Snapshot("very_important_state") + qml.CNOT(wires=[0, 1]) + qml.Snapshot() + return qml.expval(qml.PauliX(0)) + + >>> qml.snapshots(circuit)() + {0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]), + 'very_important_state': array([0.70710678+0.j, 0.+0.j, 0.70710678+0.j, 0.+0.j]), + 2: array([0.70710678+0.j, 0.+0.j, 0.+0.j, 0.70710678+0.j]), + 'execution_results': array(0.)} + + .. seealso:: :func:`~.snapshots` """ num_wires = AnyWires num_params = 0 @@ -51,7 +72,8 @@ def label(self, decimals=None, base_label=None, cache=None): def compute_decomposition(*params, wires=None, **hyperparameters): return [] - # TODO: remove once pennylane-lightning#242 is resolved - @staticmethod - def compute_matrix(*params, **hyperparams): - return np.eye(2) + def _controlled(self, _): + return Snapshot(tag=self.tag) + + def adjoint(self, do_queue=False): + return Snapshot(tag=self.tag) diff --git a/pennylane/optimize/lie_algebra.py b/pennylane/optimize/lie_algebra.py index 9d2720dc572..8a21e5ee1eb 100644 --- a/pennylane/optimize/lie_algebra.py +++ b/pennylane/optimize/lie_algebra.py @@ -90,8 +90,8 @@ def algebra_commutator(tape, observables, lie_algebra_basis_names, nqubits): for obs in observables: for o in obs: # create a list of tapes for the plus and minus shifted circuits - tapes_plus = [qml.tape.JacobianTape(p + "_p") for p in lie_algebra_basis_names] - tapes_min = [qml.tape.JacobianTape(p + "_m") for p in lie_algebra_basis_names] + tapes_plus = [qml.tape.QuantumTape(p + "_p") for p in lie_algebra_basis_names] + tapes_min = [qml.tape.QuantumTape(p + "_m") for p in lie_algebra_basis_names] # loop through all operations on the input tape for op in tape.operations: diff --git a/pennylane/qnode.py b/pennylane/qnode.py index 57b5cdf9bbd..692402899d7 100644 --- a/pennylane/qnode.py +++ b/pennylane/qnode.py @@ -482,7 +482,7 @@ def tape(self): def construct(self, args, kwargs): """Call the quantum function with a tape context, ensuring the operations get queued.""" - self._tape = qml.tape.JacobianTape() + self._tape = qml.tape.QuantumTape() with self.tape: self._qfunc_output = self.func(*args, **kwargs) diff --git a/pennylane/qnode_old.py b/pennylane/qnode_old.py index 45196ac0a19..64a7755e597 100644 --- a/pennylane/qnode_old.py +++ b/pennylane/qnode_old.py @@ -40,6 +40,11 @@ class QNode: """Represents a quantum node in the hybrid computational graph. + .. warning:: + + This QNode is deprecated and due to be removed in an upcoming + release. Please use :class:`qml.QNode <.pennylane.QNode>` instead. + A *quantum node* contains a :ref:`quantum function ` (corresponding to a :ref:`variational circuit `) and the computational device it is executed on. @@ -163,6 +168,11 @@ def __init__( argnum=None, **kwargs, ): + warnings.warn( + "qml.qnode_old.QNode is deprecated, and will be removed in an " + "upcoming release. Please use qml.QNode instead.", + UserWarning, + ) if diff_method is None: # TODO: update this behaviour once the new differentiable pipeline is the default diff --git a/pennylane/tape/cv_param_shift.py b/pennylane/tape/cv_param_shift.py index eb5b4092d58..0794dd72066 100644 --- a/pennylane/tape/cv_param_shift.py +++ b/pennylane/tape/cv_param_shift.py @@ -33,6 +33,12 @@ class CVParamShiftTape(QubitParamShiftTape): r"""Quantum tape for CV parameter-shift analytic differentiation method. + .. warning:: + + The ``CVParamShiftTape`` is deprecated. + Please use a standard :class:`~.QuantumTape`, and apply gradient transforms using + the :mod:`.gradients` module to compute parameter-shift gradients. + This class extends the :class:`~.jacobian` method of the quantum tape to support analytic gradients of Gaussian CV operations using the parameter-shift rule. This gradient method returns *exact* gradients, and can be computed directly diff --git a/pennylane/tape/jacobian_tape.py b/pennylane/tape/jacobian_tape.py index 06b41b88664..3ce99ef2d01 100644 --- a/pennylane/tape/jacobian_tape.py +++ b/pennylane/tape/jacobian_tape.py @@ -51,6 +51,12 @@ class JacobianTape(QuantumTape): See :mod:`pennylane.tape` for more details. + .. warning:: + + The ``JacobianTape`` as well as the ``JacobianTape.jacobian()`` method is deprecated. + Please use a standard :class:`~.QuantumTape`, and apply gradient transforms using + the :mod:`.gradients` module to compute Jacobians. + Args: name (str): a name given to the quantum tape do_queue (bool): Whether to queue this tape in a parent tape context. @@ -439,6 +445,12 @@ def _choose_params_with_methods(diff_methods, argnum): def jacobian(self, device, params=None, **options): r"""Compute the Jacobian of the parametrized quantum circuit recorded by the quantum tape. + .. warning:: + + The ``JacobianTape`` as well as the ``JacobianTape.jacobian()`` method is deprecated. + Please use a standard :class:`~.QuantumTape`, and apply gradient transforms using + the :mod:`.gradients` module to compute Jacobians. + The quantum tape can be interpreted as a simple :math:`\mathbb{R}^m \to \mathbb{R}^n` function, mapping :math:`m` (trainable) gate parameters to :math:`n` measurement statistics, such as expectation values or probabilities. @@ -543,6 +555,13 @@ def jacobian(self, device, params=None, **options): array([], shape=(4, 0), dtype=float64) """ # pylint: disable=too-many-statements + + warnings.warn( + "Differentiating tapes using JacobianTape.jacobian() is deprecated. " + "Please use ta standard QuantumTape with gradient transforms from " + "the qml.gradients module instead." + ) + 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") @@ -665,6 +684,12 @@ def jacobian(self, device, params=None, **options): def hessian(self, device, params=None, **options): r"""Compute the Hessian of the parametrized quantum circuit recorded by the quantum tape. + .. warning:: + + The ``JacobianTape`` as well as the ``JacobianTape.hessian()`` method is deprecated. + Please use a standard :class:`~.QuantumTape`, and apply gradient transforms using + the :mod:`.gradients` module to compute Hessians. + The quantum tape can be interpreted as a simple :math:`\mathbb{R}^m \to \mathbb{R}^n` function, mapping :math:`m` (trainable) gate parameters to :math:`n` measurement statistics, such as expectation values or probabilities. @@ -738,6 +763,12 @@ def hessian(self, device, params=None, **options): >>> tape.hessian(dev) array([], shape=(0, 0), dtype=float64) """ + warnings.warn( + "Differentiating tapes using JacobianTape.hessian() is deprecated. " + "Please use ta standard QuantumTape with gradient transforms from " + "the qml.gradients module instead." + ) + if any(m.return_type is State for m in self.measurements): raise ValueError("The Hessian method does not support circuits that return the state") diff --git a/pennylane/tape/qubit_param_shift.py b/pennylane/tape/qubit_param_shift.py index 8a4898904d3..9457d83accb 100644 --- a/pennylane/tape/qubit_param_shift.py +++ b/pennylane/tape/qubit_param_shift.py @@ -82,6 +82,12 @@ def _get_operation_recipe(op, p_idx, shifts): class QubitParamShiftTape(JacobianTape): r"""Quantum tape for qubit parameter-shift analytic differentiation method. + .. warning:: + + The ``QubitParamShiftTape`` is deprecated. + Please use a standard :class:`~.QuantumTape`, and apply gradient transforms using + the :mod:`.gradients` module to compute parameter-shift gradients. + This class extends the :class:`~.jacobian` method of the quantum tape to support analytic gradients of qubit operations using the parameter-shift rule. This gradient method returns *exact* gradients, and can be computed directly diff --git a/pennylane/tape/reversible.py b/pennylane/tape/reversible.py index f2a9a3e9ee5..df7937b2041 100644 --- a/pennylane/tape/reversible.py +++ b/pennylane/tape/reversible.py @@ -34,6 +34,12 @@ class ReversibleTape(JacobianTape): r"""Quantum tape for computing gradients via reversible analytic differentiation. + .. warning:: + + The ``ReversibleTape`` is deprecated. + Instead, create a standard ``QuantumTape``, and use devices that support + native adjoint differentiation methods, such as ``default.qubit`` and ``lightning.qubit``. + .. note:: The reversible analytic differentiation method has the following restrictions: diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py index ee544552c56..1c4b3749dd1 100644 --- a/pennylane/tape/tape.py +++ b/pennylane/tape/tape.py @@ -19,6 +19,7 @@ import contextlib import copy from threading import RLock +import warnings import numpy as np @@ -118,10 +119,10 @@ def expand_tape(tape, depth=1, stop_at=None, expand_measurements=False): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.BasisState(np.array([1, 1]), wires=[0, 'a']) - with JacobianTape() as tape2: + with QuantumTape() as tape2: qml.Rot(0.543, 0.1, 0.4, wires=0) qml.CNOT(wires=[0, 'a']) @@ -132,7 +133,7 @@ def expand_tape(tape, depth=1, stop_at=None, expand_measurements=False): >>> tape.operations [BasisState(array([1, 1]), wires=[0, 'a']), - , + , CNOT(wires=[0, 'a']), RY(0.2, wires=['a'])] @@ -254,19 +255,11 @@ class QuantumTape(AnnotatedQueue): Once constructed, the quantum tape can be executed directly on a supported - device: + device via the :func:`~.execute` function: >>> dev = qml.device("default.qubit", wires=[0, 'a']) - - Execution can take place either using the in-place constructed parameters, - - >>> tape.execute(dev) - [0.77750694] - - or by providing parameters at run time: - - >>> tape.execute(dev, params=[0.1, 0.1, 0.1]) - [0.99003329] + >>> qml.execute([tape], dev, gradient_fn=None) + [array([0.77750694])] The trainable parameters of the tape can be explicitly set, and the values of the parameters modified in-place: @@ -539,10 +532,10 @@ def expand(self, depth=1, stop_at=None, expand_measurements=False): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.BasisState(np.array([1, 1]), wires=[0, 'a']) - with JacobianTape() as tape2: + with QuantumTape() as tape2: qml.Rot(0.543, 0.1, 0.4, wires=0) qml.CNOT(wires=[0, 'a']) @@ -553,7 +546,7 @@ def expand(self, depth=1, stop_at=None, expand_measurements=False): >>> tape.operations [BasisState(array([1, 1]), wires=[0, 'a']), - , + , CNOT(wires=[0, 'a']), RY(0.2, wires=['a'])] @@ -590,7 +583,7 @@ def inv(self): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.BasisState(np.array([1, 1]), wires=[0, 'a']) qml.RX(0.432, wires=0) qml.Rot(0.543, 0.1, 0.4, wires=0).inv() @@ -709,21 +702,20 @@ def trainable_params(self): automatically excluded from the Jacobian computation. The number of trainable parameters determines the number of parameters passed to - :meth:`~.set_parameters`, :meth:`~.execute`, and :meth:`~.JacobianTape.jacobian`, - and changes the default output size of methods :meth:`~.JacobianTape.jacobian` and - :meth:`~.get_parameters()`. + :meth:`~.set_parameters`, and changes the default output size of method :meth:`~.get_parameters()`. .. note:: - Since the :meth:`~.JacobianTape.jacobian` method is not called for devices that support - native backpropagation (such as ``default.qubit.tf`` and ``default.qubit.autograd``), - this property contains no relevant information when using backpropagation to compute gradients. + For devices that support native backpropagation (such as + ``default.qubit.tf`` and ``default.qubit.autograd``), this + property contains no relevant information when using + backpropagation to compute gradients. **Example** .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -792,7 +784,7 @@ def get_parameters(self, trainable_only=True, **kwargs): # pylint:disable=unuse .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -839,7 +831,7 @@ def set_parameters(self, params, trainable_only=True): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -926,7 +918,7 @@ def operations(self): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -949,7 +941,7 @@ def observables(self): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -984,7 +976,7 @@ def measurements(self): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -1305,6 +1297,11 @@ def hash(self): def execute(self, device, params=None): """Execute the tape on a quantum device. + .. warning:: + + Executing tapes using ``tape.execute(dev)`` is deprecated. + Please use the :func:`~.execute` function instead. + Args: device (.Device): a PennyLane device that can execute quantum operations and return measurement statistics @@ -1315,7 +1312,7 @@ def execute(self, device, params=None): .. code-block:: python - with JacobianTape() as tape: + with QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) @@ -1339,6 +1336,11 @@ def execute(self, device, params=None): >>> tape.get_parameters() [0.432, 0.543, 0.133] """ + warnings.warn( + "Executing tapes using tape.execute(dev) is deprecated. " + "Please use the qml.execute([tape], dev) function instead." + ) + if params is None: params = self.get_parameters() @@ -1352,6 +1354,11 @@ def execute_device(self, params, device): For more details on differentiable tape execution, see :meth:`~.execute`. + .. warning:: + + Executing tapes using ``tape.execute(dev)`` is deprecated. + Please use the :func:`~.execute` function instead. + Args: device (~.Device): a PennyLane device that can execute quantum operations and return measurement statistics diff --git a/pennylane/transforms/batch_input.py b/pennylane/transforms/batch_input.py index da48b8824c3..b2fd48ad854 100644 --- a/pennylane/transforms/batch_input.py +++ b/pennylane/transforms/batch_input.py @@ -66,13 +66,12 @@ def circuit(inputs, weights): qml.RY(weights[1], wires=1) return qml.expval(qml.PauliZ(1)) - >>> x = np.random.uniform(0,1,(10,2)) - >>> x.requires_grad = False - >>> w = np.random.uniform(0,1,2) + >>> x = tf.random.uniform((10, 2), 0, 1) + >>> w = tf.random.uniform((2,), 0, 1) >>> circuit(x, w) + array([0.46230079, 0.73971315, 0.95666004, 0.5355225 , 0.66180948, + 0.44519553, 0.93874261, 0.9483197 , 0.78737918, 0.90866411])> """ parameters = tape.get_parameters(trainable_only=False) diff --git a/pennylane/transforms/batch_transform.py b/pennylane/transforms/batch_transform.py index c92bd14c666..ba46a70faca 100644 --- a/pennylane/transforms/batch_transform.py +++ b/pennylane/transforms/batch_transform.py @@ -76,8 +76,8 @@ def my_transform(tape, a, b): '''Generates two tapes, one with all RX replaced with RY, and the other with all RX replaced with RZ.''' - tape1 = qml.tape.JacobianTape() - tape2 = qml.tape.JacobianTape() + tape1 = qml.tape.QuantumTape() + tape2 = qml.tape.QuantumTape() # loop through all operations on the input tape for op in tape.operations + tape.measurements: @@ -102,7 +102,7 @@ def processing_fn(results): We can apply this transform to a quantum tape: - >>> with qml.tape.JacobianTape() as tape: + >>> with qml.tape.QuantumTape() as tape: ... qml.Hadamard(wires=0) ... qml.RX(-0.5, wires=0) ... qml.expval(qml.PauliX(0)) diff --git a/pennylane/transforms/commutation_dag.py b/pennylane/transforms/commutation_dag.py index e911a74128b..570bc3558ab 100644 --- a/pennylane/transforms/commutation_dag.py +++ b/pennylane/transforms/commutation_dag.py @@ -65,7 +65,8 @@ def circuit(x, y, z): the form ``(ID, CommutationDAGNode)``: >>> nodes = dag.get_nodes() - [(0, ), ...] + >>> nodes + NodeDataView({0: , ...}, data='node') You can also access specific nodes (of type :class:`~.CommutationDAGNode`) by using the :meth:`~.get_node` method. See :class:`~.CommutationDAGNode` for a list of available @@ -205,7 +206,8 @@ def simplify_rotation(rot): def simplify_controlled_rotation(crot): - r"""Simplify a general one qubit controlled rotation into CRX, CRY, CRZ and CH. + r"""Simplify a general one qubit controlled rotation into CRX, CRY, CRZ and + controlled-Hadamard. Args: crot (pennylane.CRot): One qubit controlled rotation. @@ -286,8 +288,8 @@ def simplify_u3(u3): def simplify(operation): r"""Simplify the (controlled) rotation operations :class:`~.Rot`, :class:`~.U2`, :class:`~.U3`, and :class:`~.CRot` into one of - :class:`~.RX`, :class:`~.CRX`, :class:`~.RY`, :class:`~.CRY`, :class:`~.`RZ`, - :class:`~.CZ`, :class:`~.H` and :class:`~.CH` where possible. + :class:`~.RX`, :class:`~.CRX`, :class:`~.RY`, :class:`~.CRY`, :class:`~.RZ`, + :class:`~.CZ`, :class:`~.Hadamard` and controlled-Hadamard where possible. Args: operation (.Operation): Rotation or controlled rotation. @@ -755,10 +757,10 @@ def _merge_no_duplicates(*iterables): """Merge K list without duplicate using python heapq ordered merging. Args: - *iterables: A list of k sorted lists + *iterables: A list of k sorted lists. Yields: - Iterator: List from the merging of the k ones (without duplicates) + Iterator: List from the merging of the k ones (without duplicates). """ last = object() for val in heapq.merge(*iterables): @@ -897,7 +899,7 @@ def get_node(self, node_id): return self._multi_graph.nodes(data="node")[node_id] def get_nodes(self): - """Return iterable to loop through all the nodes in the DAG + """Return iterable to loop through all the nodes in the DAG. Returns: networkx.classes.reportviews.NodeDataView: Iterable nodes. diff --git a/pennylane/transforms/condition.py b/pennylane/transforms/condition.py index 468177d8ada..d0da00a4323 100644 --- a/pennylane/transforms/condition.py +++ b/pennylane/transforms/condition.py @@ -14,7 +14,6 @@ """ Contains the condition transform. """ -from copy import copy from functools import wraps from typing import Type @@ -88,21 +87,148 @@ def cond(condition, true_fn, false_fn=None): dev = qml.device("default.qubit", wires=3) - first_par = 0.1 - sec_par = 0.3 - @qml.qnode(dev) - def qnode(): + def qnode(x, y): + qml.Hadamard(0) m_0 = qml.measure(0) - qml.cond(m_0, qml.RY)(first_par, wires=1) + qml.cond(m_0, qml.RY)(x, wires=1) + qml.Hadamard(2) + qml.RY(-np.pi/2, wires=[2]) m_1 = qml.measure(2) - qml.cond(m_0, qml.RZ)(sec_par, wires=1) + qml.cond(m_1 == 0, qml.RX)(y, wires=1) return qml.expval(qml.PauliZ(1)) + + .. code-block :: pycon + + >>> first_par = np.array(0.3, requires_grad=True) + >>> sec_par = np.array(1.23, requires_grad=True) + >>> qnode(first_par, sec_par) + tensor(0.32677361, requires_grad=True) + + .. note:: + + If the first argument of ``cond`` is a measurement value (e.g., ``m_0`` + in ``qml.cond(m_0, qml.RY)``), then ``m_0 == 1`` is considered + internally. + + .. UsageDetails:: + + **Conditional quantum functions** + + The ``cond`` transform allows conditioning quantum functions too: + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) + + def qfunc(par, wires): + qml.Hadamard(wires[0]) + qml.RY(par, wires[0]) + + @qml.qnode(dev) + def qnode(x): + qml.Hadamard(0) + m_0 = qml.measure(0) + qml.cond(m_0, qfunc)(x, wires=[1]) + return qml.expval(qml.PauliZ(1)) + + .. code-block :: pycon + + >>> par = np.array(0.3, requires_grad=True) + >>> qnode(par) + tensor(0.3522399, requires_grad=True) + + **Passing two quantum functions** + + In the qubit model, single-qubit measurements may result in one of two + outcomes. Such measurement outcomes may then be used to create + conditional expressions. + + According to the truth value of the conditional expression passed to + ``cond``, the transform can apply a quantum function in both the + ``True`` and ``False`` case: + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) + + def qfunc1(x, wires): + qml.Hadamard(wires[0]) + qml.RY(x, wires[0]) + + def qfunc2(x, wires): + qml.Hadamard(wires[0]) + qml.RZ(x, wires[0]) + + @qml.qnode(dev) + def qnode1(x): + qml.Hadamard(0) + m_0 = qml.measure(0) + qml.cond(m_0, qfunc1, qfunc2)(x, wires=[1]) + return qml.expval(qml.PauliZ(1)) + + .. code-block :: pycon + + >>> par = np.array(0.3, requires_grad=True) + >>> qnode1(par) + tensor(-0.1477601, requires_grad=True) + + The previous QNode is equivalent to using ``cond`` twice, inverting the + conditional expression in the second case using the ``~`` unary + operator: + + .. code-block:: python3 + + @qml.qnode(dev) + def qnode2(x): + qml.Hadamard(0) + m_0 = qml.measure(0) + qml.cond(m_0, qfunc1)(x, wires=[1]) + qml.cond(~m_0, qfunc2)(x, wires=[1]) + return qml.expval(qml.PauliZ(1)) + + .. code-block :: pycon + + >>> qnode2(par) + tensor(-0.1477601, requires_grad=True) + + **Quantum functions with different signatures** + + It may be that the two quantum functions passed to ``qml.cond`` have + different signatures. In such a case, ``lambda`` functions taking no + arguments can be used with Python closure: + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) + + def qfunc1(x, wire): + qml.Hadamard(wire) + qml.RY(x, wire) + + def qfunc2(x, y, z, wire): + qml.Hadamard(wire) + qml.Rot(x, y, z, wire) + + @qml.qnode(dev) + def qnode(a, x, y, z): + qml.Hadamard(0) + m_0 = qml.measure(0) + qml.cond(m_0, lambda: qfunc1(a, wire=1), lambda: qfunc2(x, y, z, wire=1))() + return qml.expval(qml.PauliZ(1)) + + .. code-block :: pycon + + >>> par = np.array(0.3, requires_grad=True) + >>> x = np.array(1.2, requires_grad=True) + >>> y = np.array(1.1, requires_grad=True) + >>> z = np.array(0.3, requires_grad=True) + >>> qnode(par, x, y, z) + tensor(-0.30922805, requires_grad=True) """ if callable(true_fn): # We assume that the callable is an operation or a quantum function - with_meas_err = ( "Only quantum functions that contain no measurements can be applied conditionally." ) @@ -127,11 +253,10 @@ def wrapper(*args, **kwargs): if else_tape.measurements: raise ConditionalTransformError(with_meas_err) - inverted_m = copy(condition) - inverted_m = ~inverted_m + inverted_condition = ~condition for op in else_tape.operations: - Conditional(inverted_m, op) + Conditional(inverted_condition, op) else: raise ConditionalTransformError( diff --git a/pennylane/transforms/draw.py b/pennylane/transforms/draw.py index 6795c387416..7960a0ccf69 100644 --- a/pennylane/transforms/draw.py +++ b/pennylane/transforms/draw.py @@ -230,14 +230,12 @@ def circuit(a, w): When requested with ``show_matrices=True``, matrix valued parameters are printed below the circuit: - .. code-block:: python - >>> @qml.qnode(qml.device('default.qubit', wires=2)) - ... def circuit(): + ... def circuit3(): ... qml.QubitUnitary(np.eye(2), wires=0) ... qml.QubitUnitary(-np.eye(4), wires=(0,1)) ... return qml.expval(qml.Hermitian(np.eye(2), wires=1)) - >>> print(qml.draw(circuit, show_matrices=True)()) + >>> print(qml.draw(circuit3, show_matrices=True)()) 0: ──U(M0)─╭U(M1)── 1: ────────╰U(M1)── <𝓗(M0)> M0 = @@ -295,13 +293,13 @@ def longer_circuit(params): .. code-block:: python - @qml.gradients.param_shift(shift=0.1) + @qml.gradients.param_shift(shifts=[(0.1,)]) @qml.qnode(qml.device('lightning.qubit', wires=1)) def transformed_circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) - print(qml.draw(transformed_circuit)(np.array(1.0))) + print(qml.draw(transformed_circuit)(np.array(1.0, requires_grad=True))) .. code-block:: none diff --git a/pennylane/transforms/qcut.py b/pennylane/transforms/qcut.py index 4335a0710c6..1e79a2c9353 100644 --- a/pennylane/transforms/qcut.py +++ b/pennylane/transforms/qcut.py @@ -722,7 +722,7 @@ def contract_tensors( The network can then be contracted using: - >>> qml.transforms.contract_tensors(tensors, graph, prep, meas) + >>> qml.transforms.qcut.contract_tensors(tensors, graph, prep, meas) 38 """ # pylint: disable=import-outside-toplevel diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py index 40e056b968c..ac0aab757ac 100644 --- a/pennylane/transforms/qfunc_transforms.py +++ b/pennylane/transforms/qfunc_transforms.py @@ -139,7 +139,7 @@ def my_transform(tape, x, y): We can apply this transform to a quantum tape: - >>> with qml.tape.JacobianTape() as tape: + >>> with qml.tape.QuantumTape() as tape: ... qml.Hadamard(wires=0) ... qml.CRX(-0.5, wires=[0, 1]) >>> new_tape = my_transform(tape, 1., 2.) diff --git a/pennylane/transforms/tape_expand.py b/pennylane/transforms/tape_expand.py index aa974a04b6a..e3444d3c6f6 100644 --- a/pennylane/transforms/tape_expand.py +++ b/pennylane/transforms/tape_expand.py @@ -74,7 +74,7 @@ def create_expand_fn(depth, stop_at=None, device=None, docstring=None): .. code-block:: python - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.RX(qml.numpy.array(-2.4, requires_grad=True), wires=1) qml.Rot(1.7, 0.92, -1.1, wires=0) diff --git a/setup.py b/setup.py index a45389ed7ba..ffd4bc7a6e3 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ "semantic_version==2.6", "autoray", "cachetools", - "pennylane-lightning>=0.21", + "pennylane-lightning>=0.22", ] info = { diff --git a/tests/gradients/test_finite_difference.py b/tests/gradients/test_finite_difference.py index aedaa5a3542..63be69ebce5 100644 --- a/tests/gradients/test_finite_difference.py +++ b/tests/gradients/test_finite_difference.py @@ -111,7 +111,7 @@ def test_behaviour(self): def test_multipliers(self): """Test that the function behaves as expected when multipliers are used""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.PauliZ(0) qml.RX(1.0, wires=0) qml.CNOT(wires=[0, 2]) @@ -136,7 +136,7 @@ def test_non_differentiable_error(self): respect to a non-differentiable argument""" psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) @@ -165,7 +165,7 @@ def test_independent_parameter(self, mocker): during the Jacobian computation.""" spy = mocker.spy(qml.gradients.finite_difference, "generate_shifted_tapes") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliZ(0)) @@ -245,7 +245,7 @@ def test_y0(self, mocker): values.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -262,7 +262,7 @@ def test_y0_provided(self): values.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -278,12 +278,12 @@ def test_independent_parameters(self): parameters, the gradient should be evaluated to zero without executing the device.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(1, wires=[0]) qml.RX(1, wires=[1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(1, wires=[0]) qml.RX(1, wires=[1]) qml.expval(qml.PauliZ(1)) @@ -314,7 +314,7 @@ def test_ragged_output(self, approx_order, strategy): dev = qml.device("default.qubit", wires=3) params = [1.0, 1.0, 1.0] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.RZ(params[2], wires=[2]) @@ -333,7 +333,7 @@ def test_single_expectation_value(self, approx_order, strategy, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -354,7 +354,7 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -380,7 +380,7 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -404,7 +404,7 @@ def test_multiple_expectation_values(self, approx_order, strategy, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -425,7 +425,7 @@ def test_var_expectation_values(self, approx_order, strategy, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -446,7 +446,7 @@ def test_prob_expectation_values(self, approx_order, strategy, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -497,7 +497,7 @@ def test_autograd(self, approx_order, strategy, tol): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -525,7 +525,7 @@ def test_autograd_ragged(self, approx_order, strategy, tol): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -552,7 +552,7 @@ def test_tf(self, approx_order, strategy, tol): params = tf.Variable([0.543, -0.654], dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -584,7 +584,7 @@ def test_tf_ragged(self, approx_order, strategy, tol): params = tf.Variable([0.543, -0.654], dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -608,7 +608,7 @@ def test_torch(self, approx_order, strategy, tol): dev = qml.device("default.qubit.torch", wires=2) params = torch.tensor([0.543, -0.654], dtype=torch.float64, requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -646,7 +646,7 @@ def test_jax(self, approx_order, strategy, tol): params = jnp.array([0.543, -0.654]) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) diff --git a/tests/gradients/test_gradient_transform.py b/tests/gradients/test_gradient_transform.py index 922b695abf9..e382694c66c 100644 --- a/tests/gradients/test_gradient_transform.py +++ b/tests/gradients/test_gradient_transform.py @@ -24,14 +24,13 @@ class TestGradMethodValidation: - """Test the helper function grad_method_validation, which is a - reduced copy of the eponymous method of ``JacobianTape``.""" + """Test the helper function grad_method_validation.""" @pytest.mark.parametrize("method", ["analytic", "best"]) def test_with_nondiff_parameters(self, method): """Test that trainable parameters without grad_method are detected correctly, raising an exception.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(np.array(0.1, requires_grad=True), wires=0) qml.RX(np.array(0.1, requires_grad=True), wires=0) qml.expval(qml.PauliZ(0)) @@ -43,7 +42,7 @@ def test_with_nondiff_parameters(self, method): def test_with_numdiff_parameters_and_analytic(self): """Test that trainable parameters with numerical grad_method ``"F"`` together with ``method="analytic"`` raises an exception.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(np.array(0.1, requires_grad=True), wires=0) qml.RX(np.array(0.1, requires_grad=True), wires=0) qml.expval(qml.PauliZ(0)) @@ -54,8 +53,7 @@ def test_with_numdiff_parameters_and_analytic(self): class TestChooseGradMethods: - """Test the helper function choose_grad_methods, which is a - reduced copy of the eponymous method of ``JacobianTape``.""" + """Test the helper function choose_grad_methods""" all_diff_methods = [ ["A"] * 2, diff --git a/tests/gradients/test_hamiltonian_gradient.py b/tests/gradients/test_hamiltonian_gradient.py index f76948693ad..3c4df7ae36e 100644 --- a/tests/gradients/test_hamiltonian_gradient.py +++ b/tests/gradients/test_hamiltonian_gradient.py @@ -21,7 +21,7 @@ def test_behaviour(): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) @@ -34,13 +34,13 @@ def test_behaviour(): tapes, processing_fn = hamiltonian_grad(tape, idx=1) res2 = processing_fn(dev.batch_execute(tapes)) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) diff --git a/tests/gradients/test_parameter_shift.py b/tests/gradients/test_parameter_shift.py index 6b387867e1a..aed659b6c4d 100644 --- a/tests/gradients/test_parameter_shift.py +++ b/tests/gradients/test_parameter_shift.py @@ -17,7 +17,7 @@ import pennylane as qml from pennylane import numpy as np from pennylane.gradients import param_shift -from pennylane.gradients.parameter_shift import _gradient_analysis +from pennylane.gradients.gradient_transform import gradient_analysis class TestGradAnalysis: @@ -28,24 +28,25 @@ def test_non_differentiable(self): correctly marked""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.CNOT(wires=[0, 1]) qml.probs(wires=[0, 1]) - _gradient_analysis(tape) + gradient_analysis(tape) assert tape._par_info[0]["grad_method"] is None assert tape._par_info[1]["grad_method"] == "A" assert tape._par_info[2]["grad_method"] == "A" def test_analysis_caching(self, mocker): - """Test that the gradient analysis is only executed once per tape""" + """Test that the gradient analysis is only executed once per tape + if grad_fn is set an unchanged.""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) @@ -53,7 +54,7 @@ def test_analysis_caching(self, mocker): qml.probs(wires=[0, 1]) spy = mocker.spy(qml.operation, "has_grad_method") - _gradient_analysis(tape) + gradient_analysis(tape, grad_fn=5) spy.assert_called() assert tape._par_info[0]["grad_method"] is None @@ -61,19 +62,19 @@ def test_analysis_caching(self, mocker): assert tape._par_info[2]["grad_method"] == "A" spy = mocker.spy(qml.operation, "has_grad_method") - _gradient_analysis(tape) + gradient_analysis(tape, grad_fn=5) spy.assert_not_called() def test_independent(self): """Test that an independent variable is properly marked as having a zero gradient""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliY(0)) - _gradient_analysis(tape) + gradient_analysis(tape) assert tape._par_info[0]["grad_method"] == "A" assert tape._par_info[1]["grad_method"] == "0" @@ -82,12 +83,12 @@ def test_independent_no_graph_mode(self): """In non-graph mode, it is impossible to determine if a parameter is independent or not""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliY(0)) - _gradient_analysis(tape, use_graph=False) + gradient_analysis(tape, use_graph=False) assert tape._par_info[0]["grad_method"] == "A" assert tape._par_info[1]["grad_method"] == "A" @@ -98,14 +99,14 @@ def test_finite_diff(self, monkeypatch): psi = np.array([1, 0, 1, 0]) / np.sqrt(2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(psi, wires=[0, 1]) qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.CNOT(wires=[0, 1]) qml.probs(wires=[0, 1]) - _gradient_analysis(tape) + gradient_analysis(tape) assert tape._par_info[0]["grad_method"] is None assert tape._par_info[1]["grad_method"] == "F" @@ -124,7 +125,7 @@ class TestShiftedTapes: def test_behaviour(self): """Test that the function behaves as expected""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.PauliZ(0) qml.RX(1.0, wires=0) qml.CNOT(wires=[0, 2]) @@ -148,7 +149,7 @@ class TestParamShift: def test_empty_circuit(self): """Test that an empty circuit works correctly""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ(0)) with pytest.warns(UserWarning, match="gradient of a tape with no trainable parameters"): @@ -157,7 +158,7 @@ def test_empty_circuit(self): def test_all_parameters_independent(self): """Test that a circuit where all parameters do not affect the output""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=0) qml.expval(qml.PauliZ(1)) @@ -169,7 +170,7 @@ def test_state_non_differentiable_error(self): respect to a state""" psi = np.array([1, 0, 1, 0]) / np.sqrt(2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.state() @@ -182,7 +183,7 @@ def test_independent_parameter(self, mocker): during the Jacobian computation.""" spy = mocker.spy(qml.gradients.parameter_shift, "expval_param_shift") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[1]) qml.expval(qml.PauliZ(0)) @@ -262,7 +263,7 @@ def test_y0(self): values.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -279,7 +280,7 @@ def test_y0_provided(self): values.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.543, wires=[0]) qml.RY(-0.654, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -298,12 +299,12 @@ def test_independent_parameters_analytic(self): parameters, the gradient should be evaluated to zero without executing the device.""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(1, wires=[0]) qml.RX(1, wires=[1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(1, wires=[0]) qml.RX(1, wires=[1]) qml.expval(qml.PauliZ(1)) @@ -345,7 +346,7 @@ def grad_recipe(self): x = np.array(0.654, requires_grad=True) dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: RX(x, wires=0) qml.expval(qml.PauliZ(0)) @@ -382,7 +383,7 @@ class NewOp(qml.operation.Operation): dev = qml.device("default.qubit", wires=2) for op in [RX, NewOp]: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: op(x, wires=0) qml.expval(qml.PauliZ(0)) @@ -403,7 +404,7 @@ def test_pauli_rotation_gradient(self, mocker, G, theta, shift, tol): spy = mocker.spy(qml.gradients.parameter_shift, "_get_operation_recipe") dev = qml.device("default.qubit", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) G(theta, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -414,10 +415,12 @@ def test_pauli_rotation_gradient(self, mocker, G, theta, shift, tol): assert len(tapes) == 2 autograd_val = fn(dev.batch_execute(tapes)) - manualgrad_val = ( - tape.execute(dev, params=[theta + np.pi / 2]) - - tape.execute(dev, params=[theta - np.pi / 2]) - ) / 2 + + tape_fwd, tape_bwd = tape.copy(copy_operations=True), tape.copy(copy_operations=True) + tape_fwd.set_parameters([theta + np.pi / 2]) + tape_bwd.set_parameters([theta - np.pi / 2]) + + manualgrad_val = np.subtract(*dev.batch_execute([tape_fwd, tape_bwd])) / 2 assert np.allclose(autograd_val, manualgrad_val, atol=tol, rtol=0) assert spy.call_args[1]["shifts"] == (shift,) @@ -435,7 +438,7 @@ def test_Rot_gradient(self, mocker, theta, shift, tol): dev = qml.device("default.qubit", wires=1) params = np.array([theta, theta**3, np.sqrt(2) * theta]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) qml.Rot(*params, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -452,8 +455,11 @@ def test_Rot_gradient(self, mocker, theta, shift, tol): s = np.zeros_like(params) s[idx] += np.pi / 2 - forward = tape.execute(dev, params=params + s) - backward = tape.execute(dev, params=params - s) + tape.set_parameters(params + s) + forward = dev.execute(tape) + + tape.set_parameters(params - s) + backward = dev.execute(tape) manualgrad_val[0, idx] = (forward - backward) / 2 @@ -471,14 +477,14 @@ def test_controlled_rotation_gradient(self, G, tol): dev = qml.device("default.qubit", wires=2) b = 0.123 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) G(b, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {1} - res = tape.execute(dev) + res = dev.execute(tape) assert np.allclose(res, -np.cos(b / 2), atol=tol, rtol=0) tapes, fn = qml.gradients.param_shift(tape) @@ -498,14 +504,14 @@ def test_CRot_gradient(self, theta, tol): dev = qml.device("default.qubit", wires=2) a, b, c = np.array([theta, theta**3, np.sqrt(2) * theta]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) qml.CRot(a, b, c, wires=[0, 1]) qml.expval(qml.PauliX(0)) tape.trainable_params = {1, 2, 3} - res = tape.execute(dev) + res = dev.execute(tape) expected = -np.cos(b / 2) * np.cos(0.5 * (a + c)) assert np.allclose(res, expected, atol=tol, rtol=0) @@ -534,7 +540,7 @@ def test_gradients_agree_finite_differences(self, tol): order finite differences""" params = np.array([0.1, -1.6, np.pi / 5]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(-1.6, wires=[0]) @@ -562,7 +568,7 @@ def test_variance_gradients_agree_finite_differences(self, tol): order finite differences""" params = np.array([0.1, -1.6, np.pi / 5]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(-1.6, wires=[0]) @@ -598,7 +604,7 @@ class RY(qml.RY): grad_method = "F" def cost_fn(params): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -643,7 +649,7 @@ class RY(qml.RY): class RX(qml.RX): grad_method = "F" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: RX(x, wires=[0]) RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -669,7 +675,7 @@ def test_single_expectation_value(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -691,7 +697,7 @@ def test_multiple_expectation_values(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -714,7 +720,7 @@ def test_var_expectation_values(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -737,7 +743,7 @@ def test_prob_expectation_values(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -782,11 +788,11 @@ def test_involutory_variance(self, tol): dev = qml.device("default.qubit", wires=1) a = 0.54 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(a, wires=0) qml.var(qml.PauliZ(0)) - res = tape.execute(dev) + res = dev.execute(tape) expected = 1 - np.cos(a) ** 2 assert np.allclose(res, expected, atol=tol, rtol=0) @@ -810,13 +816,13 @@ def test_non_involutory_variance(self, tol): A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) a = 0.54 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(a, wires=0) qml.var(qml.Hermitian(A, 0)) tape.trainable_params = {0} - res = tape.execute(dev) + res = dev.execute(tape) expected = (39 / 2) - 6 * np.sin(2 * a) + (35 / 2) * np.cos(2 * a) assert np.allclose(res, expected, atol=tol, rtol=0) @@ -840,7 +846,7 @@ def test_involutory_and_noninvolutory_variance(self, tol): A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) a = 0.54 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(a, wires=0) qml.RX(a, wires=1) qml.var(qml.PauliZ(0)) @@ -848,7 +854,7 @@ def test_involutory_and_noninvolutory_variance(self, tol): tape.trainable_params = {0, 1} - res = tape.execute(dev) + res = dev.execute(tape) expected = [1 - np.cos(a) ** 2, (39 / 2) - 6 * np.sin(2 * a) + (35 / 2) * np.cos(2 * a)] assert np.allclose(res, expected, atol=tol, rtol=0) @@ -874,7 +880,7 @@ def test_expval_and_variance(self, tol): b = -0.423 c = 0.123 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(a, wires=0) qml.RY(b, wires=1) qml.CNOT(wires=[1, 2]) @@ -884,7 +890,7 @@ def test_expval_and_variance(self, tol): qml.expval(qml.PauliZ(1)) qml.var(qml.PauliZ(2)) - res = tape.execute(dev) + res = dev.execute(tape) expected = np.array( [ np.sin(a) ** 2, @@ -921,7 +927,7 @@ def test_projector_variance(self, tol): P = np.array([1]) x, y = 0.765, -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) @@ -929,7 +935,7 @@ def test_projector_variance(self, tol): tape.trainable_params = {0, 1} - res = tape.execute(dev) + res = dev.execute(tape) expected = 0.25 * np.sin(x / 2) ** 2 * (3 + np.cos(2 * y) + 2 * np.cos(x) * np.sin(y) ** 2) assert np.allclose(res, expected, atol=tol, rtol=0) @@ -962,7 +968,7 @@ def test_autograd(self, tol): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -993,7 +999,7 @@ def test_tf(self, tol): params = tf.Variable([0.543, -0.654], dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -1025,7 +1031,7 @@ def test_torch(self, tol): dev = qml.device("default.qubit.torch", wires=2) params = torch.tensor([0.543, -0.654], dtype=torch.float64, requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -1058,7 +1064,7 @@ def test_jax(self, tol): params = jnp.array([0.543, -0.654]) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -1095,7 +1101,7 @@ def test_not_expval_error(self): weights = np.array([0.4, 0.5]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) @@ -1117,7 +1123,7 @@ def test_no_trainable_coeffs(self, mocker, tol): weights = np.array([0.4, 0.5]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) @@ -1156,7 +1162,7 @@ def test_trainable_coeffs(self, mocker, tol): weights = np.array([0.4, 0.5]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) @@ -1204,7 +1210,7 @@ def test_multiple_hamiltonians(self, mocker, tol): weights = np.array([0.4, 0.5]) x, y = weights - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) @@ -1247,7 +1253,7 @@ def cost_fn(weights, coeffs1, coeffs2, dev=None): obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) diff --git a/tests/gradients/test_parameter_shift_cv.py b/tests/gradients/test_parameter_shift_cv.py index bf86136e547..647d2eca228 100644 --- a/tests/gradients/test_parameter_shift_cv.py +++ b/tests/gradients/test_parameter_shift_cv.py @@ -34,7 +34,7 @@ def test_non_differentiable(self): """Test that a non-differentiable parameter is correctly marked""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.FockState(1, wires=0) qml.Displacement(0.543, 0, wires=[1]) qml.Beamsplitter(0, 0, wires=[0, 1]) @@ -60,7 +60,7 @@ def test_independent(self): """Test that an independent variable is properly marked as having a zero gradient""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(0.543, wires=[0]) qml.Rotation(-0.654, wires=[1]) qml.expval(qml.P(0)) @@ -75,10 +75,10 @@ def test_independent(self): def test_finite_diff(self, monkeypatch): """If an op has grad_method=F, this should be respected - by the qml.tape.JacobianTape""" + by the qml.tape.QuantumTape""" monkeypatch.setattr(qml.Rotation, "grad_method", "F") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(0.543, wires=[0]) qml.Squeezing(0.543, 0, wires=[0]) qml.expval(qml.P(0)) @@ -92,7 +92,7 @@ def test_non_gaussian_operation(self): a differentiable Gaussian operation results in numeric differentiation.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.Rotation(1.0, wires=[1]) # Non-Gaussian @@ -107,7 +107,7 @@ def test_non_gaussian_operation(self): # Kerr gate does not support the parameter-shift rule assert _grad_method(tape, 2) == "F" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.Rotation(1.0, wires=[1]) # entangle the modes @@ -127,7 +127,7 @@ def test_probability(self): """Probability is the expectation value of a higher order observable, and thus only supports numerical differentiation""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(0.543, wires=[0]) qml.Squeezing(0.543, 0, wires=[0]) qml.probs(wires=0) @@ -141,19 +141,19 @@ def test_variance(self): parameter-shift is supported. If the observable is second order, however, only finite-differences is supported.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.var(qml.P(0)) # first order assert _grad_method(tape, 0) == "A" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.var(qml.NumberOperator(0)) # second order assert _grad_method(tape, 0) == "F" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.Rotation(1.0, wires=[1]) qml.Beamsplitter(0.5, 0.0, wires=[0, 1]) @@ -169,7 +169,7 @@ def test_second_order_expectation(self): """Test that the expectation of a second-order observable forces the gradient method to use the second-order parameter-shift rule""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.expval(qml.NumberOperator(0)) # second order @@ -181,7 +181,7 @@ def test_unknown_op_grad_method(self, monkeypatch): doesn't recognize""" monkeypatch.setattr(qml.Rotation, "grad_method", "B") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=0) qml.expval(qml.X(0)) @@ -297,7 +297,7 @@ def test_no_trainable_params_tape(self): dev = qml.device("default.gaussian", wires=2) weights = [0.1, 0.2] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(weights[0], 0.0, wires=[0]) qml.Rotation(weights[1], wires=[0]) qml.expval(qml.X(0)) @@ -333,7 +333,7 @@ def circuit(params): def test_state_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a state""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.state() with pytest.raises(ValueError, match=r"return the state is not supported"): @@ -346,7 +346,7 @@ def test_force_order2(self, mocker): dev = qml.device("default.gaussian", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(1.0, 0.0, wires=[0]) qml.Rotation(2.0, wires=[0]) qml.expval(qml.X(0)) @@ -369,7 +369,7 @@ def test_no_poly_xp_support(self, mocker, monkeypatch, caplog): monkeypatch.delitem(dev._observable_map, "PolyXP") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.expval(qml.NumberOperator(0)) @@ -389,7 +389,7 @@ def test_no_poly_xp_support_variance(self, mocker, monkeypatch, caplog): monkeypatch.delitem(dev._observable_map, "PolyXP") - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Rotation(1.0, wires=[0]) qml.var(qml.X(0)) @@ -405,7 +405,7 @@ def test_independent_parameters_analytic(self): parameters, the gradient should be evaluated to zero without executing the device.""" dev = qml.device("default.gaussian", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(1, 0, wires=[1]) qml.Displacement(1, 0, wires=[0]) qml.expval(qml.X(0)) @@ -434,7 +434,7 @@ def test_all_independent(self): """Test the case where expectation values are independent of all parameters.""" dev = qml.device("default.gaussian", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(1, 0, wires=[1]) qml.Displacement(1, 0, wires=[1]) qml.expval(qml.X(0)) @@ -462,7 +462,7 @@ def test_rotation_gradient(self, gradient_recipes, mocker, tol): alpha = 0.5643 theta = 0.23354 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(alpha, 0.0, wires=[0]) qml.Rotation(theta, wires=[0]) qml.expval(qml.X(0)) @@ -490,7 +490,7 @@ def test_beamsplitter_gradient(self, mocker, tol): alpha = 0.5643 theta = 0.23354 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(alpha, 0.0, wires=[0]) qml.Beamsplitter(theta, 0.0, wires=[0, 1]) qml.expval(qml.X(0)) @@ -518,7 +518,7 @@ def test_displacement_gradient(self, mocker, tol): r = 0.5643 phi = 0.23354 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(r, phi, wires=[0]) qml.expval(qml.X(0)) @@ -555,7 +555,7 @@ class Rotation(qml.operation.CVOperation): alpha = 0.5643 r = 0.23354 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(alpha, 0.0, wires=[0]) qml.Squeezing(r, 0.0, wires=[0]) @@ -589,7 +589,7 @@ def test_squeezed_number_state_gradient(self, mocker, tol): r = 0.23354 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(r, 0.0, wires=[0]) # the fock state projector is a 'non-Gaussian' observable qml.expval(qml.FockStateProjector(np.array([2, 0]), wires=[0, 1])) @@ -615,7 +615,7 @@ def test_multiple_squeezing_gradient(self, mocker, tol): r0, phi0, r1, phi1 = [0.4, -0.3, -0.7, 0.2] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(r0, phi0, wires=[0]) qml.Squeezing(r1, phi1, wires=[0]) qml.expval(qml.NumberOperator(0)) # second order @@ -646,7 +646,7 @@ def test_multiple_second_order_observables(self, mocker, tol): r = [0.4, -0.7, 0.1, 0.2] p = [0.1, 0.2, 0.3, 0.4] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(r[0], p[0], wires=[0]) qml.Squeezing(r[1], p[1], wires=[0]) qml.Squeezing(r[2], p[2], wires=[1]) @@ -688,7 +688,7 @@ def test_gradients_gaussian_circuit(self, op, obs, tol): finite difference and analytic methods.""" tol = 1e-2 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(0.5, 0, wires=0) qml.apply(op) qml.Beamsplitter(1.3, -2.3, wires=[0, 1]) @@ -698,7 +698,7 @@ def test_gradients_gaussian_circuit(self, op, obs, tol): qml.expval(obs(wires=0)) dev = qml.device("default.gaussian", wires=2) - res = tape.execute(dev) + res = qml.execute([tape], dev, None) tape.trainable_params = set(range(2, 2 + op.num_params)) @@ -768,7 +768,7 @@ def test_interferometer_unitary(self, t, tol): requires_grad=False, ) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(0.543, 0, wires=0) qml.InterferometerUnitary(U, wires=[0, 1]) qml.expval(qml.X(0)) @@ -805,14 +805,14 @@ def test_first_order_observable(self, tol): r = 0.543 phi = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(r, 0, wires=0) qml.Rotation(phi, wires=0) qml.var(qml.X(0)) tape.trainable_params = {0, 2} - res = tape.execute(dev) + res = qml.execute([tape], dev, None) expected = np.exp(2 * r) * np.sin(phi) ** 2 + np.exp(-2 * r) * np.cos(phi) ** 2 assert np.allclose(res, expected, atol=tol, rtol=0) @@ -841,14 +841,14 @@ def test_second_order_cv(self, tol): n = 0.12 a = 0.765 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) qml.var(qml.NumberOperator(0)) tape.trainable_params = {0, 1} - res = tape.execute(dev) + res = qml.execute([tape], dev, None) expected = n**2 + n + np.abs(a) ** 2 * (1 + 2 * n) assert np.allclose(res, expected, atol=tol, rtol=0) @@ -866,7 +866,7 @@ def test_expval_and_variance(self, tol): a, b = [0.54, -0.423] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(0.5, 0, wires=0) qml.Squeezing(a, 0, wires=0) qml.Squeezing(b, 0, wires=1) @@ -893,7 +893,7 @@ def test_error_analytic_second_order(self): order observable to compute the variance derivative analytically""" dev = qml.device("default.gaussian", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(1.0, 0, wires=0) qml.var(qml.NumberOperator(0)) @@ -917,7 +917,7 @@ class DummyOp(qml.operation.CVOperation): dev.operations.add(DummyOp) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: DummyOp(1, wires=[0]) qml.expval(qml.X(0)) @@ -939,7 +939,7 @@ def test_gradients_gaussian_circuit(self, op, obs, tol): finite difference and analytic methods.""" tol = 1e-2 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Displacement(0.5, 0, wires=0) qml.apply(op) qml.Beamsplitter(1.3, -2.3, wires=[0, 1]) @@ -949,7 +949,7 @@ def test_gradients_gaussian_circuit(self, op, obs, tol): qml.var(obs(wires=0)) dev = qml.device("default.gaussian", wires=2) - res = tape.execute(dev) + res = qml.execute([tape], dev, None) tape.trainable_params = set(range(2, 2 + op.num_params)) @@ -978,7 +978,7 @@ def test_squeezed_mean_photon_variance(self, tol): r = 0.12 phi = 0.105 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(r, 0, wires=0) qml.Rotation(phi, wires=0) qml.var(qml.X(wires=[0])) @@ -1001,7 +1001,7 @@ def test_displaced_thermal_mean_photon_variance(self, tol): n = 0.12 a = 0.105 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.ThermalState(n, wires=0) qml.Displacement(a, 0, wires=0) qml.var(qml.TensorN(wires=[0])) @@ -1025,7 +1025,7 @@ def test_autograd_gradient(self, tol): phi = 0.105 def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(x[0], 0, wires=0) qml.Rotation(x[1], wires=0) qml.var(qml.X(wires=[0])) @@ -1050,7 +1050,7 @@ def test_tf(self, tol): params = tf.Variable([0.543, -0.654], dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(params[0], 0, wires=0) qml.Rotation(params[1], wires=0) qml.var(qml.X(wires=[0])) @@ -1088,7 +1088,7 @@ def test_torch(self, tol): dev = qml.device("default.gaussian", wires=1) params = torch.tensor([0.543, -0.654], dtype=torch.float64, requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(params[0], 0, wires=0) qml.Rotation(params[1], wires=0) qml.var(qml.X(wires=[0])) @@ -1130,7 +1130,7 @@ def test_jax(self, tol): params = jnp.array([0.543, -0.654]) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Squeezing(params[0], 0, wires=0) qml.Rotation(params[1], wires=0) qml.var(qml.X(wires=[0])) diff --git a/tests/gradients/test_vjp.py b/tests/gradients/test_vjp.py index 70bd9671c5f..354ebe3dfb3 100644 --- a/tests/gradients/test_vjp.py +++ b/tests/gradients/test_vjp.py @@ -140,7 +140,7 @@ def test_single_expectation_value(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -165,7 +165,7 @@ def test_multiple_expectation_values(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -191,7 +191,7 @@ def test_prob_expectation_values(self, tol): x = 0.543 y = -0.654 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -240,7 +240,7 @@ def test_dtype_matches_dy(self, dtype): zero-like.""" x = np.array([0.1], dtype=np.float64) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=0) qml.expval(qml.PauliZ(0)) @@ -280,7 +280,7 @@ def test_autograd(self, tol): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x, dy): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: ansatz(x[0], x[1]) tape.trainable_params = {0, 1} @@ -306,7 +306,7 @@ def test_torch(self, tol): params = torch.tensor(params_np, requires_grad=True, dtype=torch.float64) dy = torch.tensor([-1.0, 0.0, 0.0, 1.0], dtype=torch.float64) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: ansatz(params[0], params[1]) tape.trainable_params = {0, 1} @@ -334,7 +334,7 @@ def test_tf(self, tol): dy = tf.constant([-1.0, 0.0, 0.0, 1.0], dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: ansatz(params[0], params[1]) tape.trainable_params = {0, 1} @@ -393,7 +393,7 @@ def test_jax(self, tol): @partial(jax.jit, static_argnums=1) def cost_fn(x, dy): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: ansatz(x[0], x[1]) tape.trainable_params = {0, 1} @@ -422,7 +422,7 @@ def test_one_tape_no_trainable_parameters(self): qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) @@ -478,7 +478,7 @@ def test_zero_dy(self): qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) @@ -502,12 +502,12 @@ def test_reduction_append(self): """Test the 'append' reduction strategy""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(0.4, wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) @@ -531,12 +531,12 @@ def test_reduction_extend(self): """Test the 'extend' reduction strategy""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(0.4, wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) diff --git a/tests/interfaces/test_batch_autograd.py b/tests/interfaces/test_batch_autograd.py index f98eba3ab42..847549226ba 100644 --- a/tests/interfaces/test_batch_autograd.py +++ b/tests/interfaces/test_batch_autograd.py @@ -41,7 +41,7 @@ def test_import_error(self, mocker): dev = qml.device("default.qubit", wires=2, shots=None) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliY(1)) with pytest.raises( @@ -60,7 +60,7 @@ def test_jacobian_options(self, mocker, tol): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -85,7 +85,7 @@ def test_incorrect_mode(self): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -104,7 +104,7 @@ def test_unknown_interface(self): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -120,7 +120,7 @@ def test_forward_mode(self, mocker): spy = mocker.spy(dev, "execute_and_gradients") def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -146,7 +146,7 @@ def test_backward_mode(self, mocker): spy_gradients = mocker.spy(qml.devices.DefaultQubit, "gradients") def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -182,7 +182,7 @@ def test_no_batch_transform(self, mocker): x = 0.6 y = 0.2 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) @@ -209,7 +209,7 @@ def test_cache_maxsize(self, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cachesize): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -230,7 +230,7 @@ def test_custom_cache(self, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -250,7 +250,7 @@ def test_caching_param_shift(self, tol): dev = qml.device("default.qubit", wires=1) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -294,7 +294,7 @@ def test_caching_param_shift_hessian(self, num_params, tol): N = len(params) def cost(x, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) @@ -345,7 +345,7 @@ def test_caching_adjoint_backward(self): params = np.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -400,12 +400,12 @@ def test_execution(self, execute_kwargs): dev = qml.device("default.qubit", wires=1) def cost(a, b): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) @@ -426,7 +426,7 @@ def test_scalar_jacobian(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) return execute([tape], dev, **execute_kwargs)[0] @@ -435,7 +435,7 @@ def cost(a): assert res.shape == (1,) # compare to standard tape jacobian - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -452,7 +452,7 @@ def test_jacobian(self, execute_kwargs, tol): b = np.array(0.2, requires_grad=True) def cost(a, b, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -480,15 +480,15 @@ def test_tape_no_parameters(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=1) def cost(params): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.Hadamard(0) qml.expval(qml.PauliX(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(np.array(0.5, requires_grad=False), wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape3: + with qml.tape.QuantumTape() as tape3: qml.RY(params[0], wires=0) qml.RX(params[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -513,7 +513,7 @@ def test_reusing_quantum_tape(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -554,7 +554,7 @@ def test_classical_processing(self, execute_kwargs, tol): c = np.array(0.3, requires_grad=True) def cost(a, b, c, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + np.sin(a), wires=0) @@ -575,7 +575,7 @@ def test_no_trainable_parameters(self, execute_kwargs, tol): b = np.array(0.2, requires_grad=False) def cost(a, b, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.CNOT(wires=[0, 1]) @@ -606,7 +606,7 @@ def test_matrix_parameter(self, execute_kwargs, tol): a = np.array(0.1, requires_grad=True) def cost(a, U, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -626,7 +626,7 @@ def test_differentiable_expand(self, execute_kwargs, tol): class U3(qml.U3): def expand(self): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() theta, phi, lam = self.data wires = self.wires tape._ops += [ @@ -636,7 +636,7 @@ def expand(self): return tape def cost_fn(a, p, device): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() with tape: qml.RX(a, wires=0) @@ -678,7 +678,7 @@ def test_probability_differentiation(self, execute_kwargs, tol): pytest.skip("Adjoint differentiation does not yet support probabilities") def cost(x, y, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -731,7 +731,7 @@ def test_ragged_differentiation(self, execute_kwargs, tol): pytest.skip("Adjoint differentiation does not yet support probabilities") def cost(x, y, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -769,7 +769,7 @@ def test_sampling(self, execute_kwargs): pytest.skip("Adjoint differentiation does not support samples") def cost(x, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.sample(qml.PauliZ(0)) @@ -801,13 +801,13 @@ def test_parameter_shift_hessian(self, params, tol, recwarn): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -847,7 +847,7 @@ def test_adjoint_hessian(self, tol, recwarn): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -876,13 +876,13 @@ def test_max_diff(self, tol): params = np.array([0.543, -0.654], requires_grad=True) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -917,7 +917,7 @@ def test_changing_shots(self, mocker, tol): dev = qml.device("default.qubit", wires=2, shots=None) a, b = np.array([0.543, -0.654], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -947,7 +947,7 @@ def test_overriding_shots_with_same_value(self, mocker): dev = qml.device("default.qubit", wires=2, shots=123) a, b = np.array([0.543, -0.654], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -980,7 +980,7 @@ def test_overriding_device_with_shot_vector(self): a, b = np.array([0.543, -0.654], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -1003,7 +1003,7 @@ def test_gradient_integration(self, tol): a, b = np.array([0.543, -0.654], requires_grad=True) def cost_fn(a, b, shots): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -1045,7 +1045,7 @@ def _cost_fn(weights, coeffs1, coeffs2, dev=None): obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) diff --git a/tests/interfaces/test_batch_autograd_qnode.py b/tests/interfaces/test_batch_autograd_qnode.py index be00e29c869..57c5f14ed06 100644 --- a/tests/interfaces/test_batch_autograd_qnode.py +++ b/tests/interfaces/test_batch_autograd_qnode.py @@ -17,7 +17,7 @@ import pennylane as qml from pennylane import qnode, QNode -from pennylane.tape import JacobianTape +from pennylane.tape import QuantumTape qubit_device_and_diff_method = [ ["default.qubit", "finite-diff", "backward"], @@ -394,7 +394,7 @@ def expand(self): theta, phi, lam = self.data wires = self.wires - with JacobianTape() as tape: + with QuantumTape() as tape: qml.Rot(lam, theta, -lam, wires=wires) qml.PhaseShift(phi + lam, wires=wires) diff --git a/tests/interfaces/test_batch_jax.py b/tests/interfaces/test_batch_jax.py index aef2bbed3a8..0b0580b276e 100644 --- a/tests/interfaces/test_batch_jax.py +++ b/tests/interfaces/test_batch_jax.py @@ -39,7 +39,7 @@ def test_jacobian_options(self, mocker, interface, tol): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -65,7 +65,7 @@ def test_incorrect_mode(self, interface): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -90,7 +90,7 @@ def test_unknown_interface(self, interface): dev = qml.device("default.qubit", wires=1) def cost(a, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -111,7 +111,7 @@ def test_forward_mode(self, interface, mocker): spy = mocker.spy(dev, "execute_and_gradients") def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -141,7 +141,7 @@ def test_backward_mode(self, interface, mocker): spy_gradients = mocker.spy(qml.devices.DefaultQubit, "gradients") def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -176,7 +176,7 @@ def test_max_diff_error(self, interface): InterfaceUnsupportedError, match="The JAX interface only supports first order derivatives.", ): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -201,7 +201,7 @@ def test_cache_maxsize(self, interface, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cachesize): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -228,7 +228,7 @@ def test_custom_cache(self, interface, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -253,12 +253,12 @@ def test_custom_cache_multiple(self, interface, mocker): b = jnp.array(0.2) def cost(a, b, cache): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) @@ -284,7 +284,7 @@ def test_caching_param_shift(self, interface, tol): dev = qml.device("default.qubit", wires=1) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -326,7 +326,7 @@ def test_caching_adjoint_backward(self, interface): params = jnp.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -382,12 +382,12 @@ def test_execution(self, execute_kwargs, interface): dev = qml.device("default.qubit", wires=1) def cost(a, b): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) @@ -408,7 +408,7 @@ def test_scalar_jacobian(self, execute_kwargs, interface, tol): dev = qml.device("default.qubit", wires=2) def cost(a): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) return execute([tape], dev, interface=interface, **execute_kwargs)[0][0] @@ -417,7 +417,7 @@ def cost(a): assert res.shape == () # compare to standard tape jacobian - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -435,7 +435,7 @@ def test_reusing_quantum_tape(self, execute_kwargs, interface, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -479,7 +479,7 @@ def test_classical_processing_single_tape(self, execute_kwargs, interface, tol): c = jnp.array(0.3) def cost(a, b, c, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + jnp.sin(a), wires=0) @@ -498,13 +498,13 @@ def test_classical_processing_multiple_tapes(self, execute_kwargs, interface, to params = jax.numpy.array([0.3, 0.2]) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.Hadamard(0) qml.RY(x[0], wires=[0]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.Hadamard(0) qml.CRX(2 * x[0] * x[1], wires=[0, 1]) qml.RX(2 * x[1], wires=[1]) @@ -524,13 +524,13 @@ def test_multiple_tapes_output(self, execute_kwargs, interface, tol): params = jax.numpy.array([0.3, 0.2]) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.Hadamard(0) qml.RY(x[0], wires=[0]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.Hadamard(0) qml.CRX(2 * x[0] * x[1], wires=[0, 1]) qml.RX(2 * x[1], wires=[1]) @@ -551,7 +551,7 @@ def test_matrix_parameter(self, execute_kwargs, interface, tol): U = qml.RY(a, wires=0).get_matrix() def cost(U, device): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.PauliX(0) qml.QubitUnitary(U, wires=0) qml.expval(qml.PauliZ(0)) @@ -573,7 +573,7 @@ def test_differentiable_expand(self, execute_kwargs, interface, tol): class U3(qml.U3): def expand(self): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() theta, phi, lam = self.data wires = self.wires tape._ops += [ @@ -583,7 +583,7 @@ def expand(self): return tape def cost_fn(a, p, device): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() with tape: qml.RX(a, wires=0) @@ -624,7 +624,7 @@ def test_independent_expval(self, execute_kwargs, interface): params = jnp.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -652,7 +652,7 @@ def test_raises_for_jax_jit(self, execute_kwargs, interface, ret, mes): params = jnp.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -686,7 +686,7 @@ def test_multiple_expvals(self, execute_kwargs): params = jnp.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -712,7 +712,7 @@ def test_multiple_expvals_single_par(self, execute_kwargs): params = jnp.array([0.1]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) @@ -732,14 +732,14 @@ def test_multi_tape_jacobian(self, execute_kwargs): pytest.skip("The forward mode is tested separately as it should raise an error.") def cost(x, y, device, interface, ek): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -777,14 +777,14 @@ def test_multi_tape_jacobian_probs_expvals(self, execute_kwargs): pytest.skip("The adjoint diff method doesn't support probabilities.") def cost(x, y, device, interface, ek): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) qml.expval(qml.PauliZ(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -820,7 +820,7 @@ def test_multiple_expvals_raises_fwd_device_grad(self, execute_kwargs): params = jnp.array([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) diff --git a/tests/interfaces/test_batch_jax_qnode.py b/tests/interfaces/test_batch_jax_qnode.py index b22a7dfa11c..159fc742580 100644 --- a/tests/interfaces/test_batch_jax_qnode.py +++ b/tests/interfaces/test_batch_jax_qnode.py @@ -18,7 +18,7 @@ import pennylane as qml from pennylane import qnode, QNode -from pennylane.tape import JacobianTape +from pennylane.tape import QuantumTape from pennylane.interfaces.batch import InterfaceUnsupportedError qubit_device_and_diff_method = [ @@ -176,7 +176,7 @@ def expand(self): theta, phi, lam = self.data wires = self.wires - with JacobianTape() as tape: + with QuantumTape() as tape: qml.Rot(lam, theta, -lam, wires=wires) qml.PhaseShift(phi + lam, wires=wires) diff --git a/tests/interfaces/test_batch_tensorflow.py b/tests/interfaces/test_batch_tensorflow.py index 837e2063a42..7d0c25157f7 100644 --- a/tests/interfaces/test_batch_tensorflow.py +++ b/tests/interfaces/test_batch_tensorflow.py @@ -37,7 +37,7 @@ def test_jacobian_options(self, mocker, tol): dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -63,7 +63,7 @@ def test_incorrect_mode(self): dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -80,7 +80,7 @@ def test_forward_mode(self, mocker): spy = mocker.spy(dev, "execute_and_gradients") with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -105,7 +105,7 @@ def test_backward_mode(self, mocker): a = tf.Variable([0.1, 0.2]) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -137,7 +137,7 @@ def test_cache_maxsize(self, mocker): a = tf.Variable([0.1, 0.2]) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -159,7 +159,7 @@ def test_custom_cache(self, mocker): custom_cache = {} with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -180,7 +180,7 @@ def test_caching_param_shift(self, tol): a = tf.Variable([0.1, 0.2], dtype=tf.float64) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -221,7 +221,7 @@ def test_caching_param_shift_hessian(self, num_params, tol): N = params.shape[0] def cost(x, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) @@ -302,12 +302,12 @@ def test_execution(self, execute_kwargs): b = tf.Variable(0.2) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) @@ -324,7 +324,7 @@ def test_scalar_jacobian(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) res = execute([tape], dev, **execute_kwargs)[0] @@ -333,7 +333,7 @@ def test_scalar_jacobian(self, execute_kwargs, tol): assert res.shape == (1,) # compare to standard tape jacobian - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -351,7 +351,7 @@ def test_jacobian(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -377,15 +377,15 @@ def test_tape_no_parameters(self, execute_kwargs, tol): x, y = 1.0 * params with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.Hadamard(0) qml.expval(qml.PauliX(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(0.5, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape3: + with qml.tape.QuantumTape() as tape3: qml.RY(params[0], wires=0) qml.RX(params[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -407,7 +407,7 @@ def test_reusing_quantum_tape(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -446,7 +446,7 @@ def test_reusing_pre_constructed_quantum_tape(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -486,7 +486,7 @@ def test_classical_processing(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=1) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a * c, wires=0) qml.RZ(b, wires=0) qml.RX(c + c**2 + tf.sin(a), wires=0) @@ -507,7 +507,7 @@ def test_no_trainable_parameters(self, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(0.2, wires=0) qml.RX(b, wires=0) qml.CNOT(wires=[0, 1]) @@ -533,7 +533,7 @@ def test_matrix_parameter(self, execute_kwargs, U, tol): with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -552,7 +552,7 @@ def test_differentiable_expand(self, execute_kwargs, tol): class U3(qml.U3): def expand(self): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() theta, phi, lam = self.data wires = self.wires tape._ops += [ @@ -561,7 +561,7 @@ def expand(self): ] return tape - qtape = qml.tape.JacobianTape() + qtape = qml.tape.QuantumTape() dev = qml.device("default.qubit", wires=1) a = np.array(0.1) @@ -611,7 +611,7 @@ def test_probability_differentiation(self, execute_kwargs, tol): y = tf.Variable(-0.654, dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -654,7 +654,7 @@ def test_ragged_differentiation(self, execute_kwargs, tol): y = tf.Variable(-0.654, dtype=tf.float64) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -685,7 +685,7 @@ def test_sampling(self, execute_kwargs): dev = qml.device("default.qubit", wires=2, shots=10) with tf.GradientTape() as t: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.sample(qml.PauliZ(0)) @@ -718,13 +718,13 @@ def test_parameter_shift_hessian(self, params, tol): with tf.GradientTape() as t2: with tf.GradientTape() as t1: - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(params[0], wires=0) qml.RY(params[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -760,7 +760,7 @@ def test_hessian_vector_valued(self, tol): with tf.GradientTape() as t2: with tf.GradientTape(persistent=True) as t1: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(params[0], wires=0) qml.RX(params[1], wires=0) qml.probs(wires=0) @@ -807,7 +807,7 @@ def test_adjoint_hessian(self, tol): with tf.GradientTape() as t2: with tf.GradientTape() as t1: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -836,13 +836,13 @@ def test_max_diff(self, tol): with tf.GradientTape() as t2: with tf.GradientTape() as t1: - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(params[0], wires=[0]) qml.RY(params[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(params[0], wires=0) qml.RY(params[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -889,7 +889,7 @@ def _cost_fn(weights, coeffs1, coeffs2, dev=None): obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) diff --git a/tests/interfaces/test_batch_tensorflow_qnode.py b/tests/interfaces/test_batch_tensorflow_qnode.py index a21f6fd2186..f4ddb8a365d 100644 --- a/tests/interfaces/test_batch_tensorflow_qnode.py +++ b/tests/interfaces/test_batch_tensorflow_qnode.py @@ -19,7 +19,7 @@ import pennylane as qml from pennylane import qnode, QNode -from pennylane.tape import JacobianTape +from pennylane.tape import QuantumTape qubit_device_and_diff_method = [ @@ -369,7 +369,7 @@ def expand(self): theta, phi, lam = self.data wires = self.wires - with JacobianTape() as tape: + with QuantumTape() as tape: qml.Rot(lam, theta, -lam, wires=wires) qml.PhaseShift(phi + lam, wires=wires) diff --git a/tests/interfaces/test_batch_torch.py b/tests/interfaces/test_batch_torch.py index b926d6083b1..e677d8fc810 100644 --- a/tests/interfaces/test_batch_torch.py +++ b/tests/interfaces/test_batch_torch.py @@ -35,7 +35,7 @@ def test_jacobian_options(self, mocker, tol): dev = qml.device("default.qubit", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -60,7 +60,7 @@ def test_incorrect_mode(self): dev = qml.device("default.qubit", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -78,7 +78,7 @@ def test_forward_mode_reuse_state(self, mocker): a = torch.tensor([0.1, 0.2], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -102,7 +102,7 @@ def test_forward_mode(self, mocker): a = torch.tensor([0.1, 0.2], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -127,7 +127,7 @@ def test_backward_mode(self, mocker): a = torch.tensor([0.1, 0.2], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -158,7 +158,7 @@ def test_cache_maxsize(self, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cachesize): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -182,7 +182,7 @@ def test_custom_cache(self, mocker): spy = mocker.spy(qml.interfaces.batch, "cache_execute") def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -205,7 +205,7 @@ def test_caching_param_shift(self, tol): dev = qml.device("default.qubit", wires=1) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.probs(wires=0) @@ -237,7 +237,7 @@ def test_caching_param_shift_hessian(self, num_params, tol): N = len(params) def cost(x, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) @@ -290,7 +290,7 @@ def test_caching_adjoint_backward(self): params = torch.tensor([0.1, 0.2, 0.3]) def cost(a, cache): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) @@ -362,12 +362,12 @@ def test_execution(self, torch_device, execute_kwargs): a = torch.tensor(0.1, requires_grad=True, device=torch_device) b = torch.tensor(0.2, requires_grad=False, device=torch_device) - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(a, wires=0) qml.RX(b, wires=0) qml.expval(qml.PauliZ(0)) @@ -383,7 +383,7 @@ def test_scalar_jacobian(self, torch_device, execute_kwargs, tol): a = torch.tensor(0.1, requires_grad=True, dtype=torch.float64, device=torch_device) dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -412,7 +412,7 @@ def test_jacobian(self, torch_device, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RZ(torch.tensor(0.543, device=torch_device), wires=0) qml.RY(a, wires=0) qml.RX(b, wires=1) @@ -449,15 +449,15 @@ def test_tape_no_parameters(self, torch_device, execute_kwargs, tol): params = torch.tensor([0.1, 0.2], requires_grad=True, device=torch_device) x, y = params.detach() - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.Hadamard(0) qml.expval(qml.PauliX(0)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RY(0.5, wires=0) qml.expval(qml.PauliZ(0)) - with qml.tape.JacobianTape() as tape3: + with qml.tape.QuantumTape() as tape3: qml.RY(params[0], wires=0) qml.RX(params[1], wires=0) qml.expval(qml.PauliZ(0)) @@ -484,7 +484,7 @@ def test_reusing_quantum_tape(self, torch_device, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) @@ -534,7 +534,7 @@ def test_classical_processing(self, torch_device, execute_kwargs, tol): dev = qml.device("default.qubit", wires=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(params[0] * params[1], wires=0) qml.RZ(0.2, wires=0) qml.RX(params[1] + params[1] ** 2 + torch.sin(params[0]), wires=0) @@ -567,7 +567,7 @@ def test_no_trainable_parameters(self, torch_device, execute_kwargs, tol): """Test evaluation and Jacobian if there are no trainable parameters""" dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(0.2, wires=0) qml.RX(torch.tensor(0.1, device=torch_device), wires=0) qml.CNOT(wires=[0, 1]) @@ -600,7 +600,7 @@ def test_matrix_parameter(self, torch_device, U, execute_kwargs, tol): dev = qml.device("default.qubit", wires=2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitUnitary(U, wires=0) qml.RY(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -621,7 +621,7 @@ def test_differentiable_expand(self, torch_device, execute_kwargs, tol): class U3(qml.U3): def expand(self): - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() theta, phi, lam = self.data wires = self.wires tape._ops += [ @@ -630,7 +630,7 @@ def expand(self): ] return tape - tape = qml.tape.JacobianTape() + tape = qml.tape.QuantumTape() dev = qml.device("default.qubit", wires=1) a = np.array(0.1) @@ -698,7 +698,7 @@ def test_probability_differentiation(self, torch_device, execute_kwargs, tol): x = torch.tensor(x_val, requires_grad=True, device=torch_device) y = torch.tensor(y_val, requires_grad=True, device=torch_device) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -745,7 +745,7 @@ def test_ragged_differentiation(self, torch_device, execute_kwargs, tol): x = torch.tensor(x_val, requires_grad=True, device=torch_device) y = torch.tensor(y_val, requires_grad=True, device=torch_device) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) @@ -787,7 +787,7 @@ def test_sampling(self, torch_device, execute_kwargs): dev = qml.device("default.qubit", wires=2, shots=10) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.sample(qml.PauliZ(0)) @@ -805,7 +805,7 @@ def test_sampling_expval(self, torch_device, execute_kwargs): dev = qml.device("default.qubit", wires=2, shots=10) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.sample(qml.PauliZ(0)) @@ -828,7 +828,7 @@ def test_sampling_gradient_error(self, torch_device, execute_kwargs): x = torch.tensor(0.65, requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=[0]) qml.sample(qml.PauliZ(0)) @@ -848,7 +848,7 @@ def test_repeated_application_after_expand(self, torch_device, execute_kwargs, t weights = torch.ones((3,)) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.U3(*weights, wires=0) qml.expval(qml.PauliZ(wires=0)) @@ -875,13 +875,13 @@ def test_parameter_shift_hessian(self, torch_device, params, tol): params = torch.tensor([0.543, -0.654], requires_grad=True, dtype=torch.float64) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -917,7 +917,7 @@ def test_hessian_vector_valued(self, torch_device, tol): dev = qml.device("default.qubit", wires=1) def circuit(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(x[0], wires=0) qml.RX(x[1], wires=0) qml.probs(wires=0) @@ -985,7 +985,7 @@ def test_adjoint_hessian(self, torch_device, tol): ) def cost_fn(x): - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) @@ -1010,13 +1010,13 @@ def test_max_diff(self, torch_device, tol): params = torch.tensor([0.543, -0.654], requires_grad=True, dtype=torch.float64) def cost_fn(x): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) @@ -1067,7 +1067,7 @@ def _cost_fn(weights, coeffs1, coeffs2, dev=None): obs2 = [qml.PauliZ(0)] H2 = qml.Hamiltonian(coeffs2, obs2) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) diff --git a/tests/interfaces/test_batch_torch_qnode.py b/tests/interfaces/test_batch_torch_qnode.py index fe18a1aa039..8dc910be533 100644 --- a/tests/interfaces/test_batch_torch_qnode.py +++ b/tests/interfaces/test_batch_torch_qnode.py @@ -20,7 +20,7 @@ import pennylane as qml from pennylane import qnode, QNode -from pennylane.tape import JacobianTape +from pennylane.tape import QuantumTape qubit_device_and_diff_method = [ @@ -385,7 +385,7 @@ def expand(self): theta, phi, lam = self.data wires = self.wires - with JacobianTape() as tape: + with QuantumTape() as tape: qml.Rot(lam, theta, -lam, wires=wires) qml.PhaseShift(phi + lam, wires=wires) diff --git a/tests/ops/functions/test_generator.py b/tests/ops/functions/test_generator.py index ab3fc72f892..a21c7c1b557 100644 --- a/tests/ops/functions/test_generator.py +++ b/tests/ops/functions/test_generator.py @@ -136,6 +136,47 @@ def test_unknown_format(self): qml.generator(qml.RX, format=None)(0.5, wires=0) +class TestBackwardsCompatibility: + """Test that operators that provide the old style generator property + continue to work with a deprecation warning.""" + + def test_return_class(self): + """Test that an old-style Operator that has a generator property, + and that returns a list containing (a) operator class and (b) prefactor, + continues to work but also raises a deprecation warning.""" + + class DeprecatedClassOp(CustomOp): + generator = [qml.PauliX, -0.6] + + op = DeprecatedClassOp(0.5, wires="a") + + with pytest.warns(UserWarning, match=r"The Operator\.generator property is deprecated"): + gen, prefactor = qml.generator(op) + + assert isinstance(gen, qml.operation.Operator) + assert prefactor == -0.6 + assert gen.name == "PauliX" + assert gen.wires.tolist() == ["a"] + + def test_return_array(self): + """Test that an old-style Operator that has a generator property, + and that returns a list containing (a) array and (b) prefactor, + continues to work but also raises a deprecation warning.""" + + class DeprecatedClassOp(CustomOp): + generator = [np.diag([0, 1]), -0.6] + + op = DeprecatedClassOp(0.5, wires="a") + + with pytest.warns(UserWarning, match=r"The Operator\.generator property is deprecated"): + gen, prefactor = qml.generator(op) + + assert isinstance(gen, qml.operation.Operator) + assert prefactor == -0.6 + assert gen.name == "Hermitian" + assert gen.wires.tolist() == ["a"] + + class TestPrefactorReturn: """Tests for format="prefactor". This format attempts to isolate a prefactor (if possible) from the generator, which is useful if the generator is a Pauli word.""" diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py index 2f96b0f12b8..e600156c662 100644 --- a/tests/ops/qubit/test_matrix_ops.py +++ b/tests/ops/qubit/test_matrix_ops.py @@ -453,6 +453,13 @@ def test_matrix_representation(self, tol): assert np.allclose(res_static, expected, atol=tol) assert np.allclose(res_dynamic, expected, atol=tol) + def test_no_decomp(self): + """Test that ControlledQubitUnitary raises a decomposition undefined + error.""" + mat = qml.PauliX(0).get_matrix() + with pytest.raises(qml.operation.DecompositionUndefinedError): + qml.ControlledQubitUnitary(mat, wires=0, control_wires=1).decomposition() + label_data = [ (X, qml.QubitUnitary(X, wires=0)), diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py index 8924018b88f..41e8c77bacd 100644 --- a/tests/ops/qubit/test_non_parametric_ops.py +++ b/tests/ops/qubit/test_non_parametric_ops.py @@ -554,6 +554,27 @@ def barrier(): assert queue[1].name == "Barrier" assert queue[4].name == "Barrier" + def test_barrier_control(self): + """Test if Barrier is correctly included in queue when controlling""" + dev = qml.device("default.qubit", wires=3) + + def barrier(): + qml.PauliX(wires=0) + qml.Barrier(wires=[0, 1]) + qml.CNOT(wires=[0, 1]) + + @qml.qnode(dev) + def circuit(): + barrier() + qml.ctrl(barrier, 2)() + return qml.state() + + circuit() + tape = circuit.tape.expand(stop_at=lambda op: op.name in ["Barrier", "PauliX", "CNOT"]) + + assert tape.operations[1].name == "Barrier" + assert tape.operations[4].name == "Barrier" + class TestWireCut: """Tests for the WireCut operator""" diff --git a/tests/ops/test_snapshot.py b/tests/ops/test_snapshot.py index b3b8bc50923..8d68631a166 100644 --- a/tests/ops/test_snapshot.py +++ b/tests/ops/test_snapshot.py @@ -26,3 +26,15 @@ def test_label_method(): """Test the label method for the Snapshot operation.""" assert Snapshot().label() == "|S|" assert Snapshot("my_label").label() == "|S|" + + +def test_control(): + """Test the _controlled method for the Snapshot operation.""" + assert isinstance(Snapshot()._controlled(0), Snapshot) + assert Snapshot("my_label")._controlled(0).tag == Snapshot("my_label").tag + + +def test_adjoint(): + """Test the adjoint method for the Snapshot operation.""" + assert isinstance(Snapshot().adjoint(), Snapshot) + assert Snapshot("my_label").adjoint().tag == Snapshot("my_label").tag diff --git a/tests/qnn/test_keras.py b/tests/qnn/test_keras.py index 4c123be90f5..4bdb39acf4d 100644 --- a/tests/qnn/test_keras.py +++ b/tests/qnn/test_keras.py @@ -479,7 +479,7 @@ def f(inputs, weights): with tf.GradientTape() as tape: out = tf.reduce_sum(qlayer(inputs)) - spy = mocker.spy(qml.tape.QubitParamShiftTape, "jacobian") + spy = mocker.spy(qml.gradients, "param_shift") grad = tape.gradient(out, qlayer.trainable_weights) assert grad is not None diff --git a/tests/tape/test_qnode_old.py b/tests/tape/test_qnode_old.py index 6f6e27e4d6e..668ae05fc08 100644 --- a/tests/tape/test_qnode_old.py +++ b/tests/tape/test_qnode_old.py @@ -606,6 +606,11 @@ def test_unrecognized_keyword_arguments_validation(self): ) expected_warnings = { + ( + UserWarning, + f"qml.qnode_old.QNode is deprecated, and will be removed in an " + "upcoming release. Please use qml.QNode instead.", + ), (UserWarning, f"'{unrecognized_one}'{warning_text}"), (UserWarning, f"'{unrecognized_two}'{warning_text}"), } @@ -632,6 +637,11 @@ def test_unrecognized_keyword_arguments_validation_decorator(self): ) expected_warnings = { + ( + UserWarning, + f"qml.qnode_old.QNode is deprecated, and will be removed in an " + "upcoming release. Please use qml.QNode instead.", + ), (UserWarning, f"'{unrecognized_one}'{warning_text}"), (UserWarning, f"'{unrecognized_two}'{warning_text}"), } diff --git a/tests/templates/test_subroutines/test_approx_time_evolution.py b/tests/templates/test_subroutines/test_approx_time_evolution.py index 12a91d53beb..7d4a73fc28e 100644 --- a/tests/templates/test_subroutines/test_approx_time_evolution.py +++ b/tests/templates/test_subroutines/test_approx_time_evolution.py @@ -374,7 +374,7 @@ def test_trainable_hamiltonian(dev_name, diff_method): def create_tape(coeffs, t): H = qml.Hamiltonian(coeffs, obs) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.templates.ApproxTimeEvolution(H, t, 2) qml.expval(qml.PauliZ(0)) diff --git a/tests/templates/test_subroutines/test_qpe.py b/tests/templates/test_subroutines/test_qpe.py index 04659af861c..11e6c21449c 100644 --- a/tests/templates/test_subroutines/test_qpe.py +++ b/tests/templates/test_subroutines/test_qpe.py @@ -64,7 +64,7 @@ def test_phase_estimated(self, phase): tape = tape.expand(depth=2, stop_at=lambda obj: obj.name in dev.operations) - res = tape.execute(dev).flatten() + res = dev.execute(tape).flatten() initial_estimate = np.argmax(res) / 2 ** (wires - 1) # We need to rescale because RX is exp(- i theta X / 2) and we expect a unitary of the @@ -112,7 +112,7 @@ def test_phase_estimated_two_qubit(self): qml.probs(estimation_wires) tape = tape.expand(depth=2, stop_at=lambda obj: obj.name in dev.operations) - res = tape.execute(dev).flatten() + res = dev.execute(tape).flatten() if phase < 0: estimate = np.argmax(res) / 2 ** (wires - 2) - 1 diff --git a/tests/test_debugging.py b/tests/test_debugging.py index 1a933f3f85d..ad09e7484d5 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -22,11 +22,12 @@ class TestSnapshot: """Test the Snapshot instruction for simulators.""" - def test_default_qubit(self): + @pytest.mark.parametrize("method", [None, "backprop", "parameter-shift", "adjoint"]) + def test_default_qubit(self, method): """Test that multiple snapshots are returned correctly on the state-vector simulator.""" dev = qml.device("default.qubit", wires=2) - @qml.qnode(dev) + @qml.qnode(dev, diff_method=method) def circuit(): qml.Snapshot() qml.Hadamard(wires=0) @@ -49,11 +50,12 @@ def circuit(): assert all(k1 == k2 for k1, k2 in zip(result.keys(), expected.keys())) assert all(np.allclose(v1, v2) for v1, v2 in zip(result.values(), expected.values())) - def test_default_mixed(self): + @pytest.mark.parametrize("method", [None, "parameter-shift"]) + def test_default_mixed(self, method): """Test that multiple snapshots are returned correctly on the density-matrix simulator.""" dev = qml.device("default.mixed", wires=2) - @qml.qnode(dev) + @qml.qnode(dev, diff_method=method) def circuit(): qml.Snapshot() qml.Hadamard(wires=0) @@ -78,11 +80,12 @@ def circuit(): assert all(k1 == k2 for k1, k2 in zip(result.keys(), expected.keys())) assert all(np.allclose(v1, v2) for v1, v2 in zip(result.values(), expected.values())) - def test_default_gaussian(self): + @pytest.mark.parametrize("method", [None, "parameter-shift"]) + def test_default_gaussian(self, method): """Test that multiple snapshots are returned correctly on the CV simulator.""" dev = qml.device("default.gaussian", wires=2) - @qml.qnode(dev) + @qml.qnode(dev, diff_method=method) def circuit(): qml.Snapshot() qml.Displacement(0.5, 0, wires=0) @@ -124,11 +127,12 @@ def circuit(): for v1, v2 in zip(result.values(), expected.values()) ) - def test_lightning_qubit(self): + @pytest.mark.parametrize("method", [None, "parameter-shift", "adjoint"]) + def test_lightning_qubit(self, method): """Test that an error is (currently) raised on the lightning simulator.""" dev = qml.device("lightning.qubit", wires=2) - @qml.qnode(dev) + @qml.qnode(dev, diff_method=method) def circuit(): qml.Snapshot() qml.Hadamard(wires=0) @@ -253,26 +257,26 @@ def circuit(): if m == "state": assert np.allclose(result[2], result["execution_results"]) - @pytest.mark.parametrize("method", [None, "backprop", "parameter-shift", "adjoint"]) - def test_different_diff_methods(self, method): - """Test that snapshots work with different differentiation methods.""" + def test_controlled_circuit(self): + """Test that snapshots are returned correctly even with a controlled circuit.""" dev = qml.device("default.qubit", wires=2) - @qml.qnode(dev, diff_method=method) - def circuit(): + def circuit(params, wire): + qml.Hadamard(wire) qml.Snapshot() - qml.Hadamard(wires=0) - qml.Snapshot("very_important_state") - qml.CNOT(wires=[0, 1]) - qml.Snapshot() - return qml.expval(qml.PauliZ(0)) + qml.Rot(*params, wire) - result = qml.snapshots(circuit)() + @qml.qnode(dev) + def qnode(params): + qml.Hadamard(0) + qml.ctrl(circuit, 0)(params, wire=1) + return qml.expval(qml.PauliZ(1)) + + params = np.array([1.3, 1.4, 0.2]) + result = qml.snapshots(qnode)(params) expected = { - 0: np.array([1, 0, 0, 0]), - "very_important_state": np.array([1 / np.sqrt(2), 0, 1 / np.sqrt(2), 0]), - 2: np.array([1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2)]), - "execution_results": np.array(0), + 0: np.array([1 / np.sqrt(2), 0, 0.5, 0.5]), + "execution_results": np.array(0.36819668), } assert all(k1 == k2 for k1, k2 in zip(result.keys(), expected.keys())) diff --git a/tests/test_device.py b/tests/test_device.py index af577763506..9e296c19113 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -918,7 +918,7 @@ class TestBatchExecution: qml.PauliX(wires=0) qml.expval(qml.PauliZ(wires=0)), qml.expval(qml.PauliZ(wires=1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.PauliX(wires=0) qml.expval(qml.PauliZ(wires=0)) diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 90cb7081e6e..c59f07f23b1 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -347,10 +347,27 @@ def test_measurement_value_inversion(self, val_pair, num_inv, expected_idx): one_case = val_pair[1] mv = MeasurementValue(measurement_id="1234", zero_case=zero_case, one_case=one_case) for _ in range(num_inv): - mv = mv.__invert__() + mv_new = mv.__invert__() + + # Check that inversion involves creating a copy + assert not mv_new is mv + + mv = mv_new assert mv._control_value == val_pair[expected_idx] + def test_measurement_value_assertion_error_wrong_type(self): + """Test that the return_type related info is updated for a + measurement.""" + mv1 = MeasurementValue(measurement_id="1111") + mv2 = MeasurementValue(measurement_id="2222") + + with pytest.raises( + MeasurementValueError, + match="The equality operator is used to assert measurement outcomes, but got a value with type", + ): + mv1 == mv2 + def test_measurement_value_assertion_error(self): """Test that the return_type related info is updated for a measurement.""" diff --git a/tests/test_qnode.py b/tests/test_qnode.py index c39c36407ba..c4a40b04399 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -21,7 +21,7 @@ from pennylane import numpy as pnp from pennylane import qnode, QNode from pennylane.transforms import draw -from pennylane.tape import JacobianTape +from pennylane.tape import QuantumTape def dummyfunc(): @@ -408,12 +408,12 @@ def func(x, y): res = qn(x, y) - assert isinstance(qn.qtape, JacobianTape) + assert isinstance(qn.qtape, QuantumTape) assert len(qn.qtape.operations) == 3 assert len(qn.qtape.observables) == 1 assert qn.qtape.num_params == 2 - expected = qn.qtape.execute(dev) + expected = qml.execute([qn.tape], dev, None) assert np.allclose(res, expected, atol=tol, rtol=0) # when called, a new quantum tape is constructed @@ -614,12 +614,12 @@ def func(x, y): res = func(x, y) - assert isinstance(func.qtape, JacobianTape) + assert isinstance(func.qtape, QuantumTape) assert len(func.qtape.operations) == 3 assert len(func.qtape.observables) == 1 assert func.qtape.num_params == 2 - expected = func.qtape.execute(dev) + expected = qml.execute([func.tape], dev, None) assert np.allclose(res, expected, atol=tol, rtol=0) # when called, a new quantum tape is constructed diff --git a/tests/test_qubit_device.py b/tests/test_qubit_device.py index 97b6f48f56b..639cdecc1af 100644 --- a/tests/test_qubit_device.py +++ b/tests/test_qubit_device.py @@ -832,7 +832,7 @@ class TestBatchExecution: qml.PauliX(wires=0) qml.expval(qml.PauliZ(wires=0)), qml.expval(qml.PauliZ(wires=1)) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.PauliX(wires=0) qml.expval(qml.PauliZ(wires=0)) diff --git a/tests/test_qubit_device_adjoint_jacobian.py b/tests/test_qubit_device_adjoint_jacobian.py index 10a2bfcfc6b..4984c737592 100644 --- a/tests/test_qubit_device_adjoint_jacobian.py +++ b/tests/test_qubit_device_adjoint_jacobian.py @@ -32,7 +32,7 @@ def test_not_expval(self, dev): """Test if a QuantumFunctionError is raised for a tape with measurements that are not expectation values""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.1, wires=0) qml.var(qml.PauliZ(0)) @@ -44,7 +44,7 @@ def test_finite_shots_warns(self): dev = qml.device("default.qubit", wires=1, shots=1) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.expval(qml.PauliZ(0)) with pytest.warns( @@ -56,7 +56,7 @@ def test_unsupported_op(self, dev): """Test if a QuantumFunctionError is raised for an unsupported operation, i.e., multi-parameter operations that are not qml.Rot""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.CRot(0.1, 0.2, 0.3, wires=[0, 1]) qml.expval(qml.PauliZ(0)) @@ -68,7 +68,7 @@ def test_unsupported_op(self, dev): def test_pauli_rotation_gradient(self, G, theta, tol, dev): """Tests that the automatic gradients of Pauli rotations are correct.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) G(theta, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -88,7 +88,7 @@ def test_Rot_gradient(self, theta, tol, dev): correct.""" params = np.array([theta, theta**3, np.sqrt(2) * theta]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.QubitStateVector(np.array([1.0, -1.0], requires_grad=False) / np.sqrt(2), wires=0) qml.Rot(*params, wires=[0]) qml.expval(qml.PauliZ(0)) @@ -106,7 +106,7 @@ def test_Rot_gradient(self, theta, tol, dev): def test_ry_gradient(self, par, tol, dev): """Test that the gradient of the RY gate matches the exact analytic formula.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RY(par, wires=[0]) qml.expval(qml.PauliX(0)) @@ -125,7 +125,7 @@ def test_rx_gradient(self, tol, dev): """Test that the gradient of the RX gate matches the known formula.""" a = 0.7418 - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(a, wires=0) qml.expval(qml.PauliZ(0)) @@ -139,7 +139,7 @@ def test_multiple_rx_gradient(self, tol): dev = qml.device("default.qubit", wires=3) params = np.array([np.pi, np.pi / 2, np.pi / 3]) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(params[0], wires=0) qml.RX(params[1], wires=1) qml.RX(params[2], wires=2) @@ -162,7 +162,7 @@ def test_gradients(self, op, obs, tol, dev): """Tests that the gradients of circuits match between the finite difference and device methods.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=0) qml.RX(0.543, wires=0) qml.CNOT(wires=[0, 1]) @@ -188,7 +188,7 @@ def test_gradient_gate_with_multiple_parameters(self, tol, dev): """Tests that gates with multiple free parameters yield correct gradients.""" x, y, z = [0.5, 0.3, -0.7] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=[0]) qml.Rot(x, y, z, wires=[0]) qml.RY(-0.2, wires=[0]) @@ -210,7 +210,7 @@ def test_use_device_state(self, tol, dev): x, y, z = [0.5, 0.3, -0.7] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=[0]) qml.Rot(x, y, z, wires=[0]) qml.RY(-0.2, wires=[0]) @@ -220,7 +220,7 @@ def test_use_device_state(self, tol, dev): dM1 = dev.adjoint_jacobian(tape) - tape.execute(dev) + qml.execute([tape], dev, None) dM2 = dev.adjoint_jacobian(tape, use_device_state=True) assert np.allclose(dM1, dM2, atol=tol, rtol=0) @@ -229,7 +229,7 @@ def test_provide_starting_state(self, tol, dev): """Tests provides correct answer when provided starting state.""" x, y, z = [0.5, 0.3, -0.7] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=[0]) qml.Rot(x, y, z, wires=[0]) qml.RY(-0.2, wires=[0]) @@ -239,7 +239,7 @@ def test_provide_starting_state(self, tol, dev): dM1 = dev.adjoint_jacobian(tape) - tape.execute(dev) + qml.execute([tape], dev, None) dM2 = dev.adjoint_jacobian(tape, starting_state=dev._pre_rotated_state) assert np.allclose(dM1, dM2, atol=tol, rtol=0) diff --git a/tests/transforms/test_adjoint.py b/tests/transforms/test_adjoint.py index 4ca0845f98e..b87349705a9 100644 --- a/tests/transforms/test_adjoint.py +++ b/tests/transforms/test_adjoint.py @@ -134,7 +134,7 @@ def my_circuit(): ) -with qml.tape.JacobianTape() as tape: +with qml.tape.QuantumTape() as tape: qml.PauliX(0) qml.Hadamard(1) diff --git a/tests/transforms/test_adjoint_metric_tensor.py b/tests/transforms/test_adjoint_metric_tensor.py index 0684c9efab2..44752bf7e06 100644 --- a/tests/transforms/test_adjoint_metric_tensor.py +++ b/tests/transforms/test_adjoint_metric_tensor.py @@ -819,7 +819,7 @@ def ansatz(x, y): def test_error_finite_shots(self): """Test that an error is raised if the device has a finite number of shots set.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.RY(1.9, wires=1) dev = qml.device("default.qubit", wires=2, shots=1) diff --git a/tests/transforms/test_batch_input.py b/tests/transforms/test_batch_input.py new file mode 100644 index 00000000000..c03fd3198fc --- /dev/null +++ b/tests/transforms/test_batch_input.py @@ -0,0 +1,128 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the batch inputs transform. +""" +import pytest + +import pennylane as qml +from pennylane import numpy as np + + +def test_simple_circuit(): + """Test that batching works for a simple circuit""" + dev = qml.device("default.qubit", wires=2) + + @qml.batch_input(argnum=0) + @qml.qnode(dev, diff_method="parameter-shift") + def circuit(inputs, weights): + qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") + qml.RY(weights[0], wires=0) + qml.RY(weights[1], wires=1) + return qml.expval(qml.PauliZ(1)) + + batch_size = 5 + inputs = np.random.uniform(0, np.pi, (batch_size, 2)) + inputs.requires_grad = False + weights = np.random.uniform(-np.pi, np.pi, (2,)) + + res = circuit(inputs, weights) + assert res.shape == (batch_size,) + + +def test_value_error(): + """Test if the batch_input raises relevant errors correctly""" + + dev = qml.device("default.qubit", wires=2) + + @qml.batch_input(argnum=[0, 1]) + @qml.qnode(dev, diff_method="parameter-shift") + def circuit(input1, input2, weights): + qml.AngleEmbedding(input1, wires=range(2), rotation="Y") + qml.RY(input2[0], wires=0) + qml.RY(weights[0], wires=0) + qml.RY(weights[1], wires=1) + return qml.expval(qml.PauliZ(1)) + + batch_size = 5 + input1 = np.random.uniform(0, np.pi, (batch_size, 2)) + input1.requires_grad = False + input2 = np.random.uniform(0, np.pi, (4, 1)) + input2.requires_grad = False + weights = np.random.uniform(-np.pi, np.pi, (2,)) + + with pytest.raises(ValueError, match="Batch dimension for all gate arguments"): + res = circuit(input1, input2, weights) + + +def test_mottonenstate_preparation(mocker): + """Test that batching works for MottonenStatePreparation""" + dev = qml.device("default.qubit", wires=3) + + @qml.batch_input(argnum=0) + @qml.qnode(dev) + def circuit(data, weights): + qml.templates.MottonenStatePreparation(data, wires=[0, 1, 2]) + qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1, 2]) + return qml.probs(wires=[0, 1, 2]) + + batch_size = 3 + + # create a batched input statevector + data = np.random.random((batch_size, 2**3), requires_grad=False) + data /= np.linalg.norm(data, axis=1).reshape(-1, 1) # normalize + + # weights is not batched + weights = np.random.random((10, 3, 3), requires_grad=True) + + spy = mocker.spy(circuit.device, "batch_execute") + res = circuit(data, weights) + assert res.shape == (batch_size, 2**3) + assert len(spy.call_args[0][0]) == batch_size + + # check the results against individually executed circuits (no batching) + @qml.qnode(dev) + def circuit2(data, weights): + qml.templates.MottonenStatePreparation(data, wires=[0, 1, 2]) + qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1, 2]) + return qml.probs(wires=[0, 1, 2]) + + indiv_res = [] + for state in data: + indiv_res.append(circuit2(state, weights)) + assert np.allclose(res, indiv_res) + + +@pytest.mark.parametrize("diff_method", ["backprop", "adjoint", "parameter-shift"]) +def test_autograd(diff_method, tol): + """Test derivatives when using autograd""" + dev = qml.device("default.qubit", wires=2) + + @qml.batch_input + @qml.qnode(dev, diff_method=diff_method) + def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.1, wires=1) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) + + def cost(x): + return np.sum(circuit(x)) + + batch_size = 3 + x = np.linspace(0.1, 0.5, batch_size, requires_grad=True) + + res = qml.grad(cost)(x) + expected = -np.sin(0.1) * np.sin(x) + assert np.allclose(res, expected, atol=tol, rtol=0) diff --git a/tests/transforms/test_batch_transform.py b/tests/transforms/test_batch_transform.py index 586d426d895..f2789878259 100644 --- a/tests/transforms/test_batch_transform.py +++ b/tests/transforms/test_batch_transform.py @@ -493,8 +493,8 @@ def my_transform(tape, weights): """Generates two tapes, one with all RX replaced with RY, and the other with all RX replaced with RZ.""" - tape1 = qml.tape.JacobianTape() - tape2 = qml.tape.JacobianTape() + tape1 = qml.tape.QuantumTape() + tape2 = qml.tape.QuantumTape() # loop through all operations on the input tape for op in tape.operations + tape.measurements: @@ -650,13 +650,13 @@ def test_result(self, mocker): x = 0.6 y = 0.7 - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(H) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.Hadamard(wires=0) qml.CRX(x, wires=[0, 1]) qml.CNOT(wires=[0, 1]) @@ -683,13 +683,13 @@ def test_differentiation(self): weights = np.array([0.6, 0.8], requires_grad=True) def cost(weights): - with qml.tape.JacobianTape() as tape1: + with qml.tape.QuantumTape() as tape1: qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) qml.expval(H) - with qml.tape.JacobianTape() as tape2: + with qml.tape.QuantumTape() as tape2: qml.Hadamard(wires=0) qml.CRX(weights[0], wires=[0, 1]) qml.CNOT(wires=[0, 1]) diff --git a/tests/transforms/test_condition.py b/tests/transforms/test_condition.py index efe98f9e1d5..a131d44edf7 100644 --- a/tests/transforms/test_condition.py +++ b/tests/transforms/test_condition.py @@ -69,9 +69,32 @@ def f(x): assert ops[4].return_type == qml.operation.Probability - def test_cond_queues_with_else(self): - """Test that qml.cond queues Conditional operations as expected when an - else qfunc is also provided.""" + def tape_with_else(f, g, r): + """Tape that uses cond by passing both a true and false func.""" + with qml.tape.QuantumTape() as tape: + m_0 = qml.measure(0) + qml.cond(m_0, f, g)(r) + qml.probs(wires=1) + + return tape + + def tape_uses_cond_twice(f, g, r): + """Tape that uses cond twice such that it's equivalent to using cond + with two functions being passed (tape_with_else).""" + with qml.tape.QuantumTape() as tape: + m_0 = qml.measure(0) + qml.cond(m_0, f)(r) + qml.cond(~m_0, g)(r) + qml.probs(wires=1) + + return tape + + @pytest.mark.parametrize("tape", [tape_with_else, tape_uses_cond_twice]) + def test_cond_queues_with_else(self, tape): + """Test that qml.cond queues Conditional operations as expected in two cases: + 1. When an else qfunc is provided; + 2. When qml.cond is used twice equivalent to using an else qfunc. + """ r = 1.234 def f(x): @@ -82,11 +105,7 @@ def f(x): def g(x): qml.PauliY(1) - with qml.tape.QuantumTape() as tape: - m_0 = qml.measure(0) - qml.cond(m_0, f, g)(r) - qml.probs(wires=1) - + tape = tape(f, g, r) ops = tape.queue target_wire = qml.wires.Wires(1) @@ -111,6 +130,13 @@ def g(x): assert isinstance(ops[4].then_op, qml.PauliY) assert ops[4].then_op.wires == target_wire + # Check that: the measurement value is the same for true_fn conditional + # ops + assert ops[1].meas_val is ops[2].meas_val is ops[3].meas_val + + # However, it is not the same for the false_fn + assert ops[3].meas_val is not ops[4].meas_val + assert ops[5].return_type == qml.operation.Probability def test_cond_error(self): diff --git a/tests/transforms/test_hamiltonian_expand.py b/tests/transforms/test_hamiltonian_expand.py index a849b833ed7..441a04e7fc0 100644 --- a/tests/transforms/test_hamiltonian_expand.py +++ b/tests/transforms/test_hamiltonian_expand.py @@ -160,7 +160,7 @@ def test_hamiltonian_dif_autograd(self, tol): 0.64123, ] - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: for i in range(2): qml.RX(np.array(0), wires=0) qml.RX(np.array(0), wires=1) @@ -204,7 +204,7 @@ def test_hamiltonian_dif_tensorflow(self): ] with tf.GradientTape() as gtape: - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: for i in range(2): qml.RX(var[i, 0], wires=0) qml.RX(var[i, 1], wires=1) diff --git a/tests/transforms/test_metric_tensor.py b/tests/transforms/test_metric_tensor.py index dd6c0e9eddd..434ab5e0591 100644 --- a/tests/transforms/test_metric_tensor.py +++ b/tests/transforms/test_metric_tensor.py @@ -580,7 +580,7 @@ def test_multi_qubit_gates(self): """Test that a tape with Ising gates has the correct metric tensor tapes.""" dev = qml.device("default.qubit", wires=3) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.Hadamard(0) qml.Hadamard(2) qml.IsingXX(0.2, wires=[0, 1]) @@ -1353,7 +1353,7 @@ def aux_wire_ansatz_1(x, y): def test_get_aux_wire(aux_wire, ansatz): """Test ``_get_aux_wire`` without device_wires.""" x, y = np.array([0.2, 0.1], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: ansatz(x, y) out = _get_aux_wire(aux_wire, tape, None) @@ -1366,7 +1366,7 @@ def test_get_aux_wire(aux_wire, ansatz): def test_get_aux_wire_with_device_wires(): """Test ``_get_aux_wire`` with device_wires.""" x, y = np.array([0.2, 0.1], requires_grad=True) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(x, wires=0) qml.RX(x, wires="one") diff --git a/tests/transforms/test_optimization/test_undo_swaps.py b/tests/transforms/test_optimization/test_undo_swaps.py index 64606dc0497..c968464970d 100644 --- a/tests/transforms/test_optimization/test_undo_swaps.py +++ b/tests/transforms/test_optimization/test_undo_swaps.py @@ -36,7 +36,7 @@ def qfunc(): transformed_qfunc = undo_swaps(qfunc) tape = qml.transforms.make_tape(transformed_qfunc)() - res = tape.execute(qml.device("default.qubit", wires=2)) + res = qml.device("default.qubit", wires=2).execute(tape) assert len(tape.operations) == 2 assert np.allclose(res[0][0], 0.5) @@ -52,7 +52,7 @@ def qfunc(): transformed_qfunc = undo_swaps(qfunc) tape = qml.transforms.make_tape(transformed_qfunc)() - res = tape.execute(qml.device("default.qubit", wires=2)) + res = qml.device("default.qubit", wires=2).execute(tape) assert len(tape.operations) == 2 assert np.allclose(res[0][2], 1.0) @@ -75,10 +75,10 @@ def qfunc2(): transformed_qfunc = undo_swaps(qfunc1) tape1 = qml.transforms.make_tape(transformed_qfunc)() - res1 = tape1.execute(qml.device("default.qubit", wires=3)) + res1 = qml.device("default.qubit", wires=3).execute(tape1) tape2 = qml.transforms.make_tape(qfunc2)() - res2 = tape2.execute(qml.device("default.qubit", wires=3)) + res2 = qml.device("default.qubit", wires=3).execute(tape2) assert np.allclose(res1, res2) @@ -102,10 +102,10 @@ def qfunc2(): transformed_qfunc = undo_swaps(qfunc1) tape1 = qml.transforms.make_tape(transformed_qfunc)() - res1 = tape1.execute(qml.device("default.qubit", wires=3)) + res1 = qml.device("default.qubit", wires=3).execute(tape1) tape2 = qml.transforms.make_tape(qfunc2)() - res2 = tape2.execute(qml.device("default.qubit", wires=3)) + res2 = qml.device("default.qubit", wires=3).execute(tape2) assert np.allclose(res1, res2) diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py index 0319b566029..1393929502f 100644 --- a/tests/transforms/test_tape_expand.py +++ b/tests/transforms/test_tape_expand.py @@ -25,7 +25,7 @@ class TestCreateExpandFn: crit_0 = (~qml.operation.is_trainable) | (qml.operation.has_gen & qml.operation.is_trainable) doc_0 = "Test docstring." - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.RY(qml.numpy.array(2.1, requires_grad=True), wires=1) qml.Rot(*qml.numpy.array([0.5, 0.2, -0.1], requires_grad=True), wires=0) @@ -63,7 +63,7 @@ def test_device_and_stopping_expansion(self, mocker): dev = qml.device("default.qubit", wires=1) expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10, stop_at=self.crit_0) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.U1(0.2, wires=0) qml.Rot(*qml.numpy.array([0.5, 0.2, -0.1], requires_grad=True), wires=0) @@ -80,7 +80,7 @@ def test_device_only_expansion(self, mocker): dev = qml.device("default.qubit", wires=1) expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.U1(0.2, wires=0) qml.Rot(*qml.numpy.array([0.5, 0.2, -0.1], requires_grad=True), wires=0) @@ -96,7 +96,7 @@ def test_depth_only_expansion(self): """Test that passing a depth simply expands to that depth""" dev = qml.device("default.qubit", wires=0) - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.RY(qml.numpy.array(2.1, requires_grad=True), wires=1) qml.Rot(*qml.numpy.array([0.5, 0.2, -0.1], requires_grad=True), wires=0) @@ -169,7 +169,7 @@ class TestExpandNonunitaryGen: def test_do_not_expand(self): """Test that a tape with single-parameter operations with unitary generators and non-parametric operations is not touched.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.Hadamard(0) qml.PauliRot(0.9, "XY", wires=[0, 1]) @@ -182,7 +182,7 @@ def test_do_not_expand(self): def test_expand_multi_par(self): """Test that a tape with single-parameter operations with unitary generators and non-parametric operations is not touched.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.Hadamard(0) qml.Rot(0.9, 1.2, -0.6, wires=0) @@ -209,7 +209,7 @@ class _PhaseShift(qml.PhaseShift): def generator(self): return None - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.Hadamard(0) _PhaseShift(2.1, wires=1) @@ -225,7 +225,7 @@ def test_expand_nonunitary_generator(self): """Test that a tape with single-parameter operations with unitary generators and non-parametric operations is not touched.""" - with qml.tape.JacobianTape() as tape: + with qml.tape.QuantumTape() as tape: qml.RX(0.2, wires=0) qml.Hadamard(0) qml.PhaseShift(2.1, wires=1)