Skip to content

Commit f106f28

Browse files
[Hotfix Main]: fix(): Fix edge_split_layers implicit-default warning noise (#1801)
Co-authored-by: Ben <106089368+benflexcompute@users.noreply.github.com>
1 parent e76aba6 commit f106f28

6 files changed

Lines changed: 196 additions & 1 deletion

File tree

flow360/component/project_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
Surface,
3333
)
3434
from flow360.component.simulation.services_utils import (
35+
strip_implicit_edge_split_layers_inplace,
3536
strip_selector_matches_and_broken_entities_inplace,
3637
)
3738
from flow360.component.simulation.simulation_params import SimulationParams
@@ -725,6 +726,7 @@ def validate_params_with_context(params, root_item_type, up_to):
725726
)
726727

727728
params_as_dict = params.model_dump(mode="json", exclude_none=True)
729+
params_as_dict = strip_implicit_edge_split_layers_inplace(params, params_as_dict)
728730

729731
params, errors, warnings = services.validate_model(
730732
params_as_dict=params_as_dict,

flow360/component/simulation/meshing_param/meshing_specs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ class MeshingDefaults(Flow360BaseModel):
182182
edge_split_layers: int = pd.Field(
183183
1,
184184
ge=0,
185+
# Skip default-value validation so warnings are emitted only when users explicitly set this field.
186+
validate_default=False,
185187
description="The number of layers that are considered for edge splitting in the boundary layer mesh."
186188
+ "This only affects beta mesher.",
187189
)

flow360/component/simulation/services_utils.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Utility functions for the simulation services."""
22

3-
from typing import Any
3+
from typing import TYPE_CHECKING, Any
44

55
from flow360.component.simulation.framework.entity_expansion_utils import (
66
get_registry_from_params,
@@ -12,6 +12,39 @@
1212

1313
_MIRRORED_ENTITY_TYPE_NAMES = ("MirroredSurface", "MirroredGeometryBodyGroup")
1414

15+
if TYPE_CHECKING:
16+
from flow360.component.simulation.simulation_params import SimulationParams
17+
18+
19+
def strip_implicit_edge_split_layers_inplace(params: "SimulationParams", params_dict: dict) -> dict:
20+
"""
21+
Remove implicitly injected `edge_split_layers` from serialized params.
22+
This extra and specific function was added due to a change in schema during lifecycle of a release (uncommon)
23+
24+
Why not use `exclude_unset` or `exclude_defaults` globally during `model_dump()`?
25+
- `exclude_unset` strips many unrelated defaulted fields and can affect downstream workflows.
26+
- `exclude_defaults` also strips explicitly user-set values that equal the default.
27+
"""
28+
meshing = getattr(params, "meshing", None)
29+
defaults = getattr(meshing, "defaults", None)
30+
if defaults is None:
31+
return params_dict
32+
33+
if "edge_split_layers" in defaults.model_fields_set:
34+
# Keep explicit user setting (including explicit value equal to default).
35+
return params_dict
36+
37+
meshing_dict = params_dict.get("meshing")
38+
if not isinstance(meshing_dict, dict):
39+
return params_dict
40+
41+
defaults_dict = meshing_dict.get("defaults")
42+
if not isinstance(defaults_dict, dict):
43+
return params_dict
44+
45+
defaults_dict.pop("edge_split_layers", None)
46+
return params_dict
47+
1548

1649
def strip_selector_matches_and_broken_entities_inplace(params) -> Any:
1750
"""

flow360/component/simulation/web/draft.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
from flow360.component.simulation.framework.entity_selector import (
2222
collect_and_tokenize_selectors_in_place,
2323
)
24+
from flow360.component.simulation.services_utils import (
25+
strip_implicit_edge_split_layers_inplace,
26+
)
2427
from flow360.component.utils import formatting_validation_errors, validate_type
2528
from flow360.environment import Env
2629
from flow360.exceptions import Flow360RuntimeError, Flow360WebError
@@ -132,6 +135,7 @@ def from_cloud(cls, draft_id: IDStringType) -> Draft:
132135
def update_simulation_params(self, params):
133136
"""update the SimulationParams of the draft"""
134137
params_dict = params.model_dump(mode="json", exclude_none=True)
138+
params_dict = strip_implicit_edge_split_layers_inplace(params, params_dict)
135139
params_dict = collect_and_tokenize_selectors_in_place(params_dict)
136140

137141
self.post(

tests/simulation/params/test_validators_params.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,6 +2482,28 @@ def test_beta_mesher_only_features(mock_validation_context):
24822482
assert errors[0]["loc"] == ()
24832483

24842484

2485+
def test_edge_split_layers_default_no_warning_for_dict_input():
2486+
non_beta_context = ParamsValidationInfo({}, [])
2487+
non_beta_context.is_beta_mesher = False
2488+
2489+
with SI_unit_system, ValidationContext(VOLUME_MESH, non_beta_context) as validation_context:
2490+
defaults = MeshingDefaults.model_validate({"boundary_layer_first_layer_thickness": 1e-4})
2491+
2492+
assert "edge_split_layers" not in defaults.model_fields_set
2493+
assert validation_context.validation_warnings == []
2494+
2495+
2496+
def test_edge_split_layers_default_no_warning_for_constructor_input():
2497+
non_beta_context = ParamsValidationInfo({}, [])
2498+
non_beta_context.is_beta_mesher = False
2499+
2500+
with SI_unit_system, ValidationContext(VOLUME_MESH, non_beta_context) as validation_context:
2501+
defaults = MeshingDefaults(boundary_layer_first_layer_thickness=1e-4)
2502+
2503+
assert "edge_split_layers" not in defaults.model_fields_set
2504+
assert validation_context.validation_warnings == []
2505+
2506+
24852507
def test_geometry_AI_only_features():
24862508
with SI_unit_system:
24872509
params = SimulationParams(
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import json
2+
from types import SimpleNamespace
3+
4+
import flow360 as fl
5+
from flow360.component.project_utils import validate_params_with_context
6+
from flow360.component.simulation.framework.param_utils import AssetCache
7+
from flow360.component.simulation.meshing_param.meshing_specs import MeshingDefaults
8+
from flow360.component.simulation.services_utils import (
9+
strip_implicit_edge_split_layers_inplace,
10+
)
11+
from flow360.component.simulation.web.draft import Draft
12+
13+
14+
def _build_dummy_params(defaults: MeshingDefaults):
15+
return SimpleNamespace(meshing=SimpleNamespace(defaults=defaults))
16+
17+
18+
def _build_simulation_params(*, edge_split_layers=None):
19+
defaults_kwargs = dict(
20+
boundary_layer_first_layer_thickness=1e-4,
21+
surface_max_edge_length=1e-2,
22+
)
23+
if edge_split_layers is not None:
24+
defaults_kwargs["edge_split_layers"] = edge_split_layers
25+
26+
with fl.SI_unit_system:
27+
return fl.SimulationParams(
28+
meshing=fl.MeshingParams(
29+
defaults=fl.MeshingDefaults(**defaults_kwargs),
30+
volume_zones=[fl.AutomatedFarfield()],
31+
),
32+
private_attribute_asset_cache=AssetCache(use_inhouse_mesher=False),
33+
)
34+
35+
36+
def test_strip_implicit_edge_split_layers_removes_default_injected_field():
37+
with fl.SI_unit_system:
38+
defaults = MeshingDefaults(boundary_layer_first_layer_thickness=1e-4)
39+
40+
params = _build_dummy_params(defaults)
41+
params_dict = {
42+
"meshing": {"defaults": {"edge_split_layers": 1, "surface_edge_growth_rate": 1.2}}
43+
}
44+
45+
out = strip_implicit_edge_split_layers_inplace(params, params_dict)
46+
47+
assert out is params_dict
48+
assert "edge_split_layers" not in out["meshing"]["defaults"]
49+
assert out["meshing"]["defaults"]["surface_edge_growth_rate"] == 1.2
50+
51+
52+
def test_strip_implicit_edge_split_layers_keeps_explicit_default_value():
53+
with fl.SI_unit_system:
54+
defaults = MeshingDefaults(boundary_layer_first_layer_thickness=1e-4, edge_split_layers=1)
55+
56+
params = _build_dummy_params(defaults)
57+
params_dict = {
58+
"meshing": {"defaults": {"edge_split_layers": 1, "surface_edge_growth_rate": 1.2}}
59+
}
60+
61+
out = strip_implicit_edge_split_layers_inplace(params, params_dict)
62+
63+
assert out["meshing"]["defaults"]["edge_split_layers"] == 1
64+
65+
66+
def test_strip_implicit_edge_split_layers_keeps_explicit_non_default_value():
67+
with fl.SI_unit_system:
68+
defaults = MeshingDefaults(boundary_layer_first_layer_thickness=1e-4, edge_split_layers=3)
69+
70+
params = _build_dummy_params(defaults)
71+
params_dict = {
72+
"meshing": {"defaults": {"edge_split_layers": 3, "surface_edge_growth_rate": 1.2}}
73+
}
74+
75+
out = strip_implicit_edge_split_layers_inplace(params, params_dict)
76+
77+
assert out["meshing"]["defaults"]["edge_split_layers"] == 3
78+
79+
80+
def test_validate_params_with_context_no_warning_for_implicit_default():
81+
params = _build_simulation_params()
82+
83+
_, errors, warnings = validate_params_with_context(params, "Geometry", "VolumeMesh")
84+
85+
assert errors is None
86+
assert warnings == []
87+
88+
89+
def test_validate_params_with_context_warning_for_explicit_default_value():
90+
params = _build_simulation_params(edge_split_layers=1)
91+
92+
_, errors, warnings = validate_params_with_context(params, "Geometry", "VolumeMesh")
93+
94+
assert errors is None
95+
assert len(warnings) == 1
96+
assert warnings[0]["msg"] == (
97+
"`edge_split_layers` is only supported by the beta mesher; this setting will be ignored."
98+
)
99+
100+
101+
def test_draft_upload_payload_omits_implicit_default_edge_split_layers(monkeypatch):
102+
params = _build_simulation_params()
103+
uploaded_payload = {}
104+
105+
def _capture_post(self, *, json=None, method=None, **_kwargs):
106+
uploaded_payload["json"] = json
107+
uploaded_payload["method"] = method
108+
return {}
109+
110+
monkeypatch.setattr(Draft, "post", _capture_post, raising=True)
111+
Draft(draft_id="00000000-0000-0000-0000-000000000000").update_simulation_params(params)
112+
113+
assert uploaded_payload["method"] == "simulation/file"
114+
uploaded_dict = json.loads(uploaded_payload["json"]["data"])
115+
assert "edge_split_layers" not in uploaded_dict["meshing"]["defaults"]
116+
117+
118+
def test_draft_upload_payload_keeps_explicit_default_edge_split_layers(monkeypatch):
119+
params = _build_simulation_params(edge_split_layers=1)
120+
uploaded_payload = {}
121+
122+
def _capture_post(self, *, json=None, method=None, **_kwargs):
123+
uploaded_payload["json"] = json
124+
uploaded_payload["method"] = method
125+
return {}
126+
127+
monkeypatch.setattr(Draft, "post", _capture_post, raising=True)
128+
Draft(draft_id="00000000-0000-0000-0000-000000000000").update_simulation_params(params)
129+
130+
assert uploaded_payload["method"] == "simulation/file"
131+
uploaded_dict = json.loads(uploaded_payload["json"]["data"])
132+
assert uploaded_dict["meshing"]["defaults"]["edge_split_layers"] == 1

0 commit comments

Comments
 (0)