Skip to content

Commit 7f15e56

Browse files
committed
Moving statistic improvement based on QA testing
1 parent 48d29f9 commit 7f15e56

File tree

4 files changed

+417
-66
lines changed

4 files changed

+417
-66
lines changed

flow360/component/simulation/outputs/outputs.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,19 @@ class MovingStatistic(Flow360BaseModel):
134134
:class:`ProbeOutput`, :class:`SurfaceProbeOutput`,
135135
:class:`SurfaceIntegralOutput` and :class:`ForceOutput`.
136136
137+
Notes
138+
-----
139+
- The window size is defined by the number of data points recorded in the output.
140+
- For steady simulations, the solver typically outputs a data point once every **10 pseudo steps**.
141+
This means a :py:attr:`moving_window_size`=10 would cover 100 pseudo steps.
142+
Thus, the :py:attr:`start_step` value is automatically rounded up to
143+
the nearest multiple of 10 for steady simulations.
144+
137145
Example
138146
-------
139147
140148
Define a moving statistic to compute the standard deviation in a moving window of
141-
10 steps, with the initial 100 steps skipped.
149+
10 data points, with the initial 100 steps skipped.
142150
143151
>>> fl.MovingStatistic(
144152
... moving_window_size=10,
@@ -149,35 +157,24 @@ class MovingStatistic(Flow360BaseModel):
149157
====
150158
"""
151159

152-
moving_window_size: pd.PositiveInt = pd.Field(
160+
moving_window_size: pd.StrictInt = pd.Field(
153161
10,
154-
description="The number of pseudo/physical steps to compute moving statistics. "
155-
"For steady simulation, the moving_window_size should be a multiple of 10.",
162+
ge=2,
163+
description="The size of the moving window in data points over which the "
164+
"statistic is calculated. Must be greater than or equal to 2.",
156165
)
157166
method: Literal["mean", "min", "max", "std", "deviation"] = pd.Field(
158-
"mean", description="The type of moving statistics used to monitor the output."
167+
"mean", description="The statistical method to apply to the data within the moving window."
159168
)
160169
start_step: pd.NonNegativeInt = pd.Field(
161170
0,
162-
description="The number of pseudo/physical steps to skip before computing the moving statistics. "
163-
"For steady simulation, the moving_window_size should be a multiple of 10.",
171+
description="The number of steps (pseudo or physical) to skip at the beginning of the "
172+
"simulation before the moving statistics calculation starts. For steady "
173+
"simulations, this value is automatically rounded up to the nearest multiple of 10, "
174+
"as the solver outputs data every 10 pseudo steps.",
164175
)
165176
type_name: Literal["MovingStatistic"] = pd.Field("MovingStatistic", frozen=True)
166177

167-
@pd.field_validator("moving_window_size", "start_step", mode="after")
168-
@classmethod
169-
def _check_moving_window_for_steady_simulation(cls, value):
170-
validation_info = get_validation_info()
171-
if (
172-
validation_info
173-
and validation_info.time_stepping == TimeSteppingType.STEADY
174-
and value % 10 != 0
175-
):
176-
raise ValueError(
177-
"For steady simulation, the number of steps should be a multiple of 10."
178-
)
179-
return value
180-
181178

182179
class _OutputBase(Flow360BaseModel):
183180
output_fields: UniqueItemList[str] = pd.Field()

flow360/component/simulation/simulation_params.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
from flow360.component.simulation.utils import model_attribute_unlock
8989
from flow360.component.simulation.validation.validation_output import (
9090
_check_aero_acoustics_observer_time_step_size,
91+
_check_moving_statistic_applicability,
9192
_check_output_fields,
9293
_check_output_fields_valid_given_transition_model,
9394
_check_output_fields_valid_given_turbulence_model,
@@ -579,6 +580,11 @@ def check_time_average_output(params):
579580
"""Only allow TimeAverage output field in the unsteady simulations"""
580581
return _check_time_average_output(params)
581582

583+
@pd.model_validator(mode="after")
584+
def check_moving_statistic_applicability(params):
585+
"""Check moving statistic settings are applicable to the simulation time stepping set up."""
586+
return _check_moving_statistic_applicability(params)
587+
582588
def _register_assigned_entities(self, registry: EntityRegistry) -> EntityRegistry:
583589
"""Recursively register all entities listed in EntityList to the asset cache."""
584590
# pylint: disable=no-member

flow360/component/simulation/validation/validation_output.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Validation for output parameters
33
"""
44

5+
import math
56
from typing import List, Literal, Union, get_args, get_origin
67

78
from flow360.component.simulation.models.volume_models import Fluid
@@ -13,6 +14,9 @@
1314
)
1415
from flow360.component.simulation.time_stepping.time_stepping import Steady
1516
from flow360.component.simulation.user_code.core.types import Expression
17+
from flow360.component.simulation.validation.validation_utils import (
18+
customize_model_validator_error,
19+
)
1620

1721

1822
def _check_output_fields(params):
@@ -243,3 +247,39 @@ def _check_unique_surface_volume_probe_entity_names(params):
243247
active_entity_names.add(entity.name)
244248

245249
return params
250+
251+
252+
def _check_moving_statistic_applicability(params):
253+
254+
if not params.time_stepping:
255+
return params
256+
257+
if not params.outputs:
258+
return params
259+
260+
is_steady = isinstance(params.time_stepping, Steady)
261+
max_steps = params.time_stepping.max_steps if is_steady else params.time_stepping.steps
262+
263+
for output_index, output in enumerate(params.outputs):
264+
if not hasattr(output, "moving_statistic") or output.moving_statistic is None:
265+
continue
266+
moving_window_size_in_step = (
267+
output.moving_statistic.moving_window_size * 10
268+
if is_steady
269+
else output.moving_statistic.moving_window_size
270+
)
271+
start_step = (
272+
math.ceil(output.moving_statistic.start_step / 10) * 10
273+
if is_steady
274+
else output.moving_statistic.start_step
275+
)
276+
if moving_window_size_in_step + start_step > max_steps:
277+
raise customize_model_validator_error(
278+
model_instance=params,
279+
relative_location=("outputs", output_index, "moving_statistic"),
280+
message="`moving_statistic`'s moving_window_size + start_step exceeds "
281+
"the total number of steps in the simulation.",
282+
input_value=output.moving_statistic.model_dump(),
283+
)
284+
285+
return params

0 commit comments

Comments
 (0)