diff --git a/.github/workflows/install_deps/action.yml b/.github/workflows/install_deps/action.yml
index 9959688e660..e1dc636bb84 100644
--- a/.github/workflows/install_deps/action.yml
+++ b/.github/workflows/install_deps/action.yml
@@ -8,16 +8,6 @@ inputs:
description: The version of Python to use in order to run unit tests
required: false
default: '3.10'
- install_numpy_2:
- description: Indicate if numpy 2.0+ should be installed or not
- required: false
- type: boolean
- default: false
- numpy_2_version:
- description: The version of numpy 2.0 to use in order to run unit tests
- required: false
- type: string
- default: '2.0.0'
install_jax:
description: Indicate if JAX should be installed or not
required: false
@@ -77,13 +67,6 @@ runs:
pip install -r requirements-ci.txt --upgrade
pip install -r requirements-dev.txt --upgrade
- - name: Install numpy 2.0 version
- shell: bash
- if: inputs.install_numpy_2 == 'true'
- env:
- NUMPY2_VERSION: ${{ inputs.numpy_2_version != '' && format('~={0}', inputs.numpy_2_version) || '' }}
- run: pip install "numpy${{ env.NUMPY2_VERSION }}"
-
- name: Install PyTorch
shell: bash
if: inputs.install_pytorch == 'true'
diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml
index a80a84c9b2a..f2f1f9938dc 100644
--- a/.github/workflows/interface-unit-tests.yml
+++ b/.github/workflows/interface-unit-tests.yml
@@ -95,10 +95,10 @@ jobs:
cat >matrix_max_parallel.json <<-EOF
{
"default": 1,
- "core-tests": 8,
+ "core-tests": 5,
"gradients-tests": 2,
"jax-tests": 5,
- "tf-tests": 2,
+ "tf-tests": 3,
"device-tests": 2
}
EOF
@@ -152,16 +152,14 @@ jobs:
fromJSON(needs.setup-ci-load.outputs.python-version).torch-tests
|| fromJSON(needs.setup-ci-load.outputs.python-version).default
}}
- numpy-2-install: [true, false]
if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'torch-tests') }}
uses: ./.github/workflows/unit-test.yml
with:
- job_name: torch-tests (${{ matrix.python-version }}, numpy 2.0 ${{ matrix.numpy-2-install }})
+ job_name: torch-tests (${{ matrix.python-version }})
branch: ${{ inputs.branch }}
- coverage_artifact_name: core-interfaces-coverage-torch-${{ matrix.python-version }}-${{ matrix.numpy-2-install }}
+ coverage_artifact_name: core-interfaces-coverage-torch-${{ matrix.python-version }}
python_version: ${{ matrix.python-version }}
pipeline_mode: ${{ inputs.pipeline_mode }}
- install_numpy_2: ${{ matrix.numpy-2-install }}
install_jax: false
install_tensorflow: false
install_pytorch: true
@@ -187,16 +185,14 @@ jobs:
fromJSON(needs.setup-ci-load.outputs.python-version).autograd-tests
|| fromJSON(needs.setup-ci-load.outputs.python-version).default
}}
- numpy-2-install: [true, false]
if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'autograd-tests') }}
uses: ./.github/workflows/unit-test.yml
with:
- job_name: autograd-tests (${{ matrix.python-version }}, numpy 2.0 ${{ matrix.numpy-2-install }})
+ job_name: autograd-tests (${{ matrix.python-version }})
branch: ${{ inputs.branch }}
- coverage_artifact_name: core-interfaces-coverage-autograd-${{ matrix.python-version }}-${{ matrix.numpy-2-install }}
+ coverage_artifact_name: core-interfaces-coverage-autograd-${{ matrix.python-version }}
python_version: ${{ matrix.python-version }}
pipeline_mode: ${{ inputs.pipeline_mode }}
- install_numpy_2: ${{ matrix.numpy-2-install }}
install_jax: false
install_tensorflow: false
install_pytorch: false
@@ -300,16 +296,14 @@ jobs:
fromJSON(needs.setup-ci-load.outputs.python-version).core-tests
|| fromJSON(needs.setup-ci-load.outputs.python-version).default
}}
- numpy-2-install: [true, false]
if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'core-tests') }}
uses: ./.github/workflows/unit-test.yml
with:
- job_name: core-tests (${{ matrix.group }}, ${{ matrix.python-version }}, numpy 2.0 ${{ matrix.numpy-2-install }})
+ job_name: core-tests (${{ matrix.group }}, ${{ matrix.python-version }})
branch: ${{ inputs.branch }}
- coverage_artifact_name: core-interfaces-coverage-core-${{ matrix.python-version }}-${{ matrix.group }}-${{ matrix.numpy-2-install }}
+ coverage_artifact_name: core-interfaces-coverage-core-${{ matrix.python-version }}-${{ matrix.group }}
python_version: ${{ matrix.python-version }}
pipeline_mode: ${{ inputs.pipeline_mode }}
- install_numpy_2: ${{ matrix.numpy-2-install }}
install_jax: false
install_tensorflow: false
install_pytorch: false
@@ -387,8 +381,7 @@ jobs:
# catalyst requires the latest version of pennylane that is about to be released.
# Installing catalyst after pennylane to make sure that the latest catalyst is used.
install_catalyst_nightly: true
- # using lightning master does not work for the tests with external libraries
- install_pennylane_lightning_master: false
+ install_pennylane_lightning_master: true
pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
pytest_markers: external
additional_pip_packages: pyzx matplotlib stim quimb mitiq pennylane-qiskit ply
@@ -511,16 +504,14 @@ jobs:
fromJSON(needs.setup-ci-load.outputs.python-version).data-tests
|| fromJSON(needs.setup-ci-load.outputs.python-version).default
}}
- numpy-2-install: [true, false]
if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'data-tests') }}
uses: ./.github/workflows/unit-test.yml
with:
- job_name: data-tests (${{ matrix.python-version }}, numpy 2.0 ${{ matrix.numpy-2-install }})
+ job_name: data-tests (${{ matrix.python-version }})
branch: ${{ inputs.branch }}
- coverage_artifact_name: data-coverage-${{ matrix.python-version }}-${{ matrix.numpy-2-install }}
+ coverage_artifact_name: data-coverage-${{ matrix.python-version }}
python_version: ${{ matrix.python-version }}
pipeline_mode: ${{ inputs.pipeline_mode }}
- install_numpy_2: ${{ matrix.numpy-2-install }}
install_jax: false
install_tensorflow: false
install_pytorch: false
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 96a1a894ae3..8e11cf86473 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -24,16 +24,6 @@ on:
required: false
type: string
default: '3.10'
- install_numpy_2:
- description: Indicate if numpy 2.0+ should be installed or not
- required: false
- type: boolean
- default: false
- numpy_2_version:
- description: The version of numpy 2.0 to use in order to run unit tests
- required: false
- type: string
- default: '2.0'
pipeline_mode:
description: The pipeline mode can be unit-tests, benchmarks, or reference-benchmark
required: false
@@ -168,8 +158,6 @@ jobs:
uses: ./.github/workflows/install_deps
with:
python_version: ${{ inputs.python_version }}
- numpy_2_version: ${{ inputs.numpy_2_version }}
- install_numpy_2: ${{ inputs.install_numpy_2 }}
install_pytorch: ${{ inputs.install_pytorch }}
install_tensorflow: ${{ inputs.install_tensorflow }}
install_jax: ${{ inputs.install_jax }}
diff --git a/Makefile b/Makefile
index 7f36c24c698..8b3bec9be42 100644
--- a/Makefile
+++ b/Makefile
@@ -60,12 +60,10 @@ clean-docs:
test:
$(PYTHON) $(TESTRUNNER)
- $(PYTHON) $(PLUGIN_TESTRUNNER) --device=default.qubit.autograd
coverage:
@echo "Generating coverage report..."
$(PYTHON) $(TESTRUNNER) $(COVERAGE)
- $(PYTHON) $(PLUGIN_TESTRUNNER) --device=default.qubit.autograd $(COVERAGE) --cov-append
.PHONY:format
format:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index e9d23e790db..c91979963a9 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -9,17 +9,18 @@ deprecations are listed below.
Pending deprecations
--------------------
-* All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are deprecated. Use ``default.qubit`` instead,
- as it supports backpropagation for the many backends the legacy devices support.
+* ``Device``, ``QubitDevice``, and ``QutritDevice`` will no longer be imported top level in v0.40. They instead
+ we be available as ``qml.devices.LegacyDevice``, ``qml.devices.QubitDevice``, and ``qml.devices.QutritDevice``
+ respectively.
- - Deprecated in v0.38
- - Will be removed in v0.39
+ - Deprecated top level access in v0.39
+ - Top level access removed in v0.40
-* The logic for internally switching a device for a different backpropagation
- compatible device is now deprecated, as it was in place for the deprecated ``default.qubit.legacy``.
+* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to
+ process the diff method.
- - Deprecated in v0.38
- - Will be removed in v0.39
+ - Deprecated in v0.39
+ - Will be removed in v0.40
* The ``decomp_depth`` argument in ``qml.device`` is deprecated.
@@ -82,6 +83,18 @@ Other deprecations
Completed deprecation cycles
----------------------------
+* All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are removed. Use ``default.qubit`` instead,
+ as it supports backpropagation for the many backends the legacy devices support.
+
+ - Deprecated in v0.38
+ - Removed in v0.39
+
+* The logic for internally switching a device for a different backpropagation
+ compatible device is removed, as it was in place for removed ``default.qubit.legacy``.
+
+ - Deprecated in v0.38
+ - Removed in v0.39
+
* `Operator.expand` is now removed. Use `qml.tape.QuantumScript(op.deocomposition())` instead.
- Deprecated in v0.38
diff --git a/doc/development/release_notes.md b/doc/development/release_notes.md
index a1f5587597c..07ad0d279f7 100644
--- a/doc/development/release_notes.md
+++ b/doc/development/release_notes.md
@@ -5,6 +5,8 @@ This page contains the release notes for PennyLane.
.. mdinclude:: ../releases/changelog-dev.md
+.. mdinclude:: ../releases/changelog-0.38.1.md
+
.. mdinclude:: ../releases/changelog-0.38.0.md
.. mdinclude:: ../releases/changelog-0.37.0.md
diff --git a/doc/releases/changelog-0.38.0.md b/doc/releases/changelog-0.38.0.md
index 391f9dbf36d..eb817d4275e 100644
--- a/doc/releases/changelog-0.38.0.md
+++ b/doc/releases/changelog-0.38.0.md
@@ -1,6 +1,6 @@
:orphan:
-# Release 0.38.0 (current release)
+# Release 0.38.0
New features since last release
diff --git a/doc/releases/changelog-0.38.1.md b/doc/releases/changelog-0.38.1.md
new file mode 100644
index 00000000000..aab0ef38311
--- /dev/null
+++ b/doc/releases/changelog-0.38.1.md
@@ -0,0 +1,14 @@
+:orphan:
+
+# Release 0.38.1 (current release)
+
+Bug fixes 🐛
+
+* Fix float-to-complex casting in various places across PennyLane.
+ [(#6260)](https://github.com/PennyLaneAI/pennylane/pull/6260)
+
+Contributors ✍️
+
+This release contains contributions from (in alphabetical order):
+
+Mudit Pandey
\ No newline at end of file
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 2f0eeee194f..506504f631b 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -6,6 +6,9 @@
Improvements 🛠
+* PennyLane is now compatible with NumPy 2.0.
+ [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061)
+
* `qml.qchem.excitations` now optionally returns fermionic operators.
[(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171)
@@ -25,9 +28,10 @@
* Differentiation of hybrid programs via `qml.grad` and `qml.jacobian` can now be captured
into plxpr. When evaluating a captured `qml.grad` (`qml.jacobian`) instruction, it will
dispatch to `jax.grad` (`jax.jacobian`), which differs from the Autograd implementation
- without capture.
+ without capture. Pytree inputs and outputs are supported.
[(#6120)](https://github.com/PennyLaneAI/pennylane/pull/6120)
[(#6127)](https://github.com/PennyLaneAI/pennylane/pull/6127)
+ [(#6134)](https://github.com/PennyLaneAI/pennylane/pull/6134)
* Improve unit testing for capturing of nested control flows.
[(#6111)](https://github.com/PennyLaneAI/pennylane/pull/6111)
@@ -59,10 +63,12 @@
* Remove support for Python 3.9.
[(#6223)](https://github.com/PennyLaneAI/pennylane/pull/6223)
-* `DefaultQubitTF`, `DefaultQubitTorch`, and `DefaultQubitJax` are removed. Please use `default.qubit` for all interfaces.
+* `DefaultQubitTF`, `DefaultQubitTorch`, `DefaultQubitJax`, and `DefaultQubitAutograd` are removed.
+ Please use `default.qubit` for all interfaces.
[(#6207)](https://github.com/PennyLaneAI/pennylane/pull/6207)
[(#6208)](https://github.com/PennyLaneAI/pennylane/pull/6208)
[(#6209)](https://github.com/PennyLaneAI/pennylane/pull/6209)
+ [(#6210)](https://github.com/PennyLaneAI/pennylane/pull/6210)
* `expand_fn`, `max_expansion`, `override_shots`, and `device_batch_transform` are removed from the
signature of `qml.execute`.
@@ -81,8 +87,17 @@
* `Operator.expand` is now removed. Use `qml.tape.QuantumScript(op.deocomposition())` instead.
[(#6227)](https://github.com/PennyLaneAI/pennylane/pull/6227)
+
Deprecations 👋
+* `Device`, `QubitDevice`, and `QutritDevice` will no longer be accessible via top-level import in v0.40.
+ They will still be accessible as `qml.devices.LegacyDevice`, `qml.devices.QubitDevice`, and `qml.devices.QutritDevice`
+ respectively.
+ [(#6238)](https://github.com/PennyLaneAI/pennylane/pull/6238/)
+
+* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` and `QNode.get_gradient_fn` instead.
+ [(#6244)](https://github.com/PennyLaneAI/pennylane/pull/6244)
+
Documentation 📝
Bug fixes 🐛
@@ -102,6 +117,9 @@
* The ``qml.Qubitization`` template now orders the ``control`` wires first and the ``hamiltonian`` wires second, which is the expected according to other templates.
[(#6229)](https://github.com/PennyLaneAI/pennylane/pull/6229)
+* The ``qml.FABLE`` template now returns the correct value when JIT is enabled.
+ [(#6263)](https://github.com/PennyLaneAI/pennylane/pull/6263)
+
* Contributors ✍️
This release contains contributions from (in alphabetical order):
@@ -109,6 +127,8 @@ This release contains contributions from (in alphabetical order):
Guillermo Alonso,
Utkarsh Azad,
Lillian M. A. Frederiksen,
+Pietropaolo Frisoni,
+Emiliano Godinez,
Christina Lee,
William Maxwell,
Lee J. O'Riordan,
diff --git a/pennylane/__init__.py b/pennylane/__init__.py
index 7f029461dea..3db66c953d2 100644
--- a/pennylane/__init__.py
+++ b/pennylane/__init__.py
@@ -16,8 +16,6 @@
PennyLane can be directly imported.
"""
-import numpy as _np
-
from pennylane.boolean_fn import BooleanFn
import pennylane.numpy
@@ -180,13 +178,30 @@ def __getattr__(name):
if name == "plugin_devices":
return pennylane.devices.device_constructor.plugin_devices
+ from warnings import warn # pylint: disable=import-outside-toplevel
+
if name == "QubitDevice":
+ warn(
+ "QubitDevice will no longer be accessible top level. Please access "
+ " the class as pennylane.devices.QubitDevice",
+ PennyLaneDeprecationWarning,
+ )
return pennylane.devices._qubit_device.QubitDevice # pylint:disable=protected-access
if name == "QutritDevice":
+ warn(
+ "QutritDevice will no longer be accessible top level. Please access "
+ " the class as pennylane.devices.QutritDevice",
+ PennyLaneDeprecationWarning,
+ )
return pennylane.devices._qutrit_device.QutritDevice # pylint:disable=protected-access
if name == "Device":
+ warn(
+ "Device will no longer be accessible top level. Please access "
+ " the class as pennylane.devices.LegacyDevice",
+ PennyLaneDeprecationWarning,
+ )
return pennylane.devices._legacy_device.Device # pylint:disable=protected-access
raise AttributeError(f"module 'pennylane' has no attribute '{name}'")
diff --git a/pennylane/_grad.py b/pennylane/_grad.py
index 859ae5d9fbb..d7cb52e0d52 100644
--- a/pennylane/_grad.py
+++ b/pennylane/_grad.py
@@ -25,6 +25,7 @@
from pennylane.capture import enabled
from pennylane.capture.capture_diff import _get_grad_prim, _get_jacobian_prim
+from pennylane.capture.flatfn import FlatFn
from pennylane.compiler import compiler
from pennylane.compiler.compiler import CompileError
@@ -33,18 +34,53 @@
def _capture_diff(func, argnum=None, diff_prim=None, method=None, h=None):
"""Capture-compatible gradient computation."""
- import jax # pylint: disable=import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
+ import jax
+ from jax.tree_util import tree_flatten, tree_leaves, tree_unflatten, treedef_tuple
- if isinstance(argnum, int):
- argnum = [argnum]
if argnum is None:
- argnum = [0]
+ argnum = 0
+ if argnum_is_int := isinstance(argnum, int):
+ argnum = [argnum]
@wraps(func)
def new_func(*args, **kwargs):
- jaxpr = jax.make_jaxpr(partial(func, **kwargs))(*args)
- prim_kwargs = {"argnum": argnum, "jaxpr": jaxpr.jaxpr, "n_consts": len(jaxpr.consts)}
- return diff_prim.bind(*jaxpr.consts, *args, **prim_kwargs, method=method, h=h)
+ flat_args, in_trees = zip(*(tree_flatten(arg) for arg in args))
+ full_in_tree = treedef_tuple(in_trees)
+
+ # Create a new input tree that only takes inputs marked by argnum into account
+ trainable_in_trees = (in_tree for i, in_tree in enumerate(in_trees) if i in argnum)
+ # If an integer was provided as argnum, unpack the arguments axis of the derivatives
+ if argnum_is_int:
+ trainable_in_tree = list(trainable_in_trees)[0]
+ else:
+ trainable_in_tree = treedef_tuple(trainable_in_trees)
+
+ # Create argnum for the flat list of input arrays. For each flattened argument,
+ # add a list of flat argnums if the argument is trainable and an empty list otherwise.
+ start = 0
+ flat_argnum_gen = (
+ (
+ list(range(start, (start := start + len(flat_arg))))
+ if i in argnum
+ else list(range((start := start + len(flat_arg)), start))
+ )
+ for i, flat_arg in enumerate(flat_args)
+ )
+ flat_argnum = sum(flat_argnum_gen, start=[])
+
+ # Create fully flattened function (flat inputs & outputs)
+ flat_fn = FlatFn(partial(func, **kwargs) if kwargs else func, full_in_tree)
+ flat_args = sum(flat_args, start=[])
+ jaxpr = jax.make_jaxpr(flat_fn)(*flat_args)
+ prim_kwargs = {"argnum": flat_argnum, "jaxpr": jaxpr.jaxpr, "n_consts": len(jaxpr.consts)}
+ out_flat = diff_prim.bind(*jaxpr.consts, *flat_args, **prim_kwargs, method=method, h=h)
+ # flatten once more to go from 2D derivative structure (outputs, args) to flat structure
+ out_flat = tree_leaves(out_flat)
+ assert flat_fn.out_tree is not None, "out_tree should be set after executing flat_fn"
+ # The derivative output tree is the composition of output tree and trainable input trees
+ combined_tree = flat_fn.out_tree.compose(trainable_in_tree)
+ return tree_unflatten(combined_tree, out_flat)
return new_func
diff --git a/pennylane/_version.py b/pennylane/_version.py
index 0c24fd96c36..77639685bc6 100644
--- a/pennylane/_version.py
+++ b/pennylane/_version.py
@@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""
-__version__ = "0.39.0-dev13"
+__version__ = "0.39.0-dev15"
diff --git a/pennylane/capture/__init__.py b/pennylane/capture/__init__.py
index 6deeef29682..2e43a246e2c 100644
--- a/pennylane/capture/__init__.py
+++ b/pennylane/capture/__init__.py
@@ -34,6 +34,7 @@
~create_measurement_wires_primitive
~create_measurement_mcm_primitive
~qnode_call
+ ~FlatFn
The ``primitives`` submodule offers easy access to objects with jax dependencies such as
@@ -154,6 +155,7 @@ def _(*args, **kwargs):
create_measurement_mcm_primitive,
)
from .capture_qnode import qnode_call
+from .flatfn import FlatFn
# by defining this here, we avoid
# E0611: No name 'AbstractOperator' in module 'pennylane.capture' (no-name-in-module)
@@ -196,4 +198,5 @@ def __getattr__(key):
"AbstractOperator",
"AbstractMeasurement",
"qnode_prim",
+ "FlatFn",
)
diff --git a/pennylane/capture/capture_diff.py b/pennylane/capture/capture_diff.py
index 92dde5a2956..829a9516af1 100644
--- a/pennylane/capture/capture_diff.py
+++ b/pennylane/capture/capture_diff.py
@@ -103,7 +103,7 @@ def _(*args, argnum, jaxpr, n_consts, method, h):
def func(*inner_args):
return jax.core.eval_jaxpr(jaxpr, consts, *inner_args)
- return jax.jacobian(func, argnums=argnum)(*args)
+ return jax.tree_util.tree_leaves(jax.jacobian(func, argnums=argnum)(*args))
# pylint: disable=unused-argument
@jacobian_prim.def_abstract_eval
diff --git a/pennylane/capture/capture_qnode.py b/pennylane/capture/capture_qnode.py
index f7f06451230..491b9f3f6a4 100644
--- a/pennylane/capture/capture_qnode.py
+++ b/pennylane/capture/capture_qnode.py
@@ -82,8 +82,12 @@ def _(*args, qnode, shots, device, qnode_kwargs, qfunc_jaxpr, n_consts):
mps = qfunc_jaxpr.outvars
return _get_shapes_for(*mps, shots=shots, num_device_wires=len(device.wires))
- def _qnode_jvp(*args_and_tangents, **impl_kwargs):
- return jax.jvp(partial(qnode_prim.impl, **impl_kwargs), *args_and_tangents)
+ def make_zero(tan, arg):
+ return jax.lax.zeros_like_array(arg) if isinstance(tan, ad.Zero) else tan
+
+ def _qnode_jvp(args, tangents, **impl_kwargs):
+ tangents = tuple(map(make_zero, tangents, args))
+ return jax.jvp(partial(qnode_prim.impl, **impl_kwargs), args, tangents)
ad.primitive_jvps[qnode_prim] = _qnode_jvp
@@ -174,7 +178,7 @@ def f(x):
qnode_kwargs = {"diff_method": qnode.diff_method, **execute_kwargs, **mcm_config}
qnode_prim = _get_qnode_prim()
- flat_args, _ = jax.tree_util.tree_flatten(args)
+ flat_args = jax.tree_util.tree_leaves(args)
res = qnode_prim.bind(
*qfunc_jaxpr.consts,
*flat_args,
diff --git a/pennylane/capture/explanations.md b/pennylane/capture/explanations.md
index 61b88b94030..84feef9786f 100644
--- a/pennylane/capture/explanations.md
+++ b/pennylane/capture/explanations.md
@@ -159,12 +159,11 @@ You can also see the const variable `a` as argument `e:i32[]` to the inner neste
### Pytree handling
Evaluating a jaxpr requires accepting and returning a flat list of tensor-like inputs and outputs.
-list of tensor-like outputs. These long lists can be hard to manage and are very
-restrictive on the allowed functions, but we can take advantage of pytrees to allow handling
-arbitrary functions.
+These long lists can be hard to manage and are very restrictive on the allowed functions, but we
+can take advantage of pytrees to allow handling arbitrary functions.
To start, we import the `FlatFn` helper. This class converts a function to one that caches
-the resulting result pytree into `flat_fn.out_tree` when executed. This can be used to repack the
+the result pytree into `flat_fn.out_tree` when executed. This can be used to repack the
results into the correct shape. It also returns flattened results. This does not particularly
matter for program capture, as we will only be producing jaxpr from the function, not calling
it directly.
diff --git a/pennylane/capture/flatfn.py b/pennylane/capture/flatfn.py
index 4ba6005f41e..59e1c52b948 100644
--- a/pennylane/capture/flatfn.py
+++ b/pennylane/capture/flatfn.py
@@ -29,23 +29,48 @@ class FlatFn:
property, so that the results can be repacked later. It also returns flattened results
instead of the original result object.
+ If an ``in_tree`` is provided, the function accepts flattened inputs instead of the
+ original inputs with tree structure given by ``in_tree``.
+
+ **Example**
+
+ >>> import jax
+ >>> from pennylane.capture.flatfn import FlatFn
>>> def f(x):
... return {"y": 2+x["x"]}
>>> flat_f = FlatFn(f)
- >>> res = flat_f({"x": 0})
+ >>> arg = {"x": 0.5}
+ >>> res = flat_f(arg)
+ >>> res
+ [2.5]
+ >>> jax.tree_util.tree_unflatten(flat_f.out_tree, res)
+ {'y': 2.5}
+
+ If we want to use a fully flattened function that also takes flat inputs instead of
+ the original inputs with tree structure, we can provide the treedef for this input
+ structure:
+
+ >>> flat_args, in_tree = jax.tree_util.tree_flatten((arg,))
+ >>> flat_f = FlatFn(f, in_tree)
+ >>> res = flat_f(*flat_args)
>>> res
- [2]
+ [2.5]
>>> jax.tree_util.tree_unflatten(flat_f.out_tree, res)
{'y': 2.5}
+ Note that the ``in_tree`` has to be created by flattening a tuple of all input
+ arguments, even if there is only a single argument.
"""
- def __init__(self, f):
+ def __init__(self, f, in_tree=None):
self.f = f
+ self.in_tree = in_tree
self.out_tree = None
update_wrapper(self, f)
def __call__(self, *args):
+ if self.in_tree is not None:
+ args = jax.tree_util.tree_unflatten(self.in_tree, args)
out = self.f(*args)
out_flat, out_tree = jax.tree_util.tree_flatten(out)
self.out_tree = out_tree
diff --git a/pennylane/devices/__init__.py b/pennylane/devices/__init__.py
index 823f2b7f41d..a542ba7df1d 100644
--- a/pennylane/devices/__init__.py
+++ b/pennylane/devices/__init__.py
@@ -27,7 +27,6 @@
default_qubit
default_qubit_legacy
- default_qubit_autograd
default_gaussian
default_mixed
default_qutrit
@@ -153,9 +152,6 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi
from .default_qubit import DefaultQubit
from .legacy_facade import LegacyDeviceFacade
-# DefaultQubitTF and DefaultQubitAutograd not imported here since this
-# would lead to an automatic import of tensorflow and autograd, which are
-# not PennyLane core dependencies.
# DefaultTensor is not imported here to avoid warnings
# from quimb in case it is installed on the system.
from .default_qubit_legacy import DefaultQubitLegacy
diff --git a/pennylane/devices/_qubit_device.py b/pennylane/devices/_qubit_device.py
index a54c2985290..ef9f41f0378 100644
--- a/pennylane/devices/_qubit_device.py
+++ b/pennylane/devices/_qubit_device.py
@@ -1110,7 +1110,11 @@ def classical_shadow(self, obs, circuit):
n_snapshots = self.shots
seed = obs.seed
- with qml.workflow.set_shots(self, shots=1):
+ original_shots = self.shots
+ original_shot_vector = self._shot_vector
+
+ try:
+ self.shots = 1
# slow implementation but works for all devices
n_qubits = len(wires)
mapped_wires = np.array(self.map_wires(wires))
@@ -1139,6 +1143,10 @@ def classical_shadow(self, obs, circuit):
)
outcomes[t] = self.generate_samples()[0][mapped_wires]
+ finally:
+ self.shots = original_shots
+ # pylint: disable=attribute-defined-outside-init
+ self._shot_vector = original_shot_vector
return self._cast(self._stack([outcomes, recipes]), dtype=np.int8)
diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py
deleted file mode 100644
index abcc6e0452f..00000000000
--- a/pennylane/devices/default_qubit_autograd.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# 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.
-"""This module contains an autograd implementation of the :class:`~.DefaultQubitLegacy`
-reference plugin.
-"""
-import warnings
-
-from pennylane import PennyLaneDeprecationWarning
-from pennylane import numpy as pnp
-from pennylane.devices import DefaultQubitLegacy
-
-
-class DefaultQubitAutograd(DefaultQubitLegacy):
- r"""Simulator plugin based on ``"default.qubit.legacy"``, written using Autograd.
-
- **Short name:** ``default.qubit.autograd``
-
- This device provides a pure-state qubit simulator written using Autograd. As a result, it
- supports classical backpropagation as a means to compute the gradient. This can be faster than
- the parameter-shift rule for analytic quantum gradients when the number of parameters to be
- optimized is large.
-
- To use this device, you will need to install Autograd:
-
- .. code-block:: console
-
- pip install autograd
-
- .. warning::
- This device is deprecated. Use :class:`~pennylane.devices.DefaultQubit` instead; for example through ``qml.device("default.qubit")``, which now supports backpropagation.
-
- **Example**
-
- The ``default.qubit.autograd`` is designed to be used with end-to-end classical backpropagation
- (``diff_method="backprop"``) with the Autograd interface. This is the default method of
- differentiation when creating a QNode with this device.
-
- Using this method, the created QNode is a 'white-box', and is
- tightly integrated with your Autograd computation:
-
- >>> dev = qml.device("default.qubit.autograd", wires=1)
- >>> @qml.qnode(dev, interface="autograd", diff_method="backprop")
- ... def circuit(x):
- ... qml.RX(x[1], wires=0)
- ... qml.Rot(x[0], x[1], x[2], wires=0)
- ... return qml.expval(qml.Z(0))
- >>> weights = np.array([0.2, 0.5, 0.1], requires_grad=True)
- >>> grad_fn = qml.grad(circuit)
- >>> print(grad_fn(weights))
- array([-2.2526717e-01 -1.0086454e+00 1.3877788e-17])
-
- There are a couple of things to keep in mind when using the ``"backprop"``
- differentiation method for QNodes:
-
- * You must use the ``"autograd"`` interface for classical backpropagation, as Autograd is
- used as the device backend.
-
- * Only exact expectation values, variances, and probabilities are differentiable.
- When instantiating the device with ``analytic=False``, differentiating QNode
- outputs will result in an error.
-
- Args:
- wires (int): the number of wires to initialize the device with
- shots (None, int): How many times the circuit should be evaluated (or sampled) to estimate
- the expectation values. Defaults to ``None`` if not specified, which means that the device
- returns analytical results.
- analytic (bool): Indicates if the device should calculate expectations
- and variances analytically. In non-analytic mode, the ``diff_method="backprop"``
- QNode differentiation method is not supported and it is recommended to consider
- switching device to ``default.qubit`` and using ``diff_method="parameter-shift"``.
- """
-
- name = "Default qubit (Autograd) PennyLane plugin"
- short_name = "default.qubit.autograd"
-
- _dot = staticmethod(pnp.dot)
- _abs = staticmethod(pnp.abs)
- _reduce_sum = staticmethod(lambda array, axes: pnp.sum(array, axis=tuple(axes)))
- _reshape = staticmethod(pnp.reshape)
- _flatten = staticmethod(lambda array: array.flatten())
- _einsum = staticmethod(pnp.einsum)
- _cast = staticmethod(pnp.asarray)
- _transpose = staticmethod(pnp.transpose)
- _tensordot = staticmethod(pnp.tensordot)
- _conj = staticmethod(pnp.conj)
- _real = staticmethod(pnp.real)
- _imag = staticmethod(pnp.imag)
- _roll = staticmethod(pnp.roll)
- _stack = staticmethod(pnp.stack)
- _size = staticmethod(pnp.size)
- _ndim = staticmethod(pnp.ndim)
-
- @staticmethod
- def _asarray(array, dtype=None):
- return pnp.asarray(array, dtype=dtype)
-
- @staticmethod
- def _const_mul(constant, array):
- return constant * array
-
- def __init__(self, wires, *, shots=None, analytic=None):
- warnings.warn(
- f"Use of '{self.short_name}' is deprecated. Instead, use 'default.qubit', "
- "which supports backpropagation. "
- "If you experience issues, reach out to the PennyLane team on "
- "the discussion forum: https://discuss.pennylane.ai/",
- PennyLaneDeprecationWarning,
- )
-
- r_dtype = pnp.float64
- c_dtype = pnp.complex128
- super().__init__(wires, shots=shots, r_dtype=r_dtype, c_dtype=c_dtype, analytic=analytic)
-
- # prevent using special apply methods for these gates due to slowdown in Autograd
- # implementation
- del self._apply_ops["PauliY"]
- del self._apply_ops["Hadamard"]
- del self._apply_ops["CZ"]
-
- @classmethod
- def capabilities(cls):
- capabilities = super().capabilities().copy()
- capabilities.update(passthru_interface="autograd")
- return capabilities
-
- @staticmethod
- def _scatter(indices, array, new_dimensions):
- new_array = pnp.zeros(new_dimensions, dtype=array.dtype.type)
- new_array[indices] = array
- return new_array
diff --git a/pennylane/devices/default_qubit_legacy.py b/pennylane/devices/default_qubit_legacy.py
index bdcfd1ad1da..868c426d47f 100644
--- a/pennylane/devices/default_qubit_legacy.py
+++ b/pennylane/devices/default_qubit_legacy.py
@@ -713,9 +713,7 @@ def capabilities(cls):
supports_analytic_computation=True,
supports_broadcasting=True,
returns_state=True,
- passthru_devices={
- "autograd": "default.qubit.autograd",
- },
+ passthru_devices={},
)
return capabilities
diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py
index a35aa4b25f5..41c1e0dea2c 100644
--- a/pennylane/devices/legacy_facade.py
+++ b/pennylane/devices/legacy_facade.py
@@ -15,7 +15,6 @@
Defines a LegacyDeviceFacade class for converting legacy devices to the
new interface.
"""
-import warnings
# pylint: disable=not-callable, unused-argument
from contextlib import contextmanager
@@ -318,79 +317,6 @@ def supports_derivatives(self, execution_config=None, circuit=None) -> bool:
return False
- # pylint: disable=protected-access
- def _create_temp_device(self, batch):
- """Create a temporary device for use in a backprop execution."""
- params = []
- for t in batch:
- params.extend(t.get_parameters(trainable_only=False))
- interface = qml.math.get_interface(*params)
- if interface == "numpy":
- return self._device
-
- mapped_interface = qml.workflow.execution.INTERFACE_MAP.get(interface, interface)
-
- backprop_interface = self._device.capabilities().get("passthru_interface", None)
- if mapped_interface == backprop_interface:
- return self._device
-
- backprop_devices = self._device.capabilities().get("passthru_devices", None)
-
- if backprop_devices is None:
- raise qml.DeviceError(f"Device {self} does not support backpropagation.")
-
- if backprop_devices[mapped_interface] == self._device.short_name:
- return self._device
-
- if self.target_device.short_name != "default.qubit.legacy":
- warnings.warn(
- "The switching of devices for backpropagation is now deprecated in v0.38 and "
- "will be removed in v0.39, as this behavior was developed purely for the "
- "deprecated default.qubit.legacy.",
- qml.PennyLaneDeprecationWarning,
- )
-
- # create new backprop device
- expand_fn = self._device.expand_fn
- batch_transform = self._device.batch_transform
- if hasattr(self._device, "_debugger"):
- debugger = self._device._debugger
- else:
- debugger = "No debugger"
- tracker = self._device.tracker
-
- with warnings.catch_warnings():
- warnings.filterwarnings(
- action="ignore",
- category=qml.PennyLaneDeprecationWarning,
- message=r"use 'default.qubit'",
- )
- # we already warned about backprop device switching
- new_device = qml.device(
- backprop_devices[mapped_interface],
- wires=self._device.wires,
- shots=self._device.shots,
- ).target_device
-
- new_device.expand_fn = expand_fn
- new_device.batch_transform = batch_transform
- if debugger != "No debugger":
- new_device._debugger = debugger
- new_device.tracker = tracker
-
- return new_device
-
- # pylint: disable=protected-access
- def _update_original_device(self, temp_device):
- """After performing an execution with a backprop device, update the state of the original device."""
- # Update for state vector simulators that have the _pre_rotated_state attribute
- if hasattr(self._device, "_pre_rotated_state"):
- self._device._pre_rotated_state = temp_device._pre_rotated_state
-
- # Update for state vector simulators that have the _state attribute
- if hasattr(self._device, "_state"):
- self._device._state = temp_device._state
-
def _validate_backprop_method(self, tape):
if tape.shots:
return False
@@ -441,11 +367,7 @@ def _validate_device_method(self, _):
return self._device.capabilities().get("provides_jacobian", False)
def execute(self, circuits, execution_config=DefaultExecutionConfig):
- dev = (
- self._create_temp_device(circuits)
- if execution_config.gradient_method == "backprop"
- else self._device
- )
+ dev = self.target_device
kwargs = {}
if dev.capabilities().get("supports_mid_measure", False):
@@ -453,16 +375,10 @@ def execute(self, circuits, execution_config=DefaultExecutionConfig):
first_shot = circuits[0].shots
if all(t.shots == first_shot for t in circuits):
- results = _set_shots(dev, first_shot)(dev.batch_execute)(circuits, **kwargs)
- else:
- results = tuple(
- _set_shots(dev, t.shots)(dev.batch_execute)((t,), **kwargs)[0] for t in circuits
- )
-
- if dev is not self._device:
- self._update_original_device(dev)
-
- return results
+ return _set_shots(dev, first_shot)(dev.batch_execute)(circuits, **kwargs)
+ return tuple(
+ _set_shots(dev, t.shots)(dev.batch_execute)((t,), **kwargs)[0] for t in circuits
+ )
def execute_and_compute_derivatives(self, circuits, execution_config=DefaultExecutionConfig):
first_shot = circuits[0].shots
diff --git a/pennylane/devices/qubit/apply_operation.py b/pennylane/devices/qubit/apply_operation.py
index 8e082ee9820..dccf49ef11b 100644
--- a/pennylane/devices/qubit/apply_operation.py
+++ b/pennylane/devices/qubit/apply_operation.py
@@ -71,7 +71,7 @@ def apply_operation_einsum(op: qml.operation.Operator, state, is_state_batched:
Returns:
array[complex]: output_state
"""
- mat = op.matrix()
+ mat = qml.math.cast_like(op.matrix(), 1j)
total_indices = len(state.shape) - is_state_batched
num_indices = len(op.wires)
@@ -114,7 +114,7 @@ def apply_operation_tensordot(op: qml.operation.Operator, state, is_state_batche
Returns:
array[complex]: output_state
"""
- mat = op.matrix()
+ mat = qml.math.cast_like(op.matrix(), 1j)
total_indices = len(state.shape) - is_state_batched
num_indices = len(op.wires)
diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py
index 8e5e9740da1..f462138c4d5 100755
--- a/pennylane/devices/tests/conftest.py
+++ b/pennylane/devices/tests/conftest.py
@@ -36,7 +36,6 @@
# List of all devices that are included in PennyLane
LIST_CORE_DEVICES = {
"default.qubit",
- "default.qubit.autograd",
}
@@ -111,12 +110,6 @@ def validate_diff_method(device, diff_method, device_kwargs):
if diff_method == "backprop" and device_kwargs.get("shots") is not None:
pytest.skip(reason="test should only be run in analytic mode")
dev = device(1)
- if isinstance(dev, qml.Device):
- passthru_devices = dev.capabilities().get("passthru_devices")
- if diff_method == "backprop" and passthru_devices is None:
- pytest.skip(reason="device does not support backprop")
- return
-
config = qml.devices.ExecutionConfig(gradient_method=diff_method)
if not dev.supports_derivatives(execution_config=config):
pytest.skip(reason="device does not support diff_method")
@@ -142,12 +135,6 @@ def _device(wires):
f"plugin and all of its dependencies must be installed."
)
- if isinstance(dev, qml.Device):
- capabilities = dev.capabilities()
- if capabilities.get("model", None) != "qubit":
- # exit the tests if device based on cv model (currently not supported)
- pytest.exit("The device test suite currently only runs on qubit-based devices.")
-
return dev
return _device
diff --git a/pennylane/devices/tests/test_compare_default_qubit.py b/pennylane/devices/tests/test_compare_default_qubit.py
index 05b2b6509e8..75e91809e2a 100755
--- a/pennylane/devices/tests/test_compare_default_qubit.py
+++ b/pennylane/devices/tests/test_compare_default_qubit.py
@@ -38,9 +38,6 @@ def test_hermitian_expectation(self, device, tol, benchmark):
if dev.shots:
pytest.skip("Device is in non-analytical mode.")
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
- pytest.skip("Device does not support the Hermitian observable.")
-
if dev.name == "default.qubit":
pytest.skip("Device is default.qubit.")
@@ -107,7 +104,7 @@ def test_projector_expectation(self, device, state, tol, benchmark):
if dev.shots:
pytest.skip("Device is in non-analytical mode.")
- if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Device does not support the Projector observable.")
if dev.name == "default.qubit":
diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py
index b948f6962c4..7226e021994 100644
--- a/pennylane/devices/tests/test_gates.py
+++ b/pennylane/devices/tests/test_gates.py
@@ -358,7 +358,7 @@ def test_supported_gates_can_be_implemented(self, device_kwargs, operation):
device_kwargs["wires"] = 4 # maximum size of current gates
dev = qml.device(**device_kwargs)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if operation not in dev.operations:
pytest.skip("operation not supported.")
else:
@@ -395,7 +395,7 @@ def test_basis_state(self, device, basis_state, tol, skip_if):
"""Test basis state initialization."""
n_wires = 4
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
@qml.qnode(dev)
@@ -413,7 +413,7 @@ def test_state_prep(self, device, init_state, tol, skip_if):
"""Test StatePrep initialisation."""
n_wires = 1
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
@@ -433,7 +433,7 @@ def test_single_qubit_no_parameters(self, device, init_state, op, mat, tol, skip
"""Test PauliX application."""
n_wires = 1
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
@@ -457,7 +457,7 @@ def test_single_qubit_parameters(
"""Test single qubit gates taking a single scalar argument."""
n_wires = 1
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
@@ -477,7 +477,7 @@ def test_rotation(self, device, init_state, tol, skip_if, benchmark):
"""Test three axis rotation gate."""
n_wires = 1
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
@@ -501,7 +501,7 @@ def test_two_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_if
"""Test two qubit gates."""
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
if not dev.supports_operation(op(wires=range(n_wires)).name):
pytest.skip("op not supported")
@@ -527,7 +527,7 @@ def test_two_qubit_parameters(
"""Test parametrized two qubit gates taking a single scalar argument."""
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
@@ -549,7 +549,7 @@ def test_qubit_unitary(self, device, init_state, mat, tol, skip_if, benchmark):
n_wires = int(np.log2(len(mat)))
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "QubitUnitary" not in dev.operations:
pytest.skip("Skipped because device does not support QubitUnitary.")
@@ -574,7 +574,7 @@ def test_special_unitary(self, device, init_state, theta_, tol, skip_if, benchma
n_wires = int(np.log(len(theta_) + 1) / np.log(4))
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "SpecialUnitary" not in dev.operations:
pytest.skip("Skipped because device does not support SpecialUnitary.")
@@ -603,7 +603,7 @@ def test_three_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
rnd_state = init_state(n_wires)
diff --git a/pennylane/devices/tests/test_gates_with_expval.py b/pennylane/devices/tests/test_gates_with_expval.py
index 7239ab2bcd3..f2e7e8951d6 100755
--- a/pennylane/devices/tests/test_gates_with_expval.py
+++ b/pennylane/devices/tests/test_gates_with_expval.py
@@ -256,7 +256,7 @@ def test_supported_gate_two_wires_no_parameters(self, device, tol, name, expecte
dev = device(n_wires)
op = getattr(qml.ops, name)
- if isinstance(dev, qml.Device) and not dev.supports_operation(op):
+ if isinstance(dev, qml.devices.LegacyDevice) and not dev.supports_operation(op):
pytest.skip("operation not supported")
@qml.qnode(dev)
diff --git a/pennylane/devices/tests/test_measurements.py b/pennylane/devices/tests/test_measurements.py
index 2a81366ba36..cde18014781 100644
--- a/pennylane/devices/tests/test_measurements.py
+++ b/pennylane/devices/tests/test_measurements.py
@@ -116,7 +116,7 @@ def test_supported_observables_can_be_implemented(self, device_kwargs, observabl
if dev.shots and observable == "SparseHamiltonian":
pytest.skip("SparseHamiltonian only supported in analytic mode")
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
assert hasattr(dev, "observables")
if observable not in dev.observables:
pytest.skip("observable not supported")
@@ -313,7 +313,7 @@ def test_hermitian_expectation(self, device, tol):
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
theta = 0.432
@@ -342,7 +342,7 @@ def test_projector_expectation(self, device, tol):
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
theta = 0.732
@@ -380,7 +380,7 @@ def test_multi_mode_hermitian_expectation(self, device, tol):
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
theta = 0.432
@@ -426,7 +426,7 @@ def circuit():
def test_op_arithmetic_matches_default_qubit(self, o, device, tol):
"""Test that devices (which support the observable) match default.qubit results."""
dev = device(2)
- if isinstance(dev, qml.Device) and o.name not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and o.name not in dev.observables:
pytest.skip(f"Skipped because device does not support the {o.name} observable.")
def circuit():
@@ -448,7 +448,7 @@ def test_paulix_pauliy(self, device, tol, skip_if):
"""Test that a tensor product involving PauliX and PauliY works correctly"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -473,7 +473,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if):
"""Test that a tensor product involving PauliZ and PauliY and hadamard works correctly"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -517,7 +517,7 @@ def circ(obs):
"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
@qml.qnode(dev)
@@ -545,7 +545,7 @@ def circ(wire_labels):
"""
dev = device(wires=3)
dev_custom_labels = device(wires=label_map)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
def circ(wire_labels):
@@ -567,7 +567,7 @@ def test_hermitian(self, device, tol, skip_if):
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
@@ -609,7 +609,7 @@ def test_projector(self, device, tol, skip_if):
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
@@ -661,7 +661,7 @@ def test_sparse_hamiltonian_expval(self, device, tol):
n_wires = 4
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "SparseHamiltonian" not in dev.observables:
pytest.skip(
"Skipped because device does not support the SparseHamiltonian observable."
@@ -724,7 +724,7 @@ def test_sample_values_hermitian(self, device, tol):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
A_ = np.array([[1, 2j], [-2j, 0]])
@@ -760,7 +760,7 @@ def test_sample_values_projector(self, device, tol):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
theta = 0.543
@@ -808,7 +808,7 @@ def test_sample_values_hermitian_multi_qubit(self, device, tol):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
theta = 0.543
@@ -857,7 +857,7 @@ def test_sample_values_projector_multi_qubit(self, device, tol):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
theta = 0.543
@@ -915,7 +915,7 @@ def test_paulix_pauliy(self, device, tol, skip_if):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -959,7 +959,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -1001,7 +1001,7 @@ def test_hermitian(self, device, tol, skip_if):
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
@@ -1095,7 +1095,7 @@ def test_projector(self, device, tol, skip_if): # pylint: disable=too-many-stat
if not dev.shots:
pytest.skip("Device is in analytic mode, cannot test sampling.")
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
@@ -1270,7 +1270,7 @@ def test_var_hermitian(self, device, tol):
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
phi = 0.543
@@ -1306,7 +1306,7 @@ def test_var_projector(self, device, tol):
n_wires = 2
dev = device(n_wires)
- if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
+ if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
phi = 0.543
@@ -1367,7 +1367,7 @@ def test_paulix_pauliy(self, device, tol, skip_if):
"""Test that a tensor product involving PauliX and PauliY works correctly"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -1399,7 +1399,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if):
"""Test that a tensor product involving PauliZ and PauliY and hadamard works correctly"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
theta = 0.432
@@ -1449,7 +1449,7 @@ def circ(obs):
"""
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
@qml.qnode(dev)
@@ -1476,7 +1476,7 @@ def circ(wire_labels):
"""
dev = device(wires=3)
dev_custom_labels = device(wires=label_map)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"supports_tensor_observables": False})
def circ(wire_labels):
@@ -1498,7 +1498,7 @@ def test_hermitian(self, device, tol, skip_if):
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Hermitian" not in dev.observables:
pytest.skip("Skipped because device does not support the Hermitian observable.")
@@ -1570,7 +1570,7 @@ def test_projector(self, device, tol, skip_if):
n_wires = 3
dev = device(n_wires)
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if "Projector" not in dev.observables:
pytest.skip("Skipped because device does not support the Projector observable.")
diff --git a/pennylane/devices/tests/test_templates.py b/pennylane/devices/tests/test_templates.py
index 07b0b56b39c..86e1101340a 100644
--- a/pennylane/devices/tests/test_templates.py
+++ b/pennylane/devices/tests/test_templates.py
@@ -33,7 +33,7 @@
def check_op_supported(op, dev):
"""Skip test if device does not support an operation. Works with both device APIs"""
- if isinstance(dev, qml.Device):
+ if isinstance(dev, qml.devices.LegacyDevice):
if op.name not in dev.operations:
pytest.skip("operation not supported.")
else:
diff --git a/pennylane/devices/tests/test_tracker.py b/pennylane/devices/tests/test_tracker.py
index 75fc6145ac8..c46a5ad952e 100644
--- a/pennylane/devices/tests/test_tracker.py
+++ b/pennylane/devices/tests/test_tracker.py
@@ -26,7 +26,9 @@ def test_tracker_initialization(self, device):
dev = device(1)
- if isinstance(dev, qml.Device) and not dev.capabilities().get("supports_tracker", False):
+ if isinstance(dev, qml.devices.LegacyDevice) and not dev.capabilities().get(
+ "supports_tracker", False
+ ):
pytest.skip("Device does not support a tracker")
assert isinstance(dev.tracker, qml.Tracker)
@@ -36,7 +38,9 @@ def test_tracker_updated_in_execution_mode(self, device):
dev = device(1)
- if isinstance(dev, qml.Device) and not dev.capabilities().get("supports_tracker", False):
+ if isinstance(dev, qml.devices.LegacyDevice) and not dev.capabilities().get(
+ "supports_tracker", False
+ ):
pytest.skip("Device does not support a tracker")
@qml.qnode(dev, diff_method="parameter-shift")
diff --git a/pennylane/measurements/classical_shadow.py b/pennylane/measurements/classical_shadow.py
index 6c502858cf8..200a48a25a5 100644
--- a/pennylane/measurements/classical_shadow.py
+++ b/pennylane/measurements/classical_shadow.py
@@ -284,7 +284,11 @@ def process(self, tape, device):
n_snapshots = device.shots
seed = self.seed
- with qml.workflow.set_shots(device, shots=1):
+ original_shots = device.shots
+ original_shot_vector = device._shot_vector # pylint: disable=protected-access
+
+ try:
+ device.shots = 1
# slow implementation but works for all devices
n_qubits = len(wires)
mapped_wires = np.array(device.map_wires(wires))
@@ -311,6 +315,9 @@ def process(self, tape, device):
device.apply(tape.operations, rotations=tape.diagonalizing_gates + rotations)
outcomes[t] = device.generate_samples()[0][mapped_wires]
+ finally:
+ device.shots = original_shots
+ device._shot_vector = original_shot_vector # pylint: disable=protected-access
return qml.math.cast(qml.math.stack([outcomes, recipes]), dtype=np.int8)
diff --git a/pennylane/measurements/expval.py b/pennylane/measurements/expval.py
index 1a95f9e7ce0..3915ef481f0 100644
--- a/pennylane/measurements/expval.py
+++ b/pennylane/measurements/expval.py
@@ -151,5 +151,5 @@ def _calculate_expectation(self, probabilities):
Args:
probabilities (array): the probabilities of collapsing to eigen states
"""
- eigvals = qml.math.asarray(self.eigvals(), dtype="float64")
+ eigvals = qml.math.cast_like(self.eigvals(), 1.0)
return qml.math.dot(probabilities, eigvals)
diff --git a/pennylane/ops/op_math/controlled_decompositions.py b/pennylane/ops/op_math/controlled_decompositions.py
index 26de3761cf6..93a6e82b02f 100644
--- a/pennylane/ops/op_math/controlled_decompositions.py
+++ b/pennylane/ops/op_math/controlled_decompositions.py
@@ -55,7 +55,7 @@ def _convert_to_su2(U, return_global_phase=False):
with np.errstate(divide="ignore", invalid="ignore"):
dets = math.linalg.det(U)
- global_phase = math.cast_like(math.angle(dets), 1j) / 2
+ global_phase = math.cast_like(math.angle(dets), 1.0) / 2
U_SU2 = math.cast_like(U, dets) * math.exp(-1j * global_phase)
return (U_SU2, global_phase) if return_global_phase else U_SU2
diff --git a/pennylane/ops/qubit/special_unitary.py b/pennylane/ops/qubit/special_unitary.py
index 0cff0418f31..14807e1468f 100644
--- a/pennylane/ops/qubit/special_unitary.py
+++ b/pennylane/ops/qubit/special_unitary.py
@@ -480,8 +480,8 @@ def compute_matrix(theta: TensorLike, num_wires: int) -> TensorLike:
[-0.0942679 +0.47133952j, 0.83004499+0.28280371j]])
"""
interface = qml.math.get_interface(theta)
- if interface == "tensorflow":
- theta = qml.math.cast_like(theta, 1j)
+ theta = qml.math.cast_like(theta, 1j)
+
if num_wires > 5:
matrices = product(_pauli_matrices, repeat=num_wires)
# Drop the identity from the generator of matrices
diff --git a/pennylane/qcut/cutstrategy.py b/pennylane/qcut/cutstrategy.py
index a26a528b338..2e92a8cdb87 100644
--- a/pennylane/qcut/cutstrategy.py
+++ b/pennylane/qcut/cutstrategy.py
@@ -41,7 +41,7 @@ class CutStrategy:
check out the :func:`qml.cut_circuit() ` transform for more details.
Args:
- devices (Union[qml.Device, Sequence[qml.Device]]): Single, or Sequence of, device(s).
+ devices (Union[qml.devices.Device, Sequence[qml.devices.Device]]): Single, or Sequence of, device(s).
Optional only when ``max_free_wires`` is provided.
max_free_wires (int): Number of wires for the largest available device. Optional only when
``devices`` is provided where it defaults to the maximum number of wires among
diff --git a/pennylane/resource/specs.py b/pennylane/resource/specs.py
index 2123f8653e1..64233ab24d9 100644
--- a/pennylane/resource/specs.py
+++ b/pennylane/resource/specs.py
@@ -208,18 +208,24 @@ def specs_qnode(*args, **kwargs) -> Union[list[dict], dict]:
else qnode.diff_method
)
- if isinstance(qnode.gradient_fn, qml.transforms.core.TransformDispatcher):
- info["gradient_fn"] = _get_absolute_import_path(qnode.gradient_fn)
+ gradient_fn = qml.QNode.get_gradient_fn(
+ qnode.device,
+ qnode.interface,
+ qnode.diff_method,
+ tape=tape,
+ )[0]
+ if isinstance(gradient_fn, qml.transforms.core.TransformDispatcher):
+ info["gradient_fn"] = _get_absolute_import_path(gradient_fn)
try:
- info["num_gradient_executions"] = len(qnode.gradient_fn(tape)[0])
+ info["num_gradient_executions"] = len(gradient_fn(tape)[0])
except Exception as e: # pylint: disable=broad-except
# In the case of a broad exception, we don't want the `qml.specs` transform
# to fail. Instead, we simply indicate that the number of gradient executions
# is not supported for the reason specified.
info["num_gradient_executions"] = f"NotSupported: {str(e)}"
else:
- info["gradient_fn"] = qnode.gradient_fn
+ info["gradient_fn"] = gradient_fn
infos.append(info)
diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py
index 9758e8fc185..7cc6809ff82 100644
--- a/pennylane/tape/qscript.py
+++ b/pennylane/tape/qscript.py
@@ -555,7 +555,7 @@ def trainable_params(self) -> list[int]:
.. note::
For devices that support native backpropagation (such as
- ``default.qubit.tf`` and ``default.qubit.autograd``), this
+ ``default.qubit`` and ``default.mixed``), this
property contains no relevant information when using
backpropagation to compute gradients.
diff --git a/pennylane/templates/subroutines/fable.py b/pennylane/templates/subroutines/fable.py
index 16f1160ddd0..d9637676738 100644
--- a/pennylane/templates/subroutines/fable.py
+++ b/pennylane/templates/subroutines/fable.py
@@ -166,17 +166,19 @@ def compute_decomposition(input_matrix, wires, tol=0): # pylint:disable=argumen
for c_wire in nots:
op_list.append(qml.CNOT(wires=[c_wire] + ancilla))
op_list.append(qml.RY(2 * theta, wires=ancilla))
+ nots = {}
nots[wire_map[control_index]] = 1
+ continue
+
+ if qml.math.abs(2 * theta) > tol:
+ for c_wire in nots:
+ op_list.append(qml.CNOT(wires=[c_wire] + ancilla))
+ op_list.append(qml.RY(2 * theta, wires=ancilla))
+ nots = {}
+ if wire_map[control_index] in nots:
+ del nots[wire_map[control_index]]
else:
- if abs(2 * theta) > tol:
- for c_wire in nots:
- op_list.append(qml.CNOT(wires=[c_wire] + ancilla))
- op_list.append(qml.RY(2 * theta, wires=ancilla))
- nots = {}
- if wire_map[control_index] in nots:
- del nots[wire_map[control_index]]
- else:
- nots[wire_map[control_index]] = 1
+ nots[wire_map[control_index]] = 1
for c_wire in nots:
op_list.append(qml.CNOT([c_wire] + ancilla))
diff --git a/pennylane/workflow/construct_batch.py b/pennylane/workflow/construct_batch.py
index bfd788d7b00..1701e459f37 100644
--- a/pennylane/workflow/construct_batch.py
+++ b/pennylane/workflow/construct_batch.py
@@ -18,7 +18,7 @@
from collections.abc import Callable
from contextlib import nullcontext
from functools import wraps
-from typing import Literal, Optional, Union
+from typing import Literal, Union
import pennylane as qml
from pennylane.tape import QuantumScriptBatch
@@ -56,20 +56,24 @@ def wrapped_expand_fn(tape, *args, **kwargs):
return qml.transform(wrapped_expand_fn)
-def _get_full_transform_program(qnode: QNode) -> "qml.transforms.core.TransformProgram":
+def _get_full_transform_program(
+ qnode: QNode, gradient_fn
+) -> "qml.transforms.core.TransformProgram":
program = qml.transforms.core.TransformProgram(qnode.transform_program)
- if getattr(qnode.gradient_fn, "expand_transform", False):
+ if getattr(gradient_fn, "expand_transform", False):
program.add_transform(
- qml.transform(qnode.gradient_fn.expand_transform),
+ qml.transform(gradient_fn.expand_transform),
**qnode.gradient_kwargs,
)
- config = _make_execution_config(qnode, qnode.gradient_fn)
+ config = _make_execution_config(qnode, gradient_fn)
return program + qnode.device.preprocess(config)[0]
-def get_transform_program(qnode: "QNode", level=None) -> "qml.transforms.core.TransformProgram":
+def get_transform_program(
+ qnode: "QNode", level=None, gradient_fn="unset"
+) -> "qml.transforms.core.TransformProgram":
"""Extract a transform program at a designated level.
Args:
@@ -81,6 +85,8 @@ def get_transform_program(qnode: "QNode", level=None) -> "qml.transforms.core.Tr
* ``int``: How many transforms to include, starting from the front of the program
* ``slice``: a slice to select out components of the transform program.
+ gradient_fn (None, str, TransformDispatcher): The processed gradient fn for the workflow.
+
Returns:
TransformProgram: the transform program corresponding to the requested level.
@@ -174,7 +180,10 @@ def circuit():
TransformProgram(validate_device_wires, mid_circuit_measurements, decompose, validate_measurements, validate_observables)
"""
- full_transform_program = _get_full_transform_program(qnode)
+ if gradient_fn == "unset":
+ gradient_fn = QNode.get_gradient_fn(qnode.device, qnode.interface, qnode.diff_method)[0]
+
+ full_transform_program = _get_full_transform_program(qnode, gradient_fn)
num_user = len(qnode.transform_program)
if qnode.transform_program.has_final_transform:
@@ -193,7 +202,7 @@ def circuit():
elif level == "gradient":
readd_final_transform = True
- level = num_user + 1 if getattr(qnode.gradient_fn, "expand_transform", False) else num_user
+ level = num_user + 1 if getattr(gradient_fn, "expand_transform", False) else num_user
elif isinstance(level, str):
raise ValueError(
f"level {level} not recognized. Acceptable strings are 'device', 'top', 'user', and 'gradient'."
@@ -211,8 +220,8 @@ def circuit():
def construct_batch(
- qnode: QNode,
- level: Optional[Union[Literal["top", "user", "device", "gradient"], int, slice]] = "user",
+ qnode: Union[QNode, "qml.qnn.KerasLayer", "qml.qnn.TorchLayer"],
+ level: Union[Literal["top", "user", "device", "gradient"], int, slice, None] = "user",
) -> Callable:
"""Construct the batch of tapes and post processing for a designated stage in the transform program.
@@ -350,8 +359,10 @@ def batch_constructor(*args, **kwargs) -> tuple[QuantumScriptBatch, Postprocessi
params = initial_tape.get_parameters(trainable_only=False)
initial_tape.trainable_params = qml.math.get_trainable_indices(params)
- qnode._update_gradient_fn(tape=initial_tape)
- program = get_transform_program(qnode, level=level)
+ gradient_fn = QNode.get_gradient_fn(
+ qnode.device, qnode.interface, qnode.diff_method, tape=initial_tape
+ )[0]
+ program = get_transform_program(qnode, level=level, gradient_fn=gradient_fn)
return program((initial_tape,))
diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py
index 2465c70e481..7445bcea2b7 100644
--- a/pennylane/workflow/execution.py
+++ b/pennylane/workflow/execution.py
@@ -165,7 +165,7 @@ def _get_ml_boundary_execute(
else:
from .interfaces.jax import jax_jvp_execute as ml_boundary
- except ImportError as e: # pragma: no-cover
+ except ImportError as e: # pragma: no cover
raise qml.QuantumFunctionError(
f"{mapped_interface} not found. Please install the latest "
f"version of {mapped_interface} to enable the '{mapped_interface}' interface."
diff --git a/pennylane/workflow/interfaces/jax_jit.py b/pennylane/workflow/interfaces/jax_jit.py
index d52f686148e..cdfade8a979 100644
--- a/pennylane/workflow/interfaces/jax_jit.py
+++ b/pennylane/workflow/interfaces/jax_jit.py
@@ -96,7 +96,7 @@ def _get_counts_shape(mp: "qml.measurements.CountsMP", num_device_wires=0):
return outcome_counts
-def _result_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.Device"):
+def _result_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.devices.Device"):
"""Auxiliary function for creating the shape and dtype object structure
given a tape."""
@@ -125,7 +125,7 @@ def struct(mp, shots):
return tuple(shape) if tape.shots.has_partitioned_shots else shape[0]
-def _jac_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.Device"):
+def _jac_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.devices.Device"):
"""The shape of a jacobian for a single tape given a device.
Args:
@@ -167,14 +167,9 @@ def pure_callback_wrapper(p):
new_tapes = _set_fn(tapes.vals, p)
return _to_jax(execute_fn(new_tapes))
- if isinstance(device, qml.Device):
- device_supports_vectorization = device.capabilities().get("supports_broadcasting")
- else:
- # first order way of determining native parameter broadcasting support
- # will be inaccurate when inclusion of broadcast_expand depends on ExecutionConfig values (like adjoint)
- device_supports_vectorization = (
- qml.transforms.broadcast_expand not in device.preprocess()[0]
- )
+ # first order way of determining native parameter broadcasting support
+ # will be inaccurate when inclusion of broadcast_expand depends on ExecutionConfig values (like adjoint)
+ device_supports_vectorization = qml.transforms.broadcast_expand not in device.preprocess()[0]
out = jax.pure_callback(
pure_callback_wrapper, shape_dtype_structs, params, vectorized=device_supports_vectorization
)
diff --git a/pennylane/workflow/jacobian_products.py b/pennylane/workflow/jacobian_products.py
index d5ede1227a5..d3ae50dca65 100644
--- a/pennylane/workflow/jacobian_products.py
+++ b/pennylane/workflow/jacobian_products.py
@@ -18,7 +18,7 @@
import inspect
import logging
from collections.abc import Callable, Sequence
-from typing import Optional, Union
+from typing import Optional
import numpy as np
from cachetools import LRUCache
@@ -334,7 +334,7 @@ def compute_jacobian(self, tapes: QuantumScriptBatch):
class DeviceDerivatives(JacobianProductCalculator):
- """Calculate jacobian products via a device provided jacobian. This class relies on either ``qml.Device.gradients`` or
+ """Calculate jacobian products via a device provided jacobian. This class relies on
``qml.devices.Device.compute_derivatives``.
Args:
@@ -399,7 +399,7 @@ def __repr__(self):
def __init__(
self,
- device: Union["qml.devices.Device", "qml.Device"],
+ device: "qml.devices.Device",
execution_config: Optional["qml.devices.ExecutionConfig"] = None,
):
if execution_config is None:
diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py
index f8f724e58cf..408a0794674 100644
--- a/pennylane/workflow/qnode.py
+++ b/pennylane/workflow/qnode.py
@@ -524,7 +524,7 @@ def __init__(
# input arguments
self.func = func
self.device = device
- self._interface = interface
+ self._interface = None if diff_method is None else interface
self.diff_method = diff_method
mcm_config = qml.devices.MCMConfig(mcm_method=mcm_method, postselect_mode=postselect_mode)
cache = (max_diff > 1) if cache == "auto" else cache
@@ -542,14 +542,48 @@ def __init__(
# internal data attributes
self._tape = None
self._qfunc_output = None
- self._user_gradient_kwargs = gradient_kwargs
- self.gradient_fn = None
- self.gradient_kwargs = {}
+ self._gradient_fn = None
+ self.gradient_kwargs = gradient_kwargs
self._transform_program = TransformProgram()
- self._update_gradient_fn()
functools.update_wrapper(self, func)
+ # validation check. Will raise error if bad diff_method
+ if diff_method is not None:
+ QNode.get_gradient_fn(self.device, self.interface, self.diff_method)
+
+ @property
+ def gradient_fn(self):
+ """A processed version of ``QNode.diff_method``.
+
+ .. warning::
+
+ This property is deprecated in v0.39 and will be removed in v0.40.
+
+ Please see ``QNode.diff_method`` instead.
+
+ """
+ warnings.warn(
+ "QNode.gradient_fn is deprecated. Please use QNode.diff_method instead.",
+ qml.PennyLaneDeprecationWarning,
+ )
+ if self.diff_method is None:
+ return None
+
+ if (
+ self.device.name == "lightning.qubit"
+ and qml.metric_tensor in self.transform_program
+ and self.diff_method == "best"
+ ):
+ return qml.gradients.param_shift
+
+ if self.tape is None and self.device.shots:
+ tape = qml.tape.QuantumScript([], [], shots=self.device.shots)
+ else:
+ tape = self.tape
+
+ return QNode.get_gradient_fn(self.device, self.interface, self.diff_method, tape=tape)[0]
+
def __copy__(self) -> "QNode":
copied_qnode = QNode.__new__(QNode)
for attr, value in vars(self).items():
@@ -590,7 +624,6 @@ def interface(self, value: SupportedInterfaceUserInput):
)
self._interface = INTERFACE_MAP[value]
- self._update_gradient_fn(shots=self.device.shots)
@property
def transform_program(self) -> TransformProgram:
@@ -605,28 +638,6 @@ def add_transform(self, transform_container: TransformContainer):
"""
self._transform_program.push_back(transform_container=transform_container)
- def _update_gradient_fn(self, shots=None, tape: Optional["qml.tape.QuantumTape"] = None):
- if self.diff_method is None:
- self._interface = None
- self.gradient_fn = None
- self.gradient_kwargs = {}
- return
- if tape is None and shots:
- tape = qml.tape.QuantumScript([], [], shots=shots)
-
- diff_method = self.diff_method
- if (
- self.device.name == "lightning.qubit"
- and qml.metric_tensor in self.transform_program
- and self.diff_method == "best"
- ):
- diff_method = "parameter-shift"
-
- self.gradient_fn, self.gradient_kwargs, self.device = QNode.get_gradient_fn(
- self.device, self.interface, diff_method, tape=tape
- )
- self.gradient_kwargs.update(self._user_gradient_kwargs or {})
-
# pylint: disable=too-many-return-statements
@staticmethod
@debug_logger
@@ -652,6 +663,8 @@ def get_gradient_fn(
tuple[str or .TransformDispatcher, dict, .device.Device: Tuple containing the ``gradient_fn``,
``gradient_kwargs``, and the device to use when calling the execute function.
"""
+ if diff_method is None:
+ return None, {}, device
config = _make_execution_config(None, diff_method)
@@ -859,8 +872,22 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result:
Result
"""
-
+ if (
+ self.device.name == "lightning.qubit"
+ and qml.metric_tensor in self.transform_program
+ and self.diff_method == "best"
+ ):
+ gradient_fn = qml.gradients.param_shift
+ else:
+ gradient_fn = QNode.get_gradient_fn(
+ self.device, self.interface, self.diff_method, tape=self.tape
+ )[0]
execute_kwargs = copy.copy(self.execute_kwargs)
+
+ gradient_kwargs = copy.copy(self.gradient_kwargs)
+ if gradient_fn is qml.gradients.param_shift_cv:
+ gradient_kwargs["dev"] = self.device
+
mcm_config = copy.copy(execute_kwargs["mcm_config"])
if not self._tape.shots:
mcm_config.postselect_mode = None
@@ -875,7 +902,7 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result:
full_transform_program = qml.transforms.core.TransformProgram(self.transform_program)
inner_transform_program = qml.transforms.core.TransformProgram()
- config = _make_execution_config(self, self.gradient_fn, mcm_config)
+ config = _make_execution_config(self, gradient_fn, mcm_config)
device_transform_program, config = self.device.preprocess(execution_config=config)
if config.use_device_gradient:
@@ -884,10 +911,10 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result:
inner_transform_program += device_transform_program
# Add the gradient expand to the program if necessary
- if getattr(self.gradient_fn, "expand_transform", False):
+ if getattr(gradient_fn, "expand_transform", False):
full_transform_program.insert_front_transform(
- qml.transform(self.gradient_fn.expand_transform),
- **self.gradient_kwargs,
+ qml.transform(gradient_fn.expand_transform),
+ **gradient_kwargs,
)
# Calculate the classical jacobians if necessary
@@ -900,12 +927,12 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result:
res = qml.execute(
(self._tape,),
device=self.device,
- gradient_fn=self.gradient_fn,
+ gradient_fn=gradient_fn,
interface=self.interface,
transform_program=full_transform_program,
inner_transform=inner_transform_program,
config=config,
- gradient_kwargs=self.gradient_kwargs,
+ gradient_kwargs=gradient_kwargs,
**execute_kwargs,
)
res = res[0]
@@ -924,6 +951,9 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result:
def _impl_call(self, *args, **kwargs) -> qml.typing.Result:
+ # construct the tape
+ self.construct(args, kwargs)
+
old_interface = self.interface
if old_interface == "auto":
interface = (
@@ -933,27 +963,12 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result:
)
self._interface = INTERFACE_MAP[interface]
- if self._qfunc_uses_shots_arg:
- override_shots = False
- else:
- if "shots" not in kwargs:
- kwargs["shots"] = self.device.shots
- override_shots = kwargs["shots"]
-
- # construct the tape
- self.construct(args, kwargs)
-
- original_grad_fn = [self.gradient_fn, self.gradient_kwargs, self.device]
- self._update_gradient_fn(shots=override_shots, tape=self._tape)
-
try:
res = self._execution_component(args, kwargs)
finally:
if old_interface == "auto":
self._interface = "auto"
- _, self.gradient_kwargs, self.device = original_grad_fn
-
return res
def __call__(self, *args, **kwargs) -> qml.typing.Result:
diff --git a/pennylane/workflow/set_shots.py b/pennylane/workflow/set_shots.py
index 1bc7dff7f33..aaaed1a1a44 100644
--- a/pennylane/workflow/set_shots.py
+++ b/pennylane/workflow/set_shots.py
@@ -17,6 +17,7 @@
"""
# pylint: disable=protected-access
import contextlib
+import warnings
import pennylane as qml
from pennylane.measurements import Shots
@@ -26,6 +27,20 @@
def set_shots(device, shots):
r"""Context manager to temporarily change the shots of a device.
+
+ .. warning::
+
+ ``set_shots`` is deprecated and will be removed in PennyLane version v0.40.
+
+ To dynamically update the shots on the workflow, shots can be manually set on a ``QNode`` call:
+
+ >>> circuit(shots=my_new_shots)
+
+ When working with the internal tapes, shots should be set on each tape.
+
+ >>> tape = qml.tape.QuantumScript([], [qml.sample()], shots=50)
+
+
This context manager can be used in two ways.
As a standard context manager:
@@ -47,6 +62,12 @@ def set_shots(device, shots):
"The new device interface is not compatible with `set_shots`. "
"Set shots when calling the qnode or put the shots on the QuantumTape."
)
+ warnings.warn(
+ "set_shots is deprecated.\n"
+ "Please dyanmically update shots via keyword argument when calling a QNode "
+ " or set shots on the tape.",
+ qml.PennyLaneDeprecationWarning,
+ )
if isinstance(shots, Shots):
shots = shots.shot_vector if shots.has_partitioned_shots else shots.total_shots
if shots == device.shots:
diff --git a/requirements-ci.txt b/requirements-ci.txt
index 2fd445c728a..d552b95e904 100644
--- a/requirements-ci.txt
+++ b/requirements-ci.txt
@@ -1,4 +1,4 @@
-numpy~=1.26
+numpy
scipy<=1.13.0
cvxpy
cvxopt
diff --git a/setup.py b/setup.py
index 4e04fbeb291..41ae9775027 100644
--- a/setup.py
+++ b/setup.py
@@ -51,7 +51,6 @@
"default.qubit = pennylane.devices:DefaultQubit",
"default.qubit.legacy = pennylane.devices:DefaultQubitLegacy",
"default.gaussian = pennylane.devices:DefaultGaussian",
- "default.qubit.autograd = pennylane.devices.default_qubit_autograd:DefaultQubitAutograd",
"default.mixed = pennylane.devices.default_mixed:DefaultMixed",
"null.qubit = pennylane.devices.null_qubit:NullQubit",
"default.qutrit = pennylane.devices.default_qutrit:DefaultQutrit",
diff --git a/tests/capture/test_capture_diff.py b/tests/capture/test_capture_diff.py
index cf7834aafb8..07596e01b47 100644
--- a/tests/capture/test_capture_diff.py
+++ b/tests/capture/test_capture_diff.py
@@ -99,11 +99,11 @@ def func_jax(x):
assert qml.math.allclose(func_qml(x), jax_out)
# Check overall jaxpr properties
- if isinstance(argnum, int):
- argnum = [argnum]
jaxpr = jax.make_jaxpr(func_qml)(x)
assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)]
assert len(jaxpr.eqns) == 3
+ if isinstance(argnum, int):
+ argnum = [argnum]
assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * len(argnum)
grad_eqn = jaxpr.eqns[2]
@@ -260,6 +260,106 @@ def circuit(x):
jax.config.update("jax_enable_x64", initial_mode)
+ @pytest.mark.parametrize("argnum", ([0, 1], [0], [1]))
+ def test_grad_pytree_input(self, x64_mode, argnum):
+ """Test that the qml.grad primitive can be captured with pytree inputs."""
+
+ initial_mode = jax.config.jax_enable_x64
+ jax.config.update("jax_enable_x64", x64_mode)
+ fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32
+
+ def inner_func(x, y):
+ return jnp.prod(jnp.sin(x["a"]) * jnp.cos(y[0]["b"][1]) ** 2)
+
+ def func_qml(x):
+ return qml.grad(inner_func, argnum=argnum)(
+ {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]},)
+ )
+
+ def func_jax(x):
+ return jax.grad(inner_func, argnums=argnum)(
+ {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]},)
+ )
+
+ x = 0.7
+ jax_out = func_jax(x)
+ jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out)
+ qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(func_qml(x))
+ assert jax_out_tree == qml_out_tree
+ assert qml.math.allclose(jax_out_flat, qml_out_flat)
+
+ # Check overall jaxpr properties
+ jaxpr = jax.make_jaxpr(func_qml)(x)
+ assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)]
+ assert len(jaxpr.eqns) == 3
+ argnum = [argnum] if isinstance(argnum, int) else argnum
+ assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * len(argnum)
+
+ grad_eqn = jaxpr.eqns[2]
+ diff_eqn_assertions(grad_eqn, grad_prim, argnum=argnum)
+ assert [var.aval for var in grad_eqn.outvars] == jaxpr.out_avals
+ assert len(grad_eqn.params["jaxpr"].eqns) == 6 # 5 numeric eqns, 1 conversion eqn
+
+ manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x)
+ manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out)
+ # Assert that the output from the manual evaluation is flat
+ assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1]
+ assert qml.math.allclose(jax_out_flat, manual_out_flat)
+
+ jax.config.update("jax_enable_x64", initial_mode)
+
+ @pytest.mark.parametrize("argnum", ([0, 1, 2], [0, 2], [1], 0))
+ def test_grad_qnode_with_pytrees(self, argnum, x64_mode):
+ """Test capturing the gradient of a qnode that uses Pytrees."""
+ # pylint: disable=protected-access
+ initial_mode = jax.config.jax_enable_x64
+ jax.config.update("jax_enable_x64", x64_mode)
+ fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32
+
+ dev = qml.device("default.qubit", wires=2)
+
+ @qml.qnode(dev)
+ def circuit(x, y, z):
+ qml.RX(x["a"], wires=0)
+ qml.RY(y, wires=0)
+ qml.RZ(z[1][0], wires=0)
+ return qml.expval(qml.X(0))
+
+ dcircuit = qml.grad(circuit, argnum=argnum)
+ x = {"a": 0.6, "b": 0.9}
+ y = 0.6
+ z = ({"c": 0.5}, [0.2, 0.3])
+ qml_out = dcircuit(x, y, z)
+ qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(qml_out)
+ jax_out = jax.grad(circuit, argnums=argnum)(x, y, z)
+ jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out)
+ assert jax_out_tree == qml_out_tree
+ assert qml.math.allclose(jax_out_flat, qml_out_flat)
+
+ jaxpr = jax.make_jaxpr(dcircuit)(x, y, z)
+
+ assert len(jaxpr.eqns) == 1 # grad equation
+ assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * 6
+ argnum = [argnum] if isinstance(argnum, int) else argnum
+ num_out_avals = 2 * (0 in argnum) + (1 in argnum) + 3 * (2 in argnum)
+ assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * num_out_avals
+
+ grad_eqn = jaxpr.eqns[0]
+ assert all(invar.aval == in_aval for invar, in_aval in zip(grad_eqn.invars, jaxpr.in_avals))
+ flat_argnum = [0, 1] * (0 in argnum) + [2] * (1 in argnum) + [3, 4, 5] * (2 in argnum)
+ diff_eqn_assertions(grad_eqn, grad_prim, argnum=flat_argnum)
+ grad_jaxpr = grad_eqn.params["jaxpr"]
+ assert len(grad_jaxpr.eqns) == 1 # qnode equation
+
+ flat_args = jax.tree_util.tree_leaves((x, y, z))
+ manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, *flat_args)
+ manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out)
+ # Assert that the output from the manual evaluation is flat
+ assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1]
+ assert qml.math.allclose(jax_out_flat, manual_out_flat)
+
+ jax.config.update("jax_enable_x64", initial_mode)
+
def _jac_allclose(jac1, jac2, num_axes, atol=1e-8):
"""Test that two Jacobians, given as nested sequences of arrays, are equal."""
@@ -279,10 +379,6 @@ class TestJacobian:
@pytest.mark.parametrize("argnum", ([0, 1], [0], [1], 0, 1))
def test_classical_jacobian(self, x64_mode, argnum):
"""Test that the qml.jacobian primitive can be captured with classical nodes."""
- if isinstance(argnum, list) and len(argnum) > 1:
- # These cases will only be unlocked with Pytree support
- pytest.xfail()
-
initial_mode = jax.config.jax_enable_x64
jax.config.update("jax_enable_x64", x64_mode)
fdtype = jnp.float64 if x64_mode else jnp.float32
@@ -307,14 +403,14 @@ def inner_func(x, y):
func_jax = jax.jacobian(inner_func, argnums=argnum)
jax_out = func_jax(x, y)
- num_axes = 1 if isinstance(argnum, int) else 2
- assert _jac_allclose(func_qml(x, y), jax_out, num_axes)
+ qml_out = func_qml(x, y)
+ num_axes = 1 if (int_argnum := isinstance(argnum, int)) else 2
+ assert _jac_allclose(qml_out, jax_out, num_axes)
# Check overall jaxpr properties
- jaxpr = jax.make_jaxpr(func_jax)(x, y)
jaxpr = jax.make_jaxpr(func_qml)(x, y)
- if isinstance(argnum, int):
+ if int_argnum:
argnum = [argnum]
exp_in_avals = [shaped_array(shape) for shape in [(4,), (2, 3)]]
@@ -331,6 +427,9 @@ def inner_func(x, y):
diff_eqn_assertions(jac_eqn, jacobian_prim, argnum=argnum)
manual_eval = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x, y)
+ # Evaluating jaxpr gives flat list results. Need to adapt the JAX output to that
+ if not int_argnum:
+ jax_out = sum(jax_out, start=())
assert _jac_allclose(manual_eval, jax_out, num_axes)
jax.config.update("jax_enable_x64", initial_mode)
@@ -354,7 +453,6 @@ def func(x):
return jnp.prod(x) * jnp.sin(x), jnp.sum(x**2)
x = jnp.array([0.7, -0.9, 0.6, 0.3])
- x = x[:1]
dim = len(x)
eye = jnp.eye(dim)
@@ -382,15 +480,20 @@ def func(x):
# 2nd order
qml_func_2 = qml.jacobian(qml_func_1)
+ hyperdiag = qml.numpy.zeros((4, 4, 4))
+ for i in range(4):
+ hyperdiag[i, i, i] = 1
expected_2 = (
prod_sin[:, None, None] / x[None, :, None] / x[None, None, :]
+ - jnp.tensordot(prod_sin, eye / x**2, axes=0) # Correct diagonal entries
+ prod_cos_e_i[:, :, None] / x[None, None, :]
+ prod_cos_e_i[:, None, :] / x[None, :, None]
- - jnp.tensordot(prod_sin, eye + eye / x**2, axes=0),
- jnp.tensordot(jnp.ones(dim), eye * 2, axes=0),
+ - prod_sin * hyperdiag,
+ eye * 2,
)
# Output only has one tuple axis
- assert _jac_allclose(qml_func_2(x), expected_2, 1)
+ atol = 1e-8 if x64_mode else 2e-7
+ assert _jac_allclose(qml_func_2(x), expected_2, 1, atol=atol)
jaxpr_2 = jax.make_jaxpr(qml_func_2)(x)
assert jaxpr_2.in_avals == [jax.core.ShapedArray((dim,), fdtype)]
@@ -406,7 +509,7 @@ def func(x):
assert jac_eqn.params["jaxpr"].eqns[0].primitive == jacobian_prim
manual_eval_2 = jax.core.eval_jaxpr(jaxpr_2.jaxpr, jaxpr_2.consts, x)
- assert _jac_allclose(manual_eval_2, expected_2, 1)
+ assert _jac_allclose(manual_eval_2, expected_2, 1, atol=atol)
jax.config.update("jax_enable_x64", initial_mode)
@@ -473,3 +576,58 @@ def circuit(x):
assert _jac_allclose(manual_res, expected_res, 1)
jax.config.update("jax_enable_x64", initial_mode)
+
+ @pytest.mark.parametrize("argnum", ([0, 1], [0], [1]))
+ def test_jacobian_pytrees(self, x64_mode, argnum):
+ """Test that the qml.jacobian primitive can be captured with
+ pytree inputs and outputs."""
+
+ initial_mode = jax.config.jax_enable_x64
+ jax.config.update("jax_enable_x64", x64_mode)
+ fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32
+
+ def inner_func(x, y):
+ return {
+ "prod_cos": jnp.prod(jnp.sin(x["a"]) * jnp.cos(y[0]["b"][1]) ** 2),
+ "sum_sin": jnp.sum(jnp.sin(x["a"]) * jnp.sin(y[1]["c"]) ** 2),
+ }
+
+ def func_qml(x):
+ return qml.jacobian(inner_func, argnum=argnum)(
+ {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]}, {"c": 0.5})
+ )
+
+ def func_jax(x):
+ return jax.jacobian(inner_func, argnums=argnum)(
+ {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]}, {"c": 0.5})
+ )
+
+ x = 0.7
+ jax_out = func_jax(x)
+ jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out)
+ qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(func_qml(x))
+ assert jax_out_tree == qml_out_tree
+ assert qml.math.allclose(jax_out_flat, qml_out_flat)
+
+ # Check overall jaxpr properties
+ jaxpr = jax.make_jaxpr(func_qml)(x)
+ assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)]
+ assert len(jaxpr.eqns) == 3
+
+ argnum = [argnum] if isinstance(argnum, int) else argnum
+ # Compute the flat argnum in order to determine the expected number of out tracers
+ flat_argnum = [0] * (0 in argnum) + [1, 2] * (1 in argnum)
+ assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype)] * (2 * len(flat_argnum))
+
+ jac_eqn = jaxpr.eqns[2]
+
+ diff_eqn_assertions(jac_eqn, jacobian_prim, argnum=flat_argnum)
+ assert [var.aval for var in jac_eqn.outvars] == jaxpr.out_avals
+
+ manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x)
+ manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out)
+ # Assert that the output from the manual evaluation is flat
+ assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1]
+ assert qml.math.allclose(jax_out_flat, manual_out_flat)
+
+ jax.config.update("jax_enable_x64", initial_mode)
diff --git a/tests/conftest.py b/tests/conftest.py
index 3094a2fd784..d478b6f0998 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -36,20 +36,6 @@
TOL_STOCHASTIC = 0.05
-# pylint: disable=too-few-public-methods
-class DummyDevice(DefaultGaussian):
- """Dummy device to allow Kerr operations"""
-
- _operation_map = DefaultGaussian._operation_map.copy()
- _operation_map["Kerr"] = lambda *x, **y: np.identity(2)
-
-
-@pytest.fixture(autouse=True)
-def set_numpy_seed():
- np.random.seed(9872653)
- yield
-
-
@pytest.fixture(scope="function", autouse=True)
def capture_legacy_device_deprecation_warnings():
with warnings.catch_warnings(record=True) as recwarn:
@@ -67,6 +53,20 @@ def capture_legacy_device_deprecation_warnings():
warnings.warn(message=w.message, category=w.category)
+# pylint: disable=too-few-public-methods
+class DummyDevice(DefaultGaussian):
+ """Dummy device to allow Kerr operations"""
+
+ _operation_map = DefaultGaussian._operation_map.copy()
+ _operation_map["Kerr"] = lambda *x, **y: np.identity(2)
+
+
+@pytest.fixture(autouse=True)
+def set_numpy_seed():
+ np.random.seed(9872653)
+ yield
+
+
@pytest.fixture(scope="session")
def tol():
"""Numerical tolerance for equality tests."""
@@ -181,12 +181,12 @@ def mock_device(monkeypatch):
"""A mock instance of the abstract Device class"""
with monkeypatch.context() as m:
- dev = qml.Device
+ dev = qml.devices.LegacyDevice
m.setattr(dev, "__abstractmethods__", frozenset())
m.setattr(dev, "short_name", "mock_device")
m.setattr(dev, "capabilities", lambda cls: {"model": "qubit"})
m.setattr(dev, "operations", {"RX", "RY", "RZ", "CNOT", "SWAP"})
- yield qml.Device(wires=2) # pylint:disable=abstract-class-instantiated
+ yield qml.devices.LegacyDevice(wires=2) # pylint:disable=abstract-class-instantiated
# pylint: disable=protected-access
diff --git a/tests/devices/default_qubit/test_default_qubit_preprocessing.py b/tests/devices/default_qubit/test_default_qubit_preprocessing.py
index 29a1fdca73a..74c30f8661d 100644
--- a/tests/devices/default_qubit/test_default_qubit_preprocessing.py
+++ b/tests/devices/default_qubit/test_default_qubit_preprocessing.py
@@ -139,7 +139,6 @@ def circuit(x):
with dev.tracker:
qml.grad(circuit)(qml.numpy.array(0.1))
- assert circuit.gradient_fn == "adjoint"
assert dev.tracker.totals["execute_and_derivative_batches"] == 1
diff --git a/tests/devices/test_default_mixed_autograd.py b/tests/devices/test_default_mixed_autograd.py
index 6d690054033..c3f98d5955f 100644
--- a/tests/devices/test_default_mixed_autograd.py
+++ b/tests/devices/test_default_mixed_autograd.py
@@ -85,7 +85,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
@@ -267,7 +266,6 @@ def circuit(p):
qml.RX(p[2] / 2, wires=0)
return qml.expval(qml.PauliZ(0))
- assert circuit.gradient_fn == "backprop"
res = circuit(weights)
expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
@@ -340,9 +338,6 @@ def cost(x):
assert np.allclose(res, circuit2(p), atol=tol, rtol=0)
- assert circuit1.gradient_fn == "backprop"
- assert circuit2.gradient_fn is qml.gradients.param_shift
-
grad_fn = qml.jacobian(circuit1, 0)
res = grad_fn(p)
assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0)
@@ -549,14 +544,6 @@ def cost(params):
expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2
assert np.allclose(res, expected_cost, atol=tol, rtol=0)
- # Check that the correct differentiation method is being used.
- if diff_method == "backprop":
- assert circuit.gradient_fn == "backprop"
- elif diff_method == "parameter-shift":
- assert circuit.gradient_fn is qml.gradients.param_shift
- else:
- assert circuit.gradient_fn is qml.gradients.finite_diff
-
res = qml.grad(cost)(params)
expected_grad = (
np.array(
diff --git a/tests/devices/test_default_mixed_jax.py b/tests/devices/test_default_mixed_jax.py
index 3b0f5a2470d..c8ae1ce8ac1 100644
--- a/tests/devices/test_default_mixed_jax.py
+++ b/tests/devices/test_default_mixed_jax.py
@@ -59,7 +59,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
@@ -405,7 +404,6 @@ def circuit(p):
qml.RX(p[2] / 2, wires=0)
return qml.expval(qml.PauliZ(0))
- assert circuit.gradient_fn == "backprop"
res = decorator(circuit)(weights)
expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
@@ -477,9 +475,6 @@ def circuit(x):
res = decorator(circuit1)(p_jax)
assert np.allclose(res, circuit2(p), atol=tol, rtol=0)
- assert circuit1.gradient_fn == "backprop"
- assert circuit2.gradient_fn is qml.gradients.param_shift
-
res = decorator(jacobian_fn(circuit1, 0))(p_jax)
assert np.allclose(res, jax.jacobian(circuit2)(p), atol=tol, rtol=0)
@@ -710,14 +705,6 @@ def cost(params):
expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2
assert np.allclose(res, expected_cost, atol=tol, rtol=0)
- # Check that the correct differentiation method is being used.
- if diff_method == "backprop":
- assert circuit.gradient_fn == "backprop"
- elif diff_method == "parameter-shift":
- assert circuit.gradient_fn is qml.gradients.param_shift
- else:
- assert circuit.gradient_fn is qml.gradients.finite_diff
-
res = jax.grad(cost)(params)
expected_grad = (
diff --git a/tests/devices/test_default_mixed_tf.py b/tests/devices/test_default_mixed_tf.py
index 6fc555a1283..256f70a4668 100644
--- a/tests/devices/test_default_mixed_tf.py
+++ b/tests/devices/test_default_mixed_tf.py
@@ -60,7 +60,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
@@ -300,7 +299,6 @@ def circuit(p):
qml.RX(p[2] / 2, wires=0)
return qml.expval(qml.PauliZ(0))
- assert circuit.gradient_fn == "backprop"
res = circuit(weights)
expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
@@ -380,9 +378,6 @@ def cost(x):
assert np.allclose(res, circuit2(p), atol=tol, rtol=0)
- assert circuit1.gradient_fn == "backprop"
- assert circuit2.gradient_fn is qml.gradients.param_shift
-
res = tape.jacobian(res, p_tf)
assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0)
@@ -631,14 +626,6 @@ def cost(params):
expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2
assert np.allclose(res, expected_cost, atol=tol, rtol=0)
- # Check that the correct differentiation method is being used.
- if diff_method == "backprop":
- assert circuit.gradient_fn == "backprop"
- elif diff_method == "parameter-shift":
- assert circuit.gradient_fn is qml.gradients.param_shift
- else:
- assert circuit.gradient_fn is qml.gradients.finite_diff
-
with tf.GradientTape() as tape:
out = cost(params)
diff --git a/tests/devices/test_default_mixed_torch.py b/tests/devices/test_default_mixed_torch.py
index d85242e15fd..b5de0d0867a 100644
--- a/tests/devices/test_default_mixed_torch.py
+++ b/tests/devices/test_default_mixed_torch.py
@@ -54,7 +54,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
@@ -308,7 +307,6 @@ def circuit(p):
qml.RX(p[2] / 2, wires=0)
return qml.expval(qml.PauliZ(0))
- assert circuit.gradient_fn == "backprop"
res = circuit(weights)
expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
@@ -381,9 +379,6 @@ def circuit(x):
res = circuit1(p_torch)
assert qml.math.allclose(qml.math.stack(res), circuit2(p), atol=tol, rtol=0)
- assert circuit1.gradient_fn == "backprop"
- assert circuit2.gradient_fn is qml.gradients.param_shift
-
grad = torch.autograd.functional.jacobian(circuit1, p_torch)
grad_expected = torch.autograd.functional.jacobian(circuit2, p_torch_2)
@@ -608,14 +603,6 @@ def cost(params):
expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2
assert torch.allclose(res, torch.tensor(expected_cost), atol=tol, rtol=0)
- # Check that the correct differentiation method is being used.
- if diff_method == "backprop":
- assert circuit.gradient_fn == "backprop"
- elif diff_method == "parameter-shift":
- assert circuit.gradient_fn is qml.gradients.param_shift
- else:
- assert circuit.gradient_fn is qml.gradients.finite_diff
-
res.backward()
res = params.grad
diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py
deleted file mode 100644
index bb7c3cda0b4..00000000000
--- a/tests/devices/test_default_qubit_autograd.py
+++ /dev/null
@@ -1,802 +0,0 @@
-# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Integration tests for the ``default.qubit.autograd`` device.
-"""
-import pytest
-
-import pennylane as qml
-from pennylane import DeviceError
-from pennylane import numpy as np
-from pennylane.devices.default_qubit_autograd import DefaultQubitAutograd
-
-
-@pytest.mark.autograd
-def test_analytic_deprecation():
- """Tests if the kwarg `analytic` is used and displays error message."""
- msg = "The analytic argument has been replaced by shots=None. "
- msg += "Please use shots=None instead of analytic=True."
-
- with pytest.raises(
- DeviceError,
- match=msg,
- ):
- qml.device("default.qubit.autograd", wires=1, shots=1, analytic=True)
-
-
-@pytest.mark.autograd
-class TestQNodeIntegration:
- """Integration tests for default.qubit.autograd. This test ensures it integrates
- properly with the PennyLane UI, in particular the new QNode."""
-
- def test_defines_correct_capabilities(self):
- """Test that the device defines the right capabilities"""
-
- dev = qml.device("default.qubit.autograd", wires=1)
- cap = dev.capabilities()
- capabilities = {
- "model": "qubit",
- "supports_finite_shots": True,
- "supports_tensor_observables": True,
- "returns_probs": True,
- "returns_state": True,
- "supports_inverse_operations": True,
- "supports_analytic_computation": True,
- "passthru_interface": "autograd",
- "supports_broadcasting": True,
- "passthru_devices": {
- "autograd": "default.qubit.autograd",
- },
- }
- assert cap == capabilities
-
- def test_load_device(self):
- """Test that the plugin device loads correctly"""
- dev = qml.device("default.qubit.autograd", wires=2)
- assert dev.num_wires == 2
- assert dev.shots == qml.measurements.Shots(None)
- assert dev.short_name == "default.qubit.autograd"
- assert dev.capabilities()["passthru_interface"] == "autograd"
-
- def test_qubit_circuit(self, tol):
- """Test that the device provides the correct
- result for a simple circuit."""
- p = np.array(0.543)
-
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliY(0))
-
- expected = -np.sin(p)
-
- assert circuit.gradient_fn == "backprop"
- assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
-
- def test_qubit_circuit_broadcasted(self, tol):
- """Test that the device provides the correct
- result for a simple broadcasted circuit."""
- p = np.array([0.543, 0.21, 1.5])
-
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliY(0))
-
- expected = -np.sin(p)
-
- assert circuit.gradient_fn == "backprop"
- assert np.allclose(circuit(p), expected, atol=tol, rtol=0)
-
- def test_correct_state(self, tol):
- """Test that the device state is correct after applying a
- quantum function on the device"""
-
- dev = qml.device("default.qubit.autograd", wires=2)
-
- state = dev.state
- expected = np.array([1, 0, 0, 0])
- assert np.allclose(state, expected, atol=tol, rtol=0)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit():
- qml.Hadamard(wires=0)
- qml.RZ(np.pi / 4, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- circuit()
- state = dev.state
-
- amplitude = np.exp(-1j * np.pi / 8) / np.sqrt(2)
-
- expected = np.array([amplitude, 0, np.conj(amplitude), 0])
- assert np.allclose(state, expected, atol=tol, rtol=0)
-
- def test_correct_state_broadcasted(self, tol):
- """Test that the device state is correct after applying a
- broadcasted quantum function on the device"""
-
- dev = qml.device("default.qubit.autograd", wires=2)
-
- state = dev.state
- expected = np.array([1, 0, 0, 0])
- assert np.allclose(state, expected, atol=tol, rtol=0)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit():
- qml.Hadamard(wires=0)
- qml.RZ(np.array([np.pi / 4, np.pi / 2]), wires=0)
- return qml.expval(qml.PauliZ(0))
-
- circuit()
- state = dev.state
-
- phase = np.exp(-1j * np.pi / 8)
-
- expected = np.array(
- [
- [phase / np.sqrt(2), 0, np.conj(phase) / np.sqrt(2), 0],
- [phase**2 / np.sqrt(2), 0, np.conj(phase) ** 2 / np.sqrt(2), 0],
- ]
- )
- assert np.allclose(state, expected, atol=tol, rtol=0)
-
-
-@pytest.mark.autograd
-class TestDtypePreserved:
- """Test that the user-defined dtype of the device is preserved for QNode
- evaluation"""
-
- @pytest.mark.parametrize("r_dtype", [np.float32, np.float64])
- @pytest.mark.parametrize(
- "measurement",
- [
- qml.expval(qml.PauliY(0)),
- qml.var(qml.PauliY(0)),
- qml.probs(wires=[1]),
- qml.probs(wires=[2, 0]),
- ],
- )
- def test_real_dtype(self, r_dtype, measurement):
- """Test that the default qubit plugin returns the correct
- real data type for a simple circuit"""
- p = 0.543
-
- dev = qml.device("default.qubit.autograd", wires=3)
- dev.target_device.R_DTYPE = r_dtype
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(measurement)
-
- res = circuit(p)
- assert res.dtype == r_dtype
-
- @pytest.mark.parametrize("r_dtype", [np.float32, np.float64])
- @pytest.mark.parametrize(
- "measurement",
- [
- qml.expval(qml.PauliY(0)),
- qml.var(qml.PauliY(0)),
- qml.probs(wires=[1]),
- qml.probs(wires=[2, 0]),
- ],
- )
- def test_real_dtype_broadcasted(self, r_dtype, measurement):
- """Test that the default qubit plugin returns the correct
- real data type for a simple broadcasted circuit"""
- p = np.array([0.543, 0.21, 1.6])
-
- dev = qml.device("default.qubit.autograd", wires=3)
- dev.target_device.R_DTYPE = r_dtype
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(measurement)
-
- res = circuit(p)
- assert res.dtype == r_dtype
-
- @pytest.mark.parametrize("c_dtype_name", ["complex64", "complex128"])
- @pytest.mark.parametrize(
- "measurement",
- [qml.state(), qml.density_matrix(wires=[1]), qml.density_matrix(wires=[2, 0])],
- )
- def test_complex_dtype(self, c_dtype_name, measurement):
- """Test that the default qubit plugin returns the correct
- complex data type for a simple circuit"""
- p = 0.543
- c_dtype = np.dtype(c_dtype_name)
-
- dev = qml.device("default.qubit.autograd", wires=3)
- dev.target_device.C_DTYPE = c_dtype
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(measurement)
-
- res = circuit(p)
- assert res.dtype == c_dtype
-
- @pytest.mark.parametrize("c_dtype_name", ["complex64", "complex128"])
- def test_complex_dtype_broadcasted(self, c_dtype_name):
- """Test that the default qubit plugin returns the correct
- complex data type for a simple broadcasted circuit"""
- p = np.array([0.543, 0.21, 1.6])
- c_dtype = np.dtype(c_dtype_name)
-
- dev = qml.device("default.qubit.autograd", wires=3)
- dev.target_device.C_DTYPE = c_dtype
-
- measurement = qml.state()
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(measurement)
-
- res = circuit(p)
- assert res.dtype == c_dtype
-
-
-@pytest.mark.autograd
-class TestPassthruIntegration:
- """Tests for integration with the PassthruQNode"""
-
- def test_jacobian_variable_multiply(self, tol):
- """Test that jacobian of a QNode with an attached default.qubit.autograd device
- gives the correct result in the case of parameters multiplied by scalars"""
- x = 0.43316321
- y = 0.2162158
- z = 0.75110998
- weights = np.array([x, y, z], requires_grad=True)
-
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit(p):
- qml.RX(3 * p[0], wires=0)
- qml.RY(p[1], wires=0)
- qml.RX(p[2] / 2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- assert circuit.gradient_fn == "backprop"
- res = circuit(weights)
-
- expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad_fn = qml.jacobian(circuit, 0)
- res = grad_fn(np.array(weights))
-
- expected = np.array(
- [
- -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)),
- -np.cos(3 * x) * np.sin(y) * np.cos(z / 2),
- -0.5 * (np.sin(3 * x) * np.cos(z / 2) + np.cos(3 * x) * np.cos(y) * np.sin(z / 2)),
- ]
- )
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_jacobian_variable_multiply_broadcasted(self, tol):
- """Test that jacobian of a QNode with an attached default.qubit.autograd device
- gives the correct result in the case of broadcasted parameters multiplied by scalars"""
- x = np.array([0.43316321, 92.1, -0.5129])
- y = np.array([0.2162158, 0.241, -0.51])
- z = np.array([0.75110998, 0.12512, 9.12])
- weights = np.array([x, y, z], requires_grad=True)
-
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit(p):
- qml.RX(3 * p[0], wires=0)
- qml.RY(p[1], wires=0)
- qml.RX(p[2] / 2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- assert circuit.gradient_fn == "backprop"
- res = circuit(weights)
-
- expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad_fn = qml.jacobian(circuit, 0)
- res = grad_fn(np.array(weights))
-
- expected = np.array(
- [
- -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)),
- -np.cos(3 * x) * np.sin(y) * np.cos(z / 2),
- -0.5 * (np.sin(3 * x) * np.cos(z / 2) + np.cos(3 * x) * np.cos(y) * np.sin(z / 2)),
- ]
- )
-
- assert all(np.allclose(res[i, :, i], expected[:, i], atol=tol, rtol=0) for i in range(3))
-
- def test_jacobian_repeated(self, tol):
- """Test that jacobian of a QNode with an attached default.qubit.autograd device
- gives the correct result in the case of repeated parameters"""
- x = 0.43316321
- y = 0.2162158
- z = 0.75110998
- p = np.array([x, y, z], requires_grad=True)
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit(x):
- qml.RX(x[1], wires=0)
- qml.Rot(x[0], x[1], x[2], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- res = circuit(p)
-
- expected = np.cos(y) ** 2 - np.sin(x) * np.sin(y) ** 2
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad_fn = qml.jacobian(circuit, 0)
- res = grad_fn(p)
-
- expected = np.array(
- [-np.cos(x) * np.sin(y) ** 2, -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y), 0]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_jacobian_repeated_broadcasted(self, tol):
- """Test that jacobian of a QNode with an attached default.qubit.autograd device
- gives the correct result in the case of repeated broadcasted parameters"""
- x = np.array([0.43316321, 92.1, -0.5129])
- y = np.array([0.2162158, 0.241, -0.51])
- z = np.array([0.75110998, 0.12512, 9.12])
- p = np.array([x, y, z], requires_grad=True)
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit(x):
- qml.RX(x[1], wires=0)
- qml.Rot(x[0], x[1], x[2], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- res = circuit(p)
-
- expected = np.cos(y) ** 2 - np.sin(x) * np.sin(y) ** 2
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad_fn = qml.jacobian(circuit, 0)
- res = grad_fn(p)
-
- expected = np.array(
- [
- -np.cos(x) * np.sin(y) ** 2,
- -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y),
- np.zeros_like(x),
- ]
- )
- assert all(np.allclose(res[i, :, i], expected[:, i], atol=tol, rtol=0) for i in range(3))
-
- def test_jacobian_agrees_backprop_parameter_shift(self, tol):
- """Test that jacobian of a QNode with an attached default.qubit.autograd device
- gives the correct result with respect to the parameter-shift method"""
- p = np.array([0.43316321, 0.2162158, 0.75110998, 0.94714242], requires_grad=True)
-
- def circuit(x):
- for i in range(0, len(p), 2):
- qml.RX(x[i], wires=0)
- qml.RY(x[i + 1], wires=1)
- for i in range(2):
- qml.CNOT(wires=[i, i + 1])
- return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1))
-
- dev1 = qml.device("default.qubit.legacy", wires=3)
- dev2 = qml.device("default.qubit.legacy", wires=3)
-
- def cost(x):
- return qml.math.stack(circuit(x))
-
- circuit1 = qml.QNode(cost, dev1, diff_method="backprop", interface="autograd")
- circuit2 = qml.QNode(cost, dev2, diff_method="parameter-shift")
-
- res = circuit1(p)
-
- assert np.allclose(res, circuit2(p), atol=tol, rtol=0)
-
- assert circuit1.gradient_fn == "backprop"
- assert circuit2.gradient_fn is qml.gradients.param_shift
-
- grad_fn = qml.jacobian(circuit1, 0)
- res = grad_fn(p)
- assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0)
-
- @pytest.mark.parametrize("wires", [[0], ["abc"]])
- def test_state_differentiability(self, wires, tol):
- """Test that the device state can be differentiated"""
- dev = qml.device("default.qubit.autograd", wires=wires)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a):
- qml.RY(a, wires=wires[0])
- return qml.state()
-
- a = np.array(0.54, requires_grad=True)
-
- def cost(a):
- """A function of the device quantum state, as a function
- of input QNode parameters."""
- res = np.abs(circuit(a)) ** 2
- return res[1] - res[0]
-
- grad = qml.grad(cost)(a)
- expected = np.sin(a)
- assert np.allclose(grad, expected, atol=tol, rtol=0)
-
- def test_state_differentiability_broadcasted(self, tol):
- """Test that the broadcasted device state can be differentiated"""
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a):
- qml.RY(a, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array([0.54, 0.32, 1.2], requires_grad=True)
-
- def cost(a):
- """A function of the device quantum state, as a function
- of input QNode parameters."""
- circuit(a)
- res = np.abs(dev.state) ** 2
- return res[:, 1] - res[:, 0]
-
- grad = qml.jacobian(cost)(a)
- expected = np.diag(np.sin(a))
- assert np.allclose(grad, expected, atol=tol, rtol=0)
-
- def test_prob_differentiability(self, tol):
- """Test that the device probability can be differentiated"""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a, b):
- qml.RX(a, wires=0)
- qml.RY(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=[1])
-
- a = np.array(0.54, requires_grad=True)
- b = np.array(0.12, requires_grad=True)
-
- def cost(a, b):
- prob_wire_1 = circuit(a, b)
- return prob_wire_1[1] - prob_wire_1[0] # pylint:disable=unsubscriptable-object
-
- res = cost(a, b)
- expected = -np.cos(a) * np.cos(b)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad = qml.grad(cost)(a, b)
- expected = [np.sin(a) * np.cos(b), np.cos(a) * np.sin(b)]
- assert np.allclose(grad, expected, atol=tol, rtol=0)
-
- def test_prob_differentiability_broadcasted(self, tol):
- """Test that the broadcasted device probability can be differentiated"""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a, b):
- qml.RX(a, wires=0)
- qml.RY(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=[1])
-
- a = np.array([0.54, 0.32, 1.2], requires_grad=True)
- b = np.array(0.12, requires_grad=True)
-
- def cost(a, b):
- prob_wire_1 = circuit(a, b)
- return prob_wire_1[:, 1] - prob_wire_1[:, 0] # pylint:disable=unsubscriptable-object
-
- res = cost(a, b)
- expected = -np.cos(a) * np.cos(b)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- jac = qml.jacobian(cost)(a, b)
- expected = np.array([np.sin(a) * np.cos(b), np.cos(a) * np.sin(b)])
- expected = (np.diag(expected[0]), expected[1]) # Only first parameter is broadcasted
- assert all(np.allclose(j, e, atol=tol, rtol=0) for j, e in zip(jac, expected))
-
- def test_backprop_gradient(self, tol):
- """Tests that the gradient of the qnode is correct"""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a, b):
- qml.RX(a, wires=0)
- qml.CRX(b, wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
-
- a = np.array(-0.234, requires_grad=True)
- b = np.array(0.654, requires_grad=True)
-
- res = circuit(a, b)
- expected_cost = 0.5 * (np.cos(a) * np.cos(b) + np.cos(a) - np.cos(b) + 1)
- assert np.allclose(res, expected_cost, atol=tol, rtol=0)
-
- res = qml.grad(circuit)(a, b)
- expected_grad = np.array(
- [-0.5 * np.sin(a) * (np.cos(b) + 1), 0.5 * np.sin(b) * (1 - np.cos(a))]
- )
- assert np.allclose(res, expected_grad, atol=tol, rtol=0)
-
- def test_backprop_gradient_broadcasted(self, tol):
- """Tests that the gradient of the broadcasted qnode is correct"""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit(a, b):
- qml.RX(a, wires=0)
- qml.CRX(b, wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
-
- a = np.array(0.12, requires_grad=True)
- b = np.array([0.54, 0.32, 1.2], requires_grad=True)
-
- res = circuit(a, b)
- expected_cost = 0.5 * (np.cos(a) * np.cos(b) + np.cos(a) - np.cos(b) + 1)
- assert np.allclose(res, expected_cost, atol=tol, rtol=0)
-
- res = qml.jacobian(circuit)(a, b)
- expected = np.array([-0.5 * np.sin(a) * (np.cos(b) + 1), 0.5 * np.sin(b) * (1 - np.cos(a))])
- expected = (expected[0], np.diag(expected[1]))
- assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected))
-
- @pytest.mark.parametrize(
- "x, shift",
- [np.array((0.0, 0.0), requires_grad=True), np.array((0.5, -0.5), requires_grad=True)],
- )
- def test_hessian_at_zero(self, x, shift):
- """Tests that the Hessian at vanishing state vector amplitudes
- is correct."""
- dev = qml.device("default.qubit.autograd", wires=1)
-
- @qml.qnode(dev, interface="autograd", diff_method="backprop")
- def circuit(x):
- qml.RY(shift, wires=0)
- qml.RY(x, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- assert qml.math.isclose(qml.jacobian(circuit)(x), 0.0)
- assert qml.math.isclose(qml.jacobian(qml.jacobian(circuit))(x), -1.0)
- assert qml.math.isclose(qml.grad(qml.grad(circuit))(x), -1.0)
-
- @pytest.mark.parametrize("operation", [qml.U3, qml.U3.compute_decomposition])
- @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"])
- def test_autograd_interface_gradient(self, operation, diff_method, tol):
- """Tests that the gradient of an arbitrary U3 gate is correct
- using the Autograd interface, using a variety of differentiation methods."""
- dev = qml.device("default.qubit.autograd", wires=1)
- state = np.array(1j * np.array([1, -1]) / np.sqrt(2), requires_grad=False)
-
- @qml.qnode(dev, diff_method=diff_method, interface="autograd")
- def circuit(x, weights, w):
- """In this example, a mixture of scalar
- arguments, array arguments, and keyword arguments are used."""
- qml.StatePrep(state, wires=w)
- operation(x, weights[0], weights[1], wires=w)
- return qml.expval(qml.PauliX(w))
-
- def cost(params):
- """Perform some classical processing"""
- return circuit(params[0], params[1:], w=0) ** 2
-
- theta = 0.543
- phi = -0.234
- lam = 0.654
-
- params = np.array([theta, phi, lam], requires_grad=True)
-
- res = cost(params)
- expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2
- assert np.allclose(res, expected_cost, atol=tol, rtol=0)
-
- # Check that the correct differentiation method is being used.
- if diff_method == "backprop":
- assert circuit.gradient_fn == "backprop"
- elif diff_method == "parameter-shift":
- assert circuit.gradient_fn is qml.gradients.param_shift
- else:
- assert circuit.gradient_fn is qml.gradients.finite_diff
-
- res = qml.grad(cost)(params)
- expected_grad = (
- np.array(
- [
- np.sin(theta) * np.cos(lam) * np.cos(phi),
- np.cos(theta) * np.cos(lam) * np.sin(phi) + np.sin(lam) * np.cos(phi),
- np.cos(theta) * np.sin(lam) * np.cos(phi) + np.cos(lam) * np.sin(phi),
- ]
- )
- * 2
- * (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi))
- )
- assert np.allclose(res, expected_grad, atol=tol, rtol=0)
-
- @pytest.mark.xfail(reason="Not applicable anymore.")
- @pytest.mark.parametrize("interface", ["tf", "torch"])
- def test_error_backprop_wrong_interface(self, interface):
- """Tests that an error is raised if diff_method='backprop' but not using
- the Autograd interface"""
- dev = qml.device("default.qubit.autograd", wires=1)
-
- def circuit(x, w=None):
- qml.RZ(x, wires=w)
- return qml.expval(qml.PauliX(w))
-
- with pytest.raises(
- qml.QuantumFunctionError,
- match="default.qubit.autograd only supports diff_method='backprop' when using the autograd interface",
- ):
- qml.qnode(dev, diff_method="backprop", interface=interface)(circuit)
-
-
-@pytest.mark.autograd
-class TestHighLevelIntegration:
- """Tests for integration with higher level components of PennyLane."""
-
- def test_do_not_split_analytic_autograd(self, mocker):
- """Tests that the Hamiltonian is not split for shots=None using the autograd device."""
- dev = qml.device("default.qubit.autograd", wires=2)
- H = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)])
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit():
- return qml.expval(H)
-
- spy = mocker.spy(dev.target_device, "expval")
-
- circuit()
- # evaluated one expval altogether
- assert spy.call_count == 1
-
- def test_do_not_split_analytic_autograd_broadcasted(self, mocker):
- """Tests that the Hamiltonian is not split for shots=None
- and broadcasting using the autograd device."""
- dev = qml.device("default.qubit.autograd", wires=2)
- H = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)])
-
- @qml.qnode(dev, diff_method="backprop", interface="autograd")
- def circuit():
- qml.RX(np.zeros(5), 0)
- return qml.expval(H)
-
- spy = mocker.spy(dev.target_device, "expval")
-
- circuit()
- # evaluated one expval altogether
- assert spy.call_count == 1
-
- def test_template_integration(self):
- """Test that a PassthruQNode default.qubit.autograd works with templates."""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(weights):
- qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1])
- return qml.expval(qml.PauliZ(0))
-
- shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=2)
- weights = np.random.random(shape, requires_grad=True)
-
- grad = qml.grad(circuit)(weights)
- assert grad.shape == weights.shape
-
-
-# pylint: disable=protected-access
-@pytest.mark.autograd
-class TestOps:
- """Unit tests for operations supported by the default.qubit.autograd device"""
-
- def test_multirz_jacobian(self):
- """Test that the patched numpy functions are used for the MultiRZ
- operation and the jacobian can be computed."""
- wires = 4
- dev = qml.device("default.qubit.autograd", wires=wires)
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(param):
- qml.MultiRZ(param, wires=[0, 1])
- return qml.probs(wires=list(range(wires)))
-
- param = np.array(0.3, requires_grad=True)
- res = qml.jacobian(circuit)(param)
- assert np.allclose(res, np.zeros(wires**2))
-
- def test_full_subsystem(self, mocker):
- """Test applying a state vector to the full subsystem"""
- dev = DefaultQubitAutograd(wires=["a", "b", "c"])
- state = np.array([1, 0, 0, 0, 1, 0, 1, 1]) / 2.0
- state_wires = qml.wires.Wires(["a", "b", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
-
- assert np.all(dev._state.flatten() == state)
- spy.assert_not_called()
-
- def test_partial_subsystem(self, mocker):
- """Test applying a state vector to a subset of wires of the full subsystem"""
-
- dev = DefaultQubitAutograd(wires=["a", "b", "c"])
- state = np.array([1, 0, 1, 0]) / np.sqrt(2.0)
- state_wires = qml.wires.Wires(["a", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
- res = np.sum(dev._state, axis=(1,)).flatten()
-
- assert np.all(res == state)
- spy.assert_called()
-
-
-@pytest.mark.autograd
-class TestOpsBroadcasted:
- """Unit tests for broadcasted operations supported by the default.qubit.autograd device"""
-
- def test_multirz_jacobian_broadcasted(self):
- """Test that the patched numpy functions are used for the MultiRZ
- operation and the jacobian can be computed."""
- wires = 4
- dev = qml.device("default.qubit.autograd", wires=wires)
-
- @qml.qnode(dev, diff_method="backprop")
- def circuit(param):
- qml.MultiRZ(param, wires=[0, 1])
- return qml.probs(wires=list(range(wires)))
-
- param = np.array([0.3, 0.9, -4.3], requires_grad=True)
- res = qml.jacobian(circuit)(param)
- assert np.allclose(res, np.zeros((3, wires**2, 3)))
-
- def test_full_subsystem_broadcasted(self, mocker):
- """Test applying a state vector to the full subsystem"""
- dev = DefaultQubitAutograd(wires=["a", "b", "c"])
- state = np.array([[1, 0, 0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1, 1, 0]]) / 2.0
- state_wires = qml.wires.Wires(["a", "b", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
-
- assert np.all(dev._state.reshape((2, 8)) == state)
- spy.assert_not_called()
-
- def test_partial_subsystem_broadcasted(self, mocker):
- """Test applying a state vector to a subset of wires of the full subsystem"""
-
- dev = DefaultQubitAutograd(wires=["a", "b", "c"])
- state = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [1, 1, 0, 0]]) / np.sqrt(2.0)
- state_wires = qml.wires.Wires(["a", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
- res = np.sum(dev._state, axis=(2,)).reshape((3, 4))
-
- assert np.allclose(res, state)
- spy.assert_called()
diff --git a/tests/devices/test_default_qubit_legacy.py b/tests/devices/test_default_qubit_legacy.py
index 0f0a7945b73..9b67d18b5c6 100644
--- a/tests/devices/test_default_qubit_legacy.py
+++ b/tests/devices/test_default_qubit_legacy.py
@@ -1008,9 +1008,7 @@ def test_defines_correct_capabilities(self):
"supports_inverse_operations": True,
"supports_analytic_computation": True,
"supports_broadcasting": True,
- "passthru_devices": {
- "autograd": "default.qubit.autograd",
- },
+ "passthru_devices": {},
}
assert cap == capabilities
@@ -2348,7 +2346,7 @@ def test_Hamiltonian_filtered_from_rotations(self, mocker):
dev = qml.device("default.qubit.legacy", wires=2, shots=10)
H = qml.Hamiltonian([0.1, 0.2], [qml.PauliX(0), qml.PauliZ(1)])
- spy = mocker.spy(qml.QubitDevice, "_get_diagonalizing_gates")
+ spy = mocker.spy(qml.devices.QubitDevice, "_get_diagonalizing_gates")
qs = qml.tape.QuantumScript([qml.RX(1, 0)], [qml.expval(qml.PauliX(0)), qml.expval(H)])
rotations = dev._get_diagonalizing_gates(qs)
@@ -2386,24 +2384,11 @@ def circuit(y, z, is_state_batched):
def test_super_expval_not_called(self, is_state_batched, mocker):
"""Tests basic expval result, and ensures QubitDevice.expval is not called."""
dev = qml.device("default.qubit.legacy", wires=1)
- spy = mocker.spy(qml.QubitDevice, "expval")
+ spy = mocker.spy(qml.devices.QubitDevice, "expval")
obs = qml.sum(qml.s_prod(0.1, qml.PauliX(0)), qml.s_prod(0.2, qml.PauliZ(0)))
assert np.isclose(dev.expval(obs), 0.2)
spy.assert_not_called()
- @pytest.mark.autograd
- def test_trainable_autograd(self, is_state_batched):
- """Tests that coeffs passed to a sum are trainable with autograd."""
- if is_state_batched:
- pytest.skip(
- reason="Broadcasting, qml.jacobian and new return types do not work together"
- )
- dev = qml.device("default.qubit.legacy", wires=1)
- qnode = qml.QNode(self.circuit, dev, interface="autograd")
- y, z = np.array([1.1, 2.2])
- actual = qml.grad(qnode, argnum=[0, 1])(y, z, is_state_batched)
- assert np.allclose(actual, self.expected_grad(is_state_batched))
-
class TestGetBatchSize:
"""Tests for the helper method ``_get_batch_size`` of ``QubitDevice``."""
diff --git a/tests/devices/test_default_qubit_legacy_broadcasting.py b/tests/devices/test_default_qubit_legacy_broadcasting.py
deleted file mode 100644
index 50ef31d0294..00000000000
--- a/tests/devices/test_default_qubit_legacy_broadcasting.py
+++ /dev/null
@@ -1,2024 +0,0 @@
-# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Unit tests for the :class:`pennylane.devices.DefaultQubitLegacy` device when using broadcasting.
-"""
-# pylint: disable=protected-access,cell-var-from-loop,too-many-arguments
-import math
-from itertools import product
-
-import pytest
-from gate_data import (
- CNOT,
- CSWAP,
- CZ,
- ISWAP,
- SISWAP,
- SWAP,
- CRot3,
- CRotx,
- CRoty,
- CRotz,
- H,
- I,
- IsingXX,
- IsingYY,
- IsingZZ,
- MultiRZ1,
- MultiRZ2,
- Rot3,
- Rotx,
- Roty,
- Rotz,
- Rphi,
- S,
- T,
- Toffoli,
- X,
- Y,
- Z,
-)
-
-import pennylane as qml
-from pennylane import DeviceError
-from pennylane import numpy as np
-from pennylane.devices.default_qubit_legacy import DefaultQubitLegacy
-
-THETA = np.linspace(0.11, 1, 3)
-PHI = np.linspace(0.32, 1, 3)
-VARPHI = np.linspace(0.02, 1, 3)
-
-INVSQ2 = 1 / math.sqrt(2)
-T_PHASE = np.exp(1j * np.pi / 4)
-T_PHASE_C = np.exp(-1j * np.pi / 4)
-
-# Variant of diag that does not take the diagonal of a 2d array, but broadcasts diag.
-diag = lambda x: np.array([np.diag(_x) for _x in x]) if np.ndim(x) == 2 else np.diag(x)
-
-
-def mat_vec(mat, vec, par=None, inv=False):
- if par is not None:
- scalar = [np.isscalar(p) for p in par]
- if not all(scalar):
- batch_size = len(par[scalar.index(False)])
- par = [tuple(p if s else p[i] for p, s in zip(par, scalar)) for i in range(batch_size)]
- mat = np.array([mat(*_par) for _par in par])
- else:
- mat = mat(*par)
-
- if inv:
- mat = np.moveaxis(mat.conj(), -2, -1)
-
- return np.einsum("...ij,...j->...i", mat, vec)
-
-
-class TestApplyBroadcasted:
- """Tests that operations and inverses of certain operations are applied to a broadcasted
- state/with broadcasted parameters (or both) correctly, or that the proper errors are raised.
- """
-
- triple_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- test_data_no_parameters = [
- (qml.PauliX, triple_state, mat_vec(X, triple_state)),
- (qml.PauliY, triple_state, mat_vec(Y, triple_state)),
- (qml.PauliZ, triple_state, mat_vec(Z, triple_state)),
- (qml.S, triple_state, mat_vec(S, triple_state)),
- (qml.T, triple_state, mat_vec(T, triple_state)),
- (qml.Hadamard, triple_state, mat_vec(H, triple_state)),
- (qml.Identity, triple_state, triple_state),
- ]
-
- @pytest.mark.parametrize("operation,input,expected_output", test_data_no_parameters)
- def test_apply_operation_single_wire_no_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output
- ):
- """Tests that applying an operation yields the expected output state for single wire
- operations that have no parameters."""
-
- qubit_device_1_wire.target_device._state = np.array(
- input, dtype=qubit_device_1_wire.C_DTYPE
- )
- qubit_device_1_wire.apply([operation(wires=[0])])
-
- assert np.allclose(qubit_device_1_wire._state, np.array(expected_output), atol=tol, rtol=0)
- assert qubit_device_1_wire._state.dtype == qubit_device_1_wire.C_DTYPE
-
- single_state = np.array([[0, 0.6, 0, 0.8]])
- triple_state = np.array([[1, 0, 0, 0], [0, 0, INVSQ2, -INVSQ2], [0, 0.6, 0, 0.8]])
-
- test_data_two_wires_no_param = [
- (qml_op, state, mat_vec(mat_op, state))
- for (qml_op, mat_op), state in product(
- zip(
- [qml.CNOT, qml.SWAP, qml.CZ, qml.ISWAP, qml.SISWAP, qml.SQISW],
- [CNOT, SWAP, CZ, ISWAP, SISWAP, SISWAP],
- ),
- [single_state, triple_state],
- )
- ]
-
- @pytest.mark.parametrize("operation,input,expected_output", test_data_two_wires_no_param)
- def test_apply_operation_two_wires_no_parameters_broadcasted(
- self, qubit_device_2_wires, tol, operation, input, expected_output
- ):
- """Tests that applying an operation yields the expected output state for two wire
- operations that have no parameters."""
-
- qubit_device_2_wires.target_device._state = np.array(
- input, dtype=qubit_device_2_wires.C_DTYPE
- ).reshape((-1, 2, 2))
- qubit_device_2_wires.apply([operation(wires=[0, 1])])
-
- assert np.allclose(
- qubit_device_2_wires._state.reshape((-1, 4)),
- np.array(expected_output),
- atol=tol,
- rtol=0,
- )
- assert qubit_device_2_wires._state.dtype == qubit_device_2_wires.C_DTYPE
-
- quad_state = np.array(
- [
- [0.6, 0, 0, 0, 0, 0, 0.8, 0],
- [-INVSQ2, INVSQ2, 0, 0, 0, 0, 0, 0],
- [0, 0, 0.5, 0.5, 0.5, -0.5, 0, 0],
- [0, 0, 0.5, 0, 0.5, -0.5, 0, 0.5],
- ]
- )
- test_data_three_wires_no_parameters = [(qml.CSWAP, quad_state, mat_vec(CSWAP, quad_state))]
-
- @pytest.mark.parametrize("operation,input,expected_output", test_data_three_wires_no_parameters)
- def test_apply_operation_three_wires_no_parameters_broadcasted(
- self, qubit_device_3_wires, tol, operation, input, expected_output
- ):
- """Tests that applying an operation yields the expected output state for three wire
- operations that have no parameters."""
-
- qubit_device_3_wires.target_device._state = np.array(
- input, dtype=qubit_device_3_wires.C_DTYPE
- ).reshape((-1, 2, 2, 2))
- qubit_device_3_wires.apply([operation(wires=[0, 1, 2])])
-
- assert np.allclose(
- qubit_device_3_wires._state.reshape((-1, 8)),
- np.array(expected_output),
- atol=tol,
- rtol=0,
- )
- assert qubit_device_3_wires._state.dtype == qubit_device_3_wires.C_DTYPE
-
- single_state = np.array([[0, 0, 1, 0]])
- triple_state = np.array(
- [
- [0, 0, 1, 0],
- [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)],
- [0.5, -0.5, 0.5j, -0.5j],
- ]
- )
-
- # TODO[dwierichs]: add tests with qml.BaisState once `_apply_basis_state` supports broadcasting
- @pytest.mark.parametrize(
- "operation,expected_output,par",
- [(qml.StatePrep, s, s) for s in [single_state, triple_state]],
- )
- def test_apply_operation_state_preparation_broadcasted(
- self, qubit_device_2_wires, tol, operation, expected_output, par
- ):
- """Tests that applying an operation yields the expected output state for single wire
- operations that have no parameters."""
-
- par = np.array(par)
- qubit_device_2_wires.reset()
- qubit_device_2_wires.apply([operation(par, wires=[0, 1])])
-
- assert np.allclose(
- qubit_device_2_wires._state.reshape((-1, 4)),
- np.array(expected_output),
- atol=tol,
- rtol=0,
- )
-
- # Collect test cases for single-scalar-parameter single-wire operations and their inverses
- # For each operation, we choose broadcasted state, broadcasted params, or both
- state_1 = np.array([0.6, 0.8j])
- state_5 = np.array([[INVSQ2, INVSQ2], [0.6, 0.8], [0, 1j], [-1, 0], [-INVSQ2, INVSQ2]])
- scalar_par_1 = [np.pi / 2]
- scalar_par_5 = [[np.pi / 3, np.pi, 0.5, -1.2, -3 * np.pi / 2]]
- test_data_single_wire_with_parameters = [
- (qml_op, state, mat_vec(mat_op, state, par=par), par)
- for (qml_op, mat_op), (state, par) in product(
- zip(
- [qml.PhaseShift, qml.RX, qml.RY, qml.RZ, qml.MultiRZ],
- [Rphi, Rotx, Roty, Rotz, MultiRZ1],
- ),
- [(state_1, scalar_par_5), (state_5, scalar_par_1), (state_5, scalar_par_5)],
- )
- ]
-
- # Add qml.QubitUnitary test cases
- matrix_1_par_1 = [np.array([[1, 1j], [1j, 1]]) * INVSQ2]
- matrix_1_par_5 = [
- np.array(
- [
- np.array([[1, -1j], [-1j, 1]]) * INVSQ2,
- np.array([[1, -1], [1, 1]]) * INVSQ2,
- np.array([[T_PHASE_C, 0], [0, T_PHASE]]),
- np.array([[1, 0], [0, -1]]),
- T,
- ]
- )
- ]
- test_data_single_wire_with_parameters += [
- (qml.QubitUnitary, s, mat_vec(par[0], s), par)
- for s, par in [
- (state_1, matrix_1_par_5),
- (state_5, matrix_1_par_1),
- (state_5, matrix_1_par_5),
- ]
- ]
-
- # Add qml.DiagonalQubitUnitary test cases
- diag_par_1 = [[np.exp(1j * 0.1), np.exp(1j * np.pi)]]
- diag_par_5 = [
- np.array(
- [
- [1, -1j],
- [np.exp(1j * 0.4), np.exp(1j * -0.4)],
- [np.exp(1j * 0.1), np.exp(1j * np.pi)],
- [1.0, np.exp(1j * np.pi / 2)],
- [1, 1],
- ]
- )
- ]
- test_data_single_wire_with_parameters += [
- (qml.DiagonalQubitUnitary, s, mat_vec(diag(par[0]), s), par)
- for s, par in [(state_1, diag_par_5), (state_5, diag_par_1), (state_5, diag_par_5)]
- ]
-
- # Add qml.SpecialUnitary test cases
- theta_1_par_1 = [np.array([np.pi / 2, 0, 0])]
- theta_1_par_5 = [
- np.array(
- [[np.pi / 2, 0, 0], [0, np.pi / 2, 0], [0, 0, np.pi / 2], [0.3, 0, 0], [0.4, 0.2, 1.2]]
- )
- ]
- test_data_single_wire_with_parameters += [
- (qml.SpecialUnitary, s, mat_vec(qml.SpecialUnitary.compute_matrix(par[0], 1), s), par)
- for s, par in [(state_1, theta_1_par_5), (state_5, theta_1_par_1), (state_5, theta_1_par_5)]
- ]
-
- # Add qml.Rot test cases
- multi_par_1 = {
- "rz_0": [0.632, 0, 0],
- "ry": [0, 0.632, 0],
- "rz_1": [0, 0, 0.632],
- "mixed": [0.12, -2.468, 0.812],
- }
- multi_par_5 = {
- "rz_0": [[np.pi / 2 * i for i in range(5)], 0, 0],
- "ry": [0, [np.pi / 2 * i for i in range(5)], 0],
- "rz_1": [0, 0, [np.pi / 2 * i for i in range(5)]],
- "mixed": [[np.pi / 2 * i for i in range(5)], [np.pi / 2 * i for i in range(5)], np.pi],
- }
- for like in ["rz_0", "ry", "rz_1", "mixed"]:
- states_and_pars = [
- (state_1, multi_par_5[like]),
- (state_5, multi_par_1[like]),
- (state_5, multi_par_5[like]),
- ]
- test_data_single_wire_with_parameters += [
- (qml.Rot, s, mat_vec(Rot3, s, par=par), par) for s, par in states_and_pars
- ]
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par", test_data_single_wire_with_parameters
- )
- def test_apply_operation_single_wire_with_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output, par
- ):
- """Tests that applying an operation yields the expected output state for single wire
- operations that have parameters."""
-
- qubit_device_1_wire.target_device._state = np.array(
- input, dtype=qubit_device_1_wire.C_DTYPE
- )
-
- par = tuple(np.array(p) for p in par)
- qubit_device_1_wire.apply([operation(*par, wires=[0])])
-
- assert np.allclose(qubit_device_1_wire._state, np.array(expected_output), atol=tol, rtol=0)
- assert qubit_device_1_wire._state.dtype == qubit_device_1_wire.C_DTYPE
-
- # Collect test cases for single-scalar-parameter two-wires operations and their inverses
- # For each operation, we choose broadcasted state, broadcasted params, or both
- state_1 = np.array([0.6, 0.8j, -0.6, -0.8j]) * INVSQ2
- state_5 = np.array(
- [
- [0, 1, 0, 0],
- [0, 0, 0, 1],
- [0, INVSQ2, INVSQ2, 0],
- [0.5, 0.5j, -0.5, 0.5],
- [0.6, 0, -0.8j, 0],
- ]
- )
- scalar_par_1 = [np.pi / 2]
- scalar_par_5 = [[np.pi / 3, np.pi, 0.5, -1.2, -3 * np.pi / 2]]
- two_wires_scalar_par_ops = [
- qml.CRX,
- qml.CRY,
- qml.CRZ,
- qml.MultiRZ,
- qml.IsingXX,
- qml.IsingYY,
- qml.IsingZZ,
- ]
- two_wires_scalar_par_mats = [CRotx, CRoty, CRotz, MultiRZ2, IsingXX, IsingYY, IsingZZ]
- test_data_two_wires_with_parameters = [
- (qml_op, state, mat_vec(mat_op, state, par=par), par)
- for (qml_op, mat_op), (state, par) in product(
- zip(two_wires_scalar_par_ops, two_wires_scalar_par_mats),
- [(state_1, scalar_par_5), (state_5, scalar_par_1), (state_5, scalar_par_5)],
- )
- ]
-
- # Add qml.CRot test cases
- multi_par_1 = {
- "rz_0": [0.632, 0, 0],
- "ry": [0, 0.632, 0],
- "rz_1": [0, 0, 0.632],
- "mixed": [0.12, -2.468, 0.812],
- }
- multi_par_5 = {
- "rz_0": [[np.pi / 2 * i for i in range(5)], 0, 0],
- "ry": [0, [np.pi / 2 * i for i in range(5)], 0],
- "rz_1": [0, 0, [np.pi / 2 * i for i in range(5)]],
- "mixed": [[np.pi / 2 * i for i in range(5)], [np.pi / 2 * i for i in range(5)], np.pi],
- }
- for like in ["rz_0", "ry", "rz_1", "mixed"]:
- states_and_pars = [
- (state_1, multi_par_5[like]),
- (state_5, multi_par_1[like]),
- (state_5, multi_par_5[like]),
- ]
- test_data_two_wires_with_parameters += [
- (qml.CRot, s, mat_vec(CRot3, s, par=par), par) for s, par in states_and_pars
- ]
-
- # Add qml.QubitUnitary test cases
- matrix_2_par_1 = [SISWAP]
- matrix_2_par_5 = [
- np.array(
- [
- np.eye(4),
- np.array([[1, -1j, 0, 0], [-1j, 1, 0, 0], [0, 0, 1, -1j], [0, 0, -1j, 1]]) * INVSQ2,
- np.array([[1, -1, 0, 0], [1, 1, 0, 0], [0, 0, 1, -1j], [0, 0, 1j, -1]]) * INVSQ2,
- np.array([[T_PHASE_C, 0, 0, 0], [0, T_PHASE, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1j]]),
- SISWAP,
- ]
- )
- ]
- test_data_two_wires_with_parameters += [
- (qml.QubitUnitary, s, mat_vec(par[0], s), par)
- for s, par in [
- (state_1, matrix_2_par_5),
- (state_5, matrix_2_par_1),
- (state_5, matrix_2_par_5),
- ]
- ]
-
- # Add qml.DiagonalQubitUnitary test cases
- diag_par_1 = [np.exp(1j * np.array([0.1, np.pi, 0.2, -2.4]))]
- diag_par_5 = [
- np.array(
- [
- np.ones(4),
- [1, -1j, 1, 1j],
- [np.exp(1j * 0.4), np.exp(1j * -0.4), 1j, 1],
- [np.exp(1j * 0.1), np.exp(1j * np.pi), INVSQ2 * (1 + 1j), INVSQ2 * (1 - 1j)],
- [1.0, np.exp(1j * np.pi / 2), 1, 1],
- ]
- )
- ]
- test_data_two_wires_with_parameters += [
- (qml.DiagonalQubitUnitary, s, mat_vec(diag(par[0]), s), par)
- for s, par in [(state_1, diag_par_5), (state_5, diag_par_1), (state_5, diag_par_5)]
- ]
-
- # Add qml.SpecialUnitary test cases
- theta_2_par_1 = [np.linspace(0.1, 3, 15)]
- theta_2_par_5 = [np.array([0.4, -0.2, 1.2, -0.5, 2.2])[:, np.newaxis] * np.eye(15)[2::3]]
- test_data_two_wires_with_parameters += [
- (qml.SpecialUnitary, s, mat_vec(qml.SpecialUnitary.compute_matrix(par[0], 2), s), par)
- for s, par in [(state_1, theta_2_par_5), (state_5, theta_2_par_1), (state_5, theta_2_par_5)]
- ]
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par", test_data_two_wires_with_parameters
- )
- def test_apply_operation_two_wires_with_parameters_broadcasted(
- self, qubit_device_2_wires, tol, operation, input, expected_output, par
- ):
- """Tests that applying an operation yields the expected output state for two wire
- operations that have parameters."""
-
- shape = (5, 2, 2) if np.array(input).size == 20 else (2, 2)
- dtype = qubit_device_2_wires.C_DTYPE
- qubit_device_2_wires.target_device._state = np.array(input, dtype=dtype).reshape(shape)
- par = tuple(np.array(p) for p in par)
- qubit_device_2_wires.apply([operation(*par, wires=[0, 1])])
-
- assert np.allclose(
- qubit_device_2_wires._state.reshape((5, 4)), expected_output, atol=tol, rtol=0
- )
- assert qubit_device_2_wires._state.dtype == qubit_device_2_wires.C_DTYPE
-
- def test_apply_errors_qubit_state_vector_broadcasted(self, qubit_device_2_wires):
- """Test that apply fails for incorrect state preparation, and > 2 qubit gates"""
- with pytest.raises(ValueError, match="The state must be a vector of norm 1.0"):
- qubit_device_2_wires.apply([qml.StatePrep(np.array([[1, -1], [0, 2]]), wires=[0])])
-
- # Also test that the sum-check is *not* performed along the broadcasting dimension
- qubit_device_2_wires.apply([qml.StatePrep(np.array([[0.6, 0.8], [0.6, 0.8]]), wires=[0])])
-
- with pytest.raises(ValueError, match=r"State must be of length 4"):
- # Second dimension does not match 2**num_wires
- p = np.array([[1, 0, 1, 1, 0], [0, 1, 1, 0, 1]]) / np.sqrt(3)
- qubit_device_2_wires.apply([qml.StatePrep(p, wires=[0, 1])])
-
- with pytest.raises(ValueError, match=r"State must be of length 4"):
- # Broadcasting dimension is not first dimension
- p = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1], [0, 1, 1]]) / np.sqrt(2)
- qubit_device_2_wires.apply([qml.StatePrep(p, wires=[0, 1])])
-
- qubit_device_2_wires.reset()
- vec = qml.StatePrep(np.array([[0, 1, 0, 0], [0, 0, 1, 0]]), wires=[0, 1])
- with pytest.raises(
- DeviceError,
- match="Operation StatePrep cannot be used after other Operations have already been applied "
- "on a default.qubit.legacy device.",
- ):
- qubit_device_2_wires.apply([qml.RZ(0.5, wires=[0]), vec])
-
- @pytest.mark.skip("Applying a BasisState does not support broadcasting yet")
- def test_apply_errors_basis_state_broadcasted(self, qubit_device_2_wires):
- """Test that applying the BasisState operation raises the correct errors."""
- with pytest.raises(
- ValueError, match="BasisState parameter must consist of 0 or 1 integers."
- ):
- op = qml.BasisState(np.array([[-0.2, 4.2], [0.5, 1.2]]), wires=[0, 1])
- qubit_device_2_wires.apply([op])
-
- with pytest.raises(
- ValueError, match="BasisState parameter and wires must be of equal length."
- ):
- # Test that the error is raised
- qubit_device_2_wires.apply(
- [qml.BasisState(np.array([[0, 1], [1, 1], [1, 0]]), wires=[0])]
- )
- # Test that the broadcasting dimension is allowed to mismatch the length of the wires
- qubit_device_2_wires.apply([qml.BasisState(np.array([[0], [1], [0]]), wires=[0])])
-
- qubit_device_2_wires.reset()
- qubit_device_2_wires.apply([qml.RZ(0.5, wires=[0])])
- vec = qml.BasisState(np.array([[0, 0], [1, 0], [1, 1]]), wires=[0, 1])
- with pytest.raises(
- DeviceError,
- match="Operation BasisState cannot be used after other Operations have already been applied "
- "on a default.qubit.legacy device.",
- ):
- qubit_device_2_wires.apply([vec])
-
-
-zero = [1, 0]
-one = [0, 1]
-plus = [INVSQ2, INVSQ2]
-minus = [INVSQ2, -INVSQ2]
-y_plus = [INVSQ2, 1j * INVSQ2]
-y_minus = [INVSQ2, -1j * INVSQ2]
-
-
-class TestExpvalBroadcasted:
- """Tests that expectation values are properly calculated for broadcasted states
- or that the proper errors are raised."""
-
- @pytest.mark.parametrize(
- "operation,input,expected_output",
- [
- (qml.PauliX, np.array([plus, zero, minus]), [1, 0, -1]),
- (qml.PauliY, np.array([y_plus, zero, y_minus]), [1, 0, -1]),
- (qml.PauliZ, np.array([plus, zero, one]), [0, 1, -1]),
- (qml.Hadamard, np.array([plus, zero, one]), [INVSQ2, INVSQ2, -INVSQ2]),
- (qml.Identity, np.array([minus, zero, one]), [1, 1, 1]),
- ],
- )
- def test_expval_single_wire_no_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output
- ):
- """Tests that expectation values are properly calculated for single-wire observables without parameters."""
-
- obs = operation(wires=[0])
-
- qubit_device_1_wire.reset()
- qubit_device_1_wire.apply(
- [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates()
- )
- res = qubit_device_1_wire.expval(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par",
- [(qml.Hermitian, [zero, one, minus, y_plus], [1, 1, 1, 0], I - Y)],
- )
- def test_expval_single_wire_with_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output, par
- ):
- """Tests that expectation values are properly calculated for single-wire observables with parameters."""
-
- obs = operation(np.array(par), wires=[0])
-
- qubit_device_1_wire.reset()
- qubit_device_1_wire.apply(
- [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates()
- )
- res = qubit_device_1_wire.expval(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par",
- [
- (
- qml.Hermitian,
- [
- [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)],
- [0, 0, 0, 1],
- [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0],
- ],
- [4 / 3, 0, 1],
- [[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 0]],
- ),
- (
- qml.Hermitian,
- [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]],
- [1, -1],
- [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]],
- ),
- ],
- )
- def test_expval_two_wires_with_parameters_broadcasted(
- self, qubit_device_2_wires, tol, operation, input, expected_output, par
- ):
- """Tests that expectation values are properly calculated for two-wire observables with parameters."""
-
- obs = operation(np.array(par), wires=[0, 1])
-
- qubit_device_2_wires.reset()
- qubit_device_2_wires.apply(
- [qml.StatePrep(np.array(input), wires=[0, 1])], obs.diagonalizing_gates()
- )
- res = qubit_device_2_wires.expval(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- def test_expval_estimate_broadcasted(self):
- """Test that the expectation value is not analytically calculated"""
-
- dev = qml.device("default.qubit.legacy", wires=1, shots=3)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.RX(np.zeros(5), wires=0) # Broadcast the tape without applying an op
- return qml.expval(qml.PauliX(0))
-
- expval = circuit()
-
- # With 3 samples we are guaranteed to see a difference between
- # an estimated variance an an analytically calculated one
- assert np.all(expval != 0.0)
-
-
-class TestVarBroadcasted:
- """Tests that variances are properly calculated for broadcasted states."""
-
- @pytest.mark.parametrize(
- "operation,input,expected_output",
- [
- (qml.PauliX, [plus, zero, minus], [0, 1, 0]),
- (qml.PauliY, [y_plus, zero, y_minus], [0, 1, 0]),
- (qml.PauliZ, [plus, zero, one], [1, 0, 0]),
- (qml.Hadamard, [plus, zero, one], [0.5, 0.5, 0.5]),
- (qml.Identity, [minus, zero, one], [0, 0, 0]),
- ],
- )
- def test_var_single_wire_no_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output
- ):
- """Tests that variances are properly calculated for single-wire observables without parameters."""
-
- obs = operation(wires=[0])
-
- qubit_device_1_wire.reset()
- qubit_device_1_wire.apply(
- [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates()
- )
- res = qubit_device_1_wire.var(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par",
- [(qml.Hermitian, [zero, one, minus, y_plus], [1, 1, 1, 0], I - Y)],
- )
- def test_var_single_wire_with_parameters_broadcasted(
- self, qubit_device_1_wire, tol, operation, input, expected_output, par
- ):
- """Tests that variances are properly calculated for single-wire observables with parameters."""
-
- obs = operation(np.array(par), wires=[0])
-
- qubit_device_1_wire.reset()
- qubit_device_1_wire.apply(
- [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates()
- )
- res = qubit_device_1_wire.var(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "operation,input,expected_output,par",
- [
- (
- qml.Hermitian,
- [
- [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)],
- [0, 0, 0, 1],
- [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0],
- ],
- [11 / 9, 2, 3 / 2],
- [[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 1]],
- ),
- (
- qml.Hermitian,
- [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]],
- [0, 0],
- [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]],
- ),
- ],
- )
- def test_var_two_wires_with_parameters_broadcasted(
- self, qubit_device_2_wires, tol, operation, input, expected_output, par
- ):
- """Tests that variances are properly calculated for two-wire observables with parameters."""
-
- obs = operation(np.array(par), wires=[0, 1])
-
- qubit_device_2_wires.reset()
- qubit_device_2_wires.apply(
- [qml.StatePrep(np.array(input), wires=[0, 1])], obs.diagonalizing_gates()
- )
- res = qubit_device_2_wires.var(obs)
-
- assert np.allclose(res, expected_output, atol=tol, rtol=0)
-
- def test_var_estimate_broadcasted(self):
- """Test that the variance is not analytically calculated"""
-
- dev = qml.device("default.qubit.legacy", wires=1, shots=3)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.RX(np.zeros(5), wires=0) # Broadcast the tape without applying an op
- return qml.var(qml.PauliX(0))
-
- var = circuit()
-
- # With 3 samples we are guaranteed to see a difference between
- # an estimated variance and an analytically calculated one
- assert np.all(var != 1.0)
-
-
-class TestSampleBroadcasted:
- """Tests that samples are properly calculated for broadcasted states."""
-
- def test_sample_dimensions_broadcasted(self):
- """Tests if the samples returned by the sample function have
- the correct dimensions
- """
-
- # Explicitly resetting is necessary as the internal
- # state is set to None in __init__ and only properly
- # initialized during reset
- dev = qml.device("default.qubit.legacy", wires=2, shots=1000)
-
- dev.apply([qml.RX(np.array([np.pi / 2, 0.0]), 0), qml.RX(np.array([np.pi / 2, 0.0]), 1)])
-
- dev.target_device.shots = 10
- dev.target_device._wires_measured = {0}
- dev.target_device._samples = dev.generate_samples()
- s1 = dev.sample(qml.PauliZ(0))
- assert s1.shape == (
- 2,
- 10,
- )
-
- dev.reset()
- dev.target_device.shots = 12
- dev.target_device._wires_measured = {1}
- dev.target_device._samples = dev.generate_samples()
- s2 = dev.sample(qml.PauliZ(wires=[1]))
- assert s2.shape == (12,)
-
- dev.reset()
- dev.apply([qml.RX(np.ones(5), 0), qml.RX(np.ones(5), 1)])
- dev.target_device.shots = 17
- dev.target_device._wires_measured = {0, 1}
- dev.target_device._samples = dev.generate_samples()
- s3 = dev.sample(qml.PauliX(0) @ qml.PauliZ(1))
- assert s3.shape == (5, 17)
-
- def test_sample_values_broadcasted(self, tol):
- """Tests if the samples returned by sample have
- the correct values
- """
-
- # Explicitly resetting is necessary as the internal
- # state is set to None in __init__ and only properly
- # initialized during reset
- dev = qml.device("default.qubit.legacy", wires=2, shots=1000)
-
- dev.apply([qml.RX(np.ones(3), wires=[0])])
- dev.target_device._wires_measured = {0}
- dev.target_device._samples = dev.generate_samples()
-
- s1 = dev.sample(qml.PauliZ(0))
-
- # s1 should only contain 1 and -1, which is guaranteed if
- # they square to 1
- assert np.allclose(s1**2, 1, atol=tol, rtol=0)
-
-
-class TestDefaultQubitIntegrationBroadcasted:
- """Integration tests for default.qubit.legacy. This test ensures it integrates
- properly with the PennyLane interface, in particular QNode."""
-
- @pytest.mark.parametrize("r_dtype", [np.float32, np.float64])
- def test_qubit_circuit_broadcasted(self, qubit_device_1_wire, r_dtype, tol):
- """Test that the default qubit plugin provides correct result for a simple circuit"""
-
- p = np.array([0.543, np.pi / 2, 0.0, 1.0])
-
- dev = qubit_device_1_wire
- dev.target_device.R_DTYPE = r_dtype
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliY(0))
-
- expected = -np.sin(p)
-
- res = circuit(p)
- assert np.allclose(res, expected, atol=tol, rtol=0)
- assert res.dtype == r_dtype # pylint:disable=no-member
-
- def test_qubit_identity_broadcasted(self, qubit_device_1_wire, tol):
- """Test that the default qubit plugin provides correct result for the Identity expectation"""
-
- p = np.array([0.543, np.pi / 2, 0.0, 1.0])
-
- @qml.qnode(qubit_device_1_wire)
- def circuit(x):
- """Test quantum function"""
- qml.RX(x, wires=0)
- return qml.expval(qml.Identity(0))
-
- assert np.allclose(circuit(p), 1, atol=tol, rtol=0)
-
- def test_nonzero_shots_broadcasted(self, tol):
- """Test that the default qubit plugin provides correct result for high shot number"""
-
- shots = 10**5
- dev = qml.device("default.qubit.legacy", wires=1, shots=shots)
-
- p = np.array([0.543, np.pi / 2, 0.0, 1.0])
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x):
- """Test quantum function"""
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliY(0))
-
- runs = []
- for _ in range(100):
- runs.append(circuit(p))
-
- assert np.allclose(np.mean(runs, axis=0), -np.sin(p), atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "name,state,expected_output",
- [
- ("PauliX", [plus, minus, zero], [1, -1, 0]),
- ("PauliY", [y_plus, y_minus, zero], [1, -1, 0]),
- ("PauliZ", [plus, one, zero], [0, -1, 1]),
- ("Hadamard", [plus, one, zero], [INVSQ2, -INVSQ2, INVSQ2]),
- ],
- )
- def test_supported_observable_single_wire_no_parameters_broadcasted(
- self, qubit_device_1_wire, tol, name, state, expected_output
- ):
- """Tests supported observables on single wires without parameters."""
-
- obs = getattr(qml.ops, name)
-
- assert qubit_device_1_wire.supports_observable(name)
-
- @qml.qnode(qubit_device_1_wire)
- def circuit():
- qml.StatePrep(np.array(state), wires=[0])
- return qml.expval(obs(wires=[0]))
-
- assert np.allclose(circuit(), expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "name,state,expected_output,par",
- [
- ("Identity", [zero, one, plus], [1, 1, 1], []),
- ("Hermitian", [zero, one, minus], [1, 1, 1], [I - Y]),
- ],
- )
- def test_supported_observable_single_wire_with_parameters_broadcasted(
- self, qubit_device_1_wire, tol, name, state, expected_output, par
- ):
- """Tests supported observables on single wires with parameters."""
-
- obs = getattr(qml.ops, name)
-
- assert qubit_device_1_wire.supports_observable(name)
-
- @qml.qnode(qubit_device_1_wire)
- def circuit():
- qml.StatePrep(np.array(state), wires=[0])
- return qml.expval(obs(*par, wires=[0]))
-
- assert np.allclose(circuit(), expected_output, atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "name,state,expected_output,par",
- [
- (
- "Hermitian",
- [
- [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)],
- [0, 0, 0, 1],
- [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0],
- ],
- [4 / 3, 0, 1],
- ([[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 0]],),
- ),
- (
- "Hermitian",
- [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]],
- [1, -1],
- ([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]],),
- ),
- ],
- )
- def test_supported_observable_two_wires_with_parameters_broadcasted(
- self, qubit_device_2_wires, tol, name, state, expected_output, par
- ):
- """Tests supported observables on two wires with parameters."""
-
- obs = getattr(qml.ops, name)
-
- assert qubit_device_2_wires.supports_observable(name)
-
- @qml.qnode(qubit_device_2_wires)
- def circuit():
- qml.StatePrep(np.array(state), wires=[0, 1])
- return qml.expval(obs(*par, wires=[0, 1]))
-
- assert np.allclose(circuit(), expected_output, atol=tol, rtol=0)
-
- def test_multi_samples_return_correlated_results_broadcasted(self):
- """Tests if the samples returned by the sample function are correlated
- correctly for correlated observables.
- """
-
- dev = qml.device("default.qubit.legacy", wires=2, shots=1000)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.Hadamard(0)
- qml.RX(np.zeros(5), 0)
- qml.CNOT(wires=[0, 1])
- return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1))
-
- outcomes = circuit()
-
- assert np.array_equal(outcomes[0], outcomes[1])
-
- @pytest.mark.parametrize("num_wires", [3, 4, 5, 6, 7, 8])
- def test_multi_samples_correlated_results_more_wires_than_observable_broadcasted(
- self, num_wires
- ):
- """Tests if the samples returned by the sample function are correlated
- correctly for correlated observables on larger devices than the observables
- """
-
- dev = qml.device("default.qubit.legacy", wires=num_wires, shots=1000)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.Hadamard(0)
- qml.RX(np.zeros(5), 0)
- qml.CNOT(wires=[0, 1])
- return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1))
-
- outcomes = circuit()
-
- assert np.array_equal(outcomes[0], outcomes[1])
-
-
-# pylint: disable=unused-argument
-@pytest.mark.parametrize(
- "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])]
-)
-class TestTensorExpvalBroadcasted:
- """Test tensor expectation values for broadcasted states"""
-
- def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving PauliX and PauliY works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
- dev.reset()
-
- obs = qml.PauliX(0) @ qml.PauliY(2)
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- expected = np.sin(theta) * np.sin(phi) * np.sin(varphi)
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_pauliz_identity_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving PauliZ and Identity works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
- dev.reset()
-
- obs = qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2)
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- expected = np.cos(varphi) * np.cos(phi)
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
- obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)
-
- dev.reset()
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2)
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_hermitian_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving qml.Hermitian works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
- dev.reset()
-
- A = np.array(
- [
- [-6, 2 + 1j, -3, -5 + 2j],
- [2 - 1j, 0, 2 - 1j, -5 + 4j],
- [-3, 2 + 1j, 0, -4 + 3j],
- [-5 - 2j, -5 - 4j, -4 - 3j, -6],
- ]
- )
-
- obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2])
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- expected = 0.5 * (
- -6 * np.cos(theta) * (np.cos(varphi) + 1)
- - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi))
- + 3 * np.cos(varphi) * np.sin(phi)
- + np.sin(phi)
- )
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_hermitian_hermitian_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving two Hermitian matrices works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
-
- A1 = np.array([[1, 2], [2, 4]])
-
- A2 = np.array(
- [
- [-6, 2 + 1j, -3, -5 + 2j],
- [2 - 1j, 0, 2 - 1j, -5 + 4j],
- [-3, 2 + 1j, 0, -4 + 3j],
- [-5 - 2j, -5 - 4j, -4 - 3j, -6],
- ]
- )
-
- obs = qml.Hermitian(A1, wires=[0]) @ qml.Hermitian(A2, wires=[1, 2])
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- expected = 0.25 * (
- -30
- + 4 * np.cos(phi) * np.sin(theta)
- + 3 * np.cos(varphi) * (-10 + 4 * np.cos(phi) * np.sin(theta) - 3 * np.sin(phi))
- - 3 * np.sin(phi)
- - 2
- * (5 + np.cos(phi) * (6 + 4 * np.sin(theta)) + (-3 + 8 * np.sin(theta)) * np.sin(phi))
- * np.sin(varphi)
- + np.cos(theta)
- * (
- 18
- + 5 * np.sin(phi)
- + 3 * np.cos(varphi) * (6 + 5 * np.sin(phi))
- + 2 * (3 + 10 * np.cos(phi) - 5 * np.sin(phi)) * np.sin(varphi)
- )
- )
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_hermitian_identity_expectation_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving an Hermitian matrix and the identity works correctly"""
- dev = qml.device("default.qubit.legacy", wires=2)
-
- A = np.array(
- [[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]]
- )
-
- obs = qml.Hermitian(A, wires=[0]) @ qml.Identity(wires=[1])
-
- dev.apply(
- [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])],
- obs.diagonalizing_gates(),
- )
-
- res = dev.expval(obs)
-
- a = A[0, 0]
- re_b = A[0, 1].real
- d = A[1, 1]
- expected = ((a - d) * np.cos(theta) + 2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_hermitian_two_wires_identity_expectation_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving an Hermitian matrix for two wires and the identity works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
-
- A = np.array(
- [[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]]
- )
- Identity = np.array([[1, 0], [0, 1]])
- Ham = np.kron(np.kron(Identity, Identity), A)
- obs = qml.Hermitian(Ham, wires=[2, 1, 0])
-
- dev.apply(
- [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])],
- obs.diagonalizing_gates(),
- )
- res = dev.expval(obs)
-
- a = A[0, 0]
- re_b = A[0, 1].real
- d = A[1, 1]
-
- expected = ((a - d) * np.cos(theta) + 2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-@pytest.mark.parametrize(
- "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])]
-)
-class TestTensorVarBroadcasted:
- """Tests for variance of tensor observables for broadcasted states"""
-
- def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving PauliX and PauliY works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
-
- obs = qml.PauliX(0) @ qml.PauliY(2)
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.var(obs)
-
- expected = (
- 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2
- - np.cos(2 * (theta - phi))
- - np.cos(2 * (theta + phi))
- + 2 * np.cos(2 * theta)
- + 2 * np.cos(2 * phi)
- + 14
- ) / 16
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
- obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)
-
- dev.reset()
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.var(obs)
-
- expected = (
- 3
- + np.cos(2 * phi) * np.cos(varphi) ** 2
- - np.cos(2 * theta) * np.sin(varphi) ** 2
- - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi)
- ) / 4
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_hermitian_broadcasted(self, theta, phi, varphi, tol):
- """Test that a tensor product involving qml.Hermitian works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3)
-
- A = np.array(
- [
- [-6, 2 + 1j, -3, -5 + 2j],
- [2 - 1j, 0, 2 - 1j, -5 + 4j],
- [-3, 2 + 1j, 0, -4 + 3j],
- [-5 - 2j, -5 - 4j, -4 - 3j, -6],
- ]
- )
-
- obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2])
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- res = dev.var(obs)
-
- expected = (
- 1057
- - np.cos(2 * phi)
- + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi)
- - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi))
- + 16 * np.sin(2 * phi)
- - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi)
- - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2
- - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi)
- - 8
- * np.cos(theta)
- * (
- 4
- * np.cos(phi)
- * (
- 4
- + 8 * np.cos(varphi)
- + np.cos(2 * varphi)
- - (1 + 6 * np.cos(varphi)) * np.sin(varphi)
- )
- + np.sin(phi)
- * (
- 15
- + 8 * np.cos(varphi)
- - 11 * np.cos(2 * varphi)
- + 42 * np.sin(varphi)
- + 3 * np.sin(2 * varphi)
- )
- )
- ) / 16
-
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-@pytest.mark.parametrize(
- "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])]
-)
-class TestTensorSampleBroadcasted:
- """Test tensor sampling for broadcated states"""
-
- def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol_stochastic):
- """Test that a tensor product involving PauliX and PauliY works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6))
-
- obs = qml.PauliX(0) @ qml.PauliY(2)
-
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- dev.target_device._wires_measured = {0, 1, 2}
- dev.target_device._samples = dev.generate_samples()
- dev.sample(obs)
-
- s1 = obs.eigvals()
- p = dev.probability(wires=dev.map_wires(obs.wires))
-
- # s1 should only contain 1 and -1
- assert np.allclose(s1**2, 1, atol=tol_stochastic, rtol=0)
-
- mean = p @ s1
- expected = np.sin(theta) * np.sin(phi) * np.sin(varphi)
- assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0)
-
- var = p @ (s1**2) - (p @ s1).real ** 2
- expected = (
- 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2
- - np.cos(2 * (theta - phi))
- - np.cos(2 * (theta + phi))
- + 2 * np.cos(2 * theta)
- + 2 * np.cos(2 * phi)
- + 14
- ) / 16
- assert np.allclose(var, expected, atol=tol_stochastic, rtol=0)
-
- def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol_stochastic):
- """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6))
- obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- dev.target_device._wires_measured = {0, 1, 2}
- dev.target_device._samples = dev.generate_samples()
- dev.sample(obs)
-
- s1 = obs.eigvals()
- p = dev.marginal_prob(dev.probability(), wires=obs.wires)
-
- # s1 should only contain 1 and -1
- assert np.allclose(s1**2, 1, atol=tol_stochastic, rtol=0)
-
- mean = p @ s1
- expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2)
- assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0)
-
- var = p @ (s1**2) - (p @ s1).real ** 2
- expected = (
- 3
- + np.cos(2 * phi) * np.cos(varphi) ** 2
- - np.cos(2 * theta) * np.sin(varphi) ** 2
- - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi)
- ) / 4
- assert np.allclose(var, expected, atol=tol_stochastic, rtol=0)
-
- def test_hermitian_broadcasted(self, theta, phi, varphi, tol_stochastic):
- """Test that a tensor product involving qml.Hermitian works correctly"""
- dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6))
-
- A = 0.1 * np.array(
- [
- [-6, 2 + 1j, -3, -5 + 2j],
- [2 - 1j, 0, 2 - 1j, -5 + 4j],
- [-3, 2 + 1j, 0, -4 + 3j],
- [-5 - 2j, -5 - 4j, -4 - 3j, -6],
- ]
- )
-
- obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2])
- dev.apply(
- [
- qml.RX(theta, wires=[0]),
- qml.RX(phi, wires=[1]),
- qml.RX(varphi, wires=[2]),
- qml.CNOT(wires=[0, 1]),
- qml.CNOT(wires=[1, 2]),
- ],
- obs.diagonalizing_gates(),
- )
-
- dev.target_device._wires_measured = {0, 1, 2}
- dev.target_device._samples = dev.generate_samples()
- dev.sample(obs)
-
- s1 = obs.eigvals()
- p = dev.marginal_prob(dev.probability(), wires=obs.wires)
-
- # s1 should only contain the eigenvalues of
- # the hermitian matrix tensor product Z
- z = np.diag([1, -1])
- eigvals = np.linalg.eigvalsh(np.kron(z, A))
- assert set(np.round(s1, 8).tolist()).issubset(set(np.round(eigvals, 8).tolist()))
-
- mean = p @ s1
- expected = (
- 0.1
- * 0.5
- * (
- -6 * np.cos(theta) * (np.cos(varphi) + 1)
- - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi))
- + 3 * np.cos(varphi) * np.sin(phi)
- + np.sin(phi)
- )
- )
- assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0)
-
- var = p @ (s1**2) - (p @ s1).real ** 2
- expected = (
- 0.01
- * (
- 1057
- - np.cos(2 * phi)
- + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi)
- - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi))
- + 16 * np.sin(2 * phi)
- - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi)
- - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2
- - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi)
- - 8
- * np.cos(theta)
- * (
- 4
- * np.cos(phi)
- * (
- 4
- + 8 * np.cos(varphi)
- + np.cos(2 * varphi)
- - (1 + 6 * np.cos(varphi)) * np.sin(varphi)
- )
- + np.sin(phi)
- * (
- 15
- + 8 * np.cos(varphi)
- - 11 * np.cos(2 * varphi)
- + 42 * np.sin(varphi)
- + 3 * np.sin(2 * varphi)
- )
- )
- )
- / 16
- )
- assert np.allclose(var, expected, atol=tol_stochastic, rtol=0)
-
-
-@pytest.mark.parametrize(
- "r_dtype,c_dtype", [(np.float32, np.complex64), (np.float64, np.complex128)]
-)
-class TestDtypePreservedBroadcasted:
- """Test that the user-defined dtype of the device is preserved for QNode
- evaluation"""
-
- @pytest.mark.parametrize(
- "op",
- [
- qml.SingleExcitation,
- qml.SingleExcitationPlus,
- qml.SingleExcitationMinus,
- qml.DoubleExcitation,
- qml.DoubleExcitationPlus,
- qml.DoubleExcitationMinus,
- qml.OrbitalRotation,
- qml.FermionicSWAP,
- qml.QubitSum,
- qml.QubitCarry,
- ],
- )
- def test_state_dtype_after_op_broadcasted(self, r_dtype, c_dtype, op):
- """Test that the default qubit plugin preserves data types of states when an operation is
- applied. As TestApply class check most of operators, we here only check some subtle
- examples.
- """
-
- dev = qml.device("default.qubit.legacy", wires=4, r_dtype=r_dtype, c_dtype=c_dtype)
-
- n_wires = op.num_wires
- n_params = op.num_params
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- x = np.array([0.543, 0.622, 1.3])
- if n_params == 0:
- op(wires=range(n_wires))
- elif n_params == 1:
- op(x, wires=range(n_wires))
- else:
- op([x] * n_params, wires=range(n_wires))
- return qml.state()
-
- res = circuit()
- assert res.dtype == c_dtype # pylint:disable=no-member
-
- @pytest.mark.parametrize(
- "measurement",
- [
- qml.expval(qml.PauliY(0)),
- qml.var(qml.PauliY(0)),
- qml.probs(wires=[1]),
- qml.probs(wires=[2, 0]),
- ],
- )
- def test_measurement_real_dtype_broadcasted(self, r_dtype, c_dtype, measurement):
- """Test that the default qubit plugin provides correct result for a simple circuit"""
- p = np.array([0.543, 0.622, 1.3])
-
- dev = qml.device("default.qubit.legacy", wires=3, r_dtype=r_dtype, c_dtype=c_dtype)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(measurement)
-
- res = circuit(p)
- assert res.dtype == r_dtype
-
- def test_measurement_complex_dtype_broadcasted(self, r_dtype, c_dtype):
- """Test that the default qubit plugin provides correct result for a simple circuit"""
- p = np.array([0.543, 0.622, 1.3])
- m = qml.state()
-
- dev = qml.device("default.qubit.legacy", wires=3, r_dtype=r_dtype, c_dtype=c_dtype)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.apply(m)
-
- res = circuit(p)
- assert res.dtype == c_dtype
-
-
-class TestProbabilityIntegrationBroadcasted:
- """Test probability method for when analytic is True/False"""
-
- # pylint: disable=no-member, unused-argument
- def mock_analytic_counter(self, wires=None):
- self.analytic_counter += 1
- return np.array([1, 0, 0, 0], dtype=float)
-
- def test_probability_broadcasted(self, tol):
- """Test that the probability function works for finite and infinite shots"""
- dev = qml.device("default.qubit.legacy", wires=2, shots=1000)
- dev_analytic = qml.device("default.qubit.legacy", wires=2, shots=None)
-
- x = np.array([[0.2, 0.5, 0.4], [0.9, 0.8, 0.3]])
-
- def circuit(x):
- qml.RX(x[0], wires=0)
- qml.RY(x[1], wires=0)
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=[0, 1])
-
- prob = qml.QNode(circuit, dev)
- prob_analytic = qml.QNode(circuit, dev_analytic)
-
- assert np.allclose(prob(x).sum(axis=-1), 1, atol=tol, rtol=0)
- assert np.allclose(prob_analytic(x), prob(x), atol=0.1, rtol=0)
- assert not np.array_equal(prob_analytic(x), prob(x))
-
-
-class TestWiresIntegrationBroadcasted:
- """Test that the device integrates with PennyLane's wire management."""
-
- def make_circuit_probs(self, wires):
- """Factory for a qnode returning probabilities using arbitrary wire labels."""
- dev = qml.device("default.qubit.legacy", wires=wires)
- n_wires = len(wires)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.RX(np.array([0.5, 1.2, -0.6]), wires=wires[0 % n_wires])
- qml.RY(np.array([2.0, 0.4, 1.2]), wires=wires[1 % n_wires])
- if n_wires > 1:
- qml.CNOT(wires=[wires[0], wires[1]])
- return qml.probs(wires=wires)
-
- return circuit
-
- @pytest.mark.parametrize(
- "wires1, wires2",
- [
- (["a", "c", "d"], [2, 3, 0]),
- ([-1, -2, -3], ["q1", "ancilla", 2]),
- (["a", "c"], [3, 0]),
- ([-1, -2], ["ancilla", 2]),
- (["a"], ["nothing"]),
- ],
- )
- def test_wires_probs_broadcasted(self, wires1, wires2, tol):
- """Test that the probability vector of a circuit is independent from the wire labels used."""
-
- circuit1 = self.make_circuit_probs(wires1)
- circuit2 = self.make_circuit_probs(wires2)
-
- assert np.allclose(circuit1(), circuit2(), tol)
-
-
-class TestApplyOpsBroadcasted:
- """Tests for special methods listed in _apply_ops that use array manipulation tricks to apply
- gates in DefaultQubitLegacy."""
-
- broadcasted_state = np.arange(2**4 * 3, dtype=np.complex128).reshape((3, 2, 2, 2, 2))
- with pytest.warns(qml.PennyLaneDeprecationWarning):
- dev = qml.device("default.qubit.legacy", wires=4)
-
- single_qubit_ops = [
- (qml.PauliX, dev._apply_x),
- (qml.PauliY, dev._apply_y),
- (qml.PauliZ, dev._apply_z),
- (qml.Hadamard, dev._apply_hadamard),
- (qml.S, dev._apply_s),
- (qml.T, dev._apply_t),
- (qml.SX, dev._apply_sx),
- ]
- two_qubit_ops = [
- (qml.CNOT, dev._apply_cnot),
- (qml.SWAP, dev._apply_swap),
- (qml.CZ, dev._apply_cz),
- ]
- three_qubit_ops = [
- (qml.Toffoli, dev._apply_toffoli),
- ]
-
- @pytest.mark.parametrize("op, method", single_qubit_ops)
- def test_apply_single_qubit_op_broadcasted_state(self, op, method):
- """Test if the application of single qubit operations to a
- broadcasted state is correct."""
- state_out = method(self.broadcasted_state, axes=[2])
- op = op(wires=[1])
- matrix = op.matrix()
- state_out_einsum = np.einsum("ab,mibjk->miajk", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
- @pytest.mark.parametrize("op, method", two_qubit_ops)
- def test_apply_two_qubit_op_broadcasted_state(self, op, method):
- """Test if the application of two qubit operations to a
- broadcasted state is correct."""
- state_out = method(self.broadcasted_state, axes=[1, 2])
- op = op(wires=[0, 1])
- matrix = op.matrix()
- matrix = matrix.reshape((2, 2, 2, 2))
- state_out_einsum = np.einsum("abcd,mcdjk->mabjk", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
- @pytest.mark.parametrize("op, method", two_qubit_ops)
- def test_apply_two_qubit_op_reverse_broadcasted_state(self, op, method):
- """Test if the application of two qubit operations to a
- broadcasted state is correct when the applied wires are reversed."""
- state_out = method(self.broadcasted_state, axes=[3, 2])
- op = op(wires=[2, 1])
- matrix = op.matrix()
- matrix = matrix.reshape((2, 2, 2, 2))
- state_out_einsum = np.einsum("abcd,midck->mibak", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
- @pytest.mark.parametrize("op, method", three_qubit_ops)
- def test_apply_three_qubit_op_controls_smaller_broadcasted_state(self, op, method):
- """Test if the application of three qubit operations to a broadcasted
- state is correct when both control wires are smaller than the target wire."""
- state_out = method(self.broadcasted_state, axes=[1, 3, 4])
- op = op(wires=[0, 2, 3])
- matrix = op.matrix()
- matrix = matrix.reshape((2, 2) * 3)
- state_out_einsum = np.einsum("abcdef,mdkef->makbc", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
- @pytest.mark.parametrize("op, method", three_qubit_ops)
- def test_apply_three_qubit_op_controls_greater_broadcasted_state(self, op, method):
- """Test if the application of three qubit operations to a broadcasted
- state is correct when both control wires are greater than the target wire."""
- state_out = method(self.broadcasted_state, axes=[3, 2, 1])
- op = op(wires=[2, 1, 0])
- matrix = op.matrix()
- matrix = matrix.reshape((2, 2) * 3)
- state_out_einsum = np.einsum("abcdef,mfedk->mcbak", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
- @pytest.mark.parametrize("op, method", three_qubit_ops)
- def test_apply_three_qubit_op_controls_split_broadcasted_state(self, op, method):
- """Test if the application of three qubit operations to a broadcasted state is correct
- when one control wire is smaller and one control wire is greater than the target wire."""
- state_out = method(self.broadcasted_state, axes=[4, 2, 3])
- op = op(wires=[3, 1, 2])
- matrix = op.matrix()
- matrix = matrix.reshape((2, 2) * 3)
- state_out_einsum = np.einsum("abcdef,mkdfe->mkacb", matrix, self.broadcasted_state)
- assert np.allclose(state_out, state_out_einsum)
-
-
-class TestStateVectorBroadcasted:
- """Unit tests for the _apply_state_vector method with broadcasting"""
-
- def test_full_subsystem_broadcasted(self, mocker):
- """Test applying a state vector to the full subsystem"""
- dev = DefaultQubitLegacy(wires=["a", "b", "c"])
- state = np.array([[0, 1, 1, 0, 1, 1, 0, 0], [1, 0, 0, 0, 1, 0, 1, 1]]) / 2.0
- state_wires = qml.wires.Wires(["a", "b", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
-
- assert np.all(dev._state.reshape((2, 8)) == state)
- spy.assert_not_called()
-
- def test_partial_subsystem_broadcasted(self, mocker):
- """Test applying a state vector to a subset of wires of the full subsystem"""
-
- dev = DefaultQubitLegacy(wires=["a", "b", "c"])
- state = np.array([[0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 0, 0]]) / np.sqrt(2.0)
- state_wires = qml.wires.Wires(["a", "c"])
-
- spy = mocker.spy(dev, "_scatter")
- dev._apply_state_vector(state=state, device_wires=state_wires)
- # Axes are (broadcasting, wire "a", wire "b", wire "c"), so we sum over axis=2
- res = np.sum(dev._state, axis=(2,)).reshape((3, 4))
-
- assert np.all(res == state)
- spy.assert_called()
-
-
-class TestApplyOperationBroadcasted:
- """Unit tests for the internal _apply_operation method."""
-
- def test_internal_apply_ops_case_broadcasted(self, monkeypatch):
- """Tests that if we provide an operation that has an internal
- implementation, then we use that specific implementation.
-
- This test provides a new internal function that `default.qubit.legacy` uses to
- apply `PauliX` (rather than redefining the gate itself).
- """
- dev = qml.device("default.qubit.legacy", wires=1)
-
- test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- # Create a dummy operation
- expected_test_output = np.ones(1)
- supported_gate_application = lambda *args, **kwargs: expected_test_output
-
- with monkeypatch.context() as m:
- # Set the internal ops implementations dict
- m.setattr(dev.target_device, "_apply_ops", {"PauliX": supported_gate_application})
-
- op = qml.PauliX(0)
-
- res = dev._apply_operation(test_state, op)
- assert np.allclose(res, expected_test_output)
-
- def test_diagonal_operation_case_broadcasted(self, monkeypatch):
- """Tests the case when the operation to be applied is
- diagonal in the computational basis and the _apply_diagonal_unitary method is used."""
- dev = qml.device("default.qubit.legacy", wires=1)
- par = 0.3
-
- test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- wires = 0
- op = qml.PhaseShift(par, wires=wires)
- assert op.name not in dev._apply_ops
-
- # Set the internal _apply_diagonal_unitary
- history = []
- mock_apply_diag = lambda state, matrix, wires: history.append((state, matrix, wires))
- with monkeypatch.context() as m:
- m.setattr(dev.target_device, "_apply_diagonal_unitary", mock_apply_diag)
- assert dev._apply_diagonal_unitary == mock_apply_diag
-
- dev._apply_operation(test_state, op)
-
- res_state, res_mat, res_wires = history[0]
-
- assert np.allclose(res_state, test_state)
- assert np.allclose(res_mat, np.diag(op.matrix()))
- assert np.allclose(res_wires, wires)
-
- def test_apply_einsum_case_broadcasted(self, monkeypatch):
- """Tests the case when np.einsum is used to apply an operation in
- default.qubit."""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- wires = 0
-
- # Redefine the S gate so that it is an example for a one-qubit gate
- # that is not registered in the diagonal_in_z_basis attribute
- # pylint: disable=too-few-public-methods
- class TestSGate(qml.operation.Operation):
- num_wires = 1
-
- # pylint: disable=unused-argument
- @staticmethod
- def compute_matrix(*params, **hyperparams):
- return np.array([[1, 0], [0, 1j]])
-
- dev.operations.add("TestSGate")
- op = TestSGate(wires=wires)
-
- assert op.name in dev.operations
- assert op.name not in dev._apply_ops
-
- # Set the internal _apply_unitary_einsum
- history = []
- mock_apply_einsum = lambda state, matrix, wires: history.append((state, matrix, wires))
- with monkeypatch.context() as m:
- m.setattr(dev.target_device, "_apply_unitary_einsum", mock_apply_einsum)
-
- dev._apply_operation(test_state, op)
-
- res_state, res_mat, res_wires = history[0]
-
- assert np.allclose(res_state, test_state)
- assert np.allclose(res_mat, op.matrix())
- assert np.allclose(res_wires, wires)
-
- def test_apply_tensordot_case_broadcasted(self, monkeypatch):
- """Tests the case when np.tensordot is used to apply an operation in
- default.qubit."""
- dev = qml.device("default.qubit.legacy", wires=3)
-
- test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- wires = [0, 1, 2]
-
- # Redefine the Toffoli gate so that it is an example for a gate with
- # more than two wires
- # pylint: disable=too-few-public-methods
- class TestToffoli(qml.operation.Operation):
- num_wires = 3
-
- # pylint: disable=unused-argument
- @staticmethod
- def compute_matrix(*params, **hyperparams):
- return Toffoli
-
- dev.operations.add("TestToffoli")
- op = TestToffoli(wires=wires)
-
- assert op.name in dev.operations
- assert op.name not in dev._apply_ops
-
- # Set the internal _apply_unitary_tensordot
- history = []
- mock_apply_tensordot = lambda state, matrix, wires: history.append((state, matrix, wires))
-
- with monkeypatch.context() as m:
- m.setattr(dev.target_device, "_apply_unitary", mock_apply_tensordot)
-
- dev._apply_operation(test_state, op)
-
- res_state, res_mat, res_wires = history[0]
-
- assert np.allclose(res_state, test_state)
- assert np.allclose(res_mat, op.matrix())
- assert np.allclose(res_wires, wires)
-
- def test_identity_skipped_broadcasted(self, mocker):
- """Test that applying the identity operation does not perform any additional computations."""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- starting_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]])
- op = qml.Identity(0)
-
- spy_diagonal = mocker.spy(dev.target_device, "_apply_diagonal_unitary")
- spy_einsum = mocker.spy(dev.target_device, "_apply_unitary_einsum")
- spy_unitary = mocker.spy(dev.target_device, "_apply_unitary")
-
- res = dev._apply_operation(starting_state, op)
- assert res is starting_state
-
- spy_diagonal.assert_not_called()
- spy_einsum.assert_not_called()
- spy_unitary.assert_not_called()
-
-
-class TestHamiltonianSupportBroadcasted:
- """Tests the devices' native support for Hamiltonian observables."""
-
- def test_do_not_split_analytic_broadcasted(self, mocker):
- """Tests that the Hamiltonian is not split for shots=None."""
- dev = qml.device("default.qubit.legacy", wires=2)
- Ham = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)])
-
- @qml.qnode(dev, diff_method="parameter-shift", interface=None)
- def circuit():
- qml.RX(np.zeros(5), 0) # Broadcast the state by applying a broadcasted identity
- return qml.expval(Ham)
-
- spy = mocker.spy(dev.target_device, "expval")
-
- circuit()
- # evaluated one expval altogether
- assert spy.call_count == 1
-
- def test_split_finite_shots_broadcasted(self, mocker):
- """Tests that the Hamiltonian is split for finite shots."""
- dev = qml.device("default.qubit.legacy", wires=2, shots=10)
- spy = mocker.spy(dev.target_device, "expval")
-
- ham = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)])
-
- @qml.qnode(dev)
- def circuit():
- qml.RX(np.zeros(5), 0) # Broadcast the state by applying a broadcasted identity
- return qml.expval(ham)
-
- circuit()
-
- # evaluated one expval per Pauli observable
- assert spy.call_count == 2
-
-
-original_capabilities = qml.devices.DefaultQubitLegacy.capabilities()
-
-
-@pytest.fixture(scope="function", name="mock_default_qubit")
-def mock_default_qubit_fixture(monkeypatch):
- """A function to create a mock device that mocks the broadcasting support flag
- to be False, so that default support via broadcast_expand transform can be tested"""
-
- # pylint: disable=unused-argument
- def overwrite_support(*cls):
- capabilities = original_capabilities.copy()
- capabilities.update(supports_broadcasting=False)
- return capabilities
-
- with monkeypatch.context() as m:
- m.setattr(qml.devices.DefaultQubitLegacy, "capabilities", overwrite_support)
-
- def get_default_qubit(wires=1, shots=None):
- dev = qml.devices.DefaultQubitLegacy(wires=wires, shots=shots)
- return dev
-
- yield get_default_qubit
-
-
-@pytest.mark.parametrize("shots", [None, 100000])
-class TestBroadcastingSupportViaExpansion:
- """Tests that the device correctly makes use of ``broadcast_expand`` to
- execute broadcasted tapes if its capability to execute broadcasted tapes
- is artificially deactivated."""
-
- @pytest.mark.parametrize("x", [0.2, np.array([0.1, 0.6, 0.3]), np.array([0.1])])
- def test_with_single_broadcasted_par(self, x, shots, mock_default_qubit):
- """Test that broadcasting on a circuit with a
- single parametrized operation works."""
- dev = mock_default_qubit(wires=2, shots=shots)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- circuit.construct((np.array(x),), {})
- out = circuit(np.array(x))
-
- assert circuit.device.num_executions == (1 if isinstance(x, float) else len(x))
- tol = 1e-10 if shots is None else 1e-2
- assert qml.math.allclose(out, qml.math.cos(x), atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "x, y", [(0.2, np.array([0.4])), (np.array([0.1, 5.1]), np.array([0.1, -0.3]))]
- )
- def test_with_multiple_pars(self, x, y, shots, mock_default_qubit):
- """Test that broadcasting on a circuit with a
- single parametrized operation works."""
- dev = mock_default_qubit(wires=2, shots=shots)
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x, y):
- qml.RX(x, wires=0)
- qml.RX(y, wires=1)
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
-
- out = circuit(x, y)
- expected = qml.math.stack([qml.math.cos(x) * qml.math.ones_like(y), -qml.math.sin(y)])
-
- assert circuit.device.num_executions == len(y)
- tol = 1e-10 if shots is None else 1e-2
-
- assert qml.math.allclose(out[0], expected[0], atol=tol, rtol=0)
- assert qml.math.allclose(out[1], expected[1], atol=tol, rtol=0)
-
- @pytest.mark.parametrize(
- "x, y", [(0.2, np.array([0.4])), (np.array([0.1, 5.1]), np.array([0.1, -0.3]))]
- )
- def test_with_Hamiltonian(self, x, y, shots, mock_default_qubit):
- """Test that broadcasting on a circuit with a
- single parametrized operation works."""
- dev = mock_default_qubit(wires=2, shots=shots)
-
- Ham = qml.Hamiltonian([0.3, 0.9], [qml.PauliZ(0), qml.PauliY(1)])
- Ham.compute_grouping()
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def circuit(x, y):
- qml.RX(x, wires=0)
- qml.RX(y, wires=1)
- return qml.expval(Ham)
-
- out = circuit(x, y)
- expected = 0.3 * qml.math.cos(x) * qml.math.ones_like(y) - 0.9 * qml.math.sin(y)
-
- assert circuit.device.num_executions == len(y)
- tol = 1e-10 if shots is None else 1e-2
- assert qml.math.allclose(out, expected, atol=tol, rtol=0)
diff --git a/tests/devices/test_default_qutrit.py b/tests/devices/test_default_qutrit.py
index ee860f01af9..6d718c45f18 100644
--- a/tests/devices/test_default_qutrit.py
+++ b/tests/devices/test_default_qutrit.py
@@ -1466,7 +1466,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol, use_jit):
@@ -1658,7 +1657,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
@@ -1839,7 +1837,6 @@ def circuit(x):
expected = -np.sin(p)
- assert circuit.gradient_fn == "backprop"
assert np.isclose(circuit(p), expected, atol=tol, rtol=0)
def test_correct_state(self, tol):
diff --git a/tests/devices/test_default_qutrit_mixed.py b/tests/devices/test_default_qutrit_mixed.py
index 0dd73df430b..13f3d744bb1 100644
--- a/tests/devices/test_default_qutrit_mixed.py
+++ b/tests/devices/test_default_qutrit_mixed.py
@@ -1720,7 +1720,7 @@ def test_differentiation_jax( # pylint: disable=too-many-arguments
if use_jit:
diff_func = jax.jit(diff_func)
jac = jax.jacobian(diff_func, args_to_diff)(relaxations, misclassifications)
- assert np.allclose(jac, expected, atol=1e-6)
+ assert qml.math.allclose(jac, expected, rtol=0.05)
@pytest.mark.torch
@pytest.mark.parametrize("relaxations, misclassifications, expected", diff_parameters)
diff --git a/tests/devices/test_device.py b/tests/devices/test_legacy_device.py
similarity index 99%
rename from tests/devices/test_device.py
rename to tests/devices/test_legacy_device.py
index c05a5e76309..c08d9411dca 100644
--- a/tests/devices/test_device.py
+++ b/tests/devices/test_legacy_device.py
@@ -22,7 +22,7 @@
import pytest
import pennylane as qml
-from pennylane import Device
+from pennylane.devices import LegacyDevice as Device
from pennylane.wires import Wires
mock_device_paulis = ["PauliX", "PauliY", "PauliZ"]
@@ -188,6 +188,12 @@ def get_device(wires=1):
yield get_device
+def test_deprecated_access():
+ """Test that accessing via top-level is deprecated."""
+ with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"):
+ qml.Device # pylint: disable=pointless-statement
+
+
# pylint: disable=pointless-statement
def test_invalid_attribute_in_devices_raises_error():
with pytest.raises(AttributeError, match="'pennylane.devices' has no attribute 'blabla'"):
@@ -1151,7 +1157,7 @@ class TestGrouping:
"""Tests for the use_grouping option for devices."""
# pylint: disable=too-few-public-methods, unused-argument, missing-function-docstring, missing-class-docstring
- class SomeDevice(qml.Device):
+ class SomeDevice(qml.devices.LegacyDevice):
name = ""
short_name = ""
pennylane_requires = ""
diff --git a/tests/devices/test_legacy_facade.py b/tests/devices/test_legacy_facade.py
index dac63c2864e..4af1ae1e77a 100644
--- a/tests/devices/test_legacy_facade.py
+++ b/tests/devices/test_legacy_facade.py
@@ -21,7 +21,6 @@
import pytest
import pennylane as qml
-from pennylane.devices.default_qubit_autograd import DefaultQubitAutograd
from pennylane.devices.execution_config import ExecutionConfig
from pennylane.devices.legacy_facade import (
LegacyDeviceFacade,
@@ -401,80 +400,7 @@ class BackpropDevice(DummyDevice):
dev = LegacyDeviceFacade(BackpropDevice(wires=2, shots=None))
- x = qml.numpy.array(0.1)
- tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))])
-
assert dev.supports_derivatives(qml.devices.ExecutionConfig(gradient_method="backprop"))
- assert dev._create_temp_device((tape,)) is dev.target_device
config = qml.devices.ExecutionConfig(gradient_method="backprop", use_device_gradient=True)
assert dev.preprocess(config)[1] is config # unchanged
-
- def test_backprop_has_passthru_devices(self):
- """Test that backprop is supported if the device has passthru devices."""
-
- class BackpropDevice(DummyDevice):
-
- _capabilities = {"passthru_devices": {"autograd": "default.qubit.autograd"}}
-
- dev = LegacyDeviceFacade(BackpropDevice(shots=None))
-
- x = qml.numpy.array(0.1)
- tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))])
- assert dev.supports_derivatives()
- assert dev.supports_derivatives(ExecutionConfig(gradient_method="backprop"))
- assert dev.supports_derivatives(ExecutionConfig(gradient_method="backprop"), tape)
-
- config = qml.devices.ExecutionConfig(gradient_method="backprop", use_device_gradient=True)
- assert dev.preprocess(config)[1] is config # unchanged
-
- with pytest.warns(qml.PennyLaneDeprecationWarning, match="switching of devices"):
- tmp_device = dev._create_temp_device((tape,))
- assert tmp_device.short_name == "default.qubit.autograd"
-
- def test_backprop_passthru_device_self(self):
- """Test that the temporary device is the original device if the passthru device is itself."""
-
- class BackpropSelfDevice(DummyDevice):
-
- short_name = "BackpropSelfDevice"
-
- _capabilities = {"passthru_devices": {"autograd": "BackpropSelfDevice"}}
-
- dev = LegacyDeviceFacade(BackpropSelfDevice(wires=2))
-
- x = qml.numpy.array(0.1)
- tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))])
- tmp_dev = dev._create_temp_device((tape,))
- assert tmp_dev is dev.target_device
-
- def test_passthru_device_does_not_exist(self):
- """Test that if backprop is requested for a device that does not support it, a device error is raised."""
-
- x = qml.numpy.array(0.1)
- tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))])
-
- dev = LegacyDeviceFacade(DummyDevice(wires=2))
- config = qml.devices.ExecutionConfig(gradient_method="backprop")
- with pytest.raises(qml.DeviceError, match=r"does not support backpropagation"):
- dev.execute(tape, config)
-
- @pytest.mark.parametrize("dev_class", (qml.devices.DefaultQubitLegacy, DefaultQubitAutograd))
- def test_backprop_device_substitution(self, dev_class):
- """Test that default.qubit.legacy is substituted for a backprop device during backprop execution."""
-
- with pytest.warns(qml.PennyLaneDeprecationWarning, match="use 'default.qubit'"):
- dq_legacy = dev_class(wires=2)
- dev = LegacyDeviceFacade(dq_legacy)
-
- def f(x):
- tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))])
- return dev.execute(tape, qml.devices.ExecutionConfig(gradient_method="backprop"))
-
- assert qml.math.allclose(dq_legacy.state, np.array([1, 0, 0, 0]))
-
- with dev.tracker:
- g = qml.grad(f)(qml.numpy.array(0.5))
- assert qml.math.allclose(g, -np.sin(0.5))
- assert dev.tracker.totals["executions"] == 1
- assert not qml.math.allclose(dq_legacy.state, np.array([1, 0, 0, 0]))
diff --git a/tests/test_qubit_device.py b/tests/devices/test_qubit_device.py
similarity index 99%
rename from tests/test_qubit_device.py
rename to tests/devices/test_qubit_device.py
index 4308f6a21fd..8f50bb65329 100644
--- a/tests/test_qubit_device.py
+++ b/tests/devices/test_qubit_device.py
@@ -21,8 +21,8 @@
import pytest
import pennylane as qml
-from pennylane import QubitDevice
from pennylane import numpy as pnp
+from pennylane.devices import QubitDevice
from pennylane.measurements import (
Expectation,
ExpectationMP,
@@ -165,6 +165,12 @@ def _working_get_batch_size(tensor, expected_shape, expected_size):
return None
+def test_deprecated_access():
+ """Test that accessing via top-level is deprecated."""
+ with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"):
+ qml.QubitDevice # pylint: disable=pointless-statement
+
+
def test_notimplemented_circuit_hash(mock_qubit_device):
"""Test that the circuit hash property is not implemented"""
dev = mock_qubit_device()
@@ -1508,10 +1514,7 @@ class TestResourcesTracker:
Resources(2, 6, {"Hadamard": 3, "RX": 2, "CNOT": 1}, {1: 5, 2: 1}, 4, Shots((10, 10, 50))),
) # Resources(wires, gates, gate_types, gate_sizes, depth, shots)
- devices = (
- "default.qubit.legacy",
- "default.qubit.autograd",
- )
+ devices = ("default.qubit.legacy",)
@pytest.mark.all_interfaces
@pytest.mark.parametrize("dev_name", devices)
@@ -1658,7 +1661,7 @@ def test_generate_basis_states():
def test_samples_to_counts_all_outomces():
"""Test that _samples_to_counts can handle counts with all outcomes."""
- class DummyQubitDevice(qml.QubitDevice):
+ class DummyQubitDevice(qml.devices.QubitDevice):
author = None
name = "bla"
@@ -1679,7 +1682,7 @@ def apply(self, operations, **kwargs):
def test_no_adjoint_jacobian_errors():
"""Test that adjoint_jacobian errors with batching and shot vectors"""
- class DummyQubitDevice(qml.QubitDevice):
+ class DummyQubitDevice(qml.devices.QubitDevice):
author = None
name = "bla"
diff --git a/tests/test_qutrit_device.py b/tests/devices/test_qutrit_device.py
similarity index 99%
rename from tests/test_qutrit_device.py
rename to tests/devices/test_qutrit_device.py
index 6291a3e9d71..8799d75234b 100644
--- a/tests/test_qutrit_device.py
+++ b/tests/devices/test_qutrit_device.py
@@ -22,8 +22,8 @@
from scipy.stats import unitary_group
import pennylane as qml
-from pennylane import QubitDevice, QutritDevice
from pennylane import numpy as pnp
+from pennylane.devices import QubitDevice, QutritDevice
from pennylane.measurements import (
Counts,
CountsMP,
@@ -142,6 +142,12 @@ def get_qutrit_device(wires=1):
# TODO: Add tests for expval, var after observables are added
+def test_deprecated_access():
+ """Test that accessing via top-level is deprecated."""
+ with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"):
+ qml.QutritDevice # pylint: disable=pointless-statement
+
+
class TestOperations:
"""Tests the logic related to operations"""
diff --git a/tests/gradients/parameter_shift/test_parameter_shift.py b/tests/gradients/parameter_shift/test_parameter_shift.py
index 237178b538d..e5a00b2cdf0 100644
--- a/tests/gradients/parameter_shift/test_parameter_shift.py
+++ b/tests/gradients/parameter_shift/test_parameter_shift.py
@@ -3563,8 +3563,7 @@ def cost_fn(weights, coeffs1, coeffs2, dev=None, broadcast=False):
tape = qml.tape.QuantumScript.from_queue(q)
tape.trainable_params = {0, 1, 2, 3, 4, 5}
tapes, fn = qml.gradients.param_shift(tape, broadcast=broadcast)
- execute_fn = dev.batch_execute if isinstance(dev, qml.Device) else dev.execute
- jac = fn(execute_fn(tapes))
+ jac = fn(dev.execute(tapes))
return jac
@staticmethod
diff --git a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
index d7a0b893ce0..6d9212dceb9 100644
--- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
+++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
@@ -864,7 +864,6 @@ def cost_fn(params):
assert np.allclose(res[0], expval_expected[0], atol=finite_diff_tol)
assert np.allclose(res[1], expval_expected[1], atol=finite_diff_tol)
- @pytest.mark.autograd
@pytest.mark.parametrize("RX, RY, argnum", [(RX_with_F, qml.RY, 0), (qml.RX, RY_with_F, 1)])
def test_fallback_probs(
self, RX, RY, argnum, mocker, broadcast
@@ -2229,8 +2228,7 @@ def cost_fn(weights, coeffs1, coeffs2, dev=None, broadcast=False):
tape = qml.tape.QuantumScript.from_queue(q, shots=dev.shots)
tape.trainable_params = {0, 1, 2, 3, 4, 5}
tapes, fn = qml.gradients.param_shift(tape, broadcast=broadcast)
- execute_fn = dev.batch_execute if isinstance(dev, qml.Device) else dev.execute
- return fn(execute_fn(tapes))
+ return fn(dev.execute(tapes))
@staticmethod
def cost_fn_expected(weights, coeffs1, coeffs2):
diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py
deleted file mode 100644
index 03072f2515d..00000000000
--- a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py
+++ /dev/null
@@ -1,1367 +0,0 @@
-# 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 autograd interface"""
-# pylint: disable=protected-access,too-few-public-methods
-import sys
-
-import autograd
-import pytest
-
-import pennylane as qml
-from pennylane import numpy as np
-from pennylane.devices import DefaultQubitLegacy
-from pennylane.gradients import finite_diff, param_shift
-from pennylane.operation import AnyWires, Observable
-
-pytestmark = pytest.mark.autograd
-
-
-class TestAutogradExecuteUnitTests:
- """Unit tests for autograd execution"""
-
- def test_import_error(self, mocker):
- """Test that an exception is caught on import error"""
-
- mock = mocker.patch.object(autograd.extend, "defvjp")
- mock.side_effect = ImportError()
-
- try:
- del sys.modules["pennylane.workflow.interfaces.autograd"]
- except KeyError:
- pass
-
- dev = qml.device("default.qubit.legacy", wires=2, shots=None)
-
- with qml.queuing.AnnotatedQueue() as q:
- qml.expval(qml.PauliY(1))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- with pytest.raises(
- qml.QuantumFunctionError,
- match="autograd not found. Please install the latest version "
- "of autograd to enable the 'autograd' interface",
- ):
- qml.execute([tape], dev, gradient_fn=param_shift, interface="autograd")
-
- def test_jacobian_options(self, mocker):
- """Test setting jacobian options"""
- spy = mocker.spy(qml.gradients, "param_shift")
-
- a = np.array([0.1, 0.2], requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=1)
-
- def cost(a, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute(
- [tape],
- device,
- gradient_fn=param_shift,
- gradient_kwargs={"shifts": [(np.pi / 4,)] * 2},
- )[0]
-
- qml.jacobian(cost)(a, device=dev)
-
- for args in spy.call_args_list:
- assert args[1]["shifts"] == [(np.pi / 4,)] * 2
-
- def test_incorrect_grad_on_execution(self):
- """Test that an error is raised if a gradient transform
- is used with grad_on_execution=True"""
- a = np.array([0.1, 0.2], requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=1)
-
- def cost(a, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], device, gradient_fn=param_shift, grad_on_execution=True)[0]
-
- with pytest.raises(
- ValueError, match="Gradient transforms cannot be used with grad_on_execution=True"
- ):
- qml.jacobian(cost)(a, device=dev)
-
- def test_unknown_interface(self):
- """Test that an error is raised if the interface is unknown"""
- a = np.array([0.1, 0.2], requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=1)
-
- def cost(a, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], device, gradient_fn=param_shift, interface="None")[0]
-
- with pytest.raises(ValueError, match="interface must be in"):
- cost(a, device=dev)
-
- def test_grad_on_execution(self, mocker):
- """Test that grad on execution uses the `device.execute_and_gradients` pathway"""
- dev = qml.device("default.qubit.legacy", wires=1)
- spy = mocker.spy(dev, "execute_and_compute_derivatives")
-
- def cost(a):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute(
- [tape],
- dev,
- gradient_fn="device",
- gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True},
- )[0]
-
- a = np.array([0.1, 0.2], requires_grad=True)
- cost(a)
-
- # adjoint method only performs a single device execution, but gets both result and gradient
- assert dev.num_executions == 1
- spy.assert_called()
-
- def test_no_gradients_on_execution(self, mocker):
- """Test that no grad on execution uses the `device.batch_execute` and `device.gradients` pathway"""
- dev = qml.device("default.qubit.legacy", wires=1)
- spy_execute = mocker.spy(qml.devices.DefaultQubitLegacy, "batch_execute")
- spy_gradients = mocker.spy(qml.devices.DefaultQubitLegacy, "gradients")
-
- def cost(a):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute(
- [tape],
- dev,
- gradient_fn="device",
- grad_on_execution=False,
- gradient_kwargs={"method": "adjoint_jacobian"},
- )[0]
-
- a = np.array([0.1, 0.2], requires_grad=True)
- cost(a)
-
- assert dev.num_executions == 1
- spy_execute.assert_called()
- spy_gradients.assert_not_called()
-
- qml.jacobian(cost)(a)
- spy_gradients.assert_called()
-
-
-class TestBatchTransformExecution:
- """Tests to ensure batch transforms can be correctly executed
- via qml.execute and batch_transform"""
-
- def test_batch_transform_dynamic_shots(self):
- """Tests that the batch transform considers the number of shots for the execution, not those
- statically on the device."""
- dev = qml.device("default.qubit.legacy", wires=1)
- H = 2.0 * qml.PauliZ(0)
- qscript = qml.tape.QuantumScript(measurements=[qml.expval(H)])
- res = qml.execute([qscript], dev, interface=None)
- assert res == (2.0,)
-
-
-class TestCaching:
- """Test for caching behaviour"""
-
- def test_cache_maxsize(self, mocker):
- """Test the cachesize property of the cache"""
- dev = qml.device("default.qubit.legacy", wires=1)
- spy = mocker.spy(qml.workflow.execution._cache_transform, "_transform")
-
- def cost(a, cachesize):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.probs(wires=0)
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], dev, gradient_fn=param_shift, cachesize=cachesize)[0]
-
- params = np.array([0.1, 0.2])
- qml.jacobian(cost)(params, cachesize=2)
- cache = spy.call_args.kwargs["cache"]
-
- assert cache.maxsize == 2
- assert cache.currsize == 2
- assert len(cache) == 2
-
- def test_custom_cache(self, mocker):
- """Test the use of a custom cache object"""
- dev = qml.device("default.qubit.legacy", wires=1)
- spy = mocker.spy(qml.workflow.execution._cache_transform, "_transform")
-
- def cost(a, cache):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.probs(wires=0)
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache)[0]
-
- custom_cache = {}
- params = np.array([0.1, 0.2])
- qml.jacobian(cost)(params, cache=custom_cache)
-
- cache = spy.call_args.kwargs["cache"]
- assert cache is custom_cache
-
- def test_caching_param_shift(self, tol):
- """Test that, when using parameter-shift transform,
- caching reduces the number of evaluations to their optimum."""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- def cost(a, cache):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.probs(wires=0)
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache)[0]
-
- # Without caching, 5 jacobians should still be performed
- params = np.array([0.1, 0.2])
- qml.jacobian(cost)(params, cache=None)
- assert dev.num_executions == 5
-
- # With caching, 5 evaluations are required to compute
- # the Jacobian: 1 (forward pass) + (2 shifts * 2 params)
- dev.target_device._num_executions = 0
- jac_fn = qml.jacobian(cost)
- grad1 = jac_fn(params, cache=True)
- assert dev.num_executions == 5
-
- # Check that calling the cost function again
- # continues to evaluate the device (that is, the cache
- # is emptied between calls)
- grad2 = jac_fn(params, cache=True)
- assert dev.num_executions == 10
- assert np.allclose(grad1, grad2, atol=tol, rtol=0)
-
- # Check that calling the cost function again
- # with different parameters produces a different Jacobian
- grad2 = jac_fn(2 * params, cache=True)
- assert dev.num_executions == 15
- assert not np.allclose(grad1, grad2, atol=tol, rtol=0)
-
- @pytest.mark.parametrize("num_params", [2, 3])
- def test_caching_param_shift_hessian(self, num_params, tol):
- """Test that, when using parameter-shift transform,
- caching reduces the number of evaluations to their optimum
- when computing Hessians."""
- dev = qml.device("default.qubit.legacy", wires=2)
- params = np.arange(1, num_params + 1) / 10
-
- N = len(params)
-
- def cost(x, cache):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
-
- for i in range(2, num_params):
- qml.RZ(x[i], wires=[i % 2])
-
- qml.CNOT(wires=[0, 1])
- qml.var(qml.PauliZ(0) @ qml.PauliX(1))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache, max_diff=2)[0]
-
- # No caching: number of executions is not ideal
- hess1 = qml.jacobian(qml.grad(cost))(params, cache=False)
-
- if num_params == 2:
- # compare to theoretical result
- x, y, *_ = params
- expected = np.array(
- [
- [2 * np.cos(2 * x) * np.sin(y) ** 2, np.sin(2 * x) * np.sin(2 * y)],
- [np.sin(2 * x) * np.sin(2 * y), -2 * np.cos(x) ** 2 * np.cos(2 * y)],
- ]
- )
- assert np.allclose(expected, hess1, atol=tol, rtol=0)
-
- expected_runs = 1 # forward pass
-
- # Jacobian of an involutory observable:
- # ------------------------------------
- #
- # 2 * N execs: evaluate the analytic derivative of
- # 1 execs: Get , the expectation value of the tape with unshifted parameters.
- num_shifted_evals = 2 * N
- runs_for_jacobian = num_shifted_evals + 1
- expected_runs += runs_for_jacobian
-
- # Each tape used to compute the Jacobian is then shifted again
- expected_runs += runs_for_jacobian * num_shifted_evals
- assert dev.num_executions == expected_runs
-
- # Use caching: number of executions is ideal
- dev.target_device._num_executions = 0
- hess2 = qml.jacobian(qml.grad(cost))(params, cache=True)
- assert np.allclose(hess1, hess2, atol=tol, rtol=0)
-
- expected_runs_ideal = 1 # forward pass
- expected_runs_ideal += 2 * N # Jacobian
- expected_runs_ideal += N + 1 # Hessian diagonal
- expected_runs_ideal += 4 * N * (N - 1) // 2 # Hessian off-diagonal
- assert dev.num_executions == expected_runs_ideal
- assert expected_runs_ideal < expected_runs
-
- def test_caching_adjoint_no_grad_on_execution(self):
- """Test that caching reduces the number of adjoint evaluations
- when the grads is not on execution."""
- dev = qml.device("default.qubit.legacy", wires=2)
- params = np.array([0.1, 0.2, 0.3])
-
- def cost(a, cache):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.RY(a[2], wires=0)
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliZ(1))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(
- qml.execute(
- [tape],
- dev,
- gradient_fn="device",
- cache=cache,
- grad_on_execution=False,
- gradient_kwargs={"method": "adjoint_jacobian"},
- )[0]
- )
-
- # no caching, but jac for each batch still stored.
- qml.jacobian(cost)(params, cache=None)
- assert dev.num_executions == 1
-
- # With caching, only 1 evaluation required.
- dev.target_device._num_executions = 0
- jac_fn = qml.jacobian(cost)
- jac_fn(params, cache=True)
- assert dev.num_executions == 1
-
- def test_single_backward_pass_batch(self):
- """Tests that the backward pass is one single batch, not a bunch of batches, when parameter shift
- is requested for multiple tapes."""
-
- dev = qml.device("default.qubit.legacy", wires=2)
-
- def f(x):
- tape1 = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.probs(wires=0)])
- tape2 = qml.tape.QuantumScript([qml.RY(x, 0)], [qml.probs(wires=0)])
-
- results = qml.execute([tape1, tape2], dev, gradient_fn=qml.gradients.param_shift)
- return results[0] + results[1]
-
- x = qml.numpy.array(0.1)
- with dev.tracker:
- out = qml.jacobian(f)(x)
-
- assert dev.tracker.totals["batches"] == 2
- assert dev.tracker.history["batch_len"] == [2, 4]
- expected = [-2 * np.cos(x / 2) * np.sin(x / 2), 2 * np.sin(x / 2) * np.cos(x / 2)]
- assert qml.math.allclose(out, expected)
-
- def test_single_backward_pass_split_hamiltonian(self):
- """Tests that the backward pass is one single batch, not a bunch of batches, when parameter
- shift derivatives are requested for a tape that the device split into batches."""
-
- dev = qml.device("default.qubit.legacy", wires=2, shots=50000)
-
- H = qml.Hamiltonian([1, 1], [qml.PauliY(0), qml.PauliZ(0)], grouping_type="qwc")
-
- def f(x):
- tape = qml.tape.QuantumScript([qml.RX(x, wires=0)], [qml.expval(H)])
- return qml.execute([tape], dev, gradient_fn=qml.gradients.param_shift)[0]
-
- x = qml.numpy.array(0.1)
- with dev.tracker:
- out = qml.grad(f)(x)
-
- assert dev.tracker.totals["batches"] == 2
- assert dev.tracker.history["batch_len"] == [1, 2]
-
- assert qml.math.allclose(out, -np.cos(x) - np.sin(x), atol=0.05)
-
-
-execute_kwargs_integration = [
- {"gradient_fn": param_shift},
- {
- "gradient_fn": "device",
- "grad_on_execution": True,
- "gradient_kwargs": {"method": "adjoint_jacobian", "use_device_state": True},
- },
- {
- "gradient_fn": "device",
- "grad_on_execution": False,
- "gradient_kwargs": {"method": "adjoint_jacobian"},
- },
-]
-
-
-@pytest.mark.parametrize("execute_kwargs", execute_kwargs_integration)
-class TestAutogradExecuteIntegration:
- """Test the autograd interface execute function
- integrates well for both forward and backward execution"""
-
- def test_execution(self, execute_kwargs):
- """Test execution"""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- def cost(a, b):
- with qml.queuing.AnnotatedQueue() as q1:
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape1 = qml.tape.QuantumScript.from_queue(q1)
- with qml.queuing.AnnotatedQueue() as q2:
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape2 = qml.tape.QuantumScript.from_queue(q2)
- return qml.execute([tape1, tape2], dev, **execute_kwargs)
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=False)
- res = cost(a, b)
-
- assert len(res) == 2
- assert res[0].shape == ()
- assert res[1].shape == ()
-
- def test_scalar_jacobian(self, execute_kwargs, tol):
- """Test scalar jacobian calculation"""
- a = np.array(0.1, requires_grad=True)
- dev = qml.device("default.qubit.legacy", wires=2)
-
- def cost(a):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a, wires=0)
- qml.expval(qml.PauliZ(0))
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], dev, **execute_kwargs)[0]
-
- res = qml.jacobian(cost)(a)
- assert res.shape == ()
-
- # compare to standard tape jacobian
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a, wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- tape.trainable_params = [0]
- tapes, fn = param_shift(tape)
- expected = fn(dev.batch_execute(tapes))
-
- assert expected.shape == ()
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_jacobian(self, execute_kwargs, tol):
- """Test jacobian calculation"""
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- def cost(a, b, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliY(1))
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0])
-
- dev = qml.device("default.qubit.legacy", wires=2)
-
- res = cost(a, b, device=dev)
- expected = [np.cos(a), -np.cos(a) * np.sin(b)]
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.jacobian(cost)(a, b, device=dev)
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == (2,)
- assert res[1].shape == (2,)
-
- expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)])
- assert all(np.allclose(_r, _e, atol=tol, rtol=0) for _r, _e in zip(res, expected))
-
- @pytest.mark.filterwarnings("ignore:Attempted to compute the gradient")
- def test_tape_no_parameters(self, execute_kwargs, tol):
- """Test that a tape with no parameters is correctly
- ignored during the gradient computation"""
-
- if execute_kwargs["gradient_fn"] == "device":
- pytest.skip("Adjoint differentiation does not yet support probabilities")
-
- dev = qml.device("default.qubit.legacy", wires=2)
-
- def cost(params):
- with qml.queuing.AnnotatedQueue() as q1:
- qml.Hadamard(0)
- qml.expval(qml.PauliX(0))
-
- tape1 = qml.tape.QuantumScript.from_queue(q1)
- with qml.queuing.AnnotatedQueue() as q2:
- qml.RY(np.array(0.5, requires_grad=False), wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape2 = qml.tape.QuantumScript.from_queue(q2)
- with qml.queuing.AnnotatedQueue() as q3:
- qml.RY(params[0], wires=0)
- qml.RX(params[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape3 = qml.tape.QuantumScript.from_queue(q3)
- with qml.queuing.AnnotatedQueue() as q4:
- qml.RY(np.array(0.5, requires_grad=False), wires=0)
- qml.probs(wires=[0, 1])
-
- tape4 = qml.tape.QuantumScript.from_queue(q4)
- return sum(
- autograd.numpy.hstack(
- qml.execute([tape1, tape2, tape3, tape4], dev, **execute_kwargs)
- )
- )
-
- params = np.array([0.1, 0.2], requires_grad=True)
- x, y = params
-
- res = cost(params)
- expected = 2 + np.cos(0.5) + np.cos(x) * np.cos(y)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- grad = qml.grad(cost)(params)
- expected = [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)]
- assert np.allclose(grad, expected, atol=tol, rtol=0)
-
- @pytest.mark.filterwarnings("ignore:Attempted to compute the gradient")
- def test_tapes_with_different_return_size(self, execute_kwargs):
- """Test that tapes wit different can be executed and differentiated."""
- dev = qml.device("default.qubit.legacy", wires=2)
-
- def cost(params):
- with qml.queuing.AnnotatedQueue() as q1:
- qml.RY(params[0], wires=0)
- qml.RX(params[1], wires=0)
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliZ(1))
-
- tape1 = qml.tape.QuantumScript.from_queue(q1)
- with qml.queuing.AnnotatedQueue() as q2:
- qml.RY(np.array(0.5, requires_grad=False), wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape2 = qml.tape.QuantumScript.from_queue(q2)
- with qml.queuing.AnnotatedQueue() as q3:
- qml.RY(params[0], wires=0)
- qml.RX(params[1], wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape3 = qml.tape.QuantumScript.from_queue(q3)
- return autograd.numpy.hstack(qml.execute([tape1, tape2, tape3], dev, **execute_kwargs))
-
- params = np.array([0.1, 0.2], requires_grad=True)
-
- res = cost(params)
- assert isinstance(res, np.ndarray)
- assert res.shape == (4,)
-
- jac = qml.jacobian(cost)(params)
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (4, 2)
-
- def test_reusing_quantum_tape(self, execute_kwargs, tol):
- """Test re-using a quantum tape by passing new parameters"""
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=2)
-
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliY(1))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- assert tape.trainable_params == [0, 1]
-
- def cost(a, b):
- new_tape = tape.bind_new_parameters([a, b], [0, 1])
- return autograd.numpy.hstack(qml.execute([new_tape], dev, **execute_kwargs)[0])
-
- jac_fn = qml.jacobian(cost)
- jac = jac_fn(a, b)
-
- a = np.array(0.54, requires_grad=True)
- b = np.array(0.8, requires_grad=True)
-
- # check that the cost function continues to depend on the
- # values of the parameters for subsequent calls
- res2 = cost(2 * a, b)
- expected = [np.cos(2 * a), -np.cos(2 * a) * np.sin(b)]
- assert np.allclose(res2, expected, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(lambda a, b: cost(2 * a, b))
- jac = jac_fn(a, b)
- expected = (
- [-2 * np.sin(2 * a), 2 * np.sin(2 * a) * np.sin(b)],
- [0, -np.cos(2 * a) * np.cos(b)],
- )
- assert isinstance(jac, tuple) and len(jac) == 2
- assert all(np.allclose(_j, _e, atol=tol, rtol=0) for _j, _e in zip(jac, expected))
-
- def test_classical_processing(self, execute_kwargs):
- """Test classical processing within the quantum tape"""
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=False)
- c = np.array(0.3, requires_grad=True)
-
- def cost(a, b, c, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a * c, wires=0)
- qml.RZ(b, wires=0)
- qml.RX(c + c**2 + np.sin(a), wires=0)
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], device, **execute_kwargs)[0]
-
- dev = qml.device("default.qubit.legacy", wires=2)
- res = qml.jacobian(cost)(a, b, c, device=dev)
-
- # Only two arguments are trainable
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == ()
- assert res[1].shape == ()
-
- def test_no_trainable_parameters(self, execute_kwargs):
- """Test evaluation and Jacobian if there are no trainable parameters"""
- a = np.array(0.1, requires_grad=False)
- b = np.array(0.2, requires_grad=False)
-
- def cost(a, b, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliZ(1))
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0])
-
- dev = qml.device("default.qubit.legacy", wires=2)
- res = cost(a, b, device=dev)
- assert res.shape == (2,)
-
- with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"):
- res = qml.jacobian(cost)(a, b, device=dev)
- assert len(res) == 0
-
- def loss(a, b):
- return np.sum(cost(a, b, device=dev))
-
- with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"):
- res = qml.grad(loss)(a, b)
-
- assert np.allclose(res, 0)
-
- def test_matrix_parameter(self, execute_kwargs, tol):
- """Test that the autograd interface works correctly
- with a matrix parameter"""
- U = np.array([[0, 1], [1, 0]], requires_grad=False)
- a = np.array(0.1, requires_grad=True)
-
- def cost(a, U, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.QubitUnitary(U, wires=0)
- qml.RY(a, wires=0)
- qml.expval(qml.PauliZ(0))
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute([tape], device, **execute_kwargs)[0]
-
- dev = qml.device("default.qubit.legacy", wires=2)
- res = cost(a, U, device=dev)
- assert isinstance(res, np.ndarray)
- assert np.allclose(res, -np.cos(a), atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(cost)
- jac = jac_fn(a, U, device=dev)
- assert isinstance(jac, np.ndarray)
- assert np.allclose(jac, np.sin(a), atol=tol, rtol=0)
-
- def test_differentiable_expand(self, execute_kwargs, tol):
- """Test that operation and nested tapes expansion
- is differentiable"""
-
- class U3(qml.U3):
- def decomposition(self):
- theta, phi, lam = self.data
- wires = self.wires
- return [
- qml.Rot(lam, theta, -lam, wires=wires),
- qml.PhaseShift(phi + lam, wires=wires),
- ]
-
- def cost_fn(a, p, device):
- with qml.queuing.AnnotatedQueue() as q_tape:
- qml.RX(a, wires=0)
- U3(*p, wires=0)
- qml.expval(qml.PauliX(0))
-
- tape = qml.tape.QuantumScript.from_queue(q_tape)
- tape = tape.expand(stop_at=lambda obj: device.supports_operation(obj.name))
- return qml.execute([tape], device, **execute_kwargs)[0]
-
- a = np.array(0.1, requires_grad=False)
- p = np.array([0.1, 0.2, 0.3], requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=1)
- res = cost_fn(a, p, device=dev)
- expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * (
- np.cos(p[2]) * np.sin(p[1]) + np.cos(p[0]) * np.cos(p[1]) * np.sin(p[2])
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(cost_fn)
- res = jac_fn(a, p, device=dev)
- expected = np.array(
- [
- np.cos(p[1]) * (np.cos(a) * np.cos(p[0]) - np.sin(a) * np.sin(p[0]) * np.sin(p[2])),
- np.cos(p[1]) * np.cos(p[2]) * np.sin(a)
- - np.sin(p[1])
- * (np.cos(a) * np.sin(p[0]) + np.cos(p[0]) * np.sin(a) * np.sin(p[2])),
- np.sin(a)
- * (np.cos(p[0]) * np.cos(p[1]) * np.cos(p[2]) - np.sin(p[1]) * np.sin(p[2])),
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_probability_differentiation(self, execute_kwargs, tol):
- """Tests correct output shape and evaluation for a tape
- with prob outputs"""
-
- if execute_kwargs["gradient_fn"] == "device":
- pytest.skip("Adjoint differentiation does not yet support probabilities")
-
- def cost(x, y, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- qml.probs(wires=[0])
- qml.probs(wires=[1])
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0])
-
- dev = qml.device("default.qubit.legacy", wires=2)
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- res = cost(x, y, device=dev)
- expected = np.array(
- [
- [
- np.cos(x / 2) ** 2,
- np.sin(x / 2) ** 2,
- (1 + np.cos(x) * np.cos(y)) / 2,
- (1 - np.cos(x) * np.cos(y)) / 2,
- ],
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(cost)
- res = jac_fn(x, y, device=dev)
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == (4,)
- assert res[1].shape == (4,)
-
- expected = (
- np.array(
- [
- [
- -np.sin(x) / 2,
- np.sin(x) / 2,
- -np.sin(x) * np.cos(y) / 2,
- np.sin(x) * np.cos(y) / 2,
- ],
- ]
- ),
- np.array(
- [
- [0, 0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2],
- ]
- ),
- )
-
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def test_ragged_differentiation(self, execute_kwargs, tol):
- """Tests correct output shape and evaluation for a tape
- with prob and expval outputs"""
- if execute_kwargs["gradient_fn"] == "device":
- pytest.skip("Adjoint differentiation does not yet support probabilities")
-
- def cost(x, y, device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
- qml.probs(wires=[1])
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0])
-
- dev = qml.device("default.qubit.legacy", wires=2)
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- res = cost(x, y, device=dev)
- expected = np.array(
- [np.cos(x), (1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(cost)
- res = jac_fn(x, y, device=dev)
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == (3,)
- assert res[1].shape == (3,)
-
- expected = (
- np.array([-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]),
- np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]),
- )
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def test_sampling(self, execute_kwargs):
- """Test sampling works as expected"""
- if execute_kwargs["gradient_fn"] == "device" and (
- execute_kwargs["grad_on_execution"] is True
- or execute_kwargs["gradient_kwargs"]["method"] == "adjoint_jacobian"
- ):
- pytest.skip("Adjoint differentiation does not support samples")
-
- shots = 10
-
- def cost(device):
- with qml.queuing.AnnotatedQueue() as q:
- qml.Hadamard(wires=[0])
- qml.CNOT(wires=[0, 1])
- qml.sample(qml.PauliZ(0))
- qml.sample(qml.PauliX(1))
-
- tape = qml.tape.QuantumScript.from_queue(q, shots=10)
- return qml.execute([tape], device, **execute_kwargs)[0]
-
- dev = qml.device("default.qubit.legacy", wires=2, shots=shots)
- res = cost(device=dev)
- assert isinstance(res, tuple)
- assert len(res) == 2
- assert res[0].shape == (shots,)
- assert res[1].shape == (shots,)
-
-
-class TestHigherOrderDerivatives:
- """Test that the autograd execute function can be differentiated"""
-
- @pytest.mark.parametrize(
- "params",
- [
- np.array([0.543, -0.654], requires_grad=True),
- np.array([0, -0.654], requires_grad=True),
- np.array([-2.0, 0], requires_grad=True),
- ],
- )
- def test_parameter_shift_hessian(self, params, tol):
- """Tests that the output of the parameter-shift transform
- can be differentiated using autograd, yielding second derivatives."""
- dev = qml.device("default.qubit.autograd", wires=2)
-
- def cost_fn(x):
- with qml.queuing.AnnotatedQueue() as q1:
- 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))
-
- tape1 = qml.tape.QuantumScript.from_queue(q1)
- with qml.queuing.AnnotatedQueue() as q2:
- qml.RX(x[0], wires=0)
- qml.RY(x[0], wires=1)
- qml.CNOT(wires=[0, 1])
- qml.probs(wires=1)
-
- tape2 = qml.tape.QuantumScript.from_queue(q2)
- result = qml.execute([tape1, tape2], dev, gradient_fn=param_shift, max_diff=2)
- return result[0] + result[1][0]
-
- res = cost_fn(params)
- x, y = params
- expected = 0.5 * (3 + np.cos(x) ** 2 * np.cos(2 * y))
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.grad(cost_fn)(params)
- expected = np.array(
- [-np.cos(x) * np.cos(2 * y) * np.sin(x), -np.cos(x) ** 2 * np.sin(2 * y)]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.jacobian(qml.grad(cost_fn))(params)
- expected = np.array(
- [
- [-np.cos(2 * x) * np.cos(2 * y), np.sin(2 * x) * np.sin(2 * y)],
- [np.sin(2 * x) * np.sin(2 * y), -2 * np.cos(x) ** 2 * np.cos(2 * y)],
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_adjoint_hessian_one_param(self, tol):
- """Since the adjoint hessian is not a differentiable transform,
- higher-order derivatives are not supported."""
- dev = qml.device("default.qubit.autograd", wires=2)
- params = np.array([0.543, -0.654], requires_grad=True)
-
- def cost_fn(x):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return qml.execute(
- [tape],
- dev,
- gradient_fn="device",
- gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True},
- )[0]
-
- with pytest.warns(UserWarning, match="Output seems independent"):
- res = qml.jacobian(qml.grad(cost_fn))(params)
-
- assert np.allclose(res, np.zeros([2, 2]), atol=tol, rtol=0)
-
- def test_adjoint_hessian_multiple_params(self, tol):
- """Since the adjoint hessian is not a differentiable transform,
- higher-order derivatives are not supported."""
- dev = qml.device("default.qubit.autograd", wires=2)
- params = np.array([0.543, -0.654], requires_grad=True)
-
- def cost_fn(x):
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- qml.expval(qml.PauliZ(0))
- qml.expval(qml.PauliZ(1))
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(
- qml.execute(
- [tape],
- dev,
- gradient_fn="device",
- gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True},
- )[0]
- )
-
- with pytest.warns(UserWarning, match="Output seems independent"):
- res = qml.jacobian(qml.jacobian(cost_fn))(params)
-
- assert np.allclose(res, np.zeros([2, 2, 2]), atol=tol, rtol=0)
-
- def test_max_diff(self, tol):
- """Test that setting the max_diff parameter blocks higher-order
- derivatives"""
- dev = qml.device("default.qubit.legacy", wires=2)
- params = np.array([0.543, -0.654], requires_grad=True)
-
- def cost_fn(x):
- with qml.queuing.AnnotatedQueue() as q1:
- 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))
-
- tape1 = qml.tape.QuantumScript.from_queue(q1)
- with qml.queuing.AnnotatedQueue() as q2:
- qml.RX(x[0], wires=0)
- qml.RY(x[0], wires=1)
- qml.CNOT(wires=[0, 1])
- qml.probs(wires=1)
-
- tape2 = qml.tape.QuantumScript.from_queue(q2)
- result = qml.execute([tape1, tape2], dev, gradient_fn=param_shift, max_diff=1)
- return result[0] + result[1][0]
-
- res = cost_fn(params)
- x, y = params
- expected = 0.5 * (3 + np.cos(x) ** 2 * np.cos(2 * y))
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.grad(cost_fn)(params)
- expected = np.array(
- [-np.cos(x) * np.cos(2 * y) * np.sin(x), -np.cos(x) ** 2 * np.sin(2 * y)]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- with pytest.warns(UserWarning, match="Output seems independent"):
- res = qml.jacobian(qml.grad(cost_fn))(params)
-
- expected = np.zeros([2, 2])
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-execute_kwargs_hamiltonian = [
- {"gradient_fn": param_shift},
- {"gradient_fn": finite_diff},
-]
-
-
-@pytest.mark.parametrize("execute_kwargs", execute_kwargs_hamiltonian)
-class TestHamiltonianWorkflows:
- """Test that tapes ending with expectations
- of Hamiltonians provide correct results and gradients"""
-
- @pytest.fixture
- def cost_fn(self, execute_kwargs):
- """Cost function for gradient tests"""
-
- def _cost_fn(weights, coeffs1, coeffs2, dev=None):
- obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)]
- H1 = qml.Hamiltonian(coeffs1, obs1)
-
- obs2 = [qml.PauliZ(0)]
- H2 = qml.Hamiltonian(coeffs2, obs2)
-
- with qml.queuing.AnnotatedQueue() as q:
- qml.RX(weights[0], wires=0)
- qml.RY(weights[1], wires=1)
- qml.CNOT(wires=[0, 1])
- qml.expval(H1)
- qml.expval(H2)
-
- tape = qml.tape.QuantumScript.from_queue(q)
- return autograd.numpy.hstack(qml.execute([tape], dev, **execute_kwargs)[0])
-
- return _cost_fn
-
- @staticmethod
- def cost_fn_expected(weights, coeffs1, coeffs2):
- """Analytic value of cost_fn above"""
- a, b, c = coeffs1
- d = coeffs2[0]
- x, y = weights
- return [-c * np.sin(x) * np.sin(y) + np.cos(x) * (a + b * np.sin(y)), d * np.cos(x)]
-
- @staticmethod
- def cost_fn_jacobian(weights, coeffs1, coeffs2):
- """Analytic jacobian of cost_fn above"""
- a, b, c = coeffs1
- d = coeffs2[0]
- x, y = weights
- return np.array(
- [
- [
- -c * np.cos(x) * np.sin(y) - np.sin(x) * (a + b * np.sin(y)),
- b * np.cos(x) * np.cos(y) - c * np.cos(y) * np.sin(x),
- np.cos(x),
- np.cos(x) * np.sin(y),
- -(np.sin(x) * np.sin(y)),
- 0,
- ],
- [-d * np.sin(x), 0, 0, 0, 0, np.cos(x)],
- ]
- )
-
- def test_multiple_hamiltonians_not_trainable(self, cost_fn, execute_kwargs, tol):
- """Test hamiltonian with no trainable parameters."""
- # pylint: disable=unused-argument
- coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=False)
- coeffs2 = np.array([0.7], requires_grad=False)
- weights = np.array([0.4, 0.5], requires_grad=True)
- dev = qml.device("default.qubit.legacy", wires=2)
-
- res = cost_fn(weights, coeffs1, coeffs2, dev=dev)
- expected = self.cost_fn_expected(weights, coeffs1, coeffs2)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.jacobian(cost_fn)(weights, coeffs1, coeffs2, dev=dev)
- expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2)[:, :2]
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_multiple_hamiltonians_trainable(self, cost_fn, execute_kwargs, tol):
- """Test hamiltonian with trainable parameters."""
- # pylint: disable=unused-argument
- coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=True)
- coeffs2 = np.array([0.7], requires_grad=True)
- weights = np.array([0.4, 0.5], requires_grad=True)
- dev = qml.device("default.qubit.legacy", wires=2)
-
- res = cost_fn(weights, coeffs1, coeffs2, dev=dev)
- expected = self.cost_fn_expected(weights, coeffs1, coeffs2)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = np.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2, dev=dev))
- expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-class TestCustomJacobian:
- """Test for custom Jacobian."""
-
- def test_custom_jacobians(self):
- """Test custom Jacobian device methood"""
-
- class CustomJacobianDevice(DefaultQubitLegacy):
- @classmethod
- def capabilities(cls):
- capabilities = super().capabilities()
- capabilities["provides_jacobian"] = True
- return capabilities
-
- def jacobian(self, tape):
- # pylint: disable=unused-argument
- return np.array([1.0, 2.0, 3.0, 4.0])
-
- dev = CustomJacobianDevice(wires=2)
-
- @qml.qnode(dev, diff_method="device")
- def circuit(v):
- qml.RX(v, wires=0)
- return qml.probs(wires=[0, 1])
-
- d_circuit = qml.jacobian(circuit, argnum=0)
-
- params = np.array(1.0, requires_grad=True)
-
- d_out = d_circuit(params)
- assert np.allclose(d_out, np.array([1.0, 2.0, 3.0, 4.0]))
-
- def test_custom_jacobians_param_shift(self):
- """Test computing the gradient using the parameter-shift
- rule with a device that provides a jacobian"""
-
- class MyQubit(DefaultQubitLegacy):
- @classmethod
- def capabilities(cls):
- capabilities = super().capabilities().copy()
- capabilities.update(
- provides_jacobian=True,
- )
- return capabilities
-
- def jacobian(self, *args, **kwargs):
- raise NotImplementedError()
-
- dev = MyQubit(wires=2)
-
- @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False)
- def qnode(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- def cost(a, b):
- return autograd.numpy.hstack(qnode(a, b))
-
- res = qml.jacobian(cost)(a, b)
- expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)])
-
- assert np.allclose(res[0], expected[0])
- assert np.allclose(res[1], expected[1])
-
-
-class SpecialObject:
- """SpecialObject
- A special object that conveniently encapsulates the return value of
- a special observable supported by a special device and which supports
- multiplication with scalars and addition.
- """
-
- def __init__(self, val):
- self.val = val
-
- def __mul__(self, other):
- new = SpecialObject(self.val)
- new *= other
- return new
-
- def __imul__(self, other):
- self.val *= other
- return self
-
- def __rmul__(self, other):
- return self * other
-
- def __iadd__(self, other):
- self.val += other.val if isinstance(other, self.__class__) else other
- return self
-
- def __add__(self, other):
- new = SpecialObject(self.val)
- new += other.val if isinstance(other, self.__class__) else other
- return new
-
- def __radd__(self, other):
- return self + other
-
-
-class SpecialObservable(Observable):
- """SpecialObservable"""
-
- num_wires = AnyWires
- num_params = 0
- par_domain = None
-
- def diagonalizing_gates(self):
- """Diagonalizing gates"""
- return []
-
-
-class DeviceSupportingSpecialObservable(DefaultQubitLegacy):
- name = "Device supporting SpecialObservable"
- short_name = "default.qubit.specialobservable"
- observables = DefaultQubitLegacy.observables.union({"SpecialObservable"})
-
- @staticmethod
- def _asarray(arr, dtype=None):
- # pylint: disable=unused-argument
- return arr
-
- @classmethod
- def capabilities(cls):
- capabilities = super().capabilities().copy()
- capabilities.update(
- provides_jacobian=True,
- )
- return capabilities
-
- def expval(self, observable, **kwargs):
- if self.analytic and isinstance(observable, SpecialObservable):
- val = super().expval(qml.PauliZ(wires=0), **kwargs)
- return np.array(SpecialObject(val))
-
- return super().expval(observable, **kwargs)
-
- def jacobian(self, tape):
- # we actually let pennylane do the work of computing the
- # jacobian for us but return it as a device jacobian
- gradient_tapes, fn = qml.gradients.param_shift(tape)
- tape_jacobian = fn(qml.execute(gradient_tapes, self, None))
- return tape_jacobian
-
-
-@pytest.mark.autograd
-class TestObservableWithObjectReturnType:
- """Unit tests for qnode returning a custom object"""
-
- def test_custom_return_type(self):
- """Test custom return values for a qnode"""
-
- dev = DeviceSupportingSpecialObservable(wires=1, shots=None)
-
- # force diff_method='parameter-shift' because otherwise
- # PennyLane swaps out dev for default.qubit.autograd
- @qml.qnode(dev, diff_method="parameter-shift")
- def qnode(x):
- qml.RY(x, wires=0)
- return qml.expval(SpecialObservable(wires=0))
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def reference_qnode(x):
- qml.RY(x, wires=0)
- return qml.expval(qml.PauliZ(wires=0))
-
- out = qnode(0.2)
- assert isinstance(out, np.ndarray)
- assert isinstance(out.item(), SpecialObject)
- assert np.isclose(out.item().val, reference_qnode(0.2))
-
- def test_jacobian_with_custom_return_type(self):
- """Test differentiation of a QNode on a device supporting a
- special observable that returns an object rather than a number."""
-
- dev = DeviceSupportingSpecialObservable(wires=1, shots=None)
-
- # force diff_method='parameter-shift' because otherwise
- # PennyLane swaps out dev for default.qubit.autograd
- @qml.qnode(dev, diff_method="parameter-shift")
- def qnode(x):
- qml.RY(x, wires=0)
- return qml.expval(SpecialObservable(wires=0))
-
- @qml.qnode(dev, diff_method="parameter-shift")
- def reference_qnode(x):
- qml.RY(x, wires=0)
- return qml.expval(qml.PauliZ(wires=0))
-
- reference_jac = (qml.jacobian(reference_qnode)(np.array(0.2, requires_grad=True)),)
-
- assert np.isclose(
- reference_jac,
- qml.jacobian(qnode)(np.array(0.2, requires_grad=True)).item().val,
- )
-
- # now check that also the device jacobian works with a custom return type
- @qml.qnode(dev, diff_method="device")
- def device_gradient_qnode(x):
- qml.RY(x, wires=0)
- return qml.expval(SpecialObservable(wires=0))
-
- assert np.isclose(
- reference_jac,
- qml.jacobian(device_gradient_qnode)(np.array(0.2, requires_grad=True)).item().val,
- )
diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py
deleted file mode 100644
index eb65a655877..00000000000
--- a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py
+++ /dev/null
@@ -1,2356 +0,0 @@
-# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Integration tests for using the autograd interface with a QNode"""
-# pylint: disable=too-many-arguments,too-few-public-methods, use-dict-literal, use-implicit-booleaness-not-comparison,
-# pylint: disable=unnecessary-lambda-assignment
-import autograd
-import autograd.numpy as anp
-import pytest
-
-import pennylane as qml
-from pennylane import numpy as np
-from pennylane import qnode
-
-qubit_device_and_diff_method = [
- ["default.qubit.legacy", "finite-diff", False],
- ["default.qubit.legacy", "parameter-shift", False],
- ["default.qubit.legacy", "backprop", True],
- ["default.qubit.legacy", "adjoint", True],
- ["default.qubit.legacy", "adjoint", False],
- ["default.qubit.legacy", "spsa", False],
- ["default.qubit.legacy", "hadamard", False],
-]
-
-interface_qubit_device_and_diff_method = [
- ["autograd", "default.qubit.legacy", "finite-diff", False],
- ["autograd", "default.qubit.legacy", "parameter-shift", False],
- ["autograd", "default.qubit.legacy", "backprop", True],
- ["autograd", "default.qubit.legacy", "adjoint", True],
- ["autograd", "default.qubit.legacy", "adjoint", False],
- ["autograd", "default.qubit.legacy", "spsa", False],
- ["autograd", "default.qubit.legacy", "hadamard", False],
- ["auto", "default.qubit.legacy", "finite-diff", False],
- ["auto", "default.qubit.legacy", "parameter-shift", False],
- ["auto", "default.qubit.legacy", "backprop", True],
- ["auto", "default.qubit.legacy", "adjoint", True],
- ["auto", "default.qubit.legacy", "adjoint", False],
- ["auto", "default.qubit.legacy", "spsa", False],
- ["auto", "default.qubit.legacy", "hadamard", False],
-]
-
-pytestmark = pytest.mark.autograd
-
-TOL_FOR_SPSA = 1.0
-SEED_FOR_SPSA = 32651
-H_FOR_SPSA = 0.01
-
-
-@pytest.mark.parametrize(
- "interface,dev_name,diff_method,grad_on_execution", interface_qubit_device_and_diff_method
-)
-class TestQNode:
- """Test that using the QNode with Autograd integrates with the PennyLane stack"""
-
- # pylint: disable=unused-argument
-
- def test_nondiff_param_unwrapping(
- self, interface, dev_name, diff_method, grad_on_execution, mocker
- ):
- """Test that non-differentiable parameters are correctly unwrapped
- to NumPy ndarrays or floats (if 0-dimensional)"""
- if diff_method != "parameter-shift":
- pytest.skip("Test only supports parameter-shift")
-
- dev = qml.device("default.qubit.legacy", wires=1)
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit(x, y):
- qml.RX(x[0], wires=0)
- qml.Rot(*x[1:], wires=0)
- qml.RY(y[0], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- x = np.array([0.1, 0.2, 0.3, 0.4], requires_grad=False)
- y = np.array([0.5], requires_grad=True)
-
- param_data = []
-
- def mock_apply(*args, **kwargs):
- for op in args[0]:
- param_data.extend(op.data)
-
- mocker.patch.object(dev.target_device, "apply", side_effect=mock_apply)
- circuit(x, y)
- assert param_data == [0.1, 0.2, 0.3, 0.4, 0.5]
- assert not any(isinstance(p, np.tensor) for p in param_data)
-
- # test the jacobian works correctly
- param_data = []
- qml.grad(circuit)(x, y)
- assert param_data == [
- 0.1,
- 0.2,
- 0.3,
- 0.4,
- 0.5,
- 0.1,
- 0.2,
- 0.3,
- 0.4,
- 0.5 + np.pi / 2,
- 0.1,
- 0.2,
- 0.3,
- 0.4,
- 0.5 - np.pi / 2,
- ]
- assert not any(isinstance(p, np.tensor) for p in param_data)
-
- def test_execution_no_interface(self, interface, dev_name, diff_method, grad_on_execution):
- """Test execution works without an interface"""
- if diff_method == "backprop":
- pytest.skip("Test does not support backprop")
-
- num_wires = 1
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface=None, diff_method=diff_method)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1, requires_grad=True)
-
- res = circuit(a)
-
- # without the interface, the QNode simply returns a scalar array
- assert isinstance(res, np.ndarray)
- assert res.shape == tuple()
-
- # gradients should cause an error
- with pytest.raises(TypeError, match="must be real number, not ArrayBox"):
- qml.grad(circuit)(a)
-
- def test_execution_with_interface(self, interface, dev_name, diff_method, grad_on_execution):
- """Test execution works with the interface"""
- if diff_method == "backprop":
- pytest.skip("Test does not support backprop")
-
- num_wires = 1
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1, requires_grad=True)
- assert circuit.interface == interface
-
- # gradients should work
- grad = qml.grad(circuit)(a)
-
- assert isinstance(grad, float)
- assert grad.shape == tuple()
-
- def test_jacobian(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test jacobian calculation"""
- num_wires = 2
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "spsa":
- spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=10)
- kwargs = {**kwargs, **spsa_kwargs}
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- num_wires = 3
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, **kwargs)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
-
- res = circuit(a, b)
-
- def cost(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- assert circuit.qtape.trainable_params == [0, 1]
- assert isinstance(res, tuple)
- assert len(res) == 2
-
- expected = [np.cos(a), -np.cos(a) * np.sin(b)]
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.jacobian(cost)(a, b)
- assert isinstance(res, tuple) and len(res) == 2
- expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)])
- assert isinstance(res[0], np.ndarray)
- assert res[0].shape == (2,)
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
-
- assert isinstance(res[1], np.ndarray)
- assert res[1].shape == (2,)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def test_jacobian_no_evaluate(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test jacobian calculation when no prior circuit evaluation has been performed"""
- num_wires = 2
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
-
- if diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- num_wires = 3
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, **kwargs)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
-
- def cost(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- jac_fn = qml.jacobian(cost)
- res = jac_fn(a, b)
- expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)])
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- # call the Jacobian with new parameters
- a = np.array(0.6, requires_grad=True)
- b = np.array(0.832, requires_grad=True)
-
- res = jac_fn(a, b)
- expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)])
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def test_jacobian_options(self, interface, dev_name, diff_method, grad_on_execution):
- """Test setting jacobian options"""
- wires = [0]
- if diff_method in ["backprop", "adjoint"]:
- pytest.skip("Test does not support backprop or adjoint method")
- elif diff_method == "finite-diff":
- kwargs = {"h": 1e-8, "approx_order": 2}
- elif diff_method == "parameter-shift":
- kwargs = {"shifts": [(0.1,), (0.2,)]}
- elif diff_method == "hadamard":
- wires = [0, "aux"]
- kwargs = {"aux_wire": qml.wires.Wires("aux"), "device_wires": wires}
- else:
- kwargs = {}
-
- a = np.array([0.1, 0.2], requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=wires)
-
- @qnode(dev, interface=interface, diff_method=diff_method, **kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- circuit(a)
-
- qml.jacobian(circuit)(a)
-
- def test_changing_trainability(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test changing the trainability of parameters changes the
- number of differentiation requests made"""
- if diff_method != "parameter-shift":
- pytest.skip("Test only supports parameter-shift")
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- dev = qml.device("default.qubit.legacy", wires=2)
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
-
- def loss(a, b):
- return np.sum(autograd.numpy.hstack(circuit(a, b)))
-
- grad_fn = qml.grad(loss)
- res = grad_fn(a, b)
-
- # the tape has reported both arguments as trainable
- assert circuit.qtape.trainable_params == [0, 1]
-
- expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- # make the second QNode argument a constant
- a = np.array(0.54, requires_grad=True)
- b = np.array(0.8, requires_grad=False)
-
- res = grad_fn(a, b)
-
- # the tape has reported only the first argument as trainable
- assert circuit.qtape.trainable_params == [0]
-
- expected = [-np.sin(a) + np.sin(a) * np.sin(b)]
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- # trainability also updates on evaluation
- a = np.array(0.54, requires_grad=False)
- b = np.array(0.8, requires_grad=True)
- circuit(a, b)
- assert circuit.qtape.trainable_params == [1]
-
- def test_classical_processing(self, interface, dev_name, diff_method, grad_on_execution):
- """Test classical processing within the quantum tape"""
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=False)
- c = np.array(0.3, requires_grad=True)
-
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(
- dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- def circuit(a, b, c):
- qml.RY(a * c, wires=0)
- qml.RZ(b, wires=0)
- qml.RX(c + c**2 + np.sin(a), wires=0)
- return qml.expval(qml.PauliZ(0))
-
- res = qml.jacobian(circuit)(a, b, c)
-
- assert circuit.qtape.trainable_params == [0, 2]
- tape_params = np.array(circuit.qtape.get_parameters())
- assert np.all(tape_params == [a * c, c + c**2 + np.sin(a)])
-
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == ()
- assert res[1].shape == ()
-
- def test_no_trainable_parameters(self, interface, dev_name, diff_method, grad_on_execution):
- """Test evaluation and Jacobian if there are no trainable parameters"""
- dev = qml.device(dev_name, wires=2)
-
- @qnode(
- dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
-
- a = np.array(0.1, requires_grad=False)
- b = np.array(0.2, requires_grad=False)
-
- res = circuit(a, b)
-
- if diff_method == "finite-diff":
- assert circuit.qtape.trainable_params == []
-
- assert len(res) == 2
- assert isinstance(res, tuple)
-
- def cost0(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"):
- assert not qml.jacobian(cost0)(a, b)
-
- def cost1(a, b):
- return np.sum(circuit(a, b))
-
- with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"):
- grad = qml.grad(cost1)(a, b)
-
- assert grad == tuple()
-
- def test_matrix_parameter(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test that the autograd interface works correctly
- with a matrix parameter"""
- U = np.array([[0, 1], [1, 0]], requires_grad=False)
- a = np.array(0.1, requires_grad=True)
-
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(
- dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- def circuit(U, a):
- qml.QubitUnitary(U, wires=0)
- qml.RY(a, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- res = circuit(U, a)
-
- if diff_method == "finite-diff":
- assert circuit.qtape.trainable_params == [1]
-
- res = qml.grad(circuit)(U, a)
- assert np.allclose(res, np.sin(a), atol=tol, rtol=0)
-
- def test_gradient_non_differentiable_exception(
- self, interface, dev_name, diff_method, grad_on_execution
- ):
- """Test that an exception is raised if non-differentiable data is
- differentiated"""
- dev = qml.device(dev_name, wires=2)
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit(data1):
- qml.templates.AmplitudeEmbedding(data1, wires=[0, 1])
- return qml.expval(qml.PauliZ(0))
-
- grad_fn = qml.grad(circuit, argnum=0)
- data1 = np.array([0, 1, 1, 0], requires_grad=False) / np.sqrt(2)
-
- with pytest.raises(qml.numpy.NonDifferentiableError, match="is non-differentiable"):
- grad_fn(data1)
-
- def test_differentiable_expand(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test that operation and nested tape expansion
- is differentiable"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "spsa":
- spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=20)
- kwargs = {**kwargs, **spsa_kwargs}
- tol = TOL_FOR_SPSA
-
- class U3(qml.U3):
- def decomposition(self):
- theta, phi, lam = self.data
- wires = self.wires
- return [
- qml.Rot(lam, theta, -lam, wires=wires),
- qml.PhaseShift(phi + lam, wires=wires),
- ]
-
- dev = qml.device(dev_name, wires=2)
- a = np.array(0.1, requires_grad=False)
- p = np.array([0.1, 0.2, 0.3], requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(a, p):
- qml.RX(a, wires=0)
- U3(p[0], p[1], p[2], wires=0)
- return qml.expval(qml.PauliX(0))
-
- res = circuit(a, p)
- expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * (
- np.cos(p[2]) * np.sin(p[1]) + np.cos(p[0]) * np.cos(p[1]) * np.sin(p[2])
- )
- assert isinstance(res, np.ndarray)
- assert res.shape == ()
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- res = qml.grad(circuit)(a, p)
-
- assert isinstance(res, np.ndarray)
- assert len(res) == 3
-
- expected = np.array(
- [
- np.cos(p[1]) * (np.cos(a) * np.cos(p[0]) - np.sin(a) * np.sin(p[0]) * np.sin(p[2])),
- np.cos(p[1]) * np.cos(p[2]) * np.sin(a)
- - np.sin(p[1])
- * (np.cos(a) * np.sin(p[0]) + np.cos(p[0]) * np.sin(a) * np.sin(p[2])),
- np.sin(a)
- * (np.cos(p[0]) * np.cos(p[1]) * np.cos(p[2]) - np.sin(p[1]) * np.sin(p[2])),
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-class TestShotsIntegration:
- """Test that the QNode correctly changes shot value, and
- remains differentiable."""
-
- def test_changing_shots(self, mocker, tol):
- """Test that changing shots works on execution"""
- dev = qml.device("default.qubit.legacy", wires=2, shots=None)
- a, b = np.array([0.543, -0.654], requires_grad=True)
-
- @qnode(dev, diff_method=qml.gradients.param_shift)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliY(1))
-
- spy = mocker.spy(dev.target_device, "sample")
-
- # execute with device default shots (None)
- res = circuit(a, b)
- assert np.allclose(res, -np.cos(a) * np.sin(b), atol=tol, rtol=0)
- spy.assert_not_called()
-
- # execute with shots=100
- res = circuit(a, b, shots=100) # pylint: disable=unexpected-keyword-arg
- spy.assert_called_once()
- assert spy.spy_return.shape == (100,)
-
- # device state has been unaffected
- assert not dev.shots
- res = circuit(a, b)
- assert np.allclose(res, -np.cos(a) * np.sin(b), atol=tol, rtol=0)
- spy.assert_called_once() # same single call performed above
-
- @pytest.mark.xfail(reason="Param shift and shot vectors.")
- def test_gradient_integration(self):
- """Test that temporarily setting the shots works
- for gradient computations"""
- dev = qml.device("default.qubit.legacy", wires=2, shots=None)
- a, b = np.array([0.543, -0.654], requires_grad=True)
-
- @qnode(dev, diff_method=qml.gradients.param_shift)
- def cost_fn(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliY(1))
-
- # TODO: fix the shot vectors issue
- res = qml.jacobian(cost_fn)(a, b, shots=[10000, 10000, 10000])
- assert dev.shots is None
- assert isinstance(res, tuple) and len(res) == 2
- assert all(r.shape == (3,) for r in res)
-
- expected = [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
- assert all(
- np.allclose(np.mean(r, axis=0), e, atol=0.1, rtol=0) for r, e in zip(res, expected)
- )
-
- def test_update_diff_method(self, mocker):
- """Test that temporarily setting the shots updates the diff method"""
- dev = qml.device("default.qubit.legacy", wires=2, shots=100)
- a, b = np.array([0.543, -0.654], requires_grad=True)
-
- spy = mocker.spy(qml, "execute")
-
- @qnode(dev)
- def cost_fn(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliY(1))
-
- cost_fn(a, b)
- # since we are using finite shots, parameter-shift will
- # be chosen
- assert cost_fn.gradient_fn is qml.gradients.param_shift
- assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
-
- # if we set the shots to None, backprop can now be used
- cost_fn(a, b, shots=None) # pylint: disable=unexpected-keyword-arg
- assert spy.call_args[1]["gradient_fn"] == "backprop"
- assert cost_fn.gradient_fn == "backprop"
-
- cost_fn(a, b)
- assert cost_fn.gradient_fn is qml.gradients.param_shift
- assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
-
-
-@pytest.mark.parametrize(
- "interface,dev_name,diff_method,grad_on_execution", interface_qubit_device_and_diff_method
-)
-class TestQubitIntegration:
- """Tests that ensure various qubit circuits integrate correctly"""
-
- # pylint: disable=unused-argument
-
- def test_probability_differentiation(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Tests correct output shape and evaluation for a tape
- with a single prob output"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
- elif diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=[1])
-
- res = qml.jacobian(circuit)(x, y)
- assert isinstance(res, tuple) and len(res) == 2
-
- expected = (
- np.array([-np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2]),
- np.array([-np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]),
- )
- assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected))
-
- def test_multiple_probability_differentiation(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Tests correct output shape and evaluation for a tape
- with multiple prob outputs"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
- elif diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=[0]), qml.probs(wires=[1])
-
- res = circuit(x, y)
-
- expected = np.array(
- [
- [np.cos(x / 2) ** 2, np.sin(x / 2) ** 2],
- [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2],
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def cost(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- res = qml.jacobian(cost)(x, y)
-
- assert isinstance(res, tuple) and len(res) == 2
- assert res[0].shape == (4,)
- assert res[1].shape == (4,)
-
- expected = (
- np.array(
- [
- [
- -np.sin(x) / 2,
- np.sin(x) / 2,
- -np.sin(x) * np.cos(y) / 2,
- np.sin(x) * np.cos(y) / 2,
- ],
- ]
- ),
- np.array(
- [
- [0, 0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2],
- ]
- ),
- )
- assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected))
-
- def test_ragged_differentiation(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Tests correct output shape and evaluation for a tape
- with prob and expval outputs"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- num_wires = 2
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
- elif diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[1])
-
- res = circuit(x, y)
- assert isinstance(res, tuple)
- expected = [np.cos(x), [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2]]
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def cost(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- res = qml.jacobian(cost)(x, y)
- assert isinstance(res, tuple)
- assert len(res) == 2
-
- assert res[0].shape == (3,)
- assert res[1].shape == (3,)
-
- expected = (
- np.array([-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]),
- np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]),
- )
- assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
- assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
-
- def test_ragged_differentiation_variance(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Tests correct output shape and evaluation for a tape
- with prob and variance outputs"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
- elif diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- dev = qml.device(dev_name, wires=2)
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0)), qml.probs(wires=[1])
-
- res = circuit(x, y)
-
- expected_var = np.array(np.sin(x) ** 2)
- expected_probs = np.array(
- [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2]
- )
-
- assert isinstance(res, tuple)
- assert len(res) == 2
-
- assert isinstance(res[0], np.ndarray)
- assert res[0].shape == ()
- assert np.allclose(res[0], expected_var, atol=tol, rtol=0)
-
- assert isinstance(res[1], np.ndarray)
- assert res[1].shape == (2,)
- assert np.allclose(res[1], expected_probs, atol=tol, rtol=0)
-
- def cost(x, y):
- return autograd.numpy.hstack(circuit(x, y))
-
- jac = qml.jacobian(cost)(x, y)
- assert isinstance(res, tuple) and len(res) == 2
-
- expected = (
- np.array([np.sin(2 * x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]),
- np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]),
- )
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
-
- assert isinstance(jac[0], np.ndarray)
- assert jac[0].shape == (3,)
- assert np.allclose(jac[0], expected[0], atol=tol, rtol=0)
-
- assert isinstance(jac[1], np.ndarray)
- assert jac[1].shape == (3,)
- assert np.allclose(jac[1], expected[1], atol=tol, rtol=0)
-
- def test_chained_qnodes(self, interface, dev_name, diff_method, grad_on_execution):
- """Test that the gradient of chained QNodes works without error"""
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- class Template(qml.templates.StronglyEntanglingLayers):
- def decomposition(self):
- return [qml.templates.StronglyEntanglingLayers(*self.parameters, self.wires)]
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit1(weights):
- Template(weights, wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
-
- @qnode(dev, interface=interface, diff_method=diff_method)
- def circuit2(data, weights):
- qml.templates.AngleEmbedding(data, wires=[0, 1])
- Template(weights, wires=[0, 1])
- return qml.expval(qml.PauliX(0))
-
- def cost(w1, w2):
- c1 = circuit1(w1)
- c2 = circuit2(c1, w2)
- return np.sum(c2) ** 2
-
- w1 = qml.templates.StronglyEntanglingLayers.shape(n_wires=2, n_layers=3)
- w2 = qml.templates.StronglyEntanglingLayers.shape(n_wires=2, n_layers=4)
-
- weights = [
- np.random.random(w1, requires_grad=True),
- np.random.random(w2, requires_grad=True),
- ]
-
- grad_fn = qml.grad(cost)
- res = grad_fn(*weights)
-
- assert len(res) == 2
-
- def test_chained_gradient_value(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test that the returned gradient value for two chained qubit QNodes
- is correct."""
- kwargs = dict(interface=interface, diff_method=diff_method)
- if diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- num_wires = 3
-
- if diff_method == "hadamard":
- num_wires = 4
-
- dev1 = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev1, **kwargs)
- def circuit1(a, b, c):
- qml.RX(a, wires=0)
- qml.RX(b, wires=1)
- qml.RX(c, wires=2)
- qml.CNOT(wires=[0, 1])
- qml.CNOT(wires=[1, 2])
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(2))
-
- dev2 = qml.device("default.qubit.legacy", wires=num_wires)
-
- @qnode(dev2, interface=interface, diff_method=diff_method)
- def circuit2(data, weights):
- qml.RX(data[0], wires=0)
- qml.RX(data[1], wires=1)
- qml.CNOT(wires=[0, 1])
- qml.RZ(weights[0], wires=0)
- qml.RZ(weights[1], wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliX(0) @ qml.PauliY(1))
-
- def cost(a, b, c, weights):
- return circuit2(circuit1(a, b, c), weights)
-
- grad_fn = qml.grad(cost)
-
- # Set the first parameter of circuit1 as non-differentiable.
- a = np.array(0.4, requires_grad=False)
-
- # The remaining free parameters are all differentiable.
- b = np.array(0.5, requires_grad=True)
- c = np.array(0.1, requires_grad=True)
- weights = np.array([0.2, 0.3], requires_grad=True)
-
- res = grad_fn(a, b, c, weights)
-
- # Output should have shape [dcost/db, dcost/dc, dcost/dw],
- # where b,c are scalars, and w is a vector of length 2.
- assert len(res) == 3
- assert res[0].shape == tuple() # scalar
- assert res[1].shape == tuple() # scalar
- assert res[2].shape == (2,) # vector
-
- cacbsc = np.cos(a) * np.cos(b) * np.sin(c)
-
- expected = np.array(
- [
- # analytic expression for dcost/db
- -np.cos(a)
- * np.sin(b)
- * np.sin(c)
- * np.cos(cacbsc)
- * np.sin(weights[0])
- * np.sin(np.cos(a)),
- # analytic expression for dcost/dc
- np.cos(a)
- * np.cos(b)
- * np.cos(c)
- * np.cos(cacbsc)
- * np.sin(weights[0])
- * np.sin(np.cos(a)),
- # analytic expression for dcost/dw[0]
- np.sin(cacbsc) * np.cos(weights[0]) * np.sin(np.cos(a)),
- # analytic expression for dcost/dw[1]
- 0,
- ]
- )
-
- # np.hstack 'flattens' the ragged gradient array allowing it
- # to be compared with the expected result
- assert np.allclose(np.hstack(res), expected, atol=tol, rtol=0)
-
- if diff_method != "backprop":
- # Check that the gradient was computed
- # for all parameters in circuit2
- assert circuit2.qtape.trainable_params == [0, 1, 2, 3]
-
- # Check that the parameter-shift rule was not applied
- # to the first parameter of circuit1.
- assert circuit1.qtape.trainable_params == [1, 2]
-
- def test_second_derivative(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test second derivative calculation of a scalar valued QNode"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RY(x[0], wires=0)
- qml.RX(x[1], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- x = np.array([1.0, 2.0], requires_grad=True)
- res = circuit(x)
- g = qml.grad(circuit)(x)
- g2 = qml.grad(lambda x: np.sum(qml.grad(circuit)(x)))(x)
-
- a, b = x
-
- expected_res = np.cos(a) * np.cos(b)
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)]
- assert np.allclose(g, expected_g, atol=tol, rtol=0)
-
- expected_g2 = [
- -np.cos(a) * np.cos(b) + np.sin(a) * np.sin(b),
- np.sin(a) * np.sin(b) - np.cos(a) * np.cos(b),
- ]
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(g2, expected_g2, atol=tol, rtol=0)
-
- def test_hessian(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test hessian calculation of a scalar valued QNode"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RY(x[0], wires=0)
- qml.RX(x[1], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- x = np.array([1.0, 2.0], requires_grad=True)
- res = circuit(x)
-
- a, b = x
-
- expected_res = np.cos(a) * np.cos(b)
-
- assert isinstance(res, np.ndarray)
- assert res.shape == ()
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- grad_fn = qml.grad(circuit)
- g = grad_fn(x)
-
- expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)]
-
- assert isinstance(g, np.ndarray)
- assert g.shape == (2,)
- assert np.allclose(g, expected_g, atol=tol, rtol=0)
-
- hess = qml.jacobian(grad_fn)(x)
-
- expected_hess = [
- [-np.cos(a) * np.cos(b), np.sin(a) * np.sin(b)],
- [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)],
- ]
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (2, 2)
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
-
- def test_hessian_unused_parameter(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Test hessian calculation of a scalar valued QNode"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RY(x[0], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- x = np.array([1.0, 2.0], requires_grad=True)
- res = circuit(x)
-
- a, _ = x
-
- expected_res = np.cos(a)
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- grad_fn = qml.grad(circuit)
-
- hess = qml.jacobian(grad_fn)(x)
-
- expected_hess = [
- [-np.cos(a), 0],
- [0, 0],
- ]
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
-
- def test_hessian_vector_valued(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test hessian calculation of a vector valued QNode"""
-
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RY(x[0], wires=0)
- qml.RX(x[1], wires=0)
- return qml.probs(wires=0)
-
- x = np.array([1.0, 2.0], requires_grad=True)
- res = circuit(x)
-
- a, b = x
-
- expected_res = [0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b)]
-
- assert isinstance(res, np.ndarray)
- assert res.shape == (2,)
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(circuit)
- jac = jac_fn(x)
-
- expected_res = [
- [-0.5 * np.sin(a) * np.cos(b), -0.5 * np.cos(a) * np.sin(b)],
- [0.5 * np.sin(a) * np.cos(b), 0.5 * np.cos(a) * np.sin(b)],
- ]
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (2, 2)
- assert np.allclose(jac, expected_res, atol=tol, rtol=0)
-
- hess = qml.jacobian(jac_fn)(x)
-
- expected_hess = [
- [
- [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)],
- [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)],
- ],
- [
- [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)],
- [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)],
- ],
- ]
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (2, 2, 2)
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
-
- def test_hessian_vector_valued_postprocessing(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Test hessian calculation of a vector valued QNode with post-processing"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RX(x[0], wires=0)
- qml.RY(x[1], wires=0)
- return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(0))
-
- def cost_fn(x):
- return x @ autograd.numpy.hstack(circuit(x))
-
- x = np.array([0.76, -0.87], requires_grad=True)
- res = cost_fn(x)
-
- a, b = x
-
- expected_res = x @ [np.cos(a) * np.cos(b), np.cos(a) * np.cos(b)]
-
- assert isinstance(res, np.ndarray)
- assert res.shape == ()
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- hess = qml.jacobian(qml.grad(cost_fn))(x)
-
- expected_hess = [
- [
- -(np.cos(b) * ((a + b) * np.cos(a) + 2 * np.sin(a))),
- -(np.cos(b) * np.sin(a)) + (-np.cos(a) + (a + b) * np.sin(a)) * np.sin(b),
- ],
- [
- -(np.cos(b) * np.sin(a)) + (-np.cos(a) + (a + b) * np.sin(a)) * np.sin(b),
- -(np.cos(a) * ((a + b) * np.cos(b) + 2 * np.sin(b))),
- ],
- ]
-
- assert hess.shape == (2, 2)
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
-
- def test_hessian_vector_valued_separate_args(
- self, interface, dev_name, diff_method, grad_on_execution, tol
- ):
- """Test hessian calculation of a vector valued QNode that has separate input arguments"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=1)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.probs(wires=0)
-
- a = np.array(1.0, requires_grad=True)
- b = np.array(2.0, requires_grad=True)
- res = circuit(a, b)
-
- expected_res = [0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b)]
- assert isinstance(res, np.ndarray)
- assert res.shape == (2,)
- assert np.allclose(res, expected_res, atol=tol, rtol=0)
-
- jac_fn = qml.jacobian(circuit)
- g = jac_fn(a, b)
- assert isinstance(g, tuple) and len(g) == 2
-
- expected_g = (
- [-0.5 * np.sin(a) * np.cos(b), 0.5 * np.sin(a) * np.cos(b)],
- [-0.5 * np.cos(a) * np.sin(b), 0.5 * np.cos(a) * np.sin(b)],
- )
- assert g[0].shape == (2,)
- assert np.allclose(g[0], expected_g[0], atol=tol, rtol=0)
-
- assert g[1].shape == (2,)
- assert np.allclose(g[1], expected_g[1], atol=tol, rtol=0)
-
- jac_fn_a = lambda *args: jac_fn(*args)[0]
- jac_fn_b = lambda *args: jac_fn(*args)[1]
- hess_a = qml.jacobian(jac_fn_a)(a, b)
- hess_b = qml.jacobian(jac_fn_b)(a, b)
- assert isinstance(hess_a, tuple) and len(hess_a) == 2
- assert isinstance(hess_b, tuple) and len(hess_b) == 2
-
- exp_hess_a = (
- [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.cos(a) * np.cos(b)],
- [0.5 * np.sin(a) * np.sin(b), -0.5 * np.sin(a) * np.sin(b)],
- )
- exp_hess_b = (
- [0.5 * np.sin(a) * np.sin(b), -0.5 * np.sin(a) * np.sin(b)],
- [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.cos(a) * np.cos(b)],
- )
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- for hess, exp_hess in zip([hess_a, hess_b], [exp_hess_a, exp_hess_b]):
- assert np.allclose(hess[0], exp_hess[0], atol=tol, rtol=0)
- assert np.allclose(hess[1], exp_hess[1], atol=tol, rtol=0)
-
- def test_hessian_ragged(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test hessian calculation of a ragged QNode"""
- if diff_method not in {"parameter-shift", "backprop"}:
- pytest.skip("Test only supports parameter-shift or backprop")
-
- dev = qml.device(dev_name, wires=2)
-
- @qnode(
- dev,
- diff_method=diff_method,
- interface=interface,
- grad_on_execution=grad_on_execution,
- max_diff=2,
- )
- def circuit(x):
- qml.RY(x[0], wires=0)
- qml.RX(x[1], wires=0)
- qml.RY(x[0], wires=1)
- qml.RX(x[1], wires=1)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=1)
-
- x = np.array([1.0, 2.0], requires_grad=True)
-
- a, b = x
-
- cos_prod = np.cos(a) * np.cos(b)
- expected_res = (cos_prod, [0.5 + 0.5 * cos_prod, 0.5 - 0.5 * cos_prod])
- res = circuit(x)
- assert all(qml.math.allclose(r, e) for r, e in zip(res, expected_res))
-
- def cost_fn(x):
- return autograd.numpy.hstack(circuit(x))
-
- jac_fn = qml.jacobian(cost_fn)
-
- hess = qml.jacobian(jac_fn)(x)
- expected_hess = [
- [
- [-np.cos(a) * np.cos(b), np.sin(a) * np.sin(b)],
- [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)],
- ],
- [
- [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)],
- [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)],
- ],
- [
- [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)],
- [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)],
- ],
- ]
-
- if diff_method in {"finite-diff"}:
- tol = 10e-2
-
- assert np.allclose(hess, expected_hess, atol=tol, rtol=0)
-
- def test_state(self, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test that the state can be returned and differentiated"""
- if diff_method == "adjoint":
- pytest.skip("Adjoint does not support states")
-
- dev = qml.device(dev_name, wires=2)
-
- x = np.array(0.543, requires_grad=True)
- y = np.array(-0.654, requires_grad=True)
-
- @qnode(
- dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.state()
-
- def cost_fn(x, y):
- res = circuit(x, y)
- assert res.dtype is np.dtype("complex128")
- probs = np.abs(res) ** 2
- return probs[0] + probs[2]
-
- res = cost_fn(x, y)
-
- if diff_method not in {"backprop"}:
- pytest.skip("Test only supports backprop")
-
- res = qml.jacobian(cost_fn)(x, y)
- expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
- assert isinstance(res, tuple)
- assert len(res) == 2
-
- assert isinstance(res[0], np.ndarray)
- assert res[0].shape == ()
- assert isinstance(res[1], np.ndarray)
- assert res[1].shape == ()
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
- def test_projector(self, state, interface, dev_name, diff_method, grad_on_execution, tol):
- """Test that the variance of a projector is correctly returned"""
- kwargs = dict(
- diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
- )
- if diff_method == "adjoint":
- pytest.skip("Adjoint does not support projectors")
- elif diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- dev = qml.device(dev_name, wires=2)
- P = np.array(state, requires_grad=False)
- x, y = np.array([0.765, -0.654], requires_grad=True)
-
- @qnode(dev, **kwargs)
- def circuit(x, y):
- qml.RX(x, wires=0)
- qml.RY(y, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.Projector(P, wires=0) @ qml.PauliX(1))
-
- res = circuit(x, y)
- expected = 0.25 * np.sin(x / 2) ** 2 * (3 + np.cos(2 * y) + 2 * np.cos(x) * np.sin(y) ** 2)
- assert isinstance(res, np.ndarray)
- assert res.shape == ()
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- jac = qml.jacobian(circuit)(x, y)
- expected = np.array(
- [
- [
- 0.5 * np.sin(x) * (np.cos(x / 2) ** 2 + np.cos(2 * y) * np.sin(x / 2) ** 2),
- -2 * np.cos(y) * np.sin(x / 2) ** 4 * np.sin(y),
- ]
- ]
- )
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
-
- assert isinstance(jac[0], np.ndarray)
- assert jac[0].shape == ()
-
- assert isinstance(jac[1], np.ndarray)
- assert jac[1].shape == ()
-
- assert np.allclose(jac, expected, atol=tol, rtol=0)
-
-
-@pytest.mark.parametrize(
- "diff_method,kwargs",
- [
- ["finite-diff", {}],
- ["spsa", {"num_directions": 100, "h": 0.05}],
- ("parameter-shift", {}),
- ("parameter-shift", {"force_order2": True}),
- ],
-)
-class TestCV:
- """Tests for CV integration"""
-
- def test_first_order_observable(self, diff_method, kwargs, tol):
- """Test variance of a first order CV observable"""
- dev = qml.device("default.gaussian", wires=1)
- if diff_method == "spsa":
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- tol = TOL_FOR_SPSA
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- r = np.array(0.543, requires_grad=True)
- phi = np.array(-0.654, requires_grad=True)
-
- @qnode(dev, diff_method=diff_method, **kwargs)
- def circuit(r, phi):
- qml.Squeezing(r, 0, wires=0)
- qml.Rotation(phi, wires=0)
- return qml.var(qml.QuadX(0))
-
- res = circuit(r, phi)
- 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)
-
- # circuit jacobians
- res = qml.jacobian(circuit)(r, phi)
- expected = np.array(
- [
- [
- 2 * np.exp(2 * r) * np.sin(phi) ** 2 - 2 * np.exp(-2 * r) * np.cos(phi) ** 2,
- 2 * np.sinh(2 * r) * np.sin(2 * phi),
- ]
- ]
- )
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- def test_second_order_observable(self, diff_method, kwargs, tol):
- """Test variance of a second order CV expectation value"""
- dev = qml.device("default.gaussian", wires=1)
- if diff_method == "spsa":
- tol = TOL_FOR_SPSA
- kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA)
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- n = np.array(0.12, requires_grad=True)
- a = np.array(0.765, requires_grad=True)
-
- @qnode(dev, diff_method=diff_method, **kwargs)
- def circuit(n, a):
- qml.ThermalState(n, wires=0)
- qml.Displacement(a, 0, wires=0)
- return qml.var(qml.NumberOperator(0))
-
- res = circuit(n, a)
- expected = n**2 + n + np.abs(a) ** 2 * (1 + 2 * n)
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
- # circuit jacobians
- res = qml.jacobian(circuit)(n, a)
- expected = np.array([[2 * a**2 + 2 * n + 1, 2 * a * (2 * n + 1)]])
- assert np.allclose(res, expected, atol=tol, rtol=0)
-
-
-def test_adjoint_reuse_device_state(mocker):
- """Tests that the autograd interface reuses the device state for adjoint differentiation"""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- @qnode(dev, diff_method="adjoint")
- def circ(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- spy = mocker.spy(dev.target_device, "adjoint_jacobian")
-
- qml.grad(circ, argnum=0)(1.0)
- assert circ.device.num_executions == 1
-
- spy.assert_called_with(mocker.ANY, use_device_state=True)
-
-
-@pytest.mark.parametrize("dev_name,diff_method,grad_on_execution", qubit_device_and_diff_method)
-class TestTapeExpansion:
- """Test that tape expansion within the QNode integrates correctly
- with the Autograd interface"""
-
- @pytest.mark.parametrize("max_diff", [1, 2])
- def test_gradient_expansion_trainable_only(
- self, dev_name, diff_method, grad_on_execution, max_diff
- ):
- """Test that a *supported* operation with no gradient recipe is only
- expanded for parameter-shift and finite-differences when it is trainable."""
- if diff_method not in ("parameter-shift", "finite-diff", "spsa", "hadamard"):
- pytest.skip("Only supports gradient transforms")
- if max_diff == 2 and diff_method == "hadamard":
- pytest.skip("Max diff > 1 not supported for Hadamard gradient.")
-
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- class PhaseShift(qml.PhaseShift):
- grad_method = None
-
- def decomposition(self):
- return [qml.RY(3 * self.data[0], wires=self.wires)]
-
- @qnode(dev, diff_method=diff_method, grad_on_execution=grad_on_execution, max_diff=max_diff)
- def circuit(x, y):
- qml.Hadamard(wires=0)
- PhaseShift(x, wires=0)
- PhaseShift(2 * y, wires=0)
- return qml.expval(qml.PauliX(0))
-
- x = np.array(0.5, requires_grad=True)
- y = np.array(0.7, requires_grad=False)
- circuit(x, y)
-
- qml.grad(circuit)(x, y)
-
- @pytest.mark.parametrize("max_diff", [1, 2])
- def test_hamiltonian_expansion_analytic(
- self, dev_name, diff_method, grad_on_execution, max_diff, tol
- ):
- """Test that if there are non-commuting groups and the number of shots is None
- the first and second order gradients are correctly evaluated"""
- kwargs = dict(
- diff_method=diff_method,
- grad_on_execution=grad_on_execution,
- max_diff=max_diff,
- )
- if diff_method in ["adjoint", "hadamard"]:
- pytest.skip("The diff method requested does not yet support Hamiltonians")
- elif diff_method == "spsa":
- tol = TOL_FOR_SPSA
- spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=10)
- kwargs = {**kwargs, **spsa_kwargs}
-
- dev = qml.device(dev_name, wires=3, shots=None)
- obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)]
-
- @qnode(dev, **kwargs)
- def circuit(data, weights, coeffs):
- weights = weights.reshape(1, -1)
- qml.templates.AngleEmbedding(data, wires=[0, 1])
- qml.templates.BasicEntanglerLayers(weights, wires=[0, 1])
- return qml.expval(qml.Hamiltonian(coeffs, obs))
-
- d = np.array([0.1, 0.2], requires_grad=False)
- w = np.array([0.654, -0.734], requires_grad=True)
- c = np.array([-0.6543, 0.24, 0.54], requires_grad=True)
-
- # test output
- res = circuit(d, w, c)
- expected = c[2] * np.cos(d[1] + w[1]) - c[1] * np.sin(d[0] + w[0]) * np.sin(d[1] + w[1])
- assert np.allclose(res, expected, atol=tol)
-
- # test gradients
- grad = qml.grad(circuit)(d, w, c)
- expected_w = [
- -c[1] * np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]),
- -c[1] * np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]) - c[2] * np.sin(d[1] + w[1]),
- ]
- expected_c = [0, -np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]), np.cos(d[1] + w[1])]
- assert np.allclose(grad[0], expected_w, atol=tol)
- assert np.allclose(grad[1], expected_c, atol=tol)
-
- # test second-order derivatives
- if diff_method in ("parameter-shift", "backprop") and max_diff == 2:
- if diff_method == "backprop":
- with pytest.warns(UserWarning, match=r"Output seems independent of input."):
- grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c)
- else:
- grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c)
- assert np.allclose(grad2_c, 0, atol=tol)
-
- grad2_w_c = qml.jacobian(qml.grad(circuit, argnum=1), argnum=2)(d, w, c)
- expected = [0, -np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), 0], [
- 0,
- -np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]),
- -np.sin(d[1] + w[1]),
- ]
- assert np.allclose(grad2_w_c, expected, atol=tol)
-
- @pytest.mark.slow
- @pytest.mark.parametrize("max_diff", [1, 2])
- def test_hamiltonian_expansion_finite_shots(
- self, dev_name, diff_method, grad_on_execution, max_diff, mocker
- ):
- """Test that the Hamiltonian is expanded if there
- are non-commuting groups and the number of shots is finite
- and the first and second order gradients are correctly evaluated"""
- gradient_kwargs = {}
- tol = 0.3
- if diff_method in ("adjoint", "backprop", "hadamard"):
- pytest.skip("The adjoint and backprop methods do not yet support sampling")
- elif diff_method == "spsa":
- gradient_kwargs = dict(
- h=H_FOR_SPSA,
- sampler_rng=np.random.default_rng(SEED_FOR_SPSA),
- num_directions=20,
- )
- tol = TOL_FOR_SPSA
- elif diff_method == "finite-diff":
- gradient_kwargs = {"h": 0.05}
-
- dev = qml.device(dev_name, wires=3, shots=50000)
- spy = mocker.spy(qml.transforms, "split_non_commuting")
- obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)]
-
- @qnode(
- dev,
- diff_method=diff_method,
- grad_on_execution=grad_on_execution,
- max_diff=max_diff,
- **gradient_kwargs,
- )
- def circuit(data, weights, coeffs):
- weights = weights.reshape(1, -1)
- qml.templates.AngleEmbedding(data, wires=[0, 1])
- qml.templates.BasicEntanglerLayers(weights, wires=[0, 1])
- H = qml.Hamiltonian(coeffs, obs)
- H.compute_grouping()
- return qml.expval(H)
-
- d = np.array([0.1, 0.2], requires_grad=False)
- w = np.array([0.654, -0.734], requires_grad=True)
- c = np.array([-0.6543, 0.24, 0.54], requires_grad=True)
-
- # test output
- res = circuit(d, w, c)
- expected = c[2] * np.cos(d[1] + w[1]) - c[1] * np.sin(d[0] + w[0]) * np.sin(d[1] + w[1])
- assert np.allclose(res, expected, atol=tol)
- spy.assert_called()
-
- # test gradients
- grad = qml.grad(circuit)(d, w, c)
- expected_w = [
- -c[1] * np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]),
- -c[1] * np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]) - c[2] * np.sin(d[1] + w[1]),
- ]
- expected_c = [0, -np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]), np.cos(d[1] + w[1])]
- assert np.allclose(grad[0], expected_w, atol=tol)
- assert np.allclose(grad[1], expected_c, atol=tol)
-
- # test second-order derivatives
- if diff_method == "parameter-shift" and max_diff == 2:
- grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c)
-
- assert np.allclose(grad2_c, 0, atol=tol)
-
- grad2_w_c = qml.jacobian(qml.grad(circuit, argnum=1), argnum=2)(d, w, c)
- expected = [0, -np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), 0], [
- 0,
- -np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]),
- -np.sin(d[1] + w[1]),
- ]
- assert np.allclose(grad2_w_c, expected, atol=tol)
-
-
-class TestSample:
- """Tests for the sample integration"""
-
- def test_backprop_error(self):
- """Test that sampling in backpropagation grad_on_execution raises an error"""
- dev = qml.device("default.qubit.legacy", wires=2)
-
- @qnode(dev, diff_method="backprop")
- def circuit():
- qml.RX(0.54, wires=0)
- return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliX(1))
-
- with pytest.raises(
- qml.QuantumFunctionError, match="does not support backprop with requested circuit"
- ):
- circuit(shots=10) # pylint: disable=unexpected-keyword-arg
-
- def test_sample_dimension(self):
- """Test that the sample function outputs samples of the right size"""
- n_sample = 10
-
- dev = qml.device("default.qubit.legacy", wires=2, shots=n_sample)
-
- @qnode(dev)
- def circuit():
- qml.RX(0.54, wires=0)
- return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliX(1))
-
- res = circuit()
-
- assert isinstance(res, tuple)
- assert len(res) == 2
-
- assert res[0].shape == (10,)
- assert isinstance(res[0], np.ndarray)
-
- assert res[1].shape == (10,)
- assert isinstance(res[1], np.ndarray)
-
- def test_sample_combination(self):
- """Test the output of combining expval, var and sample"""
-
- n_sample = 10
-
- dev = qml.device("default.qubit.legacy", wires=3, shots=n_sample)
-
- @qnode(dev, diff_method="parameter-shift")
- def circuit():
- qml.RX(0.54, wires=0)
-
- return qml.sample(qml.PauliZ(0)), qml.expval(qml.PauliX(1)), qml.var(qml.PauliY(2))
-
- result = circuit()
-
- assert isinstance(result, tuple)
- assert len(result) == 3
-
- assert np.array_equal(result[0].shape, (n_sample,))
- assert isinstance(result[1], np.ndarray)
- assert isinstance(result[2], np.ndarray)
- assert result[0].dtype == np.dtype("float")
-
- def test_single_wire_sample(self):
- """Test the return type and shape of sampling a single wire"""
- n_sample = 10
-
- dev = qml.device("default.qubit.legacy", wires=1, shots=n_sample)
-
- @qnode(dev)
- def circuit():
- qml.RX(0.54, wires=0)
-
- return qml.sample(qml.PauliZ(0))
-
- result = circuit()
-
- assert isinstance(result, np.ndarray)
- assert np.array_equal(result.shape, (n_sample,))
-
- def test_multi_wire_sample_regular_shape(self):
- """Test the return type and shape of sampling multiple wires
- where a rectangular array is expected"""
- n_sample = 10
-
- dev = qml.device("default.qubit.legacy", wires=3, shots=n_sample)
-
- @qnode(dev)
- def circuit():
- return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)), qml.sample(qml.PauliZ(2))
-
- result = circuit()
-
- # If all the dimensions are equal the result will end up to be a proper rectangular array
- assert isinstance(result, tuple)
- assert len(result) == 3
-
- assert result[0].shape == (10,)
- assert isinstance(result[0], np.ndarray)
-
- assert result[1].shape == (10,)
- assert isinstance(result[1], np.ndarray)
-
- assert result[2].shape == (10,)
- assert isinstance(result[2], np.ndarray)
-
-
-@pytest.mark.parametrize("dev_name,diff_method,grad_on_execution", qubit_device_and_diff_method)
-class TestReturn:
- """Class to test the shape of the Grad/Jacobian/Hessian with different return types."""
-
- # pylint: disable=unused-argument
-
- def test_grad_single_measurement_param(self, dev_name, diff_method, grad_on_execution):
- """For one measurement and one param, the gradient is a float."""
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1, requires_grad=True)
-
- grad = qml.grad(circuit)(a)
-
- import sys
-
- python_version = sys.version_info.minor
- if diff_method == "backprop" and python_version > 7:
- # Since numpy 1.23.0
- assert isinstance(grad, np.ndarray)
- else:
- assert isinstance(grad, float)
-
- def test_grad_single_measurement_multiple_param(self, dev_name, diff_method, grad_on_execution):
- """For one measurement and multiple param, the gradient is a tuple of arrays."""
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- grad = qml.grad(circuit)(a, b)
-
- assert isinstance(grad, tuple)
- assert len(grad) == 2
- assert grad[0].shape == ()
- assert grad[1].shape == ()
-
- def test_grad_single_measurement_multiple_param_array(
- self, dev_name, diff_method, grad_on_execution
- ):
- """For one measurement and multiple param as a single array params, the gradient is an array."""
- num_wires = 1
-
- if diff_method == "hadamard":
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array([0.1, 0.2], requires_grad=True)
-
- grad = qml.grad(circuit)(a)
-
- assert isinstance(grad, np.ndarray)
- assert len(grad) == 2
- assert grad.shape == (2,)
-
- def test_jacobian_single_measurement_param_probs(
- self, dev_name, diff_method, grad_on_execution
- ):
- """For a multi dimensional measurement (probs), check that a single array is returned with the correct
- dimension"""
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array(0.1, requires_grad=True)
-
- jac = qml.jacobian(circuit)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (4,)
-
- def test_jacobian_single_measurement_probs_multiple_param(
- self, dev_name, diff_method, grad_on_execution
- ):
- """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with
- the correct dimension"""
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- jac = qml.jacobian(circuit)(a, b)
-
- assert isinstance(jac, tuple)
-
- assert isinstance(jac[0], np.ndarray)
- assert jac[0].shape == (4,)
-
- assert isinstance(jac[1], np.ndarray)
- assert jac[1].shape == (4,)
-
- def test_jacobian_single_measurement_probs_multiple_param_single_array(
- self, dev_name, diff_method, grad_on_execution
- ):
- """For a multi dimensional measurement (probs), check that a single array is returned."""
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array([0.1, 0.2], requires_grad=True)
- jac = qml.jacobian(circuit)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (4, 2)
-
- def test_jacobian_multiple_measurement_single_param(
- self, dev_name, diff_method, grad_on_execution
- ):
- """The jacobian of multiple measurements with a single params return an array."""
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array(0.1, requires_grad=True)
-
- def cost(x):
- return anp.hstack(circuit(x))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (5,)
-
- def test_jacobian_multiple_measurement_multiple_param(
- self, dev_name, diff_method, grad_on_execution
- ):
- """The jacobian of multiple measurements with a multiple params return a tuple of arrays."""
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- def cost(x, y):
- return anp.hstack(circuit(x, y))
-
- jac = qml.jacobian(cost)(a, b)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
-
- assert isinstance(jac[0], np.ndarray)
- assert jac[0].shape == (5,)
-
- assert isinstance(jac[1], np.ndarray)
- assert jac[1].shape == (5,)
-
- def test_jacobian_multiple_measurement_multiple_param_array(
- self, dev_name, diff_method, grad_on_execution
- ):
- """The jacobian of multiple measurements with a multiple params array return a single array."""
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support returning probabilities")
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 3
-
- dev = qml.device(dev_name, wires=num_wires)
-
- @qnode(dev, interface="autograd", diff_method=diff_method)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array([0.1, 0.2], requires_grad=True)
-
- def cost(x):
- return anp.hstack(circuit(x))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (5, 2)
-
- def test_hessian_expval_multiple_params(self, dev_name, diff_method, grad_on_execution):
- """The hessian of single a measurement with multiple params return a tuple of arrays."""
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 4
-
- dev = qml.device(dev_name, wires=num_wires)
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
-
- par_0 = qml.numpy.array(0.1, requires_grad=True)
- par_1 = qml.numpy.array(0.2, requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x, y):
- return anp.hstack(qml.grad(circuit)(x, y))
-
- hess = qml.jacobian(cost)(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
-
- assert isinstance(hess[0], np.ndarray)
- assert hess[0].shape == (2,)
-
- assert isinstance(hess[1], np.ndarray)
- assert hess[1].shape == (2,)
-
- def test_hessian_expval_multiple_param_array(self, dev_name, diff_method, grad_on_execution):
- """The hessian of single measurement with a multiple params array return a single array."""
-
- num_wires = 2
-
- if diff_method == "hadamard":
- num_wires = 4
-
- dev = qml.device(dev_name, wires=num_wires)
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
-
- params = qml.numpy.array([0.1, 0.2], requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
-
- hess = qml.jacobian(qml.grad(circuit))(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (2, 2)
-
- def test_hessian_var_multiple_params(self, dev_name, diff_method, grad_on_execution):
- """The hessian of single a measurement with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2)
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- par_0 = qml.numpy.array(0.1, requires_grad=True)
- par_1 = qml.numpy.array(0.2, requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x, y):
- return anp.hstack(qml.grad(circuit)(x, y))
-
- hess = qml.jacobian(cost)(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
-
- assert isinstance(hess[0], np.ndarray)
- assert hess[0].shape == (2,)
-
- assert isinstance(hess[1], np.ndarray)
- assert hess[1].shape == (2,)
-
- def test_hessian_var_multiple_param_array(self, dev_name, diff_method, grad_on_execution):
- """The hessian of single measurement with a multiple params array return a single array."""
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- dev = qml.device(dev_name, wires=2)
-
- params = qml.numpy.array([0.1, 0.2], requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1))
-
- hess = qml.jacobian(qml.grad(circuit))(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (2, 2)
-
- def test_hessian_probs_expval_multiple_params(self, dev_name, diff_method, grad_on_execution):
- """The hessian of multiple measurements with multiple params return a tuple of arrays."""
- num_wires = 2
-
- dev = qml.device(dev_name, wires=num_wires)
-
- if diff_method in ["adjoint", "hadamard"]:
- pytest.skip("The adjoint method does not currently support second-order diff.")
-
- par_0 = qml.numpy.array(0.1, requires_grad=True)
- par_1 = qml.numpy.array(0.2, requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def circuit_stack(x, y):
- return anp.hstack(circuit(x, y))
-
- def cost(x, y):
- return anp.hstack(qml.jacobian(circuit_stack)(x, y))
-
- hess = qml.jacobian(cost)(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
-
- assert isinstance(hess[0], np.ndarray)
- assert hess[0].shape == (6,)
-
- assert isinstance(hess[1], np.ndarray)
- assert hess[1].shape == (6,)
-
- def test_hessian_expval_probs_multiple_param_array(
- self, dev_name, diff_method, grad_on_execution
- ):
- """The hessian of multiple measurements with a multiple param array return a single array."""
-
- if diff_method in ["adjoint", "hadamard"]:
- pytest.skip("The adjoint method does not currently support second-order diff.")
-
- dev = qml.device(dev_name, wires=2)
-
- params = qml.numpy.array([0.1, 0.2], requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def cost(x):
- return anp.hstack(circuit(x))
-
- hess = qml.jacobian(qml.jacobian(cost))(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (3, 2, 2)
-
- def test_hessian_probs_var_multiple_params(self, dev_name, diff_method, grad_on_execution):
- """The hessian of multiple measurements with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2)
-
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- par_0 = qml.numpy.array(0.1, requires_grad=True)
- par_1 = qml.numpy.array(0.2, requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def circuit_stack(x, y):
- return anp.hstack(circuit(x, y))
-
- def cost(x, y):
- return anp.hstack(qml.jacobian(circuit_stack)(x, y))
-
- hess = qml.jacobian(cost)(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
-
- assert isinstance(hess[0], np.ndarray)
- assert hess[0].shape == (6,)
-
- assert isinstance(hess[1], np.ndarray)
- assert hess[1].shape == (6,)
-
- def test_hessian_var_probs_multiple_param_array(self, dev_name, diff_method, grad_on_execution):
- """The hessian of multiple measurements with a multiple param array return a single array."""
- if diff_method == "adjoint":
- pytest.skip("The adjoint method does not currently support second-order diff.")
- elif diff_method == "hadamard":
- pytest.skip("Hadamard gradient does not support variances.")
-
- dev = qml.device(dev_name, wires=2)
-
- params = qml.numpy.array([0.1, 0.2], requires_grad=True)
-
- @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def cost(x):
- return anp.hstack(circuit(x))
-
- hess = qml.jacobian(qml.jacobian(cost))(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (3, 2, 2)
-
-
-@pytest.mark.parametrize("dev_name", ["default.qubit.legacy", "default.mixed"])
-def test_no_ops(dev_name):
- """Test that the return value of the QNode matches in the interface
- even if there are no ops"""
-
- dev = qml.device(dev_name, wires=1)
-
- @qml.qnode(dev, interface="autograd")
- def circuit():
- qml.Hadamard(wires=0)
- return qml.state()
-
- res = circuit()
- assert isinstance(res, np.tensor)
diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py
deleted file mode 100644
index 24d1d824ced..00000000000
--- a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py
+++ /dev/null
@@ -1,658 +0,0 @@
-# Copyright 2022 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.
-"""Integration tests for using the Autograd interface with shot vectors and with a QNode"""
-# pylint: disable=too-many-arguments,redefined-outer-name
-
-import pytest
-
-import pennylane as qml
-from pennylane import numpy as np
-from pennylane import qnode
-
-pytestmark = pytest.mark.autograd
-
-shots_and_num_copies = [(((5, 2), 1, 10), 4), ((1, 10, (5, 2)), 4)]
-shots_and_num_copies_hess = [(((5, 1), 10), 2), ((10, (5, 1)), 2)]
-
-
-kwargs = {
- "finite-diff": {"h": 0.05},
- "parameter-shift": {},
- "spsa": {"h": 0.05, "num_directions": 20},
-}
-
-qubit_device_and_diff_method = [
- ["default.qubit.legacy", "finite-diff"],
- ["default.qubit.legacy", "parameter-shift"],
- ["default.qubit.legacy", "spsa"],
-]
-
-TOLS = {
- "finite-diff": 0.3,
- "parameter-shift": 1e-2,
- "spsa": 0.3,
-}
-
-
-@pytest.fixture
-def gradient_kwargs(request):
- diff_method = request.node.funcargs["diff_method"]
- return kwargs[diff_method] | (
- {"sampler_rng": np.random.default_rng(42)} if diff_method == "spsa" else {}
- )
-
-
-@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies)
-@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method)
-class TestReturnWithShotVectors:
- """Class to test the shape of the Jacobian/Hessian with different return types and shot vectors."""
-
- def test_jac_single_measurement_param(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For one measurement and one param, the gradient is a float."""
- dev = qml.device(dev_name, wires=1, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1)
-
- def cost(a):
- return qml.math.stack(circuit(a))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies,)
-
- def test_jac_single_measurement_multiple_param(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For one measurement and multiple param, the gradient is a tuple of arrays."""
- dev = qml.device(dev_name, wires=1, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array(0.1)
- b = np.array(0.2)
-
- def cost(a, b):
- return qml.math.stack(circuit(a, b))
-
- jac = qml.jacobian(cost, argnum=[0, 1])(a, b)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
- for j in jac:
- assert isinstance(j, np.ndarray)
- assert j.shape == (num_copies,)
-
- def test_jacobian_single_measurement_multiple_param_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For one measurement and multiple param as a single array params, the gradient is an array."""
- dev = qml.device(dev_name, wires=1, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.expval(qml.PauliZ(0))
-
- a = np.array([0.1, 0.2])
-
- def cost(a):
- return qml.math.stack(circuit(a))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 2)
-
- def test_jacobian_single_measurement_param_probs(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For a multi dimensional measurement (probs), check that a single array is returned with the correct
- dimension"""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array(0.1)
-
- def cost(a):
- return qml.math.stack(circuit(a))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 4)
-
- def test_jacobian_single_measurement_probs_multiple_param(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with
- the correct dimension"""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array(0.1)
- b = np.array(0.2)
-
- def cost(a, b):
- return qml.math.stack(circuit(a, b))
-
- jac = qml.jacobian(cost, argnum=[0, 1])(a, b)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
- for j in jac:
- assert isinstance(j, np.ndarray)
- assert j.shape == (num_copies, 4)
-
- def test_jacobian_single_measurement_probs_multiple_param_single_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with
- the correct dimension"""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.probs(wires=[0, 1])
-
- a = np.array([0.1, 0.2])
-
- def cost(a):
- return qml.math.stack(circuit(a))
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 4, 2)
-
- def test_jacobian_expval_expval_multiple_params(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The gradient of multiple measurements with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- par_0 = np.array(0.1)
- par_1 = np.array(0.2)
-
- @qnode(dev, diff_method=diff_method, max_diff=1, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0))
-
- def cost(x, y):
- res = circuit(x, y)
- return qml.math.stack([qml.math.stack(r) for r in res])
-
- jac = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
- for j in jac:
- assert isinstance(j, np.ndarray)
- assert j.shape == (num_copies, 2)
-
- def test_jacobian_expval_expval_multiple_params_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with a multiple params array return a single array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.RY(a[2], wires=0)
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0))
-
- a = np.array([0.1, 0.2, 0.3])
-
- def cost(a):
- res = circuit(a)
- return qml.math.stack([qml.math.stack(r) for r in res])
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 2, 3)
-
- def test_jacobian_var_var_multiple_params(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- par_0 = np.array(0.1)
- par_1 = np.array(0.2)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.var(qml.PauliZ(0))
-
- def cost(x, y):
- res = circuit(x, y)
- return qml.math.stack([qml.math.stack(r) for r in res])
-
- jac = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
- for j in jac:
- assert isinstance(j, np.ndarray)
- assert j.shape == (num_copies, 2)
-
- def test_jacobian_var_var_multiple_params_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with a multiple params array return a single array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- qml.RY(a[2], wires=0)
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.var(qml.PauliZ(0))
-
- a = np.array([0.1, 0.2, 0.3])
-
- def cost(a):
- res = circuit(a)
- return qml.math.stack([qml.math.stack(r) for r in res])
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 2, 3)
-
- def test_jacobian_multiple_measurement_single_param(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with a single params return an array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a, wires=0)
- qml.RX(0.2, wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array(0.1)
-
- def cost(a):
- res = circuit(a)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 5)
-
- def test_jacobian_multiple_measurement_multiple_param(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with a multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a, b):
- qml.RY(a, wires=0)
- qml.RX(b, wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array(0.1, requires_grad=True)
- b = np.array(0.2, requires_grad=True)
-
- def cost(a, b):
- res = circuit(a, b)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- jac = qml.jacobian(cost, argnum=[0, 1])(a, b)
-
- assert isinstance(jac, tuple)
- assert len(jac) == 2
- for j in jac:
- assert isinstance(j, np.ndarray)
- assert j.shape == (num_copies, 5)
-
- def test_jacobian_multiple_measurement_multiple_param_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The jacobian of multiple measurements with a multiple params array return a single array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(a):
- qml.RY(a[0], wires=0)
- qml.RX(a[1], wires=0)
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- a = np.array([0.1, 0.2])
-
- def cost(a):
- res = circuit(a)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- jac = qml.jacobian(cost)(a)
-
- assert isinstance(jac, np.ndarray)
- assert jac.shape == (num_copies, 5, 2)
-
-
-@pytest.mark.slow
-@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies_hess)
-@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method)
-class TestReturnShotVectorHessian:
- """Class to test the shape of the Hessian with different return types and shot vectors."""
-
- def test_hessian_expval_multiple_params(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of a single measurement with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- par_0 = np.array(0.1)
- par_1 = np.array(0.2)
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x, y):
- def cost2(x, y):
- res = circuit(x, y)
- return qml.math.stack(res)
-
- return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y))
-
- hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
- for h in hess:
- assert isinstance(h, np.ndarray)
- assert h.shape == (2, num_copies)
-
- def test_hessian_expval_multiple_param_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of single measurement with a multiple params array return a single array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- params = np.array([0.1, 0.2])
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x):
- def cost2(x):
- res = circuit(x)
- return qml.math.stack(res)
-
- return qml.jacobian(cost2)(x)
-
- hess = qml.jacobian(cost)(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (num_copies, 2, 2)
-
- def test_hessian_var_multiple_params(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of a single measurement with multiple params return a tuple of arrays."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- par_0 = np.array(0.1)
- par_1 = np.array(0.2)
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x, y):
- def cost2(x, y):
- res = circuit(x, y)
- return qml.math.stack(res)
-
- return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y))
-
- hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
- for h in hess:
- assert isinstance(h, np.ndarray)
- assert h.shape == (2, num_copies)
-
- def test_hessian_var_multiple_param_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of single measurement with a multiple params array return a single array."""
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- params = np.array([0.1, 0.2])
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.var(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x):
- def cost2(x):
- res = circuit(x)
- return qml.math.stack(res)
-
- return qml.jacobian(cost2)(x)
-
- hess = qml.jacobian(cost)(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (num_copies, 2, 2)
-
- def test_hessian_probs_expval_multiple_params(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of multiple measurements with multiple params return a tuple of arrays."""
- if diff_method == "spsa":
- pytest.skip("SPSA does not support iterated differentiation in Autograd.")
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- par_0 = np.array(0.1)
- par_1 = np.array(0.2)
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def cost(x, y):
- def cost2(x, y):
- res = circuit(x, y)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y))
-
- hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1)
-
- assert isinstance(hess, tuple)
- assert len(hess) == 2
- for h in hess:
- assert isinstance(h, np.ndarray)
- assert h.shape == (2, num_copies, 3)
-
- def test_hessian_expval_probs_multiple_param_array(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """The hessian of multiple measurements with a multiple param array return a single array."""
- if diff_method == "spsa":
- pytest.skip("SPSA does not support iterated differentiation in Autograd.")
-
- dev = qml.device(dev_name, wires=2, shots=shots)
-
- params = np.array([0.1, 0.2])
-
- @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs)
- def circuit(x):
- qml.RX(x[0], wires=[0])
- qml.RY(x[1], wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
-
- def cost(x):
- def cost2(x):
- res = circuit(x)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- return qml.jacobian(cost2)(x)
-
- hess = qml.jacobian(cost)(params)
-
- assert isinstance(hess, np.ndarray)
- assert hess.shape == (num_copies, 3, 2, 2)
-
-
-shots_and_num_copies = [((30000, 28000, 26000), 3), ((30000, (28000, 2)), 3)]
-
-
-@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies)
-@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method)
-class TestReturnShotVectorIntegration:
- """Tests for the integration of shots with the autograd interface."""
-
- def test_single_expectation_value(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """Tests correct output shape and evaluation for a tape
- with a single expval output"""
- dev = qml.device(dev_name, wires=2, shots=shots)
- x = np.array(0.543)
- y = np.array(-0.654)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
-
- def cost(x, y):
- res = circuit(x, y)
- return qml.math.stack(res)
-
- all_res = qml.jacobian(cost, argnum=[0, 1])(x, y)
-
- assert isinstance(all_res, tuple)
- assert len(all_res) == 2
-
- expected = np.array([-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)])
- tol = TOLS[diff_method]
-
- for res, exp in zip(all_res, expected):
- assert isinstance(res, np.ndarray)
- assert res.shape == (num_copies,)
- assert np.allclose(res, exp, atol=tol, rtol=0)
-
- def test_prob_expectation_values(
- self, dev_name, diff_method, gradient_kwargs, shots, num_copies
- ):
- """Tests correct output shape and evaluation for a tape
- with prob and expval outputs"""
- dev = qml.device(dev_name, wires=2, shots=shots)
- x = np.array(0.543)
- y = np.array(-0.654)
-
- @qnode(dev, diff_method=diff_method, **gradient_kwargs)
- def circuit(x, y):
- qml.RX(x, wires=[0])
- qml.RY(y, wires=[1])
- qml.CNOT(wires=[0, 1])
- return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
-
- def cost(x, y):
- res = circuit(x, y)
- return qml.math.stack([qml.math.hstack(r) for r in res])
-
- all_res = qml.jacobian(cost, argnum=[0, 1])(x, y)
-
- assert isinstance(all_res, tuple)
- assert len(all_res) == 2
-
- expected = np.array(
- [
- [
- -np.sin(x),
- -(np.cos(y / 2) ** 2 * np.sin(x)) / 2,
- -(np.sin(x) * np.sin(y / 2) ** 2) / 2,
- (np.sin(x) * np.sin(y / 2) ** 2) / 2,
- (np.cos(y / 2) ** 2 * np.sin(x)) / 2,
- ],
- [
- 0,
- -(np.cos(x / 2) ** 2 * np.sin(y)) / 2,
- (np.cos(x / 2) ** 2 * np.sin(y)) / 2,
- (np.sin(x / 2) ** 2 * np.sin(y)) / 2,
- -(np.sin(x / 2) ** 2 * np.sin(y)) / 2,
- ],
- ]
- )
-
- tol = TOLS[diff_method]
-
- for res, exp in zip(all_res, expected):
- assert isinstance(res, np.ndarray)
- assert res.shape == (num_copies, 5)
- assert np.allclose(res, exp, atol=tol, rtol=0)
diff --git a/tests/interfaces/legacy_devices_integration/test_set_shots_legacy.py b/tests/interfaces/legacy_devices_integration/test_set_shots_legacy.py
index 6e9739a631f..619bd74a066 100644
--- a/tests/interfaces/legacy_devices_integration/test_set_shots_legacy.py
+++ b/tests/interfaces/legacy_devices_integration/test_set_shots_legacy.py
@@ -14,7 +14,7 @@
"""
Tests for workflow.set_shots
"""
-
+import pytest
import pennylane as qml
from pennylane.measurements import Shots
@@ -24,16 +24,18 @@
def test_set_with_shots_class():
"""Test that shots can be set on the old device interface with a Shots class."""
- dev = qml.devices.DefaultQubitLegacy(wires=1)
- with set_shots(dev, Shots(10)):
- assert dev.shots == 10
+ dev = qml.devices.DefaultMixed(wires=1)
+ with pytest.warns(qml.PennyLaneDeprecationWarning):
+ with set_shots(dev, Shots(10)):
+ assert dev.shots == 10
assert dev.shots is None
shot_tuples = Shots((10, 10))
- with set_shots(dev, shot_tuples):
- assert dev.shots == 20
- assert dev.shot_vector == list(shot_tuples.shot_vector)
+ with pytest.warns(qml.PennyLaneDeprecationWarning):
+ with set_shots(dev, shot_tuples):
+ assert dev.shots == 20
+ assert dev.shot_vector == list(shot_tuples.shot_vector)
assert dev.shots is None
@@ -42,6 +44,7 @@ def test_shots_not_altered_if_False():
"""Test a value of False can be passed to shots, indicating to not override
shots on the device."""
- dev = qml.devices.DefaultQubitLegacy(wires=1)
- with set_shots(dev, False):
- assert dev.shots is None
+ dev = qml.devices.DefaultMixed(wires=1)
+ with pytest.warns(qml.PennyLaneDeprecationWarning):
+ with set_shots(dev, False):
+ assert dev.shots is None
diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py
index 3db765be87d..129ab56dfe8 100644
--- a/tests/interfaces/test_autograd_qnode.py
+++ b/tests/interfaces/test_autograd_qnode.py
@@ -539,17 +539,26 @@ def cost_fn(a, b):
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliY(1))
- assert cost_fn.gradient_fn == "backprop" # gets restored to default
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == "backprop" # gets restored to default
cost_fn(a, b, shots=100)
# since we are using finite shots, parameter-shift will
# be chosen
assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
- assert cost_fn.gradient_fn is qml.gradients.param_shift
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn is qml.gradients.param_shift
# if we use the default shots value of None, backprop can now be used
cost_fn(a, b)
- assert cost_fn.gradient_fn == "backprop"
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == "backprop"
assert spy.call_args[1]["gradient_fn"] == "backprop"
diff --git a/tests/interfaces/test_execute.py b/tests/interfaces/test_execute.py
index 18003f95586..1732f741d8a 100644
--- a/tests/interfaces/test_execute.py
+++ b/tests/interfaces/test_execute.py
@@ -13,6 +13,7 @@
# limitations under the License.
"""Tests for exeuction with default qubit 2 independent of any interface."""
+import numpy as np
import pytest
import pennylane as qml
@@ -42,3 +43,15 @@ def test_caching(gradient_fn):
assert tracker.totals["batches"] == 1
assert tracker.totals["executions"] == 1
assert cache[qs.hash] == -1.0
+
+
+def test_execute_legacy_device():
+ """Test that qml.execute works when passed a legacy device class."""
+
+ dev = qml.devices.DefaultMixed(wires=2)
+
+ tape = qml.tape.QuantumScript([qml.RX(0.1, 0)], [qml.expval(qml.Z(0))])
+
+ res = qml.execute((tape,), dev)
+
+ assert qml.math.allclose(res[0], np.cos(0.1))
diff --git a/tests/interfaces/test_jacobian_products.py b/tests/interfaces/test_jacobian_products.py
index 2fc275558e7..3d758c38642 100644
--- a/tests/interfaces/test_jacobian_products.py
+++ b/tests/interfaces/test_jacobian_products.py
@@ -129,21 +129,6 @@ def test_device_jacobians_initialization_new_dev(self):
assert isinstance(jpc._jacs_cache, LRUCache)
assert len(jpc._jacs_cache) == 0
- def test_device_jacobians_initialization_old_dev(self):
- """Test the private attributes are set during initialization of a DeviceDerivatives class with the
- old device interface."""
-
- device = qml.devices.DefaultQubitLegacy(wires=5)
-
- jpc = DeviceDerivatives(device, aj_config)
-
- assert jpc._device is device
- assert jpc._execution_config == aj_config
- assert isinstance(jpc._results_cache, LRUCache)
- assert len(jpc._results_cache) == 0
- assert isinstance(jpc._jacs_cache, LRUCache)
- assert len(jpc._jacs_cache) == 0
-
def test_device_jacobians_repr(self):
"""Test the repr method for device jacobians."""
device = qml.device("default.qubit")
diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py
index aa110444248..f50b2b8311f 100644
--- a/tests/interfaces/test_jax_jit_qnode.py
+++ b/tests/interfaces/test_jax_jit_qnode.py
@@ -848,11 +848,17 @@ def cost_fn(a, b):
cost_fn(a, b, shots=100) # pylint:disable=unexpected-keyword-arg
# since we are using finite shots, parameter-shift will
# be chosen
- assert cost_fn.gradient_fn == qml.gradients.param_shift
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == qml.gradients.param_shift
assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
cost_fn(a, b)
- assert cost_fn.gradient_fn == "backprop"
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == "backprop"
# if we set the shots to None, backprop can now be used
assert spy.call_args[1]["gradient_fn"] == "backprop"
diff --git a/tests/interfaces/test_jax_qnode.py b/tests/interfaces/test_jax_qnode.py
index 3ea650f96e8..d24dec3383d 100644
--- a/tests/interfaces/test_jax_qnode.py
+++ b/tests/interfaces/test_jax_qnode.py
@@ -779,12 +779,18 @@ def cost_fn(a, b):
cost_fn(a, b, shots=100)
# since we are using finite shots, parameter-shift will
# be chosen
- assert cost_fn.gradient_fn == qml.gradients.param_shift
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == qml.gradients.param_shift
assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
# if we use the default shots value of None, backprop can now be used
cost_fn(a, b)
- assert cost_fn.gradient_fn == "backprop"
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == "backprop"
assert spy.call_args[1]["gradient_fn"] == "backprop"
diff --git a/tests/interfaces/test_tensorflow_qnode.py b/tests/interfaces/test_tensorflow_qnode.py
index e3c4597ae06..c09f1632202 100644
--- a/tests/interfaces/test_tensorflow_qnode.py
+++ b/tests/interfaces/test_tensorflow_qnode.py
@@ -540,12 +540,18 @@ def circuit(weights):
circuit(weights, shots=100) # pylint:disable=unexpected-keyword-arg
# since we are using finite shots, parameter-shift will
# be chosen
- assert circuit.gradient_fn == qml.gradients.param_shift
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert circuit.gradient_fn == qml.gradients.param_shift
assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
# if we use the default shots value of None, backprop can now be used
circuit(weights)
- assert circuit.gradient_fn == "backprop"
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert circuit.gradient_fn == "backprop"
assert spy.call_args[1]["gradient_fn"] == "backprop"
diff --git a/tests/interfaces/test_torch_qnode.py b/tests/interfaces/test_torch_qnode.py
index 35854f13694..82dbda669d4 100644
--- a/tests/interfaces/test_torch_qnode.py
+++ b/tests/interfaces/test_torch_qnode.py
@@ -642,7 +642,10 @@ def cost_fn(a, b):
cost_fn(a, b, shots=100)
# since we are using finite shots, parameter-shift will
# be chosen
- assert cost_fn.gradient_fn == qml.gradients.param_shift
+ with pytest.warns(
+ qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated"
+ ):
+ assert cost_fn.gradient_fn == qml.gradients.param_shift
assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift
# if we use the default shots value of None, backprop can now be used
diff --git a/tests/logging/test_logging_autograd.py b/tests/logging/test_logging_autograd.py
index 01d637d025d..8ae7a9baaa2 100644
--- a/tests/logging/test_logging_autograd.py
+++ b/tests/logging/test_logging_autograd.py
@@ -69,7 +69,7 @@ def circuit(params):
return qml.expval(qml.PauliZ(0))
circuit(params)
- assert len(caplog.records) == 8
+ assert len(caplog.records) == 9
log_records_expected = [
(
"pennylane.workflow.qnode",
@@ -79,6 +79,10 @@ def circuit(params):
"pennylane.workflow.qnode",
["Calling "
)
- @pytest.mark.autograd
- def test_diff_method_none(self, tol):
- """Test that diff_method=None creates a QNode with no interface, and no
- device swapping."""
- dev = qml.device("default.qubit.legacy", wires=1)
-
- @qnode(dev, diff_method=None)
- def circuit(x):
- qml.RX(x, wires=0)
- return qml.expval(qml.PauliZ(0))
-
- assert circuit.interface is None
- assert circuit.gradient_fn is None
- assert circuit.device is dev
-
# QNode can still be executed
- assert np.allclose(circuit(0.5), np.cos(0.5), atol=tol, rtol=0)
+ assert np.allclose(qn(0.5), np.cos(0.5), rtol=0)
with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"):
- grad = qml.grad(circuit)(0.5)
+ grad = qml.grad(qn)(0.5)
assert np.allclose(grad, 0)
@@ -545,29 +440,6 @@ def func(x, y):
assert np.allclose(res, res2, atol=tol, rtol=0)
assert qn.qtape is not old_tape
- def test_jacobian(self):
- """Test the jacobian computation"""
- dev = qml.device("default.qubit.legacy", wires=2)
-
- def func(x, y):
- qml.RX(x, wires=0)
- qml.RY(y, wires=1)
- qml.CNOT(wires=[0, 1])
- return qml.probs(wires=0), qml.probs(wires=1)
-
- qn = QNode(
- func, dev, interface="autograd", diff_method="finite-diff", h=1e-8, approx_order=2
- )
- assert qn.gradient_kwargs["h"] == 1e-8
- assert qn.gradient_kwargs["approx_order"] == 2
-
- jac = qn.gradient_fn(qn)(
- pnp.array(0.45, requires_grad=True), pnp.array(0.1, requires_grad=True)
- )
- assert isinstance(jac, tuple) and len(jac) == 2
- assert len(jac[0]) == 2
- assert len(jac[1]) == 2
-
def test_returning_non_measurements(self):
"""Test that an exception is raised if a non-measurement
is returned from the QNode."""
@@ -941,7 +813,9 @@ def test_no_defer_measurements_if_supported(self, mocker):
"""Test that the defer_measurements transform is not used during
QNode construction if the device supports mid-circuit measurements."""
dev = qml.device("default.qubit.legacy", wires=3)
- mocker.patch.object(qml.Device, "_capabilities", {"supports_mid_measure": True})
+ mocker.patch.object(
+ qml.devices.LegacyDevice, "_capabilities", {"supports_mid_measure": True}
+ )
spy = mocker.spy(qml.defer_measurements, "_transform")
@qml.qnode(dev)
diff --git a/tests/test_return_types_qnode.py b/tests/test_return_types_qnode.py
index 6f74f84bb14..364eb468922 100644
--- a/tests/test_return_types_qnode.py
+++ b/tests/test_return_types_qnode.py
@@ -2204,7 +2204,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2232,7 +2234,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2268,7 +2272,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2295,7 +2301,9 @@ def circuit(x):
all_shot_copies = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2326,7 +2334,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2358,7 +2368,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2395,7 +2407,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2427,7 +2441,9 @@ def circuit(x):
all_shot_copies = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2455,7 +2471,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2552,7 +2570,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2584,7 +2604,9 @@ def test_scalar_sample_with_obs(self, shot_vector, meas1, meas2, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2601,7 +2623,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2642,7 +2666,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2656,7 +2682,9 @@ def circuit(x):
for m in measurement_res
)
- for shot_tuple in dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector:
+ for shot_tuple in (
+ dev.shot_vector if isinstance(dev, qml.devices.LegacyDevice) else dev.shots.shot_vector
+ ):
for idx in range(shot_tuple.copies):
for i, r in enumerate(res[idx]):
if i % 2 == 0 or shot_tuple.shots == 1:
@@ -2674,7 +2702,9 @@ def test_scalar_counts_with_obs(self, shot_vector, meas1, meas2, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2691,7 +2721,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2726,7 +2758,9 @@ def test_scalar_counts_no_obs(self, shot_vector, meas1, meas2, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2743,7 +2777,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2774,7 +2810,9 @@ def test_probs_sample(self, shot_vector, sample_obs, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2799,7 +2837,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2835,7 +2875,9 @@ def test_probs_counts(self, shot_vector, sample_obs, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2860,7 +2902,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2896,7 +2940,9 @@ def test_sample_counts(self, shot_vector, sample_wires, counts_wires, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2927,7 +2973,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
@@ -2960,7 +3008,9 @@ def test_scalar_probs_sample_counts(self, shot_vector, meas1, meas2, device):
raw_shot_vector = [
shot_tuple.shots
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
for _ in range(shot_tuple.copies)
]
@@ -2983,7 +3033,9 @@ def circuit(x):
[
shot_tuple.copies
for shot_tuple in (
- dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector
+ dev.shot_vector
+ if isinstance(dev, qml.devices.LegacyDevice)
+ else dev.shots.shot_vector
)
]
)
diff --git a/tests/test_vqe.py b/tests/test_vqe.py
index e8edc6c84cf..ca7ea9a9e38 100644
--- a/tests/test_vqe.py
+++ b/tests/test_vqe.py
@@ -193,33 +193,6 @@ def amp_embed_and_strong_ent_layer(params, wires=None):
(amp_embed_and_strong_ent_layer, (EMBED_PARAMS, LAYER_PARAMS)),
]
-#####################################################
-# Device
-
-
-@pytest.fixture(scope="function", name="mock_device")
-def mock_device_fixture(monkeypatch):
- with monkeypatch.context() as m:
- m.setattr(qml.Device, "__abstractmethods__", frozenset())
- m.setattr(
- qml.Device, "_capabilities", {"supports_tensor_observables": True, "model": "qubit"}
- )
- m.setattr(qml.Device, "operations", ["RX", "RY", "Rot", "CNOT", "Hadamard", "StatePrep"])
- m.setattr(
- qml.Device, "observables", ["PauliX", "PauliY", "PauliZ", "Hadamard", "Hermitian"]
- )
- m.setattr(qml.Device, "short_name", "MockDevice")
- m.setattr(qml.Device, "expval", lambda self, x, y, z: 1)
- m.setattr(qml.Device, "var", lambda self, x, y, z: 2)
- m.setattr(qml.Device, "sample", lambda self, x, y, z: 3)
- m.setattr(qml.Device, "apply", lambda self, x, y, z: None)
-
- def get_device(wires=1):
- return qml.Device(wires=wires) # pylint:disable=abstract-class-instantiated
-
- yield get_device
-
-
#####################################################
# Queues
diff --git a/tests/transforms/test_diagonalize_measurements.py b/tests/transforms/test_diagonalize_measurements.py
index a6774662d0b..9c6c1fa7929 100644
--- a/tests/transforms/test_diagonalize_measurements.py
+++ b/tests/transforms/test_diagonalize_measurements.py
@@ -35,6 +35,8 @@
null_postprocessing,
)
+# pylint: disable=protected-access
+
class TestDiagonalizeObservable:
"""Tests for the _diagonalize_observable method"""
@@ -310,10 +312,15 @@ def test_diagonalize_all_measurements(self, to_eigvals):
new_tape = tapes[0]
if to_eigvals:
- assert new_tape.measurements == [
- ExpectationMP(eigvals=[1.0, -1.0], wires=[0]),
- VarianceMP(eigvals=[2.0, 0.0, 0.0, -2.0], wires=[1, 2]),
- ]
+ assert len(new_tape.measurements) == 2
+ assert isinstance(new_tape.measurements[0], ExpectationMP)
+ assert isinstance(new_tape.measurements[1], VarianceMP)
+ assert new_tape.measurements[0].wires == qml.wires.Wires([0])
+ assert new_tape.measurements[1].wires == qml.wires.Wires([1, 2])
+ assert qml.math.allclose(sorted(new_tape.measurements[0]._eigvals), [-1.0, 1.0])
+ assert qml.math.allclose(
+ sorted(new_tape.measurements[1]._eigvals), [-2.0, 0.0, 0.0, 2.0]
+ )
else:
assert new_tape.measurements == [qml.expval(Z(0)), qml.var(Z(1) + Z(2))]
assert new_tape.operations == diagonalize_qwc_pauli_words([X(0), X(1), Y(2)])[0]
@@ -439,11 +446,16 @@ def test_with_duplicate_measurements(self, to_eigvals, supported_base_obs):
new_tape = tapes[0]
if to_eigvals:
- assert new_tape.measurements == [
- ExpectationMP(eigvals=[1.0, -1], wires=[0]),
- VarianceMP(eigvals=[2.0, 0.0, 0.0, -2.0], wires=[1, 2]),
- SampleMP(eigvals=[1.0, -1.0, -1.0, 1.0], wires=[0, 2]),
- ]
+ assert len(new_tape.measurements) == 3
+ assert isinstance(new_tape.measurements[0], ExpectationMP)
+ assert isinstance(new_tape.measurements[1], VarianceMP)
+ assert isinstance(new_tape.measurements[2], SampleMP)
+ assert new_tape.measurements[0].wires == qml.wires.Wires([0])
+ assert new_tape.measurements[1].wires == qml.wires.Wires([1, 2])
+ assert new_tape.measurements[2].wires == qml.wires.Wires([0, 2])
+ assert np.allclose(sorted(new_tape.measurements[0]._eigvals), [-1.0, 1])
+ assert np.allclose(sorted(new_tape.measurements[1]._eigvals), [-2.0, 0, 0, 2.0])
+ assert np.allclose(sorted(new_tape.measurements[2]._eigvals), [-1.0, -1.0, 1.0, 1.0])
else:
assert new_tape.measurements == [
qml.expval(Z(0)),
diff --git a/tests/transforms/test_qcut.py b/tests/transforms/test_qcut.py
index 1a78c1ab4e2..96b0d1e3af8 100644
--- a/tests/transforms/test_qcut.py
+++ b/tests/transforms/test_qcut.py
@@ -4681,7 +4681,7 @@ def test_init_raises(self, devices, imbalance_tolerance, num_fragments_probed):
"""Test if ill-initialized instances throw errors."""
if (
- isinstance(devices, (qml.Device, qml.devices.Device))
+ isinstance(devices, qml.devices.Device)
and imbalance_tolerance is None
and num_fragments_probed is None
):