Repository: PennyLaneAI/pennylane
Pull request #5168 (2024-02-07 00:36:04)

Add measurements in `qml.from_qiskit`
**Context:** Allows adding PennyLane measurements in `qml.from_qiskit`

**Description of the Change:** Added optional `kwargs` to `qml.load` for adding the required support. 

**Benefits:** Users can specify the measurements they require at the end of build `QNode` from a Qiskit's `QuantumCircuit`.

**Possible Drawbacks:** N/A

**Related GitHub Issues:** N/A


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.
obliviateandsurrender: [sc-56071]
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5168?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 [(`c1a813d`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/c1a813d3df83479527ad6751e5431a08294664a8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.68% compared to head [(`eb9dc90`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5168?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.68%.


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


```diff
@@            Coverage Diff             @@
##           master    #5168      +/-   ##
==========================================
- Coverage   99.68%   99.68%   -0.01%     
==========================================
  Files         394      394              
  Lines       36160    35888     -272     
==========================================
- Hits        36047    35774     -273     
- Misses        113      114       +1     
```



</details>

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


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 6d2415e63..7da8e010e 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -276,6 +276,10 @@
   are being raised unexpectedly.
   [(#5122)](https://github.com/PennyLaneAI/pennylane/pull/5122)
 
+* Users can specify a list of PennyLane `measurements` they would want as terminal measurements
+  when converting a `QuantumCircuit` using `qml.from_qiskit`.
+  [(#5168)](https://github.com/PennyLaneAI/pennylane/pull/5168)
+
 <h3>Breaking changes 💔</h3>
 
 * The entry point convention registering compilers with PennyLane has changed.
diff --git a/pennylane/io.py b/pennylane/io.py
index 5e2f3b436..752269f66 100644
--- a/pennylane/io.py
+++ b/pennylane/io.py
@@ -28,8 +28,8 @@ __plugin_devices = (
 plugin_converters = {entry.name: entry for entry in __plugin_devices}
 
 
-def load(quantum_circuit_object, format: str):
-    """Load external quantum assembly and quantum circuits from supported frameworks
+def load(quantum_circuit_object, format: str, **load_kwargs):
+    r"""Load external quantum assembly and quantum circuits from supported frameworks
     into PennyLane templates.
 
     .. note::
@@ -58,6 +58,13 @@ def load(quantum_circuit_object, format: str):
         quantum_circuit_object: the quantum circuit that will be converted
             to a PennyLane template
         format (str): the format of the quantum circuit object to convert from
+        **load_kwargs: keyword argument to pass when converting the quantum circuit
+            using the plugin
+
+    Keyword Args:
+        measurements (list[MeasurementProcess]): the list of PennyLane measurements that
+            overrides the terminal measurements that may be present in the imput circuit.
+            Currently, only supported for Qiskit's `QuantumCircuit <https://docs.pennylane.ai/projects/qiskit>`_.
 
     Returns:
         function: the PennyLane template created from the quantum circuit
@@ -69,7 +76,7 @@ def load(quantum_circuit_object, format: str):
         plugin_converter = plugin_converters[format].load()
 
         # calls the load function of the converter on the quantum circuit object
-        return plugin_converter(quantum_circuit_object)
+        return plugin_converter(quantum_circuit_object, **(load_kwargs or {}))
 
     raise ValueError(
         "Converter does not exist. Make sure the required plugin is installed "
@@ -77,7 +84,7 @@ def load(quantum_circuit_object, format: str):
     )
 
 
-def from_qiskit(quantum_circuit):
+def from_qiskit(quantum_circuit, measurements=None):
     """Loads Qiskit QuantumCircuit objects by using the converter in the
     PennyLane-Qiskit plugin.
 
@@ -99,12 +106,43 @@ def from_qiskit(quantum_circuit):
 
     Args:
         quantum_circuit (qiskit.QuantumCircuit): a quantum circuit created in qiskit
+        measurements (list[MeasurementProcess]): the list of PennyLane measurements that
+            overrides the terminal measurements that may be present in the input circuit.
 
     Returns:
-        function: the PennyLane template created based on the QuantumCircuit
-        object
+        function: the PennyLane template created based on the QuantumCircuit object
+
+    .. details::
+        :title: Usage Details
+
+        The ``measurement`` keyword allows one to add a list of PennyLane measurements
+        that will override the terminal measurements present in their ``QuantumCircuit``.
+
+        .. code-block:: python
+
+            import pennylane as qml
+            from qiskit import QuantumCircuit
+
+            qc = QuantumCircuit(2, 2)
+            qc.h(0)
+            qc.measure(0, 0)
+            qc.rz(0.24, [0])
+            qc.cx(0, 1)
+            qc.measure_all()
+
+            measurements = [qml.expval(qml.PauliZ(0)), qml.vn_entropy([1])]
+            quantum_circuit = qml.from_qiskit(qc, measurements=measurements)
+
+            @qml.qnode(qml.device("default.qubit"))
+            def circuit_loaded_qiskit_circuit():
+                return quantum_circuit()
+
+        >>> print(qml.draw(circuit_loaded_qiskit_circuit)())
+        0: ──H──┤↗├──RZ(0.24)─╭●─┤  <Z>
+        1: ───────────────────╰X─┤  vnentropy
+
     """
-    return load(quantum_circuit, format="qiskit")
+    return load(quantum_circuit, format="qiskit", measurements=measurements)
 
 
 def from_qasm(quantum_circuit: str):


Test patch:
diff --git a/tests/test_io.py b/tests/test_io.py
index 386dcfa95..9a6504818 100644
--- a/tests/test_io.py
+++ b/tests/test_io.py
@@ -42,6 +42,11 @@ class MockPluginConverter:
         """The last call arguments of the mocked loader."""
         return self.mock_loader.call_args[0]
 
+    @property
+    def call_args(self):
+        """The last call arguments of the mocked loader."""
+        return self.mock_loader.call_args
+
 
 load_entry_points = ["qiskit", "qasm", "qasm_file", "pyquil_program", "quil", "quil_file"]
 
@@ -91,3 +96,27 @@ class TestLoad:
 
             if mock_plugin_converters[plugin_converter].called:
                 raise Exception(f"The other plugin converter {plugin_converter} was called.")
+
+    @pytest.mark.parametrize(
+        "method, entry_point_name",
+        [
+            (qml.from_qiskit, "qiskit"),
+        ],
+    )
+    def test_convenience_functions_kwargs(self, method, entry_point_name, mock_plugin_converters):
+        """Test that the convenience load functions access the correct entrypoint with keywords."""
+
+        method("Test", measurements=[])
+
+        assert mock_plugin_converters[entry_point_name].called
+
+        args, kwargs = mock_plugin_converters[entry_point_name].call_args
+        assert args == ("Test",)
+        assert kwargs == {"measurements": []}
+
+        for plugin_converter in mock_plugin_converters:
+            if plugin_converter == entry_point_name:
+                continue
+
+            if mock_plugin_converters[plugin_converter].called:
+                raise Exception(f"The other plugin converter {plugin_converter} was called.")