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 all 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 @@ -311,11 +311,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
36 changes: 23 additions & 13 deletions flow360/component/simulation/simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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 @@ -76,7 +77,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 @@ -162,6 +169,7 @@ def copy(self, update=None, **kwargs) -> _ParamModelBase:
return super().copy(update=update, **kwargs)


# pylint: disable=too-many-public-methods
class SimulationParams(_ParamModelBase):
"""All-in-one class for surface meshing + volume meshing + case configurations"""

Expand Down Expand Up @@ -273,33 +281,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)

@pd.model_validator(mode="after")
def check_output_fields(params):
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 @@ -18,6 +20,7 @@
VolumeOutput,
)
from flow360.component.simulation.primitives import (
GhostSurface,
_SurfaceEntityBase,
_VolumeEntityBase,
)
Expand Down Expand Up @@ -272,6 +275,65 @@ 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

current_lvls = get_validation_levels() if get_validation_levels() else []
if all(level not in current_lvls for level in (ALL, CASE)):
return params

asset_boundary_entities = params.private_attribute_asset_cache.boundaries

if asset_boundary_entities is None or asset_boundary_entities == []:
raise ValueError("[Internal] Failed to retrieve asset boundaries")

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):
raise ValueError("No boundary conditions are defined in the `models` section.")

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 boundary condition 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


def _check_parent_volume_is_rotating(models):

current_lvls = get_validation_levels() if get_validation_levels() else []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,25 @@
]
},
"models": [
{
"type": "Wall",
"entities": {
"stored_entities": [
{
"private_attribute_registry_bucket_name": "SurfaceEntityType",
"private_attribute_entity_type_name": "Surface",
"name": "surface_x",
"private_attribute_is_interface": false,
"private_attribute_sub_components": [
"face_x_1",
"face_x_2",
"face_x_3"
]
}
]
},
"use_wall_function": false
},
{
"material": {
"dynamic_viscosity": {
Expand Down Expand Up @@ -371,5 +390,35 @@
"unit_system": {
"name": "SI"
},
"version": "24.2.0"
}
"version": "24.2.0",
"private_attribute_asset_cache": {
"project_length_unit": "m",
"project_entity_info": {
"type_name": "GeometryEntityInfo",
"face_ids": [
"face_x_1",
"face_x_2",
"face_x_3"
],
"face_group_tag": "some_tag",
"face_attribute_names": [
"some_tag"
],
"grouped_faces": [
[
{
"private_attribute_registry_bucket_name": "SurfaceEntityType",
"private_attribute_entity_type_name": "Surface",
"name": "surface_x",
"private_attribute_is_interface": false,
"private_attribute_sub_components": [
"face_x_1",
"face_x_2",
"face_x_3"
]
}
]
]
}
}
}
Loading
Loading