diff --git a/doc/source/conf.py b/doc/source/conf.py index 35f0b64975c..01773e85243 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -104,7 +104,6 @@ "*/gate/*", "*/gatebin/*", "*/grpc/*", - "*/property_fields_container.py" ] # -- General configuration --------------------------------------------------- diff --git a/src/ansys/dpf/core/__init__.py b/src/ansys/dpf/core/__init__.py index df42d9d5109..1711c6be30b 100644 --- a/src/ansys/dpf/core/__init__.py +++ b/src/ansys/dpf/core/__init__.py @@ -39,6 +39,7 @@ from ansys.dpf.core.custom_type_field import CustomTypeField # noqa: F401 from ansys.dpf.core.dimensionality import Dimensionality from ansys.dpf.core.property_field import PropertyField +from ansys.dpf.core.property_fields_container import PropertyFieldsContainer from ansys.dpf.core.string_field import StringField from ansys.dpf.core.fields_container import FieldsContainer from ansys.dpf.core.meshes_container import MeshesContainer diff --git a/src/ansys/dpf/core/mapping_types.py b/src/ansys/dpf/core/mapping_types.py index d11bd69b7e5..a96a6d00dee 100644 --- a/src/ansys/dpf/core/mapping_types.py +++ b/src/ansys/dpf/core/mapping_types.py @@ -61,6 +61,9 @@ map_types_to_cpp["float"] = "double" map_types_to_cpp["UnitSystem"] = "class dataProcessing::unit::CUnitSystem" map_types_to_cpp["dict"] = "label_space" +map_types_to_cpp["PropertyFieldsContainer"] = ( + "class dataProcessing::DpfTypeCollection" +) class _smart_dict_snake(dict): diff --git a/src/ansys/dpf/core/operators/averaging/elemental_nodal_to_nodal_fc.py b/src/ansys/dpf/core/operators/averaging/elemental_nodal_to_nodal_fc.py index aee8c51866d..af8c4457d08 100644 --- a/src/ansys/dpf/core/operators/averaging/elemental_nodal_to_nodal_fc.py +++ b/src/ansys/dpf/core/operators/averaging/elemental_nodal_to_nodal_fc.py @@ -19,6 +19,7 @@ from ansys.dpf.core.fields_container import FieldsContainer from ansys.dpf.core.meshed_region import MeshedRegion from ansys.dpf.core.meshes_container import MeshesContainer + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer from ansys.dpf.core.scoping import Scoping from ansys.dpf.core.scopings_container import ScopingsContainer @@ -52,8 +53,7 @@ class elemental_nodal_to_nodal_fc(Operator): Outputs ------- fields_container: FieldsContainer - weights: Class Dataprocessing::Dpftypecollection<Class - Dataprocessing::Cpropertyfield> + weights: PropertyFieldsContainer Gives for each node, the number of times it was found in the Elemental Nodal field. Can be used to average later. Examples @@ -507,7 +507,7 @@ def __init__(self, op: Operator): elemental_nodal_to_nodal_fc._spec().output_pin(0), 0, op ) self._outputs.append(self._fields_container) - self._weights: Output = Output( + self._weights: Output[PropertyFieldsContainer] = Output( elemental_nodal_to_nodal_fc._spec().output_pin(1), 1, op ) self._outputs.append(self._weights) @@ -531,7 +531,7 @@ def fields_container(self) -> Output[FieldsContainer]: return self._fields_container @property - def weights(self) -> Output: + def weights(self) -> Output[PropertyFieldsContainer]: r"""Allows to get weights output of the operator Gives for each node, the number of times it was found in the Elemental Nodal field. Can be used to average later. diff --git a/src/ansys/dpf/core/operators/build.py b/src/ansys/dpf/core/operators/build.py index 66e6f653cf6..35ca83cea22 100644 --- a/src/ansys/dpf/core/operators/build.py +++ b/src/ansys/dpf/core/operators/build.py @@ -43,7 +43,6 @@ "MeshSelectionManager", "Class Dataprocessing::Dpftypecollection", "Struct Iansdispatch", - "PropertyFieldsContainer", "Class Dataprocessing::Crstfilewrapper", "Char", ) diff --git a/src/ansys/dpf/core/operators/logic/enrich_materials.py b/src/ansys/dpf/core/operators/logic/enrich_materials.py index 47237413da3..e41e9ecc446 100644 --- a/src/ansys/dpf/core/operators/logic/enrich_materials.py +++ b/src/ansys/dpf/core/operators/logic/enrich_materials.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from ansys.dpf.core.fields_container import FieldsContainer + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer from ansys.dpf.core.streams_container import StreamsContainer @@ -191,7 +192,7 @@ def __init__(self, op: Operator): enrich_materials._spec().input_pin(1), 1, op, -1 ) self._inputs.append(self._streams) - self._streams_mapping: Input = Input( + self._streams_mapping: Input[PropertyFieldsContainer] = Input( enrich_materials._spec().input_pin(2), 2, op, -1 ) self._inputs.append(self._streams_mapping) @@ -235,7 +236,7 @@ def streams(self) -> Input[StreamsContainer | FieldsContainer]: return self._streams @property - def streams_mapping(self) -> Input: + def streams_mapping(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect streams_mapping input to the operator. Returns diff --git a/src/ansys/dpf/core/operators/logic/identical_pfc.py b/src/ansys/dpf/core/operators/logic/identical_pfc.py index d190d9596f4..1d56d756217 100644 --- a/src/ansys/dpf/core/operators/logic/identical_pfc.py +++ b/src/ansys/dpf/core/operators/logic/identical_pfc.py @@ -5,6 +5,7 @@ """ from __future__ import annotations +from typing import TYPE_CHECKING from warnings import warn from ansys.dpf.core.dpf_operator import Operator @@ -14,6 +15,9 @@ from ansys.dpf.core.config import Config from ansys.dpf.core.server_types import AnyServerType +if TYPE_CHECKING: + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer + class identical_pfc(Operator): r"""Checks if two property_fields_container are identical. @@ -172,17 +176,17 @@ class InputsIdenticalPfc(_Inputs): def __init__(self, op: Operator): super().__init__(identical_pfc._spec().inputs, op) - self._property_fields_containerA: Input = Input( + self._property_fields_containerA: Input[PropertyFieldsContainer] = Input( identical_pfc._spec().input_pin(0), 0, op, -1 ) self._inputs.append(self._property_fields_containerA) - self._property_fields_containerB: Input = Input( + self._property_fields_containerB: Input[PropertyFieldsContainer] = Input( identical_pfc._spec().input_pin(1), 1, op, -1 ) self._inputs.append(self._property_fields_containerB) @property - def property_fields_containerA(self) -> Input: + def property_fields_containerA(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect property_fields_containerA input to the operator. Returns @@ -201,7 +205,7 @@ def property_fields_containerA(self) -> Input: return self._property_fields_containerA @property - def property_fields_containerB(self) -> Input: + def property_fields_containerB(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect property_fields_containerB input to the operator. Returns diff --git a/src/ansys/dpf/core/operators/result/mapdl_split_to_acmo_facet_indices.py b/src/ansys/dpf/core/operators/result/mapdl_split_to_acmo_facet_indices.py index 2fff06dd8cf..167c25dd089 100644 --- a/src/ansys/dpf/core/operators/result/mapdl_split_to_acmo_facet_indices.py +++ b/src/ansys/dpf/core/operators/result/mapdl_split_to_acmo_facet_indices.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from ansys.dpf.core.fields_container import FieldsContainer + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer class mapdl_split_to_acmo_facet_indices(Operator): @@ -182,9 +183,9 @@ def __init__(self, op: Operator): mapdl_split_to_acmo_facet_indices._spec().input_pin(0), 0, op, -1 ) self._inputs.append(self._fields_container) - self._property_fields_container_element_types: Input = Input( - mapdl_split_to_acmo_facet_indices._spec().input_pin(1), 1, op, -1 - ) + self._property_fields_container_element_types: Input[ + PropertyFieldsContainer + ] = Input(mapdl_split_to_acmo_facet_indices._spec().input_pin(1), 1, op, -1) self._inputs.append(self._property_fields_container_element_types) @property @@ -209,7 +210,7 @@ def fields_container(self) -> Input[FieldsContainer]: return self._fields_container @property - def property_fields_container_element_types(self) -> Input: + def property_fields_container_element_types(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect property_fields_container_element_types input to the operator. It should only have the 'facet' label. For each facet, it stores a PropertyField with the element types of the corresponding elements.The scoping should be the same as the scoping of the corresponding Field in input 0. diff --git a/src/ansys/dpf/core/operators/scoping/rescope_property_field.py b/src/ansys/dpf/core/operators/scoping/rescope_property_field.py index 31970043e91..c2e31bdabe7 100644 --- a/src/ansys/dpf/core/operators/scoping/rescope_property_field.py +++ b/src/ansys/dpf/core/operators/scoping/rescope_property_field.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: from ansys.dpf.core.property_field import PropertyField + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer from ansys.dpf.core.scoping import Scoping @@ -187,7 +188,7 @@ class InputsRescopePropertyField(_Inputs): def __init__(self, op: Operator): super().__init__(rescope_property_field._spec().inputs, op) - self._fields: Input[PropertyField] = Input( + self._fields: Input[PropertyFieldsContainer | PropertyField] = Input( rescope_property_field._spec().input_pin(0), 0, op, -1 ) self._inputs.append(self._fields) @@ -201,7 +202,7 @@ def __init__(self, op: Operator): self._inputs.append(self._default_value) @property - def fields(self) -> Input[PropertyField]: + def fields(self) -> Input[PropertyFieldsContainer | PropertyField]: r"""Allows to connect fields input to the operator. Returns diff --git a/src/ansys/dpf/core/operators/utility/extract_scoping.py b/src/ansys/dpf/core/operators/utility/extract_scoping.py index 8dea62a8e47..db4a7ef3a76 100644 --- a/src/ansys/dpf/core/operators/utility/extract_scoping.py +++ b/src/ansys/dpf/core/operators/utility/extract_scoping.py @@ -23,6 +23,7 @@ from ansys.dpf.core.meshed_region import MeshedRegion from ansys.dpf.core.meshes_container import MeshesContainer from ansys.dpf.core.property_field import PropertyField + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer from ansys.dpf.core.scoping import Scoping from ansys.dpf.core.scopings_container import ScopingsContainer from ansys.dpf.core.string_field import StringField @@ -196,6 +197,7 @@ def __init__(self, op: Operator): Field | FieldsContainer | PropertyField + | PropertyFieldsContainer | CustomTypeField | StringField | Scoping @@ -216,6 +218,7 @@ def field_or_fields_container( Field | FieldsContainer | PropertyField + | PropertyFieldsContainer | CustomTypeField | StringField | Scoping diff --git a/src/ansys/dpf/core/operators/utility/merge_property_fields.py b/src/ansys/dpf/core/operators/utility/merge_property_fields.py index 241b147c099..908abda8f57 100644 --- a/src/ansys/dpf/core/operators/utility/merge_property_fields.py +++ b/src/ansys/dpf/core/operators/utility/merge_property_fields.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from ansys.dpf.core.property_field import PropertyField + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer class merge_property_fields(Operator): @@ -187,11 +188,11 @@ def __init__(self, op: Operator): merge_property_fields._spec().input_pin(-201), -201, op, -1 ) self._inputs.append(self._naive_merge) - self._property_fields1: Input[PropertyField] = Input( + self._property_fields1: Input[PropertyField | PropertyFieldsContainer] = Input( merge_property_fields._spec().input_pin(0), 0, op, 0 ) self._inputs.append(self._property_fields1) - self._property_fields2: Input[PropertyField] = Input( + self._property_fields2: Input[PropertyField | PropertyFieldsContainer] = Input( merge_property_fields._spec().input_pin(1), 1, op, 1 ) self._inputs.append(self._property_fields2) @@ -218,7 +219,7 @@ def naive_merge(self) -> Input[bool]: return self._naive_merge @property - def property_fields1(self) -> Input[PropertyField]: + def property_fields1(self) -> Input[PropertyField | PropertyFieldsContainer]: r"""Allows to connect property_fields1 input to the operator. Either a property fields container, a vector of property fields to merge or property fields from pin 0 to ... @@ -239,7 +240,7 @@ def property_fields1(self) -> Input[PropertyField]: return self._property_fields1 @property - def property_fields2(self) -> Input[PropertyField]: + def property_fields2(self) -> Input[PropertyField | PropertyFieldsContainer]: r"""Allows to connect property_fields2 input to the operator. Either a property fields container, a vector of property fields to merge or property fields from pin 0 to ... diff --git a/src/ansys/dpf/core/operators/utility/merge_weighted_fields_containers.py b/src/ansys/dpf/core/operators/utility/merge_weighted_fields_containers.py index eb1d181e5de..5dff318e322 100644 --- a/src/ansys/dpf/core/operators/utility/merge_weighted_fields_containers.py +++ b/src/ansys/dpf/core/operators/utility/merge_weighted_fields_containers.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from ansys.dpf.core.fields_container import FieldsContainer + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer class merge_weighted_fields_containers(Operator): @@ -36,11 +37,9 @@ class merge_weighted_fields_containers(Operator): A vector of fields containers to merge or fields containers from pin 0 to ... fields_containers2: FieldsContainer A vector of fields containers to merge or fields containers from pin 0 to ... - weights1: Class Dataprocessing::Dpftypecollection<Class - Dataprocessing::Cpropertyfield> + weights1: PropertyFieldsContainer Weights to apply to each field from pin 1000 to ... - weights2: Class Dataprocessing::Dpftypecollection<Class - Dataprocessing::Cpropertyfield> + weights2: PropertyFieldsContainer Weights to apply to each field from pin 1000 to ... Outputs @@ -65,9 +64,9 @@ class merge_weighted_fields_containers(Operator): >>> op.inputs.fields_containers1.connect(my_fields_containers1) >>> my_fields_containers2 = dpf.FieldsContainer() >>> op.inputs.fields_containers2.connect(my_fields_containers2) - >>> my_weights1 = dpf.Class Dataprocessing::Dpftypecollection<Class Dataprocessing::Cpropertyfield>() + >>> my_weights1 = dpf.PropertyFieldsContainer() >>> op.inputs.weights1.connect(my_weights1) - >>> my_weights2 = dpf.Class Dataprocessing::Dpftypecollection<Class Dataprocessing::Cpropertyfield>() + >>> my_weights2 = dpf.PropertyFieldsContainer() >>> op.inputs.weights2.connect(my_weights2) >>> # Instantiate operator and connect inputs in one line @@ -254,9 +253,9 @@ class InputsMergeWeightedFieldsContainers(_Inputs): >>> op.inputs.fields_containers1.connect(my_fields_containers1) >>> my_fields_containers2 = dpf.FieldsContainer() >>> op.inputs.fields_containers2.connect(my_fields_containers2) - >>> my_weights1 = dpf.Class Dataprocessing::Dpftypecollection<Class Dataprocessing::Cpropertyfield>() + >>> my_weights1 = dpf.PropertyFieldsContainer() >>> op.inputs.weights1.connect(my_weights1) - >>> my_weights2 = dpf.Class Dataprocessing::Dpftypecollection<Class Dataprocessing::Cpropertyfield>() + >>> my_weights2 = dpf.PropertyFieldsContainer() >>> op.inputs.weights2.connect(my_weights2) """ @@ -282,11 +281,11 @@ def __init__(self, op: Operator): merge_weighted_fields_containers._spec().input_pin(1), 1, op, 1 ) self._inputs.append(self._fields_containers2) - self._weights1: Input = Input( + self._weights1: Input[PropertyFieldsContainer] = Input( merge_weighted_fields_containers._spec().input_pin(1000), 1000, op, 0 ) self._inputs.append(self._weights1) - self._weights2: Input = Input( + self._weights2: Input[PropertyFieldsContainer] = Input( merge_weighted_fields_containers._spec().input_pin(1001), 1001, op, 1 ) self._inputs.append(self._weights2) @@ -397,7 +396,7 @@ def fields_containers2(self) -> Input[FieldsContainer]: return self._fields_containers2 @property - def weights1(self) -> Input: + def weights1(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect weights1 input to the operator. Weights to apply to each field from pin 1000 to ... @@ -418,7 +417,7 @@ def weights1(self) -> Input: return self._weights1 @property - def weights2(self) -> Input: + def weights2(self) -> Input[PropertyFieldsContainer]: r"""Allows to connect weights2 input to the operator. Weights to apply to each field from pin 1000 to ... diff --git a/src/ansys/dpf/core/operators/utility/propertyfield_get_attribute.py b/src/ansys/dpf/core/operators/utility/propertyfield_get_attribute.py index e60ce4de25c..838fc1b8d5f 100644 --- a/src/ansys/dpf/core/operators/utility/propertyfield_get_attribute.py +++ b/src/ansys/dpf/core/operators/utility/propertyfield_get_attribute.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: from ansys.dpf.core.property_field import PropertyField + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer class propertyfield_get_attribute(Operator): @@ -173,7 +174,7 @@ class InputsPropertyfieldGetAttribute(_Inputs): def __init__(self, op: Operator): super().__init__(propertyfield_get_attribute._spec().inputs, op) - self._property_field: Input[PropertyField] = Input( + self._property_field: Input[PropertyField | PropertyFieldsContainer] = Input( propertyfield_get_attribute._spec().input_pin(0), 0, op, -1 ) self._inputs.append(self._property_field) @@ -183,7 +184,7 @@ def __init__(self, op: Operator): self._inputs.append(self._property_name) @property - def property_field(self) -> Input[PropertyField]: + def property_field(self) -> Input[PropertyField | PropertyFieldsContainer]: r"""Allows to connect property_field input to the operator. Returns diff --git a/src/ansys/dpf/core/property_fields_container.py b/src/ansys/dpf/core/property_fields_container.py index 9d1f0a6f80e..28ff78ba68e 100644 --- a/src/ansys/dpf/core/property_fields_container.py +++ b/src/ansys/dpf/core/property_fields_container.py @@ -21,227 +21,63 @@ # SOFTWARE. """ -MockPropertyFieldsContainer. +PropertyFieldsContainer. -Contains classes associated with the _MockPropertyFieldsContainer. +Contains classes associated with the PropertyFieldsContainer. """ from __future__ import annotations -from collections.abc import Sequence -import copy -from typing import Dict, List, Union +from typing import TYPE_CHECKING, Optional -import numpy as np +from ansys.dpf.core import property_field +from ansys.dpf.core.collection import Collection -import ansys.dpf.core as dpf -from ansys.dpf.core import PropertyField -from ansys.dpf.core.server_types import BaseServer +if TYPE_CHECKING: + from ansys.dpf.core.property_fields_container import PropertyFieldsContainer + from ansys.dpf.core.server_types import AnyServerType -class _LabelSpaceKV: - """Class for internal use to associate a label space with a field.""" +class PropertyFieldsContainer(Collection["property_field.PropertyField"]): + """Represents a property fields container, which is a collection of property fields. - def __init__(self, _dict: Dict[str, int], _field: dpf.Field): - """Construct an association between a dictionary and a field.""" - self._dict = _dict - self._field = _field + A property fields container is a collection of property fields ordered by labels and IDs. + Each property field in the collection has an ID for each label, allowing flexible + organization and retrieval of property fields based on various criteria. - @property - def dict(self) -> dict: - """Returns the associated dictionary.""" - return self._dict + Parameters + ---------- + property_fields_container : ansys.grpc.dpf.collection_message_pb2.Collection, ctypes.c_void_p, + PropertyFieldsContainer, optional + Property fields container created from either a collection message or by copying + an existing one. The default is ``None``. + server : ansys.dpf.core.server, optional + Server with the channel connected to the remote or local instance. + The default is ``None``, in which case an attempt is made to use the global + server. - @property - def field(self) -> dpf.Field: - """Returns the associated field.""" - return self._field + Examples + -------- + Create a property fields container from scratch. - @field.setter - def field(self, value: dpf.Field): - self._field = value + >>> from ansys.dpf import core as dpf + >>> pfc = dpf.PropertyFieldsContainer() + >>> pfc.labels = ['time', 'body'] + >>> for i in range(0, 5): + ... label_space = {"time": i+1, "body": 0} + ... pfield = dpf.PropertyField() + ... pfield.data = list(range(i*10, (i+1)*10)) + ... pfc.add_entry(label_space, pfield) - def __str__(self): - """Return a string representation of the association.""" - field_str = str(self._field).replace("\n", "\n\t\t\t") - return f"Label Space: {self._dict} with field\n\t\t\t{field_str}" - - -class _MockPropertyFieldsContainer(Sequence): - """Minimal implementation of a FieldsContainer specialized for _MockPropertyFieldsContainer.""" + """ def __init__( self, - fields_container: _MockPropertyFieldsContainer = None, - server: BaseServer = None, + property_fields_container: Optional[PropertyFieldsContainer] = None, + server: Optional[AnyServerType] = None, + entries_type: type = property_field.PropertyField, ): - """Construct a _MockPropertyFieldsContainer.""" - # default constructor - self._labels = [] # used by Dataframe - self.scopings = [] - self._server = None # used by Dataframe - - self.label_spaces = [] - self.ids = [] - - # _MockPropertyFieldsContainer copy - if fields_container is not None: - self._labels = copy.deepcopy(fields_container.labels) - # self.scopings = copy.deepcopy(fields_container.scopings) - self._server = fields_container._server - - # self.ids = copy.deepcopy(fields_container.ids) - - for ls in fields_container.label_spaces: - self.add_entry(copy.deepcopy(ls.dict), ls.field.as_local_field()) - - # server copy - if server is not None: - self._server = server - - # Collection - def __str__(self) -> str: - """Return a string representation of a _MockPropertyFieldsContainer.""" - txt = f"DPF PropertyFieldsContainer with {len(self)} fields\n" - for idx, ls in enumerate(self.label_spaces): - txt += f"\t {idx}: {ls}\n" - - return txt - - @property - def labels(self) -> List[str]: - """Returns all labels of the _MockPropertyFieldsContainer.""" - return self._labels - - @labels.setter - def labels(self, labels: List[str]): - """Set all the label of the _MockPropertyFieldsContainer.""" - if len(self._labels) != 0: - raise ValueError("labels already set") - for l in labels: - self.add_label(l) - - def add_label(self, label: str): - """Add a label.""" - if label not in self._labels: - self._labels.append(label) - self.scopings.append([]) - - def has_label(self, label) -> bool: - """Check if a _MockPropertyFieldsContainer contains a given label.""" - return label in self.labels - - # used by Dataframe - def get_label_space(self, idx) -> Dict: - """Get a Label Space at a given index.""" - return self.label_spaces[idx].dict - - # used by Dataframe - def get_label_scoping(self, label="time") -> dpf.Scoping: - """Return a scoping on the fields concerned by the given label.""" - if label in self.labels: - scoping_ids = self.scopings[self.labels.index(label)] - return dpf.Scoping(ids=scoping_ids, location="") - raise KeyError(f"label {label} not found") - - def add_entry(self, label_space: Dict[str, int], value: dpf.Field): - """Add a PropertyField associated with a dictionary.""" - new_id = self._new_id() - - if hasattr(value, "_server"): - self._server = value._server - - # add Label Space - self.label_spaces.append(_LabelSpaceKV(label_space, value)) - - # Update IDs - self.ids.append(new_id) - - # Update Scopings - for label in label_space.keys(): - label_idx = self.labels.index(label) - self.scopings[label_idx].append(new_id) - - def add_field(self, label_space: Dict[str, int], field: dpf.Field): - """Add or update a field at a requested label space.""" - self.add_entry(label_space, field) - - def get_entries(self, label_space_or_index: Union[Dict[str, int], int]): - """Return a list of fields from a complete or partial specification of a dictionary.""" - if isinstance(label_space_or_index, int): - idx: int = label_space_or_index - return [self.label_spaces[idx].field] - else: - _dict: Dict[str, int] = label_space_or_index - are_keys_in_labels = [key in self.labels for key in _dict.keys()] - if all(are_keys_in_labels): - remaining = set(range(len(self.label_spaces))) - for key in _dict.keys(): - val = _dict[key] - to_remove = set() - for idx in remaining: - ls = self.label_spaces[idx] - if key in ls.dict.keys(): - if ls.dict[key] != val: - to_remove.add(idx) - else: - to_remove.add(idx) - remaining = remaining.difference(to_remove) - - idx_to_field = lambda idx: self.label_spaces[idx].field - return list(map(idx_to_field, remaining)) - else: - bad_idx = are_keys_in_labels.index(False) - bad_key = list(_dict.keys())[bad_idx] - raise KeyError(f"Key {bad_key} is not in labels: {self.labels}") - - def get_entry(self, label_space_or_index: Union[Dict[str, int], int]): - """Return the field or (first field found) corresponding to the given dictionary.""" - ret = self.get_entries(label_space_or_index) - - if len(ret) != 0: - return ret[0] - - raise ValueError("Could not find corresponding entry") - - def _new_id(self) -> int: - """Helper-method generating a new id when calling add_entry(...).""" - if len(self.ids) == 0: - self.last_id = 1 - return self.last_id - else: - self.last_id += 1 - return self.last_id - - # used by Dataframe - def get_fields(self, label_space: Dict[str, int]) -> List[dpf.Field]: - """Return the list of fields associated with given label space.""" - return self.get_entries(label_space) - - def get_field(self, label_space_or_index: Union[Dict[str, int], int]) -> dpf.Field: - """Retrieve the field at a requested index or label space.""" - return self.get_entry(label_space_or_index) - - # used by Dataframe - def __getitem__(self, key: Union[Dict[str, int], int]) -> dpf.Field: - """Retrieve the field at a requested index.""" - return self.get_field(key) - - def __len__(self) -> int: - """Retrieve the number of label spaces.""" - return len(self.label_spaces) - - def _set_field(self, ls_idx, field): - self.label_spaces[ls_idx].field = field - - def rescope(self, scoping: dpf.Scoping): # Used by post.Dataframe - """Helper-function to reproduce functionality of rescope_fc Operator.""" - copy_fc = _MockPropertyFieldsContainer(self, server=None) - for idx, label_space in enumerate(copy_fc.label_spaces): - pfield = PropertyField(location=label_space.field.location) - pfield.data = np.ravel( - [label_space._field.get_entity_data_by_id(id) for id in scoping.ids] - ) - pfield.scoping.ids = scoping.ids - copy_fc._set_field(idx, pfield) - return copy_fc + """Initialize a property fields container.""" + super().__init__( + collection=property_fields_container, server=server, entries_type=entries_type + ) diff --git a/tests/test_property_fields_container.py b/tests/test_property_fields_container.py index 5c8525b4aa6..084439e1011 100644 --- a/tests/test_property_fields_container.py +++ b/tests/test_property_fields_container.py @@ -23,47 +23,159 @@ import pytest from ansys.dpf import core as dpf -from ansys.dpf.core.property_fields_container import _LabelSpaceKV, _MockPropertyFieldsContainer +from ansys.dpf.core.property_fields_container import PropertyFieldsContainer +import conftest +@pytest.mark.skipif( + not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_8_1, reason="Available for servers >=8.1" +) def test_property_fields_container(allkindofcomplexity, server_type): + """Test PropertyFieldsContainer class.""" model = dpf.Model(allkindofcomplexity, server=server_type) - fields_container = _MockPropertyFieldsContainer(server=server_type) - fields_container.add_label(label="test") - assert fields_container.has_label(label="test") - assert fields_container.labels == ["test"] - with pytest.raises(ValueError, match="labels already set"): - fields_container.labels = ["test"] - field = model.metadata.meshed_region.elements.connectivities_field - fields_container.add_field(label_space={"test": 42}, field=field) - assert len(fields_container.label_spaces) == 1 - label_space = fields_container.label_spaces[0] - assert fields_container.get_label_space(0) == {"test": 42} - assert isinstance(label_space, _LabelSpaceKV) - assert label_space.field == field - assert label_space.dict == {"test": 42} - label_space.field = model.metadata.meshed_region.elements.element_types_field - ref = """DPF PropertyFieldsContainer with 1 fields -\t 0: Label Space: {'test': 42} with field -\t\t\t""" # noqa - assert ref in str(fields_container) - with pytest.raises(KeyError, match="label test2 not found"): - fields_container.get_label_scoping("test2") - scoping = fields_container.get_label_scoping("test") + + # Create a PropertyFieldsContainer + pfc = PropertyFieldsContainer(server=server_type) + + # Test adding labels + pfc.add_label(label="test") + assert pfc.has_label(label="test") + assert pfc.labels == ["test"] + + # Test adding another label + pfc.add_label(label="body") + assert pfc.has_label(label="body") + assert pfc.labels == ["body", "test"] + + # Get a property field from the model + property_field = model.metadata.meshed_region.elements.connectivities_field + + # Test add_entry method + pfc.add_entry(label_space={"test": 42, "body": 0}, entry=property_field) + assert len(pfc) == 1 + + # Test get_label_space + label_space = pfc.get_label_space(0) + assert label_space == {"test": 42, "body": 0} + + scoping = pfc.get_label_scoping("test") assert isinstance(scoping, dpf.Scoping) - assert scoping.ids == [1] - assert scoping.location == "" - - property_field = fields_container.get_entries(0)[0] - assert isinstance(property_field, dpf.property_field.PropertyField) - assert fields_container.get_entries({"test": 42})[0] == property_field - with pytest.raises(KeyError, match="is not in labels:"): - fields_container.get_entries(({"test2": 0})) - assert fields_container.get_entry({"test": 42}) == property_field - with pytest.raises(ValueError, match="Could not find corresponding entry"): - fields_container.get_entry(({"test": 0})) - assert fields_container[{"test": 42}] == property_field - assert len(fields_container) == 1 - - assert fields_container.get_fields({"test": 42})[0] == property_field - assert fields_container.get_field(0) == property_field + assert 42 in scoping.ids + + # Test get_entries with label space + entries = pfc.get_entries({"test": 42, "body": 0}) + assert len(entries) >= 1 + retrieved_field = entries[0] + assert isinstance(retrieved_field, dpf.PropertyField) + + # Test get_entry with label space + entry = pfc.get_entry({"test": 42, "body": 0}) + assert isinstance(entry, dpf.PropertyField) + + # Test get_entry with index + entry_by_index = pfc.get_entry(0) + assert isinstance(entry_by_index, dpf.PropertyField) + + # Test __getitem__ with index + field_by_index = pfc[0] + assert isinstance(field_by_index, dpf.PropertyField) + + # Test __getitem__ with label space + field_by_label = pfc.get_entry({"test": 42, "body": 0}) + assert isinstance(field_by_label, dpf.PropertyField) + + # Test adding more entries with different label spaces + property_field2 = model.metadata.meshed_region.elements.element_types_field + pfc.add_entry(label_space={"test": 43, "body": 0}, entry=property_field2) + assert len(pfc) == 2 + + # Test retrieving multiple entries with partial label space + entries_body0 = pfc.get_entries({"body": 0}) + assert len(entries_body0) == 2 + + # Test get_available_ids_for_label + test_ids = pfc.get_available_ids_for_label("test") + assert 42 in test_ids + assert 43 in test_ids + + +@pytest.mark.skipif( + not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_8_1, reason="Available for servers >=8.1" +) +def test_property_fields_container_from_scratch(server_type): + """Test creating PropertyFieldsContainer from scratch without a model.""" + # Create a PropertyFieldsContainer + pfc = PropertyFieldsContainer(server=server_type) + + # Set labels + pfc.labels = ["time", "body"] + assert pfc.labels == ["body", "time"] + + # Create property fields and add them + for i in range(3): + label_space = {"time": i + 1, "body": 0} + pfield = dpf.PropertyField(server=server_type) + pfield.data = list(range(i * 10, (i + 1) * 10)) + pfc.add_entry(label_space, pfield) + + # Verify collection size + assert len(pfc) == 3 + + # Test get_entries with full label space + entries = pfc.get_entries({"time": 1, "body": 0}) + assert len(entries) >= 1 + assert isinstance(entries[0], dpf.PropertyField) + + # Test get_entries with partial label space + all_time_fields = pfc.get_entries({"body": 0}) + assert len(all_time_fields) == 3 + + # Test get_entry by index + first_field = pfc.get_entry(0) + assert isinstance(first_field, dpf.PropertyField) + + # Test get_entry by label space + time2_field = pfc.get_entry({"time": 2, "body": 0}) + assert isinstance(time2_field, dpf.PropertyField) + + # Test __getitem__ + field_idx = pfc[1] + assert isinstance(field_idx, dpf.PropertyField) + + field_label = pfc.get_entry({"time": 3, "body": 0}) + assert isinstance(field_label, dpf.PropertyField) + + # Test get_label_space + ls0 = pfc.get_label_space(0) + assert ls0["time"] == 1 + assert ls0["body"] == 0 + + ls2 = pfc.get_label_space(2) + assert ls2["time"] == 3 + assert ls2["body"] == 0 + + # Test get_label_scoping + time_scoping = pfc.get_label_scoping("time") + assert isinstance(time_scoping, dpf.Scoping) + assert 1 in time_scoping.ids + assert 2 in time_scoping.ids + assert 3 in time_scoping.ids + + # Test get_available_ids_for_label + time_ids = pfc.get_available_ids_for_label("time") + assert 1 in time_ids + assert 2 in time_ids + assert 3 in time_ids + + body_ids = pfc.get_available_ids_for_label("body") + assert 0 in body_ids + + # Test has_label + assert pfc.has_label("time") + assert pfc.has_label("body") + assert not pfc.has_label("complex") + + # Test add_label + pfc.add_label("complex", default_value=0) + assert pfc.has_label("complex") + assert "complex" in pfc.labels