diff --git a/pylabrobot/resources/agilent/plates.py b/pylabrobot/resources/agilent/plates.py index 55685de6590..3f25036ef43 100644 --- a/pylabrobot/resources/agilent/plates.py +++ b/pylabrobot/resources/agilent/plates.py @@ -1,12 +1,67 @@ from pylabrobot.resources.plate import Plate from pylabrobot.resources.utils import create_ordered_items_2d from pylabrobot.resources.well import Well, WellBottomType +from pylabrobot.resources.height_volume_functions import ( + calculate_liquid_height_container_1segment_round_vbottom, + calculate_liquid_volume_container_1segment_round_vbottom, +) -def agilent_96_wellplate_150uL_Ub(name: str) -> Plate: +def _compute_volume_from_height_agilent_96_wellplate_150uL_Vb( + h: float, +): + # well depth: 12.5 mm + # well diameter at the top: 6.0 mm + if h > 12.5: + raise ValueError(f"Height {h} is too large for agilent_96_wellplate_150uL_Vb") + return calculate_liquid_volume_container_1segment_round_vbottom( + d=6.4, h_pyramid=12.5, liquid_height=h + ) + + +def _compute_height_from_volume_agilent_96_wellplate_150uL_Vb( + v: float, +): + if v > 150: + raise ValueError(f"Volume {v} is too large for agilent_96_wellplate_150uL_Vb") + return calculate_liquid_height_container_1segment_round_vbottom( + d=6.4, h_pyramid=12.5, liquid_volume=v + ) + + +def agilent_96_wellplate_150uL_Vb(name: str) -> Plate: """ Part number: 5042-8502 + Well Number | 96 + Well Form | Round + Well Diameter (A) | 6.4 mm + Well Bottom Shape | Conical + Well Volume | 150 µL + Well Working Volume | 120 µL + Well Depth (B) | 14.0 mm + Plate Height (C) | 15.9 mm + Plate Width (D) | 85.5 mm + Plate Length (E) | 127.8 mm + Row Distance (F) | 9 mm + Column Distance (G) | 9 mm + Row Offset (H) | 11.2 mm + Column Offset (I) | 14.4 mm + Frame Footprint | SBS + Frame Numbering | Alphanumeric + Skirted | Yes + Color | Clear + Material | Well: polypropylene + | Frame: polycarbonate + Temperature Range | -80 to 120 °C + Autoclavable | Yes + Sterile | Nonsterile + Stackable | Yes + Pack Size | 25, p/n 5042-8502 + Compatible Closing Mat | p/n 5067-5154 (not recommended for + | chromatography autosamplers) + Agilent Instrument Definition| Not applicable + https://www.agilent.com/cs/library/datasheets/public/ds-well-plate-specifications-5994-6035en-agilent.pdf """ @@ -19,6 +74,8 @@ def agilent_96_wellplate_150uL_Ub(name: str) -> Plate: "bottom_type": WellBottomType.U, "material_z_thickness": 0.88, # measured using z-probing "max_volume": 150, + "compute_volume_from_height": _compute_volume_from_height_agilent_96_wellplate_150uL_Vb, + "compute_height_from_volume": _compute_height_from_volume_agilent_96_wellplate_150uL_Vb, } return Plate( @@ -27,14 +84,14 @@ def agilent_96_wellplate_150uL_Ub(name: str) -> Plate: size_y=85.5, # standard size_z=15.9, # from spec lid=None, - model=agilent_96_wellplate_150uL_Ub.__name__, + model=agilent_96_wellplate_150uL_Vb.__name__, ordered_items=create_ordered_items_2d( Well, num_items_x=12, num_items_y=8, dx=14.4 - diameter / 2, # from spec dy=11.2 - diameter / 2, # from spec - dz=16.0 - 14.0 - 0.88, # spec - spec - measured + dz=16.0 - 14.0 + 1, # spec - spec - measured manually item_dx=9.0, # standard item_dy=9.0, # standard **well_kwargs, diff --git a/pylabrobot/resources/height_volume_functions.py b/pylabrobot/resources/height_volume_functions.py index ee5e53f0cc0..46c49ca04e0 100644 --- a/pylabrobot/resources/height_volume_functions.py +++ b/pylabrobot/resources/height_volume_functions.py @@ -434,6 +434,33 @@ def calculate_liquid_height_container_1segment_round_fbottom( return liquid_height +def calculate_liquid_height_container_1segment_round_vbottom( + d: float, h_pyramid: float, liquid_volume: float +) -> float: + """Calculate the height of liquid in a container with a cylindrical pyramid (cone) shape. + + Parameters: + d: The diameter of the base of the cone in mm. + h_pyramid: The height of the cone in mm. + liquid_volume: The volume of the liquid in the container in cubic millimeters. + + Returns: + The height of the liquid in the container in mm. + """ + r = d / 2 + max_volume = (1 / 3) * math.pi * r**2 * h_pyramid + + if liquid_volume > max_volume: + raise ValueError( + """WARNING: Liquid overflow detected; + check your labware definition and/or that you are using the right labware.""" + ) + + scale_factor = (liquid_volume / max_volume) ** (1 / 3) + liquid_height = scale_factor * h_pyramid + return liquid_height + + def calculate_liquid_volume_container_1segment_round_fbottom( d: float, h_cylinder: float, liquid_height: float ) -> float: @@ -458,6 +485,33 @@ def calculate_liquid_volume_container_1segment_round_fbottom( return cylinder_liquid_volume +def calculate_liquid_volume_container_1segment_round_vbottom( + d: float, h_pyramid: float, liquid_height: float +) -> float: + """Calculate the volume of liquid in a container with a cylindrical pyramid (cone) shape. + + Parameters: + d: The diameter of the base of the cone in mm. + h_pyramid: The height of the cone in mm. + liquid_height: The height of the liquid in the container in mm. + + Returns: + The volume of the liquid in cubic millimeters. + """ + r = d / 2 + if liquid_height > h_pyramid: + raise ValueError( + """WARNING: Liquid overflow detected; + check your labware definition and/or that you are using the right labware.""" + ) + + scale_factor = liquid_height / h_pyramid + liquid_volume = (1 / 3) * math.pi * r**2 * h_pyramid * (scale_factor**3) + return liquid_volume + + + + ### Example of usage using a lambda function: # def Rectangular_Reservoir(name: str) -> Plate: # """ An 8 well reservoir with a 30mL volume. """