Skip to content

LiquidHandler: gate aspirate96 tip volume tracking on does_volume_tracking()#1095

Merged
BioCam merged 1 commit into
PyLabRobot:mainfrom
BioCam:gate-aspirate96-tip-volume-tracking
Jun 22, 2026
Merged

LiquidHandler: gate aspirate96 tip volume tracking on does_volume_tracking()#1095
BioCam merged 1 commit into
PyLabRobot:mainfrom
BioCam:gate-aspirate96-tip-volume-tracking

Conversation

@BioCam

@BioCam BioCam commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

aspirate96 gated the source remove_liquid on does_volume_tracking() but added to the 96-head tip trackers unconditionally, so with volume tracking globally off the wells were left untouched while the tips were still loaded - tip state drifting away from a source that never changed.

Single-channel aspirate is the canonical pattern: the whole tracker block, tip included, sits under one does_volume_tracking() guard, and is_disabled is checked only on the container (tips are never disabled). This restructures both aspirate96 branches - single-container and 96-well - to the same shape, so the global flag governs the tip side as well as the source. When tracking is on, behavior is unchanged.

Test: with tracking off, aspirate96 leaves the 96-head tip trackers untouched.

The matching cleanup of the tip commit/rollback loops in aspirate96 and dispense96 - currently ungated but a no-op once the queue is gated - is left to a follow-up consistency PR.

Problem deep-dive

Disabling volume tracking is meant to turn off all liquid bookkeeping, but aspirate96 kept writing to the tip trackers anyway. The numbers below are from a run against hamilton_96_tiprack_300uL_filter, whose tips track to a 360 uL ceiling:

set_volume_tracking(False)              # I don't want any liquid bookkeeping
await lh.pick_up_tips96(tip_rack)
await lh.aspirate96(plate, volume=200)

After that single call, before this change:

  • the source well tracker is untouched, used = 0 (correct - tracking is off)
  • every 96-head tip tracker reads used = 200 uL

The source says nothing was removed, the tips claim to hold 200, and the books do not balance - even though tracking was explicitly off. Single-channel aspirate in the same scenario leaves the tips at 0.

The sharp, throwable consequence shows up on the next aspirate:

set_volume_tracking(False)
await lh.pick_up_tips96(tip_rack)        # tips track to 360 uL
await lh.aspirate96(plate, volume=200)   # tips silently bumped to 200, 160 uL free
await lh.aspirate96(plate, volume=200)   # before: TooLittleVolumeError: Not enough space in container: 200.0uL > 160.0uL.
                                         # after:  proceeds - tracking is off, as asked

VolumeTracker.add_liquid runs its capacity check unconditionally, so the phantom 200 uL leaves only 160 uL free and the next 200 uL overflows the tip, raising TooLittleVolumeError - the precise class of error the user disabled tracking to avoid. After this change the tip tracker is never written while tracking is off, so neither the divergence nor the spurious error occurs.

It only ever affected the tracking-off path; with tracking on the tip add fired in both versions, so normal workflows were unaffected, which is why it went unnoticed.

…tracking()`

aspirate96 gated the source remove_liquid on does_volume_tracking() but added to
the 96-head tip trackers unconditionally, so with volume tracking globally off the
wells were left untouched while the tips were still loaded. Restructure both
aspirate96 branches to match single-channel aspirate: the whole tracker block, tip
included, under one does_volume_tracking() guard, with is_disabled checked only on
the container.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BioCam BioCam requested a review from rickwierenga June 20, 2026 11:19
@BioCam BioCam merged commit 888a832 into PyLabRobot:main Jun 22, 2026
21 checks passed
@BioCam BioCam deleted the gate-aspirate96-tip-volume-tracking branch June 22, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant