Skip to content

Conversation

@BioCam
Copy link
Collaborator

@BioCam BioCam commented Dec 12, 2025

Sort resources by...
1. x ascending
2. y descending within each x
3. grouped into chunks by x
4. split into sub-chunks of size <= max_chunk_size
5. chunks sorted by length (smallest → largest)

Parameters
----------
resources : list[Any]
    List of resources that implement .get_absolute_location()
    returning an object with x and y attributes.
max_chunk_size : int
    Maximum size for any single chunk.

Returns
-------
list[list[Any]]
    A list of grouped and sorted resources.

Example
-------
>>> sorted_chunks = sort_by_xy_and_chunk_by_x(well_list, max_chunk_size=8)
>>> [
...   list(
...     zip(
...       [r.get_identifier() for r in chunk],
...       [r.get_absolute_location() for r in chunk],
...     )
...   )
...   for chunk in sorted_chunks
... ]
[[('D1', Coordinate(x=450.9, y=402.3, z=164.45)),
  ('H1', Coordinate(x=450.9, y=366.3, z=164.45)), ...],
 [('D2', Coordinate(x=459.9, y=402.3, z=164.45)), ...]]

One of the most common sorting sequences for dynamic automation processes I came across.

Not hung up on the name though - please let me know whether you can think of a better one :)

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 introduces a new utility function sort_by_xy_and_chunk_by_x that provides a common sorting pattern for dynamic automation processes involving spatial resources. The function sorts resources by x-coordinate (ascending) and y-coordinate (descending), groups them by x-coordinate, splits large groups into smaller chunks, and optionally sorts these chunks by size.

Key Changes:

  • Added sort_by_xy_and_chunk_by_x function to handle multi-dimensional sorting and chunking of resources
  • Imported groupby from itertools and added Any to typing imports

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

BioCam and others added 2 commits December 12, 2025 18:07
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

def sort_by_xy_and_chunk_by_x(
resources: list[Any],
max_chunk_size: int,

Choose a reason for hiding this comment

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

Might make sense to have max_chunk_size be optional as well. I could imagine wanting to just get a separate chunk for each unique x coordinate (as is I would just enter an arbitrarily large chunk size if I wanted to do this).

@rickwierenga
Copy link
Member

do you think we should sort and chunk in 2 separate functions?

chunk(sort(resources))

@adamferguson13
Copy link

adamferguson13 commented Dec 13, 2025

One thing that could be nice to add here (possibly in a future PR) is a "fuzz factor" that would allow you to set a tolerance within which to still group locations by X. Use case: If you have individually taught plate positions for maximum accuracy on your STAR, but you'd still like to be able to use all channels together in cases where a small difference in x (say, less than a few tenths of a mm) wouldn't matter for channel access. In this use case, I could imagine doing something like:

sorted_chunks = sort_by_xy_and_chunk_by_x(well_list, max_chunk_size=8, tolerance=0.2)

Or (using pseudocode, but this is already possible in PLR):

x_tolerance_mm = 0.1*get_well_diameter(Eppendorf_96_wellplate_250ul_Vb_skirted)
sorted_chunks = sort_by_xy_and_chunk_by_x(well_list, max_chunk_size=8, tolerance=x_tolerance_mm)

@adamferguson13
Copy link

do you think we should sort and chunk in 2 separate functions?

chunk(sort(resources))

Yes I think this would make sense! Output of sort then would just be a list instead of a list of lists

@BioCam
Copy link
Collaborator Author

BioCam commented Dec 13, 2025

a "fuzz factor" that would allow you to set a tolerance within which to still group locations by X. Use case: If you have individually taught plate positions for maximum accuracy on your STAR,

I'm sure: the STAR firmware has to be given the exact same x-coordinates for a single command if the channels are supposed to interact with the resource (i.e. pickup/drop from TipSpot, aspirate/dispense from Well) in parallel.
If there is some tolerance accepted in the command generation the firmware will perform the action but do so in as many steps as there are different x-coordinates (all only if star.allow_firmware_planning = True)

Or do you mean the sorting would add offsets if wells are "fuzzy"-close to the same x-coordinate; that would make sense for some operations but quite hard to implement?

@BioCam
Copy link
Collaborator Author

BioCam commented Dec 13, 2025

do you think we should sort and chunk in 2 separate functions?

Interesting, because I don't want to reinvent sorted() but want to provide a general high-utility sorter that abstracts having to perform multiple steps based on the very common heuristic described in this docstring

@rickwierenga
Copy link
Member

If you have individually taught plate positions for maximum accuracy on your STAR, but you'd still like to be able to use all channels together in cases where a small difference in x (say, less than a few tenths of a mm) wouldn't matter for channel access.

the goal of this function would be to find which resources can be accessed in parallel on robots without variable x-span. so I think the resolution parameter makes sense so users can match it to what their specific robot supports.

(side question: why do you "teach plate positions" to less than 0.1mm resolution on a star? the STAR firmware runs on 0.1mm units for every command so anything smaller than that is always rounded to the nearest 0.1)

@rickwierenga
Copy link
Member

Interesting, because I don't want to reinvent sorted() but want to provide a general high-utility sorter that abstracts having to perform multiple steps based on the very common heuristic described in this docstring

yes, I guess given that you just say

sorted(
  resources,
  key=lambda r: (r.get_absolute_location().x, -r.get_absolute_location().y),
)

and that sorting, grouping are similarly simple functions, it might become a little verbose to say

chunk(
  group_by(
    sorted(
      resources,
      key=lambda r: (r.get_absolute_location().x, -r.get_absolute_location().y),
    ),
    key=lambda r: r.get_absolute_location().x,
  ),
  chunk_size=8,
)

every single time

but still having functions like that which are just a tiny bit simpler than standard python syntax could be nice utilities, and then the function sort_by_xy_and_chunk_by_x could be just the snippet above

for me personally I like explicit code but I can see it being repetitive to not have sort_by_xy_and_chunk_by_x

@rickwierenga
Copy link
Member

https://docs.python.org/3/library/itertools.html#itertools.batched is only available in 3.12+...

@adamferguson13
Copy link

a "fuzz factor" that would allow you to set a tolerance within which to still group locations by X. Use case: If you have individually taught plate positions for maximum accuracy on your STAR,

I'm sure: the STAR firmware has to be given the exact same x-coordinates for a single command if the channels are supposed to interact with the resource (i.e. pickup/drop from TipSpot, aspirate/dispense from Well) in parallel. If there is some tolerance accepted in the command generation the firmware will perform the action but do so in as many steps as there are different x-coordinates (all only if star.allow_firmware_planning = True)

Or do you mean the sorting would add offsets if wells are "fuzzy"-close to the same x-coordinate; that would make sense for some operations but quite hard to implement?

Yes good point, I imagine we'd want to average the x location for the group before sending the command. I could see how that's getting a little more into the weeds with implicit logic than we might like. Even if we added an optional tolerance or resolution parameter, I'd think the default behavior would be to require the same x-coordinate to be grouped together unless a resolution is specified.

@adamferguson13
Copy link

(side question: why do you "teach plate positions" to less than 0.1mm resolution on a star? the STAR firmware runs on 0.1mm units for every command so anything smaller than that is always rounded to the nearest 0.1)

Yep correct, have never taught positions to less than 0.1 mm resolution. I said "less than a few tenths of a mm" but in reality I think this could be useful for larger differences -- really depends on the well geometry. Flat bottom 96 well plates could probably be ~2mm off in X and still be accessed at the same time.

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