Skip to content

[Hotfix 25.2]: [SCFD-3899] Added support for mach reynolds input in Aerospace condition #1104

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

Closed
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
2 changes: 0 additions & 2 deletions flow360/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
AerospaceCondition,
GenericReferenceCondition,
ThermalState,
operating_condition_from_mach_reynolds,
)
from flow360.component.simulation.outputs.output_entities import (
Isosurface,
Expand Down Expand Up @@ -239,7 +238,6 @@
"Mach",
"MassFlowRate",
"UserDefinedField",
"operating_condition_from_mach_reynolds",
"VolumeMesh",
"SurfaceMesh",
"UserDefinedFarfield",
Expand Down
119 changes: 109 additions & 10 deletions flow360/component/simulation/operating_condition/operating_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,11 @@ class AerospaceConditionCache(Flow360BaseModel):
"""[INTERNAL] Cache for AerospaceCondition inputs"""

mach: Optional[pd.NonNegativeFloat] = None
reynolds: Optional[pd.PositiveFloat] = None
project_length_unit: Optional[LengthType.Positive] = None
alpha: Optional[AngleType] = None
beta: Optional[AngleType] = None
temperature: Optional[AbsoluteTemperatureType] = None
thermal_state: Optional[ThermalState] = pd.Field(None, alias="atmosphere")
reference_mach: Optional[pd.PositiveFloat] = None

Expand Down Expand Up @@ -372,6 +375,100 @@ def from_mach(
reference_velocity_magnitude=reference_velocity_magnitude,
)

# pylint: disable=too-many-arguments
@MultiConstructorBaseModel.model_constructor
@pd.validate_call
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,
temperature: AbsoluteTemperatureType = 288.15 * u.K,
reference_mach: Optional[pd.PositiveFloat] = None,
):
"""
Create an `AerospaceCondition` from Mach number and Reynolds number.

This function computes the thermal state based on the given Mach number,
Reynolds number, and temperature, and returns an `AerospaceCondition` object
initialized with the computed thermal state and given aerodynamic angles.

Parameters
----------
mach : NonNegativeFloat
Freestream Mach number (must be non-negative).
reynolds : PositiveFloat
Freestream Reynolds number defined with mesh unit (must be positive).
project_length_unit: LengthType.Positive
Project length unit.
alpha : AngleType, optional
Angle of attack. Default is 0 degrees.
beta : AngleType, optional
Sideslip angle. Default is 0 degrees.
temperature : AbsoluteTemperatureType, optional
Freestream static temperature (must be a positive temperature value). Default is 288.15 Kelvin.
reference_mach : PositiveFloat, optional
Reference Mach number. Default is None.

Returns
-------
AerospaceCondition
An instance of :class:`AerospaceCondition` with calculated velocity, thermal state and provided parameters.

Example
-------
Example usage:

>>> condition = operating_condition_from_mach_reynolds(
... mach=0.85,
... reynolds=1e6,
... project_length_unit=1 * u.mm,
... temperature=288.15 * u.K,
... alpha=2.0 * u.deg,
... beta=0.0 * u.deg,
... reference_mach=0.85,
... )
>>> print(condition)
AerospaceCondition(...)

"""

if temperature.units is u.K and temperature.value == 288.15:
log.info("Default value of 288.15 K will be used as temperature.")

material = Air()

velocity = mach * material.get_speed_of_sound(temperature)

density = (
reynolds
* material.get_dynamic_viscosity(temperature)
/ (velocity * project_length_unit)
)

thermal_state = ThermalState(temperature=temperature, density=density)

velocity_magnitude = mach * thermal_state.speed_of_sound

reference_velocity_magnitude = (
reference_mach * thermal_state.speed_of_sound if reference_mach else None
)

log.info(
"""Density and viscosity were calculated based on input data, ThermalState will be automatically created."""
)

# pylint: disable=no-value-for-parameter
return cls(
velocity_magnitude=velocity_magnitude,
alpha=alpha,
beta=beta,
thermal_state=thermal_state,
reference_velocity_magnitude=reference_velocity_magnitude,
)

@pd.model_validator(mode="after")
@context_validator(context=CASE)
def check_valid_reference_velocity(self) -> Self:
Expand Down Expand Up @@ -401,10 +498,10 @@ def _update_input_cache(cls, value, info: pd.ValidationInfo):
def flow360_reynolds_number(self, length_unit: LengthType.Positive):
"""
Computes length_unit based Reynolds number.
:math:`Re = \\rho_{\\infty} \\cdot U_{ref} \\cdot L_{grid}/\\mu_{\\infty}` where
- :math:`rho_{\\infty}` is the freestream fluid density.
- :math:`U_{ref}` is the reference velocity magnitude or freestream velocity magnitude if reference
velocity magnitude is not set.
:math:`Re = \\rho_{\\infty} \\cdot U_{\\infty} \\cdot L_{grid}/\\mu_{\\infty}` where

- :math:`\\rho_{\\infty}` is the freestream fluid density.
- :math:`U_{\\infty}` is the freestream velocity magnitude.
- :math:`L_{grid}` is physical length represented by unit length in the given mesh/geometry file.
- :math:`\\mu_{\\infty}` is the dynamic eddy viscosity of the fluid of freestream.

Expand All @@ -413,21 +510,18 @@ def flow360_reynolds_number(self, length_unit: LengthType.Positive):
length_unit : LengthType.Positive
Physical length represented by unit length in the given mesh/geometry file.
"""
reference_velocity = (
self.reference_velocity_magnitude
if self.reference_velocity_magnitude
else self.velocity_magnitude
)

return (
self.thermal_state.density
* reference_velocity
* self.velocity_magnitude
* length_unit
/ self.thermal_state.dynamic_viscosity
).value


# pylint: disable=fixme
# TODO: AutomotiveCondition
<<<<<<< HEAD
OperatingConditionTypes = Union[GenericReferenceCondition, AerospaceCondition]


Expand Down Expand Up @@ -525,3 +619,8 @@ def operating_condition_from_mach_reynolds(
thermal_state=thermal_state,
reference_mach=reference_mach,
)
=======
OperatingConditionTypes = Union[
GenericReferenceCondition, AerospaceCondition, LiquidOperatingCondition
]
>>>>>>> 962fc438 ([SCFD-3899] Added support for mach reynolds input in Aerospace condition (#961))
48 changes: 39 additions & 9 deletions tests/simulation/framework/test_multi_constructor_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,33 +42,53 @@ def get_aerospace_condition_default_and_thermal_state_using_from():


@pytest.fixture
def get_aerospace_condition_using_from():
def get_aerospace_condition_using_from_mach():
return AerospaceCondition.from_mach(
mach=0.8,
alpha=5 * u.deg,
thermal_state=ThermalState.from_standard_atmosphere(altitude=1000 * u.m),
)


@pytest.fixture
def get_aerospace_condition_using_from_mach_reynolds():
return AerospaceCondition.from_mach_reynolds(
mach=0.8,
reynolds=1e6,
project_length_unit=u.m,
alpha=5 * u.deg,
temperature=290 * u.K,
)


def compare_objects_from_dict(dict1: dict, dict2: dict, object_class: type[Flow360BaseModel]):
obj1 = object_class.model_validate(dict1)
obj2 = object_class.model_validate(dict2)
assert obj1.model_dump_json() == obj2.model_dump_json()


def test_full_model(get_aerospace_condition_default, get_aerospace_condition_using_from):
def test_full_model(
get_aerospace_condition_default,
get_aerospace_condition_using_from_mach,
get_aerospace_condition_using_from_mach_reynolds,
):
full_data = get_aerospace_condition_default.model_dump(exclude_none=False)
data_parsed = parse_model_dict(full_data, globals())
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)

full_data = get_aerospace_condition_using_from.model_dump(exclude_none=False)
full_data = get_aerospace_condition_using_from_mach.model_dump(exclude_none=False)
data_parsed = parse_model_dict(full_data, globals())
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)

full_data = get_aerospace_condition_using_from_mach_reynolds.model_dump(exclude_none=False)
data_parsed = parse_model_dict(full_data, globals())
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)


def test_incomplete_model(
get_aerospace_condition_default,
get_aerospace_condition_using_from,
get_aerospace_condition_using_from_mach,
get_aerospace_condition_using_from_mach_reynolds,
get_aerospace_condition_default_and_thermal_state_using_from,
):
full_data = get_aerospace_condition_default.model_dump(exclude_none=False)
Expand All @@ -77,7 +97,17 @@ def test_incomplete_model(
data_parsed = parse_model_dict(incomplete_data, globals())
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)

full_data = get_aerospace_condition_using_from.model_dump(exclude_none=False)
full_data = get_aerospace_condition_using_from_mach.model_dump(exclude_none=False)
incomplete_data = {
"type_name": full_data["type_name"],
"private_attribute_constructor": full_data["private_attribute_constructor"],
"private_attribute_input_cache": full_data["private_attribute_input_cache"],
}

data_parsed = parse_model_dict(incomplete_data, globals())
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)

full_data = get_aerospace_condition_using_from_mach_reynolds.model_dump(exclude_none=False)
incomplete_data = {
"type_name": full_data["type_name"],
"private_attribute_constructor": full_data["private_attribute_constructor"],
Expand Down Expand Up @@ -105,9 +135,9 @@ def test_incomplete_model(
compare_objects_from_dict(full_data, data_parsed, AerospaceCondition)


def test_recursive_incomplete_model(get_aerospace_condition_using_from):
def test_recursive_incomplete_model(get_aerospace_condition_using_from_mach):
# `incomplete_data` contains only the private_attribute_* for both the AerospaceCondition and ThermalState
full_data = get_aerospace_condition_using_from.model_dump(exclude_none=False)
full_data = get_aerospace_condition_using_from_mach.model_dump(exclude_none=False)
input_cache = full_data["private_attribute_input_cache"]
input_cache["thermal_state"] = {
"type_name": input_cache["thermal_state"]["type_name"],
Expand Down Expand Up @@ -184,7 +214,7 @@ class ModelWithEntityList(Flow360BaseModel):
compare_objects_from_dict(full_data, data_parsed, ModelWithEntityList)


def test_entity_modification(get_aerospace_condition_using_from):
def test_entity_modification(get_aerospace_condition_using_from_mach):

my_box = Box.from_principal_axes(
name="box",
Expand All @@ -207,7 +237,7 @@ def test_entity_modification(get_aerospace_condition_using_from):
my_box.size = (1, 2, 32) * u.m
assert all(my_box.private_attribute_input_cache.size == (1, 2, 32) * u.m)

my_op = get_aerospace_condition_using_from
my_op = get_aerospace_condition_using_from_mach
my_op.alpha = -12 * u.rad
assert my_op.private_attribute_input_cache.alpha == -12 * u.rad

Expand Down
11 changes: 7 additions & 4 deletions tests/simulation/params/test_simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
from flow360.component.simulation.operating_condition.operating_condition import (
AerospaceCondition,
ThermalState,
operating_condition_from_mach_reynolds,
)
from flow360.component.simulation.primitives import (
Box,
Expand Down Expand Up @@ -295,8 +294,12 @@ def test_subsequent_param_with_different_unit_system():


def test_mach_reynolds_op_cond():
<<<<<<< HEAD

condition = operating_condition_from_mach_reynolds(
=======
condition = AerospaceCondition.from_mach_reynolds(
>>>>>>> 962fc438 ([SCFD-3899] Added support for mach reynolds input in Aerospace condition (#961))
mach=0.2,
reynolds=5e6,
temperature=288.15 * u.K,
Expand All @@ -307,7 +310,7 @@ def test_mach_reynolds_op_cond():
assertions.assertAlmostEqual(condition.thermal_state.dynamic_viscosity.value, 1.78929763e-5)
assertions.assertAlmostEqual(condition.thermal_state.density.value, 1.31452332)

condition = operating_condition_from_mach_reynolds(
condition = AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=5e6,
temperature=288.15 * u.K,
Expand All @@ -316,10 +319,10 @@ def test_mach_reynolds_op_cond():
project_length_unit=u.m,
reference_mach=0.4,
)
assertions.assertAlmostEqual(condition.thermal_state.density.value, 0.6572616596801923)
assertions.assertAlmostEqual(condition.thermal_state.density.value, 1.31452332)

with pytest.raises(ValueError, match="Input should be greater than 0"):
condition = operating_condition_from_mach_reynolds(
condition = AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=0,
temperature=288.15 * u.K,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Wall,
)
from flow360.component.simulation.operating_condition.operating_condition import (
operating_condition_from_mach_reynolds,
AerospaceCondition,
)
from flow360.component.simulation.primitives import ReferenceGeometry, Surface
from flow360.component.simulation.simulation_params import SimulationParams
Expand All @@ -24,7 +24,7 @@ def get_2dcrm_tutorial_param():
reference_geometry=ReferenceGeometry(
moment_center=[0.25, 0.005, 0], moment_length=[1, 1, 1], area=0.01
),
operating_condition=operating_condition_from_mach_reynolds(
operating_condition=AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=5e6,
temperature=272.1 * u.K,
Expand Down Expand Up @@ -52,7 +52,7 @@ def get_2dcrm_tutorial_param_deg_c():
reference_geometry=ReferenceGeometry(
moment_center=[0.25, 0.005, 0], moment_length=[1, 1, 1], area=0.01
),
operating_condition=operating_condition_from_mach_reynolds(
operating_condition=AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=5e6,
temperature=-1.05 * u.degC,
Expand Down Expand Up @@ -80,7 +80,7 @@ def get_2dcrm_tutorial_param_deg_f():
reference_geometry=ReferenceGeometry(
moment_center=[0.25, 0.005, 0], moment_length=[1, 1, 1], area=0.01
),
operating_condition=operating_condition_from_mach_reynolds(
operating_condition=AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=5e6,
temperature=30.11 * u.degF,
Expand Down