-
Notifications
You must be signed in to change notification settings - Fork 186
fix(api): optimize liquid class transfers by only changing volume mode when necessary #20009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: edge
Are you sure you want to change the base?
Conversation
… volume in pipette data
…_transfer_change_volume_mode_improvement
…_transfer_change_volume_mode_improvement
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## edge #20009 +/- ##
==========================================
- Coverage 25.31% 25.10% -0.22%
==========================================
Files 3549 3549
Lines 296827 296826 -1
Branches 42170 42171 +1
==========================================
- Hits 75145 74505 -640
- Misses 221664 222298 +634
- Partials 18 23 +5
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
| elif not self._engine_client.state.pipettes.get_ready_to_aspirate( | ||
| self._pipette_id | ||
| ): | ||
| self.prepare_to_aspirate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, still trying to wrap my head around what this is doing.
BEFORE,
if volume_for_pipette_mode_configuration is None, we would NOT have prepare_to_aspirate() here regardless of whether the pipette is ready to aspirate, right?
Can that (volume_for_pipette_mode_configuration=None and not being ready to aspirate here) ever happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right in that this could cause a difference in behavior in existing protocols and I can make sure this doesn't trigger in those instances.
The fact that we were always unconditionally going down this configure_for_volume path (which also includes the movement and prepare for aspirate) was hiding the fact that there were scenarios where we don't need to configure the volume but do need to prepare to aspirate, which would be if the pipette is starting the transfer not prepared to aspirate, i.e. it's last action was a blow-out or full dispense with push out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, my question was more: was there ever a case when volume_for_pipette_mode_configuration was unset and we were not ready to aspirate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, because the only time this would be None is in non-first aspirates in consolidate, where we would have already called prepare_to_aspirate beforehand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks!
| self.configure_for_volume(volume_for_pipette_mode_configuration) | ||
| self.prepare_to_aspirate() | ||
|
|
||
| elif not self._engine_client.state.pipettes.get_ready_to_aspirate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, my philosophical objection to adding more queries to the engine is that it makes life harder for future-us when we want to be able to say what steps a transfer will do without actually performing the transfer.
Does @ncdiehl still want PD step generation to match Python command-for-command? This would make that a bit harder since PD doesn't have an engine to query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this necessarily precludes us from doing that in the future, because honestly all we need to know is the pipette's initial state, since we'll prepare to aspirate within the transfer component executor after this. But perhaps I should clean this up a bit to make that more clear or make it more functional. But I think on its own this is a correct code, especially if an overpressure error happens during the course of a protocol which we can't predict.
As for PD, I don't think we have the equivalent to this, but this is checking a variable that is updated in response to pick_up_tip, any form of blow_out, a full dispense and that I imagine we could track
| or pipette_definition.AvailableSensorDefinition(sensors=[]), | ||
| volume_mode=liquid_class, | ||
| available_volume_modes_min_vol={ | ||
| volume_mode: props.min_volume |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, is PipetteLiquidPropertiesDefinition.min_volume a lie? It's defined as:
min_volume: float = Field(
...,
description="The minimum supported volume of the pipette.",
alias="minVolume",
)
Do you mean the minimum supported volume in this particular mode? If it's the minimum volume for the pipette in general, what's the point of introducing your available_volume_modes with different entries for default and lowVolumeDefault?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in practice the minimum supported volume of each mode, and we determine if we switch to low volume mode if both we have a low volume mode AND we are providing a volume below the default minimum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the min volume should reference the min volume in each of default or lowVolumeDefault
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryanthecoder or @jbleon95: For the lowVolumeDefault mode, is PipetteLiquidPropertiesDefinition.min_volume expected to just be 0?
I.e., for a low-volume-capable pipette, we would have something like:
- lowVolumeDefault: min_volume=0, max_volume=x
- default: min_volume=x, max_volume=1000
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, a min_volume is non-zero. in the low volume modes it's usually something like 0.5
| if not has_low_volume_mode: | ||
| return VolumeModes.default | ||
| if volume >= available_volume_modes_min_vol[VolumeModes.default]: | ||
| return VolumeModes.default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this PR seems to do a lot of plumbing (adding PipetteDict.available_volume_modes, and then the available_volume_modes_min_vol dict) just to see if volume >= available_volume_modes_min_vol[VolumeModes.default] here.
I thought you were going to use available_volume_modes_min_vol[VolumeModes.lowVolumeDefault] somehow.
If you just need the "high-volume mode transition point," would it be easier to just add that one number to the PipetteDict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I originally was passing the whole dictionary to the engine before I realized it added a lot of cruft to unit tests and decided it was easier to just pass what we need. I can go back and refactor PipetteDict to be more straightforward if we can't think of another reason to keep the full object for any future work
ddcc4
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let me know if you want to simplify the available_volume_modes/available_volume_modes_min_vol logic to keep the classes simpler.
But I'm otherwise OK in principle with this change.
sfoster1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
| or pipette_definition.AvailableSensorDefinition(sensors=[]), | ||
| volume_mode=liquid_class, | ||
| available_volume_modes_min_vol={ | ||
| volume_mode: props.min_volume |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, a min_volume is non-zero. in the low volume modes it's usually something like 0.5
Overview
This PR fixes an inefficiency in the liquid class transfer code related to its built-in
configure_for_volumecalls. Previously we would unconditionallyconfigure_for_volumeany time thevolume_for_pipette_mode_configurationargument was provided in theaspirate_liquid_classfunction call. This was provided for every aspirate intransfer_with_liquid_classanddistribute_with_liquid_class, and for the first step of every consolidate forconsolidate_with_liquid_class. In addition to this configuring the volume, this would also move the pipette to a hardcoded location 2mm above the well top, and if this was different then the submerge start position, an extraneous movement would occur.What this meant in practice was that in the worst case we were doing an extra movement and a no-op
configure_for_volumeandprepare_to_aspirateregardless of whether we were going to change volume mode or if the pipette even had different volume modes in the first place.To fix this, a new method was added to check what volume mode a specific pipette will enter given a volume. Using this, in addition to an existing method to query the current volume mode, we can skip any unneeded volume mode configurations.
When testing with existing protocols, occasionally some would fail with a prepare to aspirate error, which this inefficiency was having happen all the time. To prevent this while keeping in the optimization, we will still prepare to aspirate if we need to by querying the protocol engine.
This change has been put behind a version gate and the max supported version was increased to
2.28.Test Plan and Hands on Testing
Tested a number of existing liquid class protocols, in addition integration and snapshot tests should cover any changes.
Changelog
PipetteDictand protocol engine objects, along with methods to get the current volume mode or if the volume mode will changeReview requests
Does putting the new non-configure
prepare_to_aspiratemake sense where I put it? The pipette should not be in liquid when that is called, but t might be better to put it in after we move to the submerge start position in case for some reason it could be in a bad location for prepare to aspirate.Risk assessment
Medium, the version gate should prevent any changes to existing protocols but this will slightly change the exectuion steps of any new protocol that uses liquid class transfers