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_primitive.py
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
"""Provides the classes for IDS data nodes
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
import math
|
|
7
|
+
import operator
|
|
8
|
+
import struct
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from numbers import Complex, Integral, Number, Real
|
|
11
|
+
from typing import Tuple
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from xxhash import xxh3_64, xxh3_64_digest
|
|
15
|
+
|
|
16
|
+
from imas.ids_base import IDSBase, IDSDoc
|
|
17
|
+
from imas.ids_coordinates import IDSCoordinates
|
|
18
|
+
from imas.ids_data_type import IDSDataType
|
|
19
|
+
from imas.ids_metadata import IDSMetadata
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _binary_wrapper(op, name):
|
|
25
|
+
def func(self, other):
|
|
26
|
+
if isinstance(other, IDSPrimitive):
|
|
27
|
+
other = other.value
|
|
28
|
+
return op(self.value, other)
|
|
29
|
+
|
|
30
|
+
func.__name__ = f"__{name}__"
|
|
31
|
+
return func
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _reflected_binary_wrapper(op, name):
|
|
35
|
+
def func(self, other):
|
|
36
|
+
if isinstance(other, IDSPrimitive):
|
|
37
|
+
other = other.value
|
|
38
|
+
return op(other, self.value)
|
|
39
|
+
|
|
40
|
+
func.__name__ = f"__r{name}__"
|
|
41
|
+
return func
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _numeric_wrapper(op, name):
|
|
45
|
+
return (_binary_wrapper(op, name), _reflected_binary_wrapper(op, name))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _unary_wrapper(op, name):
|
|
49
|
+
def func(self):
|
|
50
|
+
return op(self.value)
|
|
51
|
+
|
|
52
|
+
func.__name__ = f"__{name}__"
|
|
53
|
+
return func
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class IDSPrimitive(IDSBase):
|
|
57
|
+
"""IDS leaf node
|
|
58
|
+
|
|
59
|
+
Represents actual data. Examples are (arrays of) strings, floats, integers.
|
|
60
|
+
Lives entirely in-memory until 'put' into a database.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
__doc__ = IDSDoc(__doc__)
|
|
64
|
+
__slots__ = ["_parent", "metadata", "__value"]
|
|
65
|
+
|
|
66
|
+
def __init__(self, parent: IDSBase, metadata: IDSMetadata):
|
|
67
|
+
"""Initialize IDSPrimitive
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
parent: Parent node of this leaf
|
|
71
|
+
metadata: IDSMetadata that describes this IDSPrimitive
|
|
72
|
+
"""
|
|
73
|
+
self._parent = parent
|
|
74
|
+
self.metadata = metadata
|
|
75
|
+
|
|
76
|
+
self.__value = None
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def _lazy(self):
|
|
80
|
+
"""Whether this IDS Node is part of a lazy-loaded IDSToplevel"""
|
|
81
|
+
return self._parent._lazy
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def coordinates(self):
|
|
85
|
+
"""Coordinates belonging to this quantity."""
|
|
86
|
+
return IDSCoordinates(self)
|
|
87
|
+
|
|
88
|
+
def __deepcopy__(self, memo):
|
|
89
|
+
# note: if parent needs updating it is handled by the deepcopy of our parent
|
|
90
|
+
# TODO: implement the statement on the previous line O_O
|
|
91
|
+
copy = self.__class__(self._parent, self.metadata)
|
|
92
|
+
copy.__value = deepcopy(self.__value, memo)
|
|
93
|
+
return copy
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def shape(self) -> Tuple[int, ...]:
|
|
97
|
+
"""Get the shape of the contained data.
|
|
98
|
+
|
|
99
|
+
For 0D data types, the shape is always an empty tuple.
|
|
100
|
+
See also :external:py:func:`numpy.shape`.
|
|
101
|
+
"""
|
|
102
|
+
if self.metadata.ndim == 0:
|
|
103
|
+
return tuple()
|
|
104
|
+
if self.__value is None:
|
|
105
|
+
return (0,) * self.metadata.ndim
|
|
106
|
+
return np.shape(self.__value)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def size(self) -> int:
|
|
110
|
+
"""Get the size of stored data (number of elements stored).
|
|
111
|
+
|
|
112
|
+
For 0D data types, the size is always 1 (even when set to the default).
|
|
113
|
+
For 1+D data types, the size is the number of elements stored, see
|
|
114
|
+
:external:py:attr:`numpy.ndarray.size`.
|
|
115
|
+
"""
|
|
116
|
+
if self.metadata.ndim == 0:
|
|
117
|
+
return 1
|
|
118
|
+
if self.__value is None:
|
|
119
|
+
return 0
|
|
120
|
+
if self.metadata.data_type == IDSDataType.STR:
|
|
121
|
+
return len(self.__value)
|
|
122
|
+
# self.__value must be a numpy array
|
|
123
|
+
return self.__value.size
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def has_value(self) -> bool:
|
|
127
|
+
"""True if a value is defined here that is not the default"""
|
|
128
|
+
if self.__value is None: # No value set
|
|
129
|
+
return False
|
|
130
|
+
return self.size > 0 # Default for ndarray and STR_1D types is size == 0
|
|
131
|
+
|
|
132
|
+
def __len__(self) -> int:
|
|
133
|
+
if self.metadata.ndim == 0:
|
|
134
|
+
raise TypeError(f"IDS data node of type {self.data_type} has no len()")
|
|
135
|
+
if self.__value is None:
|
|
136
|
+
return 0
|
|
137
|
+
return len(self.__value)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def _default(self):
|
|
141
|
+
data_type = self.metadata.data_type
|
|
142
|
+
if self.metadata.ndim == 0:
|
|
143
|
+
return data_type.default
|
|
144
|
+
if data_type is IDSDataType.STR:
|
|
145
|
+
return []
|
|
146
|
+
return np.empty((0,) * self.metadata.ndim, dtype=data_type.numpy_dtype)
|
|
147
|
+
|
|
148
|
+
def __iter__(self):
|
|
149
|
+
return iter(self.value)
|
|
150
|
+
|
|
151
|
+
def __repr__(self):
|
|
152
|
+
empty = value_repr = ""
|
|
153
|
+
if self.has_value:
|
|
154
|
+
value_repr = f"\n{self.value.__class__.__qualname__}({self.value!r})"
|
|
155
|
+
else:
|
|
156
|
+
empty = "empty "
|
|
157
|
+
return f"{self._build_repr_start()}, {empty}{self.data_type})>{value_repr}"
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def value(self):
|
|
161
|
+
"""Return the value of this IDSPrimitive if it is set,
|
|
162
|
+
otherwise return the default"""
|
|
163
|
+
if self.__value is None:
|
|
164
|
+
if self.metadata.ndim == 0:
|
|
165
|
+
return self._default
|
|
166
|
+
# 1+D data types can be modified in-place, first set before returning so,
|
|
167
|
+
# for example, `ids.time.value.resize(10)`` always works as expected.
|
|
168
|
+
self.__value = self._default
|
|
169
|
+
return self.__value
|
|
170
|
+
|
|
171
|
+
@value.setter
|
|
172
|
+
def value(self, setter_value):
|
|
173
|
+
# NOTE: This setter is bypassed during a get/get_slice, and self.__value is set
|
|
174
|
+
# directly.
|
|
175
|
+
if self._lazy:
|
|
176
|
+
raise ValueError("Lazy-loaded IDSs are read-only.")
|
|
177
|
+
if isinstance(setter_value, type(self)):
|
|
178
|
+
# No need to cast, just overwrite contained value
|
|
179
|
+
if (
|
|
180
|
+
setter_value.metadata.data_type is self.metadata.data_type
|
|
181
|
+
and setter_value.metadata.ndim == self.metadata.ndim
|
|
182
|
+
):
|
|
183
|
+
self.__value = setter_value.value
|
|
184
|
+
# Can we cast the internal value to a valid value?
|
|
185
|
+
else:
|
|
186
|
+
self.__value = self._cast_value(setter_value.value)
|
|
187
|
+
else:
|
|
188
|
+
self.__value = self._cast_value(setter_value)
|
|
189
|
+
if self.metadata.ndim == 0 and self.__value == self.metadata.data_type.default:
|
|
190
|
+
# Unset 0D types when setting them to their magic default value
|
|
191
|
+
self.__value = None
|
|
192
|
+
|
|
193
|
+
# Implement special __dunder__ methods that just defer to the underlying value
|
|
194
|
+
__eq__ = _binary_wrapper(operator.eq, "eq")
|
|
195
|
+
__ne__ = _binary_wrapper(operator.ne, "ne")
|
|
196
|
+
__lt__ = _binary_wrapper(operator.lt, "lt")
|
|
197
|
+
__le__ = _binary_wrapper(operator.le, "le")
|
|
198
|
+
__gt__ = _binary_wrapper(operator.gt, "gt")
|
|
199
|
+
__ge__ = _binary_wrapper(operator.ge, "ge")
|
|
200
|
+
__contains__ = _binary_wrapper(operator.contains, "contains")
|
|
201
|
+
# Numeric methods
|
|
202
|
+
__add__, __radd__ = _numeric_wrapper(operator.add, "add")
|
|
203
|
+
__sub__, __rsub__ = _numeric_wrapper(operator.sub, "sub")
|
|
204
|
+
__mul__, __rmul__ = _numeric_wrapper(operator.mul, "mul")
|
|
205
|
+
__matmul__, __rmatmul__ = _numeric_wrapper(operator.matmul, "matmul")
|
|
206
|
+
__truediv__, __rtruediv__ = _numeric_wrapper(operator.truediv, "truediv")
|
|
207
|
+
__floordiv__, __rfloordiv__ = _numeric_wrapper(operator.floordiv, "floordiv")
|
|
208
|
+
__mod__, __rmod__ = _numeric_wrapper(operator.mod, "mod")
|
|
209
|
+
__divmod__, __rdivmod__ = _numeric_wrapper(divmod, "divmod")
|
|
210
|
+
# TODO: handle the optional third argument for __pow__?
|
|
211
|
+
__pow__, __rpow__ = _numeric_wrapper(operator.pow, "pow")
|
|
212
|
+
__lshift__, __rlshift__ = _numeric_wrapper(operator.lshift, "lshift")
|
|
213
|
+
__rshift__, __rrshift__ = _numeric_wrapper(operator.rshift, "rshift")
|
|
214
|
+
__and__, __rand__ = _numeric_wrapper(operator.and_, "and")
|
|
215
|
+
__xor__, __rxor__ = _numeric_wrapper(operator.xor, "xor")
|
|
216
|
+
__or__, __ror__ = _numeric_wrapper(operator.or_, "or")
|
|
217
|
+
# unary methods
|
|
218
|
+
__neg__ = _unary_wrapper(operator.neg, "neg")
|
|
219
|
+
__pos__ = _unary_wrapper(operator.pos, "pos")
|
|
220
|
+
__abs__ = _unary_wrapper(operator.abs, "abs")
|
|
221
|
+
__invert__ = _unary_wrapper(operator.invert, "invert")
|
|
222
|
+
|
|
223
|
+
def __getitem__(self, index):
|
|
224
|
+
return self.value[index]
|
|
225
|
+
|
|
226
|
+
def __setitem__(self, index, value):
|
|
227
|
+
self.value[index] = value
|
|
228
|
+
|
|
229
|
+
def __getattr__(self, name):
|
|
230
|
+
# Forward this getattr call to our actual value
|
|
231
|
+
if not name.startswith("_"):
|
|
232
|
+
return getattr(self.value, name)
|
|
233
|
+
raise AttributeError(f"{self.__class__} object has no attribute {name}")
|
|
234
|
+
|
|
235
|
+
def _cast_value(self, value):
|
|
236
|
+
"""Cast a value to the correct type.
|
|
237
|
+
|
|
238
|
+
Must be implemented by subclasses.
|
|
239
|
+
"""
|
|
240
|
+
raise NotImplementedError()
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def data_type(self):
|
|
244
|
+
"""Combine imas ids_type and ndims to AL data_type"""
|
|
245
|
+
return "{!s}_{!s}D".format(self.metadata.data_type.value, self.metadata.ndim)
|
|
246
|
+
|
|
247
|
+
def _validate(self) -> None:
|
|
248
|
+
# Common validation logic
|
|
249
|
+
super()._validate()
|
|
250
|
+
# Validate coordinates
|
|
251
|
+
if self.metadata.ndim > 0 and self.has_value:
|
|
252
|
+
self.coordinates._validate()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
_CONVERT_MSG = "Assigning incorrect type '%s' to %r, attempting automatic conversion."
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _cast_str(node, value):
|
|
259
|
+
"""Cast a value to a string.
|
|
260
|
+
|
|
261
|
+
If value is a bytes object, decode it using UTF-8. Otherwise return str(value).
|
|
262
|
+
"""
|
|
263
|
+
if not isinstance(value, str):
|
|
264
|
+
logger.info(_CONVERT_MSG, type(value), node)
|
|
265
|
+
if isinstance(value, bytes):
|
|
266
|
+
return value.decode("UTF-8")
|
|
267
|
+
return str(value)
|
|
268
|
+
return value
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class IDSString0D(IDSPrimitive):
|
|
272
|
+
"""IDSPrimitive specialization for STR_0D."""
|
|
273
|
+
|
|
274
|
+
__doc__ = IDSDoc(__doc__)
|
|
275
|
+
__slots__ = ()
|
|
276
|
+
|
|
277
|
+
_cast_value = _cast_str
|
|
278
|
+
|
|
279
|
+
def __str__(self):
|
|
280
|
+
return self.value
|
|
281
|
+
|
|
282
|
+
def __len__(self): # override as it is the only 0D quantity with a length
|
|
283
|
+
return len(self.value)
|
|
284
|
+
|
|
285
|
+
def _xxhash(self) -> bytes:
|
|
286
|
+
return xxh3_64_digest(self.value.encode("UTF-8"))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class IDSString1D(IDSPrimitive):
|
|
290
|
+
"""IDSPrimitive specialization for STR_1D."""
|
|
291
|
+
|
|
292
|
+
__doc__ = IDSDoc(__doc__)
|
|
293
|
+
__slots__ = ()
|
|
294
|
+
|
|
295
|
+
def __getattr__(self, name):
|
|
296
|
+
# Forward this getattr call to our actual value
|
|
297
|
+
return getattr(self.value, name)
|
|
298
|
+
|
|
299
|
+
def append(self, value):
|
|
300
|
+
"""Append a string to this list."""
|
|
301
|
+
self.value.append(_cast_str(self, value))
|
|
302
|
+
|
|
303
|
+
def extend(self, value):
|
|
304
|
+
"""Extend this list with an iterable of strings."""
|
|
305
|
+
self.value.extend(_cast_str(self, val) for val in value)
|
|
306
|
+
|
|
307
|
+
def __setitem__(self, index, value):
|
|
308
|
+
if isinstance(index, slice):
|
|
309
|
+
value = (_cast_str(self, val) for val in value)
|
|
310
|
+
else:
|
|
311
|
+
value = _cast_str(self, value)
|
|
312
|
+
self.value[index] = value
|
|
313
|
+
|
|
314
|
+
def _cast_value(self, value):
|
|
315
|
+
if not isinstance(value, (str, bytes)):
|
|
316
|
+
try: # Try to iterate over value
|
|
317
|
+
return list(_cast_str(self, val) for val in value)
|
|
318
|
+
except TypeError:
|
|
319
|
+
pass # Just convert value to a string and store it as the only item
|
|
320
|
+
if isinstance(value, str):
|
|
321
|
+
logger.info(_CONVERT_MSG, type(value), self)
|
|
322
|
+
return [_cast_str(self, value)]
|
|
323
|
+
|
|
324
|
+
def _xxhash(self) -> bytes:
|
|
325
|
+
hsh = xxh3_64(len(self).to_bytes(8, "little"))
|
|
326
|
+
for s in self:
|
|
327
|
+
hsh.update(xxh3_64_digest(s.encode("UTF-8")))
|
|
328
|
+
return hsh.digest()
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class IDSNumeric0D(IDSPrimitive):
|
|
332
|
+
"""Abstract base class for INT_0D, FLT_0D and CPX_0D."""
|
|
333
|
+
|
|
334
|
+
__doc__ = IDSDoc(__doc__)
|
|
335
|
+
__slots__ = ()
|
|
336
|
+
|
|
337
|
+
def __array__(self, dtype=None):
|
|
338
|
+
return np.array(self.value, dtype=dtype)
|
|
339
|
+
|
|
340
|
+
def __str__(self):
|
|
341
|
+
return str(self.value)
|
|
342
|
+
|
|
343
|
+
def _cast_value(self, value):
|
|
344
|
+
if isinstance(value, np.ndarray) and value.ndim == 0:
|
|
345
|
+
value = value.item() # Unpack 0D numpy arrays
|
|
346
|
+
cast_value = self.metadata.data_type.python_type(value)
|
|
347
|
+
# nan != nan, so we need the second check to not complain when assigning a nan
|
|
348
|
+
if cast_value != value and not (np.isnan(cast_value) and np.isnan(value)):
|
|
349
|
+
logger.info(_CONVERT_MSG, type(value), self)
|
|
350
|
+
return cast_value
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class IDSComplex0D(IDSNumeric0D, Complex):
|
|
354
|
+
"""IDSPrimitive specialization for CPX_0D."""
|
|
355
|
+
|
|
356
|
+
__doc__ = IDSDoc(__doc__)
|
|
357
|
+
__slots__ = ()
|
|
358
|
+
|
|
359
|
+
def __complex__(self) -> complex:
|
|
360
|
+
return self.value
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def real(self) -> float:
|
|
364
|
+
return self.value.real
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def imag(self) -> float:
|
|
368
|
+
return self.value.imag
|
|
369
|
+
|
|
370
|
+
def conjugate(self) -> complex:
|
|
371
|
+
return self.value.conjugate()
|
|
372
|
+
|
|
373
|
+
def _xxhash(self) -> bytes:
|
|
374
|
+
return xxh3_64_digest(struct.pack("<dd", self.real, self.imag))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class IDSFloat0D(IDSNumeric0D, Real):
|
|
378
|
+
"""IDSPrimitive specialization for FLT_0D."""
|
|
379
|
+
|
|
380
|
+
__doc__ = IDSDoc(__doc__)
|
|
381
|
+
__slots__ = ()
|
|
382
|
+
|
|
383
|
+
def __float__(self) -> float:
|
|
384
|
+
return self.value
|
|
385
|
+
|
|
386
|
+
def __int__(self) -> int:
|
|
387
|
+
return int(self.value)
|
|
388
|
+
|
|
389
|
+
def __trunc__(self) -> int:
|
|
390
|
+
return int(self.value)
|
|
391
|
+
|
|
392
|
+
def __floor__(self) -> int:
|
|
393
|
+
return math.floor(self.value)
|
|
394
|
+
|
|
395
|
+
def __ceil__(self) -> int:
|
|
396
|
+
return math.ceil(self.value)
|
|
397
|
+
|
|
398
|
+
def __round__(self, ndigits=None):
|
|
399
|
+
return round(self.value, ndigits)
|
|
400
|
+
|
|
401
|
+
def _xxhash(self) -> bytes:
|
|
402
|
+
return xxh3_64_digest(struct.pack("<d", self.value))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class IDSInt0D(IDSNumeric0D, Integral):
|
|
406
|
+
"""IDSPrimitive specialization for INT_0D."""
|
|
407
|
+
|
|
408
|
+
__doc__ = IDSDoc(__doc__)
|
|
409
|
+
__slots__ = ()
|
|
410
|
+
|
|
411
|
+
def __int__(self):
|
|
412
|
+
return self.value
|
|
413
|
+
|
|
414
|
+
def __trunc__(self) -> int:
|
|
415
|
+
return int(self.value)
|
|
416
|
+
|
|
417
|
+
def __ceil__(self) -> int:
|
|
418
|
+
return self.value
|
|
419
|
+
|
|
420
|
+
def __floor__(self) -> int:
|
|
421
|
+
return self.value
|
|
422
|
+
|
|
423
|
+
def __round__(self, ndigits=None):
|
|
424
|
+
return round(self.value, ndigits)
|
|
425
|
+
|
|
426
|
+
def _xxhash(self) -> bytes:
|
|
427
|
+
return xxh3_64_digest(self.value.to_bytes(4, "little", signed=True))
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class IDSNumericArray(IDSPrimitive, np.lib.mixins.NDArrayOperatorsMixin):
|
|
431
|
+
"""IDSPrimitive specialization for ND numeric types (wrapping ``numpy.ndarray``)."""
|
|
432
|
+
|
|
433
|
+
__doc__ = IDSDoc(__doc__)
|
|
434
|
+
__slots__ = ()
|
|
435
|
+
|
|
436
|
+
# One might also consider adding the built-in list type to this
|
|
437
|
+
# list, to support operations like np.add(array_like, list)
|
|
438
|
+
_HANDLED_TYPES = (np.ndarray, Number)
|
|
439
|
+
|
|
440
|
+
def __array__(self, dtype=None):
|
|
441
|
+
return self.value.astype(dtype, copy=False)
|
|
442
|
+
|
|
443
|
+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
|
444
|
+
out = kwargs.get("out", ())
|
|
445
|
+
for x in inputs + out:
|
|
446
|
+
# Only support operations with instances of _HANDLED_TYPES.
|
|
447
|
+
# Use ArrayLike instead of type(self) for isinstance to
|
|
448
|
+
# allow subclasses that don't override __array_ufunc__ to
|
|
449
|
+
# handle ArrayLike objects.
|
|
450
|
+
if not isinstance(x, self._HANDLED_TYPES + (IDSPrimitive,)):
|
|
451
|
+
return NotImplemented
|
|
452
|
+
|
|
453
|
+
# Defer to the implementation of the ufunc on unwrapped values.
|
|
454
|
+
inputs = tuple(x.value if isinstance(x, IDSPrimitive) else x for x in inputs)
|
|
455
|
+
if out:
|
|
456
|
+
kwargs["out"] = tuple(
|
|
457
|
+
x.value if isinstance(x, IDSPrimitive) else x for x in out
|
|
458
|
+
)
|
|
459
|
+
result = getattr(ufunc, method)(*inputs, **kwargs)
|
|
460
|
+
|
|
461
|
+
if method == "at":
|
|
462
|
+
# no return value
|
|
463
|
+
return None
|
|
464
|
+
else:
|
|
465
|
+
# one return value
|
|
466
|
+
return result
|
|
467
|
+
|
|
468
|
+
def __repr__(self):
|
|
469
|
+
# Specify that this is a numpy array, instead of a Python array As we
|
|
470
|
+
# know .value is a numpy array, we can remove "array" part of the
|
|
471
|
+
# numpy-native repr
|
|
472
|
+
empty = value_repr = ""
|
|
473
|
+
if self.has_value:
|
|
474
|
+
value_repr = f"\n{_fullname(self.value)}{repr(self.value)[5:]}"
|
|
475
|
+
else:
|
|
476
|
+
empty = "empty "
|
|
477
|
+
return f"{self._build_repr_start()}, {empty}{self.data_type})>{value_repr}"
|
|
478
|
+
|
|
479
|
+
def _cast_value(self, value):
|
|
480
|
+
dtype = self.metadata.data_type.numpy_dtype
|
|
481
|
+
value = np.asanyarray(value)
|
|
482
|
+
if value.dtype != dtype:
|
|
483
|
+
logger.info(_CONVERT_MSG, value.dtype, self)
|
|
484
|
+
value = np.array(value, dtype=dtype, copy=False)
|
|
485
|
+
if value.ndim != self.metadata.ndim:
|
|
486
|
+
raise ValueError(f"Trying to assign a {value.ndim}D value to {self!r}.")
|
|
487
|
+
return value
|
|
488
|
+
|
|
489
|
+
def _xxhash(self) -> bytes:
|
|
490
|
+
arr = self.value
|
|
491
|
+
hsh = xxh3_64(arr.ndim.to_bytes(1, "little"))
|
|
492
|
+
hsh.update(struct.pack("<" + arr.ndim * "q", *arr.shape))
|
|
493
|
+
# Ensure array is little endian, only create a copy if it is big-endian
|
|
494
|
+
arr = arr.astype(arr.dtype.newbyteorder("little"), copy=False)
|
|
495
|
+
hsh.update(arr.tobytes(order="F"))
|
|
496
|
+
return hsh.digest()
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _fullname(o) -> str:
|
|
500
|
+
"""Get the full name to a type, including module name etc.
|
|
501
|
+
|
|
502
|
+
Examples:
|
|
503
|
+
- _fullname(np.array([1,2,3]) -> numpy.ndarray
|
|
504
|
+
- fullname([1,2,3]) -> list
|
|
505
|
+
"""
|
|
506
|
+
class_ = o.__class__
|
|
507
|
+
module = class_.__module__
|
|
508
|
+
if module == "builtins":
|
|
509
|
+
return class_.__qualname__ # avoid outputs like 'builtins.str'
|
|
510
|
+
return module + "." + class_.__qualname__
|