From 13a337f9be6ed9484164c3e8335d4db31cfc89ec Mon Sep 17 00:00:00 2001 From: Alvan Caleb Arulandu Date: Fri, 14 Feb 2025 19:57:56 -0500 Subject: [PATCH 1/2] standalone measurement --- src/pyqasm/visitor.py | 92 ++++++++++++++++++++------------- tests/qasm3/test_measurement.py | 23 ++++++++- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 8da003e7..ea3c4b15 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -343,8 +343,11 @@ def _get_op_bits( original_size_map = reg_size_map if isinstance(operation, qasm3_ast.QuantumMeasurementStatement): - assert operation.target is not None - bit_list = [operation.measure.qubit] if qubits else [operation.target] + if qubits: + bit_list = [operation.measure.qubit] + else: + assert operation.target is not None + bit_list = [operation.target] elif isinstance(operation, qasm3_ast.QuantumPhase) and operation.qubits is None: for reg_name, reg_size in reg_size_map.items(): bit_list.append( @@ -440,7 +443,6 @@ def _visit_measurement( # pylint: disable=too-many-locals source = statement.measure.qubit target = statement.target - assert source and target # # TODO: handle in-function measurements source_name: str = ( @@ -453,51 +455,67 @@ def _visit_measurement( # pylint: disable=too-many-locals span=statement.span, ) - target_name: str = ( - target.name if isinstance(target, qasm3_ast.Identifier) else target.name.name - ) - if target_name not in self._global_creg_size_map: - raise_qasm3_error( - f"Missing register declaration for {target_name} in measurement " - f"operation {statement}", - span=statement.span, - ) - source_ids = self._get_op_bits( statement, reg_size_map=self._global_qreg_size_map, qubits=True ) - target_ids = self._get_op_bits( - statement, reg_size_map=self._global_creg_size_map, qubits=False - ) - if len(source_ids) != len(target_ids): - raise_qasm3_error( - f"Register sizes of {source_name} and {target_name} do not match " - "for measurement operation", - span=statement.span, - ) unrolled_measurements = [] - for src_id, tgt_id in zip(source_ids, target_ids): - unrolled_measure = qasm3_ast.QuantumMeasurementStatement( - measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=tgt_id + + if not target: + for src_id in source_ids: + unrolled_measurements.append( + qasm3_ast.QuantumMeasurementStatement( + measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=None + ) + ) + src_name, src_id = src_id.name.name, src_id.indices[0][0].value # type: ignore + qubit_node = self._module._qubit_depths[(src_name, src_id)] + qubit_node.depth += 1 + qubit_node.num_measurements += 1 + else: + target_name: str = ( + target.name if isinstance(target, qasm3_ast.Identifier) else target.name.name ) - src_name, src_id = src_id.name.name, src_id.indices[0][0].value # type: ignore - tgt_name, tgt_id = tgt_id.name.name, tgt_id.indices[0][0].value # type: ignore + if target_name not in self._global_creg_size_map: + raise_qasm3_error( + f"Missing register declaration for {target_name} in measurement " + f"operation {statement}", + span=statement.span, + ) - qubit_node, clbit_node = ( - self._module._qubit_depths[(src_name, src_id)], - self._module._clbit_depths[(tgt_name, tgt_id)], + target_ids = self._get_op_bits( + statement, reg_size_map=self._global_creg_size_map, qubits=False ) - qubit_node.depth += 1 - qubit_node.num_measurements += 1 - clbit_node.depth += 1 - clbit_node.num_measurements += 1 + if len(source_ids) != len(target_ids): + raise_qasm3_error( + f"Register sizes of {source_name} and {target_name} do not match " + "for measurement operation", + span=statement.span, + ) + + for src_id, tgt_id in zip(source_ids, target_ids): + unrolled_measure = qasm3_ast.QuantumMeasurementStatement( + measure=qasm3_ast.QuantumMeasurement(qubit=src_id), + target=tgt_id if target else None, + ) + src_name, src_id = src_id.name.name, src_id.indices[0][0].value # type: ignore + tgt_name, tgt_id = tgt_id.name.name, tgt_id.indices[0][0].value # type: ignore + + qubit_node, clbit_node = ( + self._module._qubit_depths[(src_name, src_id)], + self._module._clbit_depths[(tgt_name, tgt_id)], + ) + qubit_node.depth += 1 + qubit_node.num_measurements += 1 + + clbit_node.depth += 1 + clbit_node.num_measurements += 1 - qubit_node.depth = max(qubit_node.depth, clbit_node.depth) - clbit_node.depth = max(qubit_node.depth, clbit_node.depth) + qubit_node.depth = max(qubit_node.depth, clbit_node.depth) + clbit_node.depth = max(qubit_node.depth, clbit_node.depth) - unrolled_measurements.append(unrolled_measure) + unrolled_measurements.append(unrolled_measure) if self._check_only: return [] diff --git a/tests/qasm3/test_measurement.py b/tests/qasm3/test_measurement.py index 54bee92f..b72c81f6 100644 --- a/tests/qasm3/test_measurement.py +++ b/tests/qasm3/test_measurement.py @@ -160,7 +160,28 @@ def test_init_measure(): module = loads(qasm3_string) module.unroll() - print(dumps(module)) + check_unrolled_qasm(dumps(module), expected_qasm) + + +def test_standalone_measurement(): + qasm3_string = """ + OPENQASM 3.0; + qubit[2] q; + h q; + measure q; + """ + + expected_qasm = """ + OPENQASM 3.0; + qubit[2] q; + h q[0]; + h q[1]; + measure q[0]; + measure q[1]; + """ + + module = loads(qasm3_string) + module.unroll() check_unrolled_qasm(dumps(module), expected_qasm) From c3fe66af07841fa1ab1009e5467c19ac8b105fd7 Mon Sep 17 00:00:00 2001 From: Alvan Caleb Arulandu Date: Fri, 14 Feb 2025 20:00:45 -0500 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e00bcb60..9920a25c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Types of changes: ## Unreleased ### Added +- Added support for standalone measurements that do not store the result in a classical register ([#141](https://github.com/qBraid/pyqasm/pull/141)). ### Improved / Modified - Re-wrote the `QasmAnalyzer.extract_qasm_version` method so that it extracts the program version just by looking at the [first non-comment line](https://github.com/openqasm/openqasm/blob/bb923eb9a84fdffe1ba6fc3c20d0b47a131523d9/source/language/comments.rst#version-string), instead of parsing the entire program ([#140](https://github.com/qBraid/pyqasm/pull/140)).