Skip to content

Circuits API Reference

This page documents the classes and functions available in the skq.circuits module. This module provides tools for creating and manipulating quantum circuits, as well as implementations of quantum algorithms.

Circuit Building Blocks

Circuit

skq.circuits.Circuit

Bases: list

Run multiple qubit gates in sequence.

Source code in src/skq/circuits/circuit.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Circuit(list):
    """Run multiple qubit gates in sequence."""

    @property
    def num_qubits(self) -> int:
        return max(g.num_qubits for g in self)

    def encodes(self, x):
        for gate in self:
            x = gate.encodes(x)
        return x

    def decodes(self, x):
        for gate in reversed(self):
            x = gate.decodes(x)
        return x

    def __call__(self, x):
        return self.encodes(x)

    def convert(self, framework="qiskit"):
        """Convert circuit to a given framework.
        :param framework: Framework to convert to.
        :return: Converter Circuit object.
        For Qiskit -> QuantumCircuit object.
        For QASM -> OpenQASM string.
        """
        converter_mapping = {"qiskit": convert_to_qiskit, "qasm": convert_to_qasm}
        assert framework in converter_mapping, f"Invalid framework. Supported frameworks: {converter_mapping.keys()}."
        return converter_mapping[framework](self)

    def draw(self, **kwargs):
        """Draw circuit using Qiskit."""
        return self.convert(framework="qiskit").draw(**kwargs)

convert(framework='qiskit')

Convert circuit to a given framework. :param framework: Framework to convert to. :return: Converter Circuit object. For Qiskit -> QuantumCircuit object. For QASM -> OpenQASM string.

Source code in src/skq/circuits/circuit.py
30
31
32
33
34
35
36
37
38
39
def convert(self, framework="qiskit"):
    """Convert circuit to a given framework.
    :param framework: Framework to convert to.
    :return: Converter Circuit object.
    For Qiskit -> QuantumCircuit object.
    For QASM -> OpenQASM string.
    """
    converter_mapping = {"qiskit": convert_to_qiskit, "qasm": convert_to_qasm}
    assert framework in converter_mapping, f"Invalid framework. Supported frameworks: {converter_mapping.keys()}."
    return converter_mapping[framework](self)

draw(**kwargs)

Draw circuit using Qiskit.

Source code in src/skq/circuits/circuit.py
41
42
43
def draw(self, **kwargs):
    """Draw circuit using Qiskit."""
    return self.convert(framework="qiskit").draw(**kwargs)

Concat

from skq.circuits import Circuit, Concat
from skq.gates import H, I

# Apply Hadamard to first qubit and Identity to second qubit
parallel_gates = Concat([H(), I()])

# Create a circuit with these parallel gates
circuit = Circuit([parallel_gates])

skq.circuits.Concat

Combine multiple qubit gates into a single gate. :param gates: List of gates to concatenate.

Source code in src/skq/circuits/circuit.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Concat:
    """
    Combine multiple qubit gates into a single gate.
    :param gates: List of gates to concatenate.
    """

    def __init__(self, gates: list[Operator]):
        assert len(gates) > 1, "Concat must have at least 2 gates."
        assert all(isinstance(g, Operator) for g in gates), "All gates must be instances of Operator."
        self.gates = gates
        self.encoding_matrix = reduce(np.kron, [g for g in gates])
        self.num_qubits = sum(g.num_qubits for g in gates)

    def encodes(self, x: np.ndarray) -> np.ndarray:
        """
        Concatenate 2 or more gates.

        :param x: Quantum state to encode.
        :return: Quantum state after encoding.
        """
        return x @ self.encoding_matrix

    def decodes(self, x: np.ndarray) -> np.ndarray:
        """
        Reverse propagation for all gates.

        :param x: Quantum state to decode.
        :return: Quantum state after decoding.
        """
        for g in reversed(self.gates):
            x = x @ np.kron(g.conj().T, np.eye(len(x) // g.shape[0]))
        return x

    def __call__(self, x):
        return self.encodes(x)

decodes(x)

Reverse propagation for all gates.

:param x: Quantum state to decode. :return: Quantum state after decoding.

Source code in src/skq/circuits/circuit.py
68
69
70
71
72
73
74
75
76
77
def decodes(self, x: np.ndarray) -> np.ndarray:
    """
    Reverse propagation for all gates.

    :param x: Quantum state to decode.
    :return: Quantum state after decoding.
    """
    for g in reversed(self.gates):
        x = x @ np.kron(g.conj().T, np.eye(len(x) // g.shape[0]))
    return x

encodes(x)

Concatenate 2 or more gates.

:param x: Quantum state to encode. :return: Quantum state after encoding.

Source code in src/skq/circuits/circuit.py
59
60
61
62
63
64
65
66
def encodes(self, x: np.ndarray) -> np.ndarray:
    """
    Concatenate 2 or more gates.

    :param x: Quantum state to encode.
    :return: Quantum state after encoding.
    """
    return x @ self.encoding_matrix

Quantum Algorithms

Bell States

The BellStates class provides circuits for creating the four Bell states, which are maximally entangled two-qubit states.

from skq.circuits import BellStates

# Create a circuit for the Φ+ Bell state (|00⟩ + |11⟩)/√2
bell_circuit = BellStates().circuit(configuration=1)

# Create a circuit for the Ψ- Bell state (|01⟩ - |10⟩)/√2
bell_circuit = BellStates().circuit(configuration=4)

skq.circuits.BellStates

Convenience class for generating Bell States as skq Pipelines. More information on defining Bell States: - https://quantumcomputinguk.org/tutorials/introduction-to-bell-states - https://quantumcomputing.stackexchange.com/a/2260

Source code in src/skq/circuits/entangled_states.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class BellStates:
    """
    Convenience class for generating Bell States as skq Pipelines.
    More information on defining Bell States:
    - https://quantumcomputinguk.org/tutorials/introduction-to-bell-states
    - https://quantumcomputing.stackexchange.com/a/2260
    """

    def circuit(self, configuration: int = 1) -> Circuit:
        """
        Return circuit for the Bell State based on the configuration.
        :param configuration: Configuration of the Bell State.
        Configuration 1: |Φ+⟩ =|00> + |11> / sqrt(2)
        Configuration 2: |Φ-⟩ =|00> - |11> / sqrt(2)
        Configuration 3: |Ψ+⟩ =|01> + |10> / sqrt(2)
        Configuration 4: |Ψ-⟩ =|01> - |10> / sqrt(2)
        :return: Circuit for the Bell State.
        """
        assert configuration in [1, 2, 3, 4], f"Invalid Bell State configuration: {configuration}. Configurations are: 1: |Φ+⟩, 2: |Φ-⟩, 3: |Ψ+⟩, 4: |Ψ-⟩"
        config_mapping = {
            1: self.omega_plus,
            2: self.omega_minus,
            3: self.phi_plus,
            4: self.phi_minus,
        }
        pipe = config_mapping[configuration]()
        return pipe

    def omega_plus(self) -> Circuit:
        """
        Return circuit for the entangled state |Φ+⟩ =|00> + |11> / sqrt(2).
        This corresponds to the 1st bell state.
        :return: Circuit for creating the 1st Bell State.
        """
        return Circuit([Concat([H(), I()]), CX()])

    def omega_minus(self) -> Circuit:
        """
        Return circuit for the entangled state |Φ−⟩ =|00> - |11> / sqrt(2).
        This corresponds to the 2nd bell state.
        :return: Circuit for creating the 2nd Bell State

        """
        return Circuit([Concat([H(), I()]), CX(), Concat([Z(), I()])])

    def phi_plus(self) -> Circuit:
        """
        Return circuit for the entangled state  |Ψ+⟩ =|01> + |10> / sqrt(2).
        This corresponds to the 3rd bell state.
        :return: Circuit for creating the 3rd Bell State
        """
        return Circuit([Concat([H(), X()]), CX()])

    def phi_minus(self) -> Circuit:
        """
        Return circuit for the entangled state |Ψ−⟩ =|01> - |10> / sqrt(2).
        This corresponds to the 4th bell state.
        :return: Circuit for creating the 4th Bell State
        """
        return Circuit([Concat([H(), X()]), Concat([Z(), Z()]), CX()])

circuit(configuration=1)

Return circuit for the Bell State based on the configuration. :param configuration: Configuration of the Bell State. Configuration 1: |Φ+⟩ =|00> + |11> / sqrt(2) Configuration 2: |Φ-⟩ =|00> - |11> / sqrt(2) Configuration 3: |Ψ+⟩ =|01> + |10> / sqrt(2) Configuration 4: |Ψ-⟩ =|01> - |10> / sqrt(2) :return: Circuit for the Bell State.

Source code in src/skq/circuits/entangled_states.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def circuit(self, configuration: int = 1) -> Circuit:
    """
    Return circuit for the Bell State based on the configuration.
    :param configuration: Configuration of the Bell State.
    Configuration 1: |Φ+⟩ =|00> + |11> / sqrt(2)
    Configuration 2: |Φ-⟩ =|00> - |11> / sqrt(2)
    Configuration 3: |Ψ+⟩ =|01> + |10> / sqrt(2)
    Configuration 4: |Ψ-⟩ =|01> - |10> / sqrt(2)
    :return: Circuit for the Bell State.
    """
    assert configuration in [1, 2, 3, 4], f"Invalid Bell State configuration: {configuration}. Configurations are: 1: |Φ+⟩, 2: |Φ-⟩, 3: |Ψ+⟩, 4: |Ψ-⟩"
    config_mapping = {
        1: self.omega_plus,
        2: self.omega_minus,
        3: self.phi_plus,
        4: self.phi_minus,
    }
    pipe = config_mapping[configuration]()
    return pipe

omega_minus()

Return circuit for the entangled state |Φ−⟩ =|00> - |11> / sqrt(2). This corresponds to the 2nd bell state. :return: Circuit for creating the 2nd Bell State

Source code in src/skq/circuits/entangled_states.py
44
45
46
47
48
49
50
51
def omega_minus(self) -> Circuit:
    """
    Return circuit for the entangled state |Φ−⟩ =|00> - |11> / sqrt(2).
    This corresponds to the 2nd bell state.
    :return: Circuit for creating the 2nd Bell State

    """
    return Circuit([Concat([H(), I()]), CX(), Concat([Z(), I()])])

omega_plus()

Return circuit for the entangled state |Φ+⟩ =|00> + |11> / sqrt(2). This corresponds to the 1st bell state. :return: Circuit for creating the 1st Bell State.

Source code in src/skq/circuits/entangled_states.py
36
37
38
39
40
41
42
def omega_plus(self) -> Circuit:
    """
    Return circuit for the entangled state |Φ+⟩ =|00> + |11> / sqrt(2).
    This corresponds to the 1st bell state.
    :return: Circuit for creating the 1st Bell State.
    """
    return Circuit([Concat([H(), I()]), CX()])

phi_minus()

Return circuit for the entangled state |Ψ−⟩ =|01> - |10> / sqrt(2). This corresponds to the 4th bell state. :return: Circuit for creating the 4th Bell State

Source code in src/skq/circuits/entangled_states.py
61
62
63
64
65
66
67
def phi_minus(self) -> Circuit:
    """
    Return circuit for the entangled state |Ψ−⟩ =|01> - |10> / sqrt(2).
    This corresponds to the 4th bell state.
    :return: Circuit for creating the 4th Bell State
    """
    return Circuit([Concat([H(), X()]), Concat([Z(), Z()]), CX()])

phi_plus()

Return circuit for the entangled state |Ψ+⟩ =|01> + |10> / sqrt(2). This corresponds to the 3rd bell state. :return: Circuit for creating the 3rd Bell State

Source code in src/skq/circuits/entangled_states.py
53
54
55
56
57
58
59
def phi_plus(self) -> Circuit:
    """
    Return circuit for the entangled state  |Ψ+⟩ =|01> + |10> / sqrt(2).
    This corresponds to the 3rd bell state.
    :return: Circuit for creating the 3rd Bell State
    """
    return Circuit([Concat([H(), X()]), CX()])

GHZ States

The GHZStates class provides circuits for creating Greenberger-Horne-Zeilinger (GHZ) states, which are multi-qubit generalizations of Bell states.

from skq.circuits import GHZStates

# Create a circuit for a 3-qubit GHZ state (|000⟩ + |111⟩)/√2
ghz_circuit = GHZStates().circuit(n_qubits=3)

skq.circuits.GHZStates

Generalization of Bell States to 3 or more qubits. Greenberger-Horne-Zeilinger (GHZ) states.

Source code in src/skq/circuits/entangled_states.py
70
71
72
73
74
75
76
77
78
79
80
81
82
class GHZStates:
    """
    Generalization of Bell States to 3 or more qubits.
    Greenberger-Horne-Zeilinger (GHZ) states.
    """

    def circuit(self, n_qubits: int) -> Circuit:
        """
        :param n_qubits: Number of qubits in the GHZ state.
        :return: Circuit for the GHZ state.
        """
        assert n_qubits > 2, "GHZ state requires at least 3 qubits"
        return Circuit([Concat([H()] + [I()] * (n_qubits - 1)), *[Concat([I()] * i + [CX()] + [I()] * (n_qubits - i - 2)) for i in range(n_qubits - 1)]])

circuit(n_qubits)

:param n_qubits: Number of qubits in the GHZ state. :return: Circuit for the GHZ state.

Source code in src/skq/circuits/entangled_states.py
76
77
78
79
80
81
82
def circuit(self, n_qubits: int) -> Circuit:
    """
    :param n_qubits: Number of qubits in the GHZ state.
    :return: Circuit for the GHZ state.
    """
    assert n_qubits > 2, "GHZ state requires at least 3 qubits"
    return Circuit([Concat([H()] + [I()] * (n_qubits - 1)), *[Concat([I()] * i + [CX()] + [I()] * (n_qubits - i - 2)) for i in range(n_qubits - 1)]])

W State

The WState class provides a circuit for creating the 3-qubit W state, which is another type of entangled state.

from skq.circuits import WState

# Create a circuit for the W state (|001⟩ + |010⟩ + |100⟩)/√3
w_circuit = WState().circuit()

skq.circuits.WState

3-qubit W State: (|001⟩ + |010⟩ + |100⟩)/√3

Source code in src/skq/circuits/entangled_states.py
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class WState:
    """3-qubit W State: (|001⟩ + |010⟩ + |100⟩)/√3"""

    def circuit(self) -> Circuit:
        theta = -2 * np.arccos(1 / np.sqrt(3))
        return Circuit(
            [
                Concat([RY(theta), I(), I()]),
                Concat([CH(), I()]),
                Concat([I(), CX()]),
                Concat([CX(), I()]),
                Concat([X(), I(), I()]),
            ]
        )

Deutsch's Algorithm

The Deutsch class implements Deutsch's algorithm, which determines whether a function is constant or balanced with a single query.

from skq.circuits import Deutsch

# Define a constant function (always returns 0)
def constant_function(x):
    return 0

# Create a circuit for Deutsch's algorithm
deutsch_circuit = Deutsch().circuit(f=constant_function)

skq.circuits.Deutsch

Deutsch's algorithm.

Source code in src/skq/circuits/deutsch.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Deutsch:
    """Deutsch's algorithm."""

    def circuit(self, f: Callable, measure: bool = True) -> Circuit:
        """
        Deutsch's algorithm
        :param f: Binary function that maps a single bit to a single bit.
        :param measure: Whether to measure the qubits.
        :return: skq Circuit that implements Deutsch's algorithm.
        """
        circuit = [
            Concat([I(), X()]),
            Concat([H(), H()]),
            DeutschOracle(f),
            Concat([H(), I()]),
        ]
        if measure:
            circuit.append(Measure())
        return Circuit(circuit)

circuit(f, measure=True)

Deutsch's algorithm :param f: Binary function that maps a single bit to a single bit. :param measure: Whether to measure the qubits. :return: skq Circuit that implements Deutsch's algorithm.

Source code in src/skq/circuits/deutsch.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def circuit(self, f: Callable, measure: bool = True) -> Circuit:
    """
    Deutsch's algorithm
    :param f: Binary function that maps a single bit to a single bit.
    :param measure: Whether to measure the qubits.
    :return: skq Circuit that implements Deutsch's algorithm.
    """
    circuit = [
        Concat([I(), X()]),
        Concat([H(), H()]),
        DeutschOracle(f),
        Concat([H(), I()]),
    ]
    if measure:
        circuit.append(Measure())
    return Circuit(circuit)

Deutsch-Jozsa Algorithm

The DeutschJozsa class implements the Deutsch-Jozsa algorithm, which is a generalization of Deutsch's algorithm to multiple qubits.

from skq.circuits import DeutschJozsa

# Define a constant function for multiple bits
def constant_function(x):
    return 0

# Create a circuit for the Deutsch-Jozsa algorithm with 3 qubits
dj_circuit = DeutschJozsa().circuit(f=constant_function, n_bits=3)

skq.circuits.DeutschJozsa

Deutsch-Jozsa algorithm.

Source code in src/skq/circuits/deutsch.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class DeutschJozsa:
    """Deutsch-Jozsa algorithm."""

    def circuit(self, f: Callable, n_bits: int, measure: bool = True) -> Circuit:
        """
        Deutsch-Josza algorithm
        :param f: Binary function that maps a single bit to a single bit.
        :return: skq Circuit that implements Deutsch-Josza algorithm.
        """
        circuit = [
            Concat([I() for _ in range(n_bits - 1)] + [X()]),
            Concat([H() for _ in range(n_bits)]),
            DeutschJozsaOracle(f, n_bits=n_bits),
            Concat([H() for _ in range(n_bits)]),
        ]
        if measure:
            circuit.append(Measure())
        return Circuit(circuit)

circuit(f, n_bits, measure=True)

Deutsch-Josza algorithm :param f: Binary function that maps a single bit to a single bit. :return: skq Circuit that implements Deutsch-Josza algorithm.

Source code in src/skq/circuits/deutsch.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def circuit(self, f: Callable, n_bits: int, measure: bool = True) -> Circuit:
    """
    Deutsch-Josza algorithm
    :param f: Binary function that maps a single bit to a single bit.
    :return: skq Circuit that implements Deutsch-Josza algorithm.
    """
    circuit = [
        Concat([I() for _ in range(n_bits - 1)] + [X()]),
        Concat([H() for _ in range(n_bits)]),
        DeutschJozsaOracle(f, n_bits=n_bits),
        Concat([H() for _ in range(n_bits)]),
    ]
    if measure:
        circuit.append(Measure())
    return Circuit(circuit)

Grover's Algorithm

The Grover class implements Grover's search algorithm, which can find a marked item in an unsorted database quadratically faster than classical algorithms.

from skq.circuits import Grover
import numpy as np

# Create a target state to search for (|100⟩)
target_state = np.zeros(8)
target_state[4] = 1

# Create a circuit for Grover's algorithm
grover_circuit = Grover().circuit(
    target_state=target_state,
    n_qubits=3,
    n_iterations=1
)

skq.circuits.Grover

Grover's search algorithm.

Source code in src/skq/circuits/grover.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Grover:
    """
    Grover's search algorithm.
    """

    def circuit(self, target_state: np.array, n_qubits: int, n_iterations: int, measure: bool = True) -> Circuit:
        """
        Grover's search algorithm
        :param target_state: Target state to search for.
        :param n_qubits: Number of qubits in the circuit.
        :param n_iterations: Number of Grover iterations to perform.
        :param measure: Whether to measure the qubits.
        :return: Circuit for the Grover search algorithm.
        """
        single_grover_iteration = [PhaseOracle(target_state), GroverDiffusion(n_qubits=n_qubits)]
        equal_superposition = Concat([H() for _ in range(n_qubits)])
        steps = [equal_superposition, *[gate for _ in range(n_iterations) for gate in single_grover_iteration]]
        if measure:
            steps.append(Measure())
        return Circuit(steps)

circuit(target_state, n_qubits, n_iterations, measure=True)

Grover's search algorithm :param target_state: Target state to search for. :param n_qubits: Number of qubits in the circuit. :param n_iterations: Number of Grover iterations to perform. :param measure: Whether to measure the qubits. :return: Circuit for the Grover search algorithm.

Source code in src/skq/circuits/grover.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def circuit(self, target_state: np.array, n_qubits: int, n_iterations: int, measure: bool = True) -> Circuit:
    """
    Grover's search algorithm
    :param target_state: Target state to search for.
    :param n_qubits: Number of qubits in the circuit.
    :param n_iterations: Number of Grover iterations to perform.
    :param measure: Whether to measure the qubits.
    :return: Circuit for the Grover search algorithm.
    """
    single_grover_iteration = [PhaseOracle(target_state), GroverDiffusion(n_qubits=n_qubits)]
    equal_superposition = Concat([H() for _ in range(n_qubits)])
    steps = [equal_superposition, *[gate for _ in range(n_iterations) for gate in single_grover_iteration]]
    if measure:
        steps.append(Measure())
    return Circuit(steps)

Circuit Conversion

SKQ circuits can be converted to other quantum computing frameworks:

from skq.circuits import Circuit, Concat
from skq.gates import H, I, CX

# Create a Bell state circuit
bell_circuit = Circuit([
    Concat([H(), I()]),  # Apply H to first qubit, I to second qubit
    CX()                 # Apply CNOT with first qubit as control
])

# Convert to Qiskit
qiskit_circuit = bell_circuit.convert(framework="qiskit")

# Convert to OpenQASM
qasm_code = bell_circuit.convert(framework="qasm")

skq.circuits.convert_to_qiskit(circuit)

Convert a skq Circuit into a Qiskit QuantumCircuit.

Source code in src/skq/circuits/circuit.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def convert_to_qiskit(circuit: Circuit) -> QuantumCircuit:
    """Convert a skq Circuit into a Qiskit QuantumCircuit."""

    def handle_gate(qc, gate, start_idx=None):
        if isinstance(gate, I):
            return
        if isinstance(gate, Measure):
            for q in range(circuit.num_qubits):
                qc.measure(q, q)
            return
        # Create list of consecutive qubit indices starting from start_idx
        qubits = list(range(start_idx, start_idx + gate.num_qubits)) if start_idx is not None else list(range(gate.num_qubits))
        qc.append(gate.to_qiskit(), qubits)

    qc = QuantumCircuit(circuit.num_qubits, circuit.num_qubits if any(isinstance(g, Measure) for g in circuit) else 0)

    for gate in circuit:
        if isinstance(gate, Concat):
            offset = 0
            for sub_gate in gate.gates:
                handle_gate(qc, sub_gate, offset)
                offset += sub_gate.num_qubits
        else:
            handle_gate(qc, gate)
    return qc

skq.circuits.convert_to_qasm(circuit)

Convert a skq Circuit into an OpenQASM string.

Source code in src/skq/circuits/circuit.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def convert_to_qasm(circuit: Circuit) -> str:
    """Convert a skq Circuit into an OpenQASM string."""
    qasm_lines = [
        "OPENQASM 2.0;",  # OpenQASM header
        'include "qelib1.inc";',  # Standard library
        f"qreg q[{circuit.num_qubits}];",  # Declare quantum register
    ]
    # Declare classical register if measurement is present
    if any(isinstance(g, Measure) for g in circuit):
        qasm_lines.append(f"creg c[{circuit.num_qubits}];")

    def handle_gate(gate, start_idx=None):
        if isinstance(gate, I):
            return []
        if isinstance(gate, Measure):
            return [f"measure q[{q}] -> c[{q}];" for q in range(circuit.num_qubits)]
        # Create list of consecutive qubit indices starting from start_idx
        qubits = list(range(start_idx, start_idx + gate.num_qubits)) if start_idx is not None else list(range(gate.num_qubits))
        return [gate.to_qasm(qubits)]

    for gate in circuit:
        if isinstance(gate, Concat):
            offset = 0
            for sub_gate in gate.gates:
                qasm_lines.extend(handle_gate(sub_gate, offset))
                offset += sub_gate.num_qubits
        else:
            qasm_lines.extend(handle_gate(gate))
    return "\n".join(qasm_lines)