Skip to content

[SCFD-3899] Added support for mach reynolds input in Aerospace condition #961

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 24 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e10f011
added operating condition from mach reynolds to aerospacecondition
wiktor-flex Apr 24, 2025
249f29b
black and isort
wiktor-flex Apr 24, 2025
5ece895
made exclude_none default in model_dump
maciej-flexcompute Apr 24, 2025
4137165
fixed test
maciej-flexcompute Apr 24, 2025
1140140
fixed solver translator unittests
wiktor-flex Apr 28, 2025
cc5b6f0
Revert "fixed solver translator unittests"
wiktor-flex Apr 30, 2025
8cf7dc1
Revert "fixed test"
wiktor-flex Apr 30, 2025
6f2d7cc
Revert "made exclude_none default in model_dump"
wiktor-flex Apr 30, 2025
bae5e3d
added removing none fields for multi constructor model
wiktor-flex Apr 30, 2025
38358f9
small function name change to be more clear
wiktor-flex Apr 30, 2025
71dff8c
Revert "small function name change to be more clear"
wiktor-flex Apr 30, 2025
509f703
Revert "added removing none fields for multi constructor model"
wiktor-flex Apr 30, 2025
cbaec5d
fixed conflicts in unittest
wiktor-flex Apr 30, 2025
5bcdf95
Merge branch 'develop' into wiktor/mach_reynolds
wiktor-flex Apr 30, 2025
d8fd481
black
wiktor-flex Apr 30, 2025
dd9f0dc
removed space for consistency
wiktor-flex Apr 30, 2025
1e4991d
Merge branch 'develop' into wiktor/mach_reynolds
benflexcompute May 9, 2025
161b7ae
changed density to be calculated from mach and not mach ref
wiktor-flex May 16, 2025
e33c0f0
changed reynolds number function to be consistent with solver
wiktor-flex May 16, 2025
f66b0ae
added empty line so that list builds correctly
wiktor-flex May 16, 2025
7fedacc
added backslashes to make infty symboo
wiktor-flex May 16, 2025
3c51ac6
Merge branch 'develop' into wiktor/mach_reynolds
wiktor-flex May 16, 2025
36c6aa6
Merge branch 'develop' into wiktor/mach_reynolds
wiktor-flex May 26, 2025
e5f46d4
Merge branch 'develop' into wiktor/mach_reynolds
wiktor-flex May 28, 2025
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 @@ -99,7 +99,6 @@
GenericReferenceCondition,
LiquidOperatingCondition,
ThermalState,
operating_condition_from_mach_reynolds,
)
from flow360.component.simulation.outputs.output_entities import (
Isosurface,
Expand Down Expand Up @@ -254,7 +253,6 @@
"Mach",
"MassFlowRate",
"UserDefinedField",
"operating_condition_from_mach_reynolds",
"VolumeMesh",
"SurfaceMesh",
"UserDefinedFarfield",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,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 @@ -370,6 +373,102 @@ 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.NonNegativeFloat,
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 if reference_mach is None else reference_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 @@ -471,99 +570,3 @@ def check_valid_reference_velocity(self) -> Self:
OperatingConditionTypes = Union[
GenericReferenceCondition, AerospaceCondition, LiquidOperatingCondition
]


# pylint: disable=too-many-arguments
@pd.validate_call
def operating_condition_from_mach_reynolds(
mach: pd.NonNegativeFloat,
reynolds: pd.PositiveFloat,
project_length_unit: LengthType.Positive = pd.Field(
description="The Length unit of the project."
),
temperature: AbsoluteTemperatureType = 288.15 * u.K,
alpha: Optional[AngleType] = 0 * u.deg,
beta: Optional[AngleType] = 0 * u.deg,
reference_mach: Optional[pd.PositiveFloat] = None,
) -> AerospaceCondition:
"""
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.
temperature : AbsoluteTemperatureType, optional
Freestream static temperature (must be a positive temperature value). Default is 288.15 Kelvin.
alpha : AngleType, optional
Angle of attack. Default is 0 degrees.
beta : AngleType, optional
Sideslip angle. Default is 0 degrees.
reference_mach : PositiveFloat, optional
Reference Mach number. Default is None.

Returns
-------
AerospaceCondition
An `AerospaceCondition` object initialized with the given parameters.

Raises
------
ValidationError
If the input values do not meet the specified constraints.
ValueError
If required parameters are missing or calculations cannot be performed.

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 if reference_mach is None else reference_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)

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

# pylint: disable=no-value-for-parameter
return AerospaceCondition.from_mach(
mach=mach,
alpha=alpha,
beta=beta,
thermal_state=thermal_state,
reference_mach=reference_mach,
)
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
7 changes: 3 additions & 4 deletions tests/simulation/params/test_simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
AerospaceCondition,
LiquidOperatingCondition,
ThermalState,
operating_condition_from_mach_reynolds,
)
from flow360.component.simulation.primitives import (
Box,
Expand Down Expand Up @@ -345,7 +344,7 @@ def test_subsequent_param_with_different_unit_system():


def test_mach_reynolds_op_cond():
condition = operating_condition_from_mach_reynolds(
condition = AerospaceCondition.from_mach_reynolds(
mach=0.2,
reynolds=5e6,
temperature=288.15 * u.K,
Expand All @@ -356,7 +355,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 @@ -368,7 +367,7 @@ def test_mach_reynolds_op_cond():
assertions.assertAlmostEqual(condition.thermal_state.density.value, 0.6572616596801923)

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
Loading