Repository: PennyLaneAI/pennylane
Pull request #3919 (2023-03-16 19:34:23)

Change `Operator.num_wires` to `AnyWires`
Change `Operator.num_wires` from an abstract value to `AnyWires`. This will make adding new operators slightly easier if the operator acts on arbitrary wires.

mudit2812: [sc-34241]
codecov[bot]: ## [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3919?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3919](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3919?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (d346183) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/ad14bb2b1f084ff7b534df4fcfccb077d9bc510f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (ad14bb2) will **decrease** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #3919      +/-   ##
==========================================
- Coverage   99.70%   99.70%   -0.01%     
==========================================
  Files         342      342              
  Lines       29856    29855       -1     
==========================================
- Hits        29769    29768       -1     
  Misses         87       87              
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/operation.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wZXJhdGlvbi5weQ==) | `97.22% <100.00%> (-0.01%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

timmysilv: do we worry that someone will forget to define it for a new Operator, although they should have? and if they forgot, what might the consequences be? For example, imagine we didn't set `num_wires` on PauliX. Today, it fails to create something like `qml.PauliX(wires=[0, 1])`, and that's good because that wouldn't make sense! With this change, that operator would exist just fine, though how it behaves would depend on implementation.
albi3ro: > do we worry that someone will forget to define it for a new Operator, although they should have? and if they forgot, what might the consequences be? For example, imagine we didn't set `num_wires` on PauliX. Today, it fails to create something like `qml.PauliX(wires=[0, 1])`, and that's good because that wouldn't make sense! With this change, that operator would exist just fine, though how it behaves would depend on implementation.

The documentation for creating `Operator`'s still says to define `num_wires`, and all of our operators still explicitly define them. In a case like `PauliX`, we know its important to validate the number of wires, and so we will define it. This will just make it easier to define templates and other very permissive operators.
timmysilv: pylint is complaining because those classes used to inherit the docstring from `Operator.num_wires`, but now the parent value is an attribute, not a property (method).
mudit2812: I've changed `Operator.num_wires` to a property, but this creates a breaking change.

If I make `Operator.num_wires` a property, then any operator that inherits it that doesn't define `num_wires` will need to be instantiated first to use `op.num_wires` and actually get a correct value back. This does not affect any current PL ops because all of them set `num_wires`, but it might affect ops added in the future.

I've always found it very helpful to be able to access `num_wires` without having to instantiate the operator, and if that changes now it could break some functionality. In that case, if I were to create a new op `Dummy` that has `num_wires = AnyWires` by default but I choose not to specify that when I implement the op, `Dummy.num_wires` would return a `property`, while `Dummy().num_wires` would return `AnyWires`.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index be0fbc88a..4de27d4b4 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -93,6 +93,9 @@
   `ops.op_math.ctrl_decomp_bisect`.
   [(#3851)](https://github.com/PennyLaneAI/pennylane/pull/3851)
 
+* Changed `Operator.num_wires` from an abstract value to `AnyWires`.
+  [(#3919)](https://github.com/PennyLaneAI/pennylane/pull/3919)
+
 <h3>Breaking changes</h3>
 
 * Both JIT interfaces are not compatible with Jax `>0.4.3`, we raise an error for those versions.
diff --git a/pennylane/operation.py b/pennylane/operation.py
index ae045c391..33f551398 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -842,10 +842,8 @@ class Operator(abc.ABC):
         """
         raise TermsUndefinedError
 
-    @property
-    @abc.abstractmethod
-    def num_wires(self):
-        """Number of wires the operator acts on."""
+    num_wires = AnyWires
+    """Number of wires the operator acts on."""
 
     @property
     def name(self):
diff --git a/pennylane/ops/op_math/composite.py b/pennylane/ops/op_math/composite.py
index 333334c57..8d3fa972e 100644
--- a/pennylane/ops/op_math/composite.py
+++ b/pennylane/ops/op_math/composite.py
@@ -122,6 +122,7 @@ class CompositeOp(Operator):
 
     @property
     def num_wires(self):
+        """Number of wires the operator acts on."""
         return len(self.wires)
 
     @property
diff --git a/pennylane/ops/op_math/symbolicop.py b/pennylane/ops/op_math/symbolicop.py
index 2713fd9ca..1c9032d50 100644
--- a/pennylane/ops/op_math/symbolicop.py
+++ b/pennylane/ops/op_math/symbolicop.py
@@ -103,6 +103,7 @@ class SymbolicOp(Operator):
 
     @property
     def num_wires(self):
+        """Number of wires the operator acts on."""
         return len(self.wires)
 
     # pylint: disable=arguments-renamed, invalid-overridden-method


Test patch:
diff --git a/tests/test_operation.py b/tests/test_operation.py
index 13d986d0c..881af7e14 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -71,6 +71,15 @@ class TestOperatorConstruction:
         with pytest.raises(qml.wires.WireError, match="Wires must be unique"):
             DummyOp(0.5, wires=[1, 1], do_queue=False)
 
+    def test_num_wires_default_any_wires(self):
+        """Test that num_wires is `AnyWires` by default."""
+
+        class DummyOp(qml.operation.Operator):
+            r"""Dummy custom operator"""
+
+        assert DummyOp.num_wires == qml.operation.AnyWires
+        assert Operator.num_wires == qml.operation.AnyWires
+
     def test_incorrect_num_params(self):
         """Test that an exception is raised if called with wrong number of parameters"""