From 61b0821c03592674bd659de16a97aa62ff095c1f Mon Sep 17 00:00:00 2001 From: QFer <46991941+QFer@users.noreply.github.com> Date: Tue, 28 Jun 2022 12:46:50 +0200 Subject: [PATCH] [SDS-617] Fix measurement statements in ProjectQ (#143) --- .travis.yml | 2 +- src/quantuminspire/projectq/backend_qx.py | 21 +++---------- .../projectq/test_backend_qx.py | 31 +++++++------------ 3 files changed, 18 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 78cf268..8b6aee2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ matrix: - name: python 3.7 python: 3.7 if: branch = dev - dist: focal + dist: bionic - name: python 3.8 python: 3.8 if: branch = dev diff --git a/src/quantuminspire/projectq/backend_qx.py b/src/quantuminspire/projectq/backend_qx.py index 45c5421..038dea3 100644 --- a/src/quantuminspire/projectq/backend_qx.py +++ b/src/quantuminspire/projectq/backend_qx.py @@ -485,8 +485,8 @@ def _store(self, cmd: Command) -> None: self._qasm += f"\nbarrier q[{qb_str}]" return - # when we find a gate after measurements we don't have fsp - # add any delayed measurement statements + # When fsp is enabled and we have skipped measurements when we find a gate after these measurements, + # switch to non-fsp and add any delayed measurement statements. if self._full_state_projection and len(self._measured_ids) != 0: self._switch_fsp_to_nonfsp() @@ -638,11 +638,6 @@ def _run(self) -> None: if self._qasm == "": return - # Finally: add measurement commands for all qubits if no measurements are given. - # Only for simulation backends, measurements after all gate operations will perform properly in FSP. - if not self._measured_ids and self._is_simulation_backend: - self.__add_measure_all_qubits() - self._finalize_qasm() self._execute_cqasm() self._filter_result_by_measured_qubits() @@ -743,13 +738,15 @@ def __init__(self, qubit_id: int) -> None: def _get_measured_qubit_iterator(self, measurement_block_index: int) -> Iterator[int]: """ Get an iterator for the measured qubits. The iterator returns the indexes of the qubits that are measured. + With fsp, the simulator will return all qubits as measured, but when there were measurements in the algorithm + we want to get these measured qubits only. :param measurement_block_index: measurement block index for multi measurement results. :return: Iterator which iterated the indexes of the measured qubits """ - if self._full_state_projection: + if self._full_state_projection and len(self._measured_ids) != 0: bit_iterator = map(lambda bit: self._physical_to_simulated(self._logical_to_physical(bit)), self._measured_ids) else: @@ -788,11 +785,3 @@ def receive(self, command_list: List[Command]) -> None: self._run() self._flushed = True self._reset() - - def __add_measure_all_qubits(self) -> None: - """ Adds measurements at the end of the quantum algorithm for all allocated qubits. """ - qubits_reference = self.main_engine.active_qubits.copy() - qubits_counts = len(qubits_reference) - for _ in range(qubits_counts): - q = qubits_reference.pop() - Measure | q diff --git a/src/tests/quantuminspire/projectq/test_backend_qx.py b/src/tests/quantuminspire/projectq/test_backend_qx.py index 7433472..4d48a4c 100644 --- a/src/tests/quantuminspire/projectq/test_backend_qx.py +++ b/src/tests/quantuminspire/projectq/test_backend_qx.py @@ -21,7 +21,7 @@ import coreapi from collections import OrderedDict from functools import reduce -from unittest import mock, TestCase +from unittest import TestCase from unittest.mock import MagicMock, patch from projectq.meta import LogicalQubitIDTag @@ -596,6 +596,7 @@ def test_receive(self, function_mock): command = MagicMock(gate=NOT, qubits=[[MagicMock(id=0)]], control_qubits=[MagicMock(id=1)]) command_list = [command_alloc0, command_alloc1, command, MagicMock(gate=FlushGate())] self.qi_backend.main_engine = MagicMock() + self.qi_backend.main_engine.mapper.current_mapping = {0: 0, 1: 1} # bits 0 and 1 are logical bits 0 and 1 with patch('sys.stdout', new_callable=io.StringIO): self.qi_backend.receive(command_list) self.assertEqual(self.qi_backend.qasm, "") @@ -611,6 +612,7 @@ def test_reuse_after_flush_raises_runtime_error(self, function_mock): api = MockApiClient() backend = QIBackend(quantum_inspire_api=api) backend.main_engine = MagicMock() + backend.main_engine.mapper.current_mapping = {0: 0, 1: 1} # bits 0 and 1 are logical bits 0 and 1 with patch('sys.stdout', new_callable=io.StringIO): self.assertRaisesRegex(RuntimeError, "Same instance of QIBackend used for circuit after Flush.", backend.receive, command_list) @@ -624,6 +626,7 @@ def test_receive_multiple_flush(self, function_mock): command_list = [command_alloc0, command_alloc1, command, MagicMock(gate=FlushGate()), MagicMock(gate=FlushGate())] self.qi_backend.main_engine = MagicMock() + self.qi_backend.main_engine.mapper.current_mapping = {0: 0, 1: 1} # bits 0 and 1 are logical bits 0 and 1 with patch('sys.stdout', new_callable=io.StringIO): self.qi_backend.receive(command_list) self.assertEqual(self.qi_backend.qasm, "") @@ -918,32 +921,22 @@ def test_run_has_correct_output(self): self.assertTrue(std_output.startswith('version 1.0\n# cQASM generated by Quantum Inspire')) self.assertTrue('qubits 0' in std_output) - def test_run_raises_error_no_result(self): - with patch('sys.stdout', new_callable=io.StringIO): - self.qi_backend.qasm = "_" - self.qi_backend.measured_ids = [0, 1] - self.qi_backend.allocation_map = [(0, 0), (1, 1)] - self.qi_backend.main_engine = MagicMock() - self.qi_backend.main_engine.mapper.current_mapping = {0: 0, 1: 1} - result_mock = MagicMock() - result_mock.get.return_value = [OrderedDict()] - self.api.execute_qasm.return_value = result_mock - self.assertRaisesRegex(ProjectQBackendError, 'Result from backend contains no histogram data!', - self.qi_backend.run) - self.api.execute_qasm.assert_called_once() - - @patch('quantuminspire.projectq.backend_qx.Measure') - def test_run_no_measurements(self, measure_mock): + def _run_raises_error_no_result(self, return_val): with patch('sys.stdout', new_callable=io.StringIO): self.qi_backend.qasm = "_" self.qi_backend.measured_ids = [] self.qi_backend.allocation_map = [(0, 0), (1, 1)] self.qi_backend.main_engine = MagicMock() - self.qi_backend.main_engine.active_qubits = [0, 1] self.qi_backend.main_engine.mapper.current_mapping = {0: 0, 1: 1} result_mock = MagicMock() - result_mock.get.return_value = [{}] + result_mock.get.return_value = return_val self.api.execute_qasm.return_value = result_mock self.assertRaisesRegex(ProjectQBackendError, 'Result from backend contains no histogram data!', self.qi_backend.run) self.api.execute_qasm.assert_called_once() + + def test_run_raises_error_no_result_as_ordered_dict(self): + self._run_raises_error_no_result([OrderedDict()]) + + def test_run_raises_error_no_result_as_empty_dict(self): + self._run_raises_error_no_result([{}])