From ce24a6191f7151d8770c1ec56747e7e3c1fa7b16 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 01:07:50 -0500 Subject: [PATCH 01/17] Modified test case for depth calculation. removed barriers in test_qasm3_depth_branching test case --- tests/qasm3/test_depth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/qasm3/test_depth.py b/tests/qasm3/test_depth.py index 487c1f2c..dae8919c 100644 --- a/tests/qasm3/test_depth.py +++ b/tests/qasm3/test_depth.py @@ -426,7 +426,7 @@ def test_qasm3_depth_no_branching(program, expected_depth): assert result.depth() == expected_depth -@pytest.mark.skip(reason="Not implemented branching conditions depth") +# @pytest.mark.skip(reason="Not implemented branching conditions depth") @pytest.mark.parametrize( "program, expected_depth", [ @@ -494,4 +494,5 @@ def test_qasm3_depth_branching(program, expected_depth): """Test calculating depth of qasm3 circuit with branching conditions""" result = loads(program) result.unroll() + result.remove_barriers() assert result.depth() == expected_depth From 704b352f9b07f88598fddf0c63a5dbea3e3ea971 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 01:09:37 -0500 Subject: [PATCH 02/17] Added depth calculation feature for branching statements --- src/pyqasm/visitor.py | 87 +++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index a454b3a3..803b0d60 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -89,7 +89,9 @@ def __init__( self._curr_scope: int = 0 self._label_scope_level: dict[int, set] = {self._curr_scope: set()} self._recording_ext_gate_depth = False - + self._is_branching_statement: bool = False + self._is_branch_qubits: set[tuple[str, int]] = set() + self._is_branch_clbits: set[tuple[str, int]] = set() self._init_utilities() def _init_utilities(self): @@ -500,10 +502,11 @@ def _visit_measurement( # pylint: disable=too-many-locals 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 + if not self._is_branching_statement: # if measurement gate is not in branching statement + 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 @@ -533,21 +536,22 @@ def _visit_measurement( # pylint: disable=too-many-locals 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 + if not self._is_branching_statement: # if measurement gate is not in branching statement + 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 + 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 + 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) @@ -852,7 +856,15 @@ def _visit_basic_gate_operation( # pylint: disable=too-many-locals self._broadcast_gate_operation(unrolled_gate_function, unrolled_targets, ctrls) ) - self._update_qubit_depth_for_gate(unrolled_targets, ctrls) + if not self._is_branching_statement: # if gate is not in branching statement + self._update_qubit_depth_for_gate(unrolled_targets, ctrls) + else: + for ops in unrolled_targets + ctrls : # get qubit registers in branching operations + for op in ops: + op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] + op.name.name + op_tuple = (op.name.name,op_idx) + self._is_branch_qubits.add(op_tuple) # check for duplicate bits for final_gate in result: @@ -952,7 +964,15 @@ def _visit_custom_gate_operation( # Update the depth only once for the entire custom gate if self._recording_ext_gate_depth: self._recording_ext_gate_depth = False - self._update_qubit_depth_for_gate([op_qubits], ctrls) + if not self._is_branching_statement: # if custom gate is not in branching statement + self._update_qubit_depth_for_gate([op_qubits], ctrls) + else: + for ops in [op_qubits] + ctrls: # get qubit registers in branching operations + for op in ops: + op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] + op.name.name + op_tuple = (op.name.name,op_idx) + self._is_branch_qubits.add(op_tuple) self._restore_context() @@ -1610,6 +1630,29 @@ def _evaluate_array_initialization( init_values.append(eval_value) return np.array(init_values, dtype=ARRAY_TYPE_MAP[base_type.__class__]) + + def _update_branching_gate_depths(self) -> None: # seperately update branching operators depth + self._is_branching_statement = False + max_depths = 0 + nodes = [] + for qbit in self._is_branch_qubits: + q_name, q_idx = qbit + q_node = self._module._qubit_depths[(q_name, q_idx)] + nodes.append(q_node) + max_depths = max(max_depths,q_node.depth + 1) + + for cbit in self._is_branch_clbits: + c_name, c_idx = cbit + c_node = self._module._clbit_depths[(c_name, c_idx)] + nodes.append(c_node) + max_depths = max(max_depths,c_node.depth + 1) + + for node in nodes: + node.depth = max_depths + + if not self._is_branching_statement: + self._is_branch_clbits.clear() + self._is_branch_qubits.clear() def _visit_branching_statement( self, statement: qasm3_ast.BranchingStatement @@ -1626,6 +1669,7 @@ def _visit_branching_statement( self._push_scope({}) self._curr_scope += 1 self._label_scope_level[self._curr_scope] = set() + self._is_branching_statement = True result = [] condition = statement.condition @@ -1649,6 +1693,9 @@ def _visit_branching_statement( ) assert isinstance(rhs_value, (bool, int)) + if reg_name is not None and reg_idx is not None and self._is_branching_statement: # get classical registers + op_tuple = (reg_name, reg_idx) + self._is_branch_clbits.add(op_tuple) if_block = self.visit_basic_block(statement.if_block) else_block = self.visit_basic_block(statement.else_block) @@ -1734,6 +1781,8 @@ def ravel(bit_ind): self._curr_scope -= 1 self._pop_scope() self._restore_context() + if self._is_branching_statement: + self._update_branching_gate_depths() if self._check_only: return [] From 515e1b9d68ca18e8ca68eed6537dcd25a57c604f Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 01:22:51 -0500 Subject: [PATCH 03/17] update depth calculation bugs --- src/pyqasm/visitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 803b0d60..e566679d 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -859,7 +859,7 @@ def _visit_basic_gate_operation( # pylint: disable=too-many-locals if not self._is_branching_statement: # if gate is not in branching statement self._update_qubit_depth_for_gate(unrolled_targets, ctrls) else: - for ops in unrolled_targets + ctrls : # get qubit registers in branching operations + for ops in unrolled_targets + [ctrls] : # get qubit registers in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] op.name.name @@ -967,7 +967,7 @@ def _visit_custom_gate_operation( if not self._is_branching_statement: # if custom gate is not in branching statement self._update_qubit_depth_for_gate([op_qubits], ctrls) else: - for ops in [op_qubits] + ctrls: # get qubit registers in branching operations + for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] op.name.name From e6dfc2a69015b878b5fcf28cbf331288dfd98708 Mon Sep 17 00:00:00 2001 From: Kapakayala Naga sai krishna vinay swami <66941388+vinayswamik@users.noreply.github.com> Date: Sat, 31 May 2025 14:11:20 -0500 Subject: [PATCH 04/17] Update src/pyqasm/visitor.py Co-authored-by: Harshit Gupta --- src/pyqasm/visitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index e566679d..a441c19c 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -89,7 +89,7 @@ def __init__( self._curr_scope: int = 0 self._label_scope_level: dict[int, set] = {self._curr_scope: set()} self._recording_ext_gate_depth = False - self._is_branching_statement: bool = False + self._in_branching_statement: bool = False self._is_branch_qubits: set[tuple[str, int]] = set() self._is_branch_clbits: set[tuple[str, int]] = set() self._init_utilities() From ffe044ccf14a8a083cc6b8c53de39ed18bcaac82 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 14:23:38 -0500 Subject: [PATCH 05/17] update changes --- src/pyqasm/visitor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index a441c19c..2001a121 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -1650,9 +1650,8 @@ def _update_branching_gate_depths(self) -> None: # seperately update branching o for node in nodes: node.depth = max_depths - if not self._is_branching_statement: - self._is_branch_clbits.clear() - self._is_branch_qubits.clear() + self._is_branch_clbits.clear() + self._is_branch_qubits.clear() def _visit_branching_statement( self, statement: qasm3_ast.BranchingStatement @@ -1781,8 +1780,7 @@ def ravel(bit_ind): self._curr_scope -= 1 self._pop_scope() self._restore_context() - if self._is_branching_statement: - self._update_branching_gate_depths() + self._update_branching_gate_depths() if self._check_only: return [] From 907f30707f71739431a575459ee687955cd0c3f8 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 14:37:31 -0500 Subject: [PATCH 06/17] update global variable name --- src/pyqasm/visitor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 2001a121..46b9a286 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -502,7 +502,7 @@ def _visit_measurement( # pylint: disable=too-many-locals measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=None ) ) - if not self._is_branching_statement: # if measurement gate is not in branching statement + if not self._in_branching_statement: # if measurement gate is not in branching statement 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 @@ -536,7 +536,7 @@ def _visit_measurement( # pylint: disable=too-many-locals measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=tgt_id if target else None, ) - if not self._is_branching_statement: # if measurement gate is not in branching statement + if not self._in_branching_statement: # if measurement gate is not in branching statement 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 @@ -856,7 +856,7 @@ def _visit_basic_gate_operation( # pylint: disable=too-many-locals self._broadcast_gate_operation(unrolled_gate_function, unrolled_targets, ctrls) ) - if not self._is_branching_statement: # if gate is not in branching statement + if not self._in_branching_statement: # if gate is not in branching statement self._update_qubit_depth_for_gate(unrolled_targets, ctrls) else: for ops in unrolled_targets + [ctrls] : # get qubit registers in branching operations @@ -964,7 +964,7 @@ def _visit_custom_gate_operation( # Update the depth only once for the entire custom gate if self._recording_ext_gate_depth: self._recording_ext_gate_depth = False - if not self._is_branching_statement: # if custom gate is not in branching statement + if not self._in_branching_statement: # if custom gate is not in branching statement self._update_qubit_depth_for_gate([op_qubits], ctrls) else: for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations @@ -1632,7 +1632,7 @@ def _evaluate_array_initialization( return np.array(init_values, dtype=ARRAY_TYPE_MAP[base_type.__class__]) def _update_branching_gate_depths(self) -> None: # seperately update branching operators depth - self._is_branching_statement = False + self._in_branching_statement = False max_depths = 0 nodes = [] for qbit in self._is_branch_qubits: @@ -1668,7 +1668,7 @@ def _visit_branching_statement( self._push_scope({}) self._curr_scope += 1 self._label_scope_level[self._curr_scope] = set() - self._is_branching_statement = True + self._in_branching_statement = True result = [] condition = statement.condition @@ -1692,7 +1692,7 @@ def _visit_branching_statement( ) assert isinstance(rhs_value, (bool, int)) - if reg_name is not None and reg_idx is not None and self._is_branching_statement: # get classical registers + if reg_name is not None and reg_idx is not None and self._in_branching_statement: # get classical registers op_tuple = (reg_name, reg_idx) self._is_branch_clbits.add(op_tuple) From 68df7d6c3c385288b75d9156d4a648392b4a6d9c Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 15:11:54 -0500 Subject: [PATCH 07/17] update code format --- src/pyqasm/visitor.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 46b9a286..bdffe0c3 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -502,7 +502,8 @@ def _visit_measurement( # pylint: disable=too-many-locals measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=None ) ) - if not self._in_branching_statement: # if measurement gate is not in branching statement + # if measurement gate is not in branching statement + if not self._in_branching_statement: 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 @@ -536,7 +537,8 @@ def _visit_measurement( # pylint: disable=too-many-locals measure=qasm3_ast.QuantumMeasurement(qubit=src_id), target=tgt_id if target else None, ) - if not self._in_branching_statement: # if measurement gate is not in branching statement + # if measurement gate is not in branching statement + if not self._in_branching_statement: 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 @@ -855,14 +857,13 @@ def _visit_basic_gate_operation( # pylint: disable=too-many-locals result.extend( self._broadcast_gate_operation(unrolled_gate_function, unrolled_targets, ctrls) ) - - if not self._in_branching_statement: # if gate is not in branching statement + # if gate is not in branching statement + if not self._in_branching_statement: self._update_qubit_depth_for_gate(unrolled_targets, ctrls) else: - for ops in unrolled_targets + [ctrls] : # get qubit registers in branching operations + for ops in unrolled_targets + [ctrls] : # get qreg in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op.name.name op_tuple = (op.name.name,op_idx) self._is_branch_qubits.add(op_tuple) @@ -970,7 +971,6 @@ def _visit_custom_gate_operation( for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op.name.name op_tuple = (op.name.name,op_idx) self._is_branch_qubits.add(op_tuple) @@ -1630,8 +1630,9 @@ def _evaluate_array_initialization( init_values.append(eval_value) return np.array(init_values, dtype=ARRAY_TYPE_MAP[base_type.__class__]) - - def _update_branching_gate_depths(self) -> None: # seperately update branching operators depth + + # seperately update branching operators depth + def _update_branching_gate_depths(self) -> None: self._in_branching_statement = False max_depths = 0 nodes = [] @@ -1646,7 +1647,7 @@ def _update_branching_gate_depths(self) -> None: # seperately update branching o c_node = self._module._clbit_depths[(c_name, c_idx)] nodes.append(c_node) max_depths = max(max_depths,c_node.depth + 1) - + for node in nodes: node.depth = max_depths @@ -1692,7 +1693,8 @@ def _visit_branching_statement( ) assert isinstance(rhs_value, (bool, int)) - if reg_name is not None and reg_idx is not None and self._in_branching_statement: # get classical registers + # get classical registers + if reg_name is not None and reg_idx is not None and self._in_branching_statement: op_tuple = (reg_name, reg_idx) self._is_branch_clbits.add(op_tuple) From 1871e49e96bdb50b344c605b3591132a03ed9030 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 15:39:01 -0500 Subject: [PATCH 08/17] Linting using pylint --- src/pyqasm/visitor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index bdffe0c3..d7739e18 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -775,7 +775,8 @@ def _update_qubit_depth_for_gate( qubit_node = self._module._qubit_depths[(qubit.name.name, qubit_id)] qubit_node.depth = max_involved_depth - def _visit_basic_gate_operation( # pylint: disable=too-many-locals + # pylint: disable=too-many-branches, too-many-locals + def _visit_basic_gate_operation( self, operation: qasm3_ast.QuantumGate, inverse: bool = False, From a9619d43b5cc999fcfe95fbcfc601fce6194cf49 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 15:50:13 -0500 Subject: [PATCH 09/17] Reformatted with black --- src/pyqasm/visitor.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index d7739e18..88c2aa3e 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -90,8 +90,8 @@ def __init__( self._label_scope_level: dict[int, set] = {self._curr_scope: set()} self._recording_ext_gate_depth = False self._in_branching_statement: bool = False - self._is_branch_qubits: set[tuple[str, int]] = set() - self._is_branch_clbits: set[tuple[str, int]] = set() + self._is_branch_qubits: set[tuple[str, int]] = set() + self._is_branch_clbits: set[tuple[str, int]] = set() self._init_utilities() def _init_utilities(self): @@ -543,8 +543,8 @@ def _visit_measurement( # pylint: disable=too-many-locals 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)], + 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 @@ -862,10 +862,10 @@ def _visit_basic_gate_operation( if not self._in_branching_statement: self._update_qubit_depth_for_gate(unrolled_targets, ctrls) else: - for ops in unrolled_targets + [ctrls] : # get qreg in branching operations + for ops in unrolled_targets + [ctrls]: # get qreg in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op_tuple = (op.name.name,op_idx) + op_tuple = (op.name.name, op_idx) self._is_branch_qubits.add(op_tuple) # check for duplicate bits @@ -966,13 +966,13 @@ def _visit_custom_gate_operation( # Update the depth only once for the entire custom gate if self._recording_ext_gate_depth: self._recording_ext_gate_depth = False - if not self._in_branching_statement: # if custom gate is not in branching statement + if not self._in_branching_statement: # if custom gate is not in branching statement self._update_qubit_depth_for_gate([op_qubits], ctrls) else: - for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations + for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations for op in ops: op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op_tuple = (op.name.name,op_idx) + op_tuple = (op.name.name, op_idx) self._is_branch_qubits.add(op_tuple) self._restore_context() @@ -1641,13 +1641,13 @@ def _update_branching_gate_depths(self) -> None: q_name, q_idx = qbit q_node = self._module._qubit_depths[(q_name, q_idx)] nodes.append(q_node) - max_depths = max(max_depths,q_node.depth + 1) + max_depths = max(max_depths, q_node.depth + 1) for cbit in self._is_branch_clbits: c_name, c_idx = cbit c_node = self._module._clbit_depths[(c_name, c_idx)] nodes.append(c_node) - max_depths = max(max_depths,c_node.depth + 1) + max_depths = max(max_depths, c_node.depth + 1) for node in nodes: node.depth = max_depths From d3ba6c917e9f868a143309032eb1700278665822 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Sat, 31 May 2025 16:14:41 -0500 Subject: [PATCH 10/17] Linting with mypy --- src/pyqasm/visitor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 88c2aa3e..a4a21301 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -864,6 +864,8 @@ def _visit_basic_gate_operation( else: for ops in unrolled_targets + [ctrls]: # get qreg in branching operations for op in ops: + assert isinstance(op.indices, list) and len(op.indices) > 0 + assert isinstance(op.indices[0], list) and len(op.indices[0]) > 0 op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] op_tuple = (op.name.name, op_idx) self._is_branch_qubits.add(op_tuple) @@ -971,6 +973,8 @@ def _visit_custom_gate_operation( else: for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations for op in ops: + assert isinstance(op.indices, list) and len(op.indices) > 0 + assert isinstance(op.indices[0], list) and len(op.indices[0]) > 0 op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] op_tuple = (op.name.name, op_idx) self._is_branch_qubits.add(op_tuple) From a6cc99e8be27c9b26e6a56dd0ff7e57ba68e2a56 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 4 Jun 2025 20:12:15 -0500 Subject: [PATCH 11/17] Change _in_branching_statement from bool to int to support nested depth tracking --- src/pyqasm/visitor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index a4a21301..dff5fe7b 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -89,7 +89,7 @@ def __init__( self._curr_scope: int = 0 self._label_scope_level: dict[int, set] = {self._curr_scope: set()} self._recording_ext_gate_depth = False - self._in_branching_statement: bool = False + self._in_branching_statement: int = 0 self._is_branch_qubits: set[tuple[str, int]] = set() self._is_branch_clbits: set[tuple[str, int]] = set() self._init_utilities() @@ -1674,7 +1674,7 @@ def _visit_branching_statement( self._push_scope({}) self._curr_scope += 1 self._label_scope_level[self._curr_scope] = set() - self._in_branching_statement = True + self._in_branching_statement += 1 result = [] condition = statement.condition @@ -1787,7 +1787,9 @@ def ravel(bit_ind): self._curr_scope -= 1 self._pop_scope() self._restore_context() - self._update_branching_gate_depths() + self._in_branching_statement -= 1 + if not self._in_branching_statement: + self._update_branching_gate_depths() if self._check_only: return [] From d396900bfab404757e259dce2bafebaa06957094 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 4 Jun 2025 20:15:35 -0500 Subject: [PATCH 12/17] Ensure full-register branches add all classical bits to _is_branch_clbits --- src/pyqasm/visitor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index dff5fe7b..45707f9d 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -1698,10 +1698,6 @@ def _visit_branching_statement( ) assert isinstance(rhs_value, (bool, int)) - # get classical registers - if reg_name is not None and reg_idx is not None and self._in_branching_statement: - op_tuple = (reg_name, reg_idx) - self._is_branch_clbits.add(op_tuple) if_block = self.visit_basic_block(statement.if_block) else_block = self.visit_basic_block(statement.else_block) @@ -1712,6 +1708,9 @@ def _visit_branching_statement( reg_idx, self._global_creg_size_map[reg_name], qubit=False, op_node=condition ) + # getting creg for depth counting + self._is_branch_clbits.add((reg_name, reg_idx)) + new_if_block = qasm3_ast.BranchingStatement( condition=qasm3_ast.BinaryExpression( op=qasm3_ast.BinaryOperator["=="], @@ -1743,6 +1742,8 @@ def _visit_branching_statement( rhs_value -= 1 size = self._global_creg_size_map[reg_name] + # getting cregs for depth counting + self._is_branch_clbits.update((reg_name, i) for i in range(size)) rhs_value_str = bin(int(rhs_value))[2:].zfill(size) else_block = self.visit_basic_block(statement.else_block) From 444f3b39258fcabcdc8c374205d36d5b50be020f Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 4 Jun 2025 20:19:04 -0500 Subject: [PATCH 13/17] Performed minor cleanups to improve readability and maintainability. --- src/pyqasm/visitor.py | 58 +++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 45707f9d..0fd6d1ec 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -862,13 +862,12 @@ def _visit_basic_gate_operation( if not self._in_branching_statement: self._update_qubit_depth_for_gate(unrolled_targets, ctrls) else: - for ops in unrolled_targets + [ctrls]: # get qreg in branching operations - for op in ops: - assert isinstance(op.indices, list) and len(op.indices) > 0 - assert isinstance(op.indices[0], list) and len(op.indices[0]) > 0 - op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op_tuple = (op.name.name, op_idx) - self._is_branch_qubits.add(op_tuple) + for qubit_subset in unrolled_targets + [ctrls]: # get qreg in branching operations + for qubit in qubit_subset: + assert isinstance(qubit.indices, list) and len(qubit.indices) > 0 + assert isinstance(qubit.indices[0], list) and len(qubit.indices[0]) > 0 + qubit_idx = Qasm3ExprEvaluator.evaluate_expression(qubit.indices[0][0])[0] + self._is_branch_qubits.add((qubit.name.name, qubit_idx)) # check for duplicate bits for final_gate in result: @@ -971,13 +970,13 @@ def _visit_custom_gate_operation( if not self._in_branching_statement: # if custom gate is not in branching statement self._update_qubit_depth_for_gate([op_qubits], ctrls) else: - for ops in [op_qubits] + [ctrls]: # get qubit registers in branching operations - for op in ops: - assert isinstance(op.indices, list) and len(op.indices) > 0 - assert isinstance(op.indices[0], list) and len(op.indices[0]) > 0 - op_idx = Qasm3ExprEvaluator.evaluate_expression(op.indices[0][0])[0] - op_tuple = (op.name.name, op_idx) - self._is_branch_qubits.add(op_tuple) + # get qubit registers in branching operations + for qubit_subset in [op_qubits] + [ctrls]: + for qubit in qubit_subset: + assert isinstance(qubit.indices, list) and len(qubit.indices) > 0 + assert isinstance(qubit.indices[0], list) and len(qubit.indices[0]) > 0 + qubit_idx = Qasm3ExprEvaluator.evaluate_expression(qubit.indices[0][0])[0] + self._is_branch_qubits.add((qubit.name.name, qubit_idx)) self._restore_context() @@ -1636,25 +1635,20 @@ def _evaluate_array_initialization( return np.array(init_values, dtype=ARRAY_TYPE_MAP[base_type.__class__]) - # seperately update branching operators depth + # update branching operators depth def _update_branching_gate_depths(self) -> None: - self._in_branching_statement = False - max_depths = 0 - nodes = [] - for qbit in self._is_branch_qubits: - q_name, q_idx = qbit - q_node = self._module._qubit_depths[(q_name, q_idx)] - nodes.append(q_node) - max_depths = max(max_depths, q_node.depth + 1) - - for cbit in self._is_branch_clbits: - c_name, c_idx = cbit - c_node = self._module._clbit_depths[(c_name, c_idx)] - nodes.append(c_node) - max_depths = max(max_depths, c_node.depth + 1) - - for node in nodes: - node.depth = max_depths + """Updates the depth of the circuit after applying branching statements.""" + all_nodes = [ + self._module._qubit_depths[(name, idx)] for name, idx in self._is_branch_qubits + ] + [self._module._clbit_depths[(name, idx)] for name, idx in self._is_branch_clbits] + + try: + max_depth = max(node.depth + 1 for node in all_nodes) + except ValueError: + max_depth = 0 + + for node in all_nodes: + node.depth = max_depth self._is_branch_clbits.clear() self._is_branch_qubits.clear() From ace350fc12b57b947ebe8a71e9017c26a71b9fb2 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 4 Jun 2025 20:19:30 -0500 Subject: [PATCH 14/17] Added new test cases --- tests/qasm3/test_depth.py | 96 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/qasm3/test_depth.py b/tests/qasm3/test_depth.py index dae8919c..abb9f117 100644 --- a/tests/qasm3/test_depth.py +++ b/tests/qasm3/test_depth.py @@ -460,8 +460,9 @@ def test_qasm3_depth_no_branching(program, expected_depth): measure q[0] -> c[0]; if (c==1) measure q[1] -> c[1]; +if (c==3) measure q[1] -> c[1]; """, - 4, + 5, ), ( """ @@ -488,6 +489,99 @@ def test_qasm3_depth_no_branching(program, expected_depth): """, 8, ), + ( + """ +OPENQASM 3.0; +include "stdgates.inc"; +gate custom a, b{ + cx a, b; + h a; +} +qubit[4] q; +bit[4] c; +bit[4] c0; +h q; +measure q -> c0; +if(c0[0]){ + x q[0]; + cx q[0], q[1]; + if (c0[1]){ + cx q[1], q[2]; + } +} +if (c[0]){ + custom q[2], q[3]; +} +array[int[32], 8] arr; +arr[0] = 1; +if(arr[0] >= 1){ + h q[0]; + h q[1]; +} +""", + 4, + ), + ( + """ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +bit[4] c; +if(c == 3){ + h q[0]; +} +if(c >= 3){ + h q[0]; +} else { + x q[0]; +} +if(c <= 3){ + h q[0]; +} else { + x q[0]; +} +if(c[0] < 4){ + h q[0]; +} else { + x q[0]; +} +""", + 4, + ), + ( + """ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c; +h q[0]; +cx q[0], q[1]; +c[0] = measure q[0]; +c[1] = measure q[1]; +if (c[0] == false) { + if (c[1] == true) { + x q[0]; + } + else { + if (c[1] == false){ + x q[1]; + } + else { + z q[0]; + } + } +} + +if (c == 0) { + x q[0]; +} +else { + y q[1]; +} +x q[0]; +""", + 6, + ), ], ) def test_qasm3_depth_branching(program, expected_depth): From 9068e09d614282399e5dcb56abd9aeaf705bd9d7 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Thu, 5 Jun 2025 15:11:04 +0530 Subject: [PATCH 15/17] Update tests/qasm3/test_depth.py remove the skip test --- tests/qasm3/test_depth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/qasm3/test_depth.py b/tests/qasm3/test_depth.py index abb9f117..e882bea2 100644 --- a/tests/qasm3/test_depth.py +++ b/tests/qasm3/test_depth.py @@ -426,7 +426,6 @@ def test_qasm3_depth_no_branching(program, expected_depth): assert result.depth() == expected_depth -# @pytest.mark.skip(reason="Not implemented branching conditions depth") @pytest.mark.parametrize( "program, expected_depth", [ From 5b39a86ef38f55fae7a0a9a560ed5abf93f410e9 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Thu, 5 Jun 2025 18:26:38 -0500 Subject: [PATCH 16/17] update test_depth.py Added new test case for the external gates inside branching statements. New code (line: 974 to 979 under _visit_custom_gate_operation in visitor.py never ran under the previous test suite. --- tests/qasm3/test_depth.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/qasm3/test_depth.py b/tests/qasm3/test_depth.py index e882bea2..68dca449 100644 --- a/tests/qasm3/test_depth.py +++ b/tests/qasm3/test_depth.py @@ -589,3 +589,38 @@ def test_qasm3_depth_branching(program, expected_depth): result.unroll() result.remove_barriers() assert result.depth() == expected_depth + + +def test_qasm3_depth_branching_for_external_gates(): + """Test calculating depth of qasm3 circuit with external gates inside branching conditions""" + qasm3_string = """ + OPENQASM 3.0; + include "stdgates.inc"; + bit[2] c; + gate my_gate q1, q2 { + h q1; + cx q1, q2; + h q2; + } + gate my_gate_two q1, q2 { + cx q1, q2; + } + + qubit[2] q; + if (c == 0){ + measure q -> c; + my_gate q[0], q[1]; + } + else { + if (c[0] == false) { + my_gate q[1], q[0]; + } + else{ + measure q -> c; + } + } + my_gate_two q[0], q[1]; + """ + result = loads(qasm3_string) + result._external_gates = ["my_gate", "my_gate_two"] + assert result.depth() == 2 From 4c623156c17c1f59da77521cf96810ab131628d7 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Thu, 5 Jun 2025 18:28:50 -0500 Subject: [PATCH 17/17] update CHANGELOG.md --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa9716d..5bdecd93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,27 @@ Types of changes: ### Fixed - Fixed the way how depth is calculated when external gates are defined with unrolling a QASM module. ([#198](https://github.com/qBraid/pyqasm/pull/198)) +- Added separate depth calculation for gates inside branching statements. ([#200](https://github.com/qBraid/pyqasm/pull/200)) + - **Example:** + ```python + OPENQASM 3.0; + include "stdgates.inc"; + qubit[4] q; + bit[4] c; + bit[4] c0; + if (c[0]){ + x q[0]; + h q[0] + } + else { + h q[1]; + } + ``` + ```text + Depth = 1 + ``` + - Previously, each gate inside an `if`/`else` block would advance only its own wire depth. Now, when any branching statement is encountered, all qubit‐ and clbit‐depths used inside that block are first incremented by one, then set to the maximum of those new values. This ensures the entire conditional block counts as single “depth” increment, rather than letting individual gates within the same branch float ahead independently. + - In the above snippet, c[0], q[0], and q[1] all jump together to a single new depth for that branch. ### Dependencies