Batching

Overview

This section details the use of the standard batching capabilities available on LINQ.

Note

A Batch is a single cycle of a workflow including all labware and tasks required to execute it.

Running multiple batches involves scheduling tasks and labware for multiple instances/cycles of a workflow. LINQ’s batching feature allows users to set up workflows that can be batched (within system capacity) without requiring any custom batching logic.

Batching Basics

In this section we introduce the basic concepts used to create or modify a workflow so that it can be batched.

Defining multiple labware instances

When writing a workflow that can be batched, labware must be defined as a range, rather than individual slots. This tells the system a range of possible locations where it can retrieve a labware of a given type. The scheduler will dynamically assign labware from this range to tasks across multiple batches.

In your Labware definition, you can define the starting location as a range of slots (instead of a single specific slot) in order to achieve this behavior. A group of labware that can be batched would look something like this:

microplates = Labware(
    id="microplates",
    labware_type="96_well_microplate",
    starting_location=LabwareLocation(
        instrument=hotel,
        slot=SlotRange(start_slot=1, end_slot=50)
    )
)

The definition of starting location slots as slot=SlotRange(start_slot=1, end_slot=50) tells the scheduler that the sequence of contiguous slots in the instrument from 1 to 50 can all store multiples of this labware that can be batched.

Note

The slot range also indicates the maximum number of batches the workflow can support. In this example, if every batch uses a single 96_well_microplate, the workflow can only be batched 50 times.

Defining slot filling rules

Similar to the location ranges explained above, tasks that expect multiple labware can be given slot ranges to specify where labware can be placed when batched.

For example, when storing labware in a hotel at the end of a workflow, if that labware can be batched then a range of slots at least equal to the range of labware must be available for storage.

Note

Ranges are only needed if the task takes multiple labware and have more than a single slot.

How to load slot ranges when batching is defined using a SlotFillingRule.

store_microplate_on_hotel = ActionTask(
    id="store_microplate_on_hotel",
    description="End position for the labware (in a workflow where labware can be batched)",
    ...
    labware_sources=[
        previous_task.forward(
            labware=microplates,
            destination_slot=SlotFillingRule(
                range=SlotRange(start_slot=51, end_slot=100),
                min_count=1,
                max_count=1,
                distribution="unique"
            ),
        ),
    ]
)

In this example, the hotel slots 51 to 100 are uniquely distributed across each microplate in each batch. The min_count and max_count argument determine the number of slots that can be filled per batch. In this example only one slot can be used for each batch cycle. Setting distribution="unique" ensures that each labware has a unique slot reserved that can not be re-used in upcoming batches.

Attention

Don’t assign fixed output slots in labware_outputs to labware using a slot filling rule! The output slot will automatically be assigned to the same as the source input slot when SlotFillingRule is used.

In the Task sharing section we explore more advanced usage of the slot filling rule.

Task sharing

The min_count and max_count argument can be used to define slot filling rules that enable labware from different batches to share a task. In the example below, a liquid handler must be loaded with exactly three microplates before it can run. This means each set of 3 consecutive batches will share the same liquid handler task.

Attention

This also means that the workflow would only be valid if it ran with a batch number that is a multiple of three.

Setting the distribution to "non_unique" enables the same three slots to be reused by the next 3 plates from the upcoming batches.

liquid_handle = ActionTask(
    id="run_liquid_handler",
    description="Run liquid handler on plates from multiple batches",
    ...
    labware_sources=[
        previous_task.forward(
            labware=microplate,
            destination_slot=SlotFillingRule(
                range=SlotRange(start_slot=2, end_slot=8),
                min_count=3,
                max_count=3, # exactly 3 microplates must be loaded
                distribution="non_unique",
            ),
        ),
    ],
)

The min_count and max_count arguments can also enable more lax rules such as this:

SlotFillingRule(
    range=SlotRange(start_slot=2, end_slot=8),
    min_count=1,
    max_count=3, # up to 3 microplates can be loaded
    distribution="non_unique",
),

In this case, the instrument does not require exactly three plates to be loaded, but can also be loaded with one or two plates. This allows the workflow to support batch numbers that are not multiples of three, as the last instance of the task could be loaded with only one or two plates.

class linq.task.SlotFillingRule(range: linq.labware.SlotRange | list[linq.labware.SlotRange], min_count: int | None = None, max_count: int | None = None, distribution: Literal['unique', 'non_unique'] = 'unique')
range: SlotRange | list[SlotRange]

The range of slots that the labware can be placed in.

min_count: int | None

The minimum number of labware items that must be placed in the slots.

max_count: int | None

The maximum number of labware items that can be placed in the slots.

distribution: Literal['unique', 'non_unique']

How the labware will be distributed across the slots. unique ensure each labware is placed in a unique slot. non_unique allows multiple labware to be placed in the same slot over many instances of the task

Setting a batch number

Finally, if your labware is defined with ranges in starting locations, and your task sequence forms a feasible and valid journey with slots correctly defined, you can then set the batch number as an argument of your workflow definition.

workflow = Workflow(
    workcell=workcell,
    name="Batchable Workflow",
    author="John Doe",
    description="A workflow that can be batched",
    ....
    batches=8, # set the batch number here. Default is 1 (i.e. no batching)
)

Although LINQ SDK does validate your workflow definition, as a user you should also ensure that your workflow design and definition enables batching correctly. Ensure that any batched labware has the required starting location range definitions and that these are supported by the instruments used. Additionally, ensure any slot filling rules have the required slot ranges and that those instruments also support that capacity. Finally, make sure the batch number you set your workflow to is feasible given your instrument capacity and defined slot ranges.

Reusable labware

You may not always require new labware iteration for every batch, this is typically the case with consumables, such as tip boxes. LINQ SDK handles reusable labware using the concept of unit consumption.

Labware definitions can include a total_units argument that defines a measure of how much capacity the labware has. Tasks that use the labware can then contain a units_consumed argument that defines how many units that task consumes from the labware. The labware will only be replaced in the next batch when the total units have been exhausted.

Let’s look at an example; consider a workflow that uses the following set of tip boxes, with total_units=3:

tips = Labware(
    id="tip_boxes",
    labware_type="tip_box_200",
    starting_location=LabwareLocation(
        instrument=hotel,
        slot=SlotRange(start_slot=51, end_slot=100)
    ),
    total_units=3,
)

Now consider the tip boxes are used in a liquid handling task, with the tip boxes as a labware source. The tip box source has a unit consumption of one for this task: (assume this is the only task in the workflow using the tip box)

liquid_handle = ActionTask(
    id="run_liquid_handler",
    description="Run liquid handler",
    ...
    labware_sources=[
        LabwareSource(labware=tip_boxes, destination_slot=1, units_consumed=1),
    ],
)

In this case the tip box would only be replaced after every third batch once all 3 units are consumed. While it still has units, the tip box will be left on the liquid handler to be reused in the following batch.