diff --git a/flow360/component/simulation/outputs/output_fields.py b/flow360/component/simulation/outputs/output_fields.py index 1c87abb5e..9062b3695 100644 --- a/flow360/component/simulation/outputs/output_fields.py +++ b/flow360/component/simulation/outputs/output_fields.py @@ -567,3 +567,33 @@ def append_component_to_output_fields(output_fields: List[str]) -> List[str]: if field == "vorticity" and "vorticityMagnitude" not in output_fields: output_fields_with_component.append("vorticityMagnitude") return output_fields_with_component + + +# In the C++ solver, "primitiveVars" expands to DataArrays named "rho", "velocity", and "p". +# "pressure" and "velocity" individually produce DataArrays with the same names ("p" and +# "velocity"). Having both creates duplicate DataArray names in VTK output, which causes +# ParaView to fail when loading a subset of fields. +_FIELDS_SUBSUMED_BY_PRIMITIVE_VARS = {"pressure", "velocity"} + + +def remove_fields_subsumed_by_primitive_vars(output_fields: List[str]) -> List[str]: + """ + Remove output fields that are already included as sub-fields of ``primitiveVars``. + + Must be called after :func:`append_component_to_output_fields` so that auto-appended + fields like ``velocity_magnitude`` are already in the list before ``velocity`` is removed. + + Parameters: + ----------- + output_fields : List[str] + The list of output fields to deduplicate. + + Returns: + -------- + List[str] + The deduplicated list with ``pressure`` and ``velocity`` removed when + ``primitiveVars`` is present. + """ + if "primitiveVars" not in output_fields: + return output_fields + return [f for f in output_fields if f not in _FIELDS_SUBSUMED_BY_PRIMITIVE_VARS] diff --git a/flow360/component/simulation/translator/solver_translator.py b/flow360/component/simulation/translator/solver_translator.py index b9576ba02..75b6b1190 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -61,6 +61,7 @@ PREDEFINED_UDF_EXPRESSIONS, append_component_to_output_fields, generate_predefined_udf, + remove_fields_subsumed_by_primitive_vars, ) from flow360.component.simulation.outputs.outputs import ( AeroAcousticOutput, @@ -300,7 +301,12 @@ def translate_output_fields( output_fields.append(output_field.name) # Filter out the UserVariable Dicts output_fields = [item for item in output_fields if isinstance(item, str)] +<<<<<<< HEAD return {"outputFields": sorted(output_fields)} +======= + output_fields = remove_fields_subsumed_by_primitive_vars(output_fields) + return {"outputFields": output_fields} +>>>>>>> c3af3b35 ([BackPort] fix: remove output fields subsumed by primitiveVars to prevent duplic… (#1951)) def surface_probe_setting_translation_func(entity: Union[SurfaceProbeOutput, SurfaceSliceOutput]): @@ -526,6 +532,7 @@ def translate_volume_output( output_fields.append(output_field.name) # Filter out the UserVariable Dicts output_fields = [item for item in output_fields if isinstance(item, str)] + output_fields = remove_fields_subsumed_by_primitive_vars(output_fields) volume_output.update( { "outputFields": sorted(output_fields), diff --git a/tests/simulation/outputs/test_output_fields.py b/tests/simulation/outputs/test_output_fields.py index 925f0bc7a..1cd0920b8 100644 --- a/tests/simulation/outputs/test_output_fields.py +++ b/tests/simulation/outputs/test_output_fields.py @@ -2,7 +2,10 @@ import flow360 as fl from flow360 import SI_unit_system, u -from flow360.component.simulation.outputs.output_fields import generate_predefined_udf +from flow360.component.simulation.outputs.output_fields import ( + generate_predefined_udf, + remove_fields_subsumed_by_primitive_vars, +) @pytest.fixture @@ -137,3 +140,47 @@ def test_generate_field_udf_no_match(simulation_params): """Test behavior when no matching predefined expression is found.""" result = generate_predefined_udf("non_existent_field_for_udf", simulation_params) assert result is None + + +class TestRemoveFieldsSubsumedByPrimitiveVars: + def test_removes_pressure_and_velocity_when_primitive_vars_present(self): + fields = ["Cp", "primitiveVars", "pressure", "velocity", "Mach"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert "primitiveVars" in result + assert "pressure" not in result + assert "velocity" not in result + assert "Cp" in result + assert "Mach" in result + + def test_keeps_pressure_and_velocity_when_no_primitive_vars(self): + fields = ["pressure", "velocity", "Cp"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert result == ["pressure", "velocity", "Cp"] + + def test_preserves_velocity_magnitude_when_velocity_removed(self): + fields = ["primitiveVars", "velocity", "velocity_magnitude", "pressure"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert "velocity" not in result + assert "pressure" not in result + assert "velocity_magnitude" in result + assert "primitiveVars" in result + + def test_no_op_on_empty_list(self): + assert remove_fields_subsumed_by_primitive_vars([]) == [] + + def test_primitive_vars_only(self): + fields = ["primitiveVars"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert result == ["primitiveVars"] + + def test_removes_pressure_only_when_velocity_absent(self): + fields = ["primitiveVars", "pressure", "Cp"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert "pressure" not in result + assert result == ["primitiveVars", "Cp"] + + def test_removes_velocity_only_when_pressure_absent(self): + fields = ["primitiveVars", "velocity", "Mach"] + result = remove_fields_subsumed_by_primitive_vars(fields) + assert "velocity" not in result + assert result == ["primitiveVars", "Mach"] diff --git a/tests/simulation/translator/test_output_translation.py b/tests/simulation/translator/test_output_translation.py index 0d91fc21a..58daaed7a 100644 --- a/tests/simulation/translator/test_output_translation.py +++ b/tests/simulation/translator/test_output_translation.py @@ -86,7 +86,6 @@ def volume_output_config(vel_in_km_per_hr): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", "vorticity", @@ -123,7 +122,6 @@ def avg_volume_output_config(vel_in_km_per_hr): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", ], @@ -170,8 +168,11 @@ def test_volume_output(volume_output_config, avg_volume_output_config): "betMetrics", "primitiveVars", "qcriterion", +<<<<<<< HEAD "velocity", "velocity_in_km_per_hr", +======= +>>>>>>> c3af3b35 ([BackPort] fix: remove output fields subsumed by primitiveVars to prevent duplic… (#1951)) "velocity_magnitude", "vorticity", "vorticityMagnitude",