Skip to content

Encapsulate pipette batch scheduling into backend-agnostic module#949

Open
BioCam wants to merge 16 commits intoPyLabRobot:mainfrom
BioCam:encapsulate-pipette-batch-scheduling
Open

Encapsulate pipette batch scheduling into backend-agnostic module#949
BioCam wants to merge 16 commits intoPyLabRobot:mainfrom
BioCam:encapsulate-pipette-batch-scheduling

Conversation

@BioCam
Copy link
Collaborator

@BioCam BioCam commented Mar 20, 2026

Extracts batch scheduling logic from STARBackend into pipette_batch_scheduling.py - a standalone, backend-agnostic module with full test coverage.

The Problem

probe_liquid_heights relied on several tightly coupled private methods (execute_batched, _probe_liquid_heights_batch, _compute_channels_in_resource_locations, _move_to_traverse_height) embedded in STARBackend. The scheduling algorithm (X grouping, Y sub-batching) was inseparable from Hamilton-specific hardware calls, making it untestable in isolation and not easily reusable by other backends.

While per-channel spacing support was already merged (PRs #862, #870, #915), the scheduling layer had gaps: non-consecutive channel batches (e.g. [0,1,2,5,6,7]) left intermediate physical channels 3,4 unpositioned, Y batch feasibility used a single-pair check rather than full pairwise span validation, and there was no lookahead between batches.

PR Content/Solution

New module: pipette_batch_scheduling.py

plan_batches() partitions channel-position pairs into executable batches. Backend-agnostic - depends only on channel indices, positions, and spacing constraints.

New capabilities

  • Phantom channel interpolation - when a batch contains non-consecutive channels (e.g. [0,2,5]), the physically intermediate channels (1,3,4) are explicitly positioned at correct pairwise spacing. Previously they were left wherever they happened to be.
  • Pairwise span validation - batch feasibility checks the full span between outermost channels via _span_required (sum of adjacent pairwise spacings), not just the gap between the candidate and the batch's lowest-Y member. This matters for mixed-channel instruments where spacing(ch0->ch3) is s(0,1) + s(1,2) + s(2,3), not 3 * max(spacings).
  • Transition optimisation/lookahead - between batches, idle channels are pre-positioned toward their next-needed Y coordinate by scanning forward through upcoming batches. This is a pure performance optimization that reduces Y travel time; it does not affect correctness.
  • Configurable X grouping - replaces round(x, 1) (Python's banker's rounding) with math.floor(x / tolerance) * tolerance and exposes x_grouping_tolerance as a parameter.

Structural changes

  • probe_liquid_heights rewritten - replaces the 5-method delegation chain (execute_batched with callback closure -> _probe_liquid_heights_batch -> _compute_channels_in_resource_locations -> _move_to_traverse_height) with a single linear flow calling plan_batches(). The method is longer (262 vs 124 lines) but reads top-to-bottom without jumping between methods or files.
  • Replaces planning.py - the old module provided X grouping and Y sub-batching but lacked phantom interpolation, span validation, and transition lookahead. Deleted along with its tests.
  • Bug fix - spacings list sizing now covers num_channels (not just max(use_channels)+1), preventing IndexError in transition optimization.

Not in scope

Detection parameter exposure (cLLD/pLLD kwargs) is intentionally deferred to a follow-up PR to keep this change focused on scheduling encapsulation.

Preview

a small taste of the new functionalities (GitHub doesn't allow larger videos)

WellPlateScene_with_logo.mp4

BioCam and others added 7 commits March 12, 2026 22:53
Replace hamilton/planning.py with pipette_batch_scheduling.py, a
self-contained module for channel-batch planning, Y-position
computation, and X-group scheduling. Refactor STAR_backend's
probe_liquid_heights and execute_batched to use the new API.
Add volume-tracker-based probe_liquid_heights mock to chatterbox.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extracts pipette channel batching/scheduling logic from the Hamilton STARBackend into a new backend-agnostic module, and rewrites probe_liquid_heights to use the new planner while adding tests for the scheduling algorithm.

Changes:

  • Added pipette_batch_scheduling.py with plan_batches() (X grouping + Y sub-batching), phantom-channel interpolation, pairwise span validation, and optional batch-transition lookahead optimization.
  • Rewrote STARBackend.probe_liquid_heights() to use plan_batches() and the new helper utilities (input validation, offset computation, absolute position computation).
  • Replaced the old Hamilton-only planning.py and its tests with new dedicated unit tests for the new scheduling module.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pylabrobot/liquid_handling/pipette_batch_scheduling.py New backend-agnostic batching/scheduling module (plan_batches, transition optimization, helpers).
pylabrobot/liquid_handling/pipette_batch_scheduling_tests.py New unit tests covering spacing logic, phantom interpolation, batching, and transition optimization.
pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Integrates new planner into probe_liquid_heights, adjusts spacing-related logic, removes legacy batching helpers.
pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py Adds missing mock methods and a simulation-friendly probe_liquid_heights implementation using shared validation.
pylabrobot/liquid_handling/backends/hamilton/planning.py Removed legacy Hamilton-only batching module.
pylabrobot/liquid_handling/backends/hamilton/planning_tests.py Removed tests for the deleted legacy planner.
pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py Refactors tests to exercise the new probe_liquid_heights flow (and rehomes some test classes).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 11473 to 11478
if spread == "wide":
offsets = get_wide_single_resource_liquid_op_offsets(
resource=well,
num_channels=len(piercing_channels),
min_spacing=self._get_maximum_minimum_spacing_between_channels(piercing_channels),
min_spacing=max(self._channels_minimum_y_spacing),
)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In pierce_foil(spread='wide'), min_spacing is now max(self._channels_minimum_y_spacing) (global max across the whole head). This can overspread channels unnecessarily (or even beyond the well/plate geometry) when the operation uses only a subset of channels with smaller pairwise constraints. Restore the previous behavior by computing spacing based on the piercing_channels subset (e.g., maximum required adjacent-pair spacing across the involved physical span).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the removal of execute_batched, the code becomes a lot less modular. and we do want to use execute_batched for other commands in the future

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, the current execute_batched seemed very limited to probing actions, which is why I removed it, but you're saying that it actually acts as an abstraction layer for any function that is meant to be called after the channels have moved to their target locations - I really like this

I will work on an implementation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I returned it and tried to make it even more adaptive:

Screenshot 2026-03-22 at 20 15 18

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with small adjustment to this workflow we can use it for...

  • probing (ztouch, cLLD, pLLD)
  • aspirate
  • dispense

BioCam and others added 2 commits March 22, 2026 14:40
…odd-span wall crash

- plan_batches now takes targets (Containers or Coordinates) and handles position computation and same-container spreading internally, replacing the external compute_offsets + compute_positions + plan_batches sequence
- Restore execute_batched on STARBackend; probe_liquid_heights uses it via _probe_batch_heights closure instead of an inline batch loop
- Make +5.5mm odd-span center-avoidance offset conditional on container width to prevent tip-wall collisions on narrow containers
- Generalize compute_positions to accept any Resource (wrt_resource), not just Deck
- Remove dead code: _optimize_batch_transitions (LATER :), _find_next_y_target
- Rename validate_probing_inputs -> validate_channel_selections
- Clean up redundant tests, add container-path coverage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does CB need probe_liquid_heights? ideally it should share the same logic as the star backend equivalent. previously I just overrode the measurement bit because that part we can't know in CB

Copy link
Collaborator Author

@BioCam BioCam Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because....

  1. Chatterbox code needs to work completely interchangeably with its backend/driver counterpart without the need to say if protocol_mode == "simulation" every time.
  2. I disagree with...

previously I just overrode the measurement bit because that part we can't know in CB

...the simulation/chatterbox actually has a unique opportunity here:
I made it return what the liquid tracker returns - this enables true simulation :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok yes this makes sense

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.

3 participants