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): proper LiquidHandlingPropertyByVolume class and validation for setting liquid class properties #16725

Open
wants to merge 5 commits into
base: edge
Choose a base branch
from

Conversation

jbleon95
Copy link
Contributor

@jbleon95 jbleon95 commented Nov 7, 2024

Overview

Closes AUTH-837

This PR adds a proper LiquidHandlingPropertyByVolume class, replacing the previous dictionary of string keys to float values that was the literal representation of the liquid class schema. Now it uses that dictionary to make a default value as well as functions to take any volume and interpolate the proper volume based on that. Methods are also there for setting new points for volume and deleting existing points.

The other part of this PR is adding validation for the different settable values in liquid class that would require validating before hand. This includes boolean values, floating points, positive ints and floats, and xyz coordinates.

Changelog

  • added LiquidHandlingPropertyByVolume class to handle interpolation for these properties and adding/deleting points
  • added validators for all settable liquid class properties that need them for clear errors

Review requests

Should all the new validators live in the general PAPI validation.py class or should they exist elsewhere?

Risk assessment

Low.

@jbleon95 jbleon95 requested a review from a team as a code owner November 7, 2024 19:34
Copy link

codecov bot commented Nov 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.43%. Comparing base (094be6e) to head (55f936d).
Report is 51 commits behind head on edge.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             edge   #16725   +/-   ##
=======================================
  Coverage   92.43%   92.43%           
=======================================
  Files          77       77           
  Lines        1283     1283           
=======================================
  Hits         1186     1186           
  Misses         97       97           
Flag Coverage Δ
g-code-testing 92.43% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Copy link
Member

@sanni-t sanni-t left a comment

Choose a reason for hiding this comment

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

Looks mostly good! Just a few suggestions. The only thing that sticks out to me is the properties_by_volume property.

# numpy interp does not automatically sort the points used for interpolation, so
# here we are sorting them by the keys (volume) and then using zip to get two
# tuples in the correct order
sorted_volumes, sorted_values = zip(*sorted(self._properties_by_volume.items()))
Copy link
Member

Choose a reason for hiding this comment

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

What if we we make a separate function for fetching the sorted dict and add caching to it so that this function doesn't end up doing sorts again and again on the same set of data? Slight efficiency improvement

# here we are sorting them by the keys (volume) and then using zip to get two
# tuples in the correct order
sorted_volumes, sorted_values = zip(*sorted(self._properties_by_volume.items()))
return float(interp(validated_volume, sorted_volumes, sorted_values))
Copy link
Member

Choose a reason for hiding this comment

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

Also, not sure if the interpolator goes through the trouble of actually doing the interpolation even for values that are readily available in the dictionary. If it does, then I think it'll be a bit better to first check if the volume exists in the dictionary and interpolate only if it doesn't exist.

def validate_coordinates(value: Sequence[float]) -> Tuple[float, float, float]:
"""Ensure value is a valid sequence of 3 floats and return a tuple of 3 floats."""
if len(value) != 3:
raise ValueError("Coordinates must be a sequence of length 3")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
raise ValueError("Coordinates must be a sequence of length 3")
raise ValueError("Coordinates must be a sequence of exactly three numbers")

maybe?


@pytest.mark.parametrize("value", [1.0, -1.0, -1])
def test_ensure_positive_int_raises(value: Union[int, float]) -> None:
"""It should return a positive integer."""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""It should return a positive integer."""
"""It should raise if value is not a positive integer."""


@pytest.mark.parametrize("value", [-1, -1.0, float("inf"), float("-inf"), float("nan")])
def test_ensure_positive_float_raises(value: Union[int, float]) -> None:
"""It should return a positive float."""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""It should return a positive float."""
"""It should raise if value is not a positive float."""


@pytest.mark.parametrize("value", [[1, 2.0], [1, 2.0, 3.3, 4.2], ["1", 2, 3]])
def test_validate_coordinates_raises(value: Sequence[Union[int, float, str]]) -> None:
"""It should return a positive integer."""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""It should return a positive integer."""
"""It should raise if value is not a positive integer."""

return self._default

@property
def properties_by_volume(self) -> Dict[Union[float, str], float]:
Copy link
Member

Choose a reason for hiding this comment

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

Hmm.. I wonder if there's a more intuitive way of representing this. Ideally would be good to avoid a call like aspirate.flow_rate_by_volume.properties_by_volume.
I guess maybe calling this property/ function something like get_dict() might also be an option



class LiquidHandlingPropertyByVolume:
def __init__(self, properties_by_volume: Dict[str, float]) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Explain? The argument is Dict[str, float], but some of the dict keys are strings and some are numbers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants