Skip to content

Commit

Permalink
Fix combination of skin extraction with scoping (#673)
Browse files Browse the repository at this point in the history
  • Loading branch information
janvonrickenbach authored Aug 28, 2024
1 parent c74810e commit 507c1e5
Show file tree
Hide file tree
Showing 5 changed files with 725 additions and 53 deletions.
2 changes: 2 additions & 0 deletions src/ansys/dpf/post/harmonic_mechanical_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def _get_result_workflow(
equivalent_op = self._model.operator(name="eqv_fc")
wf.add_operator(operator=equivalent_op)
# If a strain result, change the location now
# TBD: Why do we put the the equivalent operator
# before the averaging operator for strain results?
if (
average_op is not None
and category == ResultCategory.equivalent
Expand Down
68 changes: 49 additions & 19 deletions src/ansys/dpf/post/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
class _WfNames:
data_sources = "data_sources"
scoping = "scoping"
skin_input_mesh = "skin_input_mesh"
final_scoping = "final_scoping"
scoping_a = "scoping_a"
scoping_b = "scoping_b"
Expand Down Expand Up @@ -404,9 +405,24 @@ def select_skin(
be returned by the Operator ``operators.metadata.is_cyclic``. Used to get the skin
on the expanded mesh.
"""
op = operators.mesh.skin(server=self._server)
self._selection.add_operator(op)
mesh_input = op.inputs.mesh

def connect_any(operator_input, input_value):
# Workaround to connect any inputs: see
# https://github.com/ansys/pydpf-core/issues/1670
operator_input._operator().connect(operator_input._pin, input_value)

skin_operator = operators.mesh.skin(server=self._server)
self._selection.add_operator(skin_operator)

initial_mesh_fwd_op = operators.utility.forward(server=self._server)
self._selection.set_input_name(
_WfNames.initial_mesh, initial_mesh_fwd_op.inputs.any
)
self._selection.add_operator(initial_mesh_fwd_op)

skin_operator_input_mesh_fwd_op = operators.utility.forward(server=self._server)
connect_any(skin_operator_input_mesh_fwd_op.inputs.any, initial_mesh_fwd_op)
self._selection.add_operator(skin_operator_input_mesh_fwd_op)

if _is_model_cyclic(is_model_cyclic):
mesh_provider_cyc = operators.mesh.mesh_provider()
Expand Down Expand Up @@ -436,13 +452,11 @@ def select_skin(
server=self._server,
)
self._selection.add_operator(mesh_by_scop_op)
op.inputs.mesh.connect(mesh_by_scop_op)
skin_operator_input_mesh_fwd_op.inputs.any(mesh_by_scop_op)
else:
op.inputs.mesh.connect(mesh_provider_cyc)
self._selection.set_input_name(
_WfNames.initial_mesh, mesh_provider_cyc, 100
) # hack
mesh_input = None
skin_operator_input_mesh_fwd_op.inputs.any(mesh_provider_cyc)

mesh_provider_cyc.connect(100, initial_mesh_fwd_op.outputs.any)

elif elements is not None:
if not isinstance(elements, Scoping):
Expand All @@ -453,34 +467,50 @@ def select_skin(
scoping=elements, server=self._server
)
self._selection.add_operator(mesh_by_scop_op)
mesh_input = mesh_by_scop_op.inputs.mesh
op.inputs.mesh.connect(mesh_by_scop_op)
skin_operator_input_mesh_fwd_op.inputs.any(mesh_by_scop_op.outputs.mesh)
connect_any(mesh_by_scop_op.inputs.mesh, initial_mesh_fwd_op.outputs.any)

if mesh_input is not None:
self._selection.set_input_name(_WfNames.initial_mesh, mesh_input)
if not _is_model_cyclic(is_model_cyclic):
if location == result_native_location:
self._selection.set_output_name(_WfNames.mesh, op.outputs.mesh)
self._selection.set_output_name(_WfNames.skin, op.outputs.mesh)
self._selection.set_output_name(
_WfNames.mesh, skin_operator.outputs.mesh
)

self._selection.set_output_name(_WfNames.skin, skin_operator.outputs.mesh)
if location == locations.nodal and result_native_location == locations.nodal:
self._selection.set_output_name(
_WfNames.scoping, op.outputs.nodes_mesh_scoping
_WfNames.scoping, skin_operator.outputs.nodes_mesh_scoping
)

elif not _is_model_cyclic(is_model_cyclic) and (
result_native_location == locations.elemental
or result_native_location == locations.elemental_nodal
):
transpose_op = operators.scoping.transpose(
mesh_scoping=op.outputs.nodes_mesh_scoping, server=self._server
mesh_scoping=skin_operator.outputs.nodes_mesh_scoping,
server=self._server,
)
self._selection.add_operator(transpose_op)
self._selection.set_input_name(
_WfNames.initial_mesh, transpose_op.inputs.meshed_region
connect_any(
transpose_op.inputs.meshed_region, initial_mesh_fwd_op.outputs.any
)
self._selection.set_output_name(
_WfNames.scoping, transpose_op.outputs.mesh_scoping_as_scoping
)

connect_any(
skin_operator.inputs.mesh, skin_operator_input_mesh_fwd_op.outputs.any
)

# Provide the input mesh from which a skin was generated
# This is useful because the skin_mesh contains the mapping of
# skin elements to the original mesh element indices, which is used
# by the solid_to_skin_fc operator. The skin_input_mesh can be passed
# to the solid_to_skin_fc operator to ensure that the mapping is correct.
self._selection.set_output_name(
_WfNames.skin_input_mesh, skin_operator_input_mesh_fwd_op.outputs.any
)

def select_with_scoping(self, scoping: Scoping):
"""Directly sets the scoping as the spatial selection.
Expand Down
16 changes: 14 additions & 2 deletions src/ansys/dpf/post/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def split_mesh_by_properties(
List[elemental_properties],
Dict[elemental_properties, Union[int, List[int]]],
],
) -> Meshes:
) -> Union[Mesh, Meshes, None]:
"""Splits the simulation Mesh according to properties and returns it as Meshes.
Parameters
Expand Down Expand Up @@ -1021,12 +1021,24 @@ def _create_averaging_operator(
) # To keep for retro-compatibility
else:
inpt = first_average_op.inputs.mesh

if self._model._server.meet_version("8.0"):
# solid mesh_input only supported for server version
# 8.0 and up
average_wf.set_input_name(
_WfNames.skin_input_mesh, first_average_op.inputs.solid_mesh
)
average_wf.set_input_name(_WfNames.skin, inpt)
average_wf.connect_with(
selection.spatial_selection._selection,
output_input_names={_WfNames.skin: _WfNames.skin},
)

average_wf.connect_with(
selection.spatial_selection._selection,
output_input_names={_WfNames.skin_input_mesh: _WfNames.skin_input_mesh},
)

if location == locations.nodal:
average_op = self._model.operator(name="to_nodal_fc")
elif location == locations.elemental:
Expand All @@ -1035,6 +1047,6 @@ def _create_averaging_operator(
average_op.connect(0, forward, 0)
else:
first_average_op = average_op

# Todo: returns None if location is ElementalNodal and skin is active
if first_average_op is not None and average_op is not None:
return (first_average_op, average_op)
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pytest as a sesson fixture
"""
import os
import pathlib
import re

from ansys.dpf.core.check_version import get_server_version, meets_version
Expand Down Expand Up @@ -44,6 +45,12 @@ def get_lighting():
running_docker = os.environ.get("DPF_DOCKER", False)


def save_screenshot(dataframe, suffix=""):
"""Save a screenshot of a dataframe plot, with the current test name."""
test_path = pathlib.Path(os.environ.get("PYTEST_CURRENT_TEST"))
dataframe.plot(screenshot=f"{'_'.join(test_path.name.split('::'))}_{suffix}.jpeg")


def resolve_test_file(basename, additional_path=""):
"""Resolves a test file's full path based on the base name and the
environment.
Expand Down
Loading

0 comments on commit 507c1e5

Please sign in to comment.