Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
cb001c3
Initial forceOutput interface
angranl-flex Oct 9, 2025
43e3e8a
Update description of coefficients
angranl-flex Oct 13, 2025
1733a47
include forceOutput to init
angranl-flex Oct 15, 2025
2d690ef
Update the translator logic to support computation of BET/AD/PM coeff…
angranl-flex Oct 17, 2025
36d49e5
update remote file name for porous medium coefficients
angranl-flex Oct 20, 2025
dcffa19
Use surface models to define ForceOutput and add corresponding valida…
angranl-flex Oct 20, 2025
482f90a
unify remote file names for coefficients csv
angranl-flex Oct 21, 2025
4069475
Fix the bug when preprocess surface models with id
angranl-flex Oct 21, 2025
c7f2e25
keep the same pseudo_step and physical_step column order in the outpu…
angranl-flex Oct 22, 2025
30a1d6b
translate stop criterion setting to solver json and perform checking …
angranl-flex Oct 23, 2025
e8f7b13
Fix solver translator
angranl-flex Oct 26, 2025
d2ff28e
update solver translator for stopping criteria and fix unit test
angranl-flex Oct 27, 2025
7f2201a
add BET/AD/PM support in ForceOutput
angranl-flex Oct 28, 2025
eab5333
Fix unit test
angranl-flex Oct 28, 2025
a664eb2
dump a separate json for columnar data postprocessing
angranl-flex Oct 28, 2025
7ec5797
Fix that stopping criterion without moving statistic in MonitorOutput…
angranl-flex Oct 28, 2025
b7e7c39
update solver translator
angranl-flex Oct 29, 2025
f919bc4
Update hash calculation logic
angranl-flex Oct 29, 2025
b773241
Add validation and unit test for ForceOutput
angranl-flex Oct 30, 2025
a3ed7b5
Address comments
angranl-flex Nov 1, 2025
916c259
Enable pylint at the correct location
angranl-flex Nov 1, 2025
e561338
Remove unnecessary str input for ForceOutput and StoppingCriterion
angranl-flex Nov 3, 2025
5a51026
Move parse_model_dict forward to ensure the multiconstructor models a…
angranl-flex Nov 4, 2025
41f2978
update solver translator for more flexible dataset selection of stopp…
angranl-flex Nov 7, 2025
eae3f33
Address comments
angranl-flex Nov 12, 2025
94ee7fd
Fix unit test after rebase
angranl-flex Nov 14, 2025
2200be5
Moving statistic improvement based on QA testing
angranl-flex Nov 14, 2025
65d43f5
Add note for standard deviation computation
angranl-flex Nov 17, 2025
6c24254
Add description to explain the use of tolerance window size in stoppi…
angranl-flex Nov 18, 2025
bbd1fa7
Add stopping criterion support for imported surface integral
angranl-flex Nov 19, 2025
93ebb71
Adopt contextual_field validator and fix unit test
angranl-flex Dec 2, 2025
7536309
Address comments 1
angranl-flex Dec 12, 2025
c063fd5
Update movingStatistics method name from "std" and "deviation" to "st…
angranl-flex Dec 12, 2025
92f85ad
Add helper function to handle unit conversion in translator
angranl-flex Dec 12, 2025
4acb874
Add todo item and remove unused script
angranl-flex Dec 12, 2025
95d29e0
Add deserialized the models and outputs instead of their raw dict to …
angranl-flex Dec 15, 2025
f8cd819
Remove the logic to automatically add monitor_output from stopping cr…
angranl-flex Dec 15, 2025
35b045f
Update validation context framework for multi satge validation implem…
angranl-flex Dec 15, 2025
e6bf758
Store model and output id to ForceOutput and StoppingCriterion, fix t…
angranl-flex Dec 15, 2025
73e2c06
Raise ValueError when required attribute does not exist and add unit …
angranl-flex Dec 17, 2025
264c6cd
Merge branch 'main' into angran/forceOutput
angranl-flex Dec 17, 2025
c599831
Add docstring to explain "range" method in MovingStatistics
angranl-flex Dec 17, 2025
4f5aded
Merge branch 'main' into angran/forceOutput
angranl-flex Dec 17, 2025
b4f11a0
Fix typo
angranl-flex Dec 17, 2025
e42ef0a
Merge remote-tracking branch 'origin/main' into angran/forceOutput
angranl-flex Dec 17, 2025
3d0025f
Merge branch 'main' into angran/forceOutput
angranl-flex Dec 24, 2025
313d64d
Merge branch 'main' into angran/forceOutput
angranl-flex Dec 24, 2025
fcf2142
Fix an error that incorrectly parses the monitored column name for st…
angranl-flex Dec 24, 2025
2cef4e9
Merge remote-tracking branch 'origin/main' into angran/forceOutput
angranl-flex Dec 24, 2025
92887fc
Merge branch 'main' into angran/forceOutput
angranl-flex Dec 24, 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: 2 additions & 0 deletions flow360/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
from flow360.component.simulation.outputs.outputs import (
AeroAcousticOutput,
ForceDistributionOutput,
ForceOutput,
IsosurfaceOutput,
MovingStatistic,
Observer,
Expand Down Expand Up @@ -367,6 +368,7 @@
"CentralBelt",
"WheelBelts",
"show_available_examples",
"ForceOutput",
"RenderOutput",
"RenderOutputGroup",
"render_config",
Expand Down
27 changes: 0 additions & 27 deletions flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from flow360.component.simulation.framework.entity_base import EntityList
from flow360.component.simulation.framework.param_utils import AssetCache
from flow360.component.simulation.outputs.outputs import (
MonitorOutputType,
SurfaceIntegralOutput,
SurfaceOutput,
)
Expand Down Expand Up @@ -551,31 +550,6 @@ def _set_up_default_reference_geometry(params: SimulationParams, length_unit: Le
return params


def _set_up_monitor_output_from_stopping_criterion(params: SimulationParams):
"""
Setting up the monitor output in the stopping criterion if not provided in params.outputs.
"""
if not params.run_control:
return params
stopping_criterion = params.run_control.stopping_criteria
if not stopping_criterion:
return params
monitor_output_ids = []
if params.outputs is not None:
for output in params.outputs:
if not isinstance(output, get_args(get_args(MonitorOutputType)[0])):
continue
monitor_output_ids.append(output.private_attribute_id)
for criterion in stopping_criterion:
monitor_output = criterion.monitor_output
if isinstance(monitor_output, str):
continue
if monitor_output.private_attribute_id not in monitor_output_ids:
params.outputs.append(monitor_output)
monitor_output_ids.append(monitor_output.private_attribute_id)
return params


def set_up_params_for_uploading( # pylint: disable=too-many-arguments
root_asset,
length_unit: LengthType,
Expand Down Expand Up @@ -654,7 +628,6 @@ def set_up_params_for_uploading( # pylint: disable=too-many-arguments
params = _set_up_default_geometry_accuracy(root_asset, params, use_geometry_AI)

params = _set_up_default_reference_geometry(params, length_unit)
params = _set_up_monitor_output_from_stopping_criterion(params=params)

# Convert all reference of UserVariables to VariableToken
params = save_user_variables(params)
Expand Down
70 changes: 12 additions & 58 deletions flow360/component/results/case_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import re
from enum import Enum
from pathlib import Path
from typing import Callable, Dict, List, Optional
from typing import Callable, Dict, List, Optional, get_args

import numpy as np
import pydantic as pd
Expand All @@ -23,43 +23,22 @@
ResultTarGZModel,
)
from flow360.component.results.results_utils import (
_CD,
BETDiskCSVHeaderOperation,
DiskCoefficientsComputation,
PorousMediumCoefficientsComputation,
)
from flow360.component.simulation.conversion import unit_converter as unit_converter_v2
from flow360.component.simulation.outputs.output_fields import (
_CD_PER_STRIP,
_CD_PRESSURE,
_CD_SKIN_FRICTION,
_CL,
_CL_PRESSURE,
_CL_SKIN_FRICTION,
_CUMULATIVE_CD_CURVE,
_HEAT_FLUX,
_X,
_Y,
BETDiskCSVHeaderOperation,
DiskCoefficientsComputation,
PorousMediumCoefficientsComputation,
_CFx,
ForceOutputCoefficientNames,
_CFx_PER_SPAN,
_CFx_PRESSURE,
_CFx_SKIN_FRICTION,
_CFy,
_CFy_PRESSURE,
_CFy_SKIN_FRICTION,
_CFz,
_CFz_PER_SPAN,
_CFz_PRESSURE,
_CFz_SKIN_FRICTION,
_CMx,
_CMx_PRESSURE,
_CMx_SKIN_FRICTION,
_CMy,
_CMy_PER_SPAN,
_CMy_PRESSURE,
_CMy_SKIN_FRICTION,
_CMz,
_CMz_PRESSURE,
_CMz_SKIN_FRICTION,
)
from flow360.component.simulation.conversion import unit_converter as unit_converter_v2
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.unit_system import (
Flow360UnitSystem,
Expand Down Expand Up @@ -185,32 +164,7 @@ class SurfaceForcesResultCSVModel(PerEntityResultCSVModel, TimeSeriesResultCSVMo

remote_file_name: str = pd.Field(CaseDownloadable.SURFACE_FORCES.value, frozen=True)

_variables: List[str] = [
_CL,
_CD,
_CFx,
_CFy,
_CFz,
_CMx,
_CMy,
_CMz,
_CL_PRESSURE,
_CD_PRESSURE,
_CFx_PRESSURE,
_CFy_PRESSURE,
_CFz_PRESSURE,
_CMx_PRESSURE,
_CMy_PRESSURE,
_CMz_PRESSURE,
_CL_SKIN_FRICTION,
_CD_SKIN_FRICTION,
_CFx_SKIN_FRICTION,
_CFy_SKIN_FRICTION,
_CFz_SKIN_FRICTION,
_CMx_SKIN_FRICTION,
_CMy_SKIN_FRICTION,
_CMz_SKIN_FRICTION,
]
_variables: List[str] = list(get_args(ForceOutputCoefficientNames))

def _preprocess(self, filter_physical_steps_only: bool = True, include_time: bool = True):
"""
Expand Down Expand Up @@ -705,7 +659,7 @@ def _iterate_step_values(disk_name, disk_ctx, env, values):
class ActuatorDiskCoefficientsCSVModel(ResultCSVModel):
"""CSV model for actuator disk coefficients output."""

remote_file_name: str = pd.Field("actuator_disk_coefficients_v2.csv", frozen=True)
remote_file_name: str = pd.Field("actuatorDisk_force_coefficients_v2.csv", frozen=True)


class _BETDiskResults(_DimensionedCSVResultModel):
Expand Down Expand Up @@ -900,7 +854,7 @@ def _iterate_step_values(disk_name, disk_ctx, env, values):
class BETDiskCoefficientsCSVModel(ResultCSVModel):
"""CSV model for BET disk coefficients output."""

remote_file_name: str = pd.Field("bet_disk_coefficients_v2.csv", frozen=True)
remote_file_name: str = pd.Field("bet_force_coefficients_v2.csv", frozen=True)

def format_headers(
self, params: SimulationParams, pattern: str = "$BETName_$CylinderName"
Expand Down Expand Up @@ -984,7 +938,7 @@ def _iterate_step_values(zone_name, _, env, values):
class PorousMediumCoefficientsCSVModel(ResultCSVModel):
"""CSV model for porous medium coefficients output."""

remote_file_name: str = pd.Field("porous_medium_coefficients_v2.csv", frozen=True)
remote_file_name: str = pd.Field("porous_media_force_coefficients_v2.csv", frozen=True)


class BETForcesRadialDistributionResultCSVModel(OptionallyDownloadableResultCSVModel):
Expand Down
84 changes: 23 additions & 61 deletions flow360/component/results/results_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,21 @@
ResultCSVModel,
)
from flow360.component.simulation.models.volume_models import BETDisk
from flow360.component.simulation.outputs.output_fields import (
_CD,
_CL,
_CFx,
_CFy,
_CFz,
_CMx,
_CMy,
_CMz,
)
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.user_code.core.types import Expression
from flow360.exceptions import Flow360ValueError
from flow360.log import log

# pylint:disable=invalid-name
_CL = "CL"
_CD = "CD"
_CFx = "CFx"
_CFy = "CFy"
_CFz = "CFz"
_CMx = "CMx"
_CMy = "CMy"
_CMz = "CMz"
_CL_PRESSURE = "CLPressure"
_CD_PRESSURE = "CDPressure"
_CFx_PRESSURE = "CFxPressure"
_CFy_PRESSURE = "CFyPressure"
_CFz_PRESSURE = "CFzPressure"
_CMx_PRESSURE = "CMxPressure"
_CMy_PRESSURE = "CMyPressure"
_CMz_PRESSURE = "CMzPressure"
_CL_SKIN_FRICTION = "CLSkinFriction"
_CD_SKIN_FRICTION = "CDSkinFriction"
_CFx_SKIN_FRICTION = "CFxSkinFriction"
_CFy_SKIN_FRICTION = "CFySkinFriction"
_CFz_SKIN_FRICTION = "CFzSkinFriction"
_CMx_SKIN_FRICTION = "CMxSkinFriction"
_CMy_SKIN_FRICTION = "CMySkinFriction"
_CMz_SKIN_FRICTION = "CMzSkinFriction"
_CL_VISCOUS = "CLViscous"
_CD_VISCOUS = "CDViscous"
_CFx_VISCOUS = "CFxViscous"
_CFy_VISCOUS = "CFyViscous"
_CFz_VISCOUS = "CFzViscous"
_CMx_VISCOUS = "CMxViscous"
_CMy_VISCOUS = "CMyViscous"
_CMz_VISCOUS = "CMzViscous"
_HEAT_TRANSFER = "HeatTransfer"
_HEAT_FLUX = "HeatFlux"
_X = "X"
_Y = "Y"
_CUMULATIVE_CD_CURVE = "Cumulative_CD_Curve"
_CD_PER_STRIP = "CD_per_strip"
_CFx_PER_SPAN = "CFx_per_span"
_CFz_PER_SPAN = "CFz_per_span"
_CMy_PER_SPAN = "CMy_per_span"


# Static utilities for aerodynamic coefficient computations.

# Provides helper methods for computing aerodynamic coefficients using
Expand Down Expand Up @@ -161,7 +127,7 @@ def _get_lift_drag_direction(params: SimulationParams):


def _get_dynamic_pressure_in_flow360_unit(params: SimulationParams):
# pylint:disable=protected-access
# pylint:disable=protected-access,invalid-name

v_ref = params.reference_velocity

Expand Down Expand Up @@ -189,8 +155,10 @@ def _build_coeff_env(params) -> Dict[str, Any]:

def _copy_time_columns(src: Dict[str, list]) -> Dict[str, list]:
out: Dict[str, list] = {}
out[_PSEUDO_STEP] = src[_PSEUDO_STEP]
out[_PHYSICAL_STEP] = src[_PHYSICAL_STEP]
for key in src.keys():
if key in [_PSEUDO_STEP, _PHYSICAL_STEP]:
out[key] = src[key]
continue
return out


Expand Down Expand Up @@ -339,13 +307,6 @@ class PorousMediumCoefficientsComputation:
# pylint:disable=too-few-public-methods
"""Static utilities for porous medium coefficient computations."""

@staticmethod
def _copy_time_columns(src: Dict[str, list]) -> Dict[str, list]:
out: Dict[str, list] = {}
out[_PSEUDO_STEP] = src[_PSEUDO_STEP]
out[_PHYSICAL_STEP] = src[_PHYSICAL_STEP]
return out

@staticmethod
def _iter_zones(values: Dict[str, list]):
# Support both default "zone_<idx>_<...>" and arbitrary GenericVolume-style names
Expand Down Expand Up @@ -414,6 +375,7 @@ def compute_coefficients_static(

for zone_name in PorousMediumCoefficientsComputation._iter_zones(values):
PorousMediumCoefficientsComputation._init_zone_output(out, zone_name)
# pylint: disable=invalid-name
for CF, CM, CL_val, CD_val in iterate_step_values_func(zone_name, {}, env, values):
out[f"{zone_name}_{_CFx}"].append(CF[0])
out[f"{zone_name}_{_CFy}"].append(CF[1])
Expand All @@ -438,7 +400,7 @@ class BETDiskCSVHeaderOperation:

@staticmethod
def format_headers(
BETCSVModel: ResultCSVModel,
BETCSVModel: ResultCSVModel, # pylint: disable=invalid-name
params: SimulationParams,
pattern: str = "$BETName_$CylinderName",
) -> LocalResultCSVModel:
Expand Down Expand Up @@ -474,16 +436,16 @@ def format_headers(

disk_rename_map = {}

diskCount = 0
disk_count = 0
for disk in bet_disks:
# No draft entity (cylinder) selector so it is fine.
for disk_local_index, cylinder in enumerate(disk.entities.stored_entities):
new_name = pattern.replace("$BETName", disk.name)
new_name = new_name.replace("$CylinderName", cylinder.name)
new_name = new_name.replace("$DiskLocalIndex", str(disk_local_index))
new_name = new_name.replace("$DiskGlobalIndex", str(diskCount))
disk_rename_map[f"Disk{diskCount}"] = new_name
diskCount = diskCount + 1
new_name = new_name.replace("$DiskGlobalIndex", str(disk_count))
disk_rename_map[f"Disk{disk_count}"] = new_name
disk_count = disk_count + 1

for header, values in csv_data.items():
matched = False
Expand All @@ -494,5 +456,5 @@ def format_headers(
break
if not matched:
new_csv[header] = values
newModel = LocalResultCSVModel().from_dict(new_csv)
return newModel
new_model = LocalResultCSVModel().from_dict(new_csv)
return new_model
7 changes: 7 additions & 0 deletions flow360/component/simulation/framework/param_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,10 @@ def _set_boundary_full_name_with_zone_name(
continue
with model_attribute_unlock(surface, "private_attribute_full_name"):
surface.private_attribute_full_name = f"{give_zone_name}/{surface.name}"


def serialize_model_obj_to_id(model_obj: Flow360BaseModel) -> str:
"""Serialize a model object to its id."""
if hasattr(model_obj, "private_attribute_id"):
return model_obj.private_attribute_id
raise ValueError(f"The model object {model_obj} cannot be serialized to id.")
Loading
Loading