Skip to content

analysis

schedule

StmtDag dataclass

StmtDag(
    id_table: IdTable[
        Statement
    ] = lambda: idtable.IdTable()(),
    stmts: Dict[str, Statement] = OrderedDict(),
    out_edges: Dict[str, Set[str]] = OrderedDict(),
    inc_edges: Dict[str, Set[str]] = OrderedDict(),
    stmt_index: Dict[Statement, int] = OrderedDict(),
)

Bases: Graph[Statement]

topological_groups

topological_groups()

Split the dag into topological groups where each group contains nodes that have no dependencies on each other, but have dependencies on nodes in one or more previous groups.

Yields:

Type Description

List[str]: A list of node ids in a topological group

Raises:

Type Description
ValueError

If a cyclic dependency is detected

The idea is to yield all nodes with no dependencies, then remove those nodes from the graph repeating until no nodes are left or we reach some upper limit. Worse case is a linear dag, so we can use len(dag.stmts) as the upper limit

If we reach the limit and there are still nodes left, then we have a cyclic dependency.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/analysis/schedule.py
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
154
155
156
157
158
def topological_groups(self):
    """Split the dag into topological groups where each group
    contains nodes that have no dependencies on each other, but
    have dependencies on nodes in one or more previous groups.

    Yields:
        List[str]: A list of node ids in a topological group


    Raises:
        ValueError: If a cyclic dependency is detected


    The idea is to yield all nodes with no dependencies, then remove
    those nodes from the graph repeating until no nodes are left
    or we reach some upper limit. Worse case is a linear dag,
    so we can use len(dag.stmts) as the upper limit

    If we reach the limit and there are still nodes left, then we
    have a cyclic dependency.
    """

    inc_edges = {k: set(v) for k, v in self.inc_edges.items()}

    check_next = inc_edges.keys()

    for _ in range(len(self.stmts)):
        if len(inc_edges) == 0:
            break

        group = [node_id for node_id in check_next if len(inc_edges[node_id]) == 0]
        yield group

        check_next = set()
        for n in group:
            inc_edges.pop(n)
            for m in self.out_edges[n]:
                check_next.add(m)
                inc_edges[m].remove(n)

    if inc_edges:
        raise ValueError("Cyclic dependency detected")

cirq

dump_circuit

dump_circuit(
    mt: Method,
    qubits: Sequence[Qid] | None = None,
    **kwargs
)

Converts a squin.kernel method to a cirq.Circuit object and dumps it as JSON.

This just runs emit_circuit and calls the cirq.to_json function to emit a JSON.

Parameters:

Name Type Description Default
mt Method

The kernel method from which to construct the circuit.

required

Other Parameters:

Name Type Description
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.qubit.new 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.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/cirq/__init__.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def dump_circuit(mt: ir.Method, qubits: Sequence[cirq.Qid] | None = None, **kwargs):
    """Converts a squin.kernel method to a cirq.Circuit object and dumps it as JSON.

    This just runs `emit_circuit` and calls the `cirq.to_json` function to emit a JSON.

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

    Keyword Args:
        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.qubit.new`
            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.

    """
    circuit = emit_circuit(mt, qubits=qubits)
    return cirq.to_json(circuit, **kwargs)

emit_circuit

emit_circuit(
    mt: Method, qubits: Sequence[Qid] | None = None
) -> 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
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.qubit.new 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.

Examples:

Here's a very basic example:

from bloqade import squin

@squin.kernel
def main():
    q = squin.qubit.new(2)
    h = squin.op.h()
    squin.qubit.apply(h, q[0])
    cx = squin.op.cx()
    squin.qubit.apply(cx, q)

circuit = squin.cirq.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 kirin.dialects import ilist
from typing import Literal
import cirq

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

@squin.kernel
def main():
    q = squin.qubit.new(2)
    cx = entangle(q)
    q2 = squin.qubit.new(3)
    squin.qubit.apply(cx, [q[1], q2[2]])


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

circuit = squin.cirq.emit_circuit(main, 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/squin/cirq/__init__.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
def emit_circuit(
    mt: ir.Method,
    qubits: Sequence[cirq.Qid] | None = None,
) -> 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:
        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.qubit.new`
            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.

    ## Examples:

    Here's a very basic example:

    ```python
    from bloqade import squin

    @squin.kernel
    def main():
        q = squin.qubit.new(2)
        h = squin.op.h()
        squin.qubit.apply(h, q[0])
        cx = squin.op.cx()
        squin.qubit.apply(cx, q)

    circuit = squin.cirq.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 kirin.dialects import ilist
    from typing import Literal
    import cirq

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

    @squin.kernel
    def main():
        q = squin.qubit.new(2)
        cx = entangle(q)
        q2 = squin.qubit.new(3)
        squin.qubit.apply(cx, [q[1], q2[2]])


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

    circuit = squin.cirq.emit_circuit(main, 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 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."
        )

    emitter = EmitCirq(qubits=qubits)

    # Rewrite noise statements
    mt_ = mt.similar(mt.dialects)
    RewriteNoiseStmts(mt_.dialects)(mt_)

    return emitter.run(mt_, args=())

load_circuit

load_circuit(
    circuit: Circuit,
    kernel_name: str = "main",
    dialects: DialectGroup = kernel,
    register_as_argument: bool = False,
    return_register: bool = False,
    register_argument_name: str = "q",
    globals: dict[str, Any] | None = None,
    file: str | None = None,
    lineno_offset: int = 0,
    col_offset: int = 0,
    compactify: bool = True,
)

Converts a cirq.Circuit object into a squin kernel.

Parameters:

Name Type Description Default
circuit Circuit

The circuit to load.

required

Other Parameters:

Name Type Description
kernel_name str

The name of the kernel to load. Defaults to "main".

dialects DialectGroup | None

The dialects to use. Defaults to squin.kernel.

register_as_argument bool

Determine whether the resulting kernel function should accept a single ilist.IList[Qubit, Any] argument that is a list of qubits used within the function. This allows you to compose kernel functions generated from circuits. Defaults to False.

return_register bool

Determine whether the resulting kernel functionr returns a single value of type ilist.IList[Qubit, Any] that is the list of qubits used in the kernel function. Useful when you want to compose multiple kernel functions generated from circuits. Defaults to False.

register_argument_name str

The name of the argument that represents the qubit register. Only used when register_as_argument=True. Defaults to "q".

globals dict[str, Any] | None

The global variables to use. Defaults to None.

file str | None

The file name for error reporting. Defaults to None.

lineno_offset int

The line number offset for error reporting. Defaults to 0.

col_offset int

The column number offset for error reporting. Defaults to 0.

compactify bool

Whether to compactify the output. Defaults to True.

Usage Examples:

# from cirq's "hello qubit" example
import cirq
from bloqade import squin

# Pick a qubit.
qubit = cirq.GridQubit(0, 0)

# Create a circuit.
circuit = cirq.Circuit(
    cirq.X(qubit)**0.5,  # Square root of NOT.
    cirq.measure(qubit, key='m')  # Measurement.
)

# load the circuit as squin
main = squin.load_circuit(circuit)

# print the resulting IR
main.print()

You can also compose kernel functions generated from circuits by passing in and / or returning the respective quantum registers:

q = cirq.LineQubit.range(2)
circuit = cirq.Circuit(cirq.H(q[0]), cirq.CX(*q))

get_entangled_qubits = squin.cirq.load_circuit(
    circuit, return_register=True, kernel_name="get_entangled_qubits"
)
get_entangled_qubits.print()

entangle_qubits = squin.cirq.load_circuit(
    circuit, register_as_argument=True, kernel_name="entangle_qubits"
)

@squin.kernel
def main():
    qreg = get_entangled_qubits()
    qreg2 = squin.qubit.new(1)
    entangle_qubits([qreg[1], qreg2[0]])
    return squin.qubit.measure(qreg2)
Source code in .venv/lib/python3.12/site-packages/bloqade/squin/cirq/__init__.py
 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
154
def load_circuit(
    circuit: cirq.Circuit,
    kernel_name: str = "main",
    dialects: ir.DialectGroup = kernel,
    register_as_argument: bool = False,
    return_register: bool = False,
    register_argument_name: str = "q",
    globals: dict[str, Any] | None = None,
    file: str | None = None,
    lineno_offset: int = 0,
    col_offset: int = 0,
    compactify: bool = True,
):
    """Converts a cirq.Circuit object into a squin kernel.

    Args:
        circuit (cirq.Circuit): The circuit to load.

    Keyword Args:
        kernel_name (str): The name of the kernel to load. Defaults to "main".
        dialects (ir.DialectGroup | None): The dialects to use. Defaults to `squin.kernel`.
        register_as_argument (bool): Determine whether the resulting kernel function should accept
            a single `ilist.IList[Qubit, Any]` argument that is a list of qubits used within the
            function. This allows you to compose kernel functions generated from circuits.
            Defaults to `False`.
        return_register (bool): Determine whether the resulting kernel functionr returns a
            single value of type `ilist.IList[Qubit, Any]` that is the list of qubits used
            in the kernel function. Useful when you want to compose multiple kernel functions
            generated from circuits. Defaults to `False`.
        register_argument_name (str): The name of the argument that represents the qubit register.
            Only used when `register_as_argument=True`. Defaults to "q".
        globals (dict[str, Any] | None): The global variables to use. Defaults to None.
        file (str | None): The file name for error reporting. Defaults to None.
        lineno_offset (int): The line number offset for error reporting. Defaults to 0.
        col_offset (int): The column number offset for error reporting. Defaults to 0.
        compactify (bool): Whether to compactify the output. Defaults to True.

    ## Usage Examples:

    ```python
    # from cirq's "hello qubit" example
    import cirq
    from bloqade import squin

    # Pick a qubit.
    qubit = cirq.GridQubit(0, 0)

    # Create a circuit.
    circuit = cirq.Circuit(
        cirq.X(qubit)**0.5,  # Square root of NOT.
        cirq.measure(qubit, key='m')  # Measurement.
    )

    # load the circuit as squin
    main = squin.load_circuit(circuit)

    # print the resulting IR
    main.print()
    ```

    You can also compose kernel functions generated from circuits by passing in
    and / or returning the respective quantum registers:

    ```python
    q = cirq.LineQubit.range(2)
    circuit = cirq.Circuit(cirq.H(q[0]), cirq.CX(*q))

    get_entangled_qubits = squin.cirq.load_circuit(
        circuit, return_register=True, kernel_name="get_entangled_qubits"
    )
    get_entangled_qubits.print()

    entangle_qubits = squin.cirq.load_circuit(
        circuit, register_as_argument=True, kernel_name="entangle_qubits"
    )

    @squin.kernel
    def main():
        qreg = get_entangled_qubits()
        qreg2 = squin.qubit.new(1)
        entangle_qubits([qreg[1], qreg2[0]])
        return squin.qubit.measure(qreg2)
    ```
    """

    target = Squin(dialects=dialects, circuit=circuit)
    body = target.run(
        circuit,
        source=str(circuit),  # TODO: proper source string
        file=file,
        globals=globals,
        lineno_offset=lineno_offset,
        col_offset=col_offset,
        compactify=compactify,
        register_as_argument=register_as_argument,
        register_argument_name=register_argument_name,
    )

    if return_register:
        return_value = target.qreg
    else:
        return_value = func.ConstantNone()
        body.blocks[0].stmts.append(return_value)

    return_node = func.Return(value_or_stmt=return_value)
    body.blocks[0].stmts.append(return_node)

    self_arg_name = kernel_name + "_self"
    arg_names = [self_arg_name]
    if register_as_argument:
        args = (target.qreg.type,)
        arg_names.append(register_argument_name)
    else:
        args = ()

    # NOTE: add _self as argument; need to know signature before so do it after lowering
    signature = func.Signature(args, return_node.value.type)
    body.blocks[0].args.insert_from(
        0,
        types.Generic(ir.Method, types.Tuple.where(signature.inputs), signature.output),
        self_arg_name,
    )

    code = func.Function(
        sym_name=kernel_name,
        signature=signature,
        body=body,
    )

    return ir.Method(
        mod=None,
        py_func=None,
        sym_name=kernel_name,
        arg_names=arg_names,
        dialects=dialects,
        code=code,
    )

lowering

Squin dataclass

Squin(circuit: Circuit)

Bases: LoweringABC[CirqNode]

Lower a cirq.Circuit object to a squin kernel

lowering

ApplyAnyCallLowering dataclass

ApplyAnyCallLowering()

Bases: FromPythonCall['qubit.ApplyAny']

Custom lowering for ApplyAny that collects vararg qubits into a single tuple argument

noise

rewrite

stmts

Depolarize

Bases: NoiseChannel

Apply depolarize error to qubit

PPError

Bases: NoiseChannel

Pauli Product Error

op

rewrite

Rewrite py.binop.mult to Mult stmt

stdlib

ch

ch() -> types.Op

Control H gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
53
54
55
56
@op
def ch() -> types.Op:
    """Control H gate."""
    return control(h(), n_controls=1)

cphase

cphase(theta: float) -> types.Op

Control Phase gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
59
60
61
62
@op
def cphase(theta: float) -> types.Op:
    """Control Phase gate."""
    return control(phase(theta), n_controls=1)

cx

cx() -> types.Op

Controlled X gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
35
36
37
38
@op
def cx() -> types.Op:
    """Controlled X gate."""
    return control(x(), n_controls=1)

cy

cy() -> types.Op

Controlled Y gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
41
42
43
44
@op
def cy() -> types.Op:
    """Controlled Y gate."""
    return control(y(), n_controls=1)

cz

cz() -> types.Op

Control Z gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
47
48
49
50
@op
def cz() -> types.Op:
    """Control Z gate."""
    return control(z(), n_controls=1)

rx

rx(theta: float) -> types.Op

Rotation X gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
17
18
19
20
@op
def rx(theta: float) -> types.Op:
    """Rotation X gate."""
    return rot(x(), theta)

ry

ry(theta: float) -> types.Op

Rotation Y gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
23
24
25
26
@op
def ry(theta: float) -> types.Op:
    """Rotation Y gate."""
    return rot(y(), theta)

rz

rz(theta: float) -> types.Op

Rotation Z gate.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/op/stdlib.py
29
30
31
32
@op
def rz(theta: float) -> types.Op:
    """Rotation Z gate."""
    return rot(z(), theta)

stmts

P0

Bases: ConstantOp

The \(P_0\) projection operator.

\[ P0 = \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \]

P1

Bases: ConstantOp

The \(P_1\) projection operator.

\[ P1 = \begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix} \]

PhaseOp

Bases: PrimitiveOp

A phase operator.

\[ PhaseOp( heta) = e^{i heta} I \]

Reset

Bases: PrimitiveOp

Reset operator for qubits or wires.

ResetToOne

Bases: PrimitiveOp

Reset qubits to the one state. Mainly needed to accommodate cirq's GeneralizedAmplitudeDampingChannel

ShiftOp

Bases: PrimitiveOp

A phase shift operator.

\[ Shift( heta) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i \theta} \end{bmatrix} \]

Sn

Bases: ConstantOp

\(S_{-}\) operator.

\[ Sn = \frac{1}{2} (S_x - i S_y) = \frac{1}{2} \begin{bmatrix} 0 & 0 \\ 1 & 0 \end{bmatrix} \]

Sp

Bases: ConstantOp

\(S_{+}\) operator.

\[ Sp = \frac{1}{2} (S_x + i S_y) = \frac{1}{2}\begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix} \]

U3

Bases: PrimitiveOp

The rotation operator U3(theta, phi, lam). Note that we use the convention from the QASM2 specification, namely

\[ U_3( heta, \phi, \lambda) = R_z(\phi) R_y( heta) R_z(\lambda) \]

traits

HasSites dataclass

HasSites()

Bases: StmtTrait

An operator with a sites attribute.

qubit

qubit dialect for squin language.

This dialect defines the operations that can be performed on qubits.

Depends on: - bloqade.squin.op: provides the OpType type and semantics for operators applied to qubits. - kirin.dialects.ilist: provides the ilist.IListType type for lists of qubits.

broadcast

broadcast(
    operator: Op, qubits: IList[Qubit, Any] | list[Qubit]
) -> None

Broadcast and apply an operator to a list of qubits. For example, an operator that expects 2 qubits can be applied to a list of 2n qubits, where n is an integer > 0.

For controlled operators, the list of qubits is interpreted as sets of (controls, targets). For example

apply(CX, [q0, q1, q2, q3])

is equivalent to

apply(CX, [q0, q1])
apply(CX, [q2, q3])

Parameters:

Name Type Description Default
operator Op

The operator to broadcast and apply.

required
qubits IList[Qubit, Any] | list[Qubit]

The list of qubits to broadcast and apply the operator to. The size of the list must be inferable and match the number of qubits expected by the operator.

required

Returns:

Type Description
None

None

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/qubit.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@wraps(Broadcast)
def broadcast(operator: Op, qubits: ilist.IList[Qubit, Any] | list[Qubit]) -> None:
    """Broadcast and apply an operator to a list of qubits. For example, an operator
    that expects 2 qubits can be applied to a list of 2n qubits, where n is an integer > 0.

    For controlled operators, the list of qubits is interpreted as sets of (controls, targets).
    For example

    ```
    apply(CX, [q0, q1, q2, q3])
    ```

    is equivalent to

    ```
    apply(CX, [q0, q1])
    apply(CX, [q2, q3])
    ```

    Args:
        operator: The operator to broadcast and apply.
        qubits: The list of qubits to broadcast and apply the operator to. The size of the list
            must be inferable and match the number of qubits expected by the operator.

    Returns:
        None
    """
    ...

measure

measure(input: Qubit) -> MeasurementResult
measure(
    input: IList[Qubit, Any] | list[Qubit],
) -> ilist.IList[MeasurementResult, Any]
measure(input: Any) -> Any

Measure a qubit or qubits in the list.

Parameters:

Name Type Description Default
input Any

A qubit or a list of qubits to measure.

required

Returns:

Type Description
Any

bool | list[bool]: The result of the measurement. If a single qubit is measured, a single boolean is returned. If a list of qubits is measured, a list of booleans is returned.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/qubit.py
142
143
144
145
146
147
148
149
150
151
152
153
154
@wraps(MeasureAny)
def measure(input: Any) -> Any:
    """Measure a qubit or qubits in the list.

    Args:
        input: A qubit or a list of qubits to measure.

    Returns:
        bool | list[bool]: The result of the measurement. If a single qubit is measured,
            a single boolean is returned. If a list of qubits is measured, a list of booleans
            is returned.
    """
    ...

new

new(n_qubits: int) -> ilist.IList[Qubit, Any]

Create a new list of qubits.

Parameters:

Name Type Description Default
n_qubits(int)

The number of qubits to create.

required

Returns:

Type Description
IList[Qubit, Any]

(ilist.IList[Qubit, n_qubits]) A list of qubits.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/qubit.py
83
84
85
86
87
88
89
90
91
92
93
@wraps(New)
def new(n_qubits: int) -> ilist.IList[Qubit, Any]:
    """Create a new list of qubits.

    Args:
        n_qubits(int): The number of qubits to create.

    Returns:
        (ilist.IList[Qubit, n_qubits]) A list of qubits.
    """
    ...

rewrite

U3_to_clifford

SquinU3ToClifford

Bases: RewriteRule

Rewrite squin U3 statements to clifford when possible.

decompose_U3_gates

decompose_U3_gates(
    node: U3,
) -> Tuple[List[ir.Statement], ...]

Rewrite U3 statements to clifford gates if possible.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/rewrite/U3_to_clifford.py
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
def decompose_U3_gates(self, node: op.stmts.U3) -> Tuple[List[ir.Statement], ...]:
    """
    Rewrite U3 statements to clifford gates if possible.
    """
    theta = self.get_constant(node.theta)
    phi = self.get_constant(node.phi)
    lam = self.get_constant(node.lam)

    if theta is None or phi is None or lam is None:
        return ()

    theta_half_pi: int | None = self.resolve_angle(theta)
    phi_half_pi: int | None = self.resolve_angle(phi)
    lam_half_pi: int | None = self.resolve_angle(lam)

    if theta_half_pi is None or phi_half_pi is None or lam_half_pi is None:
        return ()

    angles_key = (theta_half_pi, phi_half_pi, lam_half_pi)
    if angles_key not in U3_HALF_PI_ANGLE_TO_GATES:
        angles_key = equivalent_u3_para(*angles_key)
        if angles_key not in U3_HALF_PI_ANGLE_TO_GATES:
            return ()

    gates_stmts = U3_HALF_PI_ANGLE_TO_GATES.get(angles_key)

    # no consistent gates, then:
    assert (
        gates_stmts is not None
    ), "internal error, U3 gates not found for angles: {}".format(angles_key)

    return gates_stmts()

resolve_angle

resolve_angle(angle: float) -> int | None

Normalize the angle to be in the range [0, 2π).

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/rewrite/U3_to_clifford.py
76
77
78
79
80
81
82
83
84
85
86
87
88
def resolve_angle(self, angle: float) -> int | None:
    """
    Normalize the angle to be in the range [0, 2π).
    """
    # convert to 0.0~1.0, in unit of pi/2
    angle_half_pi = angle / math.pi * 2.0

    mod = angle_half_pi % 1.0
    if not (np.isclose(mod, 0.0) or np.isclose(mod, 1.0)):
        return None

    else:
        return round((angle / math.tau) % 1 * 4) % 4

rewrite_ApplyOrBroadcast_onU3

rewrite_ApplyOrBroadcast_onU3(
    node: Apply | Broadcast,
) -> RewriteResult

Rewrite Apply and Broadcast nodes to their clifford equivalent statements.

Source code in .venv/lib/python3.12/site-packages/bloqade/squin/rewrite/U3_to_clifford.py
 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
def rewrite_ApplyOrBroadcast_onU3(
    self, node: qubit.Apply | qubit.Broadcast
) -> RewriteResult:
    """
    Rewrite Apply and Broadcast nodes to their clifford equivalent statements.
    """
    if not isinstance(node.operator.owner, op.stmts.U3):
        return RewriteResult()

    gates = self.decompose_U3_gates(node.operator.owner)

    if len(gates) == 0:
        return RewriteResult()

    for stmt_list in gates:
        for gate_stmt in stmt_list[:-1]:
            gate_stmt.insert_before(node)

        oper = stmt_list[-1]
        oper.insert_before(node)
        new_node = node.__class__(operator=oper.result, qubits=node.qubits)
        new_node.insert_before(node)

    node.delete()

    # rewrite U3 to clifford gates
    return RewriteResult(has_done_something=True)

equivalent_u3_para

equivalent_u3_para(
    theta_half_pi: int, phi_half_pi: int, lam_half_pi: int
) -> tuple[int, int, int]
  1. Assume all three angles are in the range [0, 4].
  2. U3(theta, phi, lam) = -U3(2pi-theta, phi+pi, lam+pi).
Source code in .venv/lib/python3.12/site-packages/bloqade/squin/rewrite/U3_to_clifford.py
48
49
50
51
52
53
54
55
def equivalent_u3_para(
    theta_half_pi: int, phi_half_pi: int, lam_half_pi: int
) -> tuple[int, int, int]:
    """
    1. Assume all three angles are in the range [0, 4].
    2. U3(theta, phi, lam) = -U3(2pi-theta, phi+pi, lam+pi).
    """
    return ((4 - theta_half_pi) % 4, (phi_half_pi + 2) % 4, (lam_half_pi + 2) % 4)

desugar

ApplyDesugarRule

Bases: RewriteRule

Desugar apply operators in the kernel.

MeasureDesugarRule

Bases: RewriteRule

Desugar measure operations in the circuit.

wire

A NVIDIA QUAKE-like wire dialect.

This dialect is expected to be used in combination with the operator dialect as an intermediate representation for analysis and optimization of quantum circuits. Thus we do not define wrapping functions for the statements in this dialect.