UniteLabs

Liquid Classes

Why pipetting needs a parameter model, and how Hamilton and Bravo diverge in how they express it.

A liquid class encapsulates all the parameters needed for one pipetting cycle, from aspiration through dispense. This includes flow or velocity rates, timing, and a volume correction curve that ensures the commanded volume matches what is actually transferred.

Water, ethanol, DMSO, and blood all behave differently under a pipette. A fixed set of flow rates and dwell times optimized for water will over-aspirate ethanol and under-aspirate glycerol. A liquid class is the SDK's way of naming "the full pipetting recipe for this fluid," so the same aspirate(volume=100) call can produce the correct physical result regardless of what the well contains.

Two device models, one concept

Hamilton and Bravo both use liquid classes, but the internal models differ significantly. Knowing the shape of each model is important — protocols written for one do not trivially port to the other.

HamiltonBravo
Motion controlFlow rates (µL/s) + dispense modeVelocity + acceleration per phase (mm/s, mm/s²)
Volume correctionCorrection curve dict (target → corrected)Polynomial coefficients
Auto-selectionMust be passed explicitlyAuto-selected by tip type + volume
Dispense modesJet empty, jet part, surface empty, surface partPipetteMode enum (SURFACE / BOTTOM)

Hamilton treats pipetting as plunger flow: flow rate controls the plunger, a correction-curve dictionary maps commanded volume to corrected volume, and the dispense mode (jet vs. surface, empty vs. part) changes how the tip positions itself relative to the liquid. Every call must be passed an explicit liquid class.

Bravo treats pipetting as motion: velocity and acceleration govern the plunger per phase, a polynomial corrects the commanded volume, and the pipette mode distinguishes above-liquid vs. in-liquid dispense. When no liquid class is passed, the SDK auto-selects one based on the mounted tip type and the requested volume.

A liquid class is a dataclass

Regardless of vendor, liquid classes are Python dataclasses. Predefined classes ship with the SDK (Hamilton's LiquidClass enum, Bravo's BravoLiquidClasses factory); custom classes are defined by subclassing the vendor's base class and overriding only the fields you need to change. All other fields inherit their defaults.

This shape — dataclass, override-what-changes, serializable to dict — means liquid classes can be versioned in code, shared between protocols, and generated from calibration data. A calibration curve can fit a polynomial for Bravo or a correction-curve dict for Hamilton, and the result is a liquid class you commit to your repo alongside the protocol that uses it.

Serialization keeps instance overrides

Every liquid class serializes to a plain JSON-compatible dict via .serialize(), and comes back through .deserialize(). The dict names its subclass under type and carries every parameter of that instance, so a class you tweaked at runtime — a custom curve, a raised flow rate, a different tip — survives the round trip. Recording only the class name would silently discard those overrides and rehydrate the shipped defaults instead.

The capability lives on the shared LiquidClass base, so it works identically for HamiltonLiquidClass, BravoLiquidClass, and any class you define yourself, with nothing to implement per device.

This is what lets a liquid class travel: persist it next to the run that used it, hand it to another process, or store it as part of a parameter set. HamiltonParameterSet.liquid_class and BravoParameterSet.liquid_class serialize through the same mechanism, so dumping a parameter set preserves the full liquid class rather than just its name.

See Liquid Classes for the serialization API in practice.

Using them in practice

  • On Hamilton: pick a predefined LiquidClass member and pass it to every pipetting call, or subclass HamiltonLiquidClass and define your own.
  • On Bravo: usually rely on auto-selection; pass a specific class explicitly when you need to override the default, or subclass BravoLiquidClass to encode your reagent.

See Liquid Classes for the procedural how-to, including predefined-class browsing, parameter reference tables, and custom-class examples.