Repository: qBraid/qBraid (language: python)

Issue:
Quantum Jobs Docs
We want to add a *Quantum Jobs* section to the [Lab User Guide](https://qbraid-qbraid.readthedocs-hosted.com/en/latest/lab/overview.html) portion of our docs page. On this page, we want to give an end-to-end walkthrough of qBraid quantum jobs. A rough list of the topics to cover include:

- What are qBraid credits and how to get them 
- Enabling/disabling quantum jobs with qBraid CLI
- Walkthrough of submitting a quantum job
- How to check jobs list & credits with CLI
- How to use the quantum jobs lab extension to view/manage jobs

See the [README](https://github.com/qBraid/qBraid#documentation) for how to develop / build the docs. 

The [quantum-jobs-docs](https://github.com/qBraid/qBraid/tree/quantum-jobs-docs) branch already includes a file [`docs/lab/quantumjobs.rst`](https://github.com/qBraid/qBraid/blob/4b82e1ab9e1d396b86af01c866243536908beb04/docs/lab/quantumjobs.rst) that you can work from. So after cloning the repo, make sure run
```
git switch quantum-jobs-docs
```
to make sure you are developing on the correct branch.

If you're unfamiliar with writing using sphinx / RST, there are lots of good resources online including [reStructuredText Primer](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html). You can also just copy and modify code from other files in the repo to add links, pictures, style, etc.



Discussion / hints:

Fix patch:
diff --git a/qbraid/transforms/qasm3/transform.py b/qbraid/transforms/qasm3/transform.py
new file mode 100644
index 000000000..cc8bd7554
--- /dev/null
+++ b/qbraid/transforms/qasm3/transform.py
@@ -0,0 +1,227 @@
+# Copyright (C) 2024 qBraid
+#
+# This file is part of the qBraid-SDK
+#
+# The qBraid-SDK 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 the qBraid-SDK, as per Section 15 of the GPL v3.
+
+"""
+Module for providing transforamtions with basis gates.
+across various other quantum software frameworks.
+
+"""
+
+from typing import List
+
+from openqasm3 import ast
+from openqasm3.parser import parse
+
+from qbraid.transpiler.conversions.openqasm3 import openqasm3_to_qasm3
+
+
+def decompose_crx(gate: ast.QuantumGate) -> List[ast.Statement]:
+    """Decompose a crx gate into its basic gate equivalents."""
+    theta = gate.arguments[0]
+    control = gate.qubits[0]
+    target = gate.qubits[1]
+
+    rz_pos_pi_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="rz"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17),
+                lhs=ast.Identifier(name="pi"),
+                rhs=ast.FloatLiteral(value=2),
+            )
+        ],
+        qubits=[target],
+    )
+    ry_pos_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="ry"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17), lhs=theta, rhs=ast.FloatLiteral(value=2)
+            )
+        ],
+        qubits=[target],
+    )
+    cx = ast.QuantumGate(
+        modifiers=[], name=ast.Identifier(name="cx"), arguments=[], qubits=[control, target]
+    )
+    ry_neg_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="ry"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17),
+                lhs=ast.UnaryExpression(ast.UnaryOperator(3), theta),
+                rhs=ast.FloatLiteral(value=2),
+            )
+        ],
+        qubits=[target],
+    )
+    rz_neg_pi_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="rz"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17),
+                lhs=ast.UnaryExpression(ast.UnaryOperator(3), ast.Identifier(name="pi")),
+                rhs=ast.FloatLiteral(value=2),
+            )
+        ],
+        qubits=[target],
+    )
+    return [rz_pos_pi_half, ry_pos_theta_half, cx, ry_neg_theta_half, cx, rz_neg_pi_half]
+
+
+def decompose_cry(gate: ast.QuantumGate) -> List[ast.Statement]:
+    """Decompose a cry gate into its basic gate equivalents."""
+    theta = gate.arguments[0]
+    control = gate.qubits[0]
+    target = gate.qubits[1]
+
+    ry_pos_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="ry"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17), lhs=theta, rhs=ast.FloatLiteral(value=2)
+            )
+        ],
+        qubits=[target],
+    )
+    cx = ast.QuantumGate(
+        modifiers=[], name=ast.Identifier(name="cx"), arguments=[], qubits=[control, target]
+    )
+    ry_neg_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="ry"),
+        arguments=[
+            ast.UnaryExpression(
+                ast.UnaryOperator(3),
+                ast.BinaryExpression(
+                    op=ast.BinaryOperator(17), lhs=theta, rhs=ast.FloatLiteral(value=2)
+                ),
+            )
+        ],
+        qubits=[target],
+    )
+    return [ry_pos_theta_half, cx, ry_neg_theta_half, cx]
+
+
+def decompose_crz(gate: ast.QuantumGate) -> List[ast.Statement]:
+    """Decompose a cry gate into its basic gate equivalents."""
+    theta = gate.arguments[0]
+    control = gate.qubits[0]
+    target = gate.qubits[1]
+
+    rz_pos_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="rz"),
+        arguments=[
+            ast.BinaryExpression(
+                op=ast.BinaryOperator(17), lhs=theta, rhs=ast.FloatLiteral(value=2)
+            )
+        ],
+        qubits=[target],
+    )
+    cx = ast.QuantumGate(
+        modifiers=[], name=ast.Identifier(name="cx"), arguments=[], qubits=[control, target]
+    )
+    rz_neg_theta_half = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="rz"),
+        arguments=[
+            ast.UnaryExpression(
+                ast.UnaryOperator(3),
+                ast.BinaryExpression(
+                    op=ast.BinaryOperator(17), lhs=theta, rhs=ast.FloatLiteral(value=2)
+                ),
+            )
+        ],
+        qubits=[target],
+    )
+    return [rz_pos_theta_half, cx, rz_neg_theta_half, cx]
+
+
+def decompose_cy(gate: ast.QuantumGate) -> List[ast.Statement]:
+    """Decompose a cy gate into its basic gate equivalents."""
+    control = gate.qubits[0]
+    target = gate.qubits[1]
+
+    cry_pi = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="cry"),
+        arguments=[ast.Identifier(name="pi")],
+        qubits=[control, target],
+    )
+    s = ast.QuantumGate(modifiers=[], name=ast.Identifier(name="s"), arguments=[], qubits=[control])
+    return transform_program(ast.Program(statements=[cry_pi, s])).statements
+
+
+def decompose_cz(gate: ast.QuantumGate) -> List[ast.Statement]:
+    """Decompose a cz gate into its basic gate equivalents."""
+    control = gate.qubits[0]
+    target = gate.qubits[1]
+
+    crz_pi = ast.QuantumGate(
+        modifiers=[],
+        name=ast.Identifier(name="crz"),
+        arguments=[ast.Identifier(name="pi")],
+        qubits=[control, target],
+    )
+    s = ast.QuantumGate(modifiers=[], name=ast.Identifier(name="s"), arguments=[], qubits=[control])
+    return transform_program(ast.Program(statements=[crz_pi, s])).statements
+
+
+def transform_program(program: ast.Program) -> ast.Program:
+    """Transform a QASM program, decomposing crx gates."""
+    transformed_statements = []
+    for statement in program.statements:
+        if isinstance(statement, ast.QuantumGate):
+            if statement.name.name == "crx":
+                transformed_statements.extend(decompose_crx(statement))
+            elif statement.name.name == "cry":
+                transformed_statements.extend(decompose_cry(statement))
+            elif statement.name.name == "crz":
+                transformed_statements.extend(decompose_crz(statement))
+            elif statement.name.name == "cy":
+                transformed_statements.extend(decompose_cy(statement))
+            elif statement.name.name == "cz":
+                transformed_statements.extend(decompose_cz(statement))
+            else:
+                transformed_statements.append(statement)
+        else:
+            transformed_statements.append(statement)
+
+    return ast.Program(statements=transformed_statements, version=program.version)
+
+
+def convert_to_basis_gates(qasm: str, basis_gates: list[str]) -> str:
+    """
+    Converts an OpenQASM 3 program to an equivalent program
+    Only uses the specified basis gates.
+
+    Args:
+        openqasm_program (str): The original OpenQASM 3 program as a string.
+        basis_gates (list[str]): A list of gate names allowed in the basis set.
+
+    Returns:
+        str: The converted OpenQASM 3 program.
+
+    Raises:
+        ValueError: if the decomposition is not possible
+    """
+    print(basis_gates)
+
+    program = parse(qasm)
+
+    converted_program = transform_program(program)
+
+    return openqasm3_to_qasm3(converted_program)


Test patch:
diff --git a/tests/transforms/test_qasm3_transforms.py b/tests/transforms/test_qasm3_transforms.py
new file mode 100644
index 000000000..b051d306f
--- /dev/null
+++ b/tests/transforms/test_qasm3_transforms.py
@@ -0,0 +1,128 @@
+# Copyright (C) 2024 qBraid
+#
+# This file is part of the qBraid-SDK
+#
+# The qBraid-SDK 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 the qBraid-SDK, as per Section 15 of the GPL v3.
+
+"""
+Unit tests for QASM transform to basic gates
+
+"""
+
+import pytest
+
+from qbraid.transforms.qasm3.transform import convert_to_basis_gates
+
+
+@pytest.mark.parametrize(
+    "original_program, expected_program",
+    [
+        (
+            """
+OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+crx(pi) q[0], q[1];
+""",
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+rz(pi / 2) q[1];
+ry(pi / 2) q[1];
+cx q[0], q[1];
+ry(-pi / 2) q[1];
+cx q[0], q[1];
+rz(-pi / 2) q[1];
+""",
+        ),
+        (
+            """
+OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+cry(pi/4) q[0], q[1];
+""",
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+ry(pi / 4 / 2) q[1];
+cx q[0], q[1];
+ry(-(pi / 4 / 2)) q[1];
+cx q[0], q[1];
+""",
+        ),
+        (
+            """
+OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+crz(pi/4) q[0], q[1];
+""",
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+rz(pi / 4 / 2) q[1];
+cx q[0], q[1];
+rz(-(pi / 4 / 2)) q[1];
+cx q[0], q[1];
+""",
+        ),
+        (
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+cy q[0], q[1];""",
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+ry(pi / 2) q[1];
+cx q[0], q[1];
+ry(-(pi / 2)) q[1];
+cx q[0], q[1];
+s q[0];
+""",
+        ),
+        (
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+cz q[0], q[1];""",
+            """OPENQASM 3.0;
+include "stdgates.inc";
+qubit[2] q;
+rz(pi / 2) q[1];
+cx q[0], q[1];
+rz(-(pi / 2)) q[1];
+cx q[0], q[1];
+s q[0];
+""",
+        ),
+    ],
+)
+def test_convert_to_basis_gates(original_program, expected_program):
+    """Test conversion of QASM3 program to basis gates"""
+    basis_gates = [
+        "x",
+        "y",
+        "z",
+        "rx",
+        "ry",
+        "rz",
+        "h",
+        "cx",
+        "s",
+        "sdg",
+        "t",
+        "tdg",
+        "sx",
+        "sxdg",
+        "swap",
+    ]
+
+    converted_program = convert_to_basis_gates(original_program, basis_gates)
+
+    assert converted_program == expected_program