Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import abc
import inspect
from functools import wraps
from typing import Any, Callable, Literal, Optional, Union
from typing import Any, Callable, Literal, Optional, Union, get_args, get_origin

import pydantic as pd

Expand Down Expand Up @@ -174,6 +174,17 @@ def model_custom_constructor_parser(model_as_dict, global_vars):
"""Parse the dictionary, construct the object and return obj dict."""
constructor_name = model_as_dict.get("private_attribute_constructor", None)
if constructor_name is not None and constructor_name != "default":

def is_optional_argument(annotation) -> bool:
# Ensure the annotation has been parsed as the typing object
# Avoid the unnecessary use of from __future__ import annotations
assert not isinstance(
arg_obj.annotation, str
), "[Internal] Invalid string type annotation. Please check importing future."
if get_origin(annotation) is Union and type(None) in get_args(annotation):
return True
return False

model_cls = get_class_by_name(model_as_dict.get("type_name"), global_vars)
input_kwargs = model_as_dict.get("private_attribute_input_cache")

Expand All @@ -182,14 +193,20 @@ def model_custom_constructor_parser(model_as_dict, global_vars):
# Filter the input_kwargs using constructor signatures
# If the argument is not found in input_kwargs:
# 1. Error out if the argument is required
# 2. Use default value if the argument is optional
input_kwargs_filtered = {
arg_name: input_kwargs[arg_name]
for arg_name in constructor_args.keys()
if arg_name in input_kwargs.keys()
}
# 2. Use default value if available, else use None if `Optional`
input_kwargs_filtered = {}
for arg_name, arg_obj in constructor_args.items():
if arg_name in input_kwargs.keys():
input_kwargs_filtered[arg_name] = input_kwargs[arg_name]
elif (
is_optional_argument(arg_obj.annotation)
and arg_obj.default is inspect.Parameter.empty
):
input_kwargs_filtered[arg_name] = None
try:
model_dict = constructor(**input_kwargs_filtered).model_dump(exclude_none=True)
model_dict = constructor(**input_kwargs_filtered).model_dump(
mode="json", exclude_none=True
)
# Make sure we do not generate a new ID.
if "private_attribute_id" in model_as_dict:
model_dict["private_attribute_id"] = model_as_dict["private_attribute_id"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Operating conditions for the simulation framework."""

from __future__ import annotations

from typing import Literal, Optional, Tuple, Union

import pydantic as pd
Expand Down Expand Up @@ -32,6 +30,7 @@
CaseField,
ConditionalField,
context_validator,
get_validation_info,
)
from flow360.log import log

Expand Down Expand Up @@ -380,9 +379,9 @@ def from_mach_reynolds(
cls,
mach: pd.PositiveFloat,
reynolds: pd.PositiveFloat,
project_length_unit: LengthType.Positive,
alpha: Optional[AngleType] = 0 * u.deg,
beta: Optional[AngleType] = 0 * u.deg,
project_length_unit: Optional[LengthType.Positive],
alpha: AngleType = 0 * u.deg,
beta: AngleType = 0 * u.deg,
temperature: AbsoluteTemperatureType = 288.15 * u.K,
reference_mach: Optional[pd.PositiveFloat] = None,
):
Expand Down Expand Up @@ -436,6 +435,12 @@ def from_mach_reynolds(
if temperature.units is u.K and temperature.value == 288.15:
log.info("Default value of 288.15 K will be used as temperature.")

if project_length_unit is None:
validation_info = get_validation_info()
if validation_info is None or validation_info.project_length_unit is None:
raise ValueError("Project length unit must be provided.")
project_length_unit = validation_info.project_length_unit

material = Air()

velocity = mach * material.get_speed_of_sound(temperature)
Expand Down
4 changes: 2 additions & 2 deletions flow360/component/simulation/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ def validate_model(
params_as_dict
)

updated_param_as_dict = parse_model_dict(updated_param_as_dict, globals())

additional_info = ParamsValidationInfo(param_as_dict=updated_param_as_dict)

with ValidationContext(levels=validation_levels_to_use, info=additional_info):
updated_param_as_dict = parse_model_dict(updated_param_as_dict, globals())
validated_param = SimulationParams(file_content=updated_param_as_dict)
except pd.ValidationError as err:
validation_errors = err.errors()
Expand Down
17 changes: 17 additions & 0 deletions flow360/component/simulation/validation/validation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

from pydantic import Field

from flow360.component.simulation.unit_system import LengthType

SURFACE_MESH = "SurfaceMesh"
VOLUME_MESH = "VolumeMesh"
CASE = "Case"
Expand Down Expand Up @@ -74,6 +76,7 @@ class ParamsValidationInfo: # pylint:disable=too-few-public-methods
"use_geometry_AI",
"using_liquid_as_material",
"time_stepping",
"project_length_unit",
]

@classmethod
Expand Down Expand Up @@ -126,6 +129,19 @@ def _get_time_stepping_(cls, param_as_dict: dict):
except KeyError:
return TimeSteppingType.UNSET

@classmethod
def _get_project_length_unit_(cls, param_as_dict: dict):
try:
project_length_unit_dict = param_as_dict["private_attribute_asset_cache"][
"project_length_unit"
]
if project_length_unit_dict:
# pylint: disable=no-member
return LengthType.validate(project_length_unit_dict)
return None
except KeyError:
return None

def __init__(self, param_as_dict: dict):
self.auto_farfield_method = self._get_auto_farfield_method_(param_as_dict=param_as_dict)
self.is_beta_mesher = self._get_is_beta_mesher_(param_as_dict=param_as_dict)
Expand All @@ -136,6 +152,7 @@ def __init__(self, param_as_dict: dict):
param_as_dict=param_as_dict
)
self.time_stepping = self._get_time_stepping_(param_as_dict=param_as_dict)
self.project_length_unit = self._get_project_length_unit_(param_as_dict=param_as_dict)


class ValidationContext:
Expand Down
49 changes: 49 additions & 0 deletions tests/simulation/service/test_services_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,46 @@ def test_validate_service():
}
params_data_from_geo["version"] = "24.11.0"

params_data_op_from_mach_reynolds = params_data_from_vm.copy()
params_data_op_from_mach_reynolds["private_attribute_asset_cache"]["project_length_unit"] = {
"value": 0.8059,
"units": "m",
}
params_data_op_from_mach_reynolds["operating_condition"] = {
"type_name": "AerospaceCondition",
"private_attribute_constructor": "from_mach_reynolds",
"private_attribute_input_cache": {
"mach": 0.84,
"reynolds": 10.0,
"alpha": {"value": 3.06, "units": "degree"},
"beta": {"value": 0.0, "units": "degree"},
"temperature": {"value": 288.15, "units": "K"},
},
"alpha": {"value": 3.06, "units": "degree"},
"beta": {"value": 0.0, "units": "degree"},
"velocity_magnitude": {
"type_name": "number",
"value": 285.84696487889875,
"units": "m/s",
},
"thermal_state": {
"type_name": "ThermalState",
"private_attribute_constructor": "default",
"private_attribute_input_cache": {},
"temperature": {"value": 288.15, "units": "K"},
"density": {"value": 7.767260032496146e-07, "units": "Pa*s**2/m**2"},
"material": {
"type": "air",
"name": "air",
"dynamic_viscosity": {
"reference_viscosity": {"value": 1.716e-05, "units": "Pa*s"},
"reference_temperature": {"value": 273.15, "units": "K"},
"effective_temperature": {"value": 110.4, "units": "K"},
},
},
},
}

_, errors, _ = services.validate_model(
params_as_dict=params_data_from_geo,
validated_by=services.ValidationCalledBy.LOCAL,
Expand All @@ -121,6 +161,15 @@ def test_validate_service():

assert errors is None

_, errors, _ = services.validate_model(
params_as_dict=params_data_op_from_mach_reynolds,
validated_by=services.ValidationCalledBy.LOCAL,
root_item_type="VolumeMesh",
validation_level=CASE,
)

assert errors is None


def test_validate_error():
params_data = {
Expand Down
Loading