From 787c3dfcbccadaa33013998b94af1c00a35c97d6 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Tue, 10 Dec 2024 16:01:04 +0100 Subject: [PATCH] Skip manual shell layer extraction for nodal results. Add more tests. --- .../post/result_workflows/_build_workflow.py | 13 ++- .../post/result_workflows/_sub_workflows.py | 14 +-- tests/conftest.py | 20 +++++ tests/test_simulation.py | 88 +++++++++++++++++++ 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/src/ansys/dpf/post/result_workflows/_build_workflow.py b/src/ansys/dpf/post/result_workflows/_build_workflow.py index 96eb5ce99..98b8420f2 100644 --- a/src/ansys/dpf/post/result_workflows/_build_workflow.py +++ b/src/ansys/dpf/post/result_workflows/_build_workflow.py @@ -139,16 +139,23 @@ def _create_result_workflows( The resulting workflows are stored in a ResultWorkflows object. """ + force_elemental_nodal = ( + create_workflow_inputs.averaging_workflow_inputs.force_elemental_nodal + ) + + is_nodal = ( + create_workflow_inputs.averaging_workflow_inputs.location == locations.nodal + and not force_elemental_nodal + ) + initial_result_wf = _create_initial_result_workflow( name=create_workflow_inputs.base_name, server=server, + is_nodal=is_nodal, shell_layer=create_workflow_inputs.shell_layer, create_operator_callable=create_operator_callable, ) - force_elemental_nodal = ( - create_workflow_inputs.averaging_workflow_inputs.force_elemental_nodal - ) average_wf = _create_averaging_workflow( location=create_workflow_inputs.averaging_workflow_inputs.location, has_skin=create_workflow_inputs.has_skin, diff --git a/src/ansys/dpf/post/result_workflows/_sub_workflows.py b/src/ansys/dpf/post/result_workflows/_sub_workflows.py index 537b4fcf7..bfc9ceecc 100644 --- a/src/ansys/dpf/post/result_workflows/_sub_workflows.py +++ b/src/ansys/dpf/post/result_workflows/_sub_workflows.py @@ -174,6 +174,7 @@ def _create_initial_result_workflow( name: str, server, shell_layer: Optional[shell_layers], + is_nodal: bool, create_operator_callable: _CreateOperatorCallable, ): initial_result_workflow = Workflow(server=server) @@ -190,12 +191,15 @@ def _create_initial_result_workflow( initial_result_workflow.set_input_name(_WfNames.shell_layer, forward_shell_layer_op) # The next section is only needed, because the shell_layer selection does not - # work for elemental results. If elemental results are requested with a chosen - # shell layer, the shell layer is not selected and the results are split into solids + # work for elemental and elemental nodal results. + # If elemental results are requested with a chosen shell layer, + # the shell layer is not selected and the results are split into solids # and shells. Here, we add an additional shell layer selection and merge_shell_solid - # operator to manually merge the results. If the shell layer was already selected, this - # should do nothing. - if shell_layer is not None: + # operator to manually merge the results. + # Note that we have to skip this step if the location is nodal, because + # the resulting location is wrong when the shell layer is selected again manually, after + # it was already selected by the initial result operator. + if shell_layer is not None and not is_nodal: merge_shell_solid_fields = create_operator_callable( name="merge::solid_shell_fields" ) diff --git a/tests/conftest.py b/tests/conftest.py index 107829a21..b84ac3a0b 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -136,6 +136,26 @@ def mixed_shell_solid_model(): ) +@pytest.fixture() +def mixed_shell_solid_with_contact_model(): + """Resolve the path of the "mixed_shell_solid_with_contact" result file.""" + return _download_file( + "result_files/extract_shell_layer", + "mixed_shell_solid_with_contact.rst", + True, + None, + False, + ) + + +@pytest.fixture() +def two_cubes_contact_model(): + """Resolve the path of the "two_cubes_contact" result file.""" + return _download_file( + "result_files/extract_shell_layer", "two_cubes_contact.rst", True, None, False + ) + + @pytest.fixture() def complex_model(): """Resolve the path of the "msup/plate1.rst" result file.""" diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 32946487a..5d16c6524 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -460,6 +460,22 @@ def mixed_shell_solid_simulation(mixed_shell_solid_model): ) +@fixture +def mixed_shell_solid_with_contact_simulation(mixed_shell_solid_with_contact_model): + return post.load_simulation( + data_sources=mixed_shell_solid_with_contact_model, + simulation_type=AvailableSimulationTypes.static_mechanical, + ) + + +@fixture +def two_cubes_contact_simulation(two_cubes_contact_model): + return post.load_simulation( + data_sources=two_cubes_contact_model, + simulation_type=AvailableSimulationTypes.static_mechanical, + ) + + @fixture def transient_simulation(plate_msup): return post.load_simulation( @@ -1452,6 +1468,78 @@ def test_shell_layer_extraction( assert checked_elements == 36 +@pytest.mark.parametrize( + "average_per_body", + [ + False, + pytest.param( + True, + marks=pytest.mark.xfail( + reason="Failing because scopings without results" + " are not handled correctly in the current implementation." + ), + ), + ], +) +@pytest.mark.parametrize("on_skin", [True, False]) +# Note: shell_layer selection with multiple layers (e.g top/bottom) currently not working correctly +# for mixed models. +@pytest.mark.parametrize("shell_layer", [shell_layers.top, shell_layers.bottom]) +@pytest.mark.parametrize("location", [locations.elemental, locations.nodal]) +@pytest.mark.parametrize( + "simulation_str", + [ + "two_cubes_contact_simulation", + pytest.param( + "mixed_shell_solid_with_contact_simulation", + marks=pytest.mark.xfail( + reason="Failing because scopings without results" + " are not handled correctly in the current implementation." + ), + ), + ], +) +def test_shell_layer_extraction_contacts( + simulation_str, average_per_body, on_skin, shell_layer, location, request +): + # Test some models with contacts, because models with contacts + # result in fields without results which can cause problems in conjunction + # with shell layer extraction. + simulation = request.getfixturevalue(simulation_str) + + if not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_9_1: + return + + if average_per_body: + averaging_config = AveragingConfig( + body_defining_properties=["mat"], average_per_body=True + ) + else: + averaging_config = AveragingConfig( + body_defining_properties=None, average_per_body=False + ) + + res = simulation._get_result( + base_name="S", + skin=on_skin, + components=["X"], + location=location, + category=ResultCategory.equivalent, + shell_layer=shell_layer, + averaging_config=averaging_config, + ) + + # Just do a rough comparison. + # This test is mainly to check if the + # workflow runs without errors because of + # empty fields for some materials + max_val = res.max().array[0] + if simulation_str == "two_cubes_contact_simulation": + assert max_val > 1 and max_val < 1.1 + else: + assert max_val > 7.7 and max_val < 7.8 + + @pytest.mark.parametrize("skin", all_configuration_ids) @pytest.mark.parametrize("result_name", ["stress", "elastic_strain", "displacement"]) @pytest.mark.parametrize("mode", [None, "principal", "equivalent"])