From 761a0f7505f0b968d4fa36326b58b0732ff9ae94 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Thu, 7 Dec 2023 17:49:39 +0100 Subject: [PATCH 01/21] Add function to extract ply wise data --- src/ansys/dpf/composites/ply_wise_data.py | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/ansys/dpf/composites/ply_wise_data.py diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py new file mode 100644 index 000000000..f8a5aaca9 --- /dev/null +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -0,0 +1,85 @@ +"""Methods to get ply wise data from a result field.""" + +from enum import Enum, IntEnum +from typing import Union + +from ansys.dpf.core import Field, MeshedRegion, Operator, operators +from ansys.dpf.gate.common import locations + + +class ReductionStrategy(Enum): + """The reduction strategy to get from spot values (BOT, MID, TOP) to a single value.""" + + MIN = "MIN" + MAX = "MAX" + AVG = "AVG" + BOT = "BOT" + MID = "MID" + TOP = "TOP" + + +def get_ply_wise_data( + field: Field, + ply_name: str, + mesh: MeshedRegion, + reduction_strategy: ReductionStrategy = ReductionStrategy.AVG, + requested_location: str = locations.elemental_nodal, + component: Union[IntEnum, int] = 0, +) -> Field: + """Get ply-wise data from a field. + + Parameters + ---------- + field: + The field to extract data from. + ply_name: + The name of the ply to extract data from. + mesh: + The meshed region. Needs to be enriched with composite information. + Use CompositeModel.get_mesh() to get the meshed region. + reduction_strategy: + The reduction strategy to get from spot values (BOT, MID, TOP) to a single value + per corner node and layer. The default is AVG. + requested_location: + The location of the output field. The default is elemental_nodal. Supported are + elemental_nodal, elemental, and nodal. + component: + The component to extract data from. Can be an int or an IntEnum. The default is 0. + """ + component_int = component.value if isinstance(component, IntEnum) else component + component_selector = operators.logic.component_selector() + + component_selector.inputs.field.connect(field) + component_selector.inputs.component_number.connect(component_int) + single_component_field = component_selector.outputs.field() + + filter_ply_data_op = Operator("composite::filter_ply_data_operator") + filter_ply_data_op.inputs.field(single_component_field) + filter_ply_data_op.inputs.mesh(mesh) + filter_ply_data_op.inputs.ply_id(ply_name) + filter_ply_data_op.inputs.reduction_strategy(reduction_strategy.value) + elemental_nodal_data = filter_ply_data_op.outputs.field() + + if requested_location == locations.elemental_nodal: + return elemental_nodal_data + + if requested_location == locations.elemental: + elemental_nodal_to_elemental = operators.averaging.elemental_mean() + elemental_nodal_to_elemental.inputs.field.connect(elemental_nodal_data) + out_field = elemental_nodal_to_elemental.outputs.field() + out_field.location = locations.elemental + return out_field + + if requested_location == locations.nodal: + elemental_nodal_to_nodal = operators.averaging.elemental_nodal_to_nodal() + elemental_nodal_to_nodal.inputs.mesh.connect(mesh) + elemental_nodal_to_nodal.inputs.field.connect(elemental_nodal_data) + out_field = elemental_nodal_to_nodal.outputs.field() + out_field.location = locations.nodal + return out_field + + raise RuntimeError( + f"Invalid requested location {requested_location}. " + f"Valid locations are {locations.elemental_nodal}, " + f"{locations.elemental}, and {locations.nodal}." + ) From 9bd530f146e11e341aaea2f92c71a6282925171b Mon Sep 17 00:00:00 2001 From: jvonrick Date: Fri, 8 Dec 2023 13:19:37 +0100 Subject: [PATCH 02/21] Add test for extract ply wise data --- tests/ply_wise_data_test.py | 136 ++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/ply_wise_data_test.py diff --git a/tests/ply_wise_data_test.py b/tests/ply_wise_data_test.py new file mode 100644 index 000000000..c9faab7f2 --- /dev/null +++ b/tests/ply_wise_data_test.py @@ -0,0 +1,136 @@ +from collections.abc import Sequence + +from ansys.dpf.gate.common import locations +import numpy as np + +from ansys.dpf.composites.composite_model import CompositeModel +from ansys.dpf.composites.constants import Sym3x3TensorComponent +from ansys.dpf.composites.ply_wise_data import ReductionStrategy, get_ply_wise_data +from tests.helper import get_basic_shell_files + + +def get_reduced_value(all_spot_values: Sequence[float], reduction_strategy: ReductionStrategy): + if reduction_strategy == ReductionStrategy.AVG: + return sum(all_spot_values) / len(all_spot_values) + if reduction_strategy == ReductionStrategy.MIN: + return min(all_spot_values) + if reduction_strategy == ReductionStrategy.MAX: + return max(all_spot_values) + if reduction_strategy == ReductionStrategy.BOT: + return all_spot_values[0] + if reduction_strategy == ReductionStrategy.MID: + return all_spot_values[2] + if reduction_strategy == ReductionStrategy.TOP: + return all_spot_values[1] + raise ValueError(f"Unknown reduction strategy: {reduction_strategy}") + + +ALL_REDUCTION_STRATEGIES = [ + ReductionStrategy.AVG, + ReductionStrategy.MIN, + ReductionStrategy.MAX, + ReductionStrategy.BOT, + ReductionStrategy.MID, + ReductionStrategy.TOP, +] + +ALL_TENSOR_COMPONENTS = [ + Sym3x3TensorComponent.TENSOR11, + Sym3x3TensorComponent.TENSOR22, + Sym3x3TensorComponent.TENSOR22, + Sym3x3TensorComponent.TENSOR21, + Sym3x3TensorComponent.TENSOR31, + Sym3x3TensorComponent.TENSOR32, +] + + +def test_get_ply_wise_data(dpf_server): + files = get_basic_shell_files() + + composite_model = CompositeModel(files, server=dpf_server) + + stress_result_op = composite_model.core_model.results.stress() + stress_result_op.inputs.bool_rotate_to_global(False) + stress_field = stress_result_op.outputs.fields_container()[0] + + def get_all_spot_values_first_element_first_node(stress_field, component=0): + all_spot_values = [] + entity_data = stress_field.get_entity_data_by_id(1)[:, component] + number_of_nodes = 4 + for spot_index in range(3): + all_spot_values.append(entity_data[spot_index * number_of_nodes]) + return all_spot_values + + first_ply = "P1L1__woven_45" + + element_id = 1 + first_node_index = 0 + + for component in ALL_TENSOR_COMPONENTS: + for reduction_strategy in ALL_REDUCTION_STRATEGIES: + all_spot_values = get_all_spot_values_first_element_first_node( + stress_field, component=component.value + ) + + elemental_nodal_data = get_ply_wise_data( + stress_field, + first_ply, + composite_model.get_mesh(), + component=component, + reduction_strategy=reduction_strategy, + ) + + assert len(elemental_nodal_data.scoping.ids) == 4 + assert len(elemental_nodal_data.get_entity_data_by_id(element_id)) == 4 + + assert np.allclose( + elemental_nodal_data.get_entity_data_by_id(element_id)[first_node_index], + get_reduced_value(all_spot_values, reduction_strategy), + ) + + elemental_data = get_ply_wise_data( + stress_field, + first_ply, + composite_model.get_mesh(), + component=component, + reduction_strategy=reduction_strategy, + requested_location=locations.elemental, + ) + assert len(elemental_data.scoping.ids) == 4 + assert len(elemental_data.get_entity_data_by_id(element_id)) == 1 + + assert np.allclose( + elemental_data.get_entity_data_by_id(element_id)[first_node_index], + sum(elemental_nodal_data.get_entity_data_by_id(element_id)) / 4, + ) + + nodal_data = get_ply_wise_data( + stress_field, + first_ply, + composite_model.get_mesh(), + component=component, + reduction_strategy=reduction_strategy, + requested_location=locations.nodal, + ) + assert len(nodal_data.scoping.ids) == 9 + assert len(nodal_data.get_entity_data_by_id(element_id)) == 1 + + # Select node that only belongs to element 1 + # no averaging needed + node_index_with_no_neighbours = 1 + first_node_index_of_element_1 = ( + composite_model.get_mesh().elements.connectivities_field.get_entity_data_by_id( + element_id + )[node_index_with_no_neighbours] + ) + node_id_to_index = composite_model.get_mesh().nodes.mapping_id_to_index + node_id = list(node_id_to_index.keys())[ + list(node_id_to_index.values()).index(first_node_index_of_element_1) + ] + + assert np.allclose( + nodal_data.get_entity_data_by_id(node_id)[0], + elemental_nodal_data.get_entity_data_by_id(element_id)[ + node_index_with_no_neighbours + ], + ) From 28933787808d8245477339275a1b35abccb8bd90 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Fri, 8 Dec 2023 13:32:29 +0100 Subject: [PATCH 03/21] Add docs --- doc/source/api/index.rst | 1 + doc/source/api/ply_wise_data.rst | 12 +++++++++++ src/ansys/dpf/composites/__init__.py | 2 ++ .../dpf/composites/layup_info/_layup_info.py | 4 ++-- src/ansys/dpf/composites/ply_wise_data.py | 2 ++ tests/ply_wise_data_test.py | 20 +++++++++++-------- 6 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 doc/source/api/ply_wise_data.rst diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 151e3ef1e..3b50fe054 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -16,6 +16,7 @@ For in-depth documentation on the different failure criteria, refer to the ACP h data_sources failure_criteria layup_info + ply_wise_data result_definition sampling_point server_helpers diff --git a/doc/source/api/ply_wise_data.rst b/doc/source/api/ply_wise_data.rst new file mode 100644 index 000000000..4e32be41b --- /dev/null +++ b/doc/source/api/ply_wise_data.rst @@ -0,0 +1,12 @@ +Data sources +------------ + +.. module:: ansys.dpf.composites.ply_wise_data + +.. autosummary:: + :toctree: _autosummary + + ReductionStrategy + get_ply_wise_data + + diff --git a/src/ansys/dpf/composites/__init__.py b/src/ansys/dpf/composites/__init__.py index 5bf3fa01d..58f4f7fec 100644 --- a/src/ansys/dpf/composites/__init__.py +++ b/src/ansys/dpf/composites/__init__.py @@ -14,6 +14,7 @@ data_sources, failure_criteria, layup_info, + ply_wise_data, result_definition, sampling_point, select_indices, @@ -27,6 +28,7 @@ "data_sources", "failure_criteria", "layup_info", + "ply_wise_data", "result_definition", "sampling_point", "server_helpers", diff --git a/src/ansys/dpf/composites/layup_info/_layup_info.py b/src/ansys/dpf/composites/layup_info/_layup_info.py index f8b758db4..af09fc68e 100644 --- a/src/ansys/dpf/composites/layup_info/_layup_info.py +++ b/src/ansys/dpf/composites/layup_info/_layup_info.py @@ -219,8 +219,8 @@ def get_dpf_material_id_by_analyis_ply_map( DPF data source with rst file or streams_provider. The streams provider is available from :attr:`.CompositeModel.core_model` (under metadata.streams_provider). - Note - ---- + Notes + ----- Cache the output because the computation can be performance-critical. """ warn( diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index f8a5aaca9..0915d7cec 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -6,6 +6,8 @@ from ansys.dpf.core import Field, MeshedRegion, Operator, operators from ansys.dpf.gate.common import locations +__all__ = ("ReductionStrategy", "get_ply_wise_data") + class ReductionStrategy(Enum): """The reduction strategy to get from spot values (BOT, MID, TOP) to a single value.""" diff --git a/tests/ply_wise_data_test.py b/tests/ply_wise_data_test.py index c9faab7f2..8f97d6dba 100644 --- a/tests/ply_wise_data_test.py +++ b/tests/ply_wise_data_test.py @@ -44,6 +44,15 @@ def get_reduced_value(all_spot_values: Sequence[float], reduction_strategy: Redu ] +def get_all_spot_values_first_element_first_node(stress_field, component=0): + all_spot_values = [] + entity_data = stress_field.get_entity_data_by_id(1)[:, component] + number_of_nodes = 4 + for spot_index in range(3): + all_spot_values.append(entity_data[spot_index * number_of_nodes]) + return all_spot_values + + def test_get_ply_wise_data(dpf_server): files = get_basic_shell_files() @@ -53,14 +62,6 @@ def test_get_ply_wise_data(dpf_server): stress_result_op.inputs.bool_rotate_to_global(False) stress_field = stress_result_op.outputs.fields_container()[0] - def get_all_spot_values_first_element_first_node(stress_field, component=0): - all_spot_values = [] - entity_data = stress_field.get_entity_data_by_id(1)[:, component] - number_of_nodes = 4 - for spot_index in range(3): - all_spot_values.append(entity_data[spot_index * number_of_nodes]) - return all_spot_values - first_ply = "P1L1__woven_45" element_id = 1 @@ -82,6 +83,7 @@ def get_all_spot_values_first_element_first_node(stress_field, component=0): assert len(elemental_nodal_data.scoping.ids) == 4 assert len(elemental_nodal_data.get_entity_data_by_id(element_id)) == 4 + assert elemental_nodal_data.location == locations.elemental_nodal assert np.allclose( elemental_nodal_data.get_entity_data_by_id(element_id)[first_node_index], @@ -98,6 +100,7 @@ def get_all_spot_values_first_element_first_node(stress_field, component=0): ) assert len(elemental_data.scoping.ids) == 4 assert len(elemental_data.get_entity_data_by_id(element_id)) == 1 + assert elemental_data.location == locations.elemental assert np.allclose( elemental_data.get_entity_data_by_id(element_id)[first_node_index], @@ -114,6 +117,7 @@ def get_all_spot_values_first_element_first_node(stress_field, component=0): ) assert len(nodal_data.scoping.ids) == 9 assert len(nodal_data.get_entity_data_by_id(element_id)) == 1 + assert nodal_data.location == locations.nodal # Select node that only belongs to element 1 # no averaging needed From b71d667fcc1b78903a0ffeec2c4e1a4612b02f83 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 11:36:23 +0100 Subject: [PATCH 04/21] Fix title in documentation --- doc/source/api/ply_wise_data.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/api/ply_wise_data.rst b/doc/source/api/ply_wise_data.rst index 4e32be41b..d34328fea 100644 --- a/doc/source/api/ply_wise_data.rst +++ b/doc/source/api/ply_wise_data.rst @@ -1,5 +1,5 @@ -Data sources ------------- +Ply wise data +------------- .. module:: ansys.dpf.composites.ply_wise_data From fd9fe91434fb19eece809bae77b00d4c5aa9c06a Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 11:44:46 +0100 Subject: [PATCH 05/21] Fix include --- tests/ply_wise_data_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ply_wise_data_test.py b/tests/ply_wise_data_test.py index 8f97d6dba..77ee37416 100644 --- a/tests/ply_wise_data_test.py +++ b/tests/ply_wise_data_test.py @@ -6,7 +6,8 @@ from ansys.dpf.composites.composite_model import CompositeModel from ansys.dpf.composites.constants import Sym3x3TensorComponent from ansys.dpf.composites.ply_wise_data import ReductionStrategy, get_ply_wise_data -from tests.helper import get_basic_shell_files + +from .helper import get_basic_shell_files def get_reduced_value(all_spot_values: Sequence[float], reduction_strategy: ReductionStrategy): From 18f5c9ebb1afbf9dabd006220219c5c6c307d05e Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Wed, 3 Jan 2024 13:17:52 +0100 Subject: [PATCH 06/21] Update src/ansys/dpf/composites/ply_wise_data.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/dpf/composites/ply_wise_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index 0915d7cec..edc977ad0 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -1,4 +1,4 @@ -"""Methods to get ply wise data from a result field.""" +"""Methods to get ply-wise data from a result field.""" from enum import Enum, IntEnum from typing import Union From 03300a07ea867222f1aad1a4e30fffecace2ebfb Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Wed, 3 Jan 2024 13:17:58 +0100 Subject: [PATCH 07/21] Update src/ansys/dpf/composites/ply_wise_data.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/dpf/composites/ply_wise_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index edc977ad0..20eb215bc 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -33,7 +33,7 @@ def get_ply_wise_data( Parameters ---------- field: - The field to extract data from. + Field to extract data from. ply_name: The name of the ply to extract data from. mesh: From eda75f0d80b5e7a0d4031adadc2bc6c37d778126 Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Wed, 3 Jan 2024 13:18:07 +0100 Subject: [PATCH 08/21] Update src/ansys/dpf/composites/ply_wise_data.py Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- src/ansys/dpf/composites/ply_wise_data.py | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index 20eb215bc..5ebcb4967 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -35,18 +35,18 @@ def get_ply_wise_data( field: Field to extract data from. ply_name: - The name of the ply to extract data from. - mesh: - The meshed region. Needs to be enriched with composite information. - Use CompositeModel.get_mesh() to get the meshed region. - reduction_strategy: - The reduction strategy to get from spot values (BOT, MID, TOP) to a single value - per corner node and layer. The default is AVG. - requested_location: - The location of the output field. The default is elemental_nodal. Supported are - elemental_nodal, elemental, and nodal. - component: - The component to extract data from. Can be an int or an IntEnum. The default is 0. + Name of the ply to extract data from. + mesh : + Meshed region. Needs to be enriched with composite information. + Use the ``CompositeModel.get_mesh()`` method to get the meshed region. + reduction_strategy : + Reduction strategy for getting from spot values (BOT, MID, TOP) to a single value + per corner node and layer. The default is ``AVG``. + requested_location : + Location of the output field. The default is ``"elemental_nodal"``. Options are + ``"elemental"``, ``"elemental_nodal"``, and ``"nodal"``. + component : int or IntEnum, optional + Component to extract data from. The default is ``0``. """ component_int = component.value if isinstance(component, IntEnum) else component component_selector = operators.logic.component_selector() From b8f06cf88f375eafb5232c664addc7fff0c99260 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 16:31:42 +0100 Subject: [PATCH 09/21] Update filtering example --- examples/006_filter_composite_data_example.py | 126 +++++++++++------- 1 file changed, 79 insertions(+), 47 deletions(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index fb832e296..895c9e1d4 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -4,9 +4,15 @@ Filter result data by different criteria ---------------------------------------- -This example show how data filtering can be used for custom postprocessing of +This example shows how data filtering can be used for custom postprocessing of layered composites. You can filter strains and stresses by material, layer, or -analysis ply. The example filters data by layer, spot, and node, as well as material +analysis ply. Filtering by analysis ply is implemented on the server side and +exposed with the function :func:`get_ply_wise_data`. In this case the data is +filtered (and reduced) on the server side and only the resulting field is returned +to the client. This is the recommended way to filter data if possible. +For more complex filtering, the data is transferred to the client and side and filtered +using numpy functionality. +The examples show filtering data by layer, spot, and node, as well as material or analysis ply ID. To learn more about how layered result data is organized, see :ref:`select_indices`. """ @@ -24,11 +30,8 @@ from ansys.dpf.composites.composite_model import CompositeModel from ansys.dpf.composites.constants import Spot, Sym3x3TensorComponent from ansys.dpf.composites.example_helper import get_continuous_fiber_example_files -from ansys.dpf.composites.layup_info import ( - AnalysisPlyInfoProvider, - get_all_analysis_ply_names, - get_dpf_material_id_by_analysis_ply_map, -) +from ansys.dpf.composites.layup_info import AnalysisPlyInfoProvider, get_all_analysis_ply_names +from ansys.dpf.composites.ply_wise_data import ReductionStrategy, get_ply_wise_data from ansys.dpf.composites.select_indices import ( get_selected_indices, get_selected_indices_by_analysis_ply, @@ -38,7 +41,7 @@ # %% # Start a DPF server and copy the example files into the current working directory. -server = connect_to_or_start_server() +server = connect_to_or_start_server(port=50054) composite_files_on_server = get_continuous_fiber_example_files(server, "shell") # %% @@ -55,6 +58,43 @@ stress_operator.inputs.bool_rotate_to_global(False) stress_field = stress_operator.get_output(pin=0, output_type=dpf.types.fields_container)[0] +# %% +# Filter data by analysis ply +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# %% +# List all available analysis plies. +all_ply_names = get_all_analysis_ply_names(composite_model.get_mesh()) +all_ply_names + +# %% +# The easiest way to filter data by analysis ply is to use the function :func:`.get_ply_wise_data`. +# This function supports different reduction strategies such as computing the average +# or maximum/minimum over the spot locations. +# It also supports selecting a specific spot (TOP, MID, BOT) directly. +# In this example we select the maximum value over all spots for each node and then request +# the elemental location which implies averaging over all nodes in an element. +# Using :func:`.get_ply_wise_data` has the advantage that all the averaging and filtering +# is done on the server side. +elemental_max = get_ply_wise_data( + field=stress_field, + ply_name="P1L1__ud_patch ns1", + mesh=composite_model.get_mesh(), + component=Sym3x3TensorComponent.TENSOR11, + reduction_strategy=ReductionStrategy.MAX, + requested_location=dpf.locations.elemental, +) + +composite_model.get_mesh().plot(elemental_max) + + +# %% +# Generic client side filtering +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# This example shows how to filter data by layer, spot, and node using the generic filtering on +# the client side. +# Here we plot stress values in the material direction for the first node and top spot. + # %% # Get element information for all elements and show the first one as an example. element_ids = stress_field.scoping.ids @@ -62,9 +102,7 @@ element_infos[0] # %% -# Plot result data -# ~~~~~~~~~~~~~~~~ -# For the top layer, plot stress values in the material direction for the first node and top spot. +# Get filtered data component = Sym3x3TensorComponent.TENSOR11 result_field = dpf.field.Field(location=dpf.locations.elemental, nature=dpf.natures.scalar) with result_field.as_local_field() as local_result_field: @@ -82,23 +120,45 @@ composite_model.get_mesh().plot(result_field) + # %% -# List analysis plies -# ~~~~~~~~~~~~~~~~~~~ -# List all available analysis plies. -all_ply_names = get_all_analysis_ply_names(composite_model.get_mesh()) -all_ply_names +# Filtering by material +# ~~~~~~~~~~~~~~~~~~~~~ +# Loop over all elements and get the maximum stress in the material direction +# for all plies that have a specific UD material. + +ud_material_id = composite_model.material_names["Epoxy Carbon UD (230 GPa) Prepreg"] +component = Sym3x3TensorComponent.TENSOR11 + +material_result_field = dpf.field.Field(location=dpf.locations.elemental, nature=dpf.natures.scalar) +with material_result_field.as_local_field() as local_result_field: + element_ids = stress_field.scoping.ids + + for element_id in element_ids: + element_info = composite_model.get_element_info(element_id) + assert element_info is not None + if ud_material_id in element_info.dpf_material_ids: + stress_data = stress_field.get_entity_data_by_id(element_id) + selected_indices = get_selected_indices_by_dpf_material_ids( + element_info, [ud_material_id] + ) + + value = np.max(stress_data[selected_indices][:, component]) + local_result_field.append([value], element_id) + +composite_model.get_mesh().plot(material_result_field) # %% -# Plot results -# ~~~~~~~~~~~~ -# Loop all elements that contain a given ply and plot the maximum stress value +# Filter by analysis ply on the client side +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Loop over all elements that contain a given ply and plot the maximum stress value # in the material direction in this ply. component = Sym3x3TensorComponent.TENSOR11 analysis_ply_info_provider = AnalysisPlyInfoProvider( mesh=composite_model.get_mesh(), name="P1L1__ud_patch ns1" ) + ply_result_field = dpf.field.Field(location=dpf.locations.elemental, nature=dpf.natures.scalar) with ply_result_field.as_local_field() as local_result_field: element_ids = analysis_ply_info_provider.property_field.scoping.ids @@ -116,31 +176,3 @@ composite_model.get_mesh().plot(ply_result_field) - -# %% -# Loop all elements and get the maximum stress in the material direction -# for all plies that have a material with DPF material ID. -# Note: It is not possible to get a DPF material ID for a -# given material name. It is only possible to get a DPF material -# ID from an analysis ply. -material_map = get_dpf_material_id_by_analysis_ply_map( - composite_model.get_mesh(), data_source_or_streams_provider=composite_model.data_sources.rst -) -ud_material_id = material_map["P1L1__ud_patch ns1"] -component = Sym3x3TensorComponent.TENSOR11 - -material_result_field = dpf.field.Field(location=dpf.locations.elemental, nature=dpf.natures.scalar) -with material_result_field.as_local_field() as local_result_field: - element_ids = analysis_ply_info_provider.property_field.scoping.ids - - for element_id in element_ids: - stress_data = stress_field.get_entity_data_by_id(element_id) - element_info = composite_model.get_element_info(element_id) - assert element_info is not None - - selected_indices = get_selected_indices_by_dpf_material_ids(element_info, [ud_material_id]) - - value = np.max(stress_data[selected_indices][:, component]) - local_result_field.append([value], element_id) - -composite_model.get_mesh().plot(material_result_field) From c119f93f4f33b61932e070ba0624d76d95e912e6 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 16:36:05 +0100 Subject: [PATCH 10/21] Remove port specification --- examples/006_filter_composite_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index 895c9e1d4..7207d5215 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -41,7 +41,7 @@ # %% # Start a DPF server and copy the example files into the current working directory. -server = connect_to_or_start_server(port=50054) +server = connect_to_or_start_server() composite_files_on_server = get_continuous_fiber_example_files(server, "shell") # %% From dfa7f6afa01b33e903b1353fc98854d51d229b39 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 16:55:26 +0100 Subject: [PATCH 11/21] Assign correct location --- src/ansys/dpf/composites/ply_wise_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index 5ebcb4967..558fa7f1c 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -63,6 +63,7 @@ def get_ply_wise_data( elemental_nodal_data = filter_ply_data_op.outputs.field() if requested_location == locations.elemental_nodal: + elemental_nodal_data.location = locations.elemental_nodal return elemental_nodal_data if requested_location == locations.elemental: From 370f95fc62251a1db5de873e5c3c56c02479ae22 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 16:57:26 +0100 Subject: [PATCH 12/21] Add version checks in example --- examples/006_filter_composite_data_example.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index 7207d5215..fbc52da94 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -37,7 +37,7 @@ get_selected_indices_by_analysis_ply, get_selected_indices_by_dpf_material_ids, ) -from ansys.dpf.composites.server_helpers import connect_to_or_start_server +from ansys.dpf.composites.server_helpers import connect_to_or_start_server, version_equal_or_later # %% # Start a DPF server and copy the example files into the current working directory. @@ -76,16 +76,17 @@ # the elemental location which implies averaging over all nodes in an element. # Using :func:`.get_ply_wise_data` has the advantage that all the averaging and filtering # is done on the server side. -elemental_max = get_ply_wise_data( - field=stress_field, - ply_name="P1L1__ud_patch ns1", - mesh=composite_model.get_mesh(), - component=Sym3x3TensorComponent.TENSOR11, - reduction_strategy=ReductionStrategy.MAX, - requested_location=dpf.locations.elemental, -) - -composite_model.get_mesh().plot(elemental_max) +if version_equal_or_later(server, "8.0"): + elemental_max = get_ply_wise_data( + field=stress_field, + ply_name="P1L1__ud_patch ns1", + mesh=composite_model.get_mesh(), + component=Sym3x3TensorComponent.TENSOR11, + reduction_strategy=ReductionStrategy.MAX, + requested_location=dpf.locations.elemental, + ) + + composite_model.get_mesh().plot(elemental_max) # %% From 61a5ac061ac91ac5234b10e892f87976af5d40e7 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 17:00:55 +0100 Subject: [PATCH 13/21] Remove auto generated type. --- src/ansys/dpf/composites/ply_wise_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index 558fa7f1c..e37dbce30 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -45,7 +45,7 @@ def get_ply_wise_data( requested_location : Location of the output field. The default is ``"elemental_nodal"``. Options are ``"elemental"``, ``"elemental_nodal"``, and ``"nodal"``. - component : int or IntEnum, optional + component : Component to extract data from. The default is ``0``. """ component_int = component.value if isinstance(component, IntEnum) else component From dafed86d5f90f39f7c762ffc20588a3c846d3326 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 17:03:59 +0100 Subject: [PATCH 14/21] Skip tests for older versions --- tests/ply_wise_data_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/ply_wise_data_test.py b/tests/ply_wise_data_test.py index 77ee37416..e81ebb0f7 100644 --- a/tests/ply_wise_data_test.py +++ b/tests/ply_wise_data_test.py @@ -6,6 +6,7 @@ from ansys.dpf.composites.composite_model import CompositeModel from ansys.dpf.composites.constants import Sym3x3TensorComponent from ansys.dpf.composites.ply_wise_data import ReductionStrategy, get_ply_wise_data +from ansys.dpf.composites.server_helpers import version_equal_or_later from .helper import get_basic_shell_files @@ -55,6 +56,8 @@ def get_all_spot_values_first_element_first_node(stress_field, component=0): def test_get_ply_wise_data(dpf_server): + if not version_equal_or_later(dpf_server, "8.0"): + return files = get_basic_shell_files() composite_model = CompositeModel(files, server=dpf_server) From dca10aa688baf3bdbd680ee7e4e9a350a7d42d94 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 17:14:08 +0100 Subject: [PATCH 15/21] Fix link in docs --- examples/006_filter_composite_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index fbc52da94..de2a0532d 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -7,7 +7,7 @@ This example shows how data filtering can be used for custom postprocessing of layered composites. You can filter strains and stresses by material, layer, or analysis ply. Filtering by analysis ply is implemented on the server side and -exposed with the function :func:`get_ply_wise_data`. In this case the data is +exposed with the function :func:`.get_ply_wise_data`. In this case the data is filtered (and reduced) on the server side and only the resulting field is returned to the client. This is the recommended way to filter data if possible. For more complex filtering, the data is transferred to the client and side and filtered From 2bacdaee40a518151d401c91aee1b2f6453a9d19 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Wed, 3 Jan 2024 18:06:50 +0100 Subject: [PATCH 16/21] Don't override location. It is now set in the backend. --- src/ansys/dpf/composites/ply_wise_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index e37dbce30..ba84cc9da 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -63,7 +63,6 @@ def get_ply_wise_data( elemental_nodal_data = filter_ply_data_op.outputs.field() if requested_location == locations.elemental_nodal: - elemental_nodal_data.location = locations.elemental_nodal return elemental_nodal_data if requested_location == locations.elemental: From 8ceb47283ea34e5807c472e1b0fe7ea41880a18c Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Fri, 5 Jan 2024 10:03:06 +0100 Subject: [PATCH 17/21] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/006_filter_composite_data_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index de2a0532d..1e12d6f39 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -90,7 +90,7 @@ # %% -# Generic client side filtering +# Generic client-side filtering # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This example shows how to filter data by layer, spot, and node using the generic filtering on # the client side. @@ -123,7 +123,7 @@ # %% -# Filtering by material +# Filter by material # ~~~~~~~~~~~~~~~~~~~~~ # Loop over all elements and get the maximum stress in the material direction # for all plies that have a specific UD material. From 21cd5594d7da918f6abffeecb21cbeabefe35d7e Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Fri, 5 Jan 2024 12:08:41 +0100 Subject: [PATCH 18/21] Update examples/006_filter_composite_data_example.py Co-authored-by: Dominik Gresch --- examples/006_filter_composite_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index 1e12d6f39..c3315c118 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -10,7 +10,7 @@ exposed with the function :func:`.get_ply_wise_data`. In this case the data is filtered (and reduced) on the server side and only the resulting field is returned to the client. This is the recommended way to filter data if possible. -For more complex filtering, the data is transferred to the client and side and filtered +For more complex filtering, the data is transferred to the client side and filtered using numpy functionality. The examples show filtering data by layer, spot, and node, as well as material or analysis ply ID. To learn more about how layered result data is organized, From 65206ae56424026a1aa0889630779f8466deb02e Mon Sep 17 00:00:00 2001 From: janvonrickenbach Date: Tue, 9 Jan 2024 09:11:59 +0100 Subject: [PATCH 19/21] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/006_filter_composite_data_example.py | 18 +++++++++--------- src/ansys/dpf/composites/ply_wise_data.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index c3315c118..fe83023f7 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -7,7 +7,7 @@ This example shows how data filtering can be used for custom postprocessing of layered composites. You can filter strains and stresses by material, layer, or analysis ply. Filtering by analysis ply is implemented on the server side and -exposed with the function :func:`.get_ply_wise_data`. In this case the data is +exposed with the :func:`.get_ply_wise_data` function. In this case, the data is filtered (and reduced) on the server side and only the resulting field is returned to the client. This is the recommended way to filter data if possible. For more complex filtering, the data is transferred to the client side and filtered @@ -68,14 +68,14 @@ all_ply_names # %% -# The easiest way to filter data by analysis ply is to use the function :func:`.get_ply_wise_data`. -# This function supports different reduction strategies such as computing the average -# or maximum/minimum over the spot locations. +# The easiest way to filter data by analysis ply is to use the :func:`.get_ply_wise_data` function. +# This function supports different reduction strategies such as computing the average, +# maximum, or minimum over the spot locations. # It also supports selecting a specific spot (TOP, MID, BOT) directly. -# In this example we select the maximum value over all spots for each node and then request -# the elemental location which implies averaging over all nodes in an element. -# Using :func:`.get_ply_wise_data` has the advantage that all the averaging and filtering -# is done on the server side. +# This example selects the maximum value over all spots for each node and then requests +# the elemental location, which implies averaging over all nodes in an element. +# Using the :func:`.get_ply_wise_data` function has the advantage that all the averaging +# and filtering is done on the server side. if version_equal_or_later(server, "8.0"): elemental_max = get_ply_wise_data( field=stress_field, @@ -94,7 +94,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This example shows how to filter data by layer, spot, and node using the generic filtering on # the client side. -# Here we plot stress values in the material direction for the first node and top spot. +# This code plots stress values in the material direction for the first node and top spot. # %% # Get element information for all elements and show the first one as an example. diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index ba84cc9da..5467fa0f2 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -10,7 +10,7 @@ class ReductionStrategy(Enum): - """The reduction strategy to get from spot values (BOT, MID, TOP) to a single value.""" + """Provides the reduction strategy for getting from spot values (BOT, MID, TOP) to a single value.""" MIN = "MIN" MAX = "MAX" @@ -37,7 +37,7 @@ def get_ply_wise_data( ply_name: Name of the ply to extract data from. mesh : - Meshed region. Needs to be enriched with composite information. + Meshed region enriched with composite information. Use the ``CompositeModel.get_mesh()`` method to get the meshed region. reduction_strategy : Reduction strategy for getting from spot values (BOT, MID, TOP) to a single value From 6dfb11dcbf8bc2d263b1cab4c4921ac8e8b2c205 Mon Sep 17 00:00:00 2001 From: jvonrick Date: Tue, 9 Jan 2024 09:21:27 +0100 Subject: [PATCH 20/21] Make doc string fit in one line --- src/ansys/dpf/composites/ply_wise_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/composites/ply_wise_data.py b/src/ansys/dpf/composites/ply_wise_data.py index 5467fa0f2..5618851cf 100644 --- a/src/ansys/dpf/composites/ply_wise_data.py +++ b/src/ansys/dpf/composites/ply_wise_data.py @@ -10,7 +10,7 @@ class ReductionStrategy(Enum): - """Provides the reduction strategy for getting from spot values (BOT, MID, TOP) to a single value.""" + """Provides the reduction strategy for getting from spot values to a single value.""" MIN = "MIN" MAX = "MAX" From e6dfd05055ae9c28f4a081ca127683e3d417752c Mon Sep 17 00:00:00 2001 From: jvonrick Date: Tue, 9 Jan 2024 11:51:41 +0100 Subject: [PATCH 21/21] Fix intendation. --- examples/006_filter_composite_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/006_filter_composite_data_example.py b/examples/006_filter_composite_data_example.py index fe83023f7..a71696e07 100644 --- a/examples/006_filter_composite_data_example.py +++ b/examples/006_filter_composite_data_example.py @@ -75,7 +75,7 @@ # This example selects the maximum value over all spots for each node and then requests # the elemental location, which implies averaging over all nodes in an element. # Using the :func:`.get_ply_wise_data` function has the advantage that all the averaging -# and filtering is done on the server side. +# and filtering is done on the server side. if version_equal_or_later(server, "8.0"): elemental_max = get_ply_wise_data( field=stress_field,