From 48e25c44242b7f9e594756896930f35e20ed1b26 Mon Sep 17 00:00:00 2001 From: NasserFlexCompute Date: Tue, 31 Mar 2026 20:12:46 +0000 Subject: [PATCH 1/2] fix: remove output fields subsumed by primitiveVars to prevent duplicate VTK DataArray names When both "primitiveVars" and "pressure"/"velocity" are in outputFields, the C++ FieldProcessor produces duplicate DataArray names ("p", "velocity") in VTK output. This causes ParaView to fail when loading a subset of fields. Strip "pressure" and "velocity" from the translated outputFields when "primitiveVars" is already present, since primitiveVars expands to rho + velocity + p in the solver. Made-with: Cursor --- .../simulation/outputs/output_fields.py | 30 ++++++++++++ .../translator/solver_translator.py | 2 + .../simulation/outputs/test_output_fields.py | 49 ++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/flow360/component/simulation/outputs/output_fields.py b/flow360/component/simulation/outputs/output_fields.py index 0897eceec..eefa593e4 100644 --- a/flow360/component/simulation/outputs/output_fields.py +++ b/flow360/component/simulation/outputs/output_fields.py @@ -575,3 +575,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 0fc97fa29..2509b866e 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -67,6 +67,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, @@ -304,6 +305,7 @@ 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)] + output_fields = remove_fields_subsumed_by_primitive_vars(output_fields) return {"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"] From 915dbab1bebc315f2fa035a128c4ca54ebaf4a75 Mon Sep 17 00:00:00 2001 From: NasserFlexCompute Date: Tue, 31 Mar 2026 20:40:26 +0000 Subject: [PATCH 2/2] fix: also apply primitiveVars dedup in translate_volume_output translate_volume_output() builds outputFields independently of translate_output_fields(), so it was missing the remove_fields_subsumed_by_primitive_vars() call. Apply the same filter and update volume output translation test expectations. Made-with: Cursor --- flow360/component/simulation/translator/solver_translator.py | 1 + tests/simulation/translator/test_output_translation.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/flow360/component/simulation/translator/solver_translator.py b/flow360/component/simulation/translator/solver_translator.py index 2509b866e..23ed6b5ad 100644 --- a/flow360/component/simulation/translator/solver_translator.py +++ b/flow360/component/simulation/translator/solver_translator.py @@ -534,6 +534,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/translator/test_output_translation.py b/tests/simulation/translator/test_output_translation.py index eb7b23dfd..379f8e7bc 100644 --- a/tests/simulation/translator/test_output_translation.py +++ b/tests/simulation/translator/test_output_translation.py @@ -88,7 +88,6 @@ def volume_output_config(vel_in_km_per_hr): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", "vorticity", @@ -125,7 +124,6 @@ def avg_volume_output_config(vel_in_km_per_hr): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", ], @@ -172,7 +170,6 @@ def test_volume_output(volume_output_config, avg_volume_output_config): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", "vorticity", @@ -190,7 +187,6 @@ def test_volume_output(volume_output_config, avg_volume_output_config): "betMetrics", "primitiveVars", "qcriterion", - "velocity", "velocity_in_km_per_hr", "velocity_magnitude", ],