Skip to content

Commit

Permalink
Average per body (#690)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
janvonrickenbach authored Oct 16, 2024
1 parent 89971d3 commit 523c102
Show file tree
Hide file tree
Showing 13 changed files with 747 additions and 124 deletions.
4 changes: 3 additions & 1 deletion src/ansys/dpf/post/fluid_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
_connect_initial_results_inputs,
)
from ansys.dpf.post.result_workflows._sub_workflows import _create_norm_workflow
from ansys.dpf.post.result_workflows._utils import _append_workflows
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
from ansys.dpf.post.selection import Selection, _WfNames
from ansys.dpf.post.simulation import Simulation
from ansys.dpf.post.species import SpeciesDict
Expand Down Expand Up @@ -254,6 +254,7 @@ def _get_result_workflow(

_connect_initial_results_inputs(
initial_result_workflow=initial_result_workflow,
split_by_body_workflow=None,
force_elemental_nodal=False,
location=location,
selection=selection,
Expand All @@ -262,6 +263,7 @@ def _get_result_workflow(
mesh=self.mesh._meshed_region,
streams_provider=self._model.metadata.streams_provider,
data_sources=self._model.metadata.data_sources,
averaging_config=AveragingConfig(),
)

query_regions_meshes = False
Expand Down
19 changes: 11 additions & 8 deletions src/ansys/dpf/post/harmonic_mechanical_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
_connect_averaging_eqv_and_principal_workflows,
_connect_initial_results_inputs,
)
from ansys.dpf.post.result_workflows._utils import _append_workflows
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
from ansys.dpf.post.selection import Selection, _WfNames
from ansys.dpf.post.simulation import MechanicalSimulation

Expand All @@ -50,6 +50,7 @@ def _get_result_workflow(
selection: Union[Selection, None] = None,
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
phase_angle_cyclic: Union[float, None] = None,
averaging_config: AveragingConfig = AveragingConfig(),
) -> (dpf.Workflow, Union[str, list[str], None], str):
"""Generate (without evaluating) the Workflow to extract results."""
result_workflow_inputs = _create_result_workflow_inputs(
Expand All @@ -60,9 +61,9 @@ def _get_result_workflow(
location=location,
selection=selection,
create_operator_callable=self._model.operator,
mesh_provider=self._model.metadata.mesh_provider,
amplitude=amplitude,
sweeping_phase=sweeping_phase,
averaging_config=averaging_config,
)
result_workflows = _create_result_workflows(
server=self._model._server,
Expand All @@ -71,6 +72,7 @@ def _get_result_workflow(
)
_connect_initial_results_inputs(
initial_result_workflow=result_workflows.initial_result_workflow,
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
selection=selection,
data_sources=self._model.metadata.data_sources,
streams_provider=self._model.metadata.streams_provider,
Expand All @@ -79,14 +81,9 @@ def _get_result_workflow(
mesh=self.mesh._meshed_region,
location=location,
force_elemental_nodal=result_workflows.force_elemental_nodal,
averaging_config=averaging_config,
)

if result_workflows.mesh_workflow:
selection.spatial_selection._selection.connect_with(
result_workflows.mesh_workflow,
output_input_names={_WfNames.initial_mesh: _WfNames.initial_mesh},
)

output_wf = _connect_averaging_eqv_and_principal_workflows(result_workflows)

output_wf = _append_workflows(
Expand Down Expand Up @@ -129,6 +126,7 @@ def _get_result(
phase_angle_cyclic: Union[float, None] = None,
external_layer: Union[bool, List[int]] = False,
skin: Union[bool, List[int]] = False,
averaging_config: AveragingConfig = AveragingConfig(),
) -> DataFrame:
"""Extract results from the simulation.
Expand Down Expand Up @@ -201,6 +199,10 @@ def _get_result(
is computed over list of elements (not supported for cyclic symmetry). Getting the
skin on more than one result (several time freq sets, split data...) is only
supported starting with Ansys 2023R2.
averaging_config:
Per default averaging happens across all bodies. The averaging config
can define that averaging happens per body and defines the properties that
are used to define a body.
Returns
-------
Expand Down Expand Up @@ -260,6 +262,7 @@ def _get_result(
selection=selection,
expand_cyclic=expand_cyclic,
phase_angle_cyclic=phase_angle_cyclic,
averaging_config=averaging_config,
)

# Evaluate the workflow
Expand Down
19 changes: 11 additions & 8 deletions src/ansys/dpf/post/modal_mechanical_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
_connect_averaging_eqv_and_principal_workflows,
_connect_initial_results_inputs,
)
from ansys.dpf.post.result_workflows._utils import _append_workflows
from ansys.dpf.post.result_workflows._utils import AveragingConfig, _append_workflows
from ansys.dpf.post.selection import Selection, _WfNames
from ansys.dpf.post.simulation import MechanicalSimulation

Expand All @@ -39,6 +39,7 @@ def _get_result_workflow(
selection: Union[Selection, None] = None,
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
phase_angle_cyclic: Union[float, None] = None,
averaging_config: AveragingConfig = AveragingConfig(),
) -> (dpf.Workflow, Union[str, list[str], None], str):
"""Generate (without evaluating) the Workflow to extract results."""
result_workflow_inputs = _create_result_workflow_inputs(
Expand All @@ -49,7 +50,7 @@ def _get_result_workflow(
location=location,
selection=selection,
create_operator_callable=self._model.operator,
mesh_provider=self._model.metadata.mesh_provider,
averaging_config=averaging_config,
)
result_workflows = _create_result_workflows(
server=self._model._server,
Expand All @@ -58,6 +59,7 @@ def _get_result_workflow(
)
_connect_initial_results_inputs(
initial_result_workflow=result_workflows.initial_result_workflow,
split_by_body_workflow=result_workflows.split_by_bodies_workflow,
selection=selection,
data_sources=self._model.metadata.data_sources,
streams_provider=self._model.metadata.streams_provider,
Expand All @@ -66,14 +68,9 @@ def _get_result_workflow(
mesh=self.mesh._meshed_region,
location=location,
force_elemental_nodal=result_workflows.force_elemental_nodal,
averaging_config=averaging_config,
)

if result_workflows.mesh_workflow:
selection.spatial_selection._selection.connect_with(
result_workflows.mesh_workflow,
output_input_names={_WfNames.initial_mesh: _WfNames.initial_mesh},
)

output_wf = _connect_averaging_eqv_and_principal_workflows(result_workflows)

output_wf = _append_workflows(
Expand Down Expand Up @@ -110,6 +107,7 @@ def _get_result(
phase_angle_cyclic: Union[float, None] = None,
external_layer: Union[bool, List[int]] = False,
skin: Union[bool, List[int]] = False,
averaging_config: AveragingConfig = AveragingConfig(),
) -> DataFrame:
"""Extract results from the simulation.
Expand Down Expand Up @@ -175,6 +173,10 @@ def _get_result(
is computed over list of elements (not supported for cyclic symmetry). Getting the
skin on more than one result (several time freq sets, split data...) is only
supported starting with Ansys 2023R2.
averaging_config:
Per default averaging happens across all bodies. The averaging config
can define that averaging happens per body and defines the properties that
are used to define a body.
Returns
-------
Expand Down Expand Up @@ -222,6 +224,7 @@ def _get_result(
selection=selection,
expand_cyclic=expand_cyclic,
phase_angle_cyclic=phase_angle_cyclic,
averaging_config=averaging_config,
)

# Evaluate the workflow
Expand Down
51 changes: 28 additions & 23 deletions src/ansys/dpf/post/result_workflows/_build_workflow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dataclasses
from typing import Any, Callable, List, Optional, Union
from typing import Callable, List, Optional, Union

from ansys.dpf.core import Operator, Workflow
from ansys.dpf.core.available_result import _result_properties
Expand All @@ -14,12 +14,15 @@
_create_equivalent_workflow,
_create_extract_component_workflow,
_create_initial_result_workflow,
_create_mesh_workflow,
_create_norm_workflow,
_create_principal_workflow,
_create_split_scope_by_body_workflow,
_create_sweeping_phase_workflow,
)
from ansys.dpf.post.result_workflows._utils import _CreateOperatorCallable
from ansys.dpf.post.result_workflows._utils import (
AveragingConfig,
_CreateOperatorCallable,
)
from ansys.dpf.post.selection import Selection, _WfNames


Expand Down Expand Up @@ -49,8 +52,6 @@ class ResultWorkflows:
compute_equivalent_before_average: bool = False
# List of component names at the end of the workflow. If None, the result is a scalar.
components: Optional[list[str]] = None
# Workflow to compute the mesh
mesh_workflow: Optional[Workflow] = None
# Workflow to compute the principal components of the result
principal_workflow: Optional[Workflow] = None
# Workflow to compute the equivalent result
Expand All @@ -61,6 +62,7 @@ class ResultWorkflows:
component_extraction_workflow: Optional[Workflow] = None
# Workflow to sweep the phase of the result
sweeping_phase_workflow: Optional[Workflow] = None
split_by_bodies_workflow: Optional[Workflow] = None


@dataclasses.dataclass
Expand All @@ -69,11 +71,6 @@ class _AveragingWorkflowInputs:
force_elemental_nodal: bool


@dataclasses.dataclass
class _MeshWorkflowInputs:
mesh_provider: Any


@dataclasses.dataclass
class _SweepingPhaseWorkflowInputs:
amplitude: bool = (False,)
Expand All @@ -91,7 +88,7 @@ class _CreateWorkflowInputs:
component_names: list[str]
components_to_extract: list[int]
should_extract_components: bool
mesh_workflow_inputs: Optional[_MeshWorkflowInputs] = None
averaging_config: AveragingConfig
sweeping_phase_workflow_inputs: Optional[_SweepingPhaseWorkflowInputs] = None


Expand All @@ -101,8 +98,16 @@ def _requires_manual_averaging(
category: ResultCategory,
selection: Optional[Selection],
create_operator_callable: Callable[[str], Operator],
average_per_body: bool,
):
res = _result_properties[base_name] if base_name in _result_properties else None
native_location = res["location"] if res is not None else None

if average_per_body and (
native_location == locations.elemental
or native_location == locations.elemental_nodal
):
return True
if category == ResultCategory.equivalent and base_name[0] == "E": # strain eqv
return True
if res is not None and selection is not None:
Expand Down Expand Up @@ -148,12 +153,6 @@ def _create_result_workflows(
components=create_workflow_inputs.component_names,
)

if create_workflow_inputs.mesh_workflow_inputs is not None:
result_workflows.mesh_workflow = _create_mesh_workflow(
mesh_provider=create_workflow_inputs.mesh_workflow_inputs.mesh_provider,
server=server,
)

if create_workflow_inputs.has_principal:
result_workflows.principal_workflow = _create_principal_workflow(
components_to_extract=create_workflow_inputs.components_to_extract,
Expand Down Expand Up @@ -207,6 +206,15 @@ def _create_result_workflows(
sweeping_phase=create_workflow_inputs.sweeping_phase_workflow_inputs.sweeping_phase,
)

avg_config = create_workflow_inputs.averaging_config
if avg_config.average_per_body:
result_workflows.split_by_bodies_workflow = (
_create_split_scope_by_body_workflow(
server=server,
body_defining_properties=avg_config.body_defining_properties,
)
)

return result_workflows


Expand All @@ -218,7 +226,7 @@ def _create_result_workflow_inputs(
norm: bool,
selection: Selection,
create_operator_callable: Callable[[str], Operator],
mesh_provider: Any,
averaging_config: AveragingConfig,
amplitude: bool = False,
sweeping_phase: Union[float, None] = 0.0,
) -> _CreateWorkflowInputs:
Expand All @@ -233,17 +241,14 @@ def _create_result_workflow_inputs(
category=category,
selection=selection,
create_operator_callable=create_operator_callable,
average_per_body=averaging_config.average_per_body,
)

averaging_workflow_inputs = _AveragingWorkflowInputs(
location=location,
force_elemental_nodal=force_elemental_nodal,
)

mesh_workflow_inputs: Optional[_MeshWorkflowInputs] = None
if selection.spatial_selection.requires_mesh:
mesh_workflow_inputs = _MeshWorkflowInputs(mesh_provider=mesh_provider)

has_principal = category == ResultCategory.principal

should_extract_components = (
Expand All @@ -266,7 +271,7 @@ def _create_result_workflow_inputs(
components_to_extract=components_to_extract,
should_extract_components=should_extract_components,
has_principal=has_principal,
mesh_workflow_inputs=mesh_workflow_inputs,
has_equivalent=category == ResultCategory.equivalent,
sweeping_phase_workflow_inputs=sweeping_phase_workflow_inputs,
averaging_config=averaging_config,
)
31 changes: 29 additions & 2 deletions src/ansys/dpf/post/result_workflows/_connect_workflow_inputs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from typing import Any
from typing import Any, Optional

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

from ansys.dpf.post.result_workflows._build_workflow import ResultWorkflows
from ansys.dpf.post.result_workflows._sub_workflows import (
_enrich_mesh_with_property_fields,
)
from ansys.dpf.post.result_workflows._utils import AveragingConfig
from ansys.dpf.post.selection import Selection, _WfNames


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

def _connect_initial_results_inputs(
initial_result_workflow: Workflow,
split_by_body_workflow: Optional[Workflow],
force_elemental_nodal: bool,
location: str,
selection: Selection,
Expand All @@ -79,14 +84,36 @@ def _connect_initial_results_inputs(
mesh: MeshedRegion,
streams_provider: Any,
data_sources: Any,
averaging_config: AveragingConfig,
):
"""Connects the inputs of the initial result workflow.
The initial result workflow is the first workflow in the result workflows chain, which
extracts the raw results from the data sources.
"""
selection_wf = selection.spatial_selection._selection

if selection.spatial_selection.requires_mesh:
selection_wf.connect(_WfNames.initial_mesh, mesh)

if averaging_config.average_per_body:
_enrich_mesh_with_property_fields(
mesh, averaging_config.body_defining_properties, streams_provider
)

if split_by_body_workflow is not None:
split_by_body_workflow.connect(_WfNames.mesh, mesh)
if force_elemental_nodal:
split_by_body_workflow.connect(_WfNames.scoping_location, "ElementalNodal")
else:
split_by_body_workflow.connect(_WfNames.scoping_location, location)
split_by_body_workflow.connect_with(
selection_wf, output_input_names={_WfNames.scoping: _WfNames.scoping}
)
selection_wf = split_by_body_workflow

initial_result_workflow.connect_with(
selection.spatial_selection._selection,
selection_wf,
output_input_names={"scoping": "mesh_scoping"},
)

Expand Down
Loading

0 comments on commit 523c102

Please sign in to comment.