Skip to content

Commit 523c102

Browse files
Average per body (#690)
Adds an averaging_config to the _get_results and _get_results_workflow functions of static_mechanical_simulation transient_mechanical_simulation modal_mechanical_simulation harmonic_mechanical_simulation By default the averaging happens across bodies, which is the current behavior. If average_per_body is activated, the results are averaged per body and the resulting Dataframe/FieldsContainer contains separate fields for each body (with the a tuple of the selected properties with which a body is identified). The properties by which bodies are identified are configurable. The tests use the properties "mat", "apdl_element_type", "elshape" and "apdl_real_id". The flag is currently not exposed for the results functions (stress, stress_nodal etc). The mesh support of the fields is always the full mesh.
1 parent 89971d3 commit 523c102

13 files changed

+747
-124
lines changed

src/ansys/dpf/post/fluid_simulation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
_connect_initial_results_inputs,
2424
)
2525
from ansys.dpf.post.result_workflows._sub_workflows import _create_norm_workflow
26-
from ansys.dpf.post.result_workflows._utils import _append_workflows
26+
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
2727
from ansys.dpf.post.selection import Selection, _WfNames
2828
from ansys.dpf.post.simulation import Simulation
2929
from ansys.dpf.post.species import SpeciesDict
@@ -254,6 +254,7 @@ def _get_result_workflow(
254254

255255
_connect_initial_results_inputs(
256256
initial_result_workflow=initial_result_workflow,
257+
split_by_body_workflow=None,
257258
force_elemental_nodal=False,
258259
location=location,
259260
selection=selection,
@@ -262,6 +263,7 @@ def _get_result_workflow(
262263
mesh=self.mesh._meshed_region,
263264
streams_provider=self._model.metadata.streams_provider,
264265
data_sources=self._model.metadata.data_sources,
266+
averaging_config=AveragingConfig(),
265267
)
266268

267269
query_regions_meshes = False

src/ansys/dpf/post/harmonic_mechanical_simulation.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
_connect_averaging_eqv_and_principal_workflows,
3131
_connect_initial_results_inputs,
3232
)
33-
from ansys.dpf.post.result_workflows._utils import _append_workflows
33+
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
3434
from ansys.dpf.post.selection import Selection, _WfNames
3535
from ansys.dpf.post.simulation import MechanicalSimulation
3636

@@ -50,6 +50,7 @@ def _get_result_workflow(
5050
selection: Union[Selection, None] = None,
5151
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
5252
phase_angle_cyclic: Union[float, None] = None,
53+
averaging_config: AveragingConfig = AveragingConfig(),
5354
) -> (dpf.Workflow, Union[str, list[str], None], str):
5455
"""Generate (without evaluating) the Workflow to extract results."""
5556
result_workflow_inputs = _create_result_workflow_inputs(
@@ -60,9 +61,9 @@ def _get_result_workflow(
6061
location=location,
6162
selection=selection,
6263
create_operator_callable=self._model.operator,
63-
mesh_provider=self._model.metadata.mesh_provider,
6464
amplitude=amplitude,
6565
sweeping_phase=sweeping_phase,
66+
averaging_config=averaging_config,
6667
)
6768
result_workflows = _create_result_workflows(
6869
server=self._model._server,
@@ -71,6 +72,7 @@ def _get_result_workflow(
7172
)
7273
_connect_initial_results_inputs(
7374
initial_result_workflow=result_workflows.initial_result_workflow,
75+
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
7476
selection=selection,
7577
data_sources=self._model.metadata.data_sources,
7678
streams_provider=self._model.metadata.streams_provider,
@@ -79,14 +81,9 @@ def _get_result_workflow(
7981
mesh=self.mesh._meshed_region,
8082
location=location,
8183
force_elemental_nodal=result_workflows.force_elemental_nodal,
84+
averaging_config=averaging_config,
8285
)
8386

84-
if result_workflows.mesh_workflow:
85-
selection.spatial_selection._selection.connect_with(
86-
result_workflows.mesh_workflow,
87-
output_input_names={_WfNames.initial_mesh: _WfNames.initial_mesh},
88-
)
89-
9087
output_wf = _connect_averaging_eqv_and_principal_workflows(result_workflows)
9188

9289
output_wf = _append_workflows(
@@ -129,6 +126,7 @@ def _get_result(
129126
phase_angle_cyclic: Union[float, None] = None,
130127
external_layer: Union[bool, List[int]] = False,
131128
skin: Union[bool, List[int]] = False,
129+
averaging_config: AveragingConfig = AveragingConfig(),
132130
) -> DataFrame:
133131
"""Extract results from the simulation.
134132
@@ -201,6 +199,10 @@ def _get_result(
201199
is computed over list of elements (not supported for cyclic symmetry). Getting the
202200
skin on more than one result (several time freq sets, split data...) is only
203201
supported starting with Ansys 2023R2.
202+
averaging_config:
203+
Per default averaging happens across all bodies. The averaging config
204+
can define that averaging happens per body and defines the properties that
205+
are used to define a body.
204206
205207
Returns
206208
-------
@@ -260,6 +262,7 @@ def _get_result(
260262
selection=selection,
261263
expand_cyclic=expand_cyclic,
262264
phase_angle_cyclic=phase_angle_cyclic,
265+
averaging_config=averaging_config,
263266
)
264267

265268
# Evaluate the workflow

src/ansys/dpf/post/modal_mechanical_simulation.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
_connect_averaging_eqv_and_principal_workflows,
2222
_connect_initial_results_inputs,
2323
)
24-
from ansys.dpf.post.result_workflows._utils import _append_workflows
24+
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
2525
from ansys.dpf.post.selection import Selection, _WfNames
2626
from ansys.dpf.post.simulation import MechanicalSimulation
2727

@@ -39,6 +39,7 @@ def _get_result_workflow(
3939
selection: Union[Selection, None] = None,
4040
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
4141
phase_angle_cyclic: Union[float, None] = None,
42+
averaging_config: AveragingConfig = AveragingConfig(),
4243
) -> (dpf.Workflow, Union[str, list[str], None], str):
4344
"""Generate (without evaluating) the Workflow to extract results."""
4445
result_workflow_inputs = _create_result_workflow_inputs(
@@ -49,7 +50,7 @@ def _get_result_workflow(
4950
location=location,
5051
selection=selection,
5152
create_operator_callable=self._model.operator,
52-
mesh_provider=self._model.metadata.mesh_provider,
53+
averaging_config=averaging_config,
5354
)
5455
result_workflows = _create_result_workflows(
5556
server=self._model._server,
@@ -58,6 +59,7 @@ def _get_result_workflow(
5859
)
5960
_connect_initial_results_inputs(
6061
initial_result_workflow=result_workflows.initial_result_workflow,
62+
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
6163
selection=selection,
6264
data_sources=self._model.metadata.data_sources,
6365
streams_provider=self._model.metadata.streams_provider,
@@ -66,14 +68,9 @@ def _get_result_workflow(
6668
mesh=self.mesh._meshed_region,
6769
location=location,
6870
force_elemental_nodal=result_workflows.force_elemental_nodal,
71+
averaging_config=averaging_config,
6972
)
7073

71-
if result_workflows.mesh_workflow:
72-
selection.spatial_selection._selection.connect_with(
73-
result_workflows.mesh_workflow,
74-
output_input_names={_WfNames.initial_mesh: _WfNames.initial_mesh},
75-
)
76-
7774
output_wf = _connect_averaging_eqv_and_principal_workflows(result_workflows)
7875

7976
output_wf = _append_workflows(
@@ -110,6 +107,7 @@ def _get_result(
110107
phase_angle_cyclic: Union[float, None] = None,
111108
external_layer: Union[bool, List[int]] = False,
112109
skin: Union[bool, List[int]] = False,
110+
averaging_config: AveragingConfig = AveragingConfig(),
113111
) -> DataFrame:
114112
"""Extract results from the simulation.
115113
@@ -175,6 +173,10 @@ def _get_result(
175173
is computed over list of elements (not supported for cyclic symmetry). Getting the
176174
skin on more than one result (several time freq sets, split data...) is only
177175
supported starting with Ansys 2023R2.
176+
averaging_config:
177+
Per default averaging happens across all bodies. The averaging config
178+
can define that averaging happens per body and defines the properties that
179+
are used to define a body.
178180
179181
Returns
180182
-------
@@ -222,6 +224,7 @@ def _get_result(
222224
selection=selection,
223225
expand_cyclic=expand_cyclic,
224226
phase_angle_cyclic=phase_angle_cyclic,
227+
averaging_config=averaging_config,
225228
)
226229

227230
# Evaluate the workflow

src/ansys/dpf/post/result_workflows/_build_workflow.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dataclasses
2-
from typing import Any, Callable, List, Optional, Union
2+
from typing import Callable, List, Optional, Union
33

44
from ansys.dpf.core import Operator, Workflow
55
from ansys.dpf.core.available_result import _result_properties
@@ -14,12 +14,15 @@
1414
_create_equivalent_workflow,
1515
_create_extract_component_workflow,
1616
_create_initial_result_workflow,
17-
_create_mesh_workflow,
1817
_create_norm_workflow,
1918
_create_principal_workflow,
19+
_create_split_scope_by_body_workflow,
2020
_create_sweeping_phase_workflow,
2121
)
22-
from ansys.dpf.post.result_workflows._utils import _CreateOperatorCallable
22+
from ansys.dpf.post.result_workflows._utils import (
23+
AveragingConfig,
24+
_CreateOperatorCallable,
25+
)
2326
from ansys.dpf.post.selection import Selection, _WfNames
2427

2528

@@ -49,8 +52,6 @@ class ResultWorkflows:
4952
compute_equivalent_before_average: bool = False
5053
# List of component names at the end of the workflow. If None, the result is a scalar.
5154
components: Optional[list[str]] = None
52-
# Workflow to compute the mesh
53-
mesh_workflow: Optional[Workflow] = None
5455
# Workflow to compute the principal components of the result
5556
principal_workflow: Optional[Workflow] = None
5657
# Workflow to compute the equivalent result
@@ -61,6 +62,7 @@ class ResultWorkflows:
6162
component_extraction_workflow: Optional[Workflow] = None
6263
# Workflow to sweep the phase of the result
6364
sweeping_phase_workflow: Optional[Workflow] = None
65+
split_by_bodies_workflow: Optional[Workflow] = None
6466

6567

6668
@dataclasses.dataclass
@@ -69,11 +71,6 @@ class _AveragingWorkflowInputs:
6971
force_elemental_nodal: bool
7072

7173

72-
@dataclasses.dataclass
73-
class _MeshWorkflowInputs:
74-
mesh_provider: Any
75-
76-
7774
@dataclasses.dataclass
7875
class _SweepingPhaseWorkflowInputs:
7976
amplitude: bool = (False,)
@@ -91,7 +88,7 @@ class _CreateWorkflowInputs:
9188
component_names: list[str]
9289
components_to_extract: list[int]
9390
should_extract_components: bool
94-
mesh_workflow_inputs: Optional[_MeshWorkflowInputs] = None
91+
averaging_config: AveragingConfig
9592
sweeping_phase_workflow_inputs: Optional[_SweepingPhaseWorkflowInputs] = None
9693

9794

@@ -101,8 +98,16 @@ def _requires_manual_averaging(
10198
category: ResultCategory,
10299
selection: Optional[Selection],
103100
create_operator_callable: Callable[[str], Operator],
101+
average_per_body: bool,
104102
):
105103
res = _result_properties[base_name] if base_name in _result_properties else None
104+
native_location = res["location"] if res is not None else None
105+
106+
if average_per_body and (
107+
native_location == locations.elemental
108+
or native_location == locations.elemental_nodal
109+
):
110+
return True
106111
if category == ResultCategory.equivalent and base_name[0] == "E": # strain eqv
107112
return True
108113
if res is not None and selection is not None:
@@ -148,12 +153,6 @@ def _create_result_workflows(
148153
components=create_workflow_inputs.component_names,
149154
)
150155

151-
if create_workflow_inputs.mesh_workflow_inputs is not None:
152-
result_workflows.mesh_workflow = _create_mesh_workflow(
153-
mesh_provider=create_workflow_inputs.mesh_workflow_inputs.mesh_provider,
154-
server=server,
155-
)
156-
157156
if create_workflow_inputs.has_principal:
158157
result_workflows.principal_workflow = _create_principal_workflow(
159158
components_to_extract=create_workflow_inputs.components_to_extract,
@@ -207,6 +206,15 @@ def _create_result_workflows(
207206
sweeping_phase=create_workflow_inputs.sweeping_phase_workflow_inputs.sweeping_phase,
208207
)
209208

209+
avg_config = create_workflow_inputs.averaging_config
210+
if avg_config.average_per_body:
211+
result_workflows.split_by_bodies_workflow = (
212+
_create_split_scope_by_body_workflow(
213+
server=server,
214+
body_defining_properties=avg_config.body_defining_properties,
215+
)
216+
)
217+
210218
return result_workflows
211219

212220

@@ -218,7 +226,7 @@ def _create_result_workflow_inputs(
218226
norm: bool,
219227
selection: Selection,
220228
create_operator_callable: Callable[[str], Operator],
221-
mesh_provider: Any,
229+
averaging_config: AveragingConfig,
222230
amplitude: bool = False,
223231
sweeping_phase: Union[float, None] = 0.0,
224232
) -> _CreateWorkflowInputs:
@@ -233,17 +241,14 @@ def _create_result_workflow_inputs(
233241
category=category,
234242
selection=selection,
235243
create_operator_callable=create_operator_callable,
244+
average_per_body=averaging_config.average_per_body,
236245
)
237246

238247
averaging_workflow_inputs = _AveragingWorkflowInputs(
239248
location=location,
240249
force_elemental_nodal=force_elemental_nodal,
241250
)
242251

243-
mesh_workflow_inputs: Optional[_MeshWorkflowInputs] = None
244-
if selection.spatial_selection.requires_mesh:
245-
mesh_workflow_inputs = _MeshWorkflowInputs(mesh_provider=mesh_provider)
246-
247252
has_principal = category == ResultCategory.principal
248253

249254
should_extract_components = (
@@ -266,7 +271,7 @@ def _create_result_workflow_inputs(
266271
components_to_extract=components_to_extract,
267272
should_extract_components=should_extract_components,
268273
has_principal=has_principal,
269-
mesh_workflow_inputs=mesh_workflow_inputs,
270274
has_equivalent=category == ResultCategory.equivalent,
271275
sweeping_phase_workflow_inputs=sweeping_phase_workflow_inputs,
276+
averaging_config=averaging_config,
272277
)

src/ansys/dpf/post/result_workflows/_connect_workflow_inputs.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
from typing import Any
1+
from typing import Any, Optional
22

33
from ansys.dpf.core import MeshedRegion, Scoping, ScopingsContainer, Workflow
44

55
from ansys.dpf.post.result_workflows._build_workflow import ResultWorkflows
6+
from ansys.dpf.post.result_workflows._sub_workflows import (
7+
_enrich_mesh_with_property_fields,
8+
)
9+
from ansys.dpf.post.result_workflows._utils import AveragingConfig
610
from ansys.dpf.post.selection import Selection, _WfNames
711

812

@@ -71,6 +75,7 @@ def _connect_cyclic_inputs(expand_cyclic, phase_angle_cyclic, result_wf: Workflo
7175

7276
def _connect_initial_results_inputs(
7377
initial_result_workflow: Workflow,
78+
split_by_body_workflow: Optional[Workflow],
7479
force_elemental_nodal: bool,
7580
location: str,
7681
selection: Selection,
@@ -79,14 +84,36 @@ def _connect_initial_results_inputs(
7984
mesh: MeshedRegion,
8085
streams_provider: Any,
8186
data_sources: Any,
87+
averaging_config: AveragingConfig,
8288
):
8389
"""Connects the inputs of the initial result workflow.
8490
8591
The initial result workflow is the first workflow in the result workflows chain, which
8692
extracts the raw results from the data sources.
8793
"""
94+
selection_wf = selection.spatial_selection._selection
95+
96+
if selection.spatial_selection.requires_mesh:
97+
selection_wf.connect(_WfNames.initial_mesh, mesh)
98+
99+
if averaging_config.average_per_body:
100+
_enrich_mesh_with_property_fields(
101+
mesh, averaging_config.body_defining_properties, streams_provider
102+
)
103+
104+
if split_by_body_workflow is not None:
105+
split_by_body_workflow.connect(_WfNames.mesh, mesh)
106+
if force_elemental_nodal:
107+
split_by_body_workflow.connect(_WfNames.scoping_location, "ElementalNodal")
108+
else:
109+
split_by_body_workflow.connect(_WfNames.scoping_location, location)
110+
split_by_body_workflow.connect_with(
111+
selection_wf, output_input_names={_WfNames.scoping: _WfNames.scoping}
112+
)
113+
selection_wf = split_by_body_workflow
114+
88115
initial_result_workflow.connect_with(
89-
selection.spatial_selection._selection,
116+
selection_wf,
90117
output_input_names={"scoping": "mesh_scoping"},
91118
)
92119

0 commit comments

Comments
 (0)