Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add convenience function to get ply-wise data #396

Merged
merged 27 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
761a0f7
Add function to extract ply wise data
janvonrickenbach Dec 7, 2023
9bd530f
Add test for extract ply wise data
janvonrickenbach Dec 8, 2023
2893378
Add docs
janvonrickenbach Dec 8, 2023
b71d667
Fix title in documentation
janvonrickenbach Jan 3, 2024
28484eb
Merge branch 'main' into feat/ply_wise_plots
janvonrickenbach Jan 3, 2024
fd9fe91
Fix include
janvonrickenbach Jan 3, 2024
c728b8c
Merge remote-tracking branch 'origin/feat/ply_wise_plots' into feat/p…
janvonrickenbach Jan 3, 2024
18f5c9e
Update src/ansys/dpf/composites/ply_wise_data.py
janvonrickenbach Jan 3, 2024
03300a0
Update src/ansys/dpf/composites/ply_wise_data.py
janvonrickenbach Jan 3, 2024
eda75f0
Update src/ansys/dpf/composites/ply_wise_data.py
janvonrickenbach Jan 3, 2024
b8f06cf
Update filtering example
janvonrickenbach Jan 3, 2024
8329007
Merge remote-tracking branch 'origin/feat/ply_wise_plots' into feat/p…
janvonrickenbach Jan 3, 2024
c119f93
Remove port specification
janvonrickenbach Jan 3, 2024
dfa7f6a
Assign correct location
janvonrickenbach Jan 3, 2024
370f95f
Add version checks in example
janvonrickenbach Jan 3, 2024
61a5ac0
Remove auto generated type.
janvonrickenbach Jan 3, 2024
dafed86
Skip tests for older versions
janvonrickenbach Jan 3, 2024
dca10aa
Fix link in docs
janvonrickenbach Jan 3, 2024
2bacdae
Don't override location. It is now set in the backend.
janvonrickenbach Jan 3, 2024
7d5f59a
Merge branch 'main' into feat/ply_wise_plots
janvonrickenbach Jan 4, 2024
8ceb472
Apply suggestions from code review
janvonrickenbach Jan 5, 2024
21cd559
Update examples/006_filter_composite_data_example.py
janvonrickenbach Jan 5, 2024
9911df8
Merge branch 'main' into feat/ply_wise_plots
janvonrickenbach Jan 5, 2024
65206ae
Apply suggestions from code review
janvonrickenbach Jan 9, 2024
6dfb11d
Make doc string fit in one line
janvonrickenbach Jan 9, 2024
e6dfd05
Fix intendation.
janvonrickenbach Jan 9, 2024
686b7b4
Merge branch 'main' into feat/ply_wise_plots
janvonrickenbach Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/api/index.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
API reference

Check warning on line 1 in doc/source/api/index.rst

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/api/index.rst#L1

[Google.Headings] 'API reference' should use sentence-style capitalization.
Raw output
{"message": "[Google.Headings] 'API reference' should use sentence-style capitalization.", "location": {"path": "doc/source/api/index.rst", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
=============

This section describes the public classes, methods, and attributes of the PyDPF Composites API.
Expand All @@ -16,6 +16,7 @@
data_sources
failure_criteria
layup_info
ply_wise_data
result_definition
sampling_point
server_helpers
Expand Down
12 changes: 12 additions & 0 deletions doc/source/api/ply_wise_data.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Ply wise data
-------------

.. module:: ansys.dpf.composites.ply_wise_data

.. autosummary::
:toctree: _autosummary

ReductionStrategy
get_ply_wise_data


127 changes: 80 additions & 47 deletions examples/006_filter_composite_data_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
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
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
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`.
"""
Expand All @@ -24,17 +30,14 @@
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,
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.
Expand All @@ -55,16 +58,52 @@
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`.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
# This function supports different reduction strategies such as computing the average
# or maximum/minimum over the spot locations.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
# 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.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
# Using :func:`.get_ply_wise_data` has the advantage that all the averaging and filtering
# is done on the server side.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
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)


# %%
# 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.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved

# %%
# Get element information for all elements and show the first one as an example.
element_ids = stress_field.scoping.ids
element_infos = [composite_model.get_element_info(element_id) for element_id in element_ids]
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:
Expand All @@ -82,23 +121,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
# 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.

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
Expand All @@ -116,31 +177,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])
roosre marked this conversation as resolved.
Show resolved Hide resolved

value = np.max(stress_data[selected_indices][:, component])
local_result_field.append([value], element_id)

composite_model.get_mesh().plot(material_result_field)
2 changes: 2 additions & 0 deletions src/ansys/dpf/composites/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
data_sources,
failure_criteria,
layup_info,
ply_wise_data,
result_definition,
sampling_point,
select_indices,
Expand All @@ -27,6 +28,7 @@
"data_sources",
"failure_criteria",
"layup_info",
"ply_wise_data",
"result_definition",
"sampling_point",
"server_helpers",
Expand Down
4 changes: 2 additions & 2 deletions src/ansys/dpf/composites/layup_info/_layup_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).

janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
Note
----
Notes
-----
Cache the output because the computation can be performance-critical.
"""
warn(
Expand Down
87 changes: 87 additions & 0 deletions src/ansys/dpf/composites/ply_wise_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""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

__all__ = ("ReductionStrategy", "get_ply_wise_data")


class ReductionStrategy(Enum):
roosre marked this conversation as resolved.
Show resolved Hide resolved
"""The reduction strategy to get from spot values (BOT, MID, TOP) to a single value."""
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved

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:
Field to extract data from.
ply_name:
Name of the ply to extract data from.
mesh :
Meshed region. Needs to be enriched with composite information.
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
Use the ``CompositeModel.get_mesh()`` method to get the meshed region.
reduction_strategy :
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
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 :
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()

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}."
)
Loading
Loading