What is the next evolution of "LiquidClass" for PLR?

Hi everyone,

following on from discussions in ParameterSet pondering and Hamilton backends no longer infer liquid classes
… in this thread I propose an open discussion on…

  1. what the classic “LiquidClass” should evolve to in PLR and
  2. how we want to systematically tackle the creation of an alternative while mainting the pragmatic programmer’s paradigm of “Do no harm” to any existing code.

(I’ll call the classic LiquidClass cLC for brevity here as we need a simple name to distinguish it from what we are aiming to create)


First: What is a classic LiquidClass?

Despite the name, a classic LiquidClass is not a programmatic “class” in the typical object-oriented programming sense. Instead, it’s a collection of empirical settings or parameters that define how a liquid should be handled by the system. The name “LiquidClass” can be a bit misleading since it implies a more abstract, programmatic definition. However, in reality, it’s more of a configuration or profile that the liquid handling system uses to optimize pipetting for different types of liquids.

Think of it as a preset that the system loads to adjust the mechanical and operational parameters for a specific liquid, rather than a “class” in the programming sense that defines an object with properties and methods.

e.g. for a Hamilton STAR cLC:

star_mapping[(1000, True, True, False, Liquid.DMSO, True, True)] = \
HighVolume_96COREHead1000ul_DMSO_DispenseJet_Empty = HamiltonLiquidClass(
  curve={500.0: 508.2, 0.0: 0.0, 100.0: 101.7, 20.0: 21.7, 1000.0: 1017.0},
  aspiration_flow_rate=250.0,
  aspiration_mix_flow_rate=250.0,
  aspiration_air_transport_volume=5.0,
  aspiration_blow_out_volume=40.0,
  aspiration_swap_speed=2.0,
  aspiration_settling_time=1.0,
  aspiration_over_aspirate_volume=0.0,
  aspiration_clot_retract_height=0.0,
  dispense_flow_rate=400.0,
  dispense_mode=3.0,
  dispense_mix_flow_rate=1.0,
  dispense_air_transport_volume=5.0,
  dispense_blow_out_volume=40.0,
  dispense_swap_speed=1.0,
  dispense_settling_time=0.0,
  dispense_stop_flow_rate=250.0,
  dispense_stop_back_volume=0.0
)

Community information gathering

Topic A. cLC Pitfall List

Maybe let’s start with everyone introducing the pitfalls they currently see with the cLC.

If you have robot-specific examples, please feel free to share them.
The more knowledge we have of how cLCs differ between different robots the more we understand about structuring an alternative solution.

Topic B. Naming an Alternative Solution

It seems to me that LiquidClass is quite a bit of a misnomer because it contains essentially zero information about a Liquid (e.g. density, viscosity, electrical conductivity, vapor pressure, …) but implies in its name that it is a class about liquids … ?

In PLR, we have the unique ability to change the name of the general concept behind cLC to something that is more intuitive and descriptive.

Please suggest alternative names, e.g. LiquidTransferInformation, a class that stores, manages and empowers a specific liquid’s transfer parameters?

Topic C. Desired Functionalities of a cLC Alternative

To define an alternative we first have to find agreement on a general LiquidTransferInformation (not stuck on the name but have to use sth here) masterclass from which machine-specific subclasses can inherit “common denominator” behaviour and extend upon it to represent machine specific parameters and adaptations:
e.g.

class HamiltonSTARLiquidTransferInformation(LiquidTransferInformation)

Identifying properties and methods to add to this method is going to be hard.
But if it makes autonomous liquid handling even a little bit more accurate I’d say it’s worth figuring out :slight_smile:

e.g. should all aspiration commands available through the hardware-firmware of a machine be accessible through a specific LiquidTransferInformation subclass:
i.e. should HamiltonSTARLiquidTransferInformation enable access to all aspiration parameters:

command_dict = {
    "at": "Aspiration type",
    "tm": "Tip pattern",
    "xp": "X-axis positions for aspiration",
    "yp": "Y-axis positions for aspiration",
    "th": "Minimum traverse height at the beginning of a command",
    "te": "Minimum Z-axis end position",
    "lp": "Liquid level detection (LLD) search height",
    "ch": "Clot detection height",
    "zl": "Liquid surface height (no LLD)",
    "po": "Pull-out distance for transport air",
    "zu": "Second section height",
    "zr": "Second section ratio",
    "zx": "Minimum height",
    "ip": "Immersion depth",
    "it": "Immersion depth direction",
    "fp": "Surface following distance",
    "av": "Aspiration volumes",
    "as": "Aspiration speed",
    "ta": "Transport air volume",
    "ba": "Blow-out air volume",
    "oa": "Pre-wetting volume",
    "lm": "LLD mode",
    "ll": "Gamma LLD sensitivity",
    "lv": "Delta pressure (DP) LLD sensitivity",
    "zo": "Aspirate position above Z touch-off",
    "ld": "Detection height difference for dual LLD",
    "de": "Swap speed",
    "wt": "Settling time",
    "mv": "Homogenization volume",
    "mc": "Homogenization cycles",
    "mp": "Homogenization position from liquid surface",
    "ms": "Homogenization speed",
    "mh": "Homogenization surface-following distance",
    "gi": "Limit curve index",
    "gj": "TADM algorithm selection",
    "gk": "Recording mode",
    "lk": "Use second section aspiration",
    "ik": "Retract height over second section to empty tip",
    "sd": "Dispensation speed during emptying tip",
    "se": "Dosing drive speed during second section search",
    "sz": "Z-drive speed during second section search",
    "io": "Cup upper edge height",
    "il": "Ratio of liquid rise to tip deep-in",
    "in": "Immersion depth for second section"
}

Some clearly should be included, others clearly should not but others yet are more ambiguous.
Which parameters fall into which category should be clearly written down for clarity.

Another question in this topic: should specific functions calling a HamiltonSTARLiquidTransferInformation be handed the Liquid class which actually deals with the information of the physicochemical properties of the liquid?
Then a PLR LiquidTransferInformation Warehouse (i.e. the PLR equivalent to the PLR Resource Library for Resources and PLR Protocol Library for sharing of automated Protocols)
…can be generated to enable the sharing and querying of parameterised LiquidTransferInformation subclasses based on these Liquid properties.


Please feel free to add topic points or correct anything I’ve written that you feel requires correction.

Or maybe you think the enormous variety of liquid handlers, liquids and resulting LiquidTransferInformation is too big/not worth structuring in PLR. Please let us know. We want to hear your views :slight_smile:

3 Likes

thank you for the detailed post, as always

I am pretty much set on having them generate kwargs, as seen here

A liquid class is nothing more than

  1. a set of parameters passed to an aspirate or dispense operation
  2. a volume correction curve

Some requirements:

  • parameter sets (“liquid classes”) should be user defined
    • eg, if the user wants to ignore mixing, they should simply omit those parameters
    • this makes it impossible to use typed dicts
      • if the user defines their own TypedDicts in their own code, that’s obviously fine
  • it should be optional to use them when aspirating/dispensing (!!!)
  • they should be usable outside the context of a 1->1 transfer (eg single asp, multi disp)
    • this means “transfer” is too narrow

re (1) It makes sense to store them as parameters than can easily be passed to a method. In Python, this can be done conveniently using dicts and kwargs. I think almost anything else would be over-engineered.

I think the bigger difficultly lies in querying a good data class to use at for specific oepration. For this, simply use the volume trackers and have a get_parameter_set() function, maybe we can add a “which liquids would be aspirated” method for convenience. Interpolation between calibrated points is useful, and we can provide a utility for that.

re (2) this is a simply multiplication or function call for the user

I understand this approach requires slightly more code than before. I consider this a feature not a bug: at least now it will be clear what is going on. The problem we having right now with moving away from the previous implementation is specifically because it was too magic-y. If it had worked, ok, but it the prebuilt liquid classes aren’t worth it.

A lot of code looks like this already:

async def transfer_media():
  await lh.aspirate(...)
  await lh.dispense(...)
async def add_water():
  await lh.aspirate(...)
  await lh.dispense(...)

it’s obvious what those functions do, and it’s easy to reuse them. Modifying as so is very logical:

async def transfer_media():
  media_params = {"flow_rate" 42, ...}
  lh.aspirate(..., **media_params)
  lh.dispense(..., **media_params)

the “liquid class” lives inside the context it’s used.

(I fundamentally believe liquid classes are a venus mindworm that we should move away from. they are needed to share this information in a linear venus drag and drop protocol, but we have better program design capabilities in python)

My vote is to build and see where we get stuck

3 Likes

I think you’re right here, is it’s hard to define a struct or a class in HSL (no GUI drag in for struct), that’s why its a whole separate database.

I do however think you should keep the name liquidClass as this is industry standard, every instrument I’ve used 10+ has that name.

1 Like