Repository: qBraid/pyqasm

Issue:
Prototype conversion flow
- Implement a basic transformation prototype to convert an unrolled program into the specified target basis set.

- For starting out, take the clifford set for this conversion -
   - Store the clifford decompositions of each gate in pyqasm
   - `unroll` the program into pyqasm default set
   - For each gate in the program which belongs to default set, convert it into its clifford equivalent using the "stored" definition, if gate is non-parameterized.
   - If the gate is parameterized, "generate" the clifford decomposition
   - Append this definition to the rebased program 
   - Convert all default gates using above steps

Hints from issue comments:
@TheGupta2012: Completed as part of #123 

Solution patch:
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4c78e9a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# Ref : https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings#per-repository-settings
+
+# Set line endings to lf 
+* text eol=lf
\ No newline at end of file
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 7d43db7..73c90f8 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -21,6 +21,6 @@ jobs:
         run: |
           python3 -m pip install --upgrade pip
           python3 -m pip install tox>=4.2.0
-      - name: Check isort, black, EOL, mypy, pylint, headers
+      - name: Check isort, black, mypy, pylint, headers
         run: |
           tox -e format-check
\ No newline at end of file
diff --git a/bin/check_line_endings.py b/bin/check_line_endings.py
deleted file mode 100644
index db84607..0000000
--- a/bin/check_line_endings.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2025 qBraid
-#
-# This file is part of PyQASM
-#
-# PyQASM is free software released under the GNU General Public License v3
-# or later. You can redistribute and/or modify it under the terms of the GPL v3.
-# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
-#
-# THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
-
-"""
-Check that all Python files in the project have Unix line endings (LF).
-"""
-
-import os
-
-
-def check_line_endings(directory):
-    failures = []
-    for root, _, files in os.walk(directory):
-        for file in files:
-            if not file.endswith(".py"):
-                continue
-            file_path = os.path.join(root, file)
-            try:
-                with open(file_path, "rb") as f:
-                    for line in f:
-                        if line.endswith(b"\r\n") or line.endswith(b"\r"):
-                            failures.append(file_path)
-                            break
-            except OSError as e:
-                print(f"Could not check file '{file_path}': {e}")
-
-    return failures
-
-
-if __name__ == "__main__":
-    failed_files = []
-
-    pyqasm_root = os.path.join(os.getcwd(), "src", "pyqasm")
-    failed_files.extend(check_line_endings(pyqasm_root))
-
-    tests = os.path.join(os.getcwd(), "tests")
-    failed_files.extend(check_line_endings(tests))
-
-    bin_dir = os.path.join(os.getcwd(), "bin")
-    failed_files.extend(check_line_endings(bin_dir))
-
-    if failed_files:
-        raise ValueError(
-            f"Error: {len(failed_files)} have incorrect line endings:\n" + "\n".join(failed_files)
-        )
-    print("\033[92mSuccess: All Python files have Unix line endings (LF).\033[0m")
diff --git a/pyproject.toml b/pyproject.toml
index 739e976..dbcbaaf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,7 +41,7 @@ dependencies = ["numpy", "openqasm3[parser]>=1.0.0,<2.0.0"]
 [project.optional-dependencies]
 cli = ["typer>=0.12.1", "rich>=10.11.0", "typing-extensions"]
 test = ["pytest", "pytest-cov"]
-lint = ["black", "isort", "pylint", "mypy", "qbraid-cli>=0.8.5"]
+lint = ["black", "isort>=6.0.0", "pylint", "mypy", "qbraid-cli>=0.8.5"]
 docs = ["sphinx>=7.3.7,<8.2.0", "sphinx-autodoc-typehints>=1.24,<3.1", "sphinx-rtd-theme>=2.0.0,<4.0.0", "docutils<0.22", "sphinx-copybutton"]
 
 [tool.setuptools_scm]
diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py
new file mode 100644
index 0000000..fd9b337
--- /dev/null
+++ b/src/pyqasm/decomposer.py
@@ -0,0 +1,161 @@
+# Copyright (C) 2025 qBraid
+#
+# This file is part of PyQASM
+#
+# PyQASM is free software released under the GNU General Public License v3
+# or later. You can redistribute and/or modify it under the terms of the GPL v3.
+# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
+#
+# THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
+
+"""
+Definition of the Decomposer class
+"""
+
+import openqasm3.ast as qasm3_ast
+from openqasm3.ast import BranchingStatement, QuantumGate
+
+from pyqasm.exceptions import RebaseError
+from pyqasm.maps.decomposition_rules import DECOMPOSITION_RULES, AppliedQubit
+from pyqasm.maps.gates import BASIS_GATE_MAP
+
+
+class Decomposer:
+    """
+    Class to decompose the gates based on the target basis set.
+    """
+
+    @classmethod
+    def process_gate_statement(cls, gate_name, statement, target_basis_set):
+        """Process the gate statement based on the target basis set.
+
+        Args:
+            gate_name: The name of the gate to process.
+            statement: The statement to process.
+            target_basis_set: The target basis set to rebase the module to.
+
+        Returns:
+            list: The processed gates based on the target basis set.
+        """
+        decomposition_rules = DECOMPOSITION_RULES[target_basis_set]
+        target_basis_gate_list = BASIS_GATE_MAP[target_basis_set]
+
+        processed_gates_list = []
+
+        if gate_name in target_basis_gate_list:
+            # Keep the gate as is
+            processed_gates_list = [statement]
+        elif gate_name in decomposition_rules:
+            # Decompose the gates
+            processed_gates_list = cls._get_decomposed_gates(
+                decomposition_rules, statement, gate_name
+            )
+        elif gate_name in {"rx", "ry", "rz"}:
+            # Approximate parameterized gates using Solovay-Kitaev
+            # Example -
+            # approx_gates = solovay_kitaev_algo(
+            #     gate_name, statement.arguments[0].value, accuracy=0.01
+            # )
+            # return approx_gates
+            pass
+        else:
+            # Raise an error if the gate is not supported in the target basis set
+            error = f"Gate '{gate_name}' is not supported in the '{target_basis_set} set'."
+            raise RebaseError(error)
+
+        return processed_gates_list
+
+    @classmethod
+    def process_branching_statement(cls, branching_statement, target_basis_set):
+        """Process the branching statement based on the target basis set.
+
+        Args:
+            branching_statement: The branching statement to process.
+            target_basis_set: The target basis set to rebase the module to.
+
+        Returns:
+            BranchingStatement: The processed branching statement based on the target basis set.
+        """
+        if_block = []
+        else_block = []
+
+        for statement in branching_statement.if_block:
+            if isinstance(statement, QuantumGate):
+                gate_name = statement.name.name
+                processed_gates_list = cls.process_gate_statement(
+                    gate_name, statement, target_basis_set
+                )
+                if_block.extend(processed_gates_list)
+            elif isinstance(statement, BranchingStatement):
+                if_block.append(cls.process_branching_statement(statement, target_basis_set))
+            else:
+                if_block.append(statement)
+
+        for statement in branching_statement.else_block:
+            if isinstance(statement, QuantumGate):
+                gate_name = statement.name.name
+                processed_gates_list = cls.process_gate_statement(
+                    gate_name, statement, target_basis_set
+                )
+                else_block.extend(processed_gates_list)
+            elif isinstance(statement, BranchingStatement):
+                else_block.append(cls.process_branching_statement(statement, target_basis_set))
+            else:
+                else_block.append(statement)
+
+        return BranchingStatement(
+            condition=branching_statement.condition, if_block=if_block, else_block=else_block
+        )
+
+    @classmethod
+    def _get_decomposed_gates(cls, decomposition_rules, statement, gate):
+        """Apply the decomposed gates based on the decomposition rules.
+
+        Args:
+            decomposition_rules: The decomposition rules to apply.
+            statement: The statement to apply the decomposition rules to.
+            gate: The name of the gate to apply the decomposition rules to.
+
+        Returns:
+            list: The decomposed gates to be applied.
+        """
+        decomposed_gates = []
+
+        for rule in decomposition_rules[gate]:
+            qubits = cls._get_qubits_for_gate(statement.qubits, rule)
+            arguments = [qasm3_ast.FloatLiteral(value=rule["param"])] if "param" in rule else []
+
+            new_gate = qasm3_ast.QuantumGate(
+                modifiers=[],
+                name=qasm3_ast.Identifier(name=rule["gate"]),
+                arguments=arguments,
+                qubits=qubits,
+            )
+
+            decomposed_gates.append(new_gate)
+        return decomposed_gates
+
+    @classmethod
+    def _get_qubits_for_gate(cls, qubits, rule):
+        """
+        Determines the order of qubits to be used for a gate operation based on the provided rule.
+
+        Args:
+            qubits: The qubits to be used for the gate operation.
+            rule: The decomposition rule to apply.
+
+        Returns:
+            list: The ordered qubits to be used for the gate operation.
+        """
+        if "controll_bit" in rule:
+            if rule["controll_bit"] == AppliedQubit.QUBIT1:
+                qubits = [qubits[0], qubits[1]]
+            else:
+                qubits = [qubits[1], qubits[0]]
+
+        elif "target_bit" in rule:
+            if rule["target_bit"] == AppliedQubit.QUBIT1:
+                qubits = [qubits[0]]
+            else:
+                qubits = [qubits[1]]
+        return qubits
diff --git a/src/pyqasm/elements.py b/src/pyqasm/elements.py
index 2ce76b9..47d5fca 100644
--- a/src/pyqasm/elements.py
+++ b/src/pyqasm/elements.py
@@ -98,3 +98,13 @@ class Variable:  # pylint: disable=too-many-instance-attributes
     is_constant: bool = False
     is_register: bool = False
     readonly: bool = False
+
+
+class BasisSet(Enum):
+    """
+    Enum for the different basis sets in Qasm.
+    """
+
+    DEFAULT = 0
+    ROTATIONAL_CX = 1
+    CLIFFORD_T = 2
diff --git a/src/pyqasm/exceptions.py b/src/pyqasm/exceptions.py
index 31039d8..ee24507 100644
--- a/src/pyqasm/exceptions.py
+++ b/src/pyqasm/exceptions.py
@@ -32,6 +32,10 @@ class UnrollError(PyQasmError):
     """Exception raised when a OpenQASM program fails unrolling."""
 
 
+class RebaseError(PyQasmError):
+    """Exception raised when a OpenQASM program fails to rebase into target basis set."""
+
+
 class QasmParsingError(QASM3ParsingError):
     """An error raised by the AST visitor during the AST-generation phase.  This is raised in cases
     where the given program could not be correctly parsed."""
diff --git a/src/pyqasm/expressions.py b/src/pyqasm/expressions.py
index 705540c..7c6e75a 100644
--- a/src/pyqasm/expressions.py
+++ b/src/pyqasm/expressions.py
@@ -29,7 +29,11 @@
     IntegerLiteral,
 )
 from openqasm3.ast import IntType as Qasm3IntType
-from openqasm3.ast import SizeOf, Statement, UnaryExpression
+from openqasm3.ast import (
+    SizeOf,
+    Statement,
+    UnaryExpression,
+)
 
 from pyqasm.analyzer import Qasm3Analyzer
 from pyqasm.exceptions import ValidationError, raise_qasm3_error
diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py
new file mode 100644
index 0000000..b2421eb
--- /dev/null
+++ b/src/pyqasm/maps/decomposition_rules.py
@@ -0,0 +1,102 @@
+# Copyright (C) 2025 qBraid
+#
+# This file is part of PyQASM
+#
+# PyQASM is free software released under the GNU General Public License v3
+# or later. You can redistribute and/or modify it under the terms of the GPL v3.
+# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
+#
+# THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
+
+"""
+Definition of the decomposition rules for the gates in the basis sets.
+"""
+from enum import Enum
+
+from pyqasm.elements import BasisSet
+from pyqasm.maps.expressions import CONSTANTS_MAP
+
+
+class AppliedQubit(Enum):
+    """Enum to represent the qubits that are involved in the decomposition of a gate."""
+
+    QUBIT1 = 0  # Control qubit
+    QUBIT2 = 1  # Target qubit
+
+
+# Decomposition rules for the gates in the basis sets.
+# The rules are defined as a dictionary where the key is the gate name.
+# The value is a list of dictionaries.
+# Each dictionary in the list represents a step in the decomposition of the gate.
+# Each step is defined by a dictionary with the following keys:
+#     - gate: The name of the gate to be applied.
+#     - param: The parameter of the gate.
+#     - target_bit: The target qubit of the gate.
+#                   This key is only used for decomposition of gates that operate on two qubits.
+#     - controll_bit: The control qubit of the gate.
+#                   This key is only used for gates that operate on two qubits.
+#
+DECOMPOSITION_RULES = {
+    BasisSet.ROTATIONAL_CX: {
+        "x": [{"gate": "rx", "param": CONSTANTS_MAP["pi"]}],
+        "y": [{"gate": "ry", "param": CONSTANTS_MAP["pi"]}],
+        "z": [{"gate": "rz", "param": CONSTANTS_MAP["pi"]}],
+        "h": [
+            {"gate": "ry", "param": CONSTANTS_MAP["pi"] / 2},
+            {"gate": "rx", "param": CONSTANTS_MAP["pi"]},
+        ],
+        "s": [{"gate": "rz", "param": CONSTANTS_MAP["pi"] / 2}],
+        "t": [{"gate": "rz", "param": CONSTANTS_MAP["pi"] / 4}],
+        "sx": [{"gate": "rx", "param": CONSTANTS_MAP["pi"] / 2}],
+        "sdg": [{"gate": "rz", "param": -CONSTANTS_MAP["pi"] / 2}],
+        "tdg": [{"gate": "rz", "param": -CONSTANTS_MAP["pi"] / 4}],
+        "cz": [
+            {"gate": "ry", "param": CONSTANTS_MAP["pi"] / 2, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "rx", "param": CONSTANTS_MAP["pi"], "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "ry", "param": CONSTANTS_MAP["pi"] / 2, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "rx", "param": CONSTANTS_MAP["pi"], "target_bit": AppliedQubit.QUBIT2},
+        ],
+        "swap": [
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT2, "target_bit": AppliedQubit.QUBIT1},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+        ],
+    },
+    BasisSet.CLIFFORD_T: {
+        "x": [{"gate": "h"}, {"gate": "s"}, {"gate": "s"}, {"gate": "h"}],
+        "y": [
+            {"gate": "s"},
+            {"gate": "s"},
+            {"gate": "h"},
+            {"gate": "s"},
+            {"gate": "s"},
+            {"gate": "h"},
+        ],
+        "z": [{"gate": "s"}, {"gate": "s"}],
+        "sx": [
+            {"gate": "s"},
+            {"gate": "s"},
+            {"gate": "s"},
+            {"gate": "h"},
+            {"gate": "s"},
+            {"gate": "s"},
+            {"gate": "s"},
+        ],
+        "cz": [
+            {"gate": "h", "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "h", "target_bit": AppliedQubit.QUBIT2},
+        ],
+        "swap": [
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT2, "target_bit": AppliedQubit.QUBIT1},
+            {"gate": "cx", "controll_bit": AppliedQubit.QUBIT1, "target_bit": AppliedQubit.QUBIT2},
+        ],
+    },
+}
+
+# """TODO: Implement the Solovay-Kitaev algorithm"""
+#
+# def solovay_kitaev_algo(gate_name, param, accuracy):
+#     pass
diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py
index fbf3d7e..da27173 100644
--- a/src/pyqasm/maps/gates.py
+++ b/src/pyqasm/maps/gates.py
@@ -21,7 +21,7 @@
 import numpy as np
 from openqasm3.ast import FloatLiteral, Identifier, IndexedIdentifier, QuantumGate, QuantumPhase
 
-from pyqasm.elements import InversionOp
+from pyqasm.elements import BasisSet, InversionOp
 from pyqasm.exceptions import ValidationError
 from pyqasm.linalg import kak_decomposition_angles
 from pyqasm.maps.expressions import CONSTANTS_MAP
@@ -1087,6 +1087,30 @@ def two_qubit_gate_op(
     "c4x": c4x_gate,
 }
 
+BASIS_GATE_MAP = {
+    # default basis set is the gate set of the stdgates.inc library file
+    BasisSet.DEFAULT: {
+        "id",
+        "rx",
+        "ry",
+        "rz",
+        "h",
+        "x",
+        "y",
+        "z",
+        "s",
+        "sx",
+        "t",
+        "sdg",
+        "tdg",
+        "cx",
+        "cz",
+        "swap",
+    },
+    BasisSet.ROTATIONAL_CX: {"rx", "ry", "rz", "cx"},
+    BasisSet.CLIFFORD_T: {"h", "t", "s", "cx", "tdg", "sdg"},
+}
+
 
 def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]:
     """
diff --git a/src/pyqasm/modules/base.py b/src/pyqasm/modules/base.py
index 0cca109..c0a796d 100644
--- a/src/pyqasm/modules/base.py
+++ b/src/pyqasm/modules/base.py
@@ -17,12 +17,14 @@
 from typing import Optional
 
 import openqasm3.ast as qasm3_ast
-from openqasm3.ast import Program
+from openqasm3.ast import BranchingStatement, Program, QuantumGate
 
 from pyqasm.analyzer import Qasm3Analyzer
+from pyqasm.decomposer import Decomposer
 from pyqasm.elements import ClbitDepthNode, QubitDepthNode
 from pyqasm.exceptions import UnrollError, ValidationError
 from pyqasm.maps import QUANTUM_STATEMENTS
+from pyqasm.maps.decomposition_rules import DECOMPOSITION_RULES
 from pyqasm.visitor import QasmVisitor
 
 
@@ -525,6 +527,55 @@ def unroll(self, **kwargs):
             self._unrolled_ast = Program(statements=[], version=self.original_program.version)
             raise err
 
+    def rebase(self, target_basis_set, in_place=True):
+        """Rebase the AST to use a specified target basis set.
+
+        Will unroll the module if not already done.
+
+        Args:
+            target_basis_set: The target basis set to rebase the module to.
+            in_place (bool): Flag to indicate if the rebase operation should be done in place.
+
+        Returns:
+            QasmModule: The module with the gates rebased to the target basis set.
+        """
+        if target_basis_set not in DECOMPOSITION_RULES:
+            raise ValueError(f"Target basis set '{target_basis_set}' is not defined.")
+
+        qasm_module = self if in_place else self.copy()
+
+        if len(qasm_module._unrolled_ast.statements) == 0:
+            qasm_module.unroll()
+
+        rebased_statements = []
+
+        for statement in qasm_module._unrolled_ast.statements:
+            if isinstance(statement, QuantumGate):
+                gate_name = statement.name.name
+
+                # Decompose the gate
+                processed_gates_list = Decomposer.process_gate_statement(
+                    gate_name, statement, target_basis_set
+                )
+                for processed_gate in processed_gates_list:
+                    rebased_statements.append(processed_gate)
+
+            elif isinstance(statement, BranchingStatement):
+                # Recursively process the if_block and else_block
+
+                rebased_statements.append(
+                    Decomposer.process_branching_statement(statement, target_basis_set)
+                )
+
+            else:
+                # Non-gate statements are directly appended
+                rebased_statements.append(statement)
+
+        # Replace the unrolled AST with the rebased one
+        qasm_module._unrolled_ast.statements = rebased_statements
+
+        return qasm_module
+
     def __str__(self) -> str:
         """Return the string representation of the QASM program
 
diff --git a/tox.ini b/tox.ini
index 2fa7c94..be4f5d5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -29,9 +29,9 @@ commands =
 [testenv:isort]
 envdir = .tox/linters
 skip_install = true
-deps = isort
+deps = isort>=6.0.0
 commands =
-    isort . {posargs} src tests bin examples
+    isort {posargs} src tests bin examples 
 
 [testenv:pylint]
 envdir = .tox/linters
@@ -47,11 +47,6 @@ deps = black
 commands =
     black src tests bin examples {posargs}
 
-[testenv:line_endings]
-skip_install = true
-commands = 
-    python bin/check_line_endings.py
-
 [testenv:mypy]
 envdir = .tox/linters
 skip_install = true
@@ -91,6 +86,5 @@ commands =
     {[testenv:pylint]commands}
     {[testenv:isort]commands} {posargs:--check-only}
     {[testenv:black]commands} {posargs:--check}
-    {[testenv:line_endings]commands}
     {[testenv:mypy]commands}
     {[testenv:headers]commands}


Test patch:
diff --git a/tests/qasm2/test_rotation_gates.py b/tests/qasm2/test_rotation_gates.py
index e08aabb..05ba4a3 100644
--- a/tests/qasm2/test_rotation_gates.py
+++ b/tests/qasm2/test_rotation_gates.py
@@ -9,7 +9,7 @@
 # THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
 
 """
-Module containing unit tests for parsing and unrolling programs that contain quantum 
+Module containing unit tests for parsing and unrolling programs that contain quantum
 rotations in qasm2 format.
 
 """
diff --git a/tests/qasm3/conftest.py b/tests/qasm3/conftest.py
index a81d6a0..68ed065 100644
--- a/tests/qasm3/conftest.py
+++ b/tests/qasm3/conftest.py
@@ -8,8 +8,7 @@
 #
 # THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
 
-"""Module containing imports for unit test resources
-"""
+"""Module containing imports for unit test resources"""
 
 # pylint: disable=wildcard-import, unused-wildcard-import
 
diff --git a/tests/qasm3/test_rebase.py b/tests/qasm3/test_rebase.py
new file mode 100644
index 0000000..7407ea9
--- /dev/null
+++ b/tests/qasm3/test_rebase.py
@@ -0,0 +1,336 @@
+# Copyright (C) 2025 qBraid
+#
+# This file is part of PyQASM
+#
+# PyQASM is free software released under the GNU General Public License v3
+# or later. You can redistribute and/or modify it under the terms of the GPL v3.
+# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
+#
+# THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
+
+"""
+Module containing unit tests for rebasing programms
+
+"""
+import pytest
+
+from pyqasm.elements import BasisSet
+from pyqasm.entrypoint import dumps, loads
+from tests.utils import check_single_qubit_gate_op, check_unrolled_qasm
+
+
+@pytest.mark.parametrize(
+    "input_gates, decomposed_gates",
+    [
+        ("x q[0];", "rx(3.141592653589793) q[0];"),
+        ("y q[0];", "ry(3.141592653589793) q[0];"),
+        ("z q[0];", "rz(3.141592653589793) q[0];"),
+        (
+            "h q[0];",
+            """
+            ry(1.5707963267948966) q[0];
+            rx(3.141592653589793) q[0];
+            """,
+        ),
+        ("s q[0];", "rz(1.5707963267948966) q[0];"),
+        ("t q[0];", "rz(0.7853981633974483) q[0];"),
+        ("sx q[0];", "rx(1.5707963267948966) q[0];"),
+        ("sdg q[0];", "rz(-1.5707963267948966) q[0];"),
+        ("tdg q[0];", "rz(-0.7853981633974483) q[0];"),
+        (
+            "cz q[0], q[1];",
+            """
+            ry(1.5707963267948966) q[1];
+            rx(3.141592653589793) q[1];
+            cx q[0], q[1];
+            ry(1.5707963267948966) q[1];
+            rx(3.141592653589793) q[1];
+            """,
+        ),
+        (
+            "swap q[0], q[1];",
+            """
+            cx q[0], q[1];
+            cx q[1], q[0];
+            cx q[0], q[1];
+            """,
+        ),
+    ],
+)
+def test_rebase_rotational_cx(input_gates, decomposed_gates):
+    """Test that the rebasing gates to rotational-CX basis works as expected."""
+
+    qasm = f"""OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q;
+    bit[2] c;
+    {input_gates}
+    c[0] = measure q[0];
+    """
+
+    expected_qasm = f"""OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q;
+    bit[2] c;
+    {decomposed_gates}
+    c[0] = measure q[0];
+    """
+
+    result = loads(qasm)
+    result.rebase(BasisSet.ROTATIONAL_CX)
+    check_unrolled_qasm(dumps(result), expected_qasm)
+
+
+@pytest.mark.parametrize(
+    "input_gates, decomposed_gates",
+    [
+        (
+            "x q[0];",
+            """
+            h q[0];
+            s q[0];
+            s q[0];
+            h q[0];
+            """,
+        ),
+        (
+            "y q[0];",
+            """
+            s q[0];
+            s q[0];
+            h q[0];
+            s q[0];
+            s q[0];
+            h q[0];
+            """,
+        ),
+        (
+            "z q[0];",
+            """
+            s q[0];
+            s q[0];
+            """,
+        ),
+        (
+            "sx q[0];",
+            """
+            s q[0];
+            s q[0];
+            s q[0];
+            h q[0];
+            s q[0];
+            s q[0];
+            s q[0];
+            """,
+        ),
+        (
+            "cz q[0], q[1];",
+            """
+            h q[1];
+            cx q[0], q[1];
+            h q[1];
+            """,
+        ),
+        (
+            "swap q[0], q[1];",
+            """
+            cx q[0], q[1];
+            cx q[1], q[0];
+            cx q[0], q[1];
+            """,
+        ),
+    ],
+)
+def test_rebase_clifford_t(input_gates, decomposed_gates):
+    """Test that the rebasing gates to clifford-T basis works as expected"""
+
+    qasm = f"""OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q;
+    bit[2] c;
+    {input_gates}
+    c[0] = measure q[0];
+    """
+
+    expected_qasm = f"""OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q;
+    bit[2] c;
+    {decomposed_gates}
+    c[0] = measure q[0];
+    """
+
+    result = loads(qasm)
+    result.rebase(BasisSet.CLIFFORD_T)
+    check_unrolled_qasm(dumps(result), expected_qasm)
+
+
+def test_rebase_if():
+    """Test converting a QASM3 program that contains if statements"""
+
+    qasm = """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 < 4){
+        h q[0];
+    } else {
+        x q[0];
+    }
+    """
+    expected_qasm = """OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[1] q;
+    bit[4] c;
+    if (c[0] == false) {
+        if (c[1] == false) {
+            if (c[2] == true) {
+                if (c[3] == true) {
+                    ry(1.5707963267948966) q[0];
+                    rx(3.141592653589793) q[0];
+                }
+            }
+        }
+    }
+    if (c[2] == true) {
+        if (c[3] == true) {
+            ry(1.5707963267948966) q[0];
+            rx(3.141592653589793) q[0];
+        } else {
+            rx(3.141592653589793) q[0];
+        }
+    } else {
+        rx(3.141592653589793) q[0];
+    }
+    if (c[0] == false) {
+       if (c[1] == false) {
+           ry(1.5707963267948966) q[0];
+           rx(3.141592653589793) q[0];
+       } else {
+           rx(3.141592653589793) q[0];
+       }
+    } else {
+        rx(3.141592653589793) q[0];
+    }
+    if (c[0] == false) {
+       if (c[1] == false) {
+           ry(1.5707963267948966) q[0];
+           rx(3.141592653589793) q[0];
+       } else {
+           rx(3.141592653589793) q[0];
+       }
+    } else {
+        rx(3.141592653589793) q[0];
+    }
+    """
+
+    result = loads(qasm)
+    result.rebase(BasisSet.ROTATIONAL_CX)
+    check_unrolled_qasm(dumps(result), expected_qasm)
+
+
+def test_rebase_invalid_basis_set():
+    qasm = """
+        OPENQASM 3.0;
+        include "stdgates.inc";
+
+        qubit[2] q;
+        bit[2] c;
+
+        h q[0];
+        cx q[0], q[1];
+        measure q->c;
+        """
+
+    result = loads(qasm)
+
+    with pytest.raises(ValueError, match="Target basis set 'invalid_basis' is not defined."):
+        result.rebase("invalid_basis")
+
+
+def test_rebase_loop():
+    """Test converting a QASM3 program that contains a for loop"""
+
+    qasm = """
+        OPENQASM 3.0;
+        include "stdgates.inc";
+
+        qubit[4] q;
+        bit[4] c;
+
+        for int i in [0:3]{
+            z q[i];
+        }
+        measure q->c;
+        """
+
+    result = loads(qasm)
+    result = result.rebase(BasisSet.CLIFFORD_T)
+
+    check_single_qubit_gate_op(result.unrolled_ast, 8, [0, 0, 1, 1, 2, 2, 3, 3], "s")
+
+
+def test_rebase_qasm_module_methods():
+    """Test that all other methods of Qasm Modules works as expected after rebase"""
+
+    qasm = """
+        OPENQASM 3.0;
+        include "stdgates.inc";
+        
+        qubit[3] q;
+        bit[3] c;
+        
+        h q[0];
+        cx q[0], q[1];
+        
+        barrier q;
+        measure q[0]->c[0];
+        """
+
+    result = loads(qasm)
+    result = result.rebase(BasisSet.ROTATIONAL_CX)
+
+    # Check has and remove barrier
+    assert result.has_barriers() is True
+    result.remove_barriers()
+    assert result.has_barriers() is False
+
+    # Check har and remove measurement
+    assert result.has_measurements() is True
+    result.remove_measurements()
+    assert result.has_measurements() is False
+
+    # Check remove idle qubit
+    assert result.num_qubits == 3
+    result.remove_idle_qubits()
+    assert result.num_qubits == 2
+
+    # Check depth
+    assert result.depth() == 3
+
+    # Check reverse qubit order
+    expected_reverse_qubit_order_qasm = """
+    OPENQASM 3.0;
+    include "stdgates.inc";
+    qubit[2] q;
+    bit[3] c;
+    ry(1.5707963267948966) q[1];
+    rx(3.141592653589793) q[1];
+    cx q[1], q[0];
+    """
+    result.reverse_qubit_order()
+    print(dumps(result))
+    check_unrolled_qasm(dumps(result), expected_reverse_qubit_order_qasm)
diff --git a/tests/qasm3/test_transformations.py b/tests/qasm3/test_transformations.py
index 2313edc..ca42220 100644
--- a/tests/qasm3/test_transformations.py
+++ b/tests/qasm3/test_transformations.py
@@ -9,7 +9,7 @@
 # THERE IS NO WARRANTY for PyQASM, as per Section 15 of the GPL v3.
 
 """
-Module containing unit tests for transformations on qasm3 programs 
+Module containing unit tests for transformations on qasm3 programs
 
 """