Backend kwargs

something that Guy Burroughes suggested to me is that we should put backend kwargs in objects rather than using python kwargs

lh.aspirate(
  containers,
  vols,
  params,
  kwarg1=X,
  kwarg2=Y,
)

is difficult to make this code hardware agnostic, since it’s not clear which are params are kwargs vs just normal parameters.

it is better to have sth like this:

lh.aspirate(
  containers,
  vols,
  params,
  backend_params=STARAspirateParams(
    kwarg1=X,
    kwarg2=Y,
  ),
)

so that it is obvious which params are which and also makes it easy to switch robots around:

lh.aspirate(
  containers,
  vols,
  params,
  backend_params=EVOAspirateParams(
    kwarg1=X,
    kwarg2=Y,
  ),
)

I plan on implementing this since in v1 it’s a good suggestion. The type for backend_params should just be Serializable, the backends will deal with the rest.

Thoughts?

3 Likes

In favor!

2 Likes

This is nice, but how will this work with PLR’s current move from this existing feature-first (liquid handler) to the new device-first (STAR) architecture?

the capabilities are feature first, and will have backend kwargs like we currently have

in favor

1 Like

PR: Replace backend_kwargs with backend_params: SerializableMixin by rickwierenga · Pull Request #954 · PyLabRobot/pylabrobot · GitHub

(only the modules I have already made into the capability architecture)

had to do something a bit awkward in Add BackendParams base class with autoreload-safe isinstance · PyLabRobot/pylabrobot@67ec8b4 · GitHub

basically when using autoreload, the isinstance(backend_params, XParams) will fail if it has reloaded because the class the user passes is not the same as the class it checks against. This is a known issue in autoreload, but I feel like in PLR we do a lot of notebook/auto reload based development, so I added a BackendParams class that overrides __instancecheck__ to be robust against auto reload. Quite hacky, but I feel like things breaking in the background is worse.

another idea: Mix right now is an awkward case

it looks like this

@dataclass(frozen=True)
class Mix:
  volume: float
  repetitions: int
  flow_rate: float

but some backends like the STAR take in more mixing parameters than the general case, like mix_position_from_liquid_surface. in v0 it’s passed through backend kwargs. in the current v1 it’s passed through BackendParams.

This is awkward in the calls:

await pip.dispense(                                                                                                    
    resources=[plate["A1"]],                                                                                             
    vols=[100.0],                                                                                                        
    mix=[Mix(volume=50.0, repetitions=5, flow_rate=100.0)],                                                              
    backend_params=STARPIPBackend.DispenseParams(                                                                                       
      mix_position_from_liquid_surface=[2.0],
    ),                                                                                                                   
) 

because mix parameters are now split

what if we added a BackendParams field to Mix?

```python
@dataclass(frozen=True)
class Mix:
  volume: float
  repetitions: int
  flow_rate: float
  backend_params: Optional[BackendParams] = None

so we could do:

await pip.dispense(                                                                                                    
  resources=[plate["A1"]],                                                                                             
  vols=[100.0], 
  mix=[
    Mix(
      volume=50,
      repetitions=5,
      flow_rate=100,
      backend_params=STARPIPBackend.MixParams(                                                                    
        position_from_liquid_surface=2.0,                                                                    
      )
    )
  ],                                                                                                        
)

I really like the idea of including backend arguments into the Mix class because its lack of including the surface_following_distance_during_mix argument (which I use for every mix action) for the STAR has been very confusing and annoying.

But I’d say adding an additional requirement for a backend-specific subclass(?) looks very awkward too and increases verbosity.

How about we just add backend-arguments directly to the Mix class?
Which would then submit all information anyway to the backend for (1) verification of value acceptance, and (2) firmware payload generation.

Note: This very specific example cannot refer to the STAR(let) because, if I’m not mistaken, that backend’s dispense command’s mixing after dispensing does not work based on a firmware issue (see post from Jan-2024).

1 Like

that will have the same downsides as what I described in the first post, so unfortunately that won’t work

we could have a star specific mix class that subclasses Mix, but that makes code less agnostic and is awkward in its own way

Most liquid handlers move along Z axis during mixing, so we really should be able to populate all the required firmware arguments in Mix, including surface_following_distance_during_mix, but also whatever the equivalent is for other liquid handlers. I think the OT2 is the only LH that does not have simultaneous z and dispenser control.

1 Like

I like that! let’s try it