UniteLabs

Liquid Classes

Using predefined and custom liquid classes on Hamilton and Agilent Bravo liquid handlers.

See Liquid Classes for the conceptual model — what a liquid class encapsulates, how the Hamilton and Bravo parameter models differ, and why liquid classes are dataclasses. This guide covers the procedural API for instantiating, browsing, and subclassing predefined classes.

Using Predefined Classes

Hamilton ships a library of named liquid classes. List available classes by printing the enum, then instantiate by name.

from unitelabs.labware.hamilton import LiquidClass

print(LiquidClass)
# HamiltonTip_300_Water_DispenseJet_Empty
# HamiltonTip_300_Water_DispenseJet_Part
# HamiltonTip_300_Water_DispenseSurface_Empty
# HamiltonTip_300_Water_DispenseSurface_Part

liquid_class = LiquidClass.HamiltonTip_300_Water_DispenseJet_Empty()

# HamiltonTip_300_Water_DispenseJet_Empty [Water (100%)]
#   aspirate_flow_rate: 100
#   dispense_flow_rate: 180
#   ...

Any parameter can be overridden after instantiation:

liquid_class.aspirate_flow_rate = 200

Liquid Class Parameters

Hamilton

Key parameters on a Hamilton liquid class:

ParameterDescription
aspirate_flow_ratePlunger speed during aspiration (µL/s)
aspirate_mix_flow_ratePlunger speed during mixing aspiration (µL/s)
aspirate_transport_air_volumeAir drawn after aspiration to prevent dripping (µL)
aspirate_blowout_air_volumePre-conditioning blowout air (µL)
aspirate_swap_speedRetract speed after aspiration (mm/s)
aspirate_settling_timeDwell time in liquid after aspiration (s)
aspirate_over_aspirate_volumePre-wetting extra volume (µL)
dispense_modeJet empty / jet part / surface empty / surface part
dispense_flow_ratePlunger speed during dispense (µL/s)
dispense_stop_flow_rateFlow rate at end of dispense step (µL/s)
dispense_stop_back_volumeAir re-aspirated immediately after dispense (µL)
curveVolume correction map: {target_µL: corrected_µL, ...}

Bravo

Key parameters on a Bravo liquid class:

ParameterDefaultDescription
aspirate_velocity5.0 mm/sPlunger speed during aspiration
aspirate_acceleration10.0 mm/s²Plunger acceleration
aspirate_velocity_into_wells50.0 mm/sZ descent velocity
aspirate_post_delay_ms250 msDwell after aspiration
dispense_velocity5.0 mm/sPlunger speed during dispense
dispense_acceleration10.0 mm/s²Plunger acceleration
dispense_post_delay_ms250 msDwell after dispense
coefficients0.0, 1.0Polynomial volume correction: corrected = c₀ + c₁·v + c₂·v² + ...

Serializing a Liquid Class

.serialize() returns a JSON-compatible dict holding the subclass name plus every parameter of the instance; .deserialize() rebuilds an equal instance of that subclass. Instance overrides are preserved — you get back the class you had, not the shipped defaults.

import json

from unitelabs.labware.hamilton import HamiltonLiquidClass, LiquidClass

liquid_class = LiquidClass.HamiltonTip_300_Water_DispenseJet_Empty()
liquid_class.aspirate_flow_rate = 200

data = liquid_class.serialize()
# {
#   "type": "HamiltonTip_300_Water_DispenseJet_Empty",
#   "liquid": {"Water": "1"},
#   "tip": "HamiltonTip_300",
#   "curve": {"0.0": 0.0, "20.0": 23.2, ...},
#   "aspirate_flow_rate": "200",
#   ...
# }

restored = HamiltonLiquidClass.deserialize(json.loads(json.dumps(data)))

assert restored == liquid_class
assert restored.aspirate_flow_rate == 200  # the override survived

Notes:

  • Numbers are strings. Decimal parameters serialize as strings so no precision is lost; deserialize parses them back to Decimal.
  • Classes are names. tip (and any other class-valued field) serializes as the class name and resolves back to the class on load.
  • Deserialize from the right base. deserialize only resolves the class it is called on or a subclass of it, so BravoLiquidClass.deserialize rejects a Hamilton liquid class instead of loading it.

Inside a parameter set

Parameter sets carry their liquid class through dumps() / loads() the same way, so the full liquid class is preserved rather than reduced to its name:

from unitelabs.labware.hamilton import LiquidClass
from unitelabs.liquid_handling.hamilton.modules.core96 import CoRe96AspirateParameterSet

parameters = CoRe96AspirateParameterSet(
    volume=100,
    liquid_class=LiquidClass.HamiltonTip_300_Water_DispenseJet_Empty(aspirate_flow_rate=200),
)

restored = CoRe96AspirateParameterSet.loads(parameters.dumps())

assert restored.liquid_class == parameters.liquid_class

Passing the liquid class type instead of an instance dumps as {"type": "<name>"} and loads back as a default-constructed instance of it — the same value the type would have resolved to at pipetting time.

Creating a Custom Liquid Class

Subclass HamiltonLiquidClass and override the fields you want to change. All other fields inherit their default values.

import dataclasses

from unitelabs.labware import Decimal, Ingredient, Liquid, Mixture, PredefinedLiquids
from unitelabs.labware.hamilton import DispenseMode, HamiltonLiquidClass, StandardTip


@dataclasses.dataclass
class EthanolJetEmpty(HamiltonLiquidClass):
    liquid: Mixture = dataclasses.field(
        default_factory=lambda: Mixture([Ingredient(PredefinedLiquids.ETHANOL, 1)])
    )
    tip: type = HamiltonTip_300

    aspirate_flow_rate: Decimal = Decimal(default="80")
    aspirate_settling_time: Decimal = Decimal(default="1.5")
    dispense_mode: int = DispenseMode.JET_EMPTY
    dispense_flow_rate: Decimal = Decimal(default="150")

    curve: dict[float, float] = dataclasses.field(
        default_factory=lambda: {
            0.0: 0.0,
            50.0: 52.1,
            100.0: 103.8,
            200.0: 207.0,
            300.0: 311.2,
        }
    )