Skip to content
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

refactor(engine): dodge thermocycler when moving pipettes #8931

Merged
merged 10 commits into from
Dec 2, 2021

Conversation

sanni-t
Copy link
Member

@sanni-t sanni-t commented Nov 29, 2021

Addresses #8911

Overview

Makes the pipettes move around the thermocycler instead of over it/ crashing into it. The logic is copied from the legacy movement planning code as is. It adds an intermediate waypoint over slot 5 when performing a move that would otherwise have to go over the slots containing a thermocycler.

Changelog

  • added thermocycler dodging to motion.py: pipettes will move through slot 5 to dodge.
  • added should_dodge_thermocycler function to geometry.py ModuleView
  • updated tests

Review requests

  • The above logic needs tracking of current well. What should happen if current_well is not known?
  • Is this missing anything?
  • Test on a real robot. Give it a move from and to locations that are in the BAD_PAIRS, once with thermocycler loaded and once without and make sure that the movement is routed over slot 5 when thermocycler is present.

Risk assessment

Medium. Should only affect limited set of movements when thermocycler is being used.

@codecov
Copy link

codecov bot commented Nov 29, 2021

Codecov Report

Merging #8931 (cccfc93) into edge (3142446) will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             edge    #8931      +/-   ##
==========================================
+ Coverage   74.79%   74.81%   +0.02%     
==========================================
  Files        1860     1860              
  Lines       49294    49355      +61     
  Branches     4852     4852              
==========================================
+ Hits        36869    36926      +57     
- Misses      11581    11585       +4     
  Partials      844      844              
Flag Coverage Δ
api 84.92% <100.00%> (+0.03%) ⬆️
robot-server 91.99% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
api/src/opentrons/protocol_engine/state/state.py 97.64% <ø> (ø)
...pi/src/opentrons/protocol_engine/state/geometry.py 100.00% <100.00%> (ø)
api/src/opentrons/protocol_engine/state/labware.py 100.00% <100.00%> (ø)
api/src/opentrons/protocol_engine/state/modules.py 72.52% <100.00%> (+4.17%) ⬆️
api/src/opentrons/protocol_engine/state/motion.py 100.00% <100.00%> (ø)
...c/opentrons/protocol_engine/execution/equipment.py 89.65% <0.00%> (-3.12%) ⬇️
robot-server/robot_server/hardware.py 75.24% <0.00%> (-1.00%) ⬇️
.../opentrons/protocol_engine/commands/load_module.py 100.00% <0.00%> (ø)
.../protocol_runner/legacy_labware_offset_provider.py 100.00% <0.00%> (ø)

Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

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

Organizational comments below

api/src/opentrons/protocol_engine/state/geometry.py Outdated Show resolved Hide resolved
api/src/opentrons/protocol_engine/state/geometry.py Outdated Show resolved Hide resolved
api/src/opentrons/protocol_engine/state/geometry.py Outdated Show resolved Hide resolved
@@ -216,3 +232,34 @@ def get_tip_drop_location(
z=well_location.offset.z - tip_z_offset,
)
)

def should_dodge_thermocycler(
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this make more sense in the MotionView class? All other logic in this class is related to static geometry

Copy link
Member Author

Choose a reason for hiding this comment

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

If this is put in the MotionView class then the test for get_movement_waypoints becomes much more complicated, and would require stubbing out an internal function, which we are trying to avoid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, I think I meant ModuleView. If the signature changes to

def should_dodge_thermocycler(
    self,
    start: DeckSlotName,
    end: DeckSlotName,
) -> bool:

...then ModuleView has everything it needs, and MotionView can call it by calling self._geometry.get_ancestor_slot_name


Another idea that does not involve moving this function would be to have it return the a list of extra_waypoints directly (or an empty list), simplifying the logic in MotionView

Comment on lines 256 to 264
if self._modules.get_all() and ModuleModel.THERMOCYCLER_MODULE_V1 in [
mod.model for mod in self._modules.get_all()
]:
transit = (
get_slot_name(from_labware).value,
get_slot_name(to_labware).value,
)
if transit in BAD_PAIRS:
return True
Copy link
Contributor

Choose a reason for hiding this comment

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

This could all be moved to a selector in ModulesView, and it might be easier to test/reason with if you do. Something along the lines of:

def should_dodge_thermocycler(self, start: DeckSlotName, end: DeckSlotName) -> bool:
    has_tc = any(
        m.model == ModuleModel.THERMOCYCLER_MODULE_V1
        for m in self._state.modules_by_id.values()
    )
    ...

Copy link
Member Author

Choose a reason for hiding this comment

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

This logic requires access to both Modules and Labware. GeometryView already has both so it seems appropriate to keep it here. Even though should_dodge_thermocycler primarily depends on the presence of a module, it is a concern of how non-module labware are placed on deck so GeometryView does feel like the appropriate place, no?

@sanni-t sanni-t marked this pull request as ready for review November 30, 2021 21:20
@sanni-t sanni-t requested a review from a team as a code owner November 30, 2021 21:20
Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

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

Code LGTM if this works on a robot.

Comment on lines 382 to 390
slot_def = standard_deck_def["locations"]["orderedSlots"][1]
slot_pos = slot_def["position"]
expected_center = Point(
x=slot_pos[0] + slot_def["boundingBox"]["xDimension"] / 2,
y=slot_pos[1] + slot_def["boundingBox"]["yDimension"] / 2,
z=slot_pos[2] + slot_def["boundingBox"]["zDimension"] / 2,
)
result = subject.get_slot_center_position(DeckSlotName.SLOT_2)
assert result == expected_center
Copy link
Contributor

Choose a reason for hiding this comment

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

This test is mirroring the production code in how it calculates the center point. Should we hard-code absolute deck coordinates in this test, instead?

@@ -216,3 +232,34 @@ def get_tip_drop_location(
z=well_location.offset.z - tip_z_offset,
)
)

def should_dodge_thermocycler(
Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, I think I meant ModuleView. If the signature changes to

def should_dodge_thermocycler(
    self,
    start: DeckSlotName,
    end: DeckSlotName,
) -> bool:

...then ModuleView has everything it needs, and MotionView can call it by calling self._geometry.get_ancestor_slot_name


Another idea that does not involve moving this function would be to have it return the a list of extra_waypoints directly (or an empty list), simplifying the logic in MotionView

Comment on lines 252 to 254
if self._modules.get_all() and ModuleModel.THERMOCYCLER_MODULE_V1 in [
mod.model for mod in self._modules.get_all()
]:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the multiple calls to get_all? Could we create ModuleView.has_thermocycler or something instead? This suggestion is moot if should_dodge_thermocycler moves to ModuleView

@@ -188,6 +188,8 @@ class WaypointSpec:
labware_z: Optional[float] = None
all_labware_z: Optional[float] = None
max_travel_z: float = 50
should_dodge_thermocycler: bool = False
extra_waypoints: Sequence[Tuple[float, float]] = ()


# TODO(mc, 2021-01-14): these tests probably need to be rethought; this fixture
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for leaving these tests in such an awful state...

@@ -104,6 +105,16 @@ def get_movement_waypoints(
else:
move_type = MoveType.GENERAL_ARC
min_travel_z = self._geometry.get_all_labware_highest_z()
if location is not None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we somehow encode this branch logic into should_dodge_thermocycler? Perhaps by making the starting slot/labware argument optional?

Copy link
Member Author

@sanni-t sanni-t Nov 30, 2021

Choose a reason for hiding this comment

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

How would that relate to the bad pairs then? nvm, I misunderstood. Sure, that won't be too bad

Copy link
Member Author

@sanni-t sanni-t Dec 1, 2021

Choose a reason for hiding this comment

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

Decided to leave this as is. If location is None then location.labware_id doesn't exist. So we have to check it here anyway.

@mcous
Copy link
Contributor

mcous commented Nov 30, 2021

Code looks pretty good! Will approve once tested on a real robot (lmk if that's already happened)

@sanni-t
Copy link
Member Author

sanni-t commented Nov 30, 2021

Code looks pretty good! Will approve once tested on a real robot (lmk if that's already happened)

Yep, tested on a robot and verified that pipette moves through slot 5 when thermocycler is loaded while moves directly when there's no thermocycler.

Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

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

🌡️

@sanni-t sanni-t merged commit a0e5924 into edge Dec 2, 2021
@nusrat813 nusrat813 self-requested a review December 2, 2021 16:55
Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

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

I know this is already merged but just chiming in to say I like where this ended up 👍

@sanni-t sanni-t deleted the moves_dodge_thermocycler branch December 2, 2021 19:14
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