Repository: PennyLaneAI/pennylane

Issue:
Add generators to some operations
PennyLane operations have a [generator](https://pennylane.readthedocs.io/en/stable/code/api/pennylane.operation.Operation.html#pennylane.operation.Operation.generator) attribute, originally added in https://github.com/PennyLaneAI/pennylane/pull/295. This attribute contains the Hamiltonian used to generate the unitary gate. The use case for this attribute is the [QNGOptimizer](https://pennylane.readthedocs.io/en/stable/code/api/pennylane.QNGOptimizer.html), which accesses the generator to construct the metric tensor.

However, some commonly-used gates in PennyLane do not yet have their generator defined. These include:
- [ ] `Rot`
- [x] `MultiRZ` (added in https://github.com/PennyLaneAI/pennylane/pull/912)
- [x] `PauliRot` (added in https://github.com/PennyLaneAI/pennylane/pull/963)
- [ ] `CRot`

Without a generator defined, these gates cannot be used within circuits with the QNG optimizer. This is a blocker for use of [StronglyEntanglingLayers](https://pennylane.readthedocs.io/en/stable/code/api/pennylane.templates.layers.StronglyEntanglingLayers.html), which have `Rot` gates contained within.

This issue requests addition of generators for the above gates.

Update: `MultiRZ` and `PauliRot` have had their generators added. This leaves `Rot` and `CRot`, but it is not clear how to proceed since these gates are multiparameter gates, and perhaps best thought of as a composition of ZYZ rotations.


Discussion:
I find these gates all have its params `phi, theta, omega`, say for CRot. Then, how to define the generator for multi-variables? 
Hi @JiahaoYao, good point that `Rot` and `CRot` have multiple parameters. Maybe we could start with `MultiRZ` and `PauliRot`, which each have one differentiable parameter.
This is a good point though. I wonder the best way to generalize the generator logic to multi-parameter gates

Resolving patch:
diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py
--- a/pennylane/ops/qubit.py
+++ b/pennylane/ops/qubit.py
@@ -972,6 +972,44 @@ def _matrix(cls, *params):
             list(range(len(pauli_word))),
         )
 
+    _generator = None
+
+    @property
+    def generator(self):
+        if self._generator is None:
+            pauli_word = self.parameters[1]
+
+            # Simplest case is if the Pauli is the identity matrix
+            if pauli_word == "I" * len(pauli_word):
+                self._generator = [np.eye(2 ** len(pauli_word)), -1 / 2]
+                return self._generator
+
+            # We first generate the matrix excluding the identity parts and expand it afterwards.
+            # To this end, we have to store on which wires the non-identity parts act
+            non_identity_wires, non_identity_gates = zip(
+                *[(wire, gate) for wire, gate in enumerate(pauli_word) if gate != "I"]
+            )
+
+            # get MultiRZ's generator
+            multi_Z_rot_generator = np.diag(pauli_eigs(len(non_identity_gates)))
+
+            # now we conjugate with Hadamard and RX to create the Pauli string
+            conjugation_matrix = functools.reduce(
+                np.kron,
+                [PauliRot._PAULI_CONJUGATION_MATRICES[gate] for gate in non_identity_gates],
+            )
+
+            self._generator = [
+                expand(
+                    conjugation_matrix.T.conj() @ multi_Z_rot_generator @ conjugation_matrix,
+                    non_identity_wires,
+                    list(range(len(pauli_word))),
+                ),
+                -1 / 2,
+            ]
+
+        return self._generator
+
     @classmethod
     def _eigvals(cls, theta, pauli_word):
         # Identity must be treated specially because its eigenvalues are all the same


Test patch:
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -1046,6 +1046,43 @@ def test_init_incorrect_pauli_word_length_error(self, pauli_word, wires):
         ):
             qml.PauliRot(0.3, pauli_word, wires=wires)
 
+    @pytest.mark.parametrize(
+        "pauli_word",
+        [
+            ("XIZ"),
+            ("IIII"),
+            ("XIYIZI"),
+            ("IXI"),
+            ("IIIIIZI"),
+            ("XYZIII"),
+            ("IIIXYZ"),
+        ],
+    )
+    def test_multirz_generator(self, pauli_word):
+        """Test that the generator of the MultiRZ gate is correct."""
+        op = qml.PauliRot(0.3, pauli_word, wires=range(len(pauli_word)))
+        gen = op.generator
+
+        if pauli_word[0] == 'I':
+            # this is the identity
+            expected_gen = qml.Identity(wires=0) 
+        else:
+            expected_gen = getattr(
+                qml, 'Pauli{}'.format(pauli_word[0]))(wires=0)
+
+        for i, pauli in enumerate(pauli_word[1:]):
+            i += 1
+            if pauli == 'I':
+                expected_gen = expected_gen @  qml.Identity(
+                    wires=i) 
+            else:
+                expected_gen = expected_gen @ getattr(
+                    qml, 'Pauli{}'.format(pauli))(wires=i)
+
+        expected_gen_mat = expected_gen.matrix
+
+        assert np.allclose(gen[0], expected_gen_mat)
+        assert gen[1] == -0.5
 
 class TestMultiRZ:
     """Test the MultiRZ operation."""
@@ -1178,6 +1215,7 @@ def test_multirz_generator(self, qubits, mocker):
         spy.assert_not_called()
 
 
+
 class TestDiagonalQubitUnitary:
     """Test the DiagonalQubitUnitary operation."""