Skip to content

Encapsulate pipette batch scheduling into backend-agnostic module#949

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

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

Conversation

@BioCam
Copy link
Copy Markdown
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
Copy Markdown
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 thread pylabrobot/liquid_handling/pipette_batch_scheduling.py Outdated
Comment thread pylabrobot/liquid_handling/pipette_batch_scheduling.py Outdated
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py Outdated
Comment thread pylabrobot/liquid_handling/pipette_batch_scheduling.py Outdated
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
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
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_chatterbox.py
Comment thread pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Comment thread pylabrobot/liquid_handling/pipette_batch_scheduling.py Outdated
Comment on lines +321 to +330
def validate_channel_selections(
containers: List[Container],
use_channels: Optional[List[int]],
num_channels: int,
) -> List[int]:
"""Validate and normalize channel selection.

If *use_channels* is ``None``, defaults to ``[0, 1, ..., len(containers)-1]``.

Returns:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this function should probably live outside of batch scheduling, and can also be used elsewhere in LH

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I agree; but not sure where it should go. Do you have a preferred home for it?

BioCam and others added 20 commits April 10, 2026 16:38
…ral pipette scheduling

Batch scheduling allows duplicate channels since `aspirate`/`dispense` will reuse the same channel across batches.

`probe_liquid_heights` rejects them because each channel probes exactly one container per call.

Callers needing more containers than channels should call `probe_liquid_heights` multiple times in sequence and this has to be explicit.
…eights` around it

Step toward evolving the chatterbox from a command echo layer into a simulator.

The chatterbox `execute_batched` iterates batches and calls the callback without physical moves.

`probe_liquid_heights` now flows through it with a mock LLD callback, running the same validation, target resolution, and batch planning as the real backend.

Protocols developed off-hardware will encounter the same errors and batch structure as on the instrument.

The `execute_batched` override will also serve future batched aspirate/dispense.
Extract `_run_lld_on_channel_batch` from the probe_liquid_heights closure to an instance method so chatterbox overrides only the sensing step instead of the entire function.

Drops ~85 lines of chatterbox duplication (full probe_liquid_heights and redundant execute_batched override) - validation, target resolution, batch planning, merge, and Z-safety now have a single source of truth.

Adds `verbose: bool = False` to `execute_batched`/`probe_liquid_heights` for optional plan printing, makes `print_batches`'s `use_channels`/`containers` parameters optional, and promotes `lld_mode` validation to fail-fast before any I/O.
Allows mixing GAMMA and PRESSURE LLD across containers in a single call (e.g. capacitive for aqueous, pressure for organic) by widening `lld_mode` to `Union[LLDMode, List[LLDMode], None]` and dispatching `detect_func` per channel within each batch.

Matches the singular-name-list-type convention of aspirate/dispense.

Backward-compatible: scalar values still work with a `DeprecationWarning` (remove in v1b1);
None default silently broadcasts to GAMMA.
…anner

`plan_batches` now takes containers + wrt_resource directly (instead of
pre-resolved Coordinates) and decides per-batch container spread via
`compute_nonconsecutive_channel_offsets`, so subsets that fit in a
no-go-zone-divided container can batch even when the full set can't.

Pipeline: `is_valid_batch` → `enumerate_valid_batches` →
`minimum_exact_cover` → `plan_batches`. `ChannelBatch` is the currency
throughout; partition is optimal (branch-and-bound), greedy-only on
realistic inputs but a strict win on any container that fits K but not
K+1 channels.

Removes the prior two-step `resolve_container_targets` + greedy
`plan_batches` pipeline and updates STAR's `_prepare_batched` to the
single call.
Replace greedy first-fit batching with container-aware exact-cover planner
BioCam added a commit to BioCam/pylabrobot that referenced this pull request Apr 17, 2026
Reads each container's tracked volume and converts to height via
compute_height_from_volume. Parameters matching the real-backend
signature are accepted but have no effect in simulation.

Will be upgraded in PyLabRobot#949 (pipette batch scheduling refactor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BioCam added a commit that referenced this pull request Apr 17, 2026
Reads each container's tracked volume and converts to height via
compute_height_from_volume. Parameters matching the real-backend
signature are accepted but have no effect in simulation.

Will be upgraded in #949 (pipette batch scheduling refactor).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BioCam and others added 2 commits April 17, 2026 12:27
…spacing tests

- Rename print_batches → log_batches (uses logger.info, no custom label)
- Remove verbose parameter from execute_batched and probe_liquid_heights
- Remove LLDMode alias from chatterbox, remove chatterbox probe_liquid_heights override
- Deprecate move_to_z_safety_after (warn-only), migrate internal callers to z_position_at_end_of_command
- Restore TestChannelsMinimumYSpacing tests (can_reach_position, position_channels_in_y_direction)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga requested a review from Copilot April 18, 2026 00:23
Copy link
Copy Markdown
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

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


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

Comment on lines +33 to +40
logger = logging.getLogger(__name__)

from pylabrobot.liquid_handling.channel_positioning import (
compute_nonconsecutive_channel_offsets,
)
from pylabrobot.resources.container import Container
from pylabrobot.resources.coordinate import Coordinate
from pylabrobot.resources.resource import Resource
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

Ruff will flag E402 here: imports from pylabrobot.* occur after logger = logging.getLogger(__name__). Please move all imports to the top of the module (or move logger initialization below the imports) so linting passes consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +87
x_groups: Dict[float, list] = {}
for b in batches:
x_key = round(b.x_position, 1)
x_groups.setdefault(x_key, []).append(b)

Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

log_batches() hard-codes X grouping as round(x, 1), which (a) ignores the caller’s configured x_tolerance/x_grouping_tolerance and (b) reintroduces banker's rounding. Consider grouping by the same X key used by planning/execution (e.g., store an x_group_key in ChannelBatch, or accept a tolerance parameter) to keep logs accurate when tolerance != 0.1mm.

Copilot uses AI. Check for mistakes.
Comment on lines 2095 to 2097
"""Pressure liquid level detection mode."""

LIQUID = 0
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

execute_batched() decides when to move X via batch.x_position != prev_batch.x_position. Since x_position is a float (and is computed as an average in the planner), exact inequality can trigger unnecessary X moves between batches that are in the same X group. Use a tolerance-based comparison (e.g. math.isclose / compare within configured X tolerance) or carry a normalized X-group key from the planner.

Copilot uses AI. Check for mistakes.
Comment on lines 2305 to +2313
"""Probe liquid surface heights in containers using liquid level detection.

Performs capacitive or pressure-based liquid level detection (LLD) by moving channels to
container positions and sensing the liquid surface. Heights are measured from the bottom
of each container's cavity.

Uses ``plan_batches`` for X/Y partitioning with per-batch container spread
(respecting no-go zones), then ``execute_batched`` to iterate batches with
Z safety.
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

probe_liquid_heights() now warns whenever move_to_z_safety_after is not None, but internal callers (e.g. probe_liquid_volumes) pass this argument unconditionally. This will emit a deprecation warning on every call. Consider updating internal call sites to the new API (map the boolean to z_position_at_end_of_command behavior) and/or only warn when the deprecated parameter is explicitly used by external callers.

Copilot uses AI. Check for mistakes.
Comment on lines 2317 to +2322
use_channels: Channel indices to use for probing (0-indexed).
resource_offsets: Optional XYZ offsets from container centers. Auto-calculated for single
containers with odd channel counts to avoid center dividers. Defaults to container centers.
lld_mode: Detection mode - LLDMode(1) for capacitive, LLDMode(2) for pressure-based.
Defaults to capacitive.
resource_offsets: Optional XYZ offsets from container centers. When not provided,
``plan_batches`` auto-spreads channels targeting the same container.
lld_mode: Detection mode. Either a single ``LLDMode`` applied to all containers
(deprecated, removed in v1b1) or a list of ``LLDMode``s (one per container)
allowing mixed GAMMA/PRESSURE within one call. ``None`` (default) applies
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

This change rejects duplicate channels in a single probe_liquid_heights() call. Previously the batching layer could serialize duplicate channel assignments (and the new scheduling module still supports that), which is useful for probing >N containers with N channels. If duplicates need to remain supported, the result aggregation should be keyed by job/container index (not channel) so a channel can appear in multiple batches without mixing measurements; otherwise, confirm no existing call paths rely on duplicate use_channels (e.g. probe_liquid_height inside aspirate/dispense).

Copilot uses AI. Check for mistakes.
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