diff --git a/flow360/component/simulation/models/validation/validation_bet_disk.py b/flow360/component/simulation/models/validation/validation_bet_disk.py index 6d2dfe35f..908ce54c8 100644 --- a/flow360/component/simulation/models/validation/validation_bet_disk.py +++ b/flow360/component/simulation/models/validation/validation_bet_disk.py @@ -4,6 +4,11 @@ from pydantic import ValidationInfo +from flow360.component.simulation.validation.validation_context import ( + TimeSteppingType, + get_validation_info, +) + def _check_bet_disk_initial_blade_direction_and_blade_line_chord(bet_disk): if bet_disk.blade_line_chord > 0 and bet_disk.initial_blade_direction is None: @@ -20,6 +25,7 @@ def _check_bet_disk_initial_blade_direction_and_blade_line_chord(bet_disk): # pylint: disable=unused-argument +# This is to enable getting name from the info. def _check_bet_disk_alphas_in_order(value, info: ValidationInfo): if any(value != sorted(value)): raise ValueError("the alphas are not in increasing order.") @@ -51,6 +57,18 @@ def _check_bet_disk_duplicate_twists(value, info: ValidationInfo): return value +def _check_bet_disk_initial_blade_direction(value, info: ValidationInfo): + validation_info = get_validation_info() + if validation_info is None: + return value + + if validation_info.time_stepping == TimeSteppingType.UNSTEADY and value is None: + raise ValueError( + "The initial_blade_direction must be specified if performing an unsteady BET Line simulation." + ) + return value + + def _check_bet_disk_sectional_radius_and_polars(bet_disk): radiuses = bet_disk.sectional_radiuses polars = bet_disk.sectional_polars diff --git a/flow360/component/simulation/models/volume_models.py b/flow360/component/simulation/models/volume_models.py index c478d7546..f3a7993f6 100644 --- a/flow360/component/simulation/models/volume_models.py +++ b/flow360/component/simulation/models/volume_models.py @@ -48,6 +48,7 @@ _check_bet_disk_alphas_in_order, _check_bet_disk_duplicate_chords, _check_bet_disk_duplicate_twists, + _check_bet_disk_initial_blade_direction, _check_bet_disk_initial_blade_direction_and_blade_line_chord, _check_bet_disk_sectional_radius_and_polars, ) @@ -779,6 +780,12 @@ def check_bet_disk_duplicate_twists(cls, value, info: pd.ValidationInfo): """validate duplicates in twists in BET disks""" return _check_bet_disk_duplicate_twists(value, info) + @pd.field_validator("initial_blade_direction", mode="after") + @classmethod + def invalid_growth_rate(cls, value, info: pd.ValidationInfo): + """Ensure initial_blade_direction is specified in an unsteady simulation""" + return _check_bet_disk_initial_blade_direction(value, info) + @pd.model_validator(mode="after") @_validator_append_instance_name def check_bet_disk_sectional_radius_and_polars(self): diff --git a/flow360/component/simulation/validation/validation_context.py b/flow360/component/simulation/validation/validation_context.py index a2d798b2d..666e4cf87 100644 --- a/flow360/component/simulation/validation/validation_context.py +++ b/flow360/component/simulation/validation/validation_context.py @@ -15,6 +15,7 @@ """ import contextvars +from enum import Enum from functools import wraps from typing import Any, Callable, List, Literal, Union @@ -26,6 +27,26 @@ # when running validation with ALL, it will report errors happing in all scenarios in one validation pass ALL = "All" + +class TimeSteppingType(Enum): + """ + Enum for time stepping type + + Attributes + ---------- + STEADY : str + Represents a steady simulation. + UNSTEADY : str + Represents an unsteady simulation. + UNSET : str + The time stepping is unset. + """ + + STEADY = "Steady" + UNSTEADY = "Unsteady" + UNSET = "Unset" + + _validation_level_ctx = contextvars.ContextVar("validation_levels", default=None) _validation_info_ctx = contextvars.ContextVar("validation_info", default=None) @@ -52,6 +73,7 @@ class ParamsValidationInfo: # pylint:disable=too-few-public-methods "is_beta_mesher", "use_geometry_AI", "using_liquid_as_material", + "time_stepping", ] @classmethod @@ -95,6 +117,15 @@ def _get_use_geometry_AI_(cls, param_as_dict: dict): # pylint:disable=invalid-n except KeyError: return False + @classmethod + def _get_time_stepping_(cls, param_as_dict: dict): + try: + if param_as_dict["time_stepping"]["type_name"] == "Unsteady": + return TimeSteppingType.UNSTEADY + return TimeSteppingType.STEADY + except KeyError: + return TimeSteppingType.UNSET + 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) @@ -104,6 +135,7 @@ def __init__(self, param_as_dict: dict): self.using_liquid_as_material = self._get_using_liquid_as_material_( param_as_dict=param_as_dict ) + self.time_stepping = self._get_time_stepping_(param_as_dict=param_as_dict) class ValidationContext: diff --git a/tests/simulation/params/test_validators_bet_disk.py b/tests/simulation/params/test_validators_bet_disk.py index 87a9bf9e5..c9f1c5c52 100644 --- a/tests/simulation/params/test_validators_bet_disk.py +++ b/tests/simulation/params/test_validators_bet_disk.py @@ -3,8 +3,10 @@ import pytest import flow360.component.simulation.units as u +from flow360.component.simulation import services from flow360.component.simulation.models.volume_models import BETDisk from flow360.component.simulation.simulation_params import SimulationParams +from flow360.component.simulation.time_stepping.time_stepping import Unsteady from tests.simulation.translator.utils.xv15_bet_disk_helper import createBETDiskSteady from tests.simulation.translator.utils.xv15BETDisk_param_generator import ( _BET_cylinder, @@ -56,6 +58,28 @@ def test_bet_disk_initial_blade_direction_with_bet_name(create_steady_bet_disk): bet_disk.blade_line_chord = 0.1 * u.inch +def test_bet_disk_initial_blade_direction_with_unsteady_simulation(create_steady_bet_disk): + with u.SI_unit_system: + params = SimulationParams( + models=[create_steady_bet_disk], + time_stepping=Unsteady( + step_size=0.01 * u.s, + steps=120, + ), + ) + + params, errors, _ = services.validate_model( + params_as_dict=params.model_dump(mode="json"), + validated_by=services.ValidationCalledBy.LOCAL, + root_item_type="VolumeMesh", + validation_level="Case", + ) + assert len(errors) == 1 + assert errors[0]["msg"] == ( + "Value error, The initial_blade_direction must be specified if performing an unsteady BET Line simulation." + ) + + def test_bet_disk_disorder_alphas(create_steady_bet_disk): bet_disk = create_steady_bet_disk with pytest.raises(