Skip to content

Task builder

SubtaskValidationError

Bases: Exception


              flowchart TD
              bloqade.core.device.task_builder.SubtaskValidationError[SubtaskValidationError]

              

              click bloqade.core.device.task_builder.SubtaskValidationError href "" "bloqade.core.device.task_builder.SubtaskValidationError"
            

Validation error if a subtask's arguments, shot count, or metadata are invalid.

TaskBuilder dataclass

TaskBuilder(
    _programs: dict[Method, int] = dict(),
    _subtasks: list[_Subtask] = list(),
    _max_program_idx: int = 0,
    _kernel_name_counter: dict[str, int] = dict(),
)

Canonical API for constructing workloads submitted through a device.

Add kernel invocations with add_subtask(), then pass the builder to Device.run_async(). The builder owns the ordered programs, arguments, metadata, and shot counts; the device owns backend-specific validation and serialization.

Builders are mutable and append-only. Adding the same ir.Method object more than once creates multiple subtasks that reuse one program entry. Finalization does not mutate the builder, so it may be inspected, copied, extended, dry-run, or submitted again.

Device.task(), Device.batch_task(), and Device.parameter_scan() are deprecated task-construction APIs. New code should use TaskBuilder for single kernels, batches, and parameter scans.

add_subtask

add_subtask(
    kernel: Method[CallArgs, T],
    num_shots: int,
    metadata: dict | None = None,
    *args: args,
    **kernel_args: kwargs
) -> int

Append one kernel invocation and return its subtask index.

Positional and keyword kernel arguments are bound against the kernel's Python signature and stored by parameter name. metadata is JSON-encoded as per-subtask user metadata. Reusing the same kernel object reuses its program index while retaining independent arguments, metadata, and shot counts for this subtask.

Parameters:

Name Type Description Default
kernel Method

Kernel to invoke.

required
num_shots int

Number of shots for this invocation. Must be at least one.

required
metadata dict | None

Optional JSON-serializable user metadata associated with this subtask. Defaults to None.

None
*args args

Positional arguments for kernel.

()
**kernel_args kwargs

Keyword arguments for kernel.

{}

Returns:

Name Type Description
int int

Zero-based index of the appended subtask.

Raises:

Type Description
TypeError

If the kernel has no inspectable Python function or the supplied arguments do not match its signature.

ValueError

If metadata is not a dictionary or None.

SubtaskValidationError

If the shot count or bound kernel arguments are invalid.

Source code in src/bloqade/core/device/task_builder.py
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
245
246
247
248
249
250
def add_subtask(
    self,
    kernel: ir.Method[CallArgs, T],
    num_shots: int,
    metadata: dict | None = None,
    *args: CallArgs.args,
    **kernel_args: CallArgs.kwargs,
) -> int:
    """Append one kernel invocation and return its subtask index.

    Positional and keyword kernel arguments are bound against the kernel's
    Python signature and stored by parameter name. ``metadata`` is
    JSON-encoded as per-subtask user metadata. Reusing the same ``kernel``
    object reuses its program index while retaining independent arguments,
    metadata, and shot counts for this subtask.

    Args:
        kernel (ir.Method): Kernel to invoke.
        num_shots (int): Number of shots for this invocation. Must be at
            least one.
        metadata (dict | None): Optional JSON-serializable user metadata
            associated with this subtask. Defaults to None.
        *args: Positional arguments for ``kernel``.
        **kernel_args: Keyword arguments for ``kernel``.

    Returns:
        int: Zero-based index of the appended subtask.

    Raises:
        TypeError: If the kernel has no inspectable Python function or the
            supplied arguments do not match its signature.
        ValueError: If ``metadata`` is not a dictionary or ``None``.
        SubtaskValidationError: If the shot count or bound kernel arguments
            are invalid.
    """
    # TODO: define equality? we COULD implement hashing on kirin kernels. as a first pass maybe it's OK to
    # just do exact object check
    if metadata is not None and not isinstance(metadata, dict):
        raise ValueError("expecting `metadata` to be a dictionary or None. ")

    base_kernel_name = kernel.sym_name if kernel.sym_name is not None else "kernel"
    kernel_name_index = self._kernel_name_counter.get(base_kernel_name, 0)
    kernel_name = (
        base_kernel_name
        if kernel_name_index == 0
        else f"{base_kernel_name}_{kernel_name_index}"
    )

    if kernel in self._programs:
        program_idx = self._programs[kernel]
        is_new_program = False
    else:
        program_idx = self._max_program_idx
        is_new_program = True

    if metadata is not None:
        subtask_metadata = TaskMetadata(user_metadata=json.dumps(metadata))
    else:
        subtask_metadata = None

    if kernel.py_func is None:
        raise TypeError(
            f"Cannot determine the argument names for kernel {kernel.sym_name!r}"
        )

    signature = inspect.signature(kernel.py_func)
    bound = signature.bind(*args, **kernel_args)

    arguments = dict(bound.arguments)

    qlam_subtask = Subtask(
        program_index=program_idx,
        num_shots=num_shots,
        arguments=arguments,
        subtask_metadata=subtask_metadata,
    )

    new_subtask = _Subtask(kernel_name=kernel_name, qlam_subtask=qlam_subtask)
    try:
        self._validate_subtask(subtask=new_subtask)
    except ValueError as e:
        raise SubtaskValidationError(f"Error during subtask validation: {e}")

    # Commit builder state only after every operation above has succeeded.
    if is_new_program:
        self._programs[kernel] = program_idx
        self._max_program_idx += 1
    self._kernel_name_counter[base_kernel_name] = kernel_name_index + 1
    self._subtasks.append(new_subtask)
    return len(self._subtasks) - 1

copy

copy() -> TaskBuilder

Return a shallow, independently editable copy of this builder.

The mutable program, subtask, and name-index containers are copied, so later calls to add_subtask() affect only the copy. Existing kernel objects and subtask records are shared rather than deep-copied.

Returns:

Name Type Description
TaskBuilder TaskBuilder

Builder containing the same programs and subtasks.

Source code in src/bloqade/core/device/task_builder.py
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
def copy(self) -> "TaskBuilder":
    """Return a shallow, independently editable copy of this builder.

    The mutable program, subtask, and name-index containers are copied, so
    later calls to ``add_subtask()`` affect only the copy. Existing kernel
    objects and subtask records are shared rather than deep-copied.

    Returns:
        TaskBuilder: Builder containing the same programs and subtasks.
    """
    return TaskBuilder(
        _programs=dict(self._programs),
        _subtasks=list(self._subtasks),
        _max_program_idx=self._max_program_idx,
        _kernel_name_counter=dict(self._kernel_name_counter),
    )

print_detailed

print_detailed() -> str

Return a detailed summary containing program IR and subtask arguments.

Source code in src/bloqade/core/device/task_builder.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def print_detailed(self) -> str:
    """Return a detailed summary containing program IR and subtask arguments."""
    ret_str = ""
    ret_str += "Task:\n"
    ret_str += "\tPrograms:\n"
    for program, program_idx in sorted(self._programs.items(), key=lambda x: x[1]):
        ret_str += f"\t\t{program_idx}. {program.print_str()}"

    ret_str += "\tSubtasks:\n"
    for subtask_idx, subtask in enumerate(self._subtasks):
        subtask_metadata = subtask.qlam_subtask.subtask_metadata
        if subtask_metadata is not None:
            user_metadata = subtask_metadata.user_metadata
            if user_metadata is not None:
                user_metadata = json.loads(user_metadata)
        else:
            user_metadata = None
        ret_str += f"\t\t{subtask_idx}. Subtask: {subtask.kernel_name}, Program {subtask.qlam_subtask.program_index}, Args {subtask.qlam_subtask.arguments}, Metadata {user_metadata} -> {subtask.qlam_subtask.num_shots} shots\n"

    return ret_str

summary

summary() -> str

Return a human-readable summary printed on dry-run.

Returns:

Name Type Description
str str

Summary describing what would be submitted.

Source code in src/bloqade/core/device/task_builder.py
 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
def summary(self) -> str:
    """Return a human-readable summary printed on dry-run.

    Returns:
        str: Summary describing what would be submitted.
    """
    ret_str = ""
    ret_str += "Task:\n"
    ret_str += "\tPrograms:\n"
    for program, program_idx in sorted(self._programs.items(), key=lambda x: x[1]):
        ret_str += f"\t\t{program_idx}. {program.sym_name}\n"

    ret_str += "\tSubtasks:\n"
    for idx, subtask in enumerate(self._subtasks):
        subtask_metadata = subtask.qlam_subtask.subtask_metadata
        if subtask_metadata is not None:
            user_metadata = subtask_metadata.user_metadata
            if user_metadata is not None:
                user_metadata = json.loads(user_metadata)
        else:
            user_metadata = None
        subtask_str = f"\t\t{idx}. Subtask: {subtask.kernel_name}, Program {subtask.qlam_subtask.program_index}, Args {subtask.qlam_subtask.arguments}, Metadata {user_metadata} -> {subtask.qlam_subtask.num_shots} shots\n"
        ret_str += subtask_str

    return ret_str

TaskFinalizeError

TaskFinalizeError(
    message: str,
    *,
    results: dict[str, ValidationResult] | None = None
)

Bases: Exception


              flowchart TD
              bloqade.core.device.task_builder.TaskFinalizeError[TaskFinalizeError]

              

              click bloqade.core.device.task_builder.TaskFinalizeError href "" "bloqade.core.device.task_builder.TaskFinalizeError"
            

Kernels failed dialect or validation checks; nothing was submitted.

Source code in src/bloqade/core/device/task_builder.py
47
48
49
50
51
def __init__(
    self, message: str, *, results: dict[str, ValidationResult] | None = None
):
    super().__init__(message)
    self.results = results or {}