Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/ansys/pydpf-composites into…
Browse files Browse the repository at this point in the history
… maint/license-headers
  • Loading branch information
greschd committed Jan 16, 2024
2 parents ba66e0b + dc8d9ab commit 134d665
Show file tree
Hide file tree
Showing 16 changed files with 1,906 additions and 1,338 deletions.
1 change: 1 addition & 0 deletions doc/source/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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

SpotReductionStrategy
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 @@ -26,9 +26,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 :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
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 @@ -46,17 +52,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 SpotReductionStrategy, 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 @@ -77,16 +80,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 :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.
# 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,
ply_name="P1L1__ud_patch ns1",
mesh=composite_model.get_mesh(),
component=Sym3x3TensorComponent.TENSOR11,
spot_reduction_strategy=SpotReductionStrategy.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.
# 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.
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 @@ -104,23 +143,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 @@ -138,31 +199,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)
178 changes: 178 additions & 0 deletions examples/012_fatigue_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
.. _fatigue_plate_example:
Evaluate fatigue for a composite plate
--------------------------------------
This example shows how to evaluate fatigue for a flat plate.
It shows how you can use PyPDF Composites to select specific layers and define a custom
combination method. For this example, the custom combination method is stress in fibre
direction.
A random load time series is created. Taking into account that the load is assumed
proportional, rainflow counting is applied to the load time series.
Load ranges are then applied on the stress combination method, and damage is evaluated
by using a dummy S-N curve.
Be aware that the fatpack package is not developed by Ansys, so it is the responsibility
of the user to verify that it works as expected. For more information, see the
`fatpack package <https://pypi.org/project/fatpack/>`_,
"""


# %%
# Set up analysis
# ~~~~~~~~~~~~~~~
# Setting up the analysis consists of loading the required modules, connecting to the
# DPF server, and retrieving the example files.
#
# Load Ansys libraries and numpy, matplotlib and fatpack
import ansys.dpf.core as dpf
import fatpack
import matplotlib.pyplot as plt
import numpy as np

from ansys.dpf.composites.composite_model import CompositeModel
from ansys.dpf.composites.constants import Sym3x3TensorComponent
from ansys.dpf.composites.example_helper import get_continuous_fiber_example_files
from ansys.dpf.composites.layup_info import AnalysisPlyInfoProvider
from ansys.dpf.composites.select_indices import get_selected_indices_by_analysis_ply
from ansys.dpf.composites.server_helpers import connect_to_or_start_server

# %%
# Start a DPF server and copy the example files into the current working directory.
server = connect_to_or_start_server()
composite_files_on_server = get_continuous_fiber_example_files(server, "fatigue")

# %%
# Create a composite model
composite_model = CompositeModel(composite_files_on_server, server)

# %%
# Read stresses and define a specific layer and a component of stress tensor
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

# %%
# Read stresses
stress_operator = composite_model.core_model.results.stress()
stress_operator.inputs.bool_rotate_to_global(False)
stress_fc = stress_operator.get_output(pin=0, output_type=dpf.types.fields_container)
stress_field = stress_fc.get_field_by_time_id(1)

# %%
# Select layer P1L1__ModelingPly.2
analysis_ply_info_provider = AnalysisPlyInfoProvider(
mesh=composite_model.get_mesh(), name="P1L1__ModelingPly.2"
)

# %%
# Select Sigma11 as the combination method
component = Sym3x3TensorComponent.TENSOR11


# %%
# Load time series and apply rainflow counting
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# A random time series is created. Load is assumed proportional, so rainflow counting
# can be directly done on the load time series to get the load ranges.
# No mean stress correction is applied.
#
number_of_times = 100
load_factor_time_series = np.random.normal(-1, 2.5, size=number_of_times)
x = np.linspace(1, number_of_times, number_of_times)
plt.xlabel("Load Index")
plt.ylabel("Load Factor")
plt.plot(x, load_factor_time_series, color="red")


# %%
# Fatpack package is used for doing the rainflow counting
load_range_factors = fatpack.find_rainflow_ranges(load_factor_time_series)


# %%
# S-N curve
# ~~~~~~~~~
# A dummy S-N curve is created. Note that this curve is not based on any
# experimental data. Sc is chosen to be twice the orthotropic stress limit in the fiber direction.
# and Nc is set to 1.
#
Sc = 2 * 1979
Nc = 1
s_n_curve = fatpack.LinearEnduranceCurve(Sc)
# Value for UD materials
s_n_curve.m = 14
s_n_curve.Nc = Nc

N = np.logspace(0, 9, 1000)
S = s_n_curve.get_stress(N)
line = plt.loglog(N, S)
plt.grid(which="both")
plt.title("Dummy Linear S-N curve")
plt.xlabel("Cycles to failure")
plt.ylabel("Stress range (MPa)")


# %%
# Damage evaluation
# ~~~~~~~~~~~~~~~~~
# Stress S11 at time 1 and layer P1L1__ModelingPly.2 are read
# for each load range. Its damage is evaluated using the dummy S-N curve.
#

damage_result_field = dpf.field.Field(location=dpf.locations.elemental, nature=dpf.natures.scalar)

with damage_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_analysis_ply(
analysis_ply_info_provider, element_info
)
# Load Range scaled by S11
s_11 = max(stress_data[selected_indices][:, component])
stress_ranges = load_range_factors * s_11
fatigue_damage = s_n_curve.find_miner_sum(stress_ranges)
local_result_field.append([fatigue_damage], element_id)


# %%
# Plot damage
composite_model.get_mesh().plot(damage_result_field, text="Fatigue Damage")


# %%
# Identify the element with the maximum damage
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
maximum_element_scoping = damage_result_field.max().scoping
max_element_id = maximum_element_scoping[0]
print(f"The element with highest damage is {max_element_id}.")
print(f"The highest damage value is {damage_result_field.max().data[0]}.")
Loading

0 comments on commit 134d665

Please sign in to comment.