Skip to content

Commit 687b2bc

Browse files
committed
Improve heterogeneous time handling
- Add tests in `test_time_slicing.py` for heterogeneous time - Add a tool for comparing lowlevel function calls between IMASPy and Python HLI - Be more consistent with Python HLI - Improve timebasepath handling - Fix integer data type in IDSPrimitive (IMAS uses 32-bits integers) TODO: - cleanup - nbc_change support for heterogeneous time - investigate failing test for the memory backend
1 parent 4ce83d0 commit 687b2bc

File tree

7 files changed

+283
-31
lines changed

7 files changed

+283
-31
lines changed

imaspy/ids_metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def __init__(self, structure_xml: Element) -> None:
3838
self.maxoccur = self.parse_maxoccur(attrib.get("maxoccur", "unbounded"))
3939
self.data_type, self.ndim = IDSDataType.parse(attrib.get("data_type", None))
4040
self.path = IDSPath(attrib.get("path", "")) # IDSToplevel has no path
41+
self.type = attrib.get("type" , None) # TODO: parse into an enum?
42+
"""Type of data: "static", "constant", "dynamic" or None"""
43+
self.timebasepath = attrib.get("timebasepath", "")
4144

4245
# Parse coordinates
4346
coors = [IDSCoordinate("")] * self.ndim

imaspy/ids_mixin.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from imaspy.al_exception import ALException
1717
from imaspy.context_store import context_store
18+
from imaspy.ids_data_type import IDSDataType
1819
from imaspy.ids_metadata import IDSMetadata
1920
from imaspy.setup_logging import root_logger as logger
2021

@@ -58,6 +59,22 @@ def _dd_parent(self) -> "IDSMixin":
5859
"""
5960
return self._parent
6061

62+
@cached_property
63+
def _is_dynamic(self) -> bool:
64+
"""True iff this element has type=dynamic, or it has a parent with type=dynamic
65+
"""
66+
return self.metadata.type == "dynamic" or self._dd_parent._is_dynamic
67+
68+
@cached_property
69+
def _aos_path(self) -> str:
70+
"""Path string relative to the nearest ancestor Array of Structure
71+
"""
72+
# FIXME: logic should be based on backend xml!
73+
if self._dd_parent.metadata.data_type in (None, IDSDataType.STRUCT_ARRAY):
74+
# data_type is None for IDS toplevel
75+
return self.metadata.name
76+
return self._dd_parent._aos_path + "/" + self.metadata.name
77+
6178
def getRelCTXPath(self, ctx: int) -> str:
6279
"""Get the path relative to given context from an absolute path"""
6380
return context_store.strip_context(self._path, ctx)

imaspy/ids_primitive.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
DOUBLE_DATA,
2424
INTEGER_DATA,
2525
COMPLEX_DATA,
26+
IDS_TIME_MODE_HETEROGENEOUS,
27+
IDS_TIME_MODE_HOMOGENEOUS,
2628
hli_utils,
2729
needs_imas,
2830
)
@@ -91,6 +93,19 @@ def _default(self):
9193
return default_value
9294
return np.full((0,) * self.metadata.ndim, default_value)
9395

96+
@property
97+
def _timebase_path(self) -> str:
98+
"""Timebase path to supply to the backend."""
99+
# Follow logic from
100+
# https://git.iter.org/projects/IMAS/repos/access-layer/browse/pythoninterface/py_ids.xsl?at=refs%2Ftags%2F4.11.4#1524-1566
101+
if not self._is_dynamic:
102+
return ""
103+
if self._time_mode == IDS_TIME_MODE_HOMOGENEOUS:
104+
return "/time"
105+
if self._time_mode == IDS_TIME_MODE_HETEROGENEOUS:
106+
# FIXME: this should be based on backend metadata!
107+
return self.metadata.timebasepath
108+
94109
def __iter__(self):
95110
return iter([])
96111

@@ -151,7 +166,7 @@ def cast_value(self, value):
151166
elif self.metadata.data_type is IDSDataType.CPX:
152167
value = np.array(value, dtype=np.complex128)
153168
elif self.metadata.data_type is IDSDataType.INT:
154-
value = np.array(value, dtype=np.int64)
169+
value = np.array(value, dtype=np.int32)
155170
elif self.metadata.data_type is IDSDataType.STR:
156171
# make sure that all the strings are decoded
157172
if isinstance(value, np.ndarray):
@@ -236,23 +251,18 @@ def put(self, ctx, homogeneousTime, **kwargs):
236251
if self.data_is_default(data, self._default):
237252
return
238253

239-
# Call signature
240-
# ual_write_data(ctx, pyFieldPath, pyTimebasePath, inputData, dataType=0, dim = 0, sizeArray = np.empty([0], dtype=np.int32))
241-
# data_type = self._ull._getDataType(data)
254+
# Call signature (at least since AL4.0.0, there are additional kwargs, which are
255+
# ignored)
256+
# ual_write_data(ctx, pyFieldPath, pyTimebasePath, inputData)
242257

243258
# Strip context from absolute path
244259
rel_path = self.getRelCTXPath(ctx)
245-
# TODO: Check ignore_nbc_change
246-
strTimeBasePath = self.getTimeBasePath(homogeneousTime)
247260

248261
if logger.level <= logging.DEBUG:
249262
log_string = " " * self.depth + " - % -38s write"
250263
logger.debug(log_string, "/".join([context_store[ctx], rel_path]))
251264

252-
# TODO: the data_type argument seems to be unused in the ual_write_data routine, remove it?
253-
status = self._ull.ual_write_data(
254-
ctx, rel_path, strTimeBasePath, data, dataType=write_type, dim=ndims
255-
)
265+
status = self._ull.ual_write_data(ctx, rel_path, self._timebase_path, data)
256266
if status != 0:
257267
raise ALException('Error writing field "{!s}"'.format(self.metadata.name))
258268

@@ -266,7 +276,7 @@ def get(self, ctx, homogeneousTime):
266276
"""
267277
# Strip context from absolute path
268278
strNodePath = self.getRelCTXPath(ctx)
269-
strTimeBasePath = self.getTimeBasePath(homogeneousTime)
279+
strTimeBasePath = self._timebase_path
270280
read_type = self._backend_type or self.metadata.data_type.value
271281
ndims = self._backend_ndims or self.metadata.ndim
272282
# we are not really ready to deal with a change in ndims

imaspy/ids_struct_array.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
from imaspy.al_exception import ALException
1212
from imaspy.context_store import context_store
1313
from imaspy.ids_coordinates import IDSCoordinates
14-
from imaspy.ids_defs import needs_imas
14+
from imaspy.ids_defs import (
15+
needs_imas,
16+
IDS_TIME_MODE_HETEROGENEOUS,
17+
IDS_TIME_MODE_HOMOGENEOUS,
18+
)
1519
from imaspy.ids_mixin import IDSMixin
1620
from imaspy.ids_structure import IDSStructure
1721
from imaspy.setup_logging import root_logger as logger
@@ -25,20 +29,24 @@ class IDSStructArray(IDSStructure, IDSMixin):
2529
but contains references to IDSStructures
2630
"""
2731

32+
# TODO: HLI compatibility
2833
def getAOSPath(self, ignore_nbc_change=1):
2934
raise NotImplementedError("{!s}.getAOSPath(ignore_nbc_change=1)".format(self))
3035

36+
# TODO: HLI compatibility
3137
@staticmethod
3238
def getAoSElement(self):
3339
logger.warning(
3440
"getAoSElement is deprecated, you should never need this", FutureWarning
3541
)
3642
return self._element_structure
3743

44+
# TODO: IMASPy-specific but not implemented. Remove?
3845
@staticmethod
3946
def getBackendInfo(parentCtx, index, homogeneousTime): # Is this specific?
4047
raise NotImplementedError("getBackendInfo(parentCtx, index, homogeneousTime)")
4148

49+
# TODO: HLI compatibility `base_path_in`
4250
def __init__(
4351
self, parent: IDSMixin, structure_xml: Element, base_path_in="element"
4452
):
@@ -80,19 +88,34 @@ def _element_structure(self):
8088
def _dd_parent(self) -> IDSMixin:
8189
return self._parent
8290

91+
@property
92+
def _timebase_path(self) -> str:
93+
"""Timebase path to supply to the backend.
94+
"""
95+
# Follow logic from
96+
# https://git.iter.org/projects/IMAS/repos/access-layer/browse/pythoninterface/py_ids.xsl?at=refs%2Ftags%2F4.11.4#367-384
97+
if self.metadata.type != "dynamic":
98+
return ""
99+
if self._time_mode == IDS_TIME_MODE_HOMOGENEOUS:
100+
return "/time"
101+
if self._time_mode == IDS_TIME_MODE_HETEROGENEOUS:
102+
return self._aos_path + "/time"
103+
83104
def __setattr__(self, key, value):
84105
object.__setattr__(self, key, value)
85106

86107
def __getattr__(self, key):
87-
object.__getattribute__(self, key)
108+
return object.__getattribute__(self, key)
88109

89110
def __getitem__(self, item):
90111
# value is a list, so the given item should be convertable to integer
112+
# TODO: perhaps we should allow slices as well?
91113
list_idx = int(item)
92114
return self.value[list_idx]
93115

94116
def __setitem__(self, item, value):
95117
# value is a list, so the given item should be convertable to integer
118+
# TODO: perhaps we should allow slices as well?
96119
list_idx = int(item)
97120
if hasattr(self, "_convert_ids_types") and self._convert_ids_types:
98121
# Convert IDS type on set time. Never try this for hidden attributes!
@@ -184,10 +207,9 @@ def get(self, parentCtx, homogeneousTime):
184207
185208
Tries to dynamically build all needed information for the UAL.
186209
"""
187-
timeBasePath = self.getTimeBasePath(homogeneousTime, 0)
188210
nodePath = self.getRelCTXPath(parentCtx)
189211
status, aosCtx, size = self._ull.ual_begin_arraystruct_action(
190-
parentCtx, nodePath, timeBasePath, 0
212+
parentCtx, nodePath, self._timebase_path, 0
191213
)
192214
if status < 0:
193215
raise ALException(
@@ -219,11 +241,12 @@ def put(self, parentCtx, homogeneousTime, **kwargs):
219241
220242
As all children _should_ support being put, just call `put` blindly.
221243
"""
222-
timeBasePath = self.getTimeBasePath(homogeneousTime)
244+
if len(self.value) == 0:
245+
return # Nothing to be done
223246
# TODO: This might be to simple for array of array of structures
224247
nodePath = self.getRelCTXPath(parentCtx)
225248
status, aosCtx, size = self._ull.ual_begin_arraystruct_action(
226-
parentCtx, nodePath, timeBasePath, len(self.value)
249+
parentCtx, nodePath, self._timebase_path, len(self.value)
227250
)
228251
if status != 0 or aosCtx < 0:
229252
raise ALException(
@@ -259,6 +282,7 @@ def put(self, parentCtx, homogeneousTime, **kwargs):
259282
status,
260283
)
261284

285+
# TODO: IMASPy internal, make this a private function?
262286
def set_backend_properties(self, structure_xml):
263287
"""set the (structure) backend properties of each child
264288
and store the structure_xml for new children"""

imaspy/ids_toplevel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ def _time_mode(self) -> int:
130130
"""Retrieve the time mode from `/ids_properties/homogeneous_time`"""
131131
return self.ids_properties.homogeneous_time
132132

133+
@property
134+
def _is_dynamic(self) -> bool:
135+
return False
136+
133137
def set_backend_properties(self, structure_xml):
134138
"""Set backend properties for this IDSToplevel and provide some logging"""
135139
# TODO: better naming (structure_xml -> backend etc)

0 commit comments

Comments
 (0)