Skip to content

Commit

Permalink
addition of multiple partial tip pickup test
Browse files Browse the repository at this point in the history
  • Loading branch information
CaseyBatten committed Mar 11, 2024
1 parent 09ab2bc commit 25a635c
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 9 deletions.
25 changes: 16 additions & 9 deletions api/src/opentrons/protocol_engine/state/tips.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tip state tracking."""
from dataclasses import dataclass
from enum import Enum
from typing import Dict, Optional, List
from typing import Dict, Optional, List, Union

from .abstract_store import HasState, HandlesActions
from ..actions import (
Expand Down Expand Up @@ -205,7 +205,7 @@ def _identify_tip_cluster(
else:
return None
elif entry_well == "A12" or entry_well == "H12":
if critical_column + i <= len(columns):
if critical_column + i < len(columns):
column = columns[critical_column + i]
else:
return None
Expand All @@ -220,7 +220,7 @@ def _identify_tip_cluster(
else:
return None
elif entry_well == "H1" or entry_well == "H12":
if critical_row + j <= len(column):
if critical_row + j < len(column):
well = column[critical_row + j]
else:
return None
Expand All @@ -233,7 +233,7 @@ def _identify_tip_cluster(

def _validate_tip_cluster(
active_columns: int, active_rows: int, tip_cluster: List[str]
) -> Optional[str]:
) -> Union[str, int, None]:
if not any(wells[well] == TipRackWellState.USED for well in tip_cluster):
return tip_cluster[0]
elif all(wells[well] == TipRackWellState.USED for well in tip_cluster):
Expand Down Expand Up @@ -261,9 +261,8 @@ def _validate_tip_cluster(
):
return None
else:
raise KeyError(
f"Tiprack {labware_id} has no valid tip selection for current Nozzle Configuration."
)
# Tiprack has no valid tip selection, cannot progress
return -1

# Search through the tiprack beginning at A1
def _cluster_search_A1(active_columns: int, active_rows: int) -> Optional[str]:
Expand All @@ -280,6 +279,8 @@ def _cluster_search_A1(active_columns: int, active_rows: int) -> Optional[str]:
)
if isinstance(result, str):
return result
elif isinstance(result, int) and result == -1:
return None
if critical_row + active_rows < len(columns[0]):
critical_row = critical_row + active_rows
else:
Expand All @@ -302,6 +303,8 @@ def _cluster_search_A12(active_columns: int, active_rows: int) -> Optional[str]:
)
if isinstance(result, str):
return result
elif isinstance(result, int) and result == -1:
return None
if critical_row + active_rows < len(columns[0]):
critical_row = critical_row + active_rows
else:
Expand All @@ -324,7 +327,9 @@ def _cluster_search_H1(active_columns: int, active_rows: int) -> Optional[str]:
)
if isinstance(result, str):
return result
if critical_row - active_rows > 0:
elif isinstance(result, int) and result == -1:
return None
if critical_row - active_rows >= 0:
critical_row = critical_row - active_rows
else:
critical_column = critical_column + 1
Expand All @@ -346,7 +351,9 @@ def _cluster_search_H12(active_columns: int, active_rows: int) -> Optional[str]:
)
if isinstance(result, str):
return result
if critical_row - active_rows > 0:
elif isinstance(result, int) and result == -1:
return None
if critical_row - active_rows >= 0:
critical_row = critical_row - active_rows
else:
critical_column = critical_column - 1
Expand Down
111 changes: 111 additions & 0 deletions api/tests/opentrons/protocol_engine/state/test_tip_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,3 +1029,114 @@ def test_next_tip_uses_active_channels(
nozzle_map=None,
)
assert result == "A2"


def test_next_tip_automatic_tip_tracking_with_partial_configurations(
subject: TipStore,
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
load_labware_command: commands.LoadLabware,
pick_up_tip_command: commands.PickUpTip,
) -> None:
"""Test tip tracking logic using multiple pipette configurations."""
# Load labware
subject.handle_action(
actions.UpdateCommandAction(private_result=None, command=load_labware_command)
)

# Load pipette
load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg]
result=commands.LoadPipetteResult(pipetteId="pipette-id")
)
load_pipette_private_result = commands.LoadPipettePrivateResult(
pipette_id="pipette-id",
serial_number="pipette-serial",
config=LoadedStaticPipetteData(
channels=96,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96),
back_left_corner_offset=Point(x=1, y=2, z=3),
front_right_corner_offset=Point(x=4, y=5, z=6),
),
)
subject.handle_action(
actions.UpdateCommandAction(
private_result=load_pipette_private_result, command=load_pipette_command
)
)

def _assert_and_pickup(well: str, nozzle_map: NozzleMap) -> None:
result = TipView(subject.state).get_next_tip(
labware_id="cool-labware",
num_tips=0,
starting_tip_name=None,
nozzle_map=nozzle_map,
)
assert result == well

pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg]
params=commands.PickUpTipParams.construct(
pipetteId="pipette-id",
labwareId="cool-labware",
wellName=result,
),
result=commands.PickUpTipResult.construct(
position=DeckPoint(x=0, y=0, z=0), tipLength=1.23
),
)

subject.handle_action(
actions.UpdateCommandAction(private_result=None, command=pick_up_tip)
)

# Configure nozzle for partial configurations
configure_nozzle_layout_cmd = commands.ConfigureNozzleLayout.construct( # type: ignore[call-arg]
result=commands.ConfigureNozzleLayoutResult()
)

def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleMap:

configure_nozzle_private_result = commands.ConfigureNozzleLayoutPrivateResult(
pipette_id="pipette-id",
nozzle_map=NozzleMap.build(
physical_nozzles=NINETY_SIX_MAP,
physical_rows=NINETY_SIX_ROWS,
physical_columns=NINETY_SIX_COLS,
starting_nozzle=start,
back_left_nozzle=back_l,
front_right_nozzle=front_r,
),
)
subject.handle_action(
actions.UpdateCommandAction(
private_result=configure_nozzle_private_result,
command=configure_nozzle_layout_cmd,
)
)
return configure_nozzle_private_result.nozzle_map

map = _reconfigure_nozzle_layout("A1", "A1", "H10")
_assert_and_pickup("A3", map)
map = _reconfigure_nozzle_layout("A1", "A1", "F2")
_assert_and_pickup("C1", map)

# Configure to single tip pickups
map = _reconfigure_nozzle_layout("H12", "H12", "H12")
_assert_and_pickup("A1", map)
map = _reconfigure_nozzle_layout("H1", "H1", "H1")
_assert_and_pickup("A2", map)
map = _reconfigure_nozzle_layout("A12", "A12", "A12")
_assert_and_pickup("B1", map)
map = _reconfigure_nozzle_layout("A1", "A1", "A1")
_assert_and_pickup("B2", map)

0 comments on commit 25a635c

Please sign in to comment.