Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] `qml.specs`: `max_expansion` keyword argument is not doing anything (what should it do?)
### Expected behavior

I'm not sure what `max_expansion` should be doing. Currently, it looks like it isn't doing anything. Maybe just the docstring

> - max_expansion (int) – The number of times the internal circuit should be expanded when calculating the specification. Defaults to qnode.max_expansion.

can be clarified?

### Actual behavior

Consider this circuit:

```
@qml.qnode(dev)
def circuit():
    qml.templates.StronglyEntanglingLayers(
        np.random.random((3, 2, 3)),
        wires=range(2)
    )
    return qml.probs(wires=(0,1))
```

Then, 

```
print(qml.specs(circuit)() == qml.specs(circuit, max_expansion=7)()) # output: True
```

no matter what the value `max_expansion` is assigned to.



### Additional information

Specifying `expansion_strategy = "device"` below updates the `'depth'` key:

```
@qml.qnode(dev, expansion_strategy="device")
def circuit():
    qml.templates.StronglyEntanglingLayers(
        np.random.random((3, 2, 3)),
        wires=range(2)
    )
    return qml.probs(wires=(0,1))

print(qml.specs(circuit)()) # 'depth' key is changed from 1 (without expansion_strategy) to 9
```

### Source code

```shell
import pennylane as qml
from pennylane import numpy as np

dev = qml.device('default.qubit', wires=2)

def layer():
    qml.templates.StronglyEntanglingLayers(
        np.random.random((3, 2, 3)),
        wires=range(2)
    )

@qml.qnode(dev, expansion_strategy="device")
def circuit_exp_strat():
    layer()
    return qml.probs(wires=(0,1))

@qml.qnode(dev)
def circuit():
    layer()
    return qml.probs(wires=(0,1))

print(qml.specs(circuit_exp_strat)())
print(qml.specs(circuit)())
print(qml.specs(circuit, max_expansion = 7)())
```


### Tracebacks

```shell
N/A
```


### System information

```shell
Name: PennyLane
Version: 0.22.1
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /opt/anaconda3/lib/python3.9/site-packages
Requires: toml, appdirs, retworkx, pennylane-lightning, numpy, autograd, semantic-version, networkx, scipy, autoray, cachetools
Required-by: PennyLane-Lightning
Platform info:           macOS-10.16-x86_64-i386-64bit
Python version:          3.9.7
Numpy version:           1.20.3
Scipy version:           1.7.1
Installed devices:
- default.gaussian (PennyLane-0.22.1)
- default.mixed (PennyLane-0.22.1)
- default.qubit (PennyLane-0.22.1)
- default.qubit.autograd (PennyLane-0.22.1)
- default.qubit.jax (PennyLane-0.22.1)
- default.qubit.tf (PennyLane-0.22.1)
- default.qubit.torch (PennyLane-0.22.1)
- lightning.qubit (PennyLane-Lightning-0.22.1)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.

Pull request:
Add `expansion_strategy` to `specs` transform
Fixes #2393 

This PR adds the `expansion_strategy` keyword argument to the `qml.specs` transform.  Using this keyword, users can calculate the specifications of the circuit that will actually be executed by the device using `expansion_strategy="device"`.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index da89fd3dd..016c4df97 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -129,6 +129,9 @@
 
 <h3>Improvements</h3>
 
+* The `qml.specs` transform now accepts an `expansion_strategy` keyword argument.
+  [(#2395)](https://github.com/PennyLaneAI/pennylane/pull/2395)
+
 * `default.qubit` and `default.mixed` now skip over identity operators instead of performing matrix multiplication
   with the identity.
   [(#2356)](https://github.com/PennyLaneAI/pennylane/pull/2356)
diff --git a/pennylane/transforms/specs.py b/pennylane/transforms/specs.py
index 664e9ce2e..63c91c8a9 100644
--- a/pennylane/transforms/specs.py
+++ b/pennylane/transforms/specs.py
@@ -21,7 +21,7 @@ def _get_absolute_import_path(fn):
     return f"{inspect.getmodule(fn).__name__}.{fn.__name__}"
 
 
-def specs(qnode, max_expansion=None):
+def specs(qnode, max_expansion=None, expansion_strategy=None):
     """Resource information about a quantum circuit.
 
     This transform converts a QNode into a callable that provides resource information
@@ -33,6 +33,15 @@ def specs(qnode, max_expansion=None):
     Keyword Args:
         max_expansion (int): The number of times the internal circuit should be expanded when
             calculating the specification. Defaults to ``qnode.max_expansion``.
+        expansion_strategy (str): The strategy to use when circuit expansions or decompositions
+            are required.
+
+            - ``gradient``: The QNode will attempt to decompose
+              the internal circuit such that all circuit operations are supported by the gradient
+              method.
+
+            - ``device``: The QNode will attempt to decompose the internal circuit
+              such that all circuit operations are natively supported by the device.
 
     Returns:
         A function that has the same argument signature as ``qnode``. This function
@@ -97,12 +106,15 @@ def specs(qnode, max_expansion=None):
             dict[str, Union[defaultdict,int]]: dictionaries that contain QNode specifications
         """
         initial_max_expansion = qnode.max_expansion
-        qnode.max_expansion = initial_max_expansion if max_expansion is None else max_expansion
+        initial_expansion_strategy = getattr(qnode, "expansion_strategy", None)
 
         try:
+            qnode.max_expansion = initial_max_expansion if max_expansion is None else max_expansion
+            qnode.expansion_strategy = expansion_strategy or initial_expansion_strategy
             qnode.construct(args, kwargs)
         finally:
             qnode.max_expansion = initial_max_expansion
+            qnode.expansion_strategy = initial_expansion_strategy
 
         info = qnode.qtape.specs.copy()
 


Test patch:
diff --git a/tests/transforms/test_specs.py b/tests/transforms/test_specs.py
index c5d5e9e18..67c9dfd3e 100644
--- a/tests/transforms/test_specs.py
+++ b/tests/transforms/test_specs.py
@@ -129,24 +129,30 @@ class TestSpecsTransform:
         assert info["num_observables"] == 1
         assert info["num_diagonalizing_gates"] == 0
 
-    def test_max_expansion(self):
-        """Test that a user can calculation specifications for a different max
-        expansion parameter."""
-
+    def make_qnode_and_params(self, initial_expansion_strategy):
+        """Generates a qnode and params for use in other tests"""
         n_layers = 2
         n_wires = 5
 
         dev = qml.device("default.qubit", wires=n_wires)
 
-        @qml.qnode(dev)
+        @qml.qnode(dev, expansion_strategy=initial_expansion_strategy)
         def circuit(params):
-            qml.templates.BasicEntanglerLayers(params, wires=range(n_wires))
+            qml.BasicEntanglerLayers(params, wires=range(n_wires))
             return qml.expval(qml.PauliZ(0))
 
-        params_shape = qml.templates.BasicEntanglerLayers.shape(n_layers=n_layers, n_wires=n_wires)
+        params_shape = qml.BasicEntanglerLayers.shape(n_layers=n_layers, n_wires=n_wires)
         rng = np.random.default_rng(seed=10)
         params = rng.standard_normal(params_shape)
 
+        return circuit, params
+
+    def test_max_expansion(self):
+        """Test that a user can calculation specifications for a different max
+        expansion parameter."""
+
+        circuit, params = self.make_qnode_and_params("device")
+
         assert circuit.max_expansion == 10
         info = qml.specs(circuit, max_expansion=0)(params)
         assert circuit.max_expansion == 10
@@ -164,6 +170,20 @@ class TestSpecsTransform:
         assert info["diff_method"] == "best"
         assert info["gradient_fn"] == "backprop"
 
+    def test_expansion_strategy(self):
+        """Test that a user can calculate specs for different expansion strategies."""
+        circuit, params = self.make_qnode_and_params("gradient")
+
+        assert circuit.expansion_strategy == "gradient"
+        info = qml.specs(circuit, expansion_strategy="device")(params)
+        assert circuit.expansion_strategy == "gradient"
+
+        assert len(info) == 15
+
+        assert info["gate_sizes"] == defaultdict(int, {1: 10, 2: 10})
+        assert info["gate_types"] == defaultdict(int, {"RX": 10, "CNOT": 10})
+        assert info["num_operations"] == 20
+
     def test_gradient_transform(self):
         """Test that a gradient transform is properly labelled"""
         dev = qml.device("default.qubit", wires=2)