Skip to content

[SCFD-3113] Added validation that vector should not contain None components #515

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

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
75 changes: 51 additions & 24 deletions flow360/component/simulation/unit_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,25 +157,39 @@ def _has_dimensions(quant, dim):
return arg_dim == dim


def _unit_object_parser(value, unyt_types: List[type]):
def _unit_object_parser(value, unyt_types: List[type], **kwargs):
"""
Parses {'value': value, 'units': units}, into unyt_type object : unyt.unyt_quantity, unyt.unyt_array
"""
if isinstance(value, dict) and "units" in value:
if "value" in value:
for unyt_type in unyt_types:
try:
return unyt_type(value["value"], value["units"], dtype=np.float64)
except u.exceptions.UnitParseError:
pass
except RuntimeError:
pass
except KeyError:
pass
else:
raise TypeError(
f"Dimensioned type instance {value} expects a 'value' field which was not given"
)
if isinstance(value, dict) is False or "units" not in value:
return value
if "value" not in value:
raise TypeError(
f"Dimensioned type instance {value} expects a 'value' field which was not given"
)
for unyt_type in unyt_types:
try:
dimensioned_value = unyt_type(value["value"], value["units"], dtype=np.float64)
# Check for Nan. This usually is a result of None being in the input value.
# with dtype=np.float64, None will be converted to np.nan.
# We need (IIRC) np.float64 to ensure consistent hashing
if kwargs.get("allow_inf_nan", False) is False:
if np.ndim(dimensioned_value.value) == 0 and np.isnan(dimensioned_value.value):
raise ValueError("NaN or None found in input scalar which is not allowed.")
if np.ndim(dimensioned_value.value) > 0 and any(np.isnan(dimensioned_value.value)):
raise ValueError("NaN or None found in input array which is not allowed.")
return dimensioned_value
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we put this logic to a separate validator and keep this validator only a parser? See the logic of _VectorType.validate() it checks step by step all conditions, it is easier to follow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

except u.exceptions.UnitParseError:
pass
except RuntimeError as e:
if str(e) == "unyt_quantity values must be numeric":
# Have to catch here otherwise there will
# be "No class found for unit_name" which is confusing.
raise ValueError(
"The numerical part of the input dimensioned value is incorrect."
) from e
except KeyError:
pass
return value


Expand Down Expand Up @@ -285,13 +299,16 @@ class _DimensionedType(metaclass=ABCMeta):
has_defaults = True

@classmethod
def validate(cls, value):
# pylint: disable=unused-argument
def validate(cls, value, *args, **kwargs):
"""
Validator for value
"""

try:
value = _unit_object_parser(value, [u.unyt_quantity, _Flow360BaseUnit.factory])
value = _unit_object_parser(
value, [u.unyt_quantity, _Flow360BaseUnit.factory], **kwargs
)
value = _is_unit_validator(value)
if cls.has_defaults:
value = _unit_inference_validator(value, cls.dim_name)
Expand Down Expand Up @@ -358,12 +375,18 @@ def get_class_object(cls, dim_type, **kwargs):
"""Get a dynamically created metaclass representing the constraint"""

class _ConType(pd.BaseModel):
value: Annotated[float, annotated_types.Interval(**kwargs)]
kwargs.pop("allow_inf_nan", None)
value: Annotated[
float,
annotated_types.Interval(
**{k: v for k, v in kwargs.items() if k != "allow_inf_nan"}
),
]

def validate(con_cls, value, *args):
def validate(con_cls, value, *args, **kwargs):
"""Additional validator for value"""
try:
dimensioned_value = dim_type.validate(value)
dimensioned_value = dim_type.validate(value, kwargs)

# Workaround to run annotated validation for numeric value of field
_ = _ConType(value=dimensioned_value.value)
Expand Down Expand Up @@ -411,7 +434,9 @@ def Constrained(cls, gt=None, ge=None, lt=None, le=None, allow_inf_nan=False):
"""
Utility method to generate a dimensioned type with constraints based on the pydantic confloat
"""
return cls._Constrained.get_class_object(cls, gt=gt, ge=ge, lt=lt, le=le)
return cls._Constrained.get_class_object(
cls, gt=gt, ge=ge, lt=lt, le=le, allow_inf_nan=allow_inf_nan
)

# pylint: disable=invalid-name
@classproperty
Expand Down Expand Up @@ -472,10 +497,12 @@ def __get_pydantic_json_schema__(

return schema

def validate(vec_cls, value, *args):
def validate(vec_cls, value, *args, **kwargs):
"""additional validator for value"""
try:
value = _unit_object_parser(value, [u.unyt_array, _Flow360BaseUnit.factory])
value = _unit_object_parser(
value, [u.unyt_array, _Flow360BaseUnit.factory], **kwargs
)
value = _is_unit_validator(value)

is_collection = isinstance(value, Collection) or (
Expand Down
11 changes: 11 additions & 0 deletions tests/simulation/framework/test_unit_system_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,17 @@ def test_unit_system():
omega={"value": [1, 1, 1], "units": "rad/s"},
)

with pytest.raises(
pd.ValidationError,
match=r"Value error, NaN or None found in input array which is not allowed.",
):
data = VectorDataWithUnits(
pt=None,
vec={"value": [1, 1, None], "units": "N"},
ax={"value": [0, 0, 1], "units": "m"},
omega={"value": [1, 1, 1], "units": "rad/s"},
)

with u.SI_unit_system:
# Note that for union types the first element of union that passes validation is inferred!
data = VectorDataWithUnits(
Expand Down
Loading