Skip to content

Commit

Permalink
refactor(api): slightly faster simulation (#9821)
Browse files Browse the repository at this point in the history
* cache thermocycler presense to avoid linear search

* cache check names

* comments. more caching.

* thermocycler_present attribute test and fix.

* constants.

* move lru_cache to ul_per_mm
  • Loading branch information
amitlissack authored Apr 6, 2022
1 parent cdf6c59 commit 3bffb41
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 5 deletions.
5 changes: 5 additions & 0 deletions api/src/opentrons/hardware_control/pipette.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import functools

""" Classes and functions for pipette state tracking
"""
from dataclasses import asdict, replace
Expand Down Expand Up @@ -287,6 +289,9 @@ def remove_tip(self) -> None:
def has_tip(self) -> bool:
return self._has_tip

# Cache max is chosen somewhat arbitrarily. With a float is input we don't
# want this to unbounded.
@functools.lru_cache(maxsize=100)
def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float:
sequence = self._config.ul_per_mm[action]
return pipette_config.piecewise_volume_conversion(ul, sequence)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from opentrons.hardware_control import NoTipAttachedError, TipAttachedError
from opentrons.hardware_control.dev_types import PipetteDict
from opentrons.hardware_control.types import HardwareAction
from opentrons.protocols.api_support.labware_like import LabwareLike
from opentrons.protocols.api_support.types import APIVersion
from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
from opentrons.protocols.api_support.labware_like import LabwareLike
from opentrons.protocols.api_support.util import FlowRates, PlungerSpeeds, Clearances
from opentrons.protocols.geometry import planning
from opentrons.protocols.context.instrument import AbstractInstrument
Expand Down
15 changes: 15 additions & 0 deletions api/src/opentrons/protocols/geometry/deck.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import functools
import logging
from collections import UserDict
from dataclasses import dataclass
Expand Down Expand Up @@ -57,6 +58,7 @@ def __init__(self, load_name: Optional[str] = None) -> None:
load_name = deck_type()
self._definition = load_deck(load_name, 2)
self._load_fixtures()
self._thermocycler_present = False

def _load_fixtures(self):
for f in self._definition["locations"]["fixtures"]:
Expand All @@ -67,6 +69,7 @@ def _load_fixtures(self):
self.__setitem__(slot_name, loaded_f)

@staticmethod
@functools.lru_cache(20)
def _assure_int(key: object) -> int:
if isinstance(key, str):
return int(key)
Expand Down Expand Up @@ -97,6 +100,10 @@ def __delitem__(self, key: types.DeckLocation) -> None:
self.data[checked_key] = None
if old:
self.recalculate_high_z()
# Update the thermocycler present member
self._thermocycler_present = any(
isinstance(item, ThermocyclerGeometry) for item in self.data.values()
)

def __setitem__(self, key: types.DeckLocation, val: DeckItem) -> None:
slot_key_int = self._check_name(key)
Expand Down Expand Up @@ -125,6 +132,9 @@ def __setitem__(self, key: types.DeckLocation, val: DeckItem) -> None:
)
self.data[slot_key_int] = val
self._highest_z = max(val.highest_z, self._highest_z)
self._thermocycler_present = any(
isinstance(item, ThermocyclerGeometry) for item in self.data.values()
)

def __contains__(self, key: object) -> bool:
try:
Expand Down Expand Up @@ -295,3 +305,8 @@ def get_item_covered_slot_keys(sk, i):
if item_slot_keys.issubset(covered_sks):
colliding_items.setdefault(sk, []).append(i)
return colliding_items

@property
def thermocycler_present(self) -> bool:
"""Is a thermocycler present on the deck."""
return self._thermocycler_present
7 changes: 3 additions & 4 deletions api/src/opentrons/protocols/geometry/planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from opentrons.protocols.api_support.labware_like import LabwareLike
from opentrons.protocols.geometry.deck import Deck
from opentrons.protocols.geometry.module_geometry import (
ThermocyclerGeometry,
ModuleGeometry,
)

Expand All @@ -34,7 +33,7 @@ def max_many(*args):
return functools.reduce(max, args[1:], args[0])


BAD_PAIRS = [
BAD_PAIRS = {
("1", "12"),
("12", "1"),
("4", "12"),
Expand All @@ -49,7 +48,7 @@ def max_many(*args):
("11", "4"),
("1", "11"),
("11", "1"),
]
}


def should_dodge_thermocycler(
Expand All @@ -61,7 +60,7 @@ def should_dodge_thermocycler(
Returns True if we need to dodge, False otherwise
"""
if any([isinstance(item, ThermocyclerGeometry) for item in deck.values()]):
if deck.thermocycler_present:
transit = (from_loc.labware.first_parent(), to_loc.labware.first_parent())
# mypy doesn't like this because transit could be none, but it's
# checked by value in BAD_PAIRS which has only strings
Expand Down
22 changes: 22 additions & 0 deletions api/tests/opentrons/protocols/geometry/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,25 @@ def test_get_non_fixture_slots():
deck[4] = trough

assert deck.get_non_fixture_slots() == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]


def test_thermocycler_present() -> None:
"""It should change when thermocycler is added/removed"""
deck = Deck()

# Empty deck. No thermocycler
assert not deck.thermocycler_present

# Add a thermocycler
deck[7] = module_geometry.load_module(
module_geometry.ThermocyclerModuleModel.THERMOCYCLER_V1, deck.position_for(7)
)
assert deck.thermocycler_present

# Add another labware
deck[4] = labware.load(trough_name, deck.position_for(4))
assert deck.thermocycler_present

# Remove thermocycler
del deck[7]
assert not deck.thermocycler_present

0 comments on commit 3bffb41

Please sign in to comment.