diff --git a/flow360/component/simulation/outputs/outputs.py b/flow360/component/simulation/outputs/outputs.py index d6dcb60ae..fc1ef4e7f 100644 --- a/flow360/component/simulation/outputs/outputs.py +++ b/flow360/component/simulation/outputs/outputs.py @@ -134,6 +134,15 @@ class SliceOutput(_AnimationAndFileFormatSettings): output_type: Literal["SliceOutput"] = pd.Field("SliceOutput", frozen=True) +class TimeAverageSliceOutput(SliceOutput): + """TimeAverageSliceOutput output settings.""" + + start_step: Union[pd.NonNegativeInt, Literal[-1]] = pd.Field( + default=-1, description="Physical time step to start calculating averaging" + ) + output_type: Literal["TimeAverageSliceOutput"] = pd.Field("TimeAverageSliceOutput", frozen=True) + + class IsosurfaceOutput(_AnimationAndFileFormatSettings): """Isosurface output settings.""" @@ -214,6 +223,7 @@ class UserDefinedFields(Flow360BaseModel): VolumeOutput, TimeAverageVolumeOutput, SliceOutput, + TimeAverageSliceOutput, IsosurfaceOutput, SurfaceIntegralOutput, ProbeOutput, diff --git a/flow360/component/simulation/translator/solver_translator.py b/flow360/component/simulation/translator/solver_translator.py index cb58440e8..633a80690 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -41,6 +41,7 @@ SurfaceIntegralOutput, SurfaceOutput, SurfaceProbeOutput, + TimeAverageSliceOutput, TimeAverageSurfaceOutput, TimeAverageVolumeOutput, VolumeOutput, @@ -57,6 +58,7 @@ remove_units_in_dict, replace_dict_key, translate_setting_and_apply_to_all_entities, + update_dict_recursively, ) from flow360.component.simulation.unit_system import LengthType from flow360.exceptions import Flow360TranslationError @@ -74,7 +76,6 @@ def init_non_average_output( has_average_capability: bool, ): """Initialize the common output attribute for non-average output.""" - has_average_capability = class_type.__name__.endswith(("VolumeOutput", "SurfaceOutput")) if has_average_capability: base["computeTimeAverages"] = False @@ -316,17 +317,19 @@ def translate_surface_output( return surface_output -def translate_slice_isosurface_output( +def translate_slice_output( output_params: list, - output_class: Union[SliceOutput, IsosurfaceOutput], - entities_name_key: str, + output_class: Union[SliceOutput, TimeAverageSliceOutput], injection_function, ): """Translate slice or isosurface output settings.""" translated_output = init_output_base( - output_params, output_class, has_average_capability=False, is_average=False + output_params, + output_class, + has_average_capability=True, + is_average=output_class is TimeAverageSliceOutput, ) - translated_output[entities_name_key] = translate_setting_and_apply_to_all_entities( + translated_output["slices"] = translate_setting_and_apply_to_all_entities( output_params, output_class, translation_func=translate_output_fields, @@ -336,6 +339,27 @@ def translate_slice_isosurface_output( return translated_output +def translate_isosurface_output( + output_params: list, + injection_function, +): + """Translate slice or isosurface output settings.""" + translated_output = init_output_base( + output_params, + IsosurfaceOutput, + has_average_capability=False, + is_average=False, + ) + translated_output["isoSurfaces"] = translate_setting_and_apply_to_all_entities( + output_params, + IsosurfaceOutput, + translation_func=translate_output_fields, + to_list=False, + entity_injection_func=injection_function, + ) + return translated_output + + def translate_monitor_output( output_params: list, monitor_type, injection_function, translation_func=translate_output_fields ): @@ -407,20 +431,28 @@ def translate_output(input_params: SimulationParams, translated: dict): outputs, TimeAverageSurfaceOutput, translated ) # Merge - surface_output.update(**surface_output_average) + update_dict_recursively(surface_output, surface_output_average) if surface_output: translated["surfaceOutput"] = add_unused_output_settings_for_comparison(surface_output) ##:: Step3: Get translated["sliceOutput"] + slice_output = {} + slice_output_average = {} if has_instance_in_list(outputs, SliceOutput): - translated["sliceOutput"] = translate_slice_isosurface_output( - outputs, SliceOutput, "slices", inject_slice_info + slice_output = translate_slice_output(outputs, SliceOutput, inject_slice_info) + if has_instance_in_list(outputs, TimeAverageSliceOutput): + slice_output_average = translate_slice_output( + outputs, TimeAverageSliceOutput, inject_slice_info ) + # Merge + update_dict_recursively(slice_output, slice_output_average) + if slice_output: + translated["sliceOutput"] = add_unused_output_settings_for_comparison(slice_output) ##:: Step4: Get translated["isoSurfaceOutput"] if has_instance_in_list(outputs, IsosurfaceOutput): - translated["isoSurfaceOutput"] = translate_slice_isosurface_output( - outputs, IsosurfaceOutput, "isoSurfaces", inject_isosurface_info + translated["isoSurfaceOutput"] = translate_isosurface_output( + outputs, inject_isosurface_info ) ##:: Step5: Get translated["monitorOutput"] diff --git a/tests/simulation/translator/ref/Flow360_om6Wing.json b/tests/simulation/translator/ref/Flow360_om6Wing.json index e9b3df95f..122f30ae7 100644 --- a/tests/simulation/translator/ref/Flow360_om6Wing.json +++ b/tests/simulation/translator/ref/Flow360_om6Wing.json @@ -51,6 +51,10 @@ "sliceOutput": { "animationFrequency": -1, "animationFrequencyOffset": 0, + "animationFrequencyTimeAverage": -1, + "animationFrequencyTimeAverageOffset": 0, + "computeTimeAverages": false, + "startAverageIntegrationStep": -1, "outputFormat": "tecplot", "outputFields": [], "slices": { diff --git a/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel.json b/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel.json index 3ca1a4cc3..21cb9d7e2 100644 --- a/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel.json +++ b/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel.json @@ -56,8 +56,12 @@ "sliceOutput": { "animationFrequency": -1, "animationFrequencyOffset": 0, + "animationFrequencyTimeAverage": -1, + "animationFrequencyTimeAverageOffset": 0, + "computeTimeAverages": false, "outputFormat": "tecplot", "outputFields": [], + "startAverageIntegrationStep": -1, "slices": { "sliceName_1": { "outputFields": [ diff --git a/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel_expression.json b/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel_expression.json index 007b04bd3..9007f06ae 100644 --- a/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel_expression.json +++ b/tests/simulation/translator/ref/Flow360_om6wing_FS_with_vel_expression.json @@ -56,8 +56,12 @@ "sliceOutput": { "animationFrequency": -1, "animationFrequencyOffset": 0, + "animationFrequencyTimeAverage": -1, + "animationFrequencyTimeAverageOffset": 0, + "computeTimeAverages": false, "outputFormat": "tecplot", "outputFields": [], + "startAverageIntegrationStep": -1, "slices": { "sliceName_1": { "outputFields": [ diff --git a/tests/simulation/translator/test_output_translation.py b/tests/simulation/translator/test_output_translation.py index 93fd120e2..ec4bafd63 100644 --- a/tests/simulation/translator/test_output_translation.py +++ b/tests/simulation/translator/test_output_translation.py @@ -106,54 +106,6 @@ def test_volume_output(volume_output_config, avg_volume_output_config): assert sorted(ref["volumeOutput"].items()) == sorted(translated["volumeOutput"].items()) -@pytest.fixture() -def surface_output_config(): - return ( - [ - SurfaceOutput( # Global - frequency=11, - frequency_offset=21, - output_format="paraview", - output_fields=["vorticity", "mutRatio"], - ), - SurfaceOutput( # Local - frequency=11, - frequency_offset=21, - entities=[Surface(name="surface1"), Surface(name="surface2")], - output_fields=["Cp"], - ), - SurfaceOutput( # Local - frequency=11, - frequency_offset=21, - entities=[ - Surface(name="surface11", private_attribute_full_name="ZoneName/surface11"), - Surface(name="surface22"), - ], - output_fields=["T"], - ), - ], - { - "animationFrequency": 11, - "animationFrequencyOffset": 21, - "animationFrequencyTimeAverage": -1, - "animationFrequencyTimeAverageOffset": 0, - "computeTimeAverages": False, - "outputFields": [], - "outputFormat": "paraview", - "startAverageIntegrationStep": -1, - "surfaces": { - "surface1": {"outputFields": ["Cp", "vorticity", "mutRatio"]}, - "ZoneName/surface11": {"outputFields": ["T", "vorticity", "mutRatio"]}, - "surface2": {"outputFields": ["Cp", "vorticity", "mutRatio"]}, - "surface22": {"outputFields": ["T", "vorticity", "mutRatio"]}, - "Wall1": {"outputFields": ["vorticity", "mutRatio"]}, - "Wall2": {"outputFields": ["vorticity", "mutRatio"]}, - }, - "writeSingleFile": False, - }, - ) - - @pytest.fixture() def surface_output_config(): return ( @@ -201,13 +153,13 @@ def avg_surface_output_config(): output_fields=["Cp"], ), TimeAverageSurfaceOutput( # Local - entities=[Surface(name="surface11"), Surface(name="surface22")], + entities=[Surface(name="surface3")], output_fields=["T"], ), ] -def test_surface_ouput( +def test_surface_output( surface_output_config, avg_surface_output_config, ): @@ -237,6 +189,7 @@ def test_surface_ouput( "surface11": {"outputFields": ["T"]}, "surface2": {"outputFields": ["Cp"]}, "surface22": {"outputFields": ["T"]}, + "surface3": {"outputFields": ["T"]}, }, "writeSingleFile": False, } @@ -287,6 +240,10 @@ def sliceoutput_config(): { "animationFrequency": 33, "animationFrequencyOffset": 22, + "animationFrequencyTimeAverage": -1, + "animationFrequencyTimeAverageOffset": 0, + "startAverageIntegrationStep": -1, + "computeTimeAverages": False, "outputFields": [], "outputFormat": "tecplot", "slices": {