Repository: qBraid/pyqasm

Issue:
Conditional unrolling for barriers
Add option to `QasmModule.unroll()` / `QasmVisitor` so that in `QasmVisitor._visit_barrier`, barrier statements are not unpacked into individual qubits. Re: https://github.com/qBraid/pyqasm/pull/122#issuecomment-2676088799

Discussion:

Solution patch:
diff --git a/src/pyqasm/modules/base.py b/src/pyqasm/modules/base.py
index 07d77f6..5b6890c 100644
--- a/src/pyqasm/modules/base.py
+++ b/src/pyqasm/modules/base.py
@@ -518,7 +518,23 @@ def validate(self):
         self._validated_program = True
 
     def unroll(self, **kwargs):
-        """Unroll the module into basic qasm operations"""
+        """Unroll the module into basic qasm operations.
+
+        Args:
+            **kwargs: Additional arguments to pass to the QasmVisitor.
+                external_gates (list[str]): List of gates that should not be unrolled.
+                unroll_barriers (bool): If True, barriers will be unrolled. Defaults to True.
+                check_only (bool): If True, only check the program without executing it.
+                                   Defaults to False.
+
+        Raises:
+            ValidationError: If the module fails validation during unrolling.
+            UnrollError: If an error occurs during the unrolling process.
+
+        Notes:
+            This method resets the module's qubit and classical bit counts before unrolling,
+            and sets them to -1 if an error occurs during unrolling.
+        """
         if not kwargs:
             kwargs = {}
         try:
diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py
index 6dce183..4c9ec5c 100644
--- a/src/pyqasm/visitor.py
+++ b/src/pyqasm/visitor.py
@@ -55,9 +55,17 @@ class QasmVisitor:
         initialize_runtime (bool): If True, quantum runtime will be initialized. Defaults to True.
         record_output (bool): If True, output of the circuit will be recorded. Defaults to True.
         external_gates (list[str]): List of gates that should not be unrolled.
+        unroll_barriers (bool): If True, barriers will be unrolled. Defaults to True.
+        check_only (bool): If True, only check the program without executing it. Defaults to False.
     """
 
-    def __init__(self, module, check_only: bool = False, external_gates: list[str] | None = None):
+    def __init__(
+        self,
+        module,
+        check_only: bool = False,
+        external_gates: list[str] | None = None,
+        unroll_barriers: bool = True,
+    ):
         self._module = module
         self._scope: deque = deque([{}])
         self._context: deque = deque([Context.GLOBAL])
@@ -74,6 +82,7 @@ def __init__(self, module, check_only: bool = False, external_gates: list[str] |
         self._external_gates: list[str] = [] if external_gates is None else external_gates
         self._subroutine_defns: dict[str, qasm3_ast.SubroutineDefinition] = {}
         self._check_only: bool = check_only
+        self._unroll_barriers: bool = unroll_barriers
         self._curr_scope: int = 0
         self._label_scope_level: dict[int, set] = {self._curr_scope: set()}
 
@@ -597,6 +606,9 @@ def _visit_barrier(self, barrier: qasm3_ast.QuantumBarrier) -> list[qasm3_ast.Qu
         if self._check_only:
             return []
 
+        if not self._unroll_barriers:
+            return [barrier]
+
         return unrolled_barriers
 
     def _get_op_parameters(self, operation: qasm3_ast.QuantumGate) -> list[float]:


Test patch:
diff --git a/tests/qasm3/test_barrier.py b/tests/qasm3/test_barrier.py
index 654d0ed..7d122bd 100644
--- a/tests/qasm3/test_barrier.py
+++ b/tests/qasm3/test_barrier.py
@@ -125,6 +125,36 @@ def test_remove_barriers():
     check_unrolled_qasm(dumps(module), expected_qasm)
 
 
+def test_unroll_barrier():
+    qasm_str = """
+    OPENQASM 3.0;
+    include "stdgates.inc";
+
+    qubit[2] q1;
+    qubit[3] q2;
+    qubit q3;
+
+    // barriers
+    barrier q1, q2, q3;
+    barrier q2[:3];
+    barrier q3[0];
+    """
+    expected_qasm = """OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q1;
+    qubit[3] q2;
+    qubit[1] q3;
+    barrier q1, q2, q3;
+    barrier q2[:3];
+    barrier q3[0];
+    """
+    module = loads(qasm_str)
+    assert module.has_barriers() is True
+    module.unroll(unroll_barriers=False)
+    assert module.has_barriers() is True
+    check_unrolled_qasm(dumps(module), expected_qasm)
+
+
 def test_incorrect_barrier():
 
     undeclared = """