Skip to content

Commit efa81ba

Browse files
authored
Post unit tests. (#232)
1 parent 104f66b commit efa81ba

File tree

14 files changed

+611
-135
lines changed

14 files changed

+611
-135
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ jobs:
187187
- name: Install pyvistaqt requirements
188188
run: make install-pyvistaqt-requirements
189189

190+
- name: Install post requirements
191+
run: make install-post
192+
190193
- name: Unit Testing
191194
run: make unittest
192195

ansys/fluent/core/meta.py

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
from ansys.fluent.core.services.datamodel_tui import PyMenu
99

1010

11+
class LocalObjectDataExtractor:
12+
def __init__(self, obj):
13+
self.field_info = lambda: obj._get_top_most_parent().session.field_info
14+
self.field_data = lambda: obj._get_top_most_parent().session.field_data
15+
self.surface_api = (
16+
lambda: obj._get_top_most_parent().session.tui.solver.surface
17+
)
18+
self.id = lambda: obj._get_top_most_parent().session.id
19+
20+
1121
class Attribute:
1222
VALID_NAMES = ["range", "allowed_values"]
1323

@@ -87,7 +97,40 @@ def __new__(cls, name, bases, attrs):
8797
return super(PyMenuMeta, cls).__new__(cls, name, bases, attrs)
8898

8999

90-
class PyLocalPropertyMeta(type):
100+
class PyLocalBaseMeta(type):
101+
@classmethod
102+
def __create_get_parent_by_type(cls):
103+
def wrapper(self, obj_type, obj=None):
104+
obj = self if obj is None else obj
105+
parent = None
106+
if getattr(obj, "_parent", None):
107+
if isinstance(obj._parent, obj_type):
108+
return obj._parent
109+
parent = self._get_parent_by_type(obj_type, obj._parent)
110+
return parent
111+
112+
return wrapper
113+
114+
@classmethod
115+
def __create_get_top_most_parent(cls):
116+
def wrapper(self, obj=None):
117+
obj = self if obj is None else obj
118+
parent = obj
119+
if getattr(obj, "_parent", None):
120+
parent = self._get_top_most_parent(obj._parent)
121+
return parent
122+
123+
return wrapper
124+
125+
def __new__(cls, name, bases, attrs):
126+
attrs[
127+
"_get_parent_by_type"
128+
] = cls.__create_get_parent_by_type()
129+
attrs["_get_top_most_parent"] = cls.__create_get_top_most_parent()
130+
return super(PyLocalBaseMeta, cls).__new__(cls, name, bases, attrs)
131+
132+
133+
class PyLocalPropertyMeta(PyLocalBaseMeta):
91134
"""Metaclass for local property classes."""
92135

93136
@classmethod
@@ -126,14 +169,8 @@ def wrapper(self, value):
126169
@classmethod
127170
def __create_init(cls):
128171
def wrapper(self, parent):
129-
def get_top_most_parent(obj):
130-
parent = obj
131-
if getattr(obj, "parent", None):
132-
parent = get_top_most_parent(obj.parent)
133-
return parent
134-
135-
self.get_session = lambda: get_top_most_parent(self).session
136-
self.parent = parent
172+
self._data_extractor = LocalObjectDataExtractor(self)
173+
self._parent = parent
137174
self._on_change_cbs = []
138175
annotations = self.__class__.__dict__.get("__annotations__")
139176
if isinstance(getattr(self.__class__, "value", None), property):
@@ -194,17 +231,17 @@ def __new__(cls, name, bases, attrs):
194231
attrs["_validate"] = cls.__create_validate()
195232
attrs["_register_on_change_cb"] = cls.__create_register_on_change()
196233
attrs["set_state"] = cls.__create_set_state()
197-
attrs["parent"] = None
198234
return super(PyLocalPropertyMeta, cls).__new__(cls, name, bases, attrs)
199235

200236

201-
class PyLocalObjectMeta(type):
237+
class PyLocalObjectMeta(PyLocalBaseMeta):
202238
"""Metaclass for local object classes."""
203239

204240
@classmethod
205241
def __create_init(cls):
206242
def wrapper(self, parent):
207-
self.parent = parent
243+
self._parent = parent
244+
self._data_extractor = LocalObjectDataExtractor(self)
208245

209246
def update(clss):
210247
for name, cls in clss.__dict__.items():
@@ -260,7 +297,8 @@ def wrapper(self, value):
260297
obj.set_state(val)
261298
else:
262299
obj.update(val)
263-
wrapper.__doc__ = "Update method."
300+
301+
wrapper.__doc__ = "Update object."
264302
return wrapper
265303

266304
# graphics = ansys.fluent.postprocessing.pyvista.Graphics(session1)
@@ -330,7 +368,6 @@ def __new__(cls, name, bases, attrs):
330368
attrs["__setattr__"] = cls.__create_setattr()
331369
attrs["__repr__"] = cls.__create_repr()
332370
attrs["update"] = cls.__create_updateitem()
333-
attrs["parent"] = None
334371
return super(PyLocalObjectMeta, cls).__new__(cls, name, bases, attrs)
335372

336373

@@ -340,8 +377,9 @@ class PyLocalNamedObjectMeta(PyLocalObjectMeta):
340377
@classmethod
341378
def __create_init(cls):
342379
def wrapper(self, name, parent):
343-
self.__name = name
344-
self.parent = parent
380+
self._name = name
381+
self._data_extractor = LocalObjectDataExtractor(self)
382+
self._parent = parent
345383

346384
def update(clss):
347385
for name, cls in clss.__dict__.items():
@@ -382,7 +420,7 @@ class PyLocalContainer(MutableMapping):
382420
"""Local container for named objects."""
383421

384422
def __init__(self, parent, object_class):
385-
self.parent = parent
423+
self._parent = parent
386424
self.__object_class = object_class
387425
self.__collection: dict = {}
388426

ansys/fluent/core/services/field_data.py

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def get_fields(self, request):
4343
return self.__stub.GetFields(request, metadata=self.__metadata)
4444

4545

46-
class FieldData:
46+
class FieldInfo:
4747
"""
48-
Provide the field data.
48+
Provides access to Fluent field info.
4949
5050
Methods
5151
-------
@@ -62,39 +62,8 @@ class FieldData:
6262
get_surfaces_info(self) -> dict
6363
Get surfaces information i.e. surface name, id and type.
6464
65-
get_surfaces(surface_ids: List[int], overset_mesh: bool) -> Dict[int, Dict]
66-
Get surfaces data i.e. coordinates and connectivity.
67-
68-
get_scalar_field(
69-
surface_ids: List[int],
70-
scalar_field: str,
71-
node_value: Optional[bool] = True,
72-
boundary_value: Optional[bool] = False,
73-
) -> Dict[int, Dict]:
74-
Get scalar field data i.e. surface data and associated
75-
scalar field values.
76-
77-
get_vector_field(
78-
surface_ids: List[int],
79-
vector_field: Optional[str] = "velocity",
80-
scalar_field: Optional[str] = "",
81-
node_value: Optional[bool] = False,
82-
) -> Dict[int, Dict]:
83-
Get vector field data i.e. surface data and associated
84-
scalar and vector field values.
85-
8665
"""
8766

88-
# data mapping
89-
_proto_field_type_to_np_data_type = {
90-
FieldDataProtoModule.FieldType.INT_ARRAY: np.int32,
91-
FieldDataProtoModule.FieldType.LONG_ARRAY: np.int64,
92-
FieldDataProtoModule.FieldType.FLOAT_ARRAY: np.float32,
93-
FieldDataProtoModule.FieldType.DOUBLE_ARRAY: np.float64,
94-
}
95-
_chunk_size = 256 * 1024
96-
_bytes_stream = True
97-
9867
def __init__(self, service: FieldDataService):
9968
self.__service = service
10069

@@ -137,7 +106,7 @@ def get_vector_fields_info(self) -> dict:
137106
def get_surfaces_info(self) -> dict:
138107
request = FieldDataProtoModule.GetSurfacesInfoResponse()
139108
response = self.__service.get_surfaces_info(request)
140-
return {
109+
info = {
141110
surface_info.surfaceName: {
142111
"surface_id": [surf.id for surf in surface_info.surfaceId],
143112
"zone_id": surface_info.zoneId.id,
@@ -146,6 +115,50 @@ def get_surfaces_info(self) -> dict:
146115
}
147116
for surface_info in response.surfaceInfo
148117
}
118+
return info
119+
120+
121+
class FieldData:
122+
"""
123+
Provides access to Fluent field data on surfaces.
124+
125+
Methods
126+
-------
127+
get_surfaces(surface_ids: List[int], overset_mesh: bool) -> Dict[int, Dict]
128+
Get surfaces data i.e. coordinates and connectivity.
129+
130+
get_scalar_field(
131+
surface_ids: List[int],
132+
scalar_field: str,
133+
node_value: Optional[bool] = True,
134+
boundary_value: Optional[bool] = False,
135+
) -> Dict[int, Dict]:
136+
Get scalar field data i.e. surface data and associated
137+
scalar field values.
138+
139+
get_vector_field(
140+
surface_ids: List[int],
141+
vector_field: Optional[str] = "velocity",
142+
scalar_field: Optional[str] = "",
143+
node_value: Optional[bool] = False,
144+
) -> Dict[int, Dict]:
145+
Get vector field data i.e. surface data and associated
146+
scalar and vector field values.
147+
148+
"""
149+
150+
# data mapping
151+
_proto_field_type_to_np_data_type = {
152+
FieldDataProtoModule.FieldType.INT_ARRAY: np.int32,
153+
FieldDataProtoModule.FieldType.LONG_ARRAY: np.int64,
154+
FieldDataProtoModule.FieldType.FLOAT_ARRAY: np.float32,
155+
FieldDataProtoModule.FieldType.DOUBLE_ARRAY: np.float64,
156+
}
157+
_chunk_size = 256 * 1024
158+
_bytes_stream = True
159+
160+
def __init__(self, service: FieldDataService):
161+
self.__service = service
149162

150163
def _extract_fields(self, chunk_iterator):
151164
def _extract_field(field_datatype, field_size, chunk_iterator):

ansys/fluent/core/session.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,23 @@
1717
DatamodelService as DatamodelService_TUI,
1818
)
1919
from ansys.fluent.core.services.datamodel_tui import PyMenu as PyMenu_TUI
20-
from ansys.fluent.core.services.field_data import FieldData, FieldDataService
20+
from ansys.fluent.core.services.field_data import (
21+
FieldInfo,
22+
FieldData,
23+
FieldDataService,
24+
)
2125
from ansys.fluent.core.services.health_check import HealthCheckService
2226
from ansys.fluent.core.services.scheme_eval import (
23-
SchemeEval, SchemeEvalService
27+
SchemeEval,
28+
SchemeEvalService,
2429
)
2530
from ansys.fluent.core.services.settings import SettingsService
2631
from ansys.fluent.core.services.transcript import TranscriptService
2732
from ansys.fluent.core.solver.flobject import get_root as settings_get_root
2833
from ansys.fluent.core.services.events import EventsService
2934
from ansys.fluent.core.solver.events_manager import EventsManager
3035

36+
3137
def _parse_server_info_file(filename: str):
3238
with open(filename, encoding="utf-8") as f:
3339
lines = f.readlines()
@@ -144,8 +150,9 @@ def __init__(
144150
if not port:
145151
port = os.getenv("PYFLUENT_FLUENT_PORT")
146152
if not port:
147-
raise RuntimeError("The port to connect to Fluent "
148-
"session is not provided.")
153+
raise ValueError(
154+
"The port to connect to Fluent session is not provided."
155+
)
149156
self._channel = grpc.insecure_channel(f"{ip}:{port}")
150157
self._metadata: List[Tuple[str, str]] = []
151158
self._id = f"session-{next(Session._id_iter)}"
@@ -170,6 +177,7 @@ def __init__(
170177
self._field_data_service = FieldDataService(
171178
self._channel, self._metadata
172179
)
180+
self.field_info = FieldInfo(self._field_data_service)
173181
self.field_data = FieldData(self._field_data_service)
174182
self.tui = Session.Tui(self._datamodel_service_tui)
175183

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""Module providing dump session data functionality."""
2+
import pickle
3+
4+
5+
def dump_session_data(
6+
session, file_path: str, fields: list = [], surfaces: list = []
7+
):
8+
"""
9+
Dump session data.
10+
11+
Parameters
12+
----------
13+
session :
14+
Session object.
15+
file_path: str
16+
Session dump file path.
17+
fields: list, optional
18+
List of fields to write. If empty then all fields will be written.
19+
surfaces: list, optional
20+
List of surfaces to write. If empty then all surfaces will be written.
21+
"""
22+
session_data = {}
23+
session_data["scalar_fields_info"] = {
24+
k: v
25+
for k, v in session.field_info.get_fields_info().items()
26+
if (not fields or v["solver_name"] in fields)
27+
}
28+
session_data["surfaces_info"] = {
29+
k: v
30+
for k, v in session.field_info.get_surfaces_info().items()
31+
if (not surfaces or k in surfaces)
32+
}
33+
session_data[
34+
"vector_fields_info"
35+
] = session.field_info.get_vector_fields_info()
36+
if not fields:
37+
fields = [
38+
v["solver_name"]
39+
for k, v in session_data["scalar_fields_info"].items()
40+
]
41+
surfaces_id = [
42+
v["surface_id"][0] for k, v in session_data["surfaces_info"].items()
43+
]
44+
session_data["range"] = {}
45+
for field in fields:
46+
session_data["range"][field] = {}
47+
for surface in surfaces_id:
48+
session_data["range"][field][surface] = {}
49+
session_data["range"][field][surface][
50+
"node_value"
51+
] = session.field_info.get_range(field, True, [surface])
52+
session_data["range"][field][surface][
53+
"cell_value"
54+
] = session.field_info.get_range(field, False, [surface])
55+
56+
session_data["scalar-field"] = {}
57+
for field in fields:
58+
session_data["scalar-field"][field] = {}
59+
for surface in surfaces_id:
60+
session_data["scalar-field"][field][surface] = {}
61+
session_data["scalar-field"][field][surface][
62+
"node_value"
63+
] = session.field_data.get_scalar_field([surface], field, True)[
64+
surface
65+
]
66+
session_data["scalar-field"][field][surface][
67+
"cell_value"
68+
] = session.field_data.get_scalar_field([surface], field, False)[
69+
surface
70+
]
71+
72+
session_data["surfaces"] = {}
73+
for surface in surfaces_id:
74+
session_data["surfaces"][surface] = session.field_data.get_surfaces(
75+
[surface]
76+
)[surface]
77+
78+
session_data["vector-field"] = {}
79+
for surface in surfaces_id:
80+
session_data["vector-field"][
81+
surface
82+
] = session.field_data.get_vector_field([surface])[surface]
83+
84+
pickle_obj = open(file_path, "wb")
85+
pickle.dump(session_data, pickle_obj)
86+
pickle_obj.close()

ansys/fluent/core/utils/generic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def cb(*args, **kwargs):
4949

5050
def in_notebook():
5151
"""Function to check if application is running in notebook."""
52-
5352
try:
5453
from IPython import get_ipython
5554

0 commit comments

Comments
 (0)