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.

Files changed (108) hide show
  1. imas/__init__.py +34 -0
  2. imas/__main__.py +17 -0
  3. imas/_util.py +260 -0
  4. imas/_version.py +16 -0
  5. imas/assets/IDSDef2MDSpreTree.xsl +161 -0
  6. imas/assets/IDS_fake_toplevel.xml +318 -0
  7. imas/assets/IDS_minimal.xml +37 -0
  8. imas/assets/IDS_minimal_2.xml +37 -0
  9. imas/assets/IDS_minimal_struct_array.xml +43 -0
  10. imas/assets/IDS_minimal_types.xml +169 -0
  11. imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
  12. imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
  13. imas/assets/README.md +5 -0
  14. imas/assets/core_profiles.ids +2438 -0
  15. imas/assets/equilibrium.ids +3172 -0
  16. imas/backends/__init__.py +12 -0
  17. imas/backends/db_entry_impl.py +122 -0
  18. imas/backends/imas_core/__init__.py +4 -0
  19. imas/backends/imas_core/al_context.py +391 -0
  20. imas/backends/imas_core/db_entry_al.py +405 -0
  21. imas/backends/imas_core/db_entry_helpers.py +200 -0
  22. imas/backends/imas_core/imas_interface.py +216 -0
  23. imas/backends/imas_core/mdsplus_model.py +367 -0
  24. imas/backends/imas_core/uda_support.py +56 -0
  25. imas/backends/netcdf/__init__.py +4 -0
  26. imas/backends/netcdf/db_entry_nc.py +176 -0
  27. imas/backends/netcdf/ids2nc.py +306 -0
  28. imas/backends/netcdf/nc2ids.py +302 -0
  29. imas/backends/netcdf/nc_metadata.py +357 -0
  30. imas/backends/netcdf/nc_validate.py +53 -0
  31. imas/command/cli.py +239 -0
  32. imas/command/db_analysis.py +290 -0
  33. imas/command/helpers.py +45 -0
  34. imas/command/timer.py +77 -0
  35. imas/db_entry.py +802 -0
  36. imas/dd_helpers.py +300 -0
  37. imas/dd_zip.py +297 -0
  38. imas/exception.py +107 -0
  39. imas/ids_base.py +149 -0
  40. imas/ids_convert.py +920 -0
  41. imas/ids_coordinates.py +400 -0
  42. imas/ids_data_type.py +115 -0
  43. imas/ids_defs.py +171 -0
  44. imas/ids_factory.py +108 -0
  45. imas/ids_identifiers.py +125 -0
  46. imas/ids_metadata.py +307 -0
  47. imas/ids_path.py +254 -0
  48. imas/ids_primitive.py +510 -0
  49. imas/ids_struct_array.py +233 -0
  50. imas/ids_structure.py +331 -0
  51. imas/ids_toplevel.py +381 -0
  52. imas/setup_logging.py +64 -0
  53. imas/test/test_all_dd_versions.py +28 -0
  54. imas/test/test_cli.py +44 -0
  55. imas/test/test_dbentry.py +104 -0
  56. imas/test/test_dd_helpers.py +134 -0
  57. imas/test/test_dd_zip.py +29 -0
  58. imas/test/test_deepcopy.py +25 -0
  59. imas/test/test_exception.py +12 -0
  60. imas/test/test_get_sample.py +388 -0
  61. imas/test/test_hash.py +188 -0
  62. imas/test/test_helpers.py +354 -0
  63. imas/test/test_identifiers.py +105 -0
  64. imas/test/test_ids2nc.py +166 -0
  65. imas/test/test_ids_ascii_data.py +39 -0
  66. imas/test/test_ids_convert.py +415 -0
  67. imas/test/test_ids_coordinates.py +165 -0
  68. imas/test/test_ids_data_type.py +40 -0
  69. imas/test/test_ids_factory.py +35 -0
  70. imas/test/test_ids_metadata.py +66 -0
  71. imas/test/test_ids_mixin.py +20 -0
  72. imas/test/test_ids_path.py +192 -0
  73. imas/test/test_ids_primitive.py +109 -0
  74. imas/test/test_ids_struct_array.py +89 -0
  75. imas/test/test_ids_structure.py +70 -0
  76. imas/test/test_ids_toplevel.py +55 -0
  77. imas/test/test_ids_validate.py +343 -0
  78. imas/test/test_latest_dd_autofill.py +74 -0
  79. imas/test/test_latest_dd_manual.py +38 -0
  80. imas/test/test_latest_dd_resample.py +102 -0
  81. imas/test/test_lazy_loading.py +201 -0
  82. imas/test/test_list_occurrences.py +82 -0
  83. imas/test/test_minimal.py +37 -0
  84. imas/test/test_minimal_conversion.py +44 -0
  85. imas/test/test_minimal_io.py +21 -0
  86. imas/test/test_minimal_struct_array_io.py +60 -0
  87. imas/test/test_minimal_types.py +436 -0
  88. imas/test/test_minimal_types_io.py +70 -0
  89. imas/test/test_minimal_types_io_automatic.py +66 -0
  90. imas/test/test_nbc_change.py +408 -0
  91. imas/test/test_nc_autofill.py +13 -0
  92. imas/test/test_nc_entry.py +28 -0
  93. imas/test/test_nc_metadata.py +153 -0
  94. imas/test/test_nc_validation.py +197 -0
  95. imas/test/test_snippets.py +25 -0
  96. imas/test/test_static_ids.py +39 -0
  97. imas/test/test_str_1d.py +99 -0
  98. imas/test/test_time_slicing.py +182 -0
  99. imas/test/test_uda_support.py +12 -0
  100. imas/test/test_util.py +209 -0
  101. imas/training.py +45 -0
  102. imas/util.py +526 -0
  103. imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
  104. imas_python-0.1.dev1618.dist-info/METADATA +288 -0
  105. imas_python-0.1.dev1618.dist-info/RECORD +108 -0
  106. imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
  107. imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
  108. imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
@@ -0,0 +1,405 @@
1
+ """DBEntry implementation using imas_core as a backend."""
2
+
3
+ import gc
4
+ import getpass
5
+ import logging
6
+ import os
7
+ from collections import deque
8
+ from typing import Any, Deque, List, Optional, Union
9
+ from urllib.parse import urlparse
10
+
11
+ from imas.backends.db_entry_impl import GetSampleParameters, GetSliceParameters
12
+ from imas.db_entry import DBEntryImpl
13
+ from imas.exception import DataEntryException, LowlevelError
14
+ from imas.ids_convert import NBCPathMap, dd_version_map_from_factories
15
+ from imas.ids_defs import (
16
+ ASCII_BACKEND,
17
+ CHAR_DATA,
18
+ CLOSE_PULSE,
19
+ CREATE_PULSE,
20
+ ERASE_PULSE,
21
+ FORCE_CREATE_PULSE,
22
+ FORCE_OPEN_PULSE,
23
+ HDF5_BACKEND,
24
+ IDS_TIME_MODE_UNKNOWN,
25
+ IDS_TIME_MODES,
26
+ INTEGER_DATA,
27
+ MDSPLUS_BACKEND,
28
+ MEMORY_BACKEND,
29
+ OPEN_PULSE,
30
+ READ_OP,
31
+ UDA_BACKEND,
32
+ UNDEFINED_INTERP,
33
+ UNDEFINED_TIME,
34
+ WRITE_OP,
35
+ )
36
+ from imas.ids_factory import IDSFactory
37
+ from imas.ids_metadata import IDSType
38
+ from imas.ids_toplevel import IDSToplevel
39
+
40
+ from .al_context import ALContext, LazyALContext
41
+ from .db_entry_helpers import delete_children, get_children, put_children
42
+ from .imas_interface import LLInterfaceError, has_imas, ll_interface
43
+ from .mdsplus_model import ensure_data_dir, mdsplus_model_dir
44
+ from .uda_support import extract_idsdef, get_dd_version_from_idsdef_xml
45
+
46
+ _BACKEND_NAME = {
47
+ ASCII_BACKEND: "ascii",
48
+ HDF5_BACKEND: "hdf5",
49
+ MEMORY_BACKEND: "memory",
50
+ MDSPLUS_BACKEND: "mdsplus",
51
+ UDA_BACKEND: "uda",
52
+ }
53
+ _OPEN_MODES = {
54
+ "r": OPEN_PULSE,
55
+ "a": FORCE_OPEN_PULSE,
56
+ "w": FORCE_CREATE_PULSE,
57
+ "x": CREATE_PULSE,
58
+ }
59
+
60
+ logger = logging.getLogger(__name__)
61
+
62
+
63
+ def require_imas_available():
64
+ if not has_imas:
65
+ raise RuntimeError(
66
+ "The IMAS Core library is not available. Please install 'imas_core', "
67
+ "or load a supported IMAS module if you use an HPC environment."
68
+ )
69
+
70
+
71
+ class ALDBEntryImpl(DBEntryImpl):
72
+ """DBEntry implementation using imas_core as a backend."""
73
+
74
+ """Map to the expected open_pulse (AL4) / begin_dataentry_action (AL5) argument."""
75
+
76
+ def __init__(self, backend: str, ctx: ALContext, factory: IDSFactory):
77
+ self.backend = backend
78
+ self._db_ctx = ctx
79
+ self._ids_factory = factory
80
+ self._lazy_ctx_cache: Deque[ALContext] = deque()
81
+
82
+ @classmethod
83
+ def from_uri(cls, uri: str, mode: str, factory: IDSFactory) -> "ALDBEntryImpl":
84
+ require_imas_available()
85
+ if mode not in _OPEN_MODES:
86
+ modes = list(_OPEN_MODES)
87
+ raise ValueError(f"Unknown mode {mode!r}, was expecting any of {modes}")
88
+ return cls._from_uri(uri, _OPEN_MODES[mode], factory)
89
+
90
+ @classmethod
91
+ def from_pulse_run(
92
+ cls,
93
+ backend_id: int,
94
+ db_name: str,
95
+ pulse: int,
96
+ run: int,
97
+ user_name: Optional[str],
98
+ data_version: Optional[str],
99
+ mode: int,
100
+ options: Any,
101
+ factory: IDSFactory,
102
+ ) -> "ALDBEntryImpl":
103
+ # Raise an error if imas is not available
104
+ require_imas_available()
105
+
106
+ # Set defaults
107
+ user_name = user_name or getpass.getuser()
108
+ data_version = data_version or factory.dd_version
109
+ options = options if options else ""
110
+
111
+ if ll_interface._al_version.major >= 5:
112
+ # We need a URI for AL 5 or later, construct from legacy parameters
113
+ status, uri = ll_interface.build_uri_from_legacy_parameters(
114
+ backend_id, pulse, run, user_name, db_name, data_version, options
115
+ )
116
+ if status != 0:
117
+ raise LowlevelError("build URI from legacy parameters", status)
118
+
119
+ return cls._from_uri(uri, mode, factory)
120
+
121
+ else:
122
+ # AL4 legacy support:
123
+ backend = _BACKEND_NAME.get(backend_id, "")
124
+ cls._setup_backend(backend, mode, factory, user_name, db_name, run)
125
+
126
+ status, ctx = ll_interface.begin_pulse_action(
127
+ backend_id, pulse, run, user_name, db_name, data_version
128
+ )
129
+ if status != 0:
130
+ raise LowlevelError("begin pulse action", status)
131
+
132
+ status = ll_interface.open_pulse(ctx, mode, options)
133
+ if status != 0:
134
+ raise LowlevelError("opening/creating data entry", status)
135
+
136
+ return cls(backend, ALContext(ctx), factory)
137
+
138
+ @classmethod
139
+ def _from_uri(cls, uri: str, mode: int, factory: IDSFactory) -> "ALDBEntryImpl":
140
+ """Helper method to actually open/create the dataentry."""
141
+ backend = urlparse(uri).path.lower().lstrip("/")
142
+ cls._setup_backend(backend, mode, factory)
143
+
144
+ status, ctx = ll_interface.begin_dataentry_action(uri, mode)
145
+ if status != 0:
146
+ raise LowlevelError("opening/creating data entry", status)
147
+
148
+ return cls(backend, ALContext(ctx), factory)
149
+
150
+ @classmethod
151
+ def _setup_backend(
152
+ cls,
153
+ backend: str,
154
+ mode: int,
155
+ factory: IDSFactory,
156
+ user_name: str = "",
157
+ db_name="",
158
+ run=1,
159
+ ) -> None:
160
+ """Custom logic for preparing some backends.
161
+
162
+ Note: user_name, db_name and run are only used for AL 4.x, they can be
163
+ omitted when using AL 5 or later.
164
+ """
165
+ if backend == "mdsplus":
166
+ # MDSplus models:
167
+ if mode != OPEN_PULSE:
168
+ # Building the MDS+ models is required when creating a new Data Entry
169
+ ids_path = mdsplus_model_dir(factory)
170
+ if ids_path:
171
+ os.environ["ids_path"] = ids_path
172
+
173
+ if ll_interface._al_version.major == 4:
174
+ # Ensure the data directory exists
175
+ # Note: MDSPLUS model directory only uses the major version component of
176
+ # IMAS_VERSION, so we'll take the first character of IMAS_VERSION:
177
+ version = factory.version[0]
178
+ ensure_data_dir(user_name, db_name, version, run)
179
+
180
+ elif backend == "hdf5":
181
+ pass # nothing to set up
182
+
183
+ elif backend == "memory":
184
+ pass # nothing to set up
185
+
186
+ elif backend == "ascii":
187
+ pass # nothing to set up
188
+
189
+ elif backend == "uda":
190
+ # Set IDSDEF_PATH to point the UDA backend to the selected DD version
191
+ idsdef_path = None
192
+
193
+ if factory._xml_path is not None:
194
+ # Factory was constructed with an explicit XML path, point UDA to that:
195
+ idsdef_path = factory._xml_path
196
+
197
+ elif "IMAS_PREFIX" in os.environ:
198
+ # Check if UDA can use the IDSDef.xml stored in $IMAS_PREFIX/include/
199
+ idsdef_path = os.environ["IMAS_PREFIX"] + "/include/IDSDef.xml"
200
+ if get_dd_version_from_idsdef_xml(idsdef_path) != factory.version:
201
+ idsdef_path = None
202
+
203
+ if idsdef_path is None:
204
+ # Extract XML from the DD zip and point UDA to it
205
+ idsdef_path = extract_idsdef(factory.version)
206
+
207
+ os.environ["IDSDEF_PATH"] = idsdef_path
208
+ logger.warning(
209
+ "The UDA backend is not tested with "
210
+ "imas-python and may not work properly. "
211
+ "Please raise any issues you find."
212
+ )
213
+
214
+ elif backend == "flexbuffers":
215
+ pass # nothing to set up
216
+
217
+ else:
218
+ logger.warning("Backend %s is unknown to imas-python", backend)
219
+
220
+ def close(self, *, erase: bool = False) -> None:
221
+ if self._db_ctx is None:
222
+ return
223
+
224
+ self._clear_lazy_ctx_cache()
225
+
226
+ mode = ERASE_PULSE if erase else CLOSE_PULSE
227
+ status = ll_interface.close_pulse(self._db_ctx.ctx, mode)
228
+ if status != 0:
229
+ raise LowlevelError("close data entry", status)
230
+
231
+ ll_interface.end_action(self._db_ctx.ctx)
232
+ self._db_ctx = None
233
+
234
+ def _clear_lazy_ctx_cache(self) -> None:
235
+ """Close any cached lazy contexts"""
236
+ while self._lazy_ctx_cache:
237
+ self._lazy_ctx_cache.pop().close()
238
+
239
+ def get(
240
+ self,
241
+ ids_name: str,
242
+ occurrence: int,
243
+ parameters: Union[None, GetSliceParameters, GetSampleParameters],
244
+ destination: IDSToplevel,
245
+ lazy: bool,
246
+ nbc_map: Optional[NBCPathMap],
247
+ ) -> None:
248
+ if self._db_ctx is None:
249
+ raise RuntimeError("Database entry is not open.")
250
+ if lazy and self.backend == "ascii":
251
+ raise RuntimeError("Lazy loading is not supported by the ASCII backend.")
252
+
253
+ # Mixing contexts can be problematic, ensure all lazy contexts are closed:
254
+ self._clear_lazy_ctx_cache()
255
+
256
+ ll_path = ids_name
257
+ if occurrence != 0:
258
+ ll_path += f"/{occurrence}"
259
+
260
+ with self._db_ctx.global_action(ll_path, READ_OP) as read_ctx:
261
+ time_mode_path = "ids_properties/homogeneous_time"
262
+ time_mode = read_ctx.read_data(time_mode_path, "", INTEGER_DATA, 0)
263
+ # This is already checked by read_dd_version, but ensure:
264
+ assert time_mode in IDS_TIME_MODES
265
+
266
+ if lazy:
267
+ context = LazyALContext(dbentry=self, nbc_map=nbc_map, time_mode=time_mode)
268
+ else:
269
+ context = self._db_ctx
270
+ # Now fill the IDSToplevel
271
+ if parameters is None or destination.metadata.type is IDSType.CONSTANT:
272
+ # called from get(), or when the IDS is constant (see IMAS-3330)
273
+ manager = context.global_action(ll_path, READ_OP)
274
+ elif isinstance(parameters, GetSliceParameters):
275
+ manager = context.slice_action(
276
+ ll_path,
277
+ READ_OP,
278
+ parameters.time_requested,
279
+ parameters.interpolation_method,
280
+ )
281
+ elif isinstance(parameters, GetSampleParameters):
282
+ manager = context.timerange_action(
283
+ ll_path,
284
+ READ_OP,
285
+ parameters.tmin,
286
+ parameters.tmax,
287
+ parameters.dtime,
288
+ parameters.interpolation_method,
289
+ )
290
+ else:
291
+ raise TypeError(f"Incorrect type for parameters: {type(parameters)}.")
292
+
293
+ with manager as read_ctx:
294
+ if lazy:
295
+ destination._set_lazy_context(read_ctx)
296
+ else:
297
+ # Get may create LOTS of new objects. Temporarily disable Python's
298
+ # garbage collector to speed up the get:
299
+ gc_enabled = gc.isenabled()
300
+ gc.disable()
301
+ get_children(destination, read_ctx, time_mode, nbc_map)
302
+ if gc_enabled:
303
+ gc.enable()
304
+
305
+ return destination
306
+
307
+ def read_dd_version(self, ids_name: str, occurrence: int) -> str:
308
+ if self._db_ctx is None:
309
+ raise RuntimeError("Database entry is not open.")
310
+ # Mixing contexts can be problematic, ensure all lazy contexts are closed:
311
+ self._clear_lazy_ctx_cache()
312
+
313
+ ll_path = ids_name
314
+ if occurrence != 0:
315
+ ll_path += f"/{occurrence}"
316
+
317
+ with self._db_ctx.global_action(ll_path, READ_OP) as read_ctx:
318
+ time_mode_path = "ids_properties/homogeneous_time"
319
+ time_mode = read_ctx.read_data(time_mode_path, "", INTEGER_DATA, 0)
320
+ dd_version_path = "ids_properties/version_put/data_dictionary"
321
+ dd_version = read_ctx.read_data(dd_version_path, "", CHAR_DATA, 1)
322
+
323
+ if time_mode not in IDS_TIME_MODES:
324
+ raise DataEntryException(
325
+ f"IDS {ids_name!r}, occurrence {occurrence} is empty."
326
+ )
327
+ return dd_version
328
+
329
+ def put(self, ids: IDSToplevel, occurrence: int, is_slice: bool) -> None:
330
+ if self._db_ctx is None:
331
+ raise RuntimeError("Database entry is not open.")
332
+
333
+ # Mixing contexts can be problematic, ensure all lazy contexts are closed:
334
+ self._clear_lazy_ctx_cache()
335
+
336
+ ids_name = ids.metadata.name
337
+ # Create a version conversion map, if needed
338
+ nbc_map = None
339
+ if ids._version != self._ids_factory._version:
340
+ ddmap, source_is_older = dd_version_map_from_factories(
341
+ ids_name, ids._parent, self._ids_factory
342
+ )
343
+ nbc_map = ddmap.old_to_new if source_is_older else ddmap.new_to_old
344
+
345
+ ll_path = ids_name
346
+ if occurrence != 0:
347
+ ll_path += f"/{occurrence}"
348
+
349
+ time_mode = ids.ids_properties.homogeneous_time
350
+ if is_slice:
351
+ with self._db_ctx.global_action(ll_path, READ_OP) as read_ctx:
352
+ db_time_mode = read_ctx.read_data(
353
+ "ids_properties/homogeneous_time", "", INTEGER_DATA, 0
354
+ )
355
+ if db_time_mode == IDS_TIME_MODE_UNKNOWN:
356
+ # No data yet on disk, so just put everything
357
+ is_slice = False
358
+ elif db_time_mode != time_mode:
359
+ raise DataEntryException(
360
+ f"Cannot change homogeneous_time from {db_time_mode} to {time_mode}"
361
+ )
362
+
363
+ if not is_slice:
364
+ # put() must first delete any existing data
365
+ with self._db_ctx.global_action(ll_path, WRITE_OP) as write_ctx:
366
+ # New IDS to ensure all fields in "our" DD version are deleted
367
+ # If ids is in another version, we might not erase all fields
368
+ delete_children(self._ids_factory.new(ids_name).metadata, write_ctx)
369
+
370
+ if is_slice:
371
+ manager = self._db_ctx.slice_action(
372
+ ll_path, WRITE_OP, UNDEFINED_TIME, UNDEFINED_INTERP
373
+ )
374
+ else:
375
+ manager = self._db_ctx.global_action(ll_path, WRITE_OP)
376
+ verify_maxoccur = self.backend == "mdsplus"
377
+ with manager as write_ctx:
378
+ put_children(ids, write_ctx, time_mode, is_slice, nbc_map, verify_maxoccur)
379
+
380
+ def access_layer_version(self) -> str:
381
+ return ll_interface._al_version_str
382
+
383
+ def delete_data(self, ids_name: str, occurrence: int) -> None:
384
+ if self._db_ctx is None:
385
+ raise RuntimeError("Database entry is not open.")
386
+ # Mixing contexts can be problematic, ensure all lazy contexts are closed:
387
+ self._clear_lazy_ctx_cache()
388
+
389
+ ll_path = ids_name
390
+ if occurrence != 0:
391
+ ll_path += f"/{occurrence}"
392
+ ids = self._ids_factory.new(ids_name)
393
+ with self._db_ctx.global_action(ll_path, WRITE_OP) as write_ctx:
394
+ delete_children(ids.metadata, write_ctx, "")
395
+
396
+ def list_all_occurrences(self, ids_name: str) -> List[int]:
397
+ try:
398
+ occurrence_list = self._db_ctx.list_all_occurrences(ids_name)
399
+ except LLInterfaceError:
400
+ # al_get_occurrences is not available in the lowlevel
401
+ raise RuntimeError(
402
+ "list_all_occurrences is not available. "
403
+ "Access Layer 5.1 or newer is required."
404
+ ) from None
405
+ return occurrence_list
@@ -0,0 +1,200 @@
1
+ # This file is part of imas-python.
2
+ # You should have received the imas-python LICENSE file with this project.
3
+ """Helper methods for loading data from and storing data to Data Entries.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ import numpy as np
9
+
10
+ from imas.ids_base import IDSBase
11
+ from imas.ids_convert import NBCPathMap
12
+ from imas.ids_data_type import IDSDataType
13
+ from imas.ids_defs import IDS_TIME_MODE_HOMOGENEOUS, IDS_TIME_MODE_INDEPENDENT
14
+ from imas.ids_metadata import IDSMetadata
15
+ from imas.ids_struct_array import IDSStructArray
16
+ from imas.ids_structure import IDSStructure
17
+
18
+ from .al_context import ALContext, LazyALContext
19
+
20
+
21
+ def get_children(
22
+ structure: IDSStructure,
23
+ ctx: ALContext,
24
+ time_mode: int,
25
+ nbc_map: Optional[NBCPathMap],
26
+ ) -> None:
27
+ """Recursively get all children of an IDSStructure."""
28
+ # NOTE: changes in this method must be propagated to _get_child and vice versa
29
+ # Performance: this method is specialized for the non-lazy get
30
+
31
+ for name, child_meta in structure._children.items():
32
+ if time_mode == IDS_TIME_MODE_INDEPENDENT and child_meta.type.is_dynamic:
33
+ continue # skip dynamic (time-dependent) nodes
34
+
35
+ path = child_meta.path_string
36
+ data_type = child_meta.data_type
37
+
38
+ if nbc_map and path in nbc_map:
39
+ if nbc_map.path[path] is None:
40
+ continue # element does not exist in the on-disk DD version
41
+ new_path = nbc_map.ctxpath[path]
42
+ timebase = nbc_map.tbp[path]
43
+ elif nbc_map and path in nbc_map.type_change:
44
+ continue # we don't handle type changes when converting implicitly
45
+ else:
46
+ new_path = child_meta._ctx_path
47
+ timebase = child_meta.timebasepath
48
+
49
+ # Override time base for homogeneous time
50
+ if timebase and time_mode == IDS_TIME_MODE_HOMOGENEOUS:
51
+ timebase = "/time"
52
+
53
+ if data_type is IDSDataType.STRUCT_ARRAY:
54
+ # Regular get/get_slice:
55
+ with ctx.arraystruct_action(new_path, timebase, 0) as (new_ctx, size):
56
+ if size > 0:
57
+ element = getattr(structure, name)
58
+ element.resize(size)
59
+ for item in element:
60
+ get_children(item, new_ctx, time_mode, nbc_map)
61
+ new_ctx.iterate_over_arraystruct(1)
62
+
63
+ elif data_type is IDSDataType.STRUCTURE:
64
+ element = getattr(structure, name)
65
+ get_children(element, ctx, time_mode, nbc_map)
66
+
67
+ else: # Data elements
68
+ ndim = child_meta._al_ndim
69
+ data = ctx.read_data(new_path, timebase, data_type.al_type, ndim)
70
+ if not (
71
+ # Empty arrays and STR_1D
72
+ data is None
73
+ # EMPTY_INT, EMPTY_FLOAT, EMPTY_COMPLEX, empty string
74
+ or (child_meta.ndim == 0 and data == data_type.default)
75
+ ):
76
+ # NOTE: bypassing IDSPrimitive.value.setter logic
77
+ getattr(structure, name)._IDSPrimitive__value = data
78
+
79
+
80
+ def _get_child(child: IDSBase, ctx: Optional[LazyALContext]):
81
+ """Get a single child when required (lazy loading)."""
82
+ # NOTE: changes in this method must be propagated to _get_children and vice versa
83
+ # Performance: this method is specialized for the lazy get
84
+
85
+ # ctx can be None when the parent structure does not exist in the on-disk DD version
86
+ if ctx is None:
87
+ return # There is no data to be loaded
88
+
89
+ time_mode = ctx.time_mode
90
+ if time_mode == IDS_TIME_MODE_INDEPENDENT and child.metadata.type.is_dynamic:
91
+ return # skip dynamic (time-dependent) nodes
92
+
93
+ child_meta = child.metadata
94
+ path = child_meta.path_string
95
+ data_type = child_meta.data_type
96
+ nbc_map = ctx.nbc_map
97
+
98
+ if nbc_map and path in nbc_map:
99
+ if nbc_map.path[path] is None:
100
+ return # element does not exist in the on-disk DD version
101
+ new_path = nbc_map.ctxpath[path]
102
+ timebase = nbc_map.tbp[path]
103
+ elif nbc_map and path in nbc_map.type_change:
104
+ return # we don't handle type changes when converting implicitly
105
+ else:
106
+ new_path = child_meta._ctx_path
107
+ timebase = child_meta.timebasepath
108
+
109
+ # Override time base for homogeneous time
110
+ if timebase and time_mode == IDS_TIME_MODE_HOMOGENEOUS:
111
+ timebase = "/time"
112
+
113
+ if data_type is IDSDataType.STRUCT_ARRAY:
114
+ aos_ctx = ctx.arraystruct_action(new_path, timebase, 0)
115
+ child._set_lazy_context(aos_ctx)
116
+
117
+ elif data_type is IDSDataType.STRUCTURE:
118
+ child._set_lazy_context(ctx)
119
+
120
+ else: # Data elements
121
+ ndim = child_meta._al_ndim
122
+ data = ctx.get_context().read_data(new_path, timebase, data_type.al_type, ndim)
123
+ if not (
124
+ # Empty arrays and STR_1D
125
+ data is None
126
+ # EMPTY_INT, EMPTY_FLOAT, EMPTY_COMPLEX, empty string
127
+ or (child_meta.ndim == 0 and data == data_type.default)
128
+ ):
129
+ if isinstance(data, np.ndarray):
130
+ # Convert the numpy array to a read-only view
131
+ data = data.view()
132
+ data.flags.writeable = False
133
+ # NOTE: bypassing IDSPrimitive.value.setter logic
134
+ child._IDSPrimitive__value = data
135
+
136
+
137
+ def delete_children(structure: IDSMetadata, ctx: ALContext) -> None:
138
+ """Recursively delete all children of an IDSStructure"""
139
+ for child_meta in structure._children.values():
140
+ if child_meta.data_type is IDSDataType.STRUCTURE:
141
+ delete_children(child_meta, ctx)
142
+ else:
143
+ ctx.delete_data(child_meta._ctx_path)
144
+
145
+
146
+ def put_children(
147
+ structure: IDSStructure,
148
+ ctx: ALContext,
149
+ time_mode: int,
150
+ is_slice: bool,
151
+ nbc_map: Optional[NBCPathMap],
152
+ verify_maxoccur: bool,
153
+ ) -> None:
154
+ """Recursively put all children of an IDSStructure"""
155
+ # Note: when putting a slice, we do not need to descend into IDSStructure and
156
+ # IDSStructArray elements if they don't contain dynamic data nodes. That is hard to
157
+ # detect now, so we just recurse and check the data elements
158
+ for element in structure.iter_nonempty_():
159
+ if time_mode == IDS_TIME_MODE_INDEPENDENT and element.metadata.type.is_dynamic:
160
+ continue # skip dynamic data when in time independent mode
161
+
162
+ path = element.metadata.path_string
163
+ if nbc_map and path in nbc_map:
164
+ if nbc_map.path[path] is None:
165
+ continue # element does not exist in the on-disk DD version
166
+ new_path = nbc_map.ctxpath[path]
167
+ timebase = nbc_map.tbp[path]
168
+ elif nbc_map and path in nbc_map.type_change:
169
+ continue # we don't handle type changes when converting implicitly
170
+ else:
171
+ new_path = element.metadata._ctx_path
172
+ timebase = element.metadata.timebasepath
173
+
174
+ # Override time base for homogeneous time
175
+ if timebase and time_mode == IDS_TIME_MODE_HOMOGENEOUS:
176
+ timebase = "/time"
177
+
178
+ if isinstance(element, IDSStructArray):
179
+ size = len(element)
180
+ if verify_maxoccur:
181
+ maxoccur = element.metadata.maxoccur
182
+ if maxoccur and size > maxoccur:
183
+ raise RuntimeError(
184
+ f"Exceeding maximum number of occurrences ({maxoccur}) "
185
+ f"of {element._path}"
186
+ )
187
+ with ctx.arraystruct_action(new_path, timebase, size) as (new_ctx, _):
188
+ for item in element:
189
+ put_children(
190
+ item, new_ctx, time_mode, is_slice, nbc_map, verify_maxoccur
191
+ )
192
+ new_ctx.iterate_over_arraystruct(1)
193
+
194
+ elif isinstance(element, IDSStructure):
195
+ put_children(element, ctx, time_mode, is_slice, nbc_map, verify_maxoccur)
196
+
197
+ else: # Data elements
198
+ if is_slice and not element.metadata.type.is_dynamic:
199
+ continue # put_slice only stores dynamic data
200
+ ctx.write_data(new_path, timebase, element.value)