Skip to content

Commit bd7e921

Browse files
authored
Add _MockPropertyFieldsContainer class (#1033)
* Move PropertyFieldsContainer from Post PR to Core * Move PropertyFieldsContainer from Post PR to Core * Add testing * Improve coverage * Fix retro * Rename to _MockPropertyFieldsContainer * Ignore property_fields_container.py on API doc generation * Remove unnecessary methods * Improve typehinting * Put back _MockPropertyFieldsContainer.rescope as it is used by post.Dataframe.select * Fix retro * Add back _set_field
1 parent 6df1efd commit bd7e921

File tree

3 files changed

+274
-1
lines changed

3 files changed

+274
-1
lines changed

.ci/build_doc.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ call sphinx-apidoc -o ../docs/source/api ../src/ansys ../src/ansys/dpf/core/log.
44
../src/ansys/dpf/core/field_base.py ../src/ansys/dpf/core/cache.py ../src/ansys/dpf/core/misc.py ^
55
../src/ansys/dpf/core/check_version.py ../src/ansys/dpf/core/operators/build.py ../src/ansys/dpf/core/operators/specification.py ^
66
../src/ansys/dpf/core/vtk_helper.py ../src/ansys/dpf/core/label_space.py ../src/ansys/dpf/core/examples/python_plugins/* ^
7-
../src/ansys/dpf/core/examples/examples.py ^
7+
../src/ansys/dpf/core/examples/examples.py ../src/ansys/dpf/core/property_fields_container.py ^
88
-f --implicit-namespaces --separate --no-headings
99
pushd .
1010
cd ../docs/
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
"""
2+
.. _ref_property_fields_container:
3+
4+
_MockPropertyFieldsContainer
5+
============================
6+
Contains classes associated with the _MockPropertyFieldsContainer.
7+
"""
8+
from __future__ import annotations
9+
10+
from collections.abc import Sequence
11+
import copy
12+
from typing import Dict, List, Union
13+
14+
import numpy as np
15+
16+
import ansys.dpf.core as dpf
17+
from ansys.dpf.core import PropertyField
18+
from ansys.dpf.core.server_types import BaseServer
19+
20+
21+
class _LabelSpaceKV:
22+
"""Class for internal use to associate a label space with a field."""
23+
24+
def __init__(self, _dict: Dict[str, int], _field: dpf.Field):
25+
"""Constructs an association between a dictionary and a field."""
26+
self._dict = _dict
27+
self._field = _field
28+
29+
@property
30+
def dict(self) -> dict:
31+
"""Returns the associated dictionary."""
32+
return self._dict
33+
34+
@property
35+
def field(self) -> dpf.Field:
36+
"""Returns the associated field."""
37+
return self._field
38+
39+
@field.setter
40+
def field(self, value: dpf.Field):
41+
self._field = value
42+
43+
def __str__(self):
44+
"""Returns a string representation of the association."""
45+
field_str = str(self._field).replace("\n", "\n\t\t\t")
46+
return f"Label Space: {self._dict} with field\n\t\t\t{field_str}"
47+
48+
49+
class _MockPropertyFieldsContainer(Sequence):
50+
"""Minimal implementation of a FieldsContainer specialized for _MockPropertyFieldsContainer."""
51+
52+
def __init__(
53+
self,
54+
fields_container: _MockPropertyFieldsContainer = None,
55+
server: BaseServer = None,
56+
):
57+
"""Constructs a _MockPropertyFieldsContainer."""
58+
# default constructor
59+
self._labels = [] # used by Dataframe
60+
self.scopings = []
61+
self._server = None # used by Dataframe
62+
63+
self.label_spaces = []
64+
self.ids = []
65+
66+
# _MockPropertyFieldsContainer copy
67+
if fields_container is not None:
68+
self._labels = copy.deepcopy(fields_container.labels)
69+
# self.scopings = copy.deepcopy(fields_container.scopings)
70+
self._server = fields_container._server
71+
72+
# self.ids = copy.deepcopy(fields_container.ids)
73+
74+
for ls in fields_container.label_spaces:
75+
self.add_entry(copy.deepcopy(ls.dict), ls.field.as_local_field())
76+
77+
# server copy
78+
if server is not None:
79+
self._server = server
80+
81+
# Collection
82+
def __str__(self) -> str:
83+
"""Returns a string representation of a _MockPropertyFieldsContainer."""
84+
txt = f"DPF PropertyFieldsContainer with {len(self)} fields\n"
85+
for idx, ls in enumerate(self.label_spaces):
86+
txt += f"\t {idx}: {ls}\n"
87+
88+
return txt
89+
90+
@property
91+
def labels(self) -> List[str]:
92+
"""Returns all labels of the _MockPropertyFieldsContainer."""
93+
return self._labels
94+
95+
@labels.setter
96+
def labels(self, labels: List[str]):
97+
"""Sets all the label of the _MockPropertyFieldsContainer."""
98+
if len(self._labels) != 0:
99+
raise ValueError("labels already set")
100+
for l in labels:
101+
self.add_label(l)
102+
103+
def add_label(self, label: str):
104+
"""Adds a label."""
105+
if label not in self._labels:
106+
self._labels.append(label)
107+
self.scopings.append([])
108+
109+
def has_label(self, label) -> bool:
110+
"""Check if a _MockPropertyFieldsContainer contains a given label."""
111+
return label in self.labels
112+
113+
# used by Dataframe
114+
def get_label_space(self, idx) -> Dict:
115+
"""Get a Label Space at a given index."""
116+
return self.label_spaces[idx].dict
117+
118+
# used by Dataframe
119+
def get_label_scoping(self, label="time") -> dpf.Scoping:
120+
"""Returns a scoping on the fields concerned by the given label."""
121+
if label in self.labels:
122+
scoping_ids = self.scopings[self.labels.index(label)]
123+
return dpf.Scoping(ids=scoping_ids, location="")
124+
raise KeyError(f"label {label} not found")
125+
126+
def add_entry(self, label_space: Dict[str, int], value: dpf.Field):
127+
"""Adds a PropertyField associated with a dictionary."""
128+
new_id = self._new_id()
129+
130+
if hasattr(value, "_server"):
131+
self._server = value._server
132+
133+
# add Label Space
134+
self.label_spaces.append(_LabelSpaceKV(label_space, value))
135+
136+
# Update IDs
137+
self.ids.append(new_id)
138+
139+
# Update Scopings
140+
for label in label_space.keys():
141+
label_idx = self.labels.index(label)
142+
self.scopings[label_idx].append(new_id)
143+
144+
def add_field(self, label_space: Dict[str, int], field: dpf.Field):
145+
"""Add or update a field at a requested label space."""
146+
self.add_entry(label_space, field)
147+
148+
def get_entries(self, label_space_or_index: Union[Dict[str, int], int]):
149+
"""Returns a list of fields from a complete or partial specification of a dictionary."""
150+
if isinstance(label_space_or_index, int):
151+
idx: int = label_space_or_index
152+
return [self.label_spaces[idx].field]
153+
else:
154+
_dict: Dict[str, int] = label_space_or_index
155+
are_keys_in_labels = [key in self.labels for key in _dict.keys()]
156+
if all(are_keys_in_labels):
157+
remaining = set(range(len(self.label_spaces)))
158+
for key in _dict.keys():
159+
val = _dict[key]
160+
to_remove = set()
161+
for idx in remaining:
162+
ls = self.label_spaces[idx]
163+
if key in ls.dict.keys():
164+
if ls.dict[key] != val:
165+
to_remove.add(idx)
166+
else:
167+
to_remove.add(idx)
168+
remaining = remaining.difference(to_remove)
169+
170+
idx_to_field = lambda idx: self.label_spaces[idx].field
171+
return list(map(idx_to_field, remaining))
172+
else:
173+
bad_idx = are_keys_in_labels.index(False)
174+
bad_key = list(_dict.keys())[bad_idx]
175+
raise KeyError(f"Key {bad_key} is not in labels: {self.labels}")
176+
177+
def get_entry(self, label_space_or_index: Union[Dict[str, int], int]):
178+
"""Returns the field or (first field found) corresponding to the given dictionary."""
179+
ret = self.get_entries(label_space_or_index)
180+
181+
if len(ret) != 0:
182+
return ret[0]
183+
184+
raise ValueError("Could not find corresponding entry")
185+
186+
def _new_id(self) -> int:
187+
"""Helper method generating a new id when calling add_entry(...)."""
188+
if len(self.ids) == 0:
189+
self.last_id = 1
190+
return self.last_id
191+
else:
192+
self.last_id += 1
193+
return self.last_id
194+
195+
# used by Dataframe
196+
def get_fields(self, label_space: Dict[str, int]) -> List[dpf.Field]:
197+
"""Returns the list of fields associated with given label space."""
198+
return self.get_entries(label_space)
199+
200+
def get_field(self, label_space_or_index: Union[Dict[str, int], int]) -> dpf.Field:
201+
"""Retrieves the field at a requested index or label space."""
202+
return self.get_entry(label_space_or_index)
203+
204+
# used by Dataframe
205+
def __getitem__(self, key: Union[Dict[str, int], int]) -> dpf.Field:
206+
"""Retrieve the field at a requested index."""
207+
return self.get_field(key)
208+
209+
def __len__(self) -> int:
210+
"""Retrieve the number of label spaces."""
211+
return len(self.label_spaces)
212+
213+
def _set_field(self, ls_idx, field):
214+
self.label_spaces[ls_idx].field = field
215+
216+
def rescope(self, scoping: dpf.Scoping): # Used by post.Dataframe
217+
"""Helper function to reproduce functionality of rescope_fc Operator."""
218+
copy_fc = _MockPropertyFieldsContainer(self, server=None)
219+
for idx, label_space in enumerate(copy_fc.label_spaces):
220+
pfield = PropertyField(location=label_space.field.location)
221+
pfield.data = np.ravel(
222+
[label_space._field.get_entity_data_by_id(id) for id in scoping.ids]
223+
)
224+
pfield.scoping.ids = scoping.ids
225+
copy_fc._set_field(idx, pfield)
226+
return copy_fc
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import pytest
2+
3+
from ansys.dpf.core.property_fields_container import _MockPropertyFieldsContainer, _LabelSpaceKV
4+
from ansys.dpf import core as dpf
5+
6+
7+
def test_property_fields_container(allkindofcomplexity, server_type):
8+
model = dpf.Model(allkindofcomplexity, server=server_type)
9+
fields_container = _MockPropertyFieldsContainer(server=server_type)
10+
fields_container.add_label(label="test")
11+
assert fields_container.has_label(label="test")
12+
assert fields_container.labels == ["test"]
13+
with pytest.raises(ValueError, match="labels already set"):
14+
fields_container.labels = ["test"]
15+
field = model.metadata.meshed_region.elements.connectivities_field
16+
fields_container.add_field(label_space={"test": 42}, field=field)
17+
assert len(fields_container.label_spaces) == 1
18+
label_space = fields_container.label_spaces[0]
19+
assert fields_container.get_label_space(0) == {"test": 42}
20+
assert isinstance(label_space, _LabelSpaceKV)
21+
assert label_space.field == field
22+
assert label_space.dict == {"test": 42}
23+
label_space.field = model.metadata.meshed_region.elements.element_types_field
24+
ref = """DPF PropertyFieldsContainer with 1 fields
25+
\t 0: Label Space: {'test': 42} with field
26+
\t\t\t""" # noqa
27+
assert ref in str(fields_container)
28+
with pytest.raises(KeyError, match="label test2 not found"):
29+
fields_container.get_label_scoping("test2")
30+
scoping = fields_container.get_label_scoping("test")
31+
assert isinstance(scoping, dpf.Scoping)
32+
assert scoping.ids == [1]
33+
assert scoping.location == ""
34+
35+
property_field = fields_container.get_entries(0)[0]
36+
assert isinstance(property_field, dpf.property_field.PropertyField)
37+
assert fields_container.get_entries({"test": 42})[0] == property_field
38+
with pytest.raises(KeyError, match="is not in labels:"):
39+
fields_container.get_entries(({"test2": 0}))
40+
assert fields_container.get_entry({"test": 42}) == property_field
41+
with pytest.raises(ValueError, match="Could not find corresponding entry"):
42+
fields_container.get_entry(({"test": 0}))
43+
assert fields_container[{"test": 42}] == property_field
44+
assert len(fields_container) == 1
45+
46+
assert fields_container.get_fields({"test": 42})[0] == property_field
47+
assert fields_container.get_field(0) == property_field

0 commit comments

Comments
 (0)