Skip to content
Open
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
9 changes: 8 additions & 1 deletion flow360/component/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
SurfaceMeshFile,
VolumeMeshFile,
formatting_validation_errors,
formatting_validation_warnings,
get_short_asset_id,
parse_datetime,
wrapstring,
Expand Down Expand Up @@ -1510,12 +1511,18 @@ def _run(
use_geometry_AI=use_geometry_AI,
)

params, errors = validate_params_with_context(
params, errors, warnings = validate_params_with_context(
params=params,
root_item_type=self.metadata.root_item_type.value,
up_to=target._cloud_resource_type_name,
)

if warnings:
log.warning(
f"Validation warnings found during local validation: "
f"{formatting_validation_warnings(warnings=warnings)}"
)

if errors is not None:
log.error(
f"Validation error found during local validation: {formatting_validation_errors(errors=errors)}"
Expand Down
8 changes: 5 additions & 3 deletions flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,14 +667,16 @@ def validate_params_with_context(params, root_item_type, up_to):
root_item_type=root_item_type, up_to=up_to
)

params, errors, _ = services.validate_model(
params_as_dict=params.model_dump(mode="json", exclude_none=True),
params_as_dict = params.model_dump(mode="json", exclude_none=True)

params, errors, warnings = services.validate_model(
params_as_dict=params_as_dict,
validated_by=services.ValidationCalledBy.LOCAL,
root_item_type=root_item_type,
validation_level=validation_level,
)

return params, errors
return params, errors, warnings


def _get_imported_surface_file_names(params, basename_only=False):
Expand Down
8 changes: 7 additions & 1 deletion flow360/component/simulation/meshing_param/volume_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.validation.validation_context import (
ParamsValidationInfo,
add_validation_warning,
contextual_field_validator,
contextual_model_validator,
get_validation_info,
Expand Down Expand Up @@ -468,6 +469,7 @@ def _validate_domain_type_bbox(cls, value):
if (
value not in ("half_body_positive_y", "half_body_negative_y")
or validation_info.global_bounding_box is None
or validation_info.planar_face_tolerance is None
):
return value

Expand Down Expand Up @@ -500,11 +502,15 @@ def _validate_domain_type_bbox(cls, value):
if y_max <= tolerance:
return value

raise ValueError(
message = (
f"The model does not cross the symmetry plane (Y=0) with tolerance {tolerance:.2g}. "
f"Model Y range: [{y_min:.2g}, {y_max:.2g}]. "
"Please check if `domain_type` is set correctly."
)
if getattr(validation_info, "entity_transformation_detected", False):
add_validation_warning(message)
return value
raise ValueError(message)


class AutomatedFarfield(_FarfieldBase):
Expand Down
27 changes: 21 additions & 6 deletions flow360/component/simulation/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@
import json
import os
from enum import Enum
from typing import Any, Collection, Dict, Iterable, Literal, Optional, Tuple, Union
from typing import (
Any,
Collection,
Dict,
Iterable,
List,
Literal,
Optional,
Tuple,
Union,
)

import pydantic as pd
from pydantic_core import ErrorDetails
Expand Down Expand Up @@ -428,7 +438,7 @@ def validate_model( # pylint: disable=too-many-locals
validation_level: Union[
Literal["SurfaceMesh", "VolumeMesh", "Case", "All"], list, None
] = ALL, # Fix implicit string concatenation
) -> Tuple[Optional[SimulationParams], Optional[list], Optional[list]]:
) -> Tuple[Optional[SimulationParams], Optional[list], List[Dict[str, Any]]]:
"""
Validate a params dict against the pydantic model.

Expand All @@ -449,8 +459,8 @@ def validate_model( # pylint: disable=too-many-locals
The validated parameters if successful, otherwise None.
validation_errors : list or None
A list of validation errors if any occurred.
validation_warnings : list or None
A list of validation warnings if any occurred.
validation_warnings : list
A list of validation warnings (empty list if no warnings were recorded).
"""

def handle_multi_constructor_model(params_as_dict: dict) -> dict:
Expand Down Expand Up @@ -509,8 +519,9 @@ def dict_preprocessing(params_as_dict: dict) -> dict:
return params_as_dict

validation_errors = None
validation_warnings = None
validation_warnings: List[Dict[str, Any]] = []
validated_param = None
validation_context: Optional[ValidationContext] = None

params_as_dict = clean_unrelated_setting_from_params_dict(params_as_dict, root_item_type)

Expand Down Expand Up @@ -541,7 +552,8 @@ def dict_preprocessing(params_as_dict: dict) -> dict:
with ValidationContext(
levels=validation_levels_to_use,
info=validation_info,
):
) as context:
validation_context = context
unit_system = updated_param_as_dict.get("unit_system")
with UnitSystem.from_dict( # pylint: disable=not-context-manager
verbose=False, **unit_system
Expand All @@ -562,6 +574,9 @@ def dict_preprocessing(params_as_dict: dict) -> dict:
validation_errors = err.errors()
except Exception as err: # pylint: disable=broad-exception-caught
validation_errors = handle_generic_exception(err, validation_errors)
finally:
if validation_context is not None:
validation_warnings = list(validation_context.validation_warnings)

if validation_errors is not None:
validation_errors = validate_error_locations(validation_errors, params_as_dict)
Expand Down
39 changes: 39 additions & 0 deletions flow360/component/simulation/validation/validation_context.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pylint: disable=too-many-lines
"""
Module for validation context handling in the simulation component of Flow360.

Expand Down Expand Up @@ -104,6 +105,7 @@ def __init__(self, param_as_dict: dict):

_validation_level_ctx = contextvars.ContextVar("validation_levels", default=None)
_validation_info_ctx = contextvars.ContextVar("validation_info", default=None)
_validation_warnings_ctx = contextvars.ContextVar("validation_warnings", default=None)


class ParamsValidationInfo: # pylint:disable=too-few-public-methods,too-many-instance-attributes
Expand Down Expand Up @@ -587,20 +589,25 @@ def __init__(self, levels: Union[str, List[str]], info: ParamsValidationInfo = N
):
self.levels = levels
self.level_token = None
self.validation_warnings = []
else:
raise ValueError(f"Invalid validation level: {levels}")

self.info = info
self.info_token = None
self.warnings_token = None

def __enter__(self):
self.level_token = _validation_level_ctx.set(self.levels)
self.info_token = _validation_info_ctx.set(self.info)
self.warnings_token = _validation_warnings_ctx.set(self.validation_warnings)
return self

def __exit__(self, exc_type, exc_val, exc_tb):
_validation_level_ctx.reset(self.level_token)
_validation_info_ctx.reset(self.info_token)
if self.warnings_token is not None:
_validation_warnings_ctx.reset(self.warnings_token)


def get_validation_levels() -> list:
Expand All @@ -623,6 +630,38 @@ def get_validation_info() -> ParamsValidationInfo:
return _validation_info_ctx.get()


def add_validation_warning(message: str) -> None:
"""
Append a validation warning message to the active ValidationContext.

Parameters
----------
message : str
Warning message to record. Converted to string if needed.

Notes
-----
No action is taken if there is no active ValidationContext.
"""
warnings_list = _validation_warnings_ctx.get()
if warnings_list is None:
return
message_str = str(message)
if any(
isinstance(existing, dict) and existing.get("msg") == message_str
for existing in warnings_list
):
return
warnings_list.append(
{
"loc": (),
"msg": message_str,
"type": "value_error",
"ctx": {},
}
)


# pylint: disable=invalid-name
def ContextField(
default=None, *, context: Literal["SurfaceMesh", "VolumeMesh", "Case"] = None, **kwargs
Expand Down
Loading
Loading