From de71f7caae503df3a3bc111680598bfd9f77b7e0 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 2 Sep 2025 21:28:04 -0500 Subject: [PATCH 1/8] Enhance QASM validation by moving unique qubit check to subroutine processor --- src/pyqasm/subroutines.py | 40 +++++++++++++++++++++++++--- src/pyqasm/validator.py | 21 --------------- tests/qasm3/resources/subroutines.py | 17 ++++++++++++ 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/pyqasm/subroutines.py b/src/pyqasm/subroutines.py index 31bc8d19..4748b6ce 100644 --- a/src/pyqasm/subroutines.py +++ b/src/pyqasm/subroutines.py @@ -48,7 +48,6 @@ from pyqasm.exceptions import ValidationError, raise_qasm3_error from pyqasm.expressions import Qasm3ExprEvaluator from pyqasm.transformer import Qasm3Transformer -from pyqasm.validator import Qasm3Validator class Qasm3SubroutineProcessor: @@ -65,6 +64,41 @@ def set_visitor_obj(cls, visitor_obj) -> None: """ cls.visitor_obj = visitor_obj + @staticmethod + def validate_unique_qubits(qubit_map: dict, reg_name: str, indices: list) -> bool: + """ + Validate that qubits passed for a given actual register are unique across + all quantum arguments in a function call and within the same argument itself. + + This function mutates the provided `qubit_map` by tracking which indices of + each register have already been used while validating earlier arguments. + + Args: + qubit_map (dict): Map used for duplicate detection; keys are register names, + values are sets of previously seen indices for that register. + reg_name (str): Actual register name appearing in the call (e.g., 'q'). + indices (list): Concrete qubit indices being bound for this argument. + + Returns: + bool: False if any duplicate is detected (within this argument or across + previously processed arguments); True otherwise. On success, the + map is updated with the new indices for subsequent checks. + """ + seen = qubit_map.setdefault(reg_name, set()) + + # Reject duplicates within the same argument (e.g., q[0], q[0]). + if len(set(indices)) != len(indices): + return False + + # Reject duplicates against indices already seen for this register. + for idx in indices: + if idx in seen: + return False + + # Update the seen set so subsequent arguments are validated against it. + seen.update(indices) + return True + @staticmethod def get_fn_actual_arg_name(actual_arg: Identifier | IndexExpression) -> Optional[str]: """Get the name of the actual argument passed to a function. @@ -540,9 +574,7 @@ def process_quantum_arg( # pylint: disable=too-many-locals span=fn_call.span, ) - if not Qasm3Validator.validate_unique_qubits( - duplicate_qubit_map, actual_arg_name, actual_qids - ): + if not cls.validate_unique_qubits(duplicate_qubit_map, actual_arg_name, actual_qids): raise_qasm3_error( f"Duplicate qubit argument for register '{actual_arg_name}' " f"in function call for '{fn_name}'", diff --git a/src/pyqasm/validator.py b/src/pyqasm/validator.py index 9a4cd82e..7160e96c 100644 --- a/src/pyqasm/validator.py +++ b/src/pyqasm/validator.py @@ -340,24 +340,3 @@ def validate_return_statement( # pylint: disable=inconsistent-return-statements return_value, op_node=return_statement, ) - - @staticmethod - def validate_unique_qubits(qubit_map: dict, reg_name: str, indices: list) -> bool: - """ - Validates that the qubits in the given register are unique. - - Args: - qubit_map (dict): Dictionary of qubits. - reg_name (str): The name of the register. - indices (list): A list of indices representing the qubits. - - Returns: - bool: True if the qubits are unique, False otherwise. - """ - if reg_name not in qubit_map: - qubit_map[reg_name] = set(indices) - else: - for idx in indices: - if idx in qubit_map[reg_name]: - return False - return True diff --git a/tests/qasm3/resources/subroutines.py b/tests/qasm3/resources/subroutines.py index 5d3dd8e8..68579c8e 100644 --- a/tests/qasm3/resources/subroutines.py +++ b/tests/qasm3/resources/subroutines.py @@ -29,6 +29,23 @@ 8, # Column number "my_function(1)", # Complete line ), + "test_duplicate_qubit_args_singletons": ( + """ + OPENQASM 3; + include "stdgates.inc"; + + def my_function(qubit a, qubit b) { + h b; + return; + } + qubit[2] q; + my_function(q[0], q[0]); + """, + r"Duplicate qubit argument for register 'q' in function call for 'my_function'", + 10, + 8, + "my_function(q[0], q[0])", + ), "redefinition_raises_error": ( """ OPENQASM 3; From e4552c275198f308abd674ea3697b19f9b8330b4 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 2 Sep 2025 21:34:37 -0500 Subject: [PATCH 2/8] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b02650..a9b3c04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Types of changes: ### Fixed - Fixed Complex value initialization error. ([#253](https://github.com/qBraid/pyqasm/pull/253)) +- Fixed duplicate qubit argument check in function calls. ([#260](https://github.com/qBraid/pyqasm/pull/260)) ### Dependencies - Bumps `@actions/checkout` from 4 to 5 ([#250](https://github.com/qBraid/pyqasm/pull/250)) From cb65298393123ba524617bc61578d8df135197a1 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 2 Sep 2025 22:04:54 -0500 Subject: [PATCH 3/8] update test case --- tests/qasm3/resources/subroutines.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/qasm3/resources/subroutines.py b/tests/qasm3/resources/subroutines.py index 68579c8e..000c46d9 100644 --- a/tests/qasm3/resources/subroutines.py +++ b/tests/qasm3/resources/subroutines.py @@ -46,6 +46,23 @@ def my_function(qubit a, qubit b) { 8, "my_function(q[0], q[0])", ), + "test_duplicate_qubit_args_singletons_2": ( + """ + OPENQASM 3; + include "stdgates.inc"; + + def my_function(qubit[2] p) { + h p; + return; + } + qubit[3] q; + my_function(q[{0, 0}]); + """, + r"Duplicate qubit argument for register 'q' in function call for 'my_function'", + 10, + 8, + "my_function(q[{0, 0}])", + ), "redefinition_raises_error": ( """ OPENQASM 3; From 788229cddfa19aa3adf74503caef6918c100a808 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 2 Sep 2025 23:57:38 -0500 Subject: [PATCH 4/8] Enhance alias handling in QASM by including alias register sizes for target qubit resolution and improving duplicate qubit validation for aliased arguments. --- src/pyqasm/subroutines.py | 29 +++++++++-- src/pyqasm/transformer.py | 4 +- src/pyqasm/visitor.py | 56 +++++++++------------ tests/qasm3/subroutines/test_subroutines.py | 30 +++++++++++ 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/pyqasm/subroutines.py b/src/pyqasm/subroutines.py index 4748b6ce..0045f34a 100644 --- a/src/pyqasm/subroutines.py +++ b/src/pyqasm/subroutines.py @@ -559,8 +559,15 @@ def process_quantum_arg( # pylint: disable=too-many-locals span=fn_call.span, ) + # Include alias register sizes when resolving actual target qubits + # so that aliased identifiers like `let a = q[i]; dummy(a);` are valid. + merged_size_map = { + **actual_qreg_size_map, + **getattr(cls.visitor_obj, "_global_alias_size_map", {}), + } + actual_qids, actual_qubits_size = Qasm3Transformer.get_target_qubits( - actual_arg, actual_qreg_size_map, actual_arg_name + actual_arg, merged_size_map, actual_arg_name ) if formal_qubit_size != actual_qubits_size: @@ -574,16 +581,28 @@ def process_quantum_arg( # pylint: disable=too-many-locals span=fn_call.span, ) - if not cls.validate_unique_qubits(duplicate_qubit_map, actual_arg_name, actual_qids): + # If the actual argument is an alias, resolve to the underlying + # register name and indices for duplicate detection and mapping. + resolved_reg_name = actual_arg_name + resolved_qids = list(actual_qids) + if getattr(actual_arg_var, "is_alias", False): + resolved_pairs = [ + cls.visitor_obj._alias_qubit_labels[(actual_arg_name, qid)] for qid in actual_qids + ] + # All alias pairs point to the same underlying register + resolved_reg_name = resolved_pairs[0][0] if resolved_pairs else actual_arg_name + resolved_qids = [pair[1] for pair in resolved_pairs] + + if not cls.validate_unique_qubits(duplicate_qubit_map, resolved_reg_name, resolved_qids): raise_qasm3_error( - f"Duplicate qubit argument for register '{actual_arg_name}' " + f"Duplicate qubit argument for register '{resolved_reg_name}' " f"in function call for '{fn_name}'", error_node=fn_call, span=fn_call.span, ) - for idx, qid in enumerate(actual_qids): - qubit_transform_map[(formal_reg_name, idx)] = (actual_arg_name, qid) + for idx, qid in enumerate(resolved_qids): + qubit_transform_map[(formal_reg_name, idx)] = (resolved_reg_name, qid) return Variable( name=formal_reg_name, diff --git a/src/pyqasm/transformer.py b/src/pyqasm/transformer.py index 0a09c65d..76f18396 100644 --- a/src/pyqasm/transformer.py +++ b/src/pyqasm/transformer.py @@ -409,7 +409,9 @@ def get_target_qubits( qid, qreg_size_map[target_name], qubit=True, op_node=target ) target_qubits_size = len(target_qids) - elif isinstance(target.index[0], (IntegerLiteral, Identifier)): # "(q[0]); OR (q[i]);" + elif isinstance( + target.index[0], (IntegerLiteral, Identifier, BinaryExpression) + ): # "(q[0]); OR (q[i]); OR (q[i+1]);" target_qids = [Qasm3ExprEvaluator.evaluate_expression(target.index[0])[0]] Qasm3Validator.validate_register_index( target_qids[0], qreg_size_map[target_name], qubit=True, op_node=target diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 887b6652..1e2d4c1e 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -2516,48 +2516,35 @@ def _visit_alias_statement(self, statement: qasm3_ast.AliasStatement) -> list[No ) aliased_reg_size = self._global_qreg_size_map[aliased_reg_name] if isinstance(value, qasm3_ast.Identifier): # "let alias = q;" - for i in range(aliased_reg_size): - self._alias_qubit_labels[(alias_reg_name, i)] = (aliased_reg_name, i) + target_qids = list(range(aliased_reg_size)) alias_reg_size = aliased_reg_size elif isinstance(value, qasm3_ast.IndexExpression): - if isinstance(value.index, qasm3_ast.DiscreteSet): # "let alias = q[{0,1}];" - qids = Qasm3Transformer.extract_values_from_discrete_set(value.index, statement) - for i, qid in enumerate(qids): + if isinstance(value.index, qasm3_ast.DiscreteSet): + target_qids = Qasm3Transformer.extract_values_from_discrete_set( + value.index, statement + ) + for qid in target_qids: Qasm3Validator.validate_register_index( qid, self._global_qreg_size_map[aliased_reg_name], qubit=True, op_node=statement, ) - self._alias_qubit_labels[(alias_reg_name, i)] = (aliased_reg_name, qid) - alias_reg_size = len(qids) - elif len(value.index) != 1: # like "let alias = q[0,1];"? - raise_qasm3_error( - "An index set can be specified by a single integer (signed or unsigned), " - "a comma-separated list of integers contained in braces {a,b,c,…}, " - "or a range", - error_node=statement, - span=statement.span, - ) - elif isinstance(value.index[0], qasm3_ast.IntegerLiteral): # "let alias = q[0];" - qid = value.index[0].value - Qasm3Validator.validate_register_index( - qid, self._global_qreg_size_map[aliased_reg_name], qubit=True, op_node=statement - ) - self._alias_qubit_labels[(alias_reg_name, 0)] = ( - aliased_reg_name, - value.index[0].value, - ) - alias_reg_size = 1 - elif isinstance(value.index[0], qasm3_ast.RangeDefinition): # "let alias = q[0:1:2];" - qids = Qasm3Transformer.get_qubits_from_range_definition( - value.index[0], - aliased_reg_size, - is_qubit_reg=True, + alias_reg_size = len(target_qids) + else: + if len(value.index) != 1: + raise_qasm3_error( + "An index set can be specified by a single integer (signed or unsigned), " + "a comma-separated list of integers contained in braces {a,b,c,…}, " + "or a range", + error_node=statement, + span=statement.span, + ) + target_qids, alias_reg_size = Qasm3Transformer.get_target_qubits( + value, {aliased_reg_name: aliased_reg_size}, aliased_reg_name ) - for i, qid in enumerate(qids): - self._alias_qubit_labels[(alias_reg_name, i)] = (aliased_reg_name, qid) - alias_reg_size = len(qids) + for i, qid in enumerate(target_qids): + self._alias_qubit_labels[(alias_reg_name, i)] = (aliased_reg_name, qid) # we are updating as the alias can be redefined as well alias_var = Variable( @@ -2569,6 +2556,9 @@ def _visit_alias_statement(self, statement: qasm3_ast.AliasStatement) -> list[No is_alias=True, span=statement.span, ) + # Mark alias variables that reference qubits as qubit variables so they + # can be passed as quantum arguments to subroutines. + alias_var.is_qubit = True if self._scope_manager.check_in_scope(alias_reg_name): # means, the alias is present in current scope diff --git a/tests/qasm3/subroutines/test_subroutines.py b/tests/qasm3/subroutines/test_subroutines.py index bf2551bb..2b30bc78 100644 --- a/tests/qasm3/subroutines/test_subroutines.py +++ b/tests/qasm3/subroutines/test_subroutines.py @@ -198,6 +198,36 @@ def my_function(int[32] a, qubit q_arg) { check_single_qubit_rotation_op(result.unrolled_ast, 3, [2, 1, 0], [2, 1, 0], "rx") +def test_alias_arg_from_loop_validates(): + """Alias of a dynamic indexed qubit used as function argument should validate.""" + qasm_str = """ + OPENQASM 3.0; + include "stdgates.inc"; + + qubit[4] q; + + def dummy(qubit[1] q_arg) -> bool { + h q_arg; + return true; + } + + for int i in [0:2] + { + let new_q = q[i]; + dummy(new_q); + } + for int i in [0:2] + { + let new_q = q[i+1]; + dummy(new_q); + } + """ + + result = loads(qasm_str) + # Should not raise ValidationError + result.unroll() + + def test_function_call_with_return(): """Test that a function call with a return value is correctly parsed.""" qasm_str = """OPENQASM 3.0; From 1a1eabb820b27752cb96f4cc6ec769fe56a5e601 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 3 Sep 2025 00:01:26 -0500 Subject: [PATCH 5/8] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b3c04b..f65ff579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,8 @@ Types of changes: ### Fixed - Fixed Complex value initialization error. ([#253](https://github.com/qBraid/pyqasm/pull/253)) -- Fixed duplicate qubit argument check in function calls. ([#260](https://github.com/qBraid/pyqasm/pull/260)) +- Fixed duplicate qubit argument check in function calls and Error in function call with aliased qubit. ([#260](https://github.com/qBraid/pyqasm/pull/260)) + ### Dependencies - Bumps `@actions/checkout` from 4 to 5 ([#250](https://github.com/qBraid/pyqasm/pull/250)) From 12744b717197da228c617e8ef10597df066c2f57 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Mon, 15 Sep 2025 16:06:38 -0500 Subject: [PATCH 6/8] update circuit drawer --- src/pyqasm/printer.py | 55 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/pyqasm/printer.py b/src/pyqasm/printer.py index 4d7ea4f0..7a7c52f3 100644 --- a/src/pyqasm/printer.py +++ b/src/pyqasm/printer.py @@ -185,7 +185,21 @@ def _compute_line_nums( return line_nums, sizes -# pylint: disable-next=too-many-branches +def _get_line_keys( + all_keys: list[tuple[str, int]], line_nums: dict[tuple[str, int], int] +) -> list[tuple[str, int]]: + if not all_keys: + return [] + + # Get line numbers for the given keys and find min/max + key_line_nums = [line_nums[key] for key in all_keys] + start_key, end_key = min(key_line_nums), max(key_line_nums) + + # Return all keys within the range + return [key for key, line_num in line_nums.items() if start_key <= line_num <= end_key] + + +# pylint: disable-next=too-many-branches,too-many-locals def _compute_moments( statements: list[ast.Statement | ast.Pragma], line_nums: dict[tuple[str, int], int] ) -> tuple[list[list[QuantumStatement]], dict[tuple[str, int], int]]: @@ -194,6 +208,21 @@ def _compute_moments( depths[k] = -1 moments: list[list[QuantumStatement]] = [] + # Find and remove final measurements (measurements at the end of the statements list) + final_measurements: list[ast.QuantumMeasurementStatement] = [] + seen_keys = set[tuple[str, int]]() + + for stmt in reversed(statements): + if not isinstance(stmt, ast.QuantumMeasurementStatement): + break + control_key = _identifier_to_key(stmt.measure.qubit) + if control_key not in seen_keys: + seen_keys.add(control_key) + final_measurements.append(stmt) + + # Remove the final measurements from the end of statements list + statements = statements[: -len(final_measurements)] if final_measurements else statements + for statement in statements: if isinstance(statement, Declaration): continue @@ -201,17 +230,22 @@ def _compute_moments( raise ValueError(f"Unsupported statement: {statement}") if isinstance(statement, ast.QuantumGate): qubits = [_identifier_to_key(q) for q in statement.qubits] - depth = 1 + max(depths[q] for q in qubits) - for q in qubits: - depths[q] = depth + # Get line keys for multi-qubit gates, otherwise use qubits directly + target_keys = _get_line_keys(qubits, line_nums) if len(qubits) > 1 else qubits + # Calculate new depth and update all affected keys + depth = 1 + max(depths[key] for key in target_keys) + for key in target_keys: + depths[key] = depth elif isinstance(statement, ast.QuantumMeasurementStatement): keys = [_identifier_to_key(statement.measure.qubit)] if statement.target: target_key = _identifier_to_key(statement.target)[0], -1 keys.append(target_key) - depth = 1 + max(depths[k] for k in keys) - for k in keys: - depths[k] = depth + line_keys = _get_line_keys(keys, line_nums) + # Calculate new depth and update all affected keys + depth = 1 + max(depths[key] for key in line_keys) + for key in line_keys: + depths[key] = depth elif isinstance(statement, ast.QuantumBarrier): qubits = [] for expr in statement.qubits: @@ -236,6 +270,13 @@ def _compute_moments( moments[depth].append(statement) + depth = max(depths.values()) + for measurement in final_measurements: + depth += 1 + if depth >= len(moments): + moments.append([]) + moments[depth].append(measurement) + return moments, depths From cb2f5a7f11e79b0b47a1fab905fa1c141e7495dd Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Mon, 15 Sep 2025 16:19:27 -0500 Subject: [PATCH 7/8] update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f65ff579..21069578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,7 +59,7 @@ Types of changes: ### Fixed - Fixed Complex value initialization error. ([#253](https://github.com/qBraid/pyqasm/pull/253)) - Fixed duplicate qubit argument check in function calls and Error in function call with aliased qubit. ([#260](https://github.com/qBraid/pyqasm/pull/260)) - +- Fixed Gate ordering across registers. ([#268](https://github.com/qBraid/pyqasm/pull/268)) ### Dependencies - Bumps `@actions/checkout` from 4 to 5 ([#250](https://github.com/qBraid/pyqasm/pull/250)) From c1e1b61de4d17945a4cf386e2236130a850e824c Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 16 Sep 2025 11:03:21 -0500 Subject: [PATCH 8/8] Add a new test for drawing circuits with random operations, including a new image for comparison. --- CHANGELOG.md | 2 +- tests/visualization/images/misc2.png | Bin 0 -> 38568 bytes tests/visualization/test_mpl_draw.py | 28 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/visualization/images/misc2.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce4974f..f5e43a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,7 @@ Types of changes: ### Fixed - Fixed Complex value initialization error. ([#253](https://github.com/qBraid/pyqasm/pull/253)) - Fixed duplicate qubit argument check in function calls and Error in function call with aliased qubit. ([#260](https://github.com/qBraid/pyqasm/pull/260)) -- Fixed Gate ordering across registers. ([#268](https://github.com/qBraid/pyqasm/pull/268)) +- Fixed Gate ordering across registers in `pyqasm.draw()` function. ([#268](https://github.com/qBraid/pyqasm/pull/268)) ======= diff --git a/tests/visualization/images/misc2.png b/tests/visualization/images/misc2.png new file mode 100644 index 0000000000000000000000000000000000000000..51c6f0f99e0da146d4d57ffa233307b48255e548 GIT binary patch literal 38568 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 literal 0 HcmV?d00001 diff --git a/tests/visualization/test_mpl_draw.py b/tests/visualization/test_mpl_draw.py index f80b78df..a76e6d57 100644 --- a/tests/visualization/test_mpl_draw.py +++ b/tests/visualization/test_mpl_draw.py @@ -156,6 +156,34 @@ def test_draw_misc_ops(): return fig +@pytest.mark.mpl_image_compare(baseline_dir="images", filename="misc2.png") +def test_draw_misc_ops_2(): + """Test drawing a circuit with random operations.""" + qasm3 = """ + OPENQASM 2.0; + include "qelib1.inc"; + gate iswap q0,q1 { s q0; s q1; h q0; cx q0,q1; cx q1,q0; h q1; } + qreg q[4]; + creg c[4]; + h q[0]; + h q[1]; + h q[2]; + h q[3]; + measure q[3] -> c[3]; + iswap q[2],q[3]; + swap q[0],q[2]; + measure q[2] -> c[2]; + swap q[1],q[3]; + swap q[0],q[2]; + cx q[0],q[1]; + measure q[3] -> c[1]; + cp(pi/4) q[2],q[3]; + measure q -> c; + """ + fig = mpl_draw(qasm3) + return fig + + def test_draw_raises_unsupported_format_error(): """Test that an error is raised for unsupported formats.""" qasm = """