From 2a767f655b642c331c4f104866609d6f816c7caa Mon Sep 17 00:00:00 2001 From: Isobel Hooper Date: Thu, 24 Oct 2024 11:24:30 +0100 Subject: [PATCH 1/3] Remove ppcirc parameter from qiskit_experimentresult_to_backendresult It's not included in any of the existing calls to this function, and the last time it seems to have been used is by IBMQBackend before the Qiskit API changed (since now IBMQBackend.get_results() handles its own result conversion). --- pytket/extensions/qiskit/result_convert.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pytket/extensions/qiskit/result_convert.py b/pytket/extensions/qiskit/result_convert.py index 2e953966..7bd0e05a 100644 --- a/pytket/extensions/qiskit/result_convert.py +++ b/pytket/extensions/qiskit/result_convert.py @@ -83,7 +83,6 @@ def _result_is_empty_shots(result: ExperimentResult) -> bool: def qiskit_experimentresult_to_backendresult( result: ExperimentResult, - ppcirc: Optional[Circuit] = None, ) -> BackendResult: if not result.success: raise RuntimeError(result.status) @@ -140,7 +139,7 @@ def qiskit_experimentresult_to_backendresult( state=state, unitary=unitary, density_matrix=density_matrix, - ppcirc=ppcirc, + ppcirc=None, ) From 8dbbdf4e799bf8897c8e2caa4253cdfe8d2d5dd9 Mon Sep 17 00:00:00 2001 From: Isobel Hooper Date: Thu, 24 Oct 2024 11:42:50 +0100 Subject: [PATCH 2/3] Add include_foo arguments to qiskit result conversions And when we call those conversion methods from _AerBaseBackend.get_results(), pass what we know about what the backend is expected to support. This should mean that if we run anything on AerStateBackend or AerUnitaryBackend, we don't get counts passed back even if the Qiskit result included them. --- pytket/extensions/qiskit/backends/aer.py | 9 ++++- pytket/extensions/qiskit/result_convert.py | 39 +++++++++++++++++----- tests/qiskit_convert_test.py | 7 +++- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index ba64c275..2eaa0aa7 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -348,7 +348,14 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul raise CircuitNotRunError(handle) res = job.result() - backresults = qiskit_result_to_backendresult(res) + backresults = qiskit_result_to_backendresult( + res, + include_shots=self._supports_shots, + include_counts=self._supports_counts, + include_state=self._supports_state, + include_unitary=self._supports_unitary, + include_density_matrix=self._supports_density_matrix, + ) for circ_index, backres in enumerate(backresults): self._cache[ResultHandle(jobid, circ_index, qubit_n, ppc)][ "result" diff --git a/pytket/extensions/qiskit/result_convert.py b/pytket/extensions/qiskit/result_convert.py index 7bd0e05a..1ffdf092 100644 --- a/pytket/extensions/qiskit/result_convert.py +++ b/pytket/extensions/qiskit/result_convert.py @@ -81,8 +81,17 @@ def _result_is_empty_shots(result: ExperimentResult) -> bool: return False +# In some cases, Qiskit returns a result with fields we don't expect - +# for example, a circuit with classical bits run on AerStateBackend will +# return counts (whether or not there were measurements). The include_foo +# arguments should be set based on what the backend supports. def qiskit_experimentresult_to_backendresult( result: ExperimentResult, + include_counts: bool = True, + include_shots: bool = True, + include_state: bool = True, + include_unitary: bool = True, + include_density_matrix: bool = True, ) -> BackendResult: if not result.success: raise RuntimeError(result.status) @@ -104,16 +113,16 @@ def qiskit_experimentresult_to_backendresult( shots, counts, state, unitary, density_matrix = (None,) * 5 datadict = result.data.to_dict() - if _result_is_empty_shots(result): + if _result_is_empty_shots(result) and include_shots: n_bits = len(c_bits) if c_bits else 0 shots = OutcomeArray.from_readouts( np.zeros((result.shots, n_bits), dtype=np.uint8) ) else: - if "memory" in datadict: + if "memory" in datadict and include_shots: memory = datadict["memory"] shots = _hex_to_outar(memory, width) - elif "counts" in datadict: + elif "counts" in datadict and include_counts: qis_counts = datadict["counts"] counts = Counter( dict( @@ -122,13 +131,13 @@ def qiskit_experimentresult_to_backendresult( ) ) - if "statevector" in datadict: + if "statevector" in datadict and include_state: state = datadict["statevector"].reverse_qargs().data - if "unitary" in datadict: + if "unitary" in datadict and include_unitary: unitary = datadict["unitary"].reverse_qargs().data - if "density_matrix" in datadict: + if "density_matrix" in datadict and include_density_matrix: density_matrix = datadict["density_matrix"].reverse_qargs().data return BackendResult( @@ -143,9 +152,23 @@ def qiskit_experimentresult_to_backendresult( ) -def qiskit_result_to_backendresult(res: Result) -> Iterator[BackendResult]: +def qiskit_result_to_backendresult( + res: Result, + include_counts: bool = True, + include_shots: bool = True, + include_state: bool = True, + include_unitary: bool = True, + include_density_matrix: bool = True, +) -> Iterator[BackendResult]: for result in res.results: - yield qiskit_experimentresult_to_backendresult(result) + yield qiskit_experimentresult_to_backendresult( + result, + include_counts, + include_shots, + include_state, + include_unitary, + include_density_matrix, + ) def backendresult_to_qiskit_resultdata( diff --git a/tests/qiskit_convert_test.py b/tests/qiskit_convert_test.py index 5dec4e86..e592a6c0 100644 --- a/tests/qiskit_convert_test.py +++ b/tests/qiskit_convert_test.py @@ -549,12 +549,17 @@ def test_convert_result() -> None: qc1.save_state() qisk_result = simulator.run(qc1, shots=10).result() - tk_res = next(qiskit_result_to_backendresult(qisk_result)) + # exclude counts from result (we don't expect them + # for the statevector sim after all) + tk_res = next(qiskit_result_to_backendresult(qisk_result, include_counts=False)) state = tk_res.get_state([Qubit("q2", 1), Qubit("q1", 0), Qubit("q2", 0)]) correct_state = np.zeros(1 << 3, dtype=complex) correct_state[6] = 1 + 0j assert compare_statevectors(state, correct_state) + # also check that we don't return counts in tket result + # even if the qiskit result includes them + assert tk_res._counts is None # check measured qc.measure(qr1[0], cr[0]) From 6f320e2b731828449d7570e8d5aefc71ed758559 Mon Sep 17 00:00:00 2001 From: Isobel Hooper Date: Thu, 24 Oct 2024 11:51:44 +0100 Subject: [PATCH 3/3] Run pylint linting --- pytket/extensions/qiskit/result_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/result_convert.py b/pytket/extensions/qiskit/result_convert.py index 1ffdf092..42b38347 100644 --- a/pytket/extensions/qiskit/result_convert.py +++ b/pytket/extensions/qiskit/result_convert.py @@ -26,7 +26,7 @@ from qiskit.result import Result # type: ignore from qiskit.result.models import ExperimentResult # type: ignore -from pytket.circuit import Bit, Qubit, UnitID, Circuit +from pytket.circuit import Bit, Qubit, UnitID from pytket.backends.backendresult import BackendResult from pytket.utils.outcomearray import OutcomeArray