Complex Mixing
This section explains the complex_mix methods for the CO-RE96 and Channels modules.
Prerequisites
A thorough understanding of advanced pipetting operations is required, as this guide builds upon the concepts introduced in the advanced pipetting guide. The prerequisites are the same as for that guide, and the basic setup will be the same (sections Power on the System and Arrange the Deck).
Parameter Sets
Complex mixing requires the use of parameter sets. There are three parameter sets of note:
CoRe96ComplexMixParameterSet- Contains a single field:
steps. This is a list ofCoRe96AspirateParameterSetandCoRe96DispenseParameterSetobjects in the order they are to be performed by the CO-RE96 head.
- Contains a single field:
ChannelComplexMixParameterSet- Contains a single field:
steps. This is a list ofChannelAspirateParameterSetandChannelDispenseParameterSetobjects in the order they are to be performed by the channel.
- Contains a single field:
ChannelsComplexMixParameterSet- Contains two fields:
parameters: This is a list ofChannelComplexMixParameterSetobjects.channels: This is a list of channel indices to be used for the mixing operation.
- Parameters and channels must be the same length, since each parameter set corresponds to a channel (in order).
- Contains two fields:
Note the difference between ChannelComplexMixParameterSet (singular Channel) and ChannelsComplexMixParameterSet (plural Channels).
The user has complete freedom to construct their aspiration and dispense parameter sets as they would for an ordinary aspiration or dispense. However, complex mixing parameter sets do enforce some additional constraints, not on the individual parameter sets, but on the list as a whole.
Constraints
The following constraints apply to complex mixing parameter sets:
stepsmust be non-empty and of even length.stepsmust alternate between the appropriate aspiration and dispense parameter sets.- In each pair, the
volumefield of the aspiration and dispense parameter sets must be equal. However, thevolumecan vary across pairs in the list.
- In each pair, the
Features
Here we describe the key features of complex mixing parameter sets.
Validation
As with other parameter sets, complex mixing parameter sets can be validated against various liquid handler states ("contexts") before being passed into a pipetting method, greatly reducing the risk of errors. Uniquely, since complex mixing parameter sets contain multiple independent operations, all of them are validated, allowing the user to evaluate the entire operation before execution:
from unitelabs.liquid_handling.hamilton.modules.core96 import CoRe96ComplexMixParameterSet, CoRe96AspirateParameterSet, CoRe96DispenseParameterSet, CoRe96Context
# Construct a complex mixing parameter set with 3 cycles of 10uL aspiration and dispensing
aspirate = CoRe96AspirateParameterSet(volume=10.0, liquid_offset=5.0, end_z_offset=0)
dispense = CoRe96DispenseParameterSet(volume=10.0, end_z_offset=0)
steps = [aspirate, dispense] * 3
complex_mix = CoRe96ComplexMixParameterSet(steps=steps)
# Construct context for validation
context = CoRe96Context(module=lhs.core96, target=some_labware)
# Validate the complex mix
complex_mix.validate(context)
The validation will either succeed (return True) or raise an exception with a descriptive error message, including which steps failed validation and why, for example:
ValueError: Parameter set CoRe96ComplexMixParameterSet failed validation due to the following warnings:
[STEP 2 (DISPENSE)]: Parameter set CoRe96DispenseParameterSet failed validation due to the following warnings:
- Liquid offset must be a positive number for LLD mode OFF, received -8.
[STEP 4 (DISPENSE)]: Parameter set CoRe96DispenseParameterSet failed validation due to the following warnings:
- Liquid offset must be a positive number for LLD mode OFF, received -8.
Factory Methods
Complex mix parameter sets each provide factory methods to help users create common complex mixing parameter sets more easily. Each of them creates a single aspirate, dispense pair of the corresponding type with the specified parameters. Those pairs can then be combined into the steps list of a complex mix parameter set.
from unitelabs.liquid_handling.hamilton.modules.pipettes import ChannelComplexMixParameterSet
# Create `ChannelAspirateParameterSet` and `ChannelDispenseParameterSet` objects
aspirate_1, dispense_1 = ChannelComplexMixParameterSet.surface_to_bottom(
volume=100,
aspirate_surface_offset=5,
dispense_bottom_offset=9,
)
aspirate_2, dispense_2 = ChannelComplexMixParameterSet.bottom_to_surface(
volume=100,
aspirate_bottom_offset=9,
dispense_surface_offset=5,
)
# Combine the steps into a `ChannelComplexMixParameterSet`
steps = [aspirate_1, dispense_1, aspirate_2, dispense_2]
complex_mix = ChannelComplexMixParameterSet(steps=steps)
Available factory methods are:
surface_to_bottom: Creates a surface mode aspiration and bottom mode dispense.bottom_to_surface: Creates a bottom mode aspiration and surface mode dispense.surface_to_surface: Creates a surface mode aspiration and surface mode dispense.bottom_to_bottom: Creates a bottom mode aspiration and bottom mode dispense.
With the CO-RE96
Here is a simple example of how to use the complex mixing method with the CO-RE96:
from unitelabs.liquid_handling.hamilton.modules.core96 import CoRe96ComplexMixParameterSet, CoRe96AspirateParameterSet, CoRe96DispenseParameterSet, CoRe96Context
from unitelabs.labware import Vector
# Construct a complex mixing parameter set with 3 cycles of 10uL aspiration and dispensing
aspirate = CoRe96AspirateParameterSet(volume=10.0, liquid_offset=5.0, end_z_offset=0)
dispense = CoRe96DispenseParameterSet(volume=10.0, offset=Vector(1, 1, 3))
steps = [aspirate, dispense] * 3
complex_mix = CoRe96ComplexMixParameterSet(steps=steps)
# Perform the complex mix
await lhs.core96.complex_mix(parameters=complex_mix, target=some_labware)
With the Channels
Users have two options when using the complex_mix method with the Channels module:
- Pass a single or list of
ChannelComplexMixParameterSetobjects toparameters, and corresponding channel indices to thechannelsparameter.- If a single value is received in
parameters, it will be broadcast to each channel inchannels. If a list is passed, it must match the length ofchannelsexactly, and each parameter set will be applied to the corresponding channel.
- If a single value is received in
- Pass a complete
ChannelsComplexMixParameterSettoparameters. This object contains thechannelsinformation, so it is not necessary to pass thechannelsparameter separately and will be ignored if this is done.
Here is an example of how to use the complex mixing method with the Channels:
from unitelabs.liquid_handling.hamilton.modules.pipettes import ChannelComplexMixParameterSet
# Create `ChannelAspirateParameterSet` and `ChannelDispenseParameterSet` objects
aspirate_1, dispense_1 = ChannelComplexMixParameterSet.surface_to_bottom(
volume=100,
aspirate_surface_offset=5,
dispense_bottom_offset=9,
)
aspirate_2, dispense_2 = ChannelComplexMixParameterSet.bottom_to_surface(
volume=100,
aspirate_bottom_offset=9,
dispense_surface_offset=5,
)
# Combine the steps into a `ChannelComplexMixParameterSet`
steps = [aspirate_1, dispense_1, aspirate_2, dispense_2]
complex_mix = ChannelComplexMixParameterSet(steps=steps)
At this point, one could simply use this parameter set to perform the same complex mix on each channel:
await lhs.pipettes.complex_mix(target=some_labware, parameters=complex_mix, channels=[0, 1, 2, 3])
Alternatively, one could use it as a starting point for creating different complex mixes for different channels:
per_channel_complex_mix = [complex_mix for _ in range(4)]
for i, complex_mix in enumerate(per_channel_complex_mix):
if i % 2 == 0:
# Set volume on all steps to 50 for even indices in per-channel list
for step in complex_mix.steps:
step.volume = 50
else:
# Set volume on all steps to 150 for odd indices in per-channel list
for step in complex_mix.steps:
step.volume = 150
await lhs.pipettes.complex_mix(target=some_labware, parameters=per_channel_complex_mix, channels=[4, 5, 6, 7])
Furthermore, one could use it as a basis for creating a ChannelsComplexMixParameterSet, and use that in the method:
from unitelabs.liquid_handling.hamilton.modules.pipettes import ChannelsComplexMixParameterSet
channels_complex_mix = ChannelsComplexMixParameterSet(
channels=[0, 1, 2, 3],
parameters=per_channel_complex_mix,
)
await lhs.pipettes.complex_mix(target=some_labware, parameters=channels_complex_mix)
Tips & Tricks
Since the complex_mix methods offer complete control over each individual step, virtually any behavior that would be possible from individual aspirate and dispense steps is possible to execute with a complex mix. However, there are a few common patterns to be aware of.
Maintaining Fixed Height
Over an increasing number of mixing steps, the z movement of the pipettor to and from the instrument's configured minimum traverse height takes an increasing amount of time. Therefore the most common use case is to maintain a constant z-height of the pipettor between consecutive steps. This is possible through the use of two parameters that are present on all Hamilton parameter sets:
min_traverse_height: the minimum z-height at which the pipettor travels to the operation's target location before performing liquid handling. This defaults to the configured minimum traverse height of the module, however it can be overridden on individual commands with the use of this parameter.end_z_offset: the z-height that the pipettor moves to when the operation is complete. As an offset, this is relative to the top of the target labware.
The common pattern to use these parameters to maintain a fixed height would look like this:
import decimal
plate_z_top = my_plate.absolute_location.z + my_plate.dimensions.z # Top of my_plate
plate_z_offset = decimal.Decimal("-5")
# Do not override min_traverse_height on first step to allow free z movement to target
aspirate_1 = CoRe96AspirateParameterSet(
end_z_offset = plate_z_offset # End operation 5mm below plate top
)
dispense_1 = CoRe96DispenseParameterSet(
min_traverse_height = plate_z_top + plate_z_offset, # Move to operation 5mm below plate top
end_z_offset = plate_z_offset
)
aspirate_2 = CoRe96AspirateParameterSet(
min_traverse_height = plate_z_top + plate_z_offset,
end_z_offset = plate_z_offset
)
dispense_2 = CoRe96DispenseParameterSet(
min_traverse_height = plate_z_top + plate_z_offset,
end_z_offset = 10 # End mix cycle above plate
)
mix = CoRe96ComplexMixParameterSet(steps=[aspirate_1, dispense_1, aspirate_2, dispense_2])
Error Handling
complex_mix methods wrap all runtime exceptions in ComplexMixErrors to provide more context about the error. It contains the original message, as well as the step and step_type fields that indicate which step failed:
from unitelabs.liquid_handling.modules.parameters import ComplexMixError
try:
lhs.core96.complex_mix(target=...,parameters=...)
except ComplexMixError as e:
print(f"Complex mixing failed: {e.message}")
print(f"Failed during step {e.step} of type {e.step_type}")
One can therefore recover errors more gracefully by interpreting the remaining operations in their parameter set's steps field and proceeding with the remaining operations.