Skip to content

Commit 8b1354e

Browse files
Fix scoping if force_elemental_nodal is true. (#739)
When manual averaging is needed we request the data as ElementalNodal and perform the nodal averaging manually (force_elemental_nodal=True). If the mesh_scoping for a source operator (such as S) is nodal, and the requested location is ElementalNodal it returns the inclusive data (all elements that have at least one node in the scoping). This leads to one extra layer of data if the initial scope is nodal (see Nodal named selection with force_elemental_nodal yield too large selection #731) The initial scope is elemental but the requested data is nodal. This happens because _build_selection converts the elemental scoping into a nodal scoping when the requested result is nodal. In build selection, there was already a call to _requires_manual_averaging, based on which the location was modified. However, _requires_manual_averaging was always called with selection = None, which basically meant it always returns false and the location is not adapted. This PR adresses the problem by making the has_skin, and has_external_layer inputs explicit in the call to _requires_manual_averaging and passing the correct values in _build_selection. It also moves the call to _requires_manual_averaging outside of all the if statements so is called in the same way as in _get_results. The selection is now inclusive for nodes (from a nodal named selections or a node id array), to support the case of nodal selections that don't select all the nodes of an element. Depending on the selection this results in an additional "layer" of nodes in the result. This is solved by returning the original selection from _build_selection and applying it in the end of the results workflow. Please have another look. Notes: This problem was not often visible because manual averaging is mostly needed in the skin case, where the final result is rescoped to the skin. It is more apparent with the new average per solid feature I removed the requires_manual_averaging method from selection and spatial selection, because it is incomplete. Some decisions depend on the result type (is it a strain result?). If these methods are needed for backward compatibility I can add calls to the new generic method with dummy arguments for the result types. Also #fixes #731
1 parent 81b9514 commit 8b1354e

12 files changed

+577
-141
lines changed

src/ansys/dpf/post/fluid_simulation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
_create_components,
2121
)
2222
from ansys.dpf.post.result_workflows._connect_workflow_inputs import (
23-
_connect_initial_results_inputs,
23+
_connect_workflow_inputs,
2424
)
2525
from ansys.dpf.post.result_workflows._sub_workflows import _create_norm_workflow
2626
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
@@ -252,9 +252,10 @@ def _get_result_workflow(
252252
"mesh_scoping", initial_result_op.inputs.mesh_scoping
253253
)
254254

255-
_connect_initial_results_inputs(
255+
_connect_workflow_inputs(
256256
initial_result_workflow=initial_result_workflow,
257257
split_by_body_workflow=None,
258+
rescoping_workflow=None,
258259
force_elemental_nodal=False,
259260
location=location,
260261
selection=selection,

src/ansys/dpf/post/harmonic_mechanical_simulation.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
----------------------------
55
66
"""
7-
from typing import List, Tuple, Union
7+
from typing import List, Optional, Tuple, Union
88
import warnings
99

1010
from ansys.dpf import core as dpf
@@ -28,9 +28,13 @@
2828
)
2929
from ansys.dpf.post.result_workflows._connect_workflow_inputs import (
3030
_connect_averaging_eqv_and_principal_workflows,
31-
_connect_initial_results_inputs,
31+
_connect_workflow_inputs,
32+
)
33+
from ansys.dpf.post.result_workflows._utils import (
34+
AveragingConfig,
35+
_append_workflows,
36+
_Rescoping,
3237
)
33-
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
3438
from ansys.dpf.post.selection import Selection, _WfNames
3539
from ansys.dpf.post.simulation import MechanicalSimulation
3640

@@ -51,6 +55,7 @@ def _get_result_workflow(
5155
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
5256
phase_angle_cyclic: Union[float, None] = None,
5357
averaging_config: AveragingConfig = AveragingConfig(),
58+
rescoping: Optional[_Rescoping] = None,
5459
) -> (dpf.Workflow, Union[str, list[str], None], str):
5560
"""Generate (without evaluating) the Workflow to extract results."""
5661
result_workflow_inputs = _create_result_workflow_inputs(
@@ -64,15 +69,17 @@ def _get_result_workflow(
6469
amplitude=amplitude,
6570
sweeping_phase=sweeping_phase,
6671
averaging_config=averaging_config,
72+
rescoping=rescoping,
6773
)
6874
result_workflows = _create_result_workflows(
6975
server=self._model._server,
7076
create_operator_callable=self._model.operator,
7177
create_workflow_inputs=result_workflow_inputs,
7278
)
73-
_connect_initial_results_inputs(
79+
_connect_workflow_inputs(
7480
initial_result_workflow=result_workflows.initial_result_workflow,
7581
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
82+
rescoping_workflow=result_workflows.rescoping_workflow,
7683
selection=selection,
7784
data_sources=self._model.metadata.data_sources,
7885
streams_provider=self._model.metadata.streams_provider,
@@ -91,6 +98,7 @@ def _get_result_workflow(
9198
result_workflows.component_extraction_workflow,
9299
result_workflows.sweeping_phase_workflow,
93100
result_workflows.norm_workflow,
101+
result_workflows.rescoping_workflow,
94102
],
95103
output_wf,
96104
)
@@ -235,7 +243,7 @@ def _get_result(
235243
"and node_ids are mutually exclusive"
236244
)
237245

238-
selection = self._build_selection(
246+
selection, rescoping = self._build_selection(
239247
base_name=base_name,
240248
category=category,
241249
selection=selection,
@@ -249,6 +257,7 @@ def _get_result(
249257
location=location,
250258
external_layer=external_layer,
251259
skin=skin,
260+
average_per_body=averaging_config.average_per_body,
252261
)
253262

254263
wf, comp, base_name = self._get_result_workflow(
@@ -263,6 +272,7 @@ def _get_result(
263272
expand_cyclic=expand_cyclic,
264273
phase_angle_cyclic=phase_angle_cyclic,
265274
averaging_config=averaging_config,
275+
rescoping=rescoping,
266276
)
267277

268278
# Evaluate the workflow

src/ansys/dpf/post/modal_mechanical_simulation.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
-------------------------
55
66
"""
7-
from typing import List, Union
7+
from typing import List, Optional, Union
88

99
from ansys.dpf import core as dpf
1010
from ansys.dpf.post import locations
@@ -19,9 +19,13 @@
1919
)
2020
from ansys.dpf.post.result_workflows._connect_workflow_inputs import (
2121
_connect_averaging_eqv_and_principal_workflows,
22-
_connect_initial_results_inputs,
22+
_connect_workflow_inputs,
23+
)
24+
from ansys.dpf.post.result_workflows._utils import (
25+
AveragingConfig,
26+
_append_workflows,
27+
_Rescoping,
2328
)
24-
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
2529
from ansys.dpf.post.selection import Selection, _WfNames
2630
from ansys.dpf.post.simulation import MechanicalSimulation
2731

@@ -40,6 +44,7 @@ def _get_result_workflow(
4044
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
4145
phase_angle_cyclic: Union[float, None] = None,
4246
averaging_config: AveragingConfig = AveragingConfig(),
47+
rescoping: Optional[_Rescoping] = None,
4348
) -> (dpf.Workflow, Union[str, list[str], None], str):
4449
"""Generate (without evaluating) the Workflow to extract results."""
4550
result_workflow_inputs = _create_result_workflow_inputs(
@@ -51,15 +56,17 @@ def _get_result_workflow(
5156
selection=selection,
5257
create_operator_callable=self._model.operator,
5358
averaging_config=averaging_config,
59+
rescoping=rescoping,
5460
)
5561
result_workflows = _create_result_workflows(
5662
server=self._model._server,
5763
create_operator_callable=self._model.operator,
5864
create_workflow_inputs=result_workflow_inputs,
5965
)
60-
_connect_initial_results_inputs(
66+
_connect_workflow_inputs(
6167
initial_result_workflow=result_workflows.initial_result_workflow,
6268
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
69+
rescoping_workflow=result_workflows.rescoping_workflow,
6370
selection=selection,
6471
data_sources=self._model.metadata.data_sources,
6572
streams_provider=self._model.metadata.streams_provider,
@@ -77,6 +84,7 @@ def _get_result_workflow(
7784
[
7885
result_workflows.component_extraction_workflow,
7986
result_workflows.norm_workflow,
87+
result_workflows.rescoping_workflow,
8088
],
8189
output_wf,
8290
)
@@ -199,7 +207,7 @@ def _get_result(
199207
elif tot == 0:
200208
set_ids = 1
201209

202-
selection = self._build_selection(
210+
selection, rescoping = self._build_selection(
203211
base_name=base_name,
204212
category=category,
205213
selection=selection,
@@ -213,6 +221,7 @@ def _get_result(
213221
location=location,
214222
external_layer=external_layer,
215223
skin=skin,
224+
average_per_body=averaging_config.average_per_body,
216225
)
217226

218227
wf, comp, base_name = self._get_result_workflow(
@@ -225,6 +234,7 @@ def _get_result(
225234
expand_cyclic=expand_cyclic,
226235
phase_angle_cyclic=phase_angle_cyclic,
227236
averaging_config=averaging_config,
237+
rescoping=rescoping,
228238
)
229239

230240
# Evaluate the workflow

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
_create_initial_result_workflow,
1717
_create_norm_workflow,
1818
_create_principal_workflow,
19+
_create_rescoping_workflow,
1920
_create_split_scope_by_body_workflow,
2021
_create_sweeping_phase_workflow,
2122
)
2223
from ansys.dpf.post.result_workflows._utils import (
2324
AveragingConfig,
2425
_CreateOperatorCallable,
26+
_Rescoping,
2527
)
2628
from ansys.dpf.post.selection import Selection, _WfNames
2729

@@ -63,6 +65,7 @@ class ResultWorkflows:
6365
# Workflow to sweep the phase of the result
6466
sweeping_phase_workflow: Optional[Workflow] = None
6567
split_by_bodies_workflow: Optional[Workflow] = None
68+
rescoping_workflow: Optional[Workflow] = None
6669

6770

6871
@dataclasses.dataclass
@@ -90,13 +93,15 @@ class _CreateWorkflowInputs:
9093
should_extract_components: bool
9194
averaging_config: AveragingConfig
9295
sweeping_phase_workflow_inputs: Optional[_SweepingPhaseWorkflowInputs] = None
96+
rescoping_workflow_inputs: Optional[_Rescoping] = None
9397

9498

9599
def _requires_manual_averaging(
96100
base_name: str,
97101
location: str,
98102
category: ResultCategory,
99-
selection: Optional[Selection],
103+
has_skin: bool,
104+
has_external_layer: bool,
100105
create_operator_callable: Callable[[str], Operator],
101106
average_per_body: bool,
102107
):
@@ -110,12 +115,17 @@ def _requires_manual_averaging(
110115
return True
111116
if category == ResultCategory.equivalent and base_name[0] == "E": # strain eqv
112117
return True
113-
if res is not None and selection is not None:
114-
return selection.requires_manual_averaging(
115-
location=location,
116-
result_native_location=res["location"],
117-
is_model_cyclic=create_operator_callable("is_cyclic").eval(),
118-
)
118+
if res is not None:
119+
is_model_cyclic = create_operator_callable("is_cyclic").eval()
120+
is_model_cyclic = is_model_cyclic in ["single_stage", "multi_stage"]
121+
if has_external_layer and is_model_cyclic and location != native_location:
122+
return True
123+
elif has_skin and (
124+
native_location == locations.elemental
125+
or native_location == locations.elemental_nodal
126+
):
127+
return True
128+
return False
119129
return False
120130

121131

@@ -215,6 +225,11 @@ def _create_result_workflows(
215225
)
216226
)
217227

228+
if create_workflow_inputs.rescoping_workflow_inputs is not None:
229+
result_workflows.rescoping_workflow = _create_rescoping_workflow(
230+
server, create_workflow_inputs.rescoping_workflow_inputs
231+
)
232+
218233
return result_workflows
219234

220235

@@ -227,6 +242,7 @@ def _create_result_workflow_inputs(
227242
selection: Selection,
228243
create_operator_callable: Callable[[str], Operator],
229244
averaging_config: AveragingConfig,
245+
rescoping: Optional[_Rescoping] = None,
230246
amplitude: bool = False,
231247
sweeping_phase: Union[float, None] = 0.0,
232248
) -> _CreateWorkflowInputs:
@@ -239,7 +255,9 @@ def _create_result_workflow_inputs(
239255
base_name=base_name,
240256
location=location,
241257
category=category,
242-
selection=selection,
258+
has_skin=_WfNames.skin in selection.spatial_selection._selection.output_names,
259+
has_external_layer=_WfNames.external_layer
260+
in selection.spatial_selection._selection.output_names,
243261
create_operator_callable=create_operator_callable,
244262
average_per_body=averaging_config.average_per_body,
245263
)
@@ -274,4 +292,5 @@ def _create_result_workflow_inputs(
274292
has_equivalent=category == ResultCategory.equivalent,
275293
sweeping_phase_workflow_inputs=sweeping_phase_workflow_inputs,
276294
averaging_config=averaging_config,
295+
rescoping_workflow_inputs=rescoping,
277296
)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ def _connect_cyclic_inputs(expand_cyclic, phase_angle_cyclic, result_wf: Workflo
7373
result_wf.connect(_WfNames.cyclic_phase, phase_angle_cyclic)
7474

7575

76-
def _connect_initial_results_inputs(
76+
def _connect_workflow_inputs(
7777
initial_result_workflow: Workflow,
7878
split_by_body_workflow: Optional[Workflow],
79+
rescoping_workflow: Optional[Workflow],
7980
force_elemental_nodal: bool,
8081
location: str,
8182
selection: Selection,
@@ -146,6 +147,11 @@ def _connect_initial_results_inputs(
146147

147148
initial_result_workflow.connect(_WfNames.mesh, mesh)
148149

150+
if rescoping_workflow:
151+
rescoping_workflow.connect(_WfNames.mesh, mesh)
152+
if _WfNames.data_sources in rescoping_workflow.input_names:
153+
rescoping_workflow.connect(_WfNames.data_sources, data_sources)
154+
149155

150156
def _connect_averaging_eqv_and_principal_workflows(
151157
result_workflows: ResultWorkflows,

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

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from ansys.dpf.gate.common import locations
55

66
from ansys.dpf.post.misc import _connect_any
7-
from ansys.dpf.post.result_workflows._utils import _CreateOperatorCallable
8-
from ansys.dpf.post.selection import _WfNames
7+
from ansys.dpf.post.result_workflows._utils import _CreateOperatorCallable, _Rescoping
8+
from ansys.dpf.post.selection import SpatialSelection, _WfNames
99

1010

1111
def _create_averaging_workflow(
@@ -269,3 +269,43 @@ def _create_split_scope_by_body_workflow(server, body_defining_properties: list[
269269
_WfNames.scoping, split_scop_op.outputs.mesh_scoping
270270
)
271271
return split_scope_by_body_wf
272+
273+
274+
def _create_rescoping_workflow(server, rescoping: _Rescoping):
275+
selection = SpatialSelection(server=server)
276+
277+
if rescoping.named_selections is not None:
278+
selection.select_named_selection(rescoping.named_selections)
279+
280+
if rescoping.node_ids is not None:
281+
selection.select_nodes(rescoping.node_ids)
282+
283+
rescoping_wf = Workflow(server=server)
284+
285+
transpose_scoping_op = operators.scoping.transpose()
286+
rescoping_wf.add_operator(transpose_scoping_op)
287+
transpose_scoping_op.inputs.requested_location(rescoping.requested_location)
288+
rescoping_wf.set_input_name(
289+
_WfNames.mesh, transpose_scoping_op.inputs.meshed_region
290+
)
291+
292+
rescoping_op = operators.scoping.rescope_fc()
293+
rescoping_wf.add_operator(rescoping_op)
294+
rescoping_op.inputs.mesh_scoping(
295+
transpose_scoping_op.outputs.mesh_scoping_as_scoping
296+
)
297+
rescoping_wf.set_input_name(
298+
_WfNames.input_data, rescoping_op.inputs.fields_container
299+
)
300+
rescoping_wf.set_input_name(
301+
_WfNames.scoping, transpose_scoping_op.inputs.mesh_scoping
302+
)
303+
rescoping_wf.set_output_name(
304+
_WfNames.output_data, rescoping_op.outputs.fields_container
305+
)
306+
307+
rescoping_wf.connect_with(
308+
selection._selection, output_input_names={_WfNames.scoping: _WfNames.scoping}
309+
)
310+
311+
return rescoping_wf

0 commit comments

Comments
 (0)