Liquid Classes
Liquid classes define the motion parameters and volume correction curves used during aspirate and dispense operations. The SDK provides predefined liquid classes for common configurations and supports creating custom liquid classes for specialized protocols.
Prerequisites
- Basic understanding of the Bravo pipetting operations (See Basic Pipetting)
Predefined Liquid Classes
The SDK includes predefined liquid classes for different head types, tip sizes, and volume ranges. Use the BravoLiquidClasses factory to access them.
from unitelabs.labware.agilent import BravoLiquidClasses
liquid_classes = BravoLiquidClasses
# Access a predefined class by name
liquid_class = liquid_classes.OQ_96LT_water_highVol
# Use with aspirate
await bravo.pipette_head.aspirate(
plate=source_reservoir,
volume=100,
liquid_class=liquid_class,
)
Available Liquid Classes
96LT Head
| Liquid Class | Tip | Volume Range | Description |
|---|---|---|---|
OQ_96LT_water_lowVol | AgilentTip_200 | 0–50 µL | Water, low volume |
OQ_96LT_water_highVol | AgilentTip_250 | 51–250 µL | Water, high volume |
Finding Liquid Classes
Use the find() method to discover liquid classes matching specific criteria.
from unitelabs.labware.agilent.tips import AgilentTip_250
results = liquid_classes.find(tip=AgilentTip_250, volume=100)
liquid_class is provided to aspirate or dispense, the SDK automatically selects a matching liquid class based on the mounted tip type and the requested volume.Volume Correction
Liquid classes define polynomial coefficients that correct for systematic pipetting errors. The corrected volume determines the actual plunger (W-axis) position during aspirate and dispense operations.
The correction formula is a polynomial:
corrected = c₀ + c₁ × volume + c₂ × volume² + ...
For example, a liquid class with coefficients [0.0, 1.0074] applies a linear correction: the plunger moves slightly further than the nominal volume to compensate for dead volume or compression effects.
Liquid Class Parameters
Every liquid class defines the following parameters that control motion during pipetting:
| Parameter | Default | Description |
|---|---|---|
liquid | Mixture() | Liquid type (e.g., water, ethanol) |
tip | AgilentTip_200 | Compatible tip type |
min_volume / max_volume | 0 / 250 µL | Valid volume range |
coefficients | 0.0, 1.0 | Polynomial volume correction coefficients |
aspirate_velocity | 5.0 mm/s | Plunger velocity during aspirate |
aspirate_acceleration | 10.0 mm/s² | Plunger acceleration during aspirate |
aspirate_velocity_into_wells | 50.0 mm/s | Z descent velocity into wells |
aspirate_velocity_out_of_wells | 50.0 mm/s | Z retract velocity out of wells |
aspirate_acceleration_into_wells | 100.0 mm/s² | Z descent acceleration |
aspirate_acceleration_out_of_wells | 100.0 mm/s² | Z retract acceleration |
aspirate_post_delay_ms | 250 ms | Delay after aspirate completes |
dispense_velocity | 5.0 mm/s | Plunger velocity during dispense |
dispense_acceleration | 10.0 mm/s² | Plunger acceleration during dispense |
dispense_velocity_into_wells | 50.0 mm/s | Z descent velocity into wells |
dispense_velocity_out_of_wells | 50.0 mm/s | Z retract velocity out of wells |
dispense_acceleration_into_wells | 100.0 mm/s² | Z descent acceleration |
dispense_acceleration_out_of_wells | 100.0 mm/s² | Z retract acceleration |
dispense_post_delay_ms | 250 ms | Delay after dispense completes |
Custom Liquid Class with Coefficients
Create a custom liquid class by subclassing BravoLiquidClass and providing direct polynomial coefficients. This approach is best when you already know the correction formula.
import dataclasses
import decimal
from unitelabs.labware.agilent.liquids.bravo_liquid_class import BravoLiquidClass
from unitelabs.labware.agilent.tips import AgilentTip_250, AgilentTip
from unitelabs.labware.liquids import Mixture, PredefinedLiquids
from unitelabs.labware.math import Decimal
def _ethanol_mixture() -> Mixture:
return Mixture({PredefinedLiquids.ETHANOL: 1})
@dataclasses.dataclass
class EthanolLiquidClass(BravoLiquidClass):
liquid: Mixture = dataclasses.field(default_factory=lambda: Mixture({Liquid.ETHANOL: 1}))
tip: type[AgilentTip] = AgilentTip_250
min_volume: Decimal = dataclasses.field(default=Decimal(default="0"))
max_volume: Decimal = dataclasses.field(default=Decimal(default="250"))
coefficients: list[decimal.Decimal] = dataclasses.field(
default_factory=lambda: [decimal.Decimal("0.05"), decimal.Decimal("1.02")]
)
aspirate_velocity: Decimal = dataclasses.field(default=Decimal(default="35.0"))
dispense_velocity: Decimal = dataclasses.field(default=Decimal(default="40.0"))
The coefficients [0.05, 1.02] define a linear correction: corrected = 0.05 + 1.02 × volume. Only the parameters you want to override need to be specified; all others inherit from the default values.
await bravo.pipette_head.aspirate(
plate=source_reservoir,
volume=100,
liquid_class=EthanolLiquidClass,
)
Custom Liquid Class with Calibration Curve
When you have measured calibration data but don't know the exact correction formula, you can provide a curve dictionary and let the SDK fit a polynomial automatically. Set coefficients to an integer specifying the polynomial order.
@dataclasses.dataclass
class CalibratedEthanol(BravoLiquidClass):
tip: type[AgilentTip] = AgilentTip_250
min_volume: Decimal = dataclasses.field(default=Decimal(default="51"))
max_volume: Decimal = dataclasses.field(default=Decimal(default="250"))
# Set coefficients to an int to specify polynomial order for fitting
coefficients: list[decimal.Decimal] | int = 2
# Measured calibration points: target µL → corrected plunger µL
curve: dict[float, float] | None = dataclasses.field(
default_factory=lambda: {
0: 0.0,
50: 51.8,
100: 102.5,
150: 153.4,
200: 204.6,
250: 255.9,
}
)
aspirate_velocity: Decimal = dataclasses.field(default=Decimal(default="35.0"))
aspirate_acceleration: Decimal = dataclasses.field(default=Decimal(default="75.0"))
aspirate_velocity_into_wells: Decimal = dataclasses.field(default=Decimal(default="45.0"))
aspirate_velocity_out_of_wells: Decimal = dataclasses.field(default=Decimal(default="55.0"))
aspirate_acceleration_into_wells: Decimal = dataclasses.field(default=Decimal(default="90.0"))
aspirate_acceleration_out_of_wells: Decimal = dataclasses.field(default=Decimal(default="110.0"))
aspirate_post_delay_ms: int = 500
dispense_velocity: Decimal = dataclasses.field(default=Decimal(default="40.0"))
dispense_acceleration: Decimal = dataclasses.field(default=Decimal(default="80.0"))
dispense_velocity_into_wells: Decimal = dataclasses.field(default=Decimal(default="55.0"))
dispense_velocity_out_of_wells: Decimal = dataclasses.field(default=Decimal(default="45.0"))
dispense_acceleration_into_wells: Decimal = dataclasses.field(default=Decimal(default="110.0"))
dispense_acceleration_out_of_wells: Decimal = dataclasses.field(default=Decimal(default="90.0"))
dispense_post_delay_ms: int = 500
coefficients: int = 2means "fit a quadratic polynomial (2 coefficients) to the calibration data"- The
curvedictionary maps target volumes (µL) to measured corrected plunger volumes (µL) - The SDK uses least-squares fitting to derive the polynomial coefficients automatically at initialization time