Skip to content

[SCFD-2744] [SCFD-2069] Added BC completeness check #489

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 19 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
78681a0
Added BC completeness check
benflexcompute Oct 8, 2024
91398c5
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Oct 8, 2024
221a4dd
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Oct 9, 2024
8dd1272
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Oct 23, 2024
0aa87d2
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Oct 25, 2024
1f93112
Fixing unit test
benflexcompute Oct 25, 2024
71f575c
Fixed unit test
benflexcompute Oct 25, 2024
971359b
Fix lint
benflexcompute Oct 25, 2024
0d7e880
Update flow360/component/simulation/validation/validation_simulation_…
benflexcompute Oct 31, 2024
c9ecb85
Update flow360/component/simulation/validation/validation_simulation_…
benflexcompute Oct 31, 2024
8da50a5
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Oct 31, 2024
9c234a1
Fixed the unit test
benflexcompute Oct 31, 2024
a3ea7b2
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Nov 1, 2024
c443d28
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Nov 1, 2024
31960a0
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Nov 7, 2024
ecf2380
FIX UNIT TEST
benflexcompute Nov 7, 2024
093e708
Added guard on the validation level
benflexcompute Nov 8, 2024
f497d72
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Nov 8, 2024
1ff683a
Merge branch 'develop' into BenY/AddBCCompletenessCheck
benflexcompute Nov 12, 2024
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
39 changes: 29 additions & 10 deletions flow360/component/simulation/entity_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,27 @@ def _get_list_of_entities(
entity_attribute_names = self.edge_attribute_names
entity_full_list = self.grouped_edges

# Use the supplied one if not None
if attribute_name is not None:
# pylint: disable=unsupported-membership-test,unsubscriptable-object
if attribute_name in entity_attribute_names:
# pylint: disable=no-member
return entity_full_list[entity_attribute_names.index(attribute_name)]
raise ValueError(
f"The given attribute_name {attribute_name} is not found"
f" in geometry metadata. Available: {entity_attribute_names}"
specified_attribute_name = attribute_name
else:
specified_attribute_name = (
self.face_group_tag if entity_type_name == "face" else self.edge_group_tag
)
raise ValueError("Attribute name is required to get the full list of grouped entities.")

# pylint: disable=unsupported-membership-test,unsubscriptable-object
if specified_attribute_name in entity_attribute_names:
# pylint: disable=no-member
return entity_full_list[entity_attribute_names.index(specified_attribute_name)]
raise ValueError(
f"The given attribute_name {attribute_name} is not found"
f" in geometry metadata. Available: {entity_attribute_names}"
)

def get_boundaries(self, attribute_name: str = None) -> list:
"""
Get the full list of boundary names. If it is geometry then use supplied attribute name to get the list.
Get the full list of boundaries.
If attribute_name is supplied then ignore stored face_group_tag and use supplied one.
"""
return self._get_list_of_entities(attribute_name, "face")

Expand All @@ -144,12 +151,24 @@ class VolumeMeshEntityInfo(EntityInfoModel):
zones: list[GenericVolume] = pd.Field([])
boundaries: list[Surface] = pd.Field([])

@pd.field_validator("boundaries", mode="after")
@classmethod
def check_all_surface_has_interface_indicator(cls, value):
"""private_attribute_is_interface should have been set comming from volume mesh."""
for item in value:
if item.private_attribute_is_interface is None:
raise ValueError(
"[INTERNAL] {item.name} is missing private_attribute_is_interface attribute!."
)
return value

# pylint: disable=arguments-differ
def get_boundaries(self) -> list:
"""
Get the full list of boundary names. If it is geometry then use supplied attribute name to get the list.
"""
return self.boundaries
# pylint: disable=not-an-iterable
return [item for item in self.boundaries if item.private_attribute_is_interface is False]


class SurfaceMeshEntityInfo(EntityInfoModel):
Expand Down
1 change: 1 addition & 0 deletions flow360/component/simulation/framework/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def one_of(cls, values):
# pylint: disable=no-self-argument
# pylint: disable=duplicate-code
@pd.model_validator(mode="before")
@classmethod
def handle_conflicting_fields(cls, values):
"""
root validator to handle deprecated aliases and fields
Expand Down
9 changes: 9 additions & 0 deletions flow360/component/simulation/framework/param_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ class AssetCache(Flow360BaseModel):
Union[GeometryEntityInfo, VolumeMeshEntityInfo, SurfaceMeshEntityInfo]
] = pd.Field(None, frozen=True, discriminator="type_name")

@property
def boundaries(self):
"""
Get all boundaries from the cached entity info.
"""
if self.project_entity_info is None:
return None
return self.project_entity_info.get_boundaries()


def register_entity_list(model: Flow360BaseModel, registry: EntityRegistry) -> None:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,15 @@ def from_mach(
reference_velocity_magnitude=reference_velocity_magnitude,
)

@context_validator(context=CASE)
@pd.model_validator(mode="after")
@context_validator(context=CASE)
def check_valid_reference_velocity(self) -> Self:
"""Ensure reference velocity is provided when freestream velocity is 0."""
if self.velocity_magnitude.value == 0 and self.reference_velocity_magnitude is None:
if (
self.velocity_magnitude is not None
and self.velocity_magnitude.value == 0
and self.reference_velocity_magnitude is None
):
raise ValueError(
"Reference velocity magnitude/Mach must be provided when freestream velocity magnitude/Mach is 0."
)
Expand Down
35 changes: 22 additions & 13 deletions flow360/component/simulation/simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
)
from flow360.component.simulation.validation.validation_simulation_params import (
_check_cht_solver_settings,
_check_complete_boundary_condition_and_unknown_surface,
_check_consistency_ddes_volume_output,
_check_consistency_wall_function_and_surface_output,
_check_duplicate_entities_in_models,
Expand All @@ -57,7 +58,13 @@
from flow360.exceptions import Flow360ConfigurationError, Flow360RuntimeError
from flow360.version import __version__

from .validation.validation_context import SURFACE_MESH, CaseField, ContextField
from .validation.validation_context import (
CASE,
SURFACE_MESH,
CaseField,
ContextField,
context_validator,
)

ModelTypes = Annotated[Union[VolumeModelTypes, SurfaceModelTypes], pd.Field(discriminator="type")]

Expand Down Expand Up @@ -235,33 +242,35 @@ def check_cht_solver_settings(self):
return _check_cht_solver_settings(self)

@pd.model_validator(mode="after")
@classmethod
def check_consistency_wall_function_and_surface_output(cls, v):
def check_consistency_wall_function_and_surface_output(self):
"""Only allow wallFunctionMetric output field when there is a Wall model with a wall function enabled"""
return _check_consistency_wall_function_and_surface_output(v)
return _check_consistency_wall_function_and_surface_output(self)

@pd.model_validator(mode="after")
@classmethod
def check_consistency_ddes_volume_output(cls, v):
def check_consistency_ddes_volume_output(self):
"""Only allow DDES output field when there is a corresponding solver with DDES enabled in models"""
return _check_consistency_ddes_volume_output(v)
return _check_consistency_ddes_volume_output(self)

@pd.model_validator(mode="after")
def check_duplicate_entities_in_models(self):
"""Only allow each Surface/Volume entity to appear once in the Surface/Volume model"""
return _check_duplicate_entities_in_models(self)

@pd.model_validator(mode="after")
@classmethod
def check_numerical_dissipation_factor_output(cls, v):
def check_numerical_dissipation_factor_output(self):
"""Only allow numericalDissipationFactor output field when the NS solver has low numerical dissipation"""
return _check_numerical_dissipation_factor_output(v)
return _check_numerical_dissipation_factor_output(self)

@pd.model_validator(mode="after")
@classmethod
def check_low_mach_preconditioner_output(cls, v):
def check_low_mach_preconditioner_output(self):
"""Only allow lowMachPreconditioner output field when the lowMachPreconditioner is enabled in the NS solver"""
return _check_low_mach_preconditioner_output(v)
return _check_low_mach_preconditioner_output(self)

@pd.model_validator(mode="after")
@context_validator(context=CASE)
def check_complete_boundary_condition_and_unknown_surface(self):
"""Make sure that all boundaries have been assigned with a boundary condition"""
return _check_complete_boundary_condition_and_unknown_surface(self)

def _move_registry_to_asset_cache(self, registry: EntityRegistry) -> EntityRegistry:
"""Recursively register all entities listed in EntityList to the asset cache."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,8 @@ def get_surface_meshing_json(input_params: SimulationParams, mesh_units):

##:: >> Step 6: Tell surface mesher how do we group boundaries.
translated["boundaries"] = {}
face_group_tag = input_params.private_attribute_asset_cache.project_entity_info.face_group_tag
grouped_faces: List[Surface] = (
input_params.private_attribute_asset_cache.project_entity_info.get_boundaries(
face_group_tag
)
input_params.private_attribute_asset_cache.project_entity_info.get_boundaries()
)
for surface in grouped_faces:
for face_id in surface.private_attribute_sub_components:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
validation for SimulationParams
"""

from typing import get_args

from flow360.component.simulation.models.solver_numerics import NoneSolver
from flow360.component.simulation.models.surface_models import Wall
from flow360.component.simulation.models.surface_models import SurfaceModelTypes, Wall
from flow360.component.simulation.models.volume_models import (
Fluid,
NavierStokesInitialCondition,
Expand All @@ -17,6 +19,7 @@
VolumeOutput,
)
from flow360.component.simulation.primitives import (
GhostSurface,
_SurfaceEntityBase,
_VolumeEntityBase,
)
Expand Down Expand Up @@ -264,3 +267,55 @@ def _validate_cht_has_heat_transfer(params):
)

return params


def _check_complete_boundary_condition_and_unknown_surface(params):
## Step 1: Get all boundaries patches from asset cache
asset_boundary_entities = params.private_attribute_asset_cache.boundaries
if asset_boundary_entities is None or asset_boundary_entities == []:
return params
asset_boundaries = {boundary.name for boundary in asset_boundary_entities}

## Step 2: Collect all used boundaries from the models
if len(params.models) == 1 and isinstance(params.models[0], Fluid):
return params
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are no BC defined at all, why don't we raise?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because even if user composes a SimulationParams for meshing only this validator will still get triggered. The context validator does not control when validator is called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this validator to adapt with current validation context. We only raise if we have CASE ValidationContext


used_boundaries = set()

for model in params.models:
if not isinstance(model, get_args(SurfaceModelTypes)):
continue

entities = []
# pylint: disable=protected-access
if hasattr(model, "entities"):
entities = model.entities._get_expanded_entities(create_hard_copy=False)
elif hasattr(model, "entity_pairs"): # Periodic BC
entities = [
pair for surface_pair in model.entity_pairs.items for pair in surface_pair.pair
]

for entity in entities:
if isinstance(entity, GhostSurface):
continue
used_boundaries.add(entity.name)

## Step 3: Use set operations to find missing and unknown boundaries
missing_boundaries = asset_boundaries - used_boundaries
unknown_boundaries = used_boundaries - asset_boundaries

if missing_boundaries:
missing_list = ", ".join(sorted(missing_boundaries))
raise ValueError(
f"The following boundaries do not have a boundary condition: {missing_list}. "
"Please add them to a model in the `models` section."
)

if unknown_boundaries:
unknown_list = ", ".join(sorted(unknown_boundaries))
raise ValueError(
f"The following boundaries are not known `Surface` "
f"entities but appear in the `models` section: {unknown_list}."
)

return params
102 changes: 101 additions & 1 deletion tests/simulation/params/test_validators_params.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import json
import re
import unittest
from typing import Literal

import pytest

import flow360.component.simulation.units as u
from flow360.component.simulation.entity_info import VolumeMeshEntityInfo
from flow360.component.simulation.framework.param_utils import AssetCache
from flow360.component.simulation.meshing_param.volume_params import AutomatedFarfield
from flow360.component.simulation.models.material import SolidMaterial, aluminum
from flow360.component.simulation.models.surface_models import SlipWall, Wall
from flow360.component.simulation.models.surface_models import (
Freestream,
Periodic,
SlipWall,
Translational,
Wall,
)
from flow360.component.simulation.models.volume_models import (
Fluid,
HeatEquationInitialCondition,
NavierStokesInitialCondition,
Solid,
)
from flow360.component.simulation.operating_condition.operating_condition import (
AerospaceCondition,
)
from flow360.component.simulation.outputs.outputs import SurfaceOutput, VolumeOutput
from flow360.component.simulation.primitives import GenericVolume, Surface
from flow360.component.simulation.services import validate_model
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.time_stepping.time_stepping import Steady, Unsteady
from flow360.component.simulation.unit_system import SI_unit_system
from flow360.component.simulation.validation.validation_context import (
ALL,
CASE,
SURFACE_MESH,
VOLUME_MESH,
ValidationLevelContext,
)

assertions = unittest.TestCase("__init__")

Expand Down Expand Up @@ -293,6 +315,73 @@ def test_cht_solver_settings_validator(
)


def test_incomplete_BC():
##:: Construct a dummy asset cache

wall_1 = Surface(
name="wall_1", private_attribute_is_interface=False, private_attribute_tag_key="test"
)
periodic_1 = Surface(
name="periodic_1", private_attribute_is_interface=False, private_attribute_tag_key="test"
)
periodic_2 = Surface(
name="periodic_2", private_attribute_is_interface=False, private_attribute_tag_key="test"
)
i_exist = Surface(
name="i_exist", private_attribute_is_interface=False, private_attribute_tag_key="test"
)
no_bc = Surface(
name="no_bc", private_attribute_is_interface=False, private_attribute_tag_key="test"
)
some_interface = Surface(
name="some_interface", private_attribute_is_interface=True, private_attribute_tag_key="test"
)

asset_cache = AssetCache(
project_length_unit="inch",
project_entity_info=VolumeMeshEntityInfo(
boundaries=[wall_1, periodic_1, periodic_2, i_exist, some_interface, no_bc]
),
)
auto_farfield = AutomatedFarfield(name="my_farfield")

with pytest.raises(
ValueError,
match=re.escape(
r"The following boundaries do not have a boundary condition: no_bc. Please add them to a model in the `models` section."
),
):
with SI_unit_system:
SimulationParams(
models=[
Fluid(),
Wall(entities=wall_1),
Periodic(surface_pairs=(periodic_1, periodic_2), spec=Translational()),
SlipWall(entities=[i_exist]),
Freestream(entities=auto_farfield.farfield),
],
private_attribute_asset_cache=asset_cache,
)
with pytest.raises(
ValueError,
match=re.escape(
r"The following boundaries are not known `Surface` entities but appear in the `models` section: plz_dont_do_this."
),
):
with SI_unit_system:
SimulationParams(
models=[
Fluid(),
Wall(entities=[wall_1]),
Periodic(surface_pairs=(periodic_1, periodic_2), spec=Translational()),
SlipWall(entities=[i_exist]),
Freestream(entities=auto_farfield.farfield),
SlipWall(entities=[Surface(name="plz_dont_do_this"), no_bc]),
],
private_attribute_asset_cache=asset_cache,
)


def test_duplicate_entities_in_models():
entity_generic_volume = GenericVolume(name="Duplicate Volume")
entity_surface = Surface(name="Duplicate Surface")
Expand Down Expand Up @@ -324,3 +413,14 @@ def test_duplicate_entities_in_models():
_ = SimulationParams(
models=[volume_model1, volume_model2, surface_model1, surface_model2, surface_model3],
)


def test_valid_reference_velocity():
with pytest.raises(
ValueError,
match=re.escape(
"Reference velocity magnitude/Mach must be provided when freestream velocity magnitude/Mach is 0."
),
):
with SI_unit_system:
SimulationParams(operating_condition=AerospaceCondition(velocity_magnitude=0))
Loading