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(api): slightly faster simulation #9821

Merged
merged 6 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
amitlissack marked this conversation as resolved.
Show resolved Hide resolved
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()]):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is called in each plan_moves which is called in each move_to. A needless search for a value that can be cached.

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