Repository: PennyLaneAI/pennylane
Pull request #5023 (2024-01-08 21:13:16)

Add a `workflow` module
[sc-52373]

This PR creates a new `workflow` module and moves QNode and `interfaces` into it.

File changes include:
* `pennylane/qnode.py` -> `pennylane/workflow/qnode.py`
* `pennylane/interfaces/execution.py` -> `pennylane/workflow/execution.py`
* `pennylane/interfaces/set_shots.py` -> `pennylane/workflow/set_shots.py`
* `pennylane/interfaces/jacobian_products.py` -> ` pennylane/workflow/jacobian_products.py`
* `pennylane/interfaces/autograd.py` -> `pennylane/workflow/interfaces/autograd.py`
* `pennylane/interfaces/jax.py` -> `pennylane/workflow/interfaces/jax.py`
* `pennylane/interfaces/jax_jit.py` -> `pennylane/workflow/interfaces/jax_jit.py`
* `pennylane/interfaces/tensorflow.py` -> `pennylane/workflow/interfaces/tensorflow.py`
* `pennylane/interfaces/tensorflow_autograph.py` -> `pennylane/workflow/interfaces/tensorflow_autograph.py`


A subsequent PR will flesh out the documentation changes. Only minor documentation changes are made here.

This PR also introduces deprecation warnings when anything is requested from `qml.interfaces`. It does not include warnings about things like `qml.interfaces.jax_jit.jax_jvp_execute`, but I think that is fine.

So that this PR strictly *moves* `qnode.py` and keeps its git history, we will add back in deprecation for the old qnode location in a follow up PR.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5023?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`3710433`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/3710433d3bbbb7f6f92fc8a478cbaadabc646616?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.67% compared to head [(`719b146`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5023?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.67%.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5023      +/-   ##
==========================================
- Coverage   99.67%   99.67%   -0.01%     
==========================================
  Files         392      394       +2     
  Lines       35572    35314     -258     
==========================================
- Hits        35457    35198     -259     
- Misses        115      116       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5023?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

albi3ro: > should we rename test files too? either way lgtm!

I'd like to do that in a follow up PR. I'd like to rename the "default qubit 2" in all the test names at the same time.

Patch:
diff --git a/doc/code/qml_interfaces.rst b/doc/code/qml_workflow.rst
similarity index 72%
rename from doc/code/qml_interfaces.rst
rename to doc/code/qml_workflow.rst
index b4d6e1148..6c9204f37 100644
--- a/doc/code/qml_interfaces.rst
+++ b/doc/code/qml_workflow.rst
@@ -1,7 +1,7 @@
-qml.interfaces
+qml.workflow
 ==============
 
-.. currentmodule:: pennylane.interfaces
+.. currentmodule:: pennylane.workflow
 
 .. warning::
 
@@ -13,4 +13,4 @@ qml.interfaces
 
 .. rubric:: Modules
 
-.. automodule:: pennylane.interfaces
+.. automodule:: pennylane.workflow
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 1f2bbfd05..bdb86df5c 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -9,6 +9,11 @@ deprecations are listed below.
 Pending deprecations
 --------------------
 
+* The contents of ``qml.interfaces`` is moved inside ``qml.workflow``.
+
+  - Contents moved in v0.35
+  - Old import path removed in v0.36.
+
 * ``qml.transforms.one_qubit_decomposition`` and ``qml.transforms.two_qubit_decomposition`` are deprecated. Instead,
   you should use ``qml.ops.one_qubit_decomposition`` and ``qml.ops.two_qubit_decomposition`` .
 
diff --git a/doc/index.rst b/doc/index.rst
index 88fce8d5e..3eebd5644 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -212,10 +212,10 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve
    :hidden:
 
    code/qml_devices
-   code/qml_interfaces
    code/qml_measurements
    code/qml_operation
    code/qml_queuing
    code/qml_tape
    code/qml_utils
    code/qml_wires
+   code/qml_workflow
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index bbcfe2018..689729d55 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -9,6 +9,8 @@
 * Update `tests/ops/functions/conftest.py` to ensure all operator types are tested for validity.
   [(#4978)](https://github.com/PennyLaneAI/pennylane/pull/4978)
   
+* A new `pennylane.workflow` module is added. This module now contains `qnode.py`, `execution.py`, `set_shots.py`, `jacobian_products.py`, and the submodule `interfaces`.
+
 <h4>Community contributions 🥳</h4>
 
 * The transform `split_non_commuting` now accepts measurements of type `probs`, `sample` and `counts` which accept both wires and observables. 
@@ -30,5 +32,6 @@
 This release contains contributions from (in alphabetical order):
 
 Abhishek Abhishek,
+Christina Lee,
 Isaac De Vlugt,
 Matthew Silverman.
diff --git a/pennylane/__init__.py b/pennylane/__init__.py
index 6f29d5394..51f8c416b 100644
--- a/pennylane/__init__.py
+++ b/pennylane/__init__.py
@@ -80,7 +80,7 @@ from pennylane.templates.swapnetworks import *
 from pennylane.templates.state_preparations import *
 from pennylane.templates.subroutines import *
 from pennylane import qaoa
-from pennylane.qnode import QNode, qnode
+from pennylane.workflow import QNode, qnode, execute
 from pennylane.transforms import (
     transform,
     batch_params,
@@ -126,7 +126,6 @@ import pennylane.gradients  # pylint:disable=wrong-import-order
 import pennylane.qinfo
 
 # pylint:disable=wrong-import-order
-from pennylane.interfaces import execute  # pylint:disable=wrong-import-order
 import pennylane.logging  # pylint:disable=wrong-import-order
 
 from pennylane.compiler import qjit, while_loop, for_loop
@@ -134,6 +133,8 @@ import pennylane.compiler
 
 import pennylane.data
 
+import pennylane.interfaces
+
 # Look for an existing configuration file
 default_config = Configuration("config.toml")
 
diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py
index 643755dc5..1c6dfa2d0 100644
--- a/pennylane/_qubit_device.py
+++ b/pennylane/_qubit_device.py
@@ -19,6 +19,7 @@ This module contains the :class:`QubitDevice` abstract base class.
 # For now, arguments may be different from the signatures provided in Device
 # e.g. instead of expval(self, observable, wires, par) have expval(self, observable)
 # pylint: disable=arguments-differ, abstract-method, no-value-for-parameter,too-many-instance-attributes,too-many-branches, no-member, bad-option-value, arguments-renamed
+# pylint: disable=too-many-arguments
 import abc
 import itertools
 import warnings
@@ -31,7 +32,7 @@ import numpy as np
 
 import pennylane as qml
 from pennylane import Device, DeviceError
-from pennylane.interfaces import set_shots
+from pennylane.workflow import set_shots
 from pennylane.math import multiply as qmlmul
 from pennylane.math import sum as qmlsum
 from pennylane.measurements import (
diff --git a/pennylane/devices/execution_config.py b/pennylane/devices/execution_config.py
index e3f35670d..ec7bbcc43 100644
--- a/pennylane/devices/execution_config.py
+++ b/pennylane/devices/execution_config.py
@@ -17,7 +17,7 @@ Contains the :class:`ExecutionConfig` data class.
 from dataclasses import dataclass
 from typing import Optional
 
-from pennylane.interfaces import SUPPORTED_INTERFACES
+from pennylane.workflow import SUPPORTED_INTERFACES
 from pennylane.gradients import SUPPORTED_GRADIENT_KWARGS
 
 SUPPORTED_GRADIENT_METHODS = [
diff --git a/pennylane/interfaces/__init__.py b/pennylane/interfaces/__init__.py
index ebb489002..4f07e56a6 100644
--- a/pennylane/interfaces/__init__.py
+++ b/pennylane/interfaces/__init__.py
@@ -1,61 +1,28 @@
-# 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 subpackage defines functions for interfacing devices' execution
-capabilities with different machine learning libraries.
-
-.. currentmodule:: pennylane
-
-Execution functions and utilities
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-    :toctree: api
-
-    ~execute
-    ~interfaces.cache_execute
-    ~interfaces.set_shots
-
-Supported interfaces
-~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-    :toctree: api
-
-    ~interfaces.autograd
-    ~interfaces.jax
-    ~interfaces.jax_jit
-    ~interfaces.tensorflow
-    ~interfaces.tensorflow_autograph
-    ~interfaces.torch
-
-Jacobian Product Calculation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-    :toctree: api
-
-    ~interfaces.jacobian_products.JacobianProductCalculator
-    ~interfaces.jacobian_products.TransformJacobianProducts
-    ~interfaces.jacobian_products.DeviceDerivatives
-    ~interfaces.jacobian_products.DeviceJacobianProducts
-
-"""
-from .execution import cache_execute, execute, INTERFACE_MAP, SUPPORTED_INTERFACES
-from .set_shots import set_shots
-
-
-class InterfaceUnsupportedError(NotImplementedError):
-    """Exception raised when features not supported by an interface are
-    attempted to be used."""
+# Copyright 2018-2023 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.
+"""
+Deprecated module. Please see ``pennylane.workflow``.
+"""
+from warnings import warn
+
+import pennylane as qml
+from pennylane import workflow
+
+
+def __getattr__(name):
+    warn(
+        "pennylane.interfaces has been moved into pennylane.workflow. Please import from there instead.",
+        qml.PennyLaneDeprecationWarning,
+    )
+    return getattr(workflow, name)
diff --git a/pennylane/measurements/classical_shadow.py b/pennylane/measurements/classical_shadow.py
index f8470d35a..f25c39804 100644
--- a/pennylane/measurements/classical_shadow.py
+++ b/pennylane/measurements/classical_shadow.py
@@ -284,7 +284,7 @@ class ClassicalShadowMP(MeasurementTransform):
         n_snapshots = device.shots
         seed = self.seed
 
-        with qml.interfaces.set_shots(device, shots=1):
+        with qml.workflow.set_shots(device, shots=1):
             # slow implementation but works for all devices
             n_qubits = len(wires)
             mapped_wires = np.array(device.map_wires(wires))
diff --git a/pennylane/ops/functions/map_wires.py b/pennylane/ops/functions/map_wires.py
index 6e890aca8..bc6263efc 100644
--- a/pennylane/ops/functions/map_wires.py
+++ b/pennylane/ops/functions/map_wires.py
@@ -20,7 +20,7 @@ from typing import Callable, Union, Sequence
 import pennylane as qml
 from pennylane.measurements import MeasurementProcess
 from pennylane.operation import Operator
-from pennylane.qnode import QNode
+from pennylane.workflow import QNode
 from pennylane.queuing import QueuingManager
 from pennylane.tape import QuantumScript, QuantumTape
 from pennylane import transform
diff --git a/pennylane/ops/functions/simplify.py b/pennylane/ops/functions/simplify.py
index 51c5f30f0..ee4ab20e6 100644
--- a/pennylane/ops/functions/simplify.py
+++ b/pennylane/ops/functions/simplify.py
@@ -20,7 +20,7 @@ from typing import Callable, Union, Sequence
 import pennylane as qml
 from pennylane.measurements import MeasurementProcess
 from pennylane.operation import Operator
-from pennylane.qnode import QNode
+from pennylane.workflow import QNode
 from pennylane.queuing import QueuingManager
 from pennylane.tape import QuantumScript, QuantumTape
 
diff --git a/pennylane/qnn/torch.py b/pennylane/qnn/torch.py
index 61b89462d..f99eefece 100644
--- a/pennylane/qnn/torch.py
+++ b/pennylane/qnn/torch.py
@@ -19,9 +19,9 @@ import functools
 import inspect
 import math
 from collections.abc import Iterable
-from typing import Callable, Dict, Union, Any, Text
+from typing import Callable, Dict, Union, Any
 
-from pennylane.qnode import QNode
+from pennylane import QNode
 
 try:
     import torch
@@ -530,7 +530,7 @@ class TorchLayer(Module):
         return self._input_arg
 
     @staticmethod
-    def set_input_argument(input_name: Text = "inputs") -> None:
+    def set_input_argument(input_name: str = "inputs") -> None:
         """
         Set the name of the input argument.
 
diff --git a/pennylane/workflow/__init__.py b/pennylane/workflow/__init__.py
new file mode 100644
index 000000000..00aceb7e1
--- /dev/null
+++ b/pennylane/workflow/__init__.py
@@ -0,0 +1,57 @@
+# Copyright 2018-2024 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 the core objects for managing a PennyLane workflow.
+
+.. currentmodule:: pennylane
+
+Execution functions and utilities
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autosummary::
+    :toctree: api
+
+    ~execute
+    ~workflow.cache_execute
+    ~workflow.set_shots
+
+Supported interfaces
+~~~~~~~~~~~~~~~~~~~~
+
+.. autosummary::
+    :toctree: api
+
+    ~workflow.interfaces.autograd
+    ~workflow.interfaces.jax
+    ~workflow.interfaces.jax_jit
+    ~workflow.interfaces.tensorflow
+    ~workflow.interfaces.tensorflow_autograph
+    ~workflow.interfaces.torch
+
+Jacobian Product Calculation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autosummary::
+    :toctree: api
+
+    ~workflow.jacobian_products.JacobianProductCalculator
+    ~workflow.jacobian_products.TransformJacobianProducts
+    ~workflow.jacobian_products.DeviceDerivatives
+    ~workflow.jacobian_products.DeviceJacobianProducts
+
+
+"""
+from .set_shots import set_shots
+from .execution import execute, cache_execute, SUPPORTED_INTERFACES, INTERFACE_MAP
+from .qnode import QNode, qnode
diff --git a/pennylane/interfaces/execution.py b/pennylane/workflow/execution.py
similarity index 95%
rename from pennylane/interfaces/execution.py
rename to pennylane/workflow/execution.py
index d6c05fbce..efcf0fe1f 100644
--- a/pennylane/interfaces/execution.py
+++ b/pennylane/workflow/execution.py
@@ -114,32 +114,32 @@ def _get_ml_boundary_execute(
     mapped_interface = INTERFACE_MAP[interface]
     try:
         if mapped_interface == "autograd":
-            from .autograd import autograd_execute as ml_boundary
+            from .interfaces.autograd import autograd_execute as ml_boundary
 
         elif mapped_interface == "tf":
             import tensorflow as tf
 
             if not tf.executing_eagerly() or "autograph" in interface:
-                from .tensorflow_autograph import execute as ml_boundary
+                from .interfaces.tensorflow_autograph import execute as ml_boundary
 
                 ml_boundary = partial(ml_boundary, grad_on_execution=grad_on_execution)
 
             else:
-                from .tensorflow import execute as ml_boundary
+                from .interfaces.tensorflow import execute as ml_boundary
 
         elif mapped_interface == "torch":
-            from .torch import execute as ml_boundary
+            from .interfaces.torch import execute as ml_boundary
 
         elif interface == "jax-jit":
             if device_vjp:
-                from .jax_jit import jax_jit_vjp_execute as ml_boundary
+                from .interfaces.jax_jit import jax_jit_vjp_execute as ml_boundary
             else:
-                from .jax_jit import jax_jit_jvp_execute as ml_boundary
+                from .interfaces.jax_jit import jax_jit_jvp_execute as ml_boundary
         else:  # interface in {"jax", "jax-python", "JAX"}:
             if device_vjp:
-                from .jax import jax_vjp_execute as ml_boundary
+                from .interfaces.jax import jax_vjp_execute as ml_boundary
             else:
-                from .jax import jax_jvp_execute as ml_boundary
+                from .interfaces.jax import jax_jvp_execute as ml_boundary
 
     except ImportError as e:  # pragma: no-cover
         raise qml.QuantumFunctionError(
@@ -240,8 +240,7 @@ def _make_inner_execute(
     else:
         device_execution = partial(device.execute, execution_config=execution_config)
 
-    # use qml.interfaces so that mocker can spy on it during testing
-    cached_device_execution = qml.interfaces.cache_execute(
+    cached_device_execution = qml.workflow.cache_execute(
         device_execution, cache, return_tuple=False
     )
 
@@ -560,7 +559,7 @@ def execute(
         interface = qml.math.get_interface(*params)
     if interface == "jax":
         try:  # pragma: no-cover
-            from .jax import get_jax_interface_name
+            from .interfaces.jax import get_jax_interface_name
         except ImportError as e:  # pragma: no-cover
             raise qml.QuantumFunctionError(  # pragma: no-cover
                 "jax not found. Please install the latest "  # pragma: no-cover
@@ -749,9 +748,8 @@ def execute(
             execute_fn = inner_execute_with_empty_jac
 
             # replace the backward gradient computation
-            # use qml.interfaces so that mocker can spy on it during testing
             gradient_fn_with_shots = set_shots(device, override_shots)(device.gradients)
-            cached_gradient_fn = qml.interfaces.cache_execute(
+            cached_gradient_fn = qml.workflow.cache_execute(
                 gradient_fn_with_shots,
                 cache,
                 pass_kwargs=True,
diff --git a/pennylane/workflow/interfaces/__init__.py b/pennylane/workflow/interfaces/__init__.py
new file mode 100644
index 000000000..a9eb7b89e
--- /dev/null
+++ b/pennylane/workflow/interfaces/__init__.py
@@ -0,0 +1,22 @@
+# 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 subpackage defines functions for interfacing devices' execution
+capabilities with different machine learning libraries.
+"""
+
+
+class InterfaceUnsupportedError(NotImplementedError):
+    """Exception raised when features not supported by an interface are
+    attempted to be used."""
diff --git a/pennylane/interfaces/autograd.py b/pennylane/workflow/interfaces/autograd.py
similarity index 94%
rename from pennylane/interfaces/autograd.py
rename to pennylane/workflow/interfaces/autograd.py
index 0f3156f26..54f35d3db 100644
--- a/pennylane/interfaces/autograd.py
+++ b/pennylane/workflow/interfaces/autograd.py
@@ -101,7 +101,7 @@ logger.addHandler(logging.NullHandler())
 def autograd_execute(
     tapes: Batch,
     execute_fn: ExecuteFn,
-    jpc: qml.interfaces.jacobian_products.JacobianProductCalculator,
+    jpc: qml.workflow.jacobian_products.JacobianProductCalculator,
     device=None,
 ):
     """Execute a batch of tapes with Autograd parameters on a device.
@@ -118,8 +118,8 @@ def autograd_execute(
 
     **Example:**
 
-    >>> from pennylane.interfaces.jacobian_products import DeviceDerivatives
-    >>> from pennylane.interfaces.autograd import autograd_execute
+    >>> from pennylane.workflow.jacobian_products import DeviceDerivatives
+    >>> from pennylane.workflow.autograd import autograd_execute
     >>> execute_fn = qml.device('default.qubit').execute
     >>> config = qml.devices.ExecutionConfig(gradient_method="adjoint", use_device_gradient=True)
     >>> jpc = DeviceDerivatives(qml.device('default.qubit'), config)
diff --git a/pennylane/interfaces/jax.py b/pennylane/workflow/interfaces/jax.py
similarity index 100%
rename from pennylane/interfaces/jax.py
rename to pennylane/workflow/interfaces/jax.py
diff --git a/pennylane/interfaces/jax_jit.py b/pennylane/workflow/interfaces/jax_jit.py
similarity index 97%
rename from pennylane/interfaces/jax_jit.py
rename to pennylane/workflow/interfaces/jax_jit.py
index 6b2124bcd..eb87b84fe 100644
--- a/pennylane/interfaces/jax_jit.py
+++ b/pennylane/workflow/interfaces/jax_jit.py
@@ -42,7 +42,7 @@ import jax.numpy as jnp
 import pennylane as qml
 from pennylane.typing import ResultBatch
 
-from .jacobian_products import _compute_jvps
+from ..jacobian_products import _compute_jvps
 
 from .jax import _NonPytreeWrapper
 
@@ -70,6 +70,7 @@ def _set_parameters_on_copy(tapes, params):
     return tuple(t.bind_new_parameters(a, list(range(len(a)))) for t, a in zip(tapes, params))
 
 
+# pylint: disable=no-member
 def _jax_dtype(m_type):
     if m_type == int:
         return jnp.int64 if jax.config.jax_enable_x64 else jnp.int32
diff --git a/pennylane/interfaces/tensorflow.py b/pennylane/workflow/interfaces/tensorflow.py
similarity index 100%
rename from pennylane/interfaces/tensorflow.py
rename to pennylane/workflow/interfaces/tensorflow.py
diff --git a/pennylane/interfaces/tensorflow_autograph.py b/pennylane/workflow/interfaces/tensorflow_autograph.py
similarity index 100%
rename from pennylane/interfaces/tensorflow_autograph.py
rename to pennylane/workflow/interfaces/tensorflow_autograph.py
diff --git a/pennylane/interfaces/torch.py b/pennylane/workflow/interfaces/torch.py
similarity index 100%
rename from pennylane/interfaces/torch.py
rename to pennylane/workflow/interfaces/torch.py
diff --git a/pennylane/interfaces/jacobian_products.py b/pennylane/workflow/jacobian_products.py
similarity index 100%
rename from pennylane/interfaces/jacobian_products.py
rename to pennylane/workflow/jacobian_products.py
diff --git a/pennylane/qnode.py b/pennylane/workflow/qnode.py
similarity index 97%
rename from pennylane/qnode.py
rename to pennylane/workflow/qnode.py
index d5326eab1..8a3736454 100644
--- a/pennylane/qnode.py
+++ b/pennylane/workflow/qnode.py
@@ -25,10 +25,12 @@ import logging
 
 import pennylane as qml
 from pennylane import Device
-from pennylane.interfaces import INTERFACE_MAP, SUPPORTED_INTERFACES, set_shots
 from pennylane.measurements import CountsMP, MidMeasureMP, Shots
 from pennylane.tape import QuantumTape, QuantumScript
 
+from .execution import INTERFACE_MAP, SUPPORTED_INTERFACES
+from .set_shots import set_shots
+
 logger = logging.getLogger(__name__)
 logger.addHandler(logging.NullHandler())
 
@@ -75,7 +77,7 @@ class QNode:
         device (~.Device): a PennyLane-compatible device
         interface (str): The interface that will be used for classical backpropagation.
             This affects the types of objects that can be passed to/returned from the QNode. See
-            ``qml.interfaces.SUPPORTED_INTERFACES`` for a list of all accepted strings.
+            ``qml.workflow.SUPPORTED_INTERFACES`` for a list of all accepted strings.
 
             * ``"autograd"``: Allows autograd to backpropagate
               through the QNode. The QNode accepts default Python types


Test patch:
diff --git a/pennylane/interfaces/set_shots.py b/pennylane/workflow/set_shots.py
similarity index 100%
rename from pennylane/interfaces/set_shots.py
rename to pennylane/workflow/set_shots.py
diff --git a/tests/interfaces/default_qubit_2_integration/test_autograd_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_autograd_default_qubit_2.py
index 18d8035e0..aeeb9c8ae 100644
--- a/tests/interfaces/default_qubit_2_integration/test_autograd_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_autograd_default_qubit_2.py
@@ -22,7 +22,7 @@ from pennylane import numpy as np
 import pennylane as qml
 from pennylane.devices import DefaultQubit
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 from pennylane.measurements import Shots
 
 pytestmark = pytest.mark.autograd
diff --git a/tests/interfaces/default_qubit_2_integration/test_execute_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_execute_default_qubit_2.py
index 618620e34..aa527cac4 100644
--- a/tests/interfaces/default_qubit_2_integration/test_execute_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_execute_default_qubit_2.py
@@ -16,7 +16,7 @@ import pytest
 
 import pennylane as qml
 from pennylane import numpy as np
-from pennylane.interfaces.execution import _preprocess_expand_fn
+from pennylane.workflow.execution import _preprocess_expand_fn
 
 from pennylane.devices import DefaultQubit
 
diff --git a/tests/interfaces/default_qubit_2_integration/test_jax_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_jax_default_qubit_2.py
index 302f42325..d8f2c0f0e 100644
--- a/tests/interfaces/default_qubit_2_integration/test_jax_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_jax_default_qubit_2.py
@@ -20,7 +20,7 @@ from param_shift_dev import ParamShiftDerivativesDevice
 import pennylane as qml
 from pennylane.devices import DefaultQubit
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 from pennylane.measurements import Shots
 
 jax = pytest.importorskip("jax")
diff --git a/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
index b65655db9..471b09b46 100644
--- a/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
@@ -2910,7 +2910,7 @@ class TestSinglePrecision:
     # pylint: disable=import-outside-toplevel
     def test_type_conversion_fallback(self):
         """Test that if the type isn't int, float, or complex, we still have a fallback."""
-        from pennylane.interfaces.jax_jit import _jax_dtype
+        from pennylane.workflow.interfaces.jax_jit import _jax_dtype
 
         assert _jax_dtype(bool) == jax.numpy.dtype(bool)
 
diff --git a/tests/interfaces/default_qubit_2_integration/test_tensorflow_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_tensorflow_default_qubit_2.py
index fc4084703..096b19716 100644
--- a/tests/interfaces/default_qubit_2_integration/test_tensorflow_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_tensorflow_default_qubit_2.py
@@ -18,7 +18,7 @@ import numpy as np
 import pennylane as qml
 from pennylane.devices import DefaultQubit
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 
 pytestmark = pytest.mark.tf
 tf = pytest.importorskip("tensorflow")
diff --git a/tests/interfaces/default_qubit_2_integration/test_torch_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_torch_default_qubit_2.py
index e98aaf15d..0618ab8bc 100644
--- a/tests/interfaces/default_qubit_2_integration/test_torch_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_torch_default_qubit_2.py
@@ -20,7 +20,7 @@ from param_shift_dev import ParamShiftDerivativesDevice
 import pennylane as qml
 from pennylane.devices import DefaultQubit
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 from pennylane.measurements import Shots
 
 torch = pytest.importorskip("torch")
diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py
index ef525eb9d..cd2ef932d 100644
--- a/tests/interfaces/test_autograd.py
+++ b/tests/interfaces/test_autograd.py
@@ -38,7 +38,7 @@ class TestAutogradExecuteUnitTests:
         mock.side_effect = ImportError()
 
         try:
-            del sys.modules["pennylane.interfaces.autograd"]
+            del sys.modules["pennylane.workflow.interfaces.autograd"]
         except KeyError:
             pass
 
@@ -228,7 +228,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cachesize):
             with qml.queuing.AnnotatedQueue() as q:
@@ -250,7 +250,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cache):
             with qml.queuing.AnnotatedQueue() as q:
diff --git a/tests/interfaces/test_execute.py b/tests/interfaces/test_execute.py
index e12c5d8ff..272a0d7bf 100644
--- a/tests/interfaces/test_execute.py
+++ b/tests/interfaces/test_execute.py
@@ -26,3 +26,16 @@ def test_old_interface_no_device_jacobian_products():
     tape = qml.tape.QuantumScript([qml.RX(1.0, wires=0)], [qml.expval(qml.PauliZ(0))])
     with pytest.raises(qml.QuantumFunctionError):
         qml.execute((tape,), dev, device_vjp=True)
+
+
+def test_deprecated_location_warnings():
+    """Test that deprecation warnings are raised when something is imported from pennylane.interfaces."""
+
+    with pytest.warns(qml.PennyLaneDeprecationWarning):
+        assert qml.interfaces.execute is qml.workflow.execute
+
+    with pytest.warns(qml.PennyLaneDeprecationWarning):
+        assert qml.interfaces.set_shots is qml.workflow.set_shots
+
+    with pytest.warns(qml.PennyLaneDeprecationWarning):
+        assert qml.interfaces.cache_execute is qml.workflow.cache_execute
diff --git a/tests/interfaces/test_jacobian_products.py b/tests/interfaces/test_jacobian_products.py
index bde1e50b8..721366b88 100644
--- a/tests/interfaces/test_jacobian_products.py
+++ b/tests/interfaces/test_jacobian_products.py
@@ -22,7 +22,7 @@ from param_shift_dev import ParamShiftDerivativesDevice
 import numpy as np
 
 import pennylane as qml
-from pennylane.interfaces.jacobian_products import (
+from pennylane.workflow.jacobian_products import (
     JacobianProductCalculator,
     TransformJacobianProducts,
     DeviceDerivatives,
diff --git a/tests/interfaces/test_jax.py b/tests/interfaces/test_jax.py
index cae66f6bc..ad762b9a7 100644
--- a/tests/interfaces/test_jax.py
+++ b/tests/interfaces/test_jax.py
@@ -18,7 +18,7 @@ import numpy as np
 
 import pennylane as qml
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 
 pytestmark = pytest.mark.jax
 
@@ -194,7 +194,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cachesize):
             with qml.queuing.AnnotatedQueue() as q:
@@ -221,7 +221,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cache):
             with qml.queuing.AnnotatedQueue() as q:
@@ -247,7 +247,7 @@ class TestCaching:
     def test_custom_cache_multiple(self, mocker):
         """Test the use of a custom cache object with multiple tapes"""
         dev = qml.device("default.qubit.legacy", wires=1)
-        spy = mocker.spy(qml.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         a = jax.numpy.array(0.1)
         b = jax.numpy.array(0.2)
@@ -497,7 +497,7 @@ class TestJaxExecuteIntegration:
                 qml.expval(qml.PauliZ(0))
 
             tape = qml.tape.QuantumScript.from_queue(q)
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)[0]
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)[0]
             return res
 
         results = jax.grad(cost)(params, cache=None)
@@ -692,7 +692,7 @@ class TestVectorValued:
                 qml.expval(qml.PauliZ(1))
 
             tape = qml.tape.QuantumScript.from_queue(q)
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)
             return res[0]
 
         res = jax.jacobian(cost)(params, cache=None)
@@ -719,7 +719,7 @@ class TestVectorValued:
                 qml.expval(qml.PauliZ(1))
 
             tape = qml.tape.QuantumScript.from_queue(q)
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)
             return res[0]
 
         res = jax.jacobian(cost)(params, cache=None)
diff --git a/tests/interfaces/test_jax_jit.py b/tests/interfaces/test_jax_jit.py
index 8fe2e14c5..495fb24fa 100644
--- a/tests/interfaces/test_jax_jit.py
+++ b/tests/interfaces/test_jax_jit.py
@@ -19,7 +19,7 @@ import numpy as np
 
 import pennylane as qml
 from pennylane.gradients import param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 
 pytestmark = pytest.mark.jax
 
@@ -183,7 +183,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cachesize):
             with qml.queuing.AnnotatedQueue() as q:
@@ -211,7 +211,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cache):
             with qml.queuing.AnnotatedQueue() as q:
@@ -238,7 +238,7 @@ class TestCaching:
     def test_custom_cache_multiple(self, mocker):
         """Test the use of a custom cache object with multiple tapes"""
         dev = qml.device("default.qubit.legacy", wires=1)
-        spy = mocker.spy(qml.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         a = jax.numpy.array(0.1)
         b = jax.numpy.array(0.2)
@@ -496,7 +496,7 @@ class TestJaxExecuteIntegration:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)[0]
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)[0]
             return res
 
         cost = jax.jit(cost)
@@ -706,7 +706,7 @@ class TestVectorValuedJIT:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)
             return res[0]
 
         res = jax.jit(cost)(params, cache=None)
@@ -733,7 +733,7 @@ class TestVectorValuedJIT:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)
             return res[0]
 
         res = jax.jit(jax.grad(cost), static_argnums=1)(params, cache=None)
@@ -778,7 +778,7 @@ class TestVectorValuedJIT:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)[0]
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)[0]
             return res
 
         res = jax.jit(cost, static_argnums=1)(params, cache=None)
@@ -812,7 +812,7 @@ class TestVectorValuedJIT:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)[0]
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)[0]
             return res
 
         res = jax.jit(cost, static_argnums=1)(params, cache=None)
@@ -833,7 +833,7 @@ class TestVectorValuedJIT:
 
             tape = qml.tape.QuantumScript.from_queue(q)
 
-            res = qml.interfaces.execute([tape], dev, cache=cache, **execute_kwargs)[0]
+            res = qml.execute([tape], dev, cache=cache, **execute_kwargs)[0]
             return res[0] + res[1]
 
         res = jax.jit(jax.grad(cost), static_argnums=1)(params, cache=None)
diff --git a/tests/interfaces/test_set_shots.py b/tests/interfaces/test_set_shots.py
index 1a2374e65..3df48e592 100644
--- a/tests/interfaces/test_set_shots.py
+++ b/tests/interfaces/test_set_shots.py
@@ -12,13 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """
-Tests for interfaces.set_shots
+Tests for workflow.set_shots
 """
 
 import pytest
 
 import pennylane as qml
-from pennylane.interfaces import set_shots
+from pennylane.workflow import set_shots
 from pennylane.measurements import Shots
 
 
diff --git a/tests/interfaces/test_tensorflow.py b/tests/interfaces/test_tensorflow.py
index 45b7ac441..74d9eb99c 100644
--- a/tests/interfaces/test_tensorflow.py
+++ b/tests/interfaces/test_tensorflow.py
@@ -18,7 +18,7 @@ import pytest
 
 import pennylane as qml
 from pennylane.gradients import finite_diff, param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 
 pytestmark = pytest.mark.tf
 
@@ -137,7 +137,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
         a = tf.Variable([0.1, 0.2])
 
         with tf.GradientTape() as t:
@@ -159,7 +159,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
         a = tf.Variable([0.1, 0.2])
         custom_cache = {}
 
diff --git a/tests/interfaces/test_torch.py b/tests/interfaces/test_torch.py
index 6e5053246..4c519360d 100644
--- a/tests/interfaces/test_torch.py
+++ b/tests/interfaces/test_torch.py
@@ -18,7 +18,7 @@ import pytest
 
 import pennylane as qml
 from pennylane.gradients import finite_diff, param_shift
-from pennylane.interfaces import execute
+from pennylane import execute
 
 pytestmark = pytest.mark.torch
 torch = pytest.importorskip("torch")
@@ -170,7 +170,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cachesize):
             with qml.queuing.AnnotatedQueue() as q:
@@ -196,7 +196,7 @@ class TestCaching:
     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.interfaces, "cache_execute")
+        spy = mocker.spy(qml.workflow, "cache_execute")
 
         def cost(a, cache):
             with qml.queuing.AnnotatedQueue() as q:
@@ -1099,7 +1099,8 @@ class TestHigherOrderDerivatives:
         )
         assert torch.allclose(res.detach(), expected_res, atol=tol, rtol=0)
 
-        jac_fn = lambda x: torch_functional.jacobian(circuit, x, create_graph=True)
+        def jac_fn(x):
+            return torch_functional.jacobian(circuit, x, create_graph=True)
 
         g = jac_fn(x)
 
diff --git a/tests/logging/test_logging_autograd.py b/tests/logging/test_logging_autograd.py
index ce7de9614..ec968cb84 100644
--- a/tests/logging/test_logging_autograd.py
+++ b/tests/logging/test_logging_autograd.py
@@ -82,11 +82,11 @@ class TestLogging:
 
         log_records_expected = [
             (
-                "pennylane.qnode",
+                "pennylane.workflow.qnode",
                 ["Creating QNode(func=<function TestLogging.test_dq_qnode_execution"],
             ),
             (
-                "pennylane.interfaces.execution",
+                "pennylane.workflow.execution",
                 [
                     "device=<default.qubit device (wires=2)",
                     "gradient_fn=None, interface=None",
@@ -125,7 +125,7 @@ class TestLogging:
 
         log_records_expected = [
             (
-                "pennylane.qnode",
+                "pennylane.workflow.qnode",
                 [
                     "Creating QNode(func=<function TestLogging.test_dq_qnode_execution_grad",
                     "device=<default.qubit device (wires=2)",
@@ -133,7 +133,7 @@ class TestLogging:
                 ],
             ),
             (
-                "pennylane.interfaces.execution",
+                "pennylane.workflow.execution",
                 [
                     "Entry with args=(tapes=(<QuantumScript: wires=[0], params=1>,)",
                     _grad_log_map[diff_method],
diff --git a/tests/measurements/legacy/test_mutual_info_legacy.py b/tests/measurements/legacy/test_mutual_info_legacy.py
index cdab46590..765d90346 100644
--- a/tests/measurements/legacy/test_mutual_info_legacy.py
+++ b/tests/measurements/legacy/test_mutual_info_legacy.py
@@ -16,7 +16,7 @@ import numpy as np
 import pytest
 
 import pennylane as qml
-from pennylane.interfaces import INTERFACE_MAP
+from pennylane.workflow import INTERFACE_MAP
 from pennylane.measurements import Shots
 
 
diff --git a/tests/measurements/legacy/test_vn_entropy_legacy.py b/tests/measurements/legacy/test_vn_entropy_legacy.py
index 3c38417e2..4472711e9 100644
--- a/tests/measurements/legacy/test_vn_entropy_legacy.py
+++ b/tests/measurements/legacy/test_vn_entropy_legacy.py
@@ -16,7 +16,7 @@ import numpy as np
 import pytest
 
 import pennylane as qml
-from pennylane.interfaces import INTERFACE_MAP
+from pennylane.workflow import INTERFACE_MAP
 from pennylane.measurements import Shots
 from pennylane.measurements.vn_entropy import VnEntropyMP
 
diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py
index e2c7cc7bb..f007ef053 100644
--- a/tests/test_qnode_legacy.py
+++ b/tests/test_qnode_legacy.py
@@ -148,7 +148,7 @@ class TestValidation:
 
     # pylint: disable=protected-access
     @pytest.mark.all_interfaces
-    @pytest.mark.parametrize("accepted_name, official_name", qml.interfaces.INTERFACE_MAP.items())
+    @pytest.mark.parametrize("accepted_name, official_name", qml.workflow.INTERFACE_MAP.items())
     def test_validate_backprop_method_all_interface_names(self, accepted_name, official_name):
         """Test that backprop devices are mapped for all possible interface names."""
         if accepted_name in {None, "auto", "scipy"}: