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 support for plots on reference surfaces #384

Merged
merged 13 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ jobs:
# if other containers must be tested.
run: |
docker pull ghcr.io/ansys/pydpf-composites:${{ env.CONTAINER_TAG }}
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
docker pull ghcr.io/ansys/pydpf-composites:2024r1_pre0
docker pull ghcr.io/ansys/pydpf-composites:2024r1
docker pull ghcr.io/ansys/pydpf-composites:2023r2_pre1

- name: "Checkout the project"
Expand Down
19 changes: 17 additions & 2 deletions examples/008_assembly_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
from ansys.dpf.composites.constants import FailureOutput
from ansys.dpf.composites.example_helper import get_continuous_fiber_example_files
from ansys.dpf.composites.failure_criteria import CombinedFailureCriterion, MaxStressCriterion
from ansys.dpf.composites.server_helpers import connect_to_or_start_server, version_older_than
from ansys.dpf.composites.server_helpers import (
connect_to_or_start_server,
version_equal_or_later,
version_older_than,
)

# %%
# Start a DPF server and copy the example files into the current working directory.
Expand All @@ -47,11 +51,22 @@
# %%
# Plot IRF
# ~~~~~~~~
# Plot the maximum IRF per element.
# Plot the maximum IRF per (solid) element.
output_all_elements = composite_model.evaluate_failure_criteria(combined_criterion=combined_fc)
irf_field = output_all_elements.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
irf_field.plot()

# %%
# Plot IRF
# ~~~~~~~~
# Plot the maximum IRF on the reference surface
if version_equal_or_later(server, "8.0"):
irf_field = output_all_elements.get_field(
{"failure_label": FailureOutput.FAILURE_VALUE_REF_SURFACE}
)
irf_field.plot()


# %%
# Get element information
# ~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
107 changes: 102 additions & 5 deletions src/ansys/dpf/composites/_composite_model_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from numpy.typing import NDArray

from .composite_scope import CompositeScope
from .constants import FAILURE_LABEL, REF_SURFACE_NAME, TIME_LABEL, FailureOutput
from .data_sources import (
CompositeDataSources,
ContinuousFiberCompositesFiles,
Expand All @@ -24,11 +25,18 @@
add_layup_info_to_mesh,
get_element_info_provider,
)
from .layup_info._reference_surface import (
_get_map_to_reference_surface_operator,
_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 .result_definition import FailureMeasureEnum
from .sampling_point import SamplingPointNew
from .server_helpers import upload_continuous_fiber_composite_files_to_server
from .server_helpers import (
upload_continuous_fiber_composite_files_to_server,
version_equal_or_later,
)
from .unit_system import get_unit_system


Expand All @@ -50,6 +58,66 @@ def inner(*args: Sequence[Any], **kwargs: Sequence[Any]) -> Any:
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) -> FieldsContainer:
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 @@ -108,6 +176,16 @@ def __init__(
unit_system=self._unit_system,
)

if version_equal_or_later(self._server, "8.0"):
self._reference_surface_and_mapping_field = _get_reference_surface_and_mapping_field(
data_sources=self.data_sources.composite, unit_system=self._unit_system
)

self._map_to_reference_surface_operator = _get_map_to_reference_surface_operator(
reference_surface_and_mapping_field=self._reference_surface_and_mapping_field,
element_layer_indices_field=self.get_mesh().property_field("element_layer_indices"),
)

self._element_info_provider = get_element_info_provider(
mesh=self.get_mesh(),
stream_provider_or_data_source=self._get_rst_streams_provider(),
Expand Down Expand Up @@ -270,7 +348,7 @@ def evaluate_failure_criteria(
scope_config_reader_op.inputs.ply_ids(selected_plies_op.outputs.strings)

# configure operator to chunk the scope
chunking_data_tree = dpf.DataTree({"max_chunk_size": 50000})
chunking_data_tree = dpf.DataTree({"max_chunk_size": max_chunk_size})
if ns_in:
chunking_data_tree.add({"named_selections": ns_in})

Expand Down Expand Up @@ -380,10 +458,29 @@ def evaluate_failure_criteria(
"No output is generated! Please check the scope (element and ply ids)."
)

if measure == FailureMeasureEnum.INVERSE_RESERVE_FACTOR:
return max_merger.outputs.merged_fields_container()
if version_equal_or_later(self._server, "8.0"):
self._map_to_reference_surface_operator.inputs.min_container(
min_merger.outputs.merged_fields_container()
)
self._map_to_reference_surface_operator.inputs.max_container(
max_merger.outputs.merged_fields_container()
)

if measure == FailureMeasureEnum.INVERSE_RESERVE_FACTOR:
return _merge_containers(
max_merger.outputs.merged_fields_container(),
self._map_to_reference_surface_operator.outputs.max_container(),
)
else:
return _merge_containers(
min_merger.outputs.merged_fields_container(),
self._map_to_reference_surface_operator.outputs.min_container(),
)
else:
return min_merger.outputs.merged_fields_container()
if measure == FailureMeasureEnum.INVERSE_RESERVE_FACTOR:
return max_merger.outputs.merged_fields_container()
else:
return min_merger.outputs.merged_fields_container()

@_deprecated_composite_definition_label
def get_sampling_point(
Expand Down
3 changes: 3 additions & 0 deletions src/ansys/dpf/composites/_composite_model_impl_2023r2.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def evaluate_failure_criteria(
composite_scope: Optional[CompositeScope] = None,
measure: FailureMeasureEnum = FailureMeasureEnum.INVERSE_RESERVE_FACTOR,
write_data_for_full_element_scope: bool = True,
max_chunk_size: int = 50000,
) -> FieldsContainer:
"""Get a fields container with the evaluated failure criteria.

Expand All @@ -249,6 +250,8 @@ def evaluate_failure_criteria(
part of ``composite_scope.plies``. If no element scope is
specified (``composite_scope.elements``), a (potentially zero)
failure value is written for all elements.
max_chunk_size:
A higher value results in more memory consumption, but faster evaluation.

.. note::

Expand Down
9 changes: 8 additions & 1 deletion src/ansys/dpf/composites/composite_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def evaluate_failure_criteria(
composite_scope: Optional[CompositeScope] = None,
measure: FailureMeasureEnum = FailureMeasureEnum.INVERSE_RESERVE_FACTOR,
write_data_for_full_element_scope: bool = True,
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
max_chunk_size: int = 50000,
) -> FieldsContainer:
"""Get a fields container with the evaluated failure criteria.

Expand All @@ -171,6 +172,8 @@ def evaluate_failure_criteria(
part of ``composite_scope.plies``. If no element scope is
specified (``composite_scope.elements``), a (potentially zero)
failure value is written for all elements.
max_chunk_size:
A higher value results in more memory consumption, but faster evaluation.

.. note::

Expand All @@ -179,7 +182,11 @@ def evaluate_failure_criteria(

"""
return self._implementation.evaluate_failure_criteria(
combined_criterion, composite_scope, measure, write_data_for_full_element_scope
combined_criterion,
composite_scope,
measure,
write_data_for_full_element_scope,
max_chunk_size,
)

def get_sampling_point(
Expand Down
21 changes: 19 additions & 2 deletions src/ansys/dpf/composites/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
"""Collection of constants used across PyDPF Composites."""

from enum import IntEnum

__all__ = ("Spot", "Sym3x3TensorComponent", "FailureOutput")
__all__ = (
"Spot",
"Sym3x3TensorComponent",
"FailureOutput",
"REF_SURFACE_NAME",
"FAILURE_LABEL",
"TIME_LABEL",
)

FAILURE_LABEL = "failure_label"
TIME_LABEL = "time"


class Spot(IntEnum):
janvonrickenbach marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -33,3 +42,11 @@ class FailureOutput(IntEnum):
FAILURE_MODE = 0
FAILURE_VALUE = 1
MAX_LAYER_INDEX = 2
FAILURE_MODE_REF_SURFACE = 3
FAILURE_VALUE_REF_SURFACE = 4
MAX_GLOBAL_LAYER_IN_STACK = 5
MAX_LOCAL_LAYER_IN_ELEMENT = 6
MAX_SOLID_ELEMENT_ID = 7


REF_SURFACE_NAME = "Reference Surface"
36 changes: 36 additions & 0 deletions src/ansys/dpf/composites/layup_info/_reference_surface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from dataclasses import dataclass

from ansys.dpf.core import DataSources, Field, Operator, PropertyField

from ansys.dpf.composites.unit_system import UnitSystemProvider


@dataclass(frozen=True)
class _ReferenceSurfaceMeshAndMappingField:
mesh: Field
mapping_field: Field


def _get_reference_surface_and_mapping_field(
data_sources: DataSources, unit_system: UnitSystemProvider
) -> _ReferenceSurfaceMeshAndMappingField:
ref_surface_operator = Operator("composite::reference_surface_operator")
ref_surface_operator.inputs.data_sources.connect(data_sources)
ref_surface_operator.inputs.unit_system.connect(unit_system)
return _ReferenceSurfaceMeshAndMappingField(
mesh=ref_surface_operator.outputs.mesh(),
mapping_field=ref_surface_operator.outputs.mapping_field(),
)


def _get_map_to_reference_surface_operator(
reference_surface_and_mapping_field: _ReferenceSurfaceMeshAndMappingField,
element_layer_indices_field: PropertyField,
) -> Operator:
map_to_reference_surface_operator = Operator("composite::map_to_reference_surface_operator")
map_to_reference_surface_operator.inputs.mapping_field.connect(
reference_surface_and_mapping_field.mapping_field
)
map_to_reference_surface_operator.inputs.mesh.connect(reference_surface_and_mapping_field.mesh)
map_to_reference_surface_operator.inputs.layers_per_element.connect(element_layer_indices_field)
return map_to_reference_surface_operator
4 changes: 3 additions & 1 deletion src/ansys/dpf/composites/server_helpers/_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ class _DpfVersionInfo:

_DPF_VERSIONS: dict[str, _DpfVersionInfo] = {
"5.0": _DpfVersionInfo("5.0", "2023 R1", "Initial release of DPF Composites."),
"7.0": _DpfVersionInfo("7.0", "2024 R1", "DPF Composites plugin with sub-operators."),
"7.0": _DpfVersionInfo("7.0", "2024 R1 pre 0", "DPF Composites plugin with sub-operators."),
"7.1": _DpfVersionInfo("7.1", "2024 R1", "Layer index starts at 1."),
"8.0": _DpfVersionInfo("8.0", "2024 R2 pre 0", "Reference surface support"),
}


Expand Down
Loading
Loading