imas-python 0.1.dev1618__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imas-python might be problematic. Click here for more details.
- imas/__init__.py +34 -0
- imas/__main__.py +17 -0
- imas/_util.py +260 -0
- imas/_version.py +16 -0
- imas/assets/IDSDef2MDSpreTree.xsl +161 -0
- imas/assets/IDS_fake_toplevel.xml +318 -0
- imas/assets/IDS_minimal.xml +37 -0
- imas/assets/IDS_minimal_2.xml +37 -0
- imas/assets/IDS_minimal_struct_array.xml +43 -0
- imas/assets/IDS_minimal_types.xml +169 -0
- imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
- imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
- imas/assets/README.md +5 -0
- imas/assets/core_profiles.ids +2438 -0
- imas/assets/equilibrium.ids +3172 -0
- imas/backends/__init__.py +12 -0
- imas/backends/db_entry_impl.py +122 -0
- imas/backends/imas_core/__init__.py +4 -0
- imas/backends/imas_core/al_context.py +391 -0
- imas/backends/imas_core/db_entry_al.py +405 -0
- imas/backends/imas_core/db_entry_helpers.py +200 -0
- imas/backends/imas_core/imas_interface.py +216 -0
- imas/backends/imas_core/mdsplus_model.py +367 -0
- imas/backends/imas_core/uda_support.py +56 -0
- imas/backends/netcdf/__init__.py +4 -0
- imas/backends/netcdf/db_entry_nc.py +176 -0
- imas/backends/netcdf/ids2nc.py +306 -0
- imas/backends/netcdf/nc2ids.py +302 -0
- imas/backends/netcdf/nc_metadata.py +357 -0
- imas/backends/netcdf/nc_validate.py +53 -0
- imas/command/cli.py +239 -0
- imas/command/db_analysis.py +290 -0
- imas/command/helpers.py +45 -0
- imas/command/timer.py +77 -0
- imas/db_entry.py +802 -0
- imas/dd_helpers.py +300 -0
- imas/dd_zip.py +297 -0
- imas/exception.py +107 -0
- imas/ids_base.py +149 -0
- imas/ids_convert.py +920 -0
- imas/ids_coordinates.py +400 -0
- imas/ids_data_type.py +115 -0
- imas/ids_defs.py +171 -0
- imas/ids_factory.py +108 -0
- imas/ids_identifiers.py +125 -0
- imas/ids_metadata.py +307 -0
- imas/ids_path.py +254 -0
- imas/ids_primitive.py +510 -0
- imas/ids_struct_array.py +233 -0
- imas/ids_structure.py +331 -0
- imas/ids_toplevel.py +381 -0
- imas/setup_logging.py +64 -0
- imas/test/test_all_dd_versions.py +28 -0
- imas/test/test_cli.py +44 -0
- imas/test/test_dbentry.py +104 -0
- imas/test/test_dd_helpers.py +134 -0
- imas/test/test_dd_zip.py +29 -0
- imas/test/test_deepcopy.py +25 -0
- imas/test/test_exception.py +12 -0
- imas/test/test_get_sample.py +388 -0
- imas/test/test_hash.py +188 -0
- imas/test/test_helpers.py +354 -0
- imas/test/test_identifiers.py +105 -0
- imas/test/test_ids2nc.py +166 -0
- imas/test/test_ids_ascii_data.py +39 -0
- imas/test/test_ids_convert.py +415 -0
- imas/test/test_ids_coordinates.py +165 -0
- imas/test/test_ids_data_type.py +40 -0
- imas/test/test_ids_factory.py +35 -0
- imas/test/test_ids_metadata.py +66 -0
- imas/test/test_ids_mixin.py +20 -0
- imas/test/test_ids_path.py +192 -0
- imas/test/test_ids_primitive.py +109 -0
- imas/test/test_ids_struct_array.py +89 -0
- imas/test/test_ids_structure.py +70 -0
- imas/test/test_ids_toplevel.py +55 -0
- imas/test/test_ids_validate.py +343 -0
- imas/test/test_latest_dd_autofill.py +74 -0
- imas/test/test_latest_dd_manual.py +38 -0
- imas/test/test_latest_dd_resample.py +102 -0
- imas/test/test_lazy_loading.py +201 -0
- imas/test/test_list_occurrences.py +82 -0
- imas/test/test_minimal.py +37 -0
- imas/test/test_minimal_conversion.py +44 -0
- imas/test/test_minimal_io.py +21 -0
- imas/test/test_minimal_struct_array_io.py +60 -0
- imas/test/test_minimal_types.py +436 -0
- imas/test/test_minimal_types_io.py +70 -0
- imas/test/test_minimal_types_io_automatic.py +66 -0
- imas/test/test_nbc_change.py +408 -0
- imas/test/test_nc_autofill.py +13 -0
- imas/test/test_nc_entry.py +28 -0
- imas/test/test_nc_metadata.py +153 -0
- imas/test/test_nc_validation.py +197 -0
- imas/test/test_snippets.py +25 -0
- imas/test/test_static_ids.py +39 -0
- imas/test/test_str_1d.py +99 -0
- imas/test/test_time_slicing.py +182 -0
- imas/test/test_uda_support.py +12 -0
- imas/test/test_util.py +209 -0
- imas/training.py +45 -0
- imas/util.py +526 -0
- imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
- imas_python-0.1.dev1618.dist-info/METADATA +288 -0
- imas_python-0.1.dev1618.dist-info/RECORD +108 -0
- imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
- imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
- imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
imas/ids_struct_array.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
"""IDS StructArray represents an Array of Structures in the IDS tree.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from xxhash import xxh3_64
|
|
11
|
+
|
|
12
|
+
from imas.backends.imas_core.al_context import LazyALArrayStructContext
|
|
13
|
+
from imas.ids_base import IDSBase, IDSDoc
|
|
14
|
+
from imas.ids_coordinates import IDSCoordinates
|
|
15
|
+
from imas.ids_identifiers import IDSIdentifier
|
|
16
|
+
from imas.ids_metadata import IDSMetadata
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class IDSStructArray(IDSBase):
|
|
22
|
+
"""IDS array of structures (AoS) node
|
|
23
|
+
|
|
24
|
+
Represents a node in the IDS tree. Does not itself contain data,
|
|
25
|
+
but contains references to IDSStructures
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__doc__ = IDSDoc(__doc__)
|
|
29
|
+
__slots__ = ["_parent", "_lazy", "metadata", "value", "_lazy_ctx"]
|
|
30
|
+
|
|
31
|
+
def __init__(self, parent: IDSBase, metadata: IDSMetadata):
|
|
32
|
+
"""Initialize IDSStructArray from XML specification
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
parent: Parent structure. Can be anything, but at database write
|
|
36
|
+
time should be something with a path attribute
|
|
37
|
+
metadata: IDSMetadata describing the structure of the IDS
|
|
38
|
+
"""
|
|
39
|
+
self._parent = parent
|
|
40
|
+
self._lazy = parent._lazy
|
|
41
|
+
self.metadata = metadata
|
|
42
|
+
|
|
43
|
+
# Initialize with an 0-length list or None when lazy loading
|
|
44
|
+
self.value = None if self._lazy else []
|
|
45
|
+
""""""
|
|
46
|
+
|
|
47
|
+
# Lazy loading context, only applicable when self._lazy is True
|
|
48
|
+
# When lazy loading, all items in self.value are None until they are requested
|
|
49
|
+
self._lazy_ctx: Optional[LazyALArrayStructContext] = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def coordinates(self):
|
|
53
|
+
"""Coordinates of this array of structures."""
|
|
54
|
+
return IDSCoordinates(self)
|
|
55
|
+
|
|
56
|
+
def __deepcopy__(self, memo):
|
|
57
|
+
copy = self.__class__(self._parent, self.metadata)
|
|
58
|
+
for value in self.value:
|
|
59
|
+
value_copy = deepcopy(value, memo)
|
|
60
|
+
value_copy._parent = copy
|
|
61
|
+
copy.value.append(value_copy)
|
|
62
|
+
return copy
|
|
63
|
+
|
|
64
|
+
def __eq__(self, other) -> bool:
|
|
65
|
+
if self is other:
|
|
66
|
+
return True
|
|
67
|
+
if not isinstance(other, IDSStructArray):
|
|
68
|
+
return False
|
|
69
|
+
# Equal if same size and all contained structures are the same
|
|
70
|
+
return len(self) == len(other) and all(a == b for a, b in zip(self, other))
|
|
71
|
+
|
|
72
|
+
def _set_lazy_context(self, ctx: LazyALArrayStructContext) -> None:
|
|
73
|
+
"""Called by DBEntry during a lazy get/get_slice.
|
|
74
|
+
|
|
75
|
+
Set the context that we can use for retrieving our size and children.
|
|
76
|
+
"""
|
|
77
|
+
self._lazy_ctx = ctx
|
|
78
|
+
|
|
79
|
+
def _load(self, item: Optional[int]) -> None:
|
|
80
|
+
"""When lazy loading, ensure that the requested item is loaded.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
item: index of the item to load. When None, just ensure that our size is
|
|
84
|
+
loaded from the lowlevel.
|
|
85
|
+
"""
|
|
86
|
+
assert self._lazy
|
|
87
|
+
if self.value is not None: # We already loaded our size
|
|
88
|
+
if item is None:
|
|
89
|
+
return
|
|
90
|
+
if self.value[item] is not None:
|
|
91
|
+
return # item is already loaded
|
|
92
|
+
# Load requested data from the backend
|
|
93
|
+
if self.value is None:
|
|
94
|
+
if self._lazy_ctx is None:
|
|
95
|
+
# Lazy context can be None when:
|
|
96
|
+
# 1. The element does not exist in the on-disk DD version
|
|
97
|
+
# 2. The element exists, but changed type compared to the on-disk DD
|
|
98
|
+
# In both cases we just report that we're empty
|
|
99
|
+
self.value = []
|
|
100
|
+
else:
|
|
101
|
+
ctx = self._lazy_ctx.get_context()
|
|
102
|
+
self.value = [None] * ctx.size
|
|
103
|
+
|
|
104
|
+
if item is not None:
|
|
105
|
+
if item < 0:
|
|
106
|
+
item += len(self)
|
|
107
|
+
if item < 0 or item >= len(self):
|
|
108
|
+
raise IndexError("list index out of range")
|
|
109
|
+
# Create the requested item
|
|
110
|
+
from imas.ids_structure import IDSStructure
|
|
111
|
+
|
|
112
|
+
element = self.value[item] = IDSStructure(self, self.metadata)
|
|
113
|
+
element._set_lazy_context(self._lazy_ctx.iterate_to_index(item))
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def _element_structure(self):
|
|
117
|
+
"""Prepare an element structure JIT"""
|
|
118
|
+
from imas.ids_structure import IDSStructure
|
|
119
|
+
|
|
120
|
+
struct = IDSStructure(self, self.metadata)
|
|
121
|
+
return struct
|
|
122
|
+
|
|
123
|
+
def __getitem__(self, item):
|
|
124
|
+
# value is a list, so the given item should be convertable to integer
|
|
125
|
+
# TODO: perhaps we should allow slices as well?
|
|
126
|
+
list_idx = int(item)
|
|
127
|
+
if self._lazy:
|
|
128
|
+
self._load(item)
|
|
129
|
+
return self.value[list_idx]
|
|
130
|
+
|
|
131
|
+
def __setitem__(self, item, value):
|
|
132
|
+
# value is a list, so the given item should be convertable to integer
|
|
133
|
+
# TODO: perhaps we should allow slices as well?
|
|
134
|
+
if self._lazy:
|
|
135
|
+
raise ValueError("Lazy-loaded IDSs are read-only.")
|
|
136
|
+
list_idx = int(item)
|
|
137
|
+
if isinstance(value, (IDSIdentifier, str, int)):
|
|
138
|
+
self.value[list_idx]._assign_identifier(value)
|
|
139
|
+
else: # FIXME: check if value is of the correct class
|
|
140
|
+
self.value[list_idx] = value
|
|
141
|
+
|
|
142
|
+
def __len__(self) -> int:
|
|
143
|
+
if self._lazy:
|
|
144
|
+
self._load(None)
|
|
145
|
+
return len(self.value)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def shape(self) -> Tuple[int]:
|
|
149
|
+
"""Get the shape of the contained data.
|
|
150
|
+
|
|
151
|
+
This will always return a tuple: ``(len(self), )``.
|
|
152
|
+
"""
|
|
153
|
+
if self._lazy:
|
|
154
|
+
self._load(None)
|
|
155
|
+
return (len(self.value),)
|
|
156
|
+
|
|
157
|
+
def append(self, elt):
|
|
158
|
+
"""Append elements to the end of the array of structures.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
elt: IDS structure, or list of IDS structures, to append to this array
|
|
162
|
+
"""
|
|
163
|
+
if self._lazy:
|
|
164
|
+
raise ValueError("Lazy-loaded IDSs are read-only.")
|
|
165
|
+
if not isinstance(elt, list):
|
|
166
|
+
elements = [elt]
|
|
167
|
+
else:
|
|
168
|
+
elements = elt
|
|
169
|
+
for e in elements:
|
|
170
|
+
# Just blindly append for now
|
|
171
|
+
# TODO: Maybe check if user is not trying to append weird elements
|
|
172
|
+
e._parent = self
|
|
173
|
+
self.value.append(e)
|
|
174
|
+
|
|
175
|
+
def __repr__(self):
|
|
176
|
+
return f"{self._build_repr_start()} with {len(self)} items)>"
|
|
177
|
+
|
|
178
|
+
def resize(self, nbelt: int, keep: bool = False):
|
|
179
|
+
"""Resize an array of structures.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
nbelt: The number of elements for the targeted array of structure,
|
|
183
|
+
which can be smaller or bigger than the size of the current
|
|
184
|
+
array if it already exists.
|
|
185
|
+
keep: Specifies if the targeted array of structure should keep
|
|
186
|
+
existing data in remaining elements after resizing it.
|
|
187
|
+
"""
|
|
188
|
+
if self._lazy:
|
|
189
|
+
raise ValueError("Lazy-loaded IDSs are read-only.")
|
|
190
|
+
if nbelt < 0:
|
|
191
|
+
raise ValueError(f"Invalid size {nbelt}: size may not be negative")
|
|
192
|
+
if not keep:
|
|
193
|
+
self.value = []
|
|
194
|
+
cur = len(self.value)
|
|
195
|
+
if nbelt > cur:
|
|
196
|
+
# Create new structures to fill this AoS with
|
|
197
|
+
from imas.ids_structure import IDSStructure
|
|
198
|
+
|
|
199
|
+
new_els = [IDSStructure(self, self.metadata) for _ in range(nbelt - cur)]
|
|
200
|
+
if cur:
|
|
201
|
+
self.value.extend(new_els)
|
|
202
|
+
else:
|
|
203
|
+
self.value = new_els
|
|
204
|
+
elif nbelt < cur:
|
|
205
|
+
self.value = self.value[:nbelt]
|
|
206
|
+
else: # nbelt == cur
|
|
207
|
+
pass # nothing to do, already correct size
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def has_value(self) -> bool:
|
|
211
|
+
"""True if this struct-array has nonzero size"""
|
|
212
|
+
# Note self.__len__ will lazy load our size if needed
|
|
213
|
+
return len(self) > 0
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def size(self) -> int:
|
|
217
|
+
"""Get the number of elements in this array"""
|
|
218
|
+
return len(self)
|
|
219
|
+
|
|
220
|
+
def _validate(self) -> None:
|
|
221
|
+
# Common validation logic
|
|
222
|
+
super()._validate()
|
|
223
|
+
# IDSStructArray specific: validate coordinates and child nodes
|
|
224
|
+
if self.has_value:
|
|
225
|
+
self.coordinates._validate()
|
|
226
|
+
for child in self:
|
|
227
|
+
child._validate()
|
|
228
|
+
|
|
229
|
+
def _xxhash(self) -> bytes:
|
|
230
|
+
hsh = xxh3_64(len(self).to_bytes(8, "little"))
|
|
231
|
+
for s in self:
|
|
232
|
+
hsh.update(s._xxhash())
|
|
233
|
+
return hsh.digest()
|
imas/ids_structure.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
"""A structure in an IDS
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import Generator, List, Optional, Union
|
|
10
|
+
|
|
11
|
+
from xxhash import xxh3_64
|
|
12
|
+
|
|
13
|
+
from imas.backends.imas_core.al_context import LazyALContext
|
|
14
|
+
from imas.ids_base import IDSBase, IDSDoc
|
|
15
|
+
from imas.ids_identifiers import IDSIdentifier
|
|
16
|
+
from imas.ids_metadata import IDSDataType, IDSMetadata
|
|
17
|
+
from imas.ids_path import IDSPath
|
|
18
|
+
from imas.ids_primitive import IDSPrimitive
|
|
19
|
+
from imas.ids_struct_array import IDSStructArray
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IDSStructure(IDSBase):
|
|
25
|
+
"""IDS structure node
|
|
26
|
+
|
|
27
|
+
Represents a node in the IDS tree. Does not itself contain data,
|
|
28
|
+
but contains references to leaf nodes with data (IDSPrimitive) or
|
|
29
|
+
other node-like structures, for example other IDSStructures or
|
|
30
|
+
IDSStructArrays
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
__doc__ = IDSDoc(__doc__)
|
|
34
|
+
_children: "MappingProxyType[str, IDSMetadata]"
|
|
35
|
+
_lazy_context: Optional[LazyALContext]
|
|
36
|
+
|
|
37
|
+
def __init__(self, parent: IDSBase, metadata: IDSMetadata):
|
|
38
|
+
"""Initialize IDSStructure from metadata specification
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
parent: Parent structure. Can be anything, but at database write
|
|
42
|
+
time should be something with a path attribute
|
|
43
|
+
metadata: IDSMetadata describing the structure of the IDS
|
|
44
|
+
"""
|
|
45
|
+
# Performance hack: bypass our __setattr__ implementation during __init__:
|
|
46
|
+
dct = self.__dict__
|
|
47
|
+
dct["_parent"] = parent
|
|
48
|
+
# parent._lazy is undefined for IDSToplevel, but then _lazy is already set
|
|
49
|
+
if "_lazy" not in dct:
|
|
50
|
+
dct["_lazy"] = parent._lazy
|
|
51
|
+
dct["metadata"] = metadata
|
|
52
|
+
|
|
53
|
+
dct["_children"] = metadata._children
|
|
54
|
+
dct["_lazy_context"] = None
|
|
55
|
+
|
|
56
|
+
def __getattr__(self, name):
|
|
57
|
+
if name not in self._children:
|
|
58
|
+
raise AttributeError(
|
|
59
|
+
f"IDS structure '{self._path}' has no attribute '{name}'"
|
|
60
|
+
)
|
|
61
|
+
# Create child node
|
|
62
|
+
child_meta = self._children[name]
|
|
63
|
+
child = child_meta._node_type(self, child_meta)
|
|
64
|
+
self.__dict__[name] = child # bypass setattr logic below: avoid recursion
|
|
65
|
+
if self._lazy: # lazy load the child
|
|
66
|
+
from imas.backends.imas_core.db_entry_helpers import _get_child
|
|
67
|
+
|
|
68
|
+
_get_child(child, self._lazy_context)
|
|
69
|
+
return child
|
|
70
|
+
|
|
71
|
+
def _assign_identifier(self, value: Union[IDSIdentifier, str, int]) -> None:
|
|
72
|
+
identifier_enum = self.metadata.identifier_enum
|
|
73
|
+
if identifier_enum is None:
|
|
74
|
+
raise TypeError(
|
|
75
|
+
f"Cannot assign {value!r} to {self!r}. This structure is not an "
|
|
76
|
+
"identifier, or the Data Dictionary does not define the "
|
|
77
|
+
"identifier options."
|
|
78
|
+
)
|
|
79
|
+
if isinstance(value, IDSIdentifier):
|
|
80
|
+
if not isinstance(value, identifier_enum):
|
|
81
|
+
raise TypeError(
|
|
82
|
+
f"Assigning incorrect identifier. {value} is a "
|
|
83
|
+
f"{type(value).__name__}, was expecting a "
|
|
84
|
+
f"{identifier_enum.__name__}"
|
|
85
|
+
)
|
|
86
|
+
elif isinstance(value, int):
|
|
87
|
+
value = identifier_enum(value)
|
|
88
|
+
else: # value is a string
|
|
89
|
+
try:
|
|
90
|
+
value = identifier_enum[value]
|
|
91
|
+
except KeyError:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"{value!r} is not a valid {identifier_enum.__name__}"
|
|
94
|
+
) from None
|
|
95
|
+
self.name = value.name
|
|
96
|
+
self.index = value.index
|
|
97
|
+
self.description = value.description
|
|
98
|
+
|
|
99
|
+
def __setattr__(self, key, value):
|
|
100
|
+
"""
|
|
101
|
+
'Smart' setting of attributes. To be able to warn the user on imas
|
|
102
|
+
IDS interaction time, instead of on database put time
|
|
103
|
+
Only try to cast user-facing attributes, as core developers might
|
|
104
|
+
want to always bypass this mechanism (I know I do!)
|
|
105
|
+
"""
|
|
106
|
+
# Skip logic for any value that is not a child IDS node
|
|
107
|
+
if key.startswith("_"):
|
|
108
|
+
return super().__setattr__(key, value)
|
|
109
|
+
|
|
110
|
+
# This will raise an attribute error when there is no child named 'key', fine?
|
|
111
|
+
attr = getattr(self, key)
|
|
112
|
+
|
|
113
|
+
if isinstance(attr, IDSStructure):
|
|
114
|
+
if isinstance(value, (IDSIdentifier, str, int)):
|
|
115
|
+
return attr._assign_identifier(value)
|
|
116
|
+
|
|
117
|
+
if not isinstance(value, IDSStructure):
|
|
118
|
+
raise TypeError(
|
|
119
|
+
f"Trying to set structure field {key} with non-structure."
|
|
120
|
+
)
|
|
121
|
+
if value.metadata.path != attr.metadata.path:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Trying to set structure field {attr.metadata.path} "
|
|
124
|
+
f"with a non-matching structure {value.metadata.path}."
|
|
125
|
+
)
|
|
126
|
+
super().__setattr__(key, value)
|
|
127
|
+
value._parent = self
|
|
128
|
+
|
|
129
|
+
elif isinstance(attr, IDSStructArray):
|
|
130
|
+
if not isinstance(value, IDSStructArray):
|
|
131
|
+
raise TypeError(
|
|
132
|
+
f"Trying to set struct array field {key} with non-struct-array."
|
|
133
|
+
)
|
|
134
|
+
if value.metadata.path != attr.metadata.path:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"Trying to set struct array field {attr.metadata.path} "
|
|
137
|
+
f"with a non-matching struct array {value.metadata.path}."
|
|
138
|
+
)
|
|
139
|
+
super().__setattr__(key, value)
|
|
140
|
+
value._parent = self
|
|
141
|
+
|
|
142
|
+
else:
|
|
143
|
+
attr.value = value
|
|
144
|
+
|
|
145
|
+
def __deepcopy__(self, memo):
|
|
146
|
+
if self._lazy:
|
|
147
|
+
raise NotImplementedError(
|
|
148
|
+
"deepcopy is not implemented for lazy-loaded IDSs."
|
|
149
|
+
)
|
|
150
|
+
copy = self.__class__(self._parent, self.metadata)
|
|
151
|
+
for child in self._children:
|
|
152
|
+
if child in self.__dict__:
|
|
153
|
+
child_copy = deepcopy(getattr(self, child), memo)
|
|
154
|
+
setattr(copy, child, child_copy)
|
|
155
|
+
return copy
|
|
156
|
+
|
|
157
|
+
def __dir__(self) -> List[str]:
|
|
158
|
+
return sorted(set(object.__dir__(self)).union(self._children))
|
|
159
|
+
|
|
160
|
+
def __eq__(self, other) -> bool:
|
|
161
|
+
if self is other:
|
|
162
|
+
return True
|
|
163
|
+
if not isinstance(other, IDSStructure):
|
|
164
|
+
return False if isinstance(other, IDSBase) else NotImplemented
|
|
165
|
+
from imas.util import idsdiffgen # local import to avoid circular import
|
|
166
|
+
|
|
167
|
+
for _ in idsdiffgen(self, other):
|
|
168
|
+
return False # Not equal if there is any difference
|
|
169
|
+
return True # Equal when there are no differences
|
|
170
|
+
|
|
171
|
+
def _set_lazy_context(self, ctx: LazyALContext) -> None:
|
|
172
|
+
"""Called by DBEntry during a lazy get/get_slice.
|
|
173
|
+
|
|
174
|
+
Set the context that we can use for retrieving our children.
|
|
175
|
+
"""
|
|
176
|
+
self._lazy_context = ctx
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def _dd_parent(self) -> IDSBase:
|
|
180
|
+
if self.metadata.data_type is IDSDataType.STRUCT_ARRAY:
|
|
181
|
+
return self._parent._parent
|
|
182
|
+
return self._parent
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def has_value(self) -> bool:
|
|
186
|
+
"""True if any of the children has a non-default value"""
|
|
187
|
+
if self._lazy:
|
|
188
|
+
raise NotImplementedError(
|
|
189
|
+
"`has_value` is not implemented for lazy-loaded structures."
|
|
190
|
+
)
|
|
191
|
+
for _ in self.iter_nonempty_():
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def iter_nonempty_(self, *, accept_lazy=False) -> Generator[IDSBase, None, None]:
|
|
196
|
+
"""Iterate over all child nodes with non-default value.
|
|
197
|
+
|
|
198
|
+
Note:
|
|
199
|
+
The name ends with an underscore so it won't clash with IDS child names.
|
|
200
|
+
|
|
201
|
+
Caution:
|
|
202
|
+
This method works differently for lazy-loaded IDSs.
|
|
203
|
+
|
|
204
|
+
By default, an error is raised when calling this method for lazy loaded
|
|
205
|
+
IDSs. You may set the keyword argument ``accept_lazy`` to ``True`` to
|
|
206
|
+
iterate over all `loaded` child nodes with a non-default value. See below
|
|
207
|
+
example for lazy-loaded IDSs.
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
|
|
211
|
+
.. code-block:: python
|
|
212
|
+
:caption: ``iter_nonempty_`` for fully loaded IDSs
|
|
213
|
+
|
|
214
|
+
>>> import imas.training
|
|
215
|
+
>>> entry = imas.training.get_training_db_entry()
|
|
216
|
+
>>> cp = entry.get("core_profiles")
|
|
217
|
+
>>> list(cp.iter_nonempty_())
|
|
218
|
+
[
|
|
219
|
+
<IDSStructure (IDS:core_profiles, ids_properties)>,
|
|
220
|
+
<IDSStructArray (IDS:core_profiles, profiles_1d with 3 items)>,
|
|
221
|
+
<IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
|
|
222
|
+
numpy.ndarray([ 3.98722186, 432.93759781, 792. ])
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
.. code-block:: python
|
|
226
|
+
:caption: ``iter_nonempty_`` for lazy-loaded IDSs
|
|
227
|
+
|
|
228
|
+
>>> import imas.training
|
|
229
|
+
>>> entry = imas.training.get_training_db_entry()
|
|
230
|
+
>>> cp = entry.get("core_profiles", lazy=True)
|
|
231
|
+
>>> list(cp.iter_nonempty_())
|
|
232
|
+
RuntimeError: Iterating over non-empty nodes of a lazy loaded IDS will
|
|
233
|
+
skip nodes that are not loaded. Set accept_lazy=True to continue.
|
|
234
|
+
See the documentation for more information: [...]
|
|
235
|
+
>>> list(cp.iter_nonempty_(accept_lazy=True))
|
|
236
|
+
[]
|
|
237
|
+
>>> # An empty list because nothing is loaded. Load `time`:
|
|
238
|
+
>>> cp.time
|
|
239
|
+
<IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
|
|
240
|
+
numpy.ndarray([ 3.98722186, 432.93759781, 792. ])
|
|
241
|
+
>>> list(cp.iter_nonempty_(accept_lazy=True))
|
|
242
|
+
[<IDSNumericArray (IDS:core_profiles, time, FLT_1D)>
|
|
243
|
+
numpy.ndarray([ 3.98722186, 432.93759781, 792. ])]
|
|
244
|
+
|
|
245
|
+
Keyword Args:
|
|
246
|
+
accept_lazy: Accept that this method will only yield loaded nodes of a
|
|
247
|
+
lazy-loaded IDS. Non-empty nodes that have not been loaded from the
|
|
248
|
+
backend are not iterated over. See detailed explanation above.
|
|
249
|
+
"""
|
|
250
|
+
if self._lazy and not accept_lazy:
|
|
251
|
+
raise RuntimeError(
|
|
252
|
+
"Iterating over non-empty nodes of a lazy loaded IDS will skip nodes "
|
|
253
|
+
"that are not loaded. Set accept_lazy=True to continue. "
|
|
254
|
+
"See the documentation for more information: "
|
|
255
|
+
"https://sharepoint.iter.org/departments/POP/CM/IMDesign/"
|
|
256
|
+
"Code%20Documentation/imas-python-doc/generated/imas.ids_structure."
|
|
257
|
+
"IDSStructure.html#imas.ids_structure.IDSStructure.iter_nonempty_"
|
|
258
|
+
)
|
|
259
|
+
for child in self._children:
|
|
260
|
+
if child in self.__dict__:
|
|
261
|
+
child_node = getattr(self, child)
|
|
262
|
+
if ( # IDSStructure.has_value is not implemented when lazy-loaded:
|
|
263
|
+
self._lazy and isinstance(child_node, IDSStructure)
|
|
264
|
+
) or child_node.has_value:
|
|
265
|
+
yield child_node
|
|
266
|
+
|
|
267
|
+
def __iter__(self):
|
|
268
|
+
"""Iterate over this structure's children"""
|
|
269
|
+
return iter(map(self.__getitem__, self._children))
|
|
270
|
+
|
|
271
|
+
def __str__(self):
|
|
272
|
+
return '%s("%s")' % (type(self).__name__, self.metadata.name)
|
|
273
|
+
|
|
274
|
+
def __getitem__(self, key):
|
|
275
|
+
keyname = str(key)
|
|
276
|
+
if keyname in self._children:
|
|
277
|
+
return getattr(self, keyname)
|
|
278
|
+
|
|
279
|
+
path = IDSPath(keyname)
|
|
280
|
+
if len(path) == 1 and path.indices[0] is None:
|
|
281
|
+
raise AttributeError(f"'{self!r}' has no attribute '{keyname}'")
|
|
282
|
+
|
|
283
|
+
return path.goto(self, from_root=False)
|
|
284
|
+
|
|
285
|
+
def __repr__(self):
|
|
286
|
+
return f"{self._build_repr_start()})>"
|
|
287
|
+
|
|
288
|
+
def __setitem__(self, key, value):
|
|
289
|
+
keyname = str(key)
|
|
290
|
+
if keyname in self._children:
|
|
291
|
+
return self.__setattr__(keyname, value)
|
|
292
|
+
|
|
293
|
+
path = IDSPath(keyname)
|
|
294
|
+
if len(path) == 1 and path.indices[0] is None:
|
|
295
|
+
raise AttributeError(f"'{self!r}' has no attribute '{keyname}'")
|
|
296
|
+
|
|
297
|
+
attr = path.goto(self, from_root=False)
|
|
298
|
+
if isinstance(attr, IDSPrimitive):
|
|
299
|
+
attr.value = value
|
|
300
|
+
else:
|
|
301
|
+
# Setting an IDSStructArray or IDSStructure: delegate to the
|
|
302
|
+
# relevant __setitem__ of its parent
|
|
303
|
+
parent = attr._parent
|
|
304
|
+
parent[path.parts[-1]] = value
|
|
305
|
+
|
|
306
|
+
def _validate(self) -> None:
|
|
307
|
+
# Common validation logic
|
|
308
|
+
super()._validate()
|
|
309
|
+
# IDSStructure specific: validate child nodes
|
|
310
|
+
# accept_lazy=True: users are warned in IDSToplevel.validate()
|
|
311
|
+
for child in self.iter_nonempty_(accept_lazy=True):
|
|
312
|
+
child._validate()
|
|
313
|
+
|
|
314
|
+
def _xxhash(self) -> bytes:
|
|
315
|
+
hsh = xxh3_64()
|
|
316
|
+
children = sorted(self._children)
|
|
317
|
+
|
|
318
|
+
# Skip ids_properties.version_put
|
|
319
|
+
if self.metadata.name == "ids_properties":
|
|
320
|
+
if "version_put" in children: # Some old DDs don't have version_put defined
|
|
321
|
+
children.remove("version_put")
|
|
322
|
+
|
|
323
|
+
for childname in children:
|
|
324
|
+
child = self[childname]
|
|
325
|
+
if not child.has_value:
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
hsh.update(childname.encode("UTF-8"))
|
|
329
|
+
hsh.update(child._xxhash())
|
|
330
|
+
|
|
331
|
+
return hsh.digest()
|