Skip to content

State injection

Bridge move.LogicalInitialize to Gemini state-injection kernel arguments.

On Gemini hardware a logical qubit is prepared by state injection: a single physical qubit is rotated into the requested state and then encoded into the Steane [[7,1,3]] code by a fixed pulse schedule. The pulse kernel that does this (state_injection_init in qlue) is parameterised by, for each of the two interleaved column groups of the Gemini grid, a y_mask selecting the active rows plus per-row axis_angle / rotation_angle lists, and by one shared CZ top-hat window.

Everything the bridge needs to know is a lanes convention:

  • the angle units of move.LogicalInitialize (turns, 1.0 == 2*pi),
  • how a :class:~bloqade.lanes.bytecode.encoding.LocationAddress maps onto the (col_group, row) geometry of the Gemini arch spec,
  • the shape of the y_mask and per-row angle lists.

Keeping the adapter here, next to the dialect it consumes, means it moves in lock-step with those conventions instead of drifting in a downstream repo.

Angle convention

move.LogicalInitialize carries the U3(theta, phi, lam) Euler angles that prepare each logical qubit from |0>. Matching

U3(theta, phi, lam)|0> = cos(theta/2)|0> + e^{i phi} sin(theta/2)|1>

against a rotation R_n(beta) about an equatorial axis n at angle alpha from X,

R_n(beta)|0> = cos(beta/2)|0> - i e^{i alpha} sin(beta/2)|1>,

gives beta = theta and alpha = phi + pi/2. In turns that is rotation_angle = theta and axis_angle = phi + 0.25. lam only contributes a global phase on |0> and is dropped. This is the inverse of the axis_angle -> (phi, lam) mapping move2squin applies to move.LocalR / move.GlobalR, so both directions agree.

The CZ top-hat window is a hardware calibration quantity expressed in the pulse kernels' own coordinate frame (which is not the arch spec's corner-at-zero frame), so it is always supplied by the caller.

N_COL_GROUPS module-attribute

N_COL_GROUPS = 2

Number of interleaved column groups on the Gemini grid.

N_ROWS module-attribute

N_ROWS = 5

Number of logical-qubit rows per column group on the Gemini grid.

QUARTER_TURN module-attribute

QUARTER_TURN = 0.25

Offset (turns) from the phi Euler angle to the rotation-axis angle.

CZWindow

Bases: NamedTuple


              flowchart TD
              bloqade.lanes.arch.gemini.state_injection.CZWindow[CZWindow]

              

              click bloqade.lanes.arch.gemini.state_injection.CZWindow href "" "bloqade.lanes.arch.gemini.state_injection.CZWindow"
            

CZ top-hat window in the pulse kernels' coordinate frame (micrometres).

ymin/ymax bound the rows that should see the CZ pulse; ymin_keepout/ymax_keepout extend that by the keep-out margin. The window is global in x and shared by both column groups, so it must span every row that is active in either y_mask.

from_bounds classmethod

from_bounds(
    ymin: float, ymax: float, keep_out_buffer: float = 10.0
) -> CZWindow

Build a window from row bounds plus a symmetric keep-out margin.

Source code in .venv/lib/python3.14/site-packages/bloqade/lanes/arch/gemini/state_injection.py
83
84
85
86
87
88
@classmethod
def from_bounds(
    cls, ymin: float, ymax: float, keep_out_buffer: float = 10.0
) -> CZWindow:
    """Build a window from row bounds plus a symmetric keep-out margin."""
    return cls(ymin, ymax, ymin - keep_out_buffer, ymax + keep_out_buffer)

logical_initialize_to_state_injection_args

logical_initialize_to_state_injection_args(
    node: LogicalInitialize,
    arch_spec: ArchSpec,
    cz_window: CZWindow,
    *,
    n_rows: int = N_ROWS,
    atol: float = 1e-06
) -> tuple[ir.SSAValue, ...]

Build the state_injection_init argument list for a LogicalInitialize.

Inserts the supporting statements before node (which is left in place for the caller to replace) and returns, in order::

(y_mask_col0, axis_angles_col0, rotation_angles_col0,
 y_mask_col1, axis_angles_col1, rotation_angles_col1,
 ymin, ymax, ymin_keepout, ymax_keepout)

Each y_mask is an IList[bool, n_rows] marking the rows of that column group that hold a logical qubit; the aligned angle lists carry that qubit's angles, or 0.0 for rows the mask switches off (inert, as the mask removes them from the pulse entirely). Angles are in turns; see the module docstring for the U3 -> (axis, rotation) derivation.

Parameters:

Name Type Description Default
node LogicalInitialize

the move.LogicalInitialize to bridge.

required
arch_spec ArchSpec

the Gemini logical arch spec node was placed on.

required
cz_window CZWindow

CZ top-hat window in the pulse kernels' frame.

required
n_rows int

rows per column group expected by the pulse kernel.

N_ROWS
atol float

tolerance for matching site positions to the zone grid.

1e-06

Raises:

Type Description
ValueError

if the node is empty, spans several zones, has mismatched argument lengths, holds a location :func:resolve_col_group_and_row rejects, puts two qubits in one (col_group, row) slot, has a row outside range(n_rows), or the window is malformed. Validation completes before any IR is inserted.

Source code in .venv/lib/python3.14/site-packages/bloqade/lanes/arch/gemini/state_injection.py
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def logical_initialize_to_state_injection_args(
    node: move.LogicalInitialize,
    arch_spec: ArchSpec,
    cz_window: CZWindow,
    *,
    n_rows: int = N_ROWS,
    atol: float = 1e-6,
) -> tuple[ir.SSAValue, ...]:
    """Build the ``state_injection_init`` argument list for a ``LogicalInitialize``.

    Inserts the supporting statements before ``node`` (which is left in
    place for the caller to replace) and returns, in order::

        (y_mask_col0, axis_angles_col0, rotation_angles_col0,
         y_mask_col1, axis_angles_col1, rotation_angles_col1,
         ymin, ymax, ymin_keepout, ymax_keepout)

    Each ``y_mask`` is an ``IList[bool, n_rows]`` marking the rows of that
    column group that hold a logical qubit; the aligned angle lists carry
    that qubit's angles, or ``0.0`` for rows the mask switches off (inert, as
    the mask removes them from the pulse entirely). Angles are in turns; see
    the module docstring for the ``U3 -> (axis, rotation)`` derivation.

    Args:
        node: the ``move.LogicalInitialize`` to bridge.
        arch_spec: the Gemini logical arch spec ``node`` was placed on.
        cz_window: CZ top-hat window in the pulse kernels' frame.
        n_rows: rows per column group expected by the pulse kernel.
        atol: tolerance for matching site positions to the zone grid.

    Raises:
        ValueError: if the node is empty, spans several zones, has
            mismatched argument lengths, holds a location
            :func:`resolve_col_group_and_row` rejects, puts two qubits in one
            ``(col_group, row)`` slot, has a row outside ``range(n_rows)``, or
            the window is malformed. Validation completes before any IR is
            inserted.
    """
    cz_window.validate()

    addresses = node.location_addresses
    if not addresses:
        raise ValueError("LogicalInitialize has no locations; nothing to inject")
    zone_ids = sorted({location.zone_id for location in addresses})
    if len(zone_ids) > 1:
        raise ValueError(
            f"state injection addresses a single zone, but the node spans zones {zone_ids}"
        )

    # Resolve and validate every location before touching the IR, so a
    # rejected node leaves the caller's block unchanged.
    resolved: dict[tuple[int, int], tuple[LocationAddress, ir.SSAValue, ir.SSAValue]]
    resolved = {}
    for location, theta, phi, _lam in zip(
        addresses, node.thetas, node.phis, node.lams, strict=True
    ):
        col_group, row = resolve_col_group_and_row(arch_spec, location, atol=atol)
        if row >= n_rows:
            raise ValueError(
                f"{location!r} resolves to row {row}, but the state-injection "
                f"kernel only addresses {n_rows} rows"
            )
        slot = (col_group, row)
        if slot in resolved:
            raise ValueError(
                f"{location!r} and {resolved[slot][0]!r} both resolve to column "
                f"group {col_group}, row {row}; a LogicalInitialize may hold at "
                "most one logical qubit per slot"
            )
        resolved[slot] = (location, theta, phi)

    (quarter_stmt := py.Constant(QUARTER_TURN)).insert_before(node)
    converted: dict[
        tuple[ir.SSAValue, ir.SSAValue], tuple[ir.SSAValue, ir.SSAValue]
    ] = {}
    angles: dict[tuple[int, int], tuple[ir.SSAValue, ir.SSAValue]] = {}
    for slot, (_, theta, phi) in resolved.items():
        if (theta, phi) not in converted:
            converted[(theta, phi)] = theta_phi_to_axis_rotation_angle(
                theta, phi, node, quarter_turn=quarter_stmt.result
            )
        angles[slot] = converted[(theta, phi)]

    zero: ir.SSAValue | None = None

    def filler() -> ir.SSAValue:
        nonlocal zero
        if zero is None:
            (zero_stmt := py.Constant(0.0)).insert_before(node)
            zero = zero_stmt.result
        return zero

    args: list[ir.SSAValue] = []
    for col_group in range(N_COL_GROUPS):
        mask = [(col_group, row) in angles for row in range(n_rows)]
        (mask_stmt := py.Constant(ilist.IList(mask, elem=types.Bool))).insert_before(
            node
        )
        axis_values = tuple(
            angles[(col_group, row)][0] if mask[row] else filler()
            for row in range(n_rows)
        )
        rotation_values = tuple(
            angles[(col_group, row)][1] if mask[row] else filler()
            for row in range(n_rows)
        )
        (axis_stmt := ilist.New(axis_values)).insert_before(node)
        (rotation_stmt := ilist.New(rotation_values)).insert_before(node)
        args.extend((mask_stmt.result, axis_stmt.result, rotation_stmt.result))

    for bound in cz_window:
        (bound_stmt := py.Constant(float(bound))).insert_before(node)
        args.append(bound_stmt.result)

    return tuple(args)

make_state_injection_args_getter

make_state_injection_args_getter(
    arch_spec: ArchSpec,
    cz_window: CZWindow,
    *,
    n_rows: int = N_ROWS,
    atol: float = 1e-06
) -> Callable[
    [move.LogicalInitialize], tuple[ir.SSAValue, ...]
]

Bind everything but the node, for RewriteLogicalInitialize-style hooks.

Downstream rewrites that swap a LogicalInitialize for a kernel invocation take a node -> args callable; this returns one that calls :func:logical_initialize_to_state_injection_args with the given spec and window.

Source code in .venv/lib/python3.14/site-packages/bloqade/lanes/arch/gemini/state_injection.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
def make_state_injection_args_getter(
    arch_spec: ArchSpec,
    cz_window: CZWindow,
    *,
    n_rows: int = N_ROWS,
    atol: float = 1e-6,
) -> Callable[[move.LogicalInitialize], tuple[ir.SSAValue, ...]]:
    """Bind everything but the node, for ``RewriteLogicalInitialize``-style hooks.

    Downstream rewrites that swap a ``LogicalInitialize`` for a kernel
    invocation take a ``node -> args`` callable; this returns one that calls
    :func:`logical_initialize_to_state_injection_args` with the given spec
    and window.
    """

    def get_args(node: move.LogicalInitialize) -> tuple[ir.SSAValue, ...]:
        return logical_initialize_to_state_injection_args(
            node, arch_spec, cz_window, n_rows=n_rows, atol=atol
        )

    return get_args

resolve_col_group_and_row

resolve_col_group_and_row(
    arch_spec: ArchSpec,
    location: LocationAddress,
    *,
    atol: float = 1e-06
) -> tuple[int, int]

Resolve a Gemini logical-spec location to its (col_group, row).

The Gemini logical zone grid has four columns, left0, right0, left1, right1: the left and right sites of a column-group-0 word pair, then those of a column-group-1 pair. col_group is the pair the location's column belongs to and row is the index of its y position.

The state-injection pulse only addresses the left site of each pair, so a right-site location is rejected rather than silently mapped onto its left partner. Physical-spec locations are rejected too: one logical qubit spans many physical sites, so a single physical site has no slot.

Raises:

Type Description
ValueError

if arch_spec does not have the Gemini logical grid shape, location is not a valid site, its position is off the zone grid, or it is a right site.

Source code in .venv/lib/python3.14/site-packages/bloqade/lanes/arch/gemini/state_injection.py
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
def resolve_col_group_and_row(
    arch_spec: ArchSpec, location: LocationAddress, *, atol: float = 1e-6
) -> tuple[int, int]:
    """Resolve a Gemini *logical*-spec location to its ``(col_group, row)``.

    The Gemini logical zone grid has four columns, ``left0, right0, left1,
    right1``: the left and right sites of a column-group-0 word pair, then
    those of a column-group-1 pair. ``col_group`` is the pair the location's
    column belongs to and ``row`` is the index of its ``y`` position.

    The state-injection pulse only addresses the *left* site of each pair,
    so a right-site location is rejected rather than silently mapped onto
    its left partner. Physical-spec locations are rejected too: one logical
    qubit spans many physical sites, so a single physical site has no slot.

    Raises:
        ValueError: if ``arch_spec`` does not have the Gemini logical grid
            shape, ``location`` is not a valid site, its position is off the
            zone grid, or it is a right site.
    """
    zones = arch_spec.zones
    if not 0 <= location.zone_id < len(zones):
        raise ValueError(f"{location!r} names a zone that {arch_spec!r} lacks")
    grid = zones[location.zone_id].grid
    xs, ys = tuple(grid.x_positions), tuple(grid.y_positions)
    if len(xs) != _N_LOGICAL_COLUMNS:
        raise ValueError(
            f"state injection expects the Gemini logical grid of "
            f"{_N_LOGICAL_COLUMNS} columns, got {len(xs)}; pass the logical arch spec"
        )
    x, y = arch_spec.get_position(location)
    col = _match_index(xs, x, atol, "x")
    row = _match_index(ys, y, atol, "y")
    col_group, side = divmod(col, _N_LOGICAL_COLUMNS // N_COL_GROUPS)
    if side:
        raise ValueError(
            f"{location!r} is the right site of its word pair; the state-injection "
            "pulse only addresses left sites"
        )
    return col_group, row

theta_phi_to_axis_rotation_angle

theta_phi_to_axis_rotation_angle(
    theta: SSAValue,
    phi: SSAValue,
    insertion_point: Statement,
    *,
    quarter_turn: SSAValue | None = None
) -> tuple[ir.SSAValue, ir.SSAValue]

Insert IR mapping U3 (theta, phi) to (axis_angle, rotation_angle).

All values are in turns. rotation_angle is theta itself (no new statement); axis_angle is phi + 0.25. Pass quarter_turn to reuse an existing 0.25 constant instead of inserting a new one.

Source code in .venv/lib/python3.14/site-packages/bloqade/lanes/arch/gemini/state_injection.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def theta_phi_to_axis_rotation_angle(
    theta: ir.SSAValue,
    phi: ir.SSAValue,
    insertion_point: ir.Statement,
    *,
    quarter_turn: ir.SSAValue | None = None,
) -> tuple[ir.SSAValue, ir.SSAValue]:
    """Insert IR mapping U3 ``(theta, phi)`` to ``(axis_angle, rotation_angle)``.

    All values are in turns. ``rotation_angle`` is ``theta`` itself (no new
    statement); ``axis_angle`` is ``phi + 0.25``. Pass ``quarter_turn`` to
    reuse an existing ``0.25`` constant instead of inserting a new one.
    """
    if quarter_turn is None:
        (quarter_stmt := py.Constant(QUARTER_TURN)).insert_before(insertion_point)
        quarter_turn = quarter_stmt.result
    (axis_stmt := py.Add(phi, quarter_turn)).insert_before(insertion_point)
    return axis_stmt.result, theta