Repository: PennyLaneAI/pennylane
Pull request #6877 (2025-01-23 22:38:45)

Fix `InterfaceEnum` object
**Context:**

`QNode.interface` returns a `str` type,
```python
    @property
    def interface(self) -> str:
        """The interface used by the QNode"""
        return self._interface.value
```
Therefore, this equality will never be true,
```python
from pennylane.math.interface_utils import Interface

>>> "jax" == Interface.JAX
False
```

Moreover, `str` cannot be compared with `Enum` objects at all. 🤯 

**Description of the Change:**

- Adjust the `InterfaceEnum` class to have dunder methods to overwrite comparisons. This way it will error out if you try to compare it with a string. 
- Add test coverage for the private helpers in `qnode.py`

Now we have,
```python
from pennylane.math.interface_utils import Interface

>>> "jax" == Interface.JAX
TypeError: Cannot compare Interface with str
```

**Benefits:** Logic is correct.

**Possible Drawbacks:** None identified.

[sc-83034]


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6877?dropdown=coverage&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:
> Project coverage is 99.59%. Comparing base [(`61dbc71`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/61dbc7145cb9b0883e3dff817399c983db178279?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`eafed97`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/eafed9773387c1a04f8ee34b9715268b127d7b60?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 4 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #6877   +/-   ##
=======================================
  Coverage   99.59%   99.59%           
=======================================
  Files         477      477           
  Lines       45233    45242    +9     
=======================================
+ Hits        45050    45059    +9     
  Misses        183      183           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6877?dropdown=coverage&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).

JerryChen97: Still weird though how can this branch ever pass coverage test...
andrijapau: @albi3ro, this is only really used by `construct_batch`.  I'm thinking use `construct_batch` to build the tapes and then execute (and test using `dev.tracker`).

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 882803922..f5c4c9620 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -103,6 +103,9 @@
 
 <h3>Internal changes ⚙️</h3>
 
+* Improved the `InterfaceEnum` object to prevent direct comparisons to `str` objects.
+  [(#6877)](https://github.com/PennyLaneAI/pennylane/pull/6877)
+
 * Added a `QmlPrimitive` class that inherits `jax.core.Primitive` to a new `qml.capture.custom_primitives` module.
   This class contains a `prim_type` property so that we can differentiate between different sets of PennyLane primitives.
   Consequently, `QmlPrimitive` is now used to define all PennyLane primitives.
diff --git a/pennylane/math/interface_utils.py b/pennylane/math/interface_utils.py
index 4fc401b9f..df6eface8 100644
--- a/pennylane/math/interface_utils.py
+++ b/pennylane/math/interface_utils.py
@@ -46,6 +46,15 @@ class Interface(Enum):
         }
         return mapping[self]
 
+    def __eq__(self, interface):
+        if isinstance(interface, str):
+            raise TypeError("Cannot compare Interface with str")
+        return super().__eq__(interface)
+
+    def __hash__(self):
+        # pylint: disable=useless-super-delegation
+        return super().__hash__()
+
 
 InterfaceLike = Union[str, Interface, None]
 
@@ -225,7 +234,7 @@ def get_canonical_interface_name(user_input: InterfaceLike) -> Interface:
         Interface: canonical interface
     """
 
-    if user_input in SUPPORTED_INTERFACE_NAMES:
+    if isinstance(user_input, Interface) and user_input in SUPPORTED_INTERFACE_NAMES:
         return user_input
     try:
         return INTERFACE_MAP[user_input]
diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py
index ea1a9e339..0929bc5bb 100644
--- a/pennylane/workflow/qnode.py
+++ b/pennylane/workflow/qnode.py
@@ -60,11 +60,11 @@ def _convert_to_interface(result, interface: Interface):
 def _make_execution_config(
     circuit: Optional["QNode"], diff_method=None, mcm_config=None
 ) -> "qml.devices.ExecutionConfig":
-    circuit_interface = getattr(circuit, "interface", Interface.NUMPY)
+    circuit_interface = getattr(circuit, "interface", Interface.NUMPY.value)
     execute_kwargs = getattr(circuit, "execute_kwargs", {})
     gradient_kwargs = getattr(circuit, "gradient_kwargs", {})
     grad_on_execution = execute_kwargs.get("grad_on_execution")
-    if circuit_interface == Interface.JAX:
+    if circuit_interface in {Interface.JAX.value, Interface.JAX_JIT.value}:
         grad_on_execution = False
     elif grad_on_execution == "best":
         grad_on_execution = None
diff --git a/pennylane/workflow/resolution.py b/pennylane/workflow/resolution.py
index 77d85aae9..6f9de5b0e 100644
--- a/pennylane/workflow/resolution.py
+++ b/pennylane/workflow/resolution.py
@@ -96,7 +96,6 @@ def _resolve_interface(interface: Union[str, Interface], tapes: QuantumScriptBat
     Returns:
         Interface: resolved interface
     """
-
     interface = get_canonical_interface_name(interface)
 
     if interface == Interface.AUTO:
@@ -104,11 +103,11 @@ def _resolve_interface(interface: Union[str, Interface], tapes: QuantumScriptBat
         for tape in tapes:
             params.extend(tape.get_parameters(trainable_only=False))
         interface = get_interface(*params)
-        if interface != Interface.NUMPY:
-            try:
-                interface = get_canonical_interface_name(interface)
-            except ValueError:
-                interface = Interface.NUMPY
+        try:
+            interface = get_canonical_interface_name(interface)
+        except ValueError:
+            # If the interface is not recognized, default to numpy, like networkx
+            interface = Interface.NUMPY
     if interface == Interface.TF and _use_tensorflow_autograph():
         interface = Interface.TF_AUTOGRAPH
     if interface == Interface.JAX:


Test patch:
diff --git a/tests/math/test_functions.py b/tests/math/test_functions.py
index 54990b137..bcf1510d1 100644
--- a/tests/math/test_functions.py
+++ b/tests/math/test_functions.py
@@ -1024,6 +1024,18 @@ def test_get_interface(t, interface):
     assert res == interface
 
 
+# pylint: disable=too-few-public-methods
+class TestInterfaceEnum:
+    """Test the Interface enum class"""
+
+    def test_eq(self):
+        """Test that an error is raised if comparing to string"""
+        assert fn.Interface.NUMPY == fn.Interface.NUMPY
+        with pytest.raises(TypeError, match="Cannot compare Interface with str"):
+            # pylint: disable=pointless-statement
+            fn.Interface.NUMPY == "numpy"
+
+
 @pytest.mark.parametrize("t", test_data)
 def test_toarray(t):
     """Test that the toarray method correctly converts the input
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
index 5d270d1f9..68e91cd40 100644
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -29,6 +29,7 @@ from pennylane import numpy as pnp
 from pennylane import qnode
 from pennylane.tape import QuantumScript, QuantumScriptBatch
 from pennylane.typing import PostprocessingFn
+from pennylane.workflow.qnode import _make_execution_config
 
 
 def dummyfunc():
@@ -2073,3 +2074,52 @@ def test_resets_after_execution_error():
         circuit(qml.numpy.array(0.1))
 
     assert circuit.interface == "auto"
+
+
+class TestPrivateFunctions:
+    """Tests for private functions in the QNode class."""
+
+    def test_make_execution_config_with_no_qnode(self):
+        """Test that the _make_execution_config function correctly creates an execution config."""
+        diff_method = "best"
+        mcm_config = qml.devices.MCMConfig(postselect_mode="fill-shots", mcm_method="deferred")
+        config = _make_execution_config(None, diff_method, mcm_config)
+
+        expected_config = qml.devices.ExecutionConfig(
+            interface="numpy",
+            gradient_keyword_arguments={},
+            use_device_jacobian_product=False,
+            grad_on_execution=None,
+            gradient_method=diff_method,
+            mcm_config=mcm_config,
+        )
+
+        assert config == expected_config
+
+    @pytest.mark.parametrize("interface", ["autograd", "torch", "tf", "jax", "jax-jit"])
+    def test_make_execution_config_with_qnode(self, interface):
+        """Test that a execution config is made correctly with no QNode."""
+        if "jax" in interface:
+            grad_on_execution = False
+        else:
+            grad_on_execution = None
+
+        @qml.qnode(qml.device("default.qubit"), interface=interface)
+        def circuit():
+            qml.H(0)
+            return qml.probs()
+
+        diff_method = "best"
+        mcm_config = qml.devices.MCMConfig(postselect_mode="fill-shots", mcm_method="deferred")
+        config = _make_execution_config(circuit, diff_method, mcm_config)
+
+        expected_config = qml.devices.ExecutionConfig(
+            interface=interface,
+            gradient_keyword_arguments={},
+            use_device_jacobian_product=False,
+            grad_on_execution=grad_on_execution,
+            gradient_method=diff_method,
+            mcm_config=mcm_config,
+        )
+
+        assert config == expected_config