Skip to content

Base

emit_circuit

emit_circuit(
    mt: Method,
    qubits: Sequence[Qid] | None = None,
    circuit_qubits: Sequence[Qid] | None = None,
    args: tuple = (),
    ignore_returns: bool = False,
) -> cirq.Circuit

Converts a squin.kernel method to a cirq.Circuit object.

Parameters:

Name Type Description Default
mt Method

The kernel method from which to construct the circuit.

required

Other Parameters:

Name Type Description
circuit_qubits Sequence[Qid] | None

A list of qubits to use as the qubits in the circuit. Defaults to None. If this is None, then cirq.LineQubits are inserted for every squin.qalloc statement in the order they appear inside the kernel. Note: If a list of qubits is provided, make sure that there is a sufficient number of qubits for the resulting circuit.

args tuple

The arguments of the kernel function from which to emit a circuit.

ignore_returns bool

If False, emitting a circuit from a kernel that returns a value will error. Set it to True in order to ignore the return value(s). Defaults to False.

Examples:

Here's a very basic example:

from bloqade import squin
from bloqade.cirq_utils import emit_circuit

@squin.kernel
def main():
    q = squin.qalloc(2)
    squin.h(q[0])
    squin.cx(q[0], q[1])

circuit = emit_circuit(main)

print(circuit)

You can also compose multiple kernels. Those are emitted as subcircuits within the "main" circuit. Subkernels can accept arguments and return a value.

from bloqade import squin
from bloqade.cirq_utils import emit_circuit
from kirin.dialects import ilist
from typing import Literal
import cirq

@squin.kernel
def entangle(q: ilist.IList[squin.qubit.Qubit, Literal[2]]):
    squin.h(q[0])
    squin.cx(q[0], q[1])

@squin.kernel
def main():
    q = squin.qalloc(2)
    q2 = squin.qalloc(3)
    squin.cx(q[1], q2[2])


# custom list of qubits on grid
qubits = [cirq.GridQubit(i, i+1) for i in range(5)]

circuit = emit_circuit(main, circuit_qubits=qubits)
print(circuit)

We also passed in a custom list of qubits above. This allows you to provide a custom geometry and manipulate the qubits in other circuits directly written in cirq as well.

Source code in .venv/lib/python3.12/site-packages/bloqade/cirq_utils/emit/base.py
 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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 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
108
109
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def emit_circuit(
    mt: ir.Method,
    qubits: Sequence[cirq.Qid] | None = None,
    circuit_qubits: Sequence[cirq.Qid] | None = None,
    args: tuple = (),
    ignore_returns: bool = False,
) -> cirq.Circuit:
    """Converts a squin.kernel method to a cirq.Circuit object.

    Args:
        mt (ir.Method): The kernel method from which to construct the circuit.

    Keyword Args:
        circuit_qubits (Sequence[cirq.Qid] | None):
            A list of qubits to use as the qubits in the circuit. Defaults to None.
            If this is None, then `cirq.LineQubit`s are inserted for every `squin.qalloc`
            statement in the order they appear inside the kernel.
            **Note**: If a list of qubits is provided, make sure that there is a sufficient
            number of qubits for the resulting circuit.
        args (tuple):
            The arguments of the kernel function from which to emit a circuit.
        ignore_returns (bool):
            If `False`, emitting a circuit from a kernel that returns a value will error.
            Set it to `True` in order to ignore the return value(s). Defaults to `False`.

    ## Examples:

    Here's a very basic example:

    ```python
    from bloqade import squin
    from bloqade.cirq_utils import emit_circuit

    @squin.kernel
    def main():
        q = squin.qalloc(2)
        squin.h(q[0])
        squin.cx(q[0], q[1])

    circuit = emit_circuit(main)

    print(circuit)
    ```

    You can also compose multiple kernels. Those are emitted as subcircuits within the "main" circuit.
    Subkernels can accept arguments and return a value.

    ```python
    from bloqade import squin
    from bloqade.cirq_utils import emit_circuit
    from kirin.dialects import ilist
    from typing import Literal
    import cirq

    @squin.kernel
    def entangle(q: ilist.IList[squin.qubit.Qubit, Literal[2]]):
        squin.h(q[0])
        squin.cx(q[0], q[1])

    @squin.kernel
    def main():
        q = squin.qalloc(2)
        q2 = squin.qalloc(3)
        squin.cx(q[1], q2[2])


    # custom list of qubits on grid
    qubits = [cirq.GridQubit(i, i+1) for i in range(5)]

    circuit = emit_circuit(main, circuit_qubits=qubits)
    print(circuit)

    ```

    We also passed in a custom list of qubits above. This allows you to provide a custom geometry
    and manipulate the qubits in other circuits directly written in cirq as well.
    """

    if circuit_qubits is None and qubits is not None:
        circuit_qubits = qubits
        warn(
            "The keyword argument `qubits` is deprecated. Use `circuit_qubits` instead."
        )

    if (
        not ignore_returns
        and isinstance(mt.code, func.Function)
        and not mt.code.signature.output.is_subseteq(types.NoneType)
    ):
        raise EmitError(
            "The method you are trying to convert to a circuit has a return value, but returning from a circuit is not supported."
            " Set `ignore_returns = True` in order to simply ignore the return values and emit a circuit."
        )

    if len(args) != len(mt.args):
        raise ValueError(
            f"The method from which you're trying to emit a circuit takes {len(mt.args)} as input, but you passed in {len(args)} via the `args` keyword!"
        )

    emitter = EmitCirq(qubits=circuit_qubits)

    symbol_op_trait = mt.code.get_trait(ir.SymbolOpInterface)
    if (symbol_op_trait := mt.code.get_trait(ir.SymbolOpInterface)) is None:
        raise EmitError("The method is not a symbol, cannot emit circuit!")

    sym_name = symbol_op_trait.get_sym_name(mt.code).unwrap()

    if (signature_trait := mt.code.get_trait(ir.HasSignature)) is None:
        raise EmitError(
            f"The method {sym_name} does not have a signature, cannot emit circuit!"
        )

    signature = signature_trait.get_signature(mt.code)
    new_signature = func.Signature(inputs=(), output=signature.output)

    callable_region = mt.callable_region.clone()
    entry_block = callable_region.blocks[0]
    args_ssa = list(entry_block.args)
    first_stmt = entry_block.first_stmt

    assert first_stmt is not None, "Method has no statements!"
    if len(args_ssa) - 1 != len(args):
        raise EmitError(
            f"The method {sym_name} takes {len(args_ssa) - 1} arguments, but you passed in {len(args)} via the `args` keyword!"
        )

    for arg, arg_ssa in zip(args, args_ssa[1:], strict=True):
        (value := py.Constant(arg)).insert_before(first_stmt)
        arg_ssa.replace_by(value.result)
        entry_block.args.delete(arg_ssa)

    new_func = func.Function(
        sym_name=sym_name, body=callable_region, signature=new_signature
    )
    mt_ = ir.Method(None, None, sym_name, [], mt.dialects, new_func)

    AggressiveUnroll(mt_.dialects).fixpoint(mt_)
    return emitter.run(mt_, args=())