Skip to content

Commit

Permalink
Provide access to the ply type parameter of the materials (#465)
Browse files Browse the repository at this point in the history
* add property material_metadata to the composite model to provide access to additional material properties such as ply type and solver material index.

---------

Co-authored-by: Dominik Gresch <[email protected]>
  • Loading branch information
roosre and greschd authored Jun 4, 2024
1 parent 7786a12 commit e0508f1
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 97 deletions.
1 change: 1 addition & 0 deletions doc/source/api/layup_info.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ example shows how to evaluate material properties.
get_constant_property_dict
:template: autosummary/no_methods_doc/base.rst.jinja2
MaterialProperty
MaterialMetadata



Expand Down
153 changes: 62 additions & 91 deletions src/ansys/dpf/composites/_composite_model_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"""Composite Model Interface."""
# New interface after 2023 R2
from collections.abc import Collection, Sequence
from typing import Any, Callable, Optional, cast
from typing import Optional, cast
from warnings import warn

import ansys.dpf.core as dpf
Expand All @@ -32,8 +32,9 @@
import numpy as np
from numpy.typing import NDArray

from ._composite_model_impl_helpers import _deprecated_composite_definition_label, _merge_containers
from .composite_scope import CompositeScope
from .constants import FAILURE_LABEL, REF_SURFACE_NAME, TIME_LABEL, FailureOutput
from .constants import REF_SURFACE_NAME
from .data_sources import (
CompositeDataSources,
ContinuousFiberCompositesFiles,
Expand All @@ -54,7 +55,11 @@
_get_reference_surface_and_mapping_field,
)
from .layup_info.material_operators import MaterialOperators, get_material_operators
from .layup_info.material_properties import MaterialProperty, get_constant_property_dict
from .layup_info.material_properties import (
MaterialMetadata,
MaterialProperty,
get_constant_property_dict,
)
from .result_definition import FailureMeasureEnum
from .sampling_point import SamplingPointNew
from .server_helpers import (
Expand All @@ -65,84 +70,6 @@
from .unit_system import get_unit_system


def _deprecated_composite_definition_label(func: Callable[..., Any]) -> Any:
"""Emit a warning when the deprecated ``composite_definition_label`` is used."""
function_arg = "composite_definition_label"

def inner(*args: Sequence[Any], **kwargs: Sequence[Any]) -> Any:
if function_arg in kwargs.keys():
if kwargs[function_arg]:
warn(
f"Use of {function_arg} is deprecated. Function {func.__name__}."
" can be called without this argument.",
category=DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)

return inner


def _merge_containers(
non_ref_surface_container: FieldsContainer, ref_surface_container: FieldsContainer
) -> FieldsContainer:
"""
Merge the results fields container.
Merges the results fields container of the non-reference surface and the reference surface.
"""
assert sorted(non_ref_surface_container.labels) == sorted(ref_surface_container.labels)

ref_surface_time_ids = ref_surface_container.get_available_ids_for_label(TIME_LABEL)
non_ref_surface_time_ids = non_ref_surface_container.get_available_ids_for_label(TIME_LABEL)

assert sorted(ref_surface_time_ids) == sorted(non_ref_surface_time_ids)

out_container = dpf.FieldsContainer()
out_container.labels = [TIME_LABEL, FAILURE_LABEL]
out_container.time_freq_support = non_ref_surface_container.time_freq_support

def add_to_output_container(time_id: int, source_container: FieldsContainer) -> None:
fields = source_container.get_fields({TIME_LABEL: time_id})
for field in fields:
failure_enum = _get_failure_enum_from_name(field.name)
out_container.add_field({TIME_LABEL: time_id, FAILURE_LABEL: failure_enum}, field)

for current_time_id in ref_surface_time_ids:
add_to_output_container(current_time_id, ref_surface_container)
add_to_output_container(current_time_id, non_ref_surface_container)

return out_container


def _get_failure_enum_from_name(name: str) -> FailureOutput:
if name.startswith("Failure Mode"):
if name.endswith(REF_SURFACE_NAME):
return FailureOutput.FAILURE_MODE_REF_SURFACE
else:
return FailureOutput.FAILURE_MODE

if name.startswith("IRF") or name.startswith("SF") or name.startswith("SM"):
if name.endswith(REF_SURFACE_NAME):
return FailureOutput.FAILURE_VALUE_REF_SURFACE
else:
return FailureOutput.FAILURE_VALUE

if name.startswith("Layer Index"):
return FailureOutput.MAX_LAYER_INDEX

if name.startswith("Global Layer in Stack"):
return FailureOutput.MAX_GLOBAL_LAYER_IN_STACK

if name.startswith("Local Layer in Element"):
return FailureOutput.MAX_LOCAL_LAYER_IN_ELEMENT

if name.startswith("Solid Element Id"):
return FailureOutput.MAX_SOLID_ELEMENT_ID

raise RuntimeError("Could not determine failure output from name: " + name)


class CompositeModelImpl:
"""Provides access to the basic composite postprocessing functionality.
Expand Down Expand Up @@ -269,19 +196,15 @@ def material_names(self) -> dict[str, int]:
Material name to DPF material ID map.
This property can be used to filter analysis plies
or element layers.
or element layers by material name.
"""
try:
helper_op = dpf.Operator("composite::materials_container_helper")
except Exception as exc:
helper_op = self._material_operators.material_container_helper_op
if helper_op is None:
raise RuntimeError(
f"Operator composite::materials_container_helper doesn't exist. "
f"This could be because the server version is 2024 R1-pre0. The "
f"latest preview or the unified installer can be used instead. "
f"Error: {exc}"
) from exc
"The used DPF server does not support the requested data. "
"Use version 2024 R1-pre0 or later."
)

helper_op.inputs.materials_container(self._material_operators.material_provider.outputs)
string_field = helper_op.outputs.material_names()
material_ids = string_field.scoping.ids

Expand All @@ -291,6 +214,54 @@ def material_names(self) -> dict[str, int]:

return names

@property
def material_metadata(self) -> dict[int, MaterialMetadata]:
"""
DPF material ID to metadata map of the materials.
This data can be used to filter analysis plies
or element layers by ply type, material name etc.
Note: ply type is only available in DPF server version 9.0 (2025 R1 pre0) and later.
"""
helper_op = self._material_operators.material_container_helper_op
if helper_op is None:
raise RuntimeError(
"The used DPF server does not support the requested data. "
"Use version 2024 R1-pre0 or later."
)
material_name_field = helper_op.outputs.material_names()
if hasattr(helper_op.outputs, "solver_material_ids"):
solver_id_field = helper_op.outputs.solver_material_ids()
else:
solver_id_field = None
material_ids = material_name_field.scoping.ids
if hasattr(helper_op.outputs, "ply_types"):
ply_type_field = helper_op.outputs.ply_types()
else:
ply_type_field = None

metadata = {}
for dpf_mat_id in material_ids:
metadata[dpf_mat_id] = MaterialMetadata(
dpf_material_id=dpf_mat_id,
material_name=material_name_field.data[
material_name_field.scoping.index(dpf_mat_id)
],
ply_type=(
ply_type_field.data[ply_type_field.scoping.index(dpf_mat_id)]
if ply_type_field
else None
),
solver_material_id=(
solver_id_field.data[solver_id_field.scoping.index(dpf_mat_id)]
if solver_id_field
else None
),
)

return metadata

@_deprecated_composite_definition_label
def get_mesh(self, composite_definition_label: Optional[str] = None) -> MeshedRegion:
"""Get the underlying DPF meshed region.
Expand Down
15 changes: 14 additions & 1 deletion src/ansys/dpf/composites/_composite_model_impl_2023r2.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@
get_element_info_provider,
)
from .layup_info.material_operators import MaterialOperators, get_material_operators
from .layup_info.material_properties import MaterialProperty, get_constant_property_dict
from .layup_info.material_properties import (
MaterialMetadata,
MaterialProperty,
get_constant_property_dict,
)
from .result_definition import FailureMeasureEnum, ResultDefinition, ResultDefinitionScope
from .sampling_point_2023r2 import SamplingPoint2023R2
from .sampling_point_types import SamplingPoint
Expand Down Expand Up @@ -226,6 +230,15 @@ def material_names(self) -> dict[str, int]:
" or later should be used instead."
)

@property
def material_metadata(self) -> dict[int, MaterialMetadata]:
"""DPF Material ID to metadata map. Metadata are for example name and ply type."""
raise NotImplementedError(
"material_metadata is not implemented"
" for this version of DPF. DPF server 9.0 (2025 R1 pre0)"
" or later should be used instead."
)

def get_layup_operator(self, composite_definition_label: Optional[str] = None) -> Operator:
"""Get the lay-up operator.
Expand Down
110 changes: 110 additions & 0 deletions src/ansys/dpf/composites/_composite_model_impl_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 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.

"""Composite Model Interface."""
# New interface after 2023 R2
from collections.abc import Sequence
from typing import Any, Callable
from warnings import warn

import ansys.dpf.core as dpf
from ansys.dpf.core import FieldsContainer

from .constants import FAILURE_LABEL, REF_SURFACE_NAME, TIME_LABEL, FailureOutput


def _deprecated_composite_definition_label(func: Callable[..., Any]) -> Any:
"""Emit a warning when the deprecated ``composite_definition_label`` is used."""
function_arg = "composite_definition_label"

def inner(*args: Sequence[Any], **kwargs: Sequence[Any]) -> Any:
if function_arg in kwargs.keys():
if kwargs[function_arg]:
warn(
f"Use of {function_arg} is deprecated. Function {func.__name__}."
" can be called without this argument.",
category=DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)

return inner


def _merge_containers(
non_ref_surface_container: FieldsContainer, ref_surface_container: FieldsContainer
) -> FieldsContainer:
"""
Merge the results fields container.
Merges the results fields container of the non-reference surface and the reference surface.
"""
assert sorted(non_ref_surface_container.labels) == sorted(ref_surface_container.labels)

ref_surface_time_ids = ref_surface_container.get_available_ids_for_label(TIME_LABEL)
non_ref_surface_time_ids = non_ref_surface_container.get_available_ids_for_label(TIME_LABEL)

assert sorted(ref_surface_time_ids) == sorted(non_ref_surface_time_ids)

out_container = dpf.FieldsContainer()
out_container.labels = [TIME_LABEL, FAILURE_LABEL]
out_container.time_freq_support = non_ref_surface_container.time_freq_support

def add_to_output_container(time_id: int, source_container: FieldsContainer) -> None:
fields = source_container.get_fields({TIME_LABEL: time_id})
for field in fields:
failure_enum = _get_failure_enum_from_name(field.name)
out_container.add_field({TIME_LABEL: time_id, FAILURE_LABEL: failure_enum}, field)

for current_time_id in ref_surface_time_ids:
add_to_output_container(current_time_id, ref_surface_container)
add_to_output_container(current_time_id, non_ref_surface_container)

return out_container


def _get_failure_enum_from_name(name: str) -> FailureOutput:
if name.startswith("Failure Mode"):
if name.endswith(REF_SURFACE_NAME):
return FailureOutput.FAILURE_MODE_REF_SURFACE
else:
return FailureOutput.FAILURE_MODE

if name.startswith("IRF") or name.startswith("SF") or name.startswith("SM"):
if name.endswith(REF_SURFACE_NAME):
return FailureOutput.FAILURE_VALUE_REF_SURFACE
else:
return FailureOutput.FAILURE_VALUE

if name.startswith("Layer Index"):
return FailureOutput.MAX_LAYER_INDEX

if name.startswith("Global Layer in Stack"):
return FailureOutput.MAX_GLOBAL_LAYER_IN_STACK

if name.startswith("Local Layer in Element"):
return FailureOutput.MAX_LOCAL_LAYER_IN_ELEMENT

if name.startswith("Solid Element Id"):
return FailureOutput.MAX_SOLID_ELEMENT_ID

raise RuntimeError("Could not determine failure output from name: " + name)
21 changes: 19 additions & 2 deletions src/ansys/dpf/composites/composite_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from .failure_criteria import CombinedFailureCriterion
from .layup_info import ElementInfo, LayerProperty, LayupModelContextType
from .layup_info.material_operators import MaterialOperators
from .layup_info.material_properties import MaterialProperty
from .layup_info.material_properties import MaterialMetadata, MaterialProperty
from .result_definition import FailureMeasureEnum
from .sampling_point_types import SamplingPoint

Expand Down Expand Up @@ -131,9 +131,26 @@ def material_operators(self) -> MaterialOperators:

@property
def material_names(self) -> dict[str, int]:
"""Get material name to DPF material ID map."""
"""
Material name to DPF material ID map.
This property can be used to filter analysis plies
or element layers by material name.
"""
return self._implementation.material_names

@property
def material_metadata(self) -> dict[int, MaterialMetadata]:
"""
DPF material ID to metadata map of the materials.
This data can be used to filter analysis plies
or element layers by ply type, material name etc.
Note: ply type is only available in DPF server version 9.0 (2025 R1 pre0) and later.
"""
return self._implementation.material_metadata

def get_mesh(self, composite_definition_label: Optional[str] = None) -> MeshedRegion:
"""Get the underlying DPF meshed region.
Expand Down
Loading

0 comments on commit e0508f1

Please sign in to comment.