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

feat(api): inner well geometry unit tests #17082

Draft
wants to merge 15 commits into
base: edge
Choose a base branch
from
57 changes: 13 additions & 44 deletions api/src/opentrons/protocol_engine/state/frustum_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,12 @@ def _circular_frustum_polynomial_roots(


def _volume_from_height_circular(
target_height: float,
total_frustum_height: float,
bottom_radius: float,
top_radius: float,
target_height: float, segment: ConicalFrustum
) -> float:
"""Find the volume given a height within a circular frustum."""
a, b, c = _circular_frustum_polynomial_roots(
bottom_radius=bottom_radius,
top_radius=top_radius,
total_frustum_height=total_frustum_height,
)
volume = a * (target_height**3) + b * (target_height**2) + c * target_height
return volume
heights = segment.height_to_volume_table.keys()
best_fit_height = min(heights, key=lambda x: abs(x - target_height))
return segment.height_to_volume_table[best_fit_height]


def _volume_from_height_rectangular(
Expand Down Expand Up @@ -138,26 +131,12 @@ def _volume_from_height_squared_cone(


def _height_from_volume_circular(
volume: float,
total_frustum_height: float,
bottom_radius: float,
top_radius: float,
target_volume: float, segment: ConicalFrustum
) -> float:
"""Find the height given a volume within a circular frustum."""
a, b, c = _circular_frustum_polynomial_roots(
bottom_radius=bottom_radius,
top_radius=top_radius,
total_frustum_height=total_frustum_height,
)
d = volume * -1
x_intercept_roots = (a, b, c, d)

height_from_volume_roots = roots(x_intercept_roots)
height = _reject_unacceptable_heights(
potential_heights=list(height_from_volume_roots),
max_height=total_frustum_height,
)
return height
"""Find the height given a volume within a squared cone segment."""
volumes = segment.volume_to_height_table.keys()
best_fit_volume = min(volumes, key=lambda x: abs(x - target_volume))
return segment.volume_to_height_table[best_fit_volume]


def _height_from_volume_rectangular(
Expand Down Expand Up @@ -243,9 +222,7 @@ def _get_segment_capacity(segment: WellSegment) -> float:
return (
_volume_from_height_circular(
target_height=section_height,
total_frustum_height=section_height,
bottom_radius=(segment.bottomDiameter / 2),
top_radius=(segment.topDiameter / 2),
segment=segment,
)
* segment.count
)
Expand Down Expand Up @@ -293,12 +270,7 @@ def height_at_volume_within_section(
radius_of_curvature=section.radiusOfCurvature,
)
case ConicalFrustum():
return _height_from_volume_circular(
volume=target_volume_relative,
top_radius=(section.bottomDiameter / 2),
bottom_radius=(section.topDiameter / 2),
total_frustum_height=section_height,
)
return _height_from_volume_circular(target_volume_relative, section)
case CuboidalFrustum():
return _height_from_volume_rectangular(
volume=target_volume_relative,
Expand Down Expand Up @@ -334,10 +306,7 @@ def volume_at_height_within_section(
case ConicalFrustum():
return (
_volume_from_height_circular(
target_height=target_height_relative,
total_frustum_height=section_height,
bottom_radius=(section.bottomDiameter / 2),
top_radius=(section.topDiameter / 2),
target_height=target_height_relative, segment=section
)
* section.count
)
Expand Down Expand Up @@ -427,7 +396,7 @@ def _find_height_in_partial_frustum(
if (
bottom_section_volume
< target_volume
< (bottom_section_volume + section_volume)
<= (bottom_section_volume + section_volume)
):
relative_target_volume = target_volume - bottom_section_volume
section_height = section.topHeight - section.bottomHeight
Expand Down
3 changes: 3 additions & 0 deletions api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,8 @@ def get_well_height(
) -> float:
"""Get the height of a specified well for a labware."""
well_def = self._labware.get_well_definition(labware_id, well_name)
# if labware_id == "axygen_1_reservoir_90ml":
# breakpoint()
return well_def.depth

def _get_highest_z_from_labware_data(self, lw_data: LoadedLabware) -> float:
Expand Down Expand Up @@ -1528,6 +1530,7 @@ def get_well_volume_at_height(
) -> float:
"""Convert well height to volume."""
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
# breakpoint()
return find_volume_at_well_height(
target_height=height, well_geometry=well_geometry
)
Expand Down
1 change: 0 additions & 1 deletion api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,6 @@ def get_well_definition(
will be used.
"""
definition = self.get_definition(labware_id)

if well_name is None:
well_name = definition.ordering[0][0]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
"""Arguments needed to test inner geometry.

Each labware has 2 nominal volumes calculated in solidworks.
- One is a nominal bottom volume, calculated some set distance from the bottom of the inside of the well.
- The other is a nominal top volume, calculated some set distance from the top of the inside of the well.
"""
labware_to_test = [
# "opentrons_96_aluminumblock_generic_pcr_strip_200ul",
# "opentrons_24_tuberack_generic_2ml_screwcap",
"appliedbiosystemsmicroamp_384_wellplate_40ul",
"biorad_384_wellplate_50ul",
"corning_96_wellplate_360ul_flat",
"corning_384_wellplate_112ul_flat",
"axygen_1_reservoir_90ml",
"biorad_96_wellplate_200ul_pcr",
"corning_48_wellplate_1.6ml_flat",
"corning_6_wellplate_16.8ml_flat",
"corning_24_wellplate_3.4ml_flat",
"corning_12_wellplate_6.9ml_flat",
"thermoscientificnunc_96_wellplate_1300ul",
"opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap",
"opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap",
"agilent_1_reservoir_290ml",
"usascientific_96_wellplate_2.4ml_deep",
"thermoscientificnunc_96_wellplate_2000ul",
"usascientific_12_reservoir_22ml",
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical-15mL-ONLY",
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical-50mL-ONLY",
"nest_96_wellplate_200ul_flat",
"nest_96_wellplate_100ul_pcr_full_skirt",
"opentrons_96_wellplate_200ul_pcr_full_skirt",
"opentrons_24_tuberack_nest_0.5ml_screwcap",
"nest_1_reservoir_195ml",
"nest_12_reservoir_15ml",
"opentrons_24_tuberack_nest_1.5ml_snapcap",
"nest_96_wellplate_2ml_deep",
"opentrons_24_tuberack_nest_2ml_snapcap",
"nest_1_reservoir_290ml",
"opentrons_24_tuberack_nest_1.5ml_screwcap",
"opentrons_24_tuberack_nest_2ml_screwcap",
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical-50mL-ONLY",
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical-15mL-ONLY",
]
print(f"len of list = {len(labware_to_test)}")
print(f"len of set = {len(set(labware_to_test))}")


INNER_WELL_GEOMETRY_TEST_PARAMS = [
[
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical",
"conicalWell15mL",
16.7,
15546.9,
3.0,
5.0,
],
[
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical",
"conicalWell50mL",
111.2,
56110.3,
3.0,
5.0,
],
["opentrons_24_tuberack_nest_2ml_screwcap", "conicalWell", 66.6, 2104.9, 3.0, 3.0],
[
"opentrons_24_tuberack_nest_1.5ml_screwcap",
"conicalWell",
19.5,
1750.8,
3.0,
3.0,
],
# weird one
["nest_1_reservoir_290ml", "cuboidalWell", 16570.380, 271690.520, 3.0, 3.0],
# failing isclose
["opentrons_24_tuberack_nest_2ml_snapcap", "conicalWell", 69.62, 2148.5, 3.0, 3.0],
["nest_96_wellplate_2ml_deep", "cuboidalWell", 118.3, 2060.4, 3.0, 3.0],
["opentrons_24_tuberack_nest_1.5ml_snapcap", "conicalWell", 27.8, 1682.3, 3.0, 3.0],
["nest_12_reservoir_15ml", "cuboidalWell", 1219.0, 13236.1, 3.0, 3.0],
# weird one
["nest_1_reservoir_195ml", "cuboidalWell", 14034.2, 172301.9, 3.0, 3.0],
[
"opentrons_24_tuberack_nest_0.5ml_screwcap",
"conicalWell",
21.95,
795.4,
3.0,
3.0,
],
[
"opentrons_96_wellplate_200ul_pcr_full_skirt",
"conicalWell",
14.3,
150.2,
3.0,
3.0,
],
["nest_96_wellplate_100ul_pcr_full_skirt", "conicalWell", 15.5, 150.8, 3.0, 3.0],
["nest_96_wellplate_200ul_flat", "conicalWell", 96.3, 259.8, 3.0, 3.0],
[
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical",
"50mlconicalWell",
163.9,
57720.5,
3.0,
3.0,
],
[
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical",
"15mlconicalWell",
40.8,
15956.6,
3.0,
3.0,
],
# failed - squared cone
["usascientific_12_reservoir_22ml", "cuboidalWell", 61.6, 21111.5, 3.0, 3.0],
["thermoscientificnunc_96_wellplate_2000ul", "conicalWell", 73.5, 1768.0, 3.0, 3.0],
# skipped usascientific_96_wellplate_2.4ml_deep since it doesnt have a definition yet
# weird one
["agilent_1_reservoir_290ml", "cuboidalWell", 15652.9, 268813.8, 3.0, 3.0],
[
"opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap",
"conicalWell",
25.8,
1576.1,
3.0,
3.0,
],
["thermoscientificnunc_96_wellplate_1300ul", "conicalWell", 73.5, 1155.1, 3.0, 3.0],
["corning_12_wellplate_6.9ml_flat", "conicalWell", 1156.3, 5654.8, 3.0, 3.0],
["corning_24_wellplate_3.4ml_flat", "conicalWell", 579.0, 2853.4, 3.0, 3.0],
["corning_6_wellplate_16.8ml_flat", "conicalWell", 2862.1, 13901.9, 3.0, 3.0],
["corning_48_wellplate_1.6ml_flat", "conicalWell", 268.9, 1327.0, 3.0, 3.0],
["biorad_96_wellplate_200ul_pcr", "conicalWell", 17.9, 161.2, 3.0, 3.0],
["axygen_1_reservoir_90ml", "cuboidalWell", 22373.4, 70450.6, 3.0, 3.0],
# fails- this one thought it was a rectangle ?
["corning_384_wellplate_112ul_flat", "flatWell", 22.4, 77.4, 2.88, 3.0],
["corning_96_wellplate_360ul_flat", "conicalWell", 97.2, 257.1, 3.0, 3.0],
["biorad_384_wellplate_50ul", "conicalWell", 7.7, 27.8, 3.0, 3.0],
[
"appliedbiosystemsmicroamp_384_wellplate_40ul",
"conicalWell",
7.44,
26.19,
3.0,
3.0,
],
[
"opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap",
"conicalWell",
60.940,
2163.980,
3.0,
3.0
],
[
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical",
"conicalWell15mL",
16.690,
15546.930,
3.0,
5.0
],
[
"opentrons_10_tuberack_nest_4x50ml_6x15ml_conical",
"conicalWell50mL",
111.200,
56110.279,
3.0,
5.0
],
[
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical",
"15mlconicalWell",
40.830,
15956.600,
3.0,
3.0
],
[
"opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical",
"50mlconicalWell",
163.860,
57720.510,
3.0,
3.0
],
["usascientific_96_wellplate_2.4ml_deep", "cuboidalWell", 72.22, 2241.36, 3.0, 3.0],
]
print(f"len of existing labware = {len(INNER_WELL_GEOMETRY_TEST_PARAMS)}")
existing_names = [param[0] for param in INNER_WELL_GEOMETRY_TEST_PARAMS]
diff = set(labware_to_test).difference(set(existing_names))
print("\nlabware missing:")
for missing in diff:
print(missing)

gotta_add = [
# "opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap",
# check if the test is looking for the right name below
# "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical-15mL-ONLY",
# "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical-50mL-ONLY",
# "opentrons_24_tuberack_generic_2ml_screwcap", # doesnt have volume estimates
# check if the test is looking for the right name below
# "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical-50mL-ONLY",
# "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical-15mL-ONLY",
# "usascientific_96_wellplate_2.4ml_deep" # definition isnt in yet
]
failing = [
"corning_384_wellplate_112ul_flat",
"agilent_1_reservoir_290ml",
"usascientific_12_reservoir_22ml",
"nest_1_reservoir_195ml",
"opentrons_24_tuberack_nest_2ml_snapcap",
"nest_1_reservoir_290ml",
]
Loading
Loading